فهرست منبع

专家库样式调整

yanghao 6 روز پیش
والد
کامیت
7eb95457d8
2فایلهای تغییر یافته به همراه33 افزوده شده و 1981 حذف شده
  1. 33 10
      src/views/expert/list/index.vue
  2. 0 1971
      src/views/expert/list/index2.vue

+ 33 - 10
src/views/expert/list/index.vue

@@ -136,7 +136,10 @@
     >
     >
       <div class="team-cell">
       <div class="team-cell">
         <span class="team-index">0{{ rowIndex + 1 }}</span>
         <span class="team-index">0{{ rowIndex + 1 }}</span>
-        <span class="team-name">{{ row.teamName }}</span>
+        <div class="team-details">
+          <span class="team-name">{{ row.teamName }}</span>
+          <span class="team-count">{{ row.memberCount }}人</span>
+        </div>
       </div>
       </div>
 
 
       <table class="content-table" :aria-label="row.teamName">
       <table class="content-table" :aria-label="row.teamName">
@@ -751,6 +754,7 @@ type CellItem =
 
 
 type TableRow = {
 type TableRow = {
   teamName: string
   teamName: string
+  memberCount: number
   education: string
   education: string
   searchText: string
   searchText: string
   bookmarks: string[]
   bookmarks: string[]
@@ -1024,6 +1028,7 @@ const tableData = computed<TableRow[]>(() => {
 
 
     return {
     return {
       teamName: group.echelonLabel,
       teamName: group.echelonLabel,
+      memberCount: group.experts.length,
       education: educationLabels.join('、') || '-',
       education: educationLabels.join('、') || '-',
       searchText,
       searchText,
       bookmarks,
       bookmarks,
@@ -1564,16 +1569,20 @@ onMounted(async () => {
   box-shadow: 0 0 0 1px #234395 inset;
   box-shadow: 0 0 0 1px #234395 inset;
 }
 }
 
 
-.filter-fields :deep(.el-input__inner),
-.filter-fields :deep(.el-select__placeholder),
-.filter-fields :deep(.el-select__selected-item) {
-  color: #365379;
-  font-size: 16px;
-}
+// .filter-fields :deep(.el-input__inner) {
+//   color: #365379;
+//   font-size: 16px;
+// }
 
 
-.filter-fields :deep(.el-input__inner::placeholder) {
-  color: #9aabc2;
-}
+// .filter-fields :deep(.el-select__selected-item) {
+//   color: #000 !important;
+//   font-size: 16px;
+// }
+
+// .filter-fields :deep(.el-select__placeholder) {
+//   color: #ccc !important;
+//   font-size: 16px;
+// }
 
 
 .filter-fields :deep(.el-input__prefix) {
 .filter-fields :deep(.el-input__prefix) {
   color: #91a5c0;
   color: #91a5c0;
@@ -1888,6 +1897,20 @@ onMounted(async () => {
   white-space: nowrap;
   white-space: nowrap;
 }
 }
 
 
+.team-details {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  min-width: 0;
+}
+
+.team-count {
+  color: rgba(24, 37, 54, 0.58);
+  font-size: 11px;
+  font-weight: 500;
+  white-space: nowrap;
+}
+
 .content-table {
 .content-table {
   flex: 1 1 auto;
   flex: 1 1 auto;
   width: 0;
   width: 0;

+ 0 - 1971
src/views/expert/list/index2.vue

@@ -1,1971 +0,0 @@
-<template>
-  <div class="expert-step-table">
-    <el-card>
-      <div class="search-layout">
-        <section class="filter-panel">
-          <div class="filter-fields">
-            <el-input v-model="queryParams.expertName" placeholder="姓名 / 专业 / 业绩" clearable />
-
-            <el-select v-model="queryParams.education" placeholder="请选择专家学历" clearable>
-              <el-option
-                v-for="dict in getStrDictOptions(DICT_TYPE.RD_EXPERT_EDUCATION)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-
-            <el-select v-model="queryParams.echelon" placeholder="请选择专家梯队" clearable>
-              <el-option
-                v-for="dict in getStrDictOptions(DICT_TYPE.RD_EXPERT_ECHELON)"
-                :key="dict.value"
-                :label="dict.label"
-                :value="dict.value"
-              />
-            </el-select>
-
-            <div class="flex">
-              <el-button type="primary" @click="handleAdd"
-                ><Icon icon="ep:plus" class="mr-5px" />新增</el-button
-              >
-
-              <el-button type="primary" @click="getList"
-                ><Icon icon="ep:search" class="mr-5px" />搜索</el-button
-              >
-
-              <el-button type="primary" @click="handleReset"
-                ><Icon icon="ep:refresh" class="mr-5px" />重置</el-button
-              >
-            </div>
-          </div>
-        </section>
-
-        <aside class="word-cloud-panel">
-          <div class="word-cloud">
-            <button
-              v-for="word in wordCloudWords"
-              :key="word.text"
-              type="button"
-              class="cloud-word"
-              :style="{
-                left: word.left,
-                top: word.top,
-                '--rotate': `${word.rotate}deg`,
-                fontSize: `${word.size}px`,
-                color: word.color,
-                transform: `rotate(${word.rotate}deg)`
-              }"
-              @click="handleWordClick(word.text)"
-            >
-              {{ word.text }}
-            </button>
-          </div>
-        </aside>
-      </div>
-
-      <div
-        v-for="(row, rowIndex) in filteredTableData"
-        :key="row.teamName"
-        class="ladder-row"
-        :style="{ width: row.tableWidth, '--row-bg': row.backgroundColor }"
-      >
-        <div class="team-cell">
-          <span class="team-index">0{{ rowIndex + 1 }}</span>
-          <span class="team-name">{{ row.teamName }}</span>
-        </div>
-
-        <table class="content-table" :aria-label="row.teamName">
-          <colgroup>
-            <col
-              v-for="(_, index) in row.columnWidths || []"
-              :key="index"
-              :style="{ width: `${getColumnPercent(row, index)}%` }"
-            />
-          </colgroup>
-
-          <tbody>
-            <tr>
-              <td
-                v-for="(cell, index) in row.cells || []"
-                :key="index"
-                :class="['step-cell', cell.tone === 'danger' ? 'notice-cell' : '']"
-              >
-                <span class="cell-bookmark">{{ row.bookmarks[index] }}</span>
-
-                <template v-if="cell.type === 'members'">
-                  <button
-                    v-for="member in cell.members"
-                    :key="member.id"
-                    type="button"
-                    class="member-link"
-                    @click="handleMemberClick(member)"
-                  >
-                    {{ member.expertName }}
-                  </button>
-                </template>
-
-                <span v-else :class="['cell-text', cell.tone === 'danger' ? 'is-danger' : '']">
-                  {{ cell.text }}
-                </span>
-              </td>
-            </tr>
-          </tbody>
-        </table>
-      </div>
-    </el-card>
-
-    <el-drawer
-      v-model="drawerVisible"
-      size="56%"
-      :with-header="false"
-      class="expert-drawer"
-      destroy-on-close
-    >
-      <template v-if="activeExpert">
-        <div class="drawer-shell" v-loading="drawerLoading">
-          <header class="drawer-header">
-            <div>
-              <h2>{{ activeExpert.name }} - 专家详情</h2>
-              <p>
-                {{ activeExpert.role }} / {{ activeExpert.team }} /
-                {{ activeExpert.direction.join('、') }}
-              </p>
-            </div>
-          </header>
-
-          <section class="drawer-profile">
-            <div class="profile-aside">
-              <div class="profile-avatar">
-                <img :src="activeExpert.avatar" :alt="activeExpert.name" />
-                <!-- <span v-else>{{ activeExpert.name.slice(0, 1) }}</span> -->
-              </div>
-              <div class="profile-tags">
-                <span v-for="tag in activeExpert.tags" :key="tag" class="profile-tag">{{
-                  tag
-                }}</span>
-              </div>
-            </div>
-
-            <div class="profile-main">
-              <div class="info-block">
-                <span class="info-label">专家序列 / 梯队 / 学历</span>
-                <strong
-                  >{{ activeExpert.role }} / {{ activeExpert.team }} /
-                  {{ activeExpert.education }}</strong
-                >
-              </div>
-
-              <div class="info-block">
-                <span class="info-label">专业方向</span>
-                <strong>{{ activeExpert.direction.join('、') || '-' }}</strong>
-              </div>
-
-              <div class="info-block">
-                <span class="info-label">专业软件技能</span>
-                <strong>{{ activeExpert.softwareSkills.join('、') || '-' }}</strong>
-              </div>
-
-              <div class="info-block">
-                <span class="info-label">毕业院校 / 出生日期</span>
-                <strong
-                  >{{ activeExpert.school || '-' }} / {{ activeExpert.birthdayText || '-' }}</strong
-                >
-              </div>
-
-              <div class="info-block">
-                <span class="info-label">职称 / 岗位</span>
-                <strong
-                  >{{ activeExpert.professionalTitle || '-' }} /
-                  {{ activeExpert.jobTitle || '-' }}</strong
-                >
-              </div>
-
-              <div class="info-block">
-                <span class="info-label">历任岗位</span>
-                <strong>{{ activeExpert.formerPost || '-' }}</strong>
-              </div>
-
-              <div class="info-block">
-                <span class="info-label">主要业绩</span>
-                <strong>{{ activeExpert.achievement || '-' }}</strong>
-              </div>
-
-              <div class="profile-actions">
-                <el-button type="primary" size="large" @click="handleJoinTeam">加入团队</el-button>
-                <el-button size="large" type="primary" plain @click="handleEdit"
-                  >维护档案</el-button
-                >
-                <el-button size="large" plain>下载履历</el-button>
-              </div>
-            </div>
-          </section>
-
-          <section class="record-panel">
-            <div class="record-header">
-              <h3>所在团队</h3>
-            </div>
-
-            <el-table
-              :data="currentExpertDetail?.teams || []"
-              class="record-table"
-              table-layout="fixed"
-            >
-              <el-table-column prop="teamName" label="团队名称" min-width="120" align="center" />
-              <el-table-column label="团队所属公司" min-width="120" align="center">
-                <template #default="{ row }">
-                  {{ getDeptName(row.deptId) || '-' }}
-                </template>
-              </el-table-column>
-              <el-table-column label="团队 Logo" align="center" width="120">
-                <template #default="{ row }">
-                  <el-image
-                    v-if="row.avatar"
-                    :src="row.avatar"
-                    fit="cover"
-                    class="team-logo"
-                    :preview-src-list="[row.avatar]"
-                    preview-teleported
-                  />
-                  <span v-else>-</span>
-                </template>
-              </el-table-column>
-              <el-table-column
-                prop="summary"
-                label="团队概述"
-                min-width="240"
-                align="center"
-                show-overflow-tooltip
-              />
-            </el-table>
-          </section>
-        </div>
-      </template>
-    </el-drawer>
-
-    <Dialog
-      :title="dialogTitle"
-      v-model="dialogVisible"
-      :close-on-click-modal="false"
-      :close-on-press-escape="false"
-      destroy-on-close
-      @close="closeDialog"
-      width="60%"
-    >
-      <el-form
-        ref="formRef"
-        :model="formData"
-        :rules="formRules"
-        v-loading="formLoading"
-        label-width="auto"
-      >
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="部门" prop="deptId">
-              <el-tree-select
-                clearable
-                v-model="formData.deptId"
-                :data="deptList2"
-                :props="defaultProps"
-                :check-strictly="false"
-                node-key="id"
-                filterable
-                placeholder="请选择所在部门"
-              />
-            </el-form-item>
-          </el-col>
-
-          <el-col :span="12">
-            <el-form-item label="专家姓名" prop="expertName">
-              <el-input v-model="formData.expertName" placeholder="请输入专家姓名" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="专家序列" prop="expertRank">
-              <el-select
-                multiple
-                v-model="formData.expertRank"
-                placeholder="请选择专家序列"
-                clearable
-              >
-                <el-option
-                  v-for="dict in getStrDictOptions(DICT_TYPE.RD_EXPERT_RANK)"
-                  :key="dict.value"
-                  :label="dict.label"
-                  :value="Number(dict.value)"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="专家梯队" prop="echelon">
-              <el-select v-model="formData.echelon" placeholder="请选择专家梯队" clearable>
-                <el-option
-                  v-for="dict in getStrDictOptions(DICT_TYPE.RD_EXPERT_ECHELON)"
-                  :key="dict.value"
-                  :label="dict.label"
-                  :value="dict.value"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="学历" prop="education">
-              <el-select v-model="formData.education" placeholder="请选择专家学历" clearable>
-                <el-option
-                  v-for="dict in getStrDictOptions(DICT_TYPE.RD_EXPERT_EDUCATION)"
-                  :key="dict.value"
-                  :label="dict.label"
-                  :value="dict.value"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="专业方向" prop="specialDirection">
-              <el-select
-                v-model="formData.specialDirection"
-                placeholder="请选择专业方向"
-                clearable
-                multiple
-                filterable
-              >
-                <el-option
-                  v-for="dict in getStrDictOptions(DICT_TYPE.RD_EXPERT_TECHNICAL)"
-                  :key="dict.value"
-                  :label="dict.label"
-                  :value="Number(dict.value)"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="专业软件技能" prop="softwareSkills">
-              <el-select
-                v-model="formData.softwareSkills"
-                placeholder="请选择专业软件技能"
-                clearable
-                multiple
-                filterable
-              >
-                <el-option
-                  v-for="dict in getStrDictOptions(DICT_TYPE.RD_EXPERT_SOFTWARE)"
-                  :key="dict.value"
-                  :label="dict.label"
-                  :value="Number(dict.value)"
-                />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="所属团队" prop="teamIds">
-              <div class="team-select-field">
-                <el-input :model-value="selectedTeamNames" placeholder="请选择所属团队" readonly />
-                <el-button type="primary" plain @click="openTeamDialog">选择团队</el-button>
-              </div>
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="毕业院校" prop="school">
-              <el-input
-                v-model="formData.school"
-                placeholder="请输入毕业院校"
-                style="width: 100%"
-              />
-            </el-form-item>
-          </el-col>
-          <el-col :span="12">
-            <el-form-item label="出生日期" prop="birthday">
-              <el-date-picker
-                v-model="formData.birthday"
-                type="date"
-                value-format="x"
-                placeholder="请选择出生日期"
-                style="width: 100%"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="头像" prop="avatar">
-              <UploadImg v-model="formData.avatar" height="50px" />
-            </el-form-item>
-          </el-col>
-
-          <el-col :span="12">
-            <el-form-item label="历任岗位" prop="formerPost">
-              <el-input v-model="formData.formerPost" placeholder="请输入历任岗位" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="12">
-            <el-form-item label="岗位" prop="jobTitle">
-              <el-input v-model="formData.jobTitle" placeholder="请输入岗位" />
-            </el-form-item>
-          </el-col>
-
-          <el-col :span="12">
-            <el-form-item label="职称" prop="professionalTitle">
-              <el-input v-model="formData.professionalTitle" placeholder="请输入职称" />
-            </el-form-item>
-          </el-col>
-        </el-row>
-
-        <el-row :gutter="20">
-          <el-col :span="24">
-            <el-form-item label="主要业绩" prop="achievement">
-              <el-input
-                type="textarea"
-                v-model="formData.achievement"
-                placeholder="请输入主要业绩"
-              />
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-
-      <template #footer>
-        <el-button @click="closeDialog">取 消</el-button>
-        <el-button type="primary" @click="submitExpertForm" :loading="submitLoading"
-          >确 定</el-button
-        >
-      </template>
-    </Dialog>
-
-    <Dialog v-model="teamDialogVisible" title="选择所属团队" width="70%" class="team-picker-dialog">
-      <el-form :inline="true" :model="teamQueryParams" class="-mb-15px">
-        <el-form-item label="团队名称">
-          <el-input
-            v-model="teamQueryParams.teamName"
-            placeholder="请输入团队名称"
-            clearable
-            @keyup.enter="handleTeamQuery"
-          />
-        </el-form-item>
-        <el-form-item>
-          <el-button @click="handleTeamQuery">
-            <Icon icon="ep:search" class="mr-5px" />
-            搜索
-          </el-button>
-          <el-button @click="resetTeamQuery">
-            <Icon icon="ep:refresh" class="mr-5px" />
-            重置
-          </el-button>
-        </el-form-item>
-      </el-form>
-
-      <div class="team-picker-table-wrap">
-        <el-table
-          ref="teamTableRef"
-          v-loading="teamLoading"
-          :data="teamList"
-          row-key="id"
-          height="calc(100vh - 400px)"
-          @selection-change="handleTeamSelectionChange"
-        >
-          <el-table-column type="selection" width="55" reserve-selection />
-          <el-table-column label="团队名称" prop="teamName" width="120" />
-          <el-table-column label="团队所属公司" align="center" min-width="120">
-            <template #default="{ row }">
-              {{ getDeptName(row.deptId) || '-' }}
-            </template>
-          </el-table-column>
-          <el-table-column label="团队 Logo" align="center" width="120">
-            <template #default="{ row }">
-              <el-image
-                v-if="row.avatar"
-                :src="row.avatar"
-                fit="cover"
-                class="team-logo"
-                :preview-src-list="[row.avatar]"
-                preview-teleported
-              />
-              <span v-else>-</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="团队概述" prop="summary" min-width="240" show-overflow-tooltip />
-        </el-table>
-
-        <Pagination
-          v-model:page="teamQueryParams.pageNo"
-          v-model:limit="teamQueryParams.pageSize"
-          :total="teamTotal"
-          @pagination="getTeamOptionsList"
-        />
-      </div>
-
-      <template #footer>
-        <div class="team-picker-footer">
-          <el-button @click="teamDialogVisible = false">取 消</el-button>
-          <el-button type="primary" @click="confirmTeamSelection">确 定</el-button>
-        </div>
-      </template>
-    </Dialog>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { computed, nextTick, onMounted, reactive, ref } from 'vue'
-import {
-  createExpert,
-  getExpertDetail,
-  getExpertList,
-  getTeamList,
-  updateExpert
-} from '@/api/expert'
-import * as DeptApi from '@/api/system/dept'
-import { defaultProps } from '@/utils/tree'
-import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
-import { ElTable } from 'element-plus'
-
-const defaultFormData = () => ({
-  id: undefined,
-  expertName: '',
-  achievement: '',
-  echelon: '',
-  education: '',
-  specialDirection: [],
-  softwareSkills: [],
-  teamIds: [],
-  school: '',
-  birthday: null,
-  avatar: '',
-  deptId: '',
-  formerPost: '',
-  jobTitle: '',
-  professionalTitle: '',
-  expertRank: []
-})
-
-const deptList2 = ref<Tree[]>([]) // 树形结构
-
-const formLoading = ref(false) // 表单加载中
-const submitLoading = ref(false) // 提交按钮加载中
-const teamLoading = ref(false)
-const drawerLoading = ref(false)
-
-// 对话框相关
-const dialogVisible = ref(false)
-const dialogTitle = ref('')
-const isEdit = ref(false)
-const teamDialogVisible = ref(false)
-const teamDialogMode = ref<'form' | 'drawer'>('form')
-
-// 表单相关
-const formRef = ref()
-const formData = ref(defaultFormData())
-const teamTableRef = ref<InstanceType<typeof ElTable>>()
-const teamList = ref<any[]>([])
-const teamTotal = ref(0)
-const tempSelectedTeams = ref<any[]>([])
-const selectedTeams = ref<any[]>([])
-const tempSelectedTeamMap = ref<Record<number, any>>({})
-const syncingTeamSelection = ref(false)
-const currentExpertDetail = ref<any>(null)
-
-const formRules = {
-  expertName: [{ required: true, message: '请输入专家名称', trigger: 'blur' }],
-  achievement: [{ required: true, message: '请输入成果', trigger: 'blur' }],
-  echelon: [{ required: true, message: '请选择专家层级', trigger: 'blur' }],
-  education: [{ required: true, message: '请选择专家学历', trigger: 'blur' }],
-  specialDirection: [{ required: true, message: '请选择专业方向', trigger: 'blur' }],
-  // softwareSkills: [{ required: true, message: '请选择专业软件技能', trigger: 'blur' }],
-  // teamIds: [{ required: true, message: '请选择所属团队', trigger: 'blur' }],
-  school: [{ required: true, message: '请输入毕业院校', trigger: 'blur' }],
-  birthday: [{ required: true, message: '请选择出生日期', trigger: 'blur' }],
-  avatar: [{ required: true, message: '请上传头像', trigger: 'blur' }],
-  deptId: [{ required: true, message: '请选择部门', trigger: 'blur' }],
-  // formerPost: [{ required: true, message: '请输入历任岗位', trigger: 'blur' }],
-  // jobTitle: [{ required: true, message: '请输入岗位', trigger: 'blur' }],
-  // professionalTitle: [{ required: true, message: '请输入职称', trigger: 'blur' }],
-  expertRank: [{ required: true, message: '请选择专家序列', trigger: 'blur' }]
-}
-
-// 重置表单
-const resetForm = () => {
-  formData.value = defaultFormData()
-  selectedTeams.value = []
-  tempSelectedTeams.value = []
-  tempSelectedTeamMap.value = {}
-  formRef.value?.clearValidate()
-}
-// 关闭对话框
-const closeDialog = () => {
-  dialogVisible.value = false
-  resetForm()
-}
-
-const fillTeamSelection = (teams: any[] = []) => {
-  selectedTeams.value = [...teams]
-  tempSelectedTeams.value = [...teams]
-  tempSelectedTeamMap.value = Object.fromEntries(teams.map((team) => [team.id, team]))
-}
-
-const buildFormDataFromExpertDetail = (detail: any) => {
-  const teams = detail?.teams || []
-  fillTeamSelection(teams)
-  return {
-    id: detail?.id,
-    expertName: detail?.expertName || '',
-    achievement: detail?.achievement || '',
-    echelon: detail?.echelon || '',
-    education: detail?.education || '',
-    specialDirection: detail?.specialDirection || [],
-    softwareSkills: detail?.softwareSkills || [],
-    teamIds: teams.length ? teams.map((team) => team.id) : detail?.teamIds || [],
-    school: detail?.school || '',
-    birthday: detail?.birthday || null,
-    avatar: detail?.avatar || '',
-    deptId: detail?.deptId || '',
-    formerPost: detail?.formerPost || '',
-    jobTitle: detail?.jobTitle || '',
-    professionalTitle: detail?.professionalTitle || '',
-    expertRank: detail?.expertRank || []
-  }
-}
-
-type CellItem =
-  | {
-      type: 'members'
-      members: ExpertItem[]
-    }
-  | {
-      type: 'text'
-      text: string
-      tone?: 'normal' | 'danger'
-    }
-
-type TableRow = {
-  teamName: string
-  education: string
-  searchText: string
-  bookmarks: string[]
-  tableWidth: string
-  backgroundColor: string
-  columnWidths: number[]
-  cells: CellItem[]
-}
-
-type ExpertRecord = {
-  date: string
-  event: string
-  project: string
-  status: string
-  statusType: 'success' | 'primary'
-}
-
-type ExpertProfile = {
-  id: number
-  name: string
-  avatar: string
-  role: string
-  team: string
-  company: string
-  age: number
-  education: string
-  direction: string[]
-  softwareSkills: string[]
-  teamNames: string[]
-  teamIds: number[]
-  school: string
-  birthdayText: string
-  formerPost: string
-  jobTitle: string
-  professionalTitle: string
-  tags: string[]
-  achievement: string
-  records: ExpertRecord[]
-}
-
-type ExpertItem = {
-  id: number
-  deptId: number
-  expertName: string
-  echelon: string
-  expertRank: number[]
-  professionalTitle: string
-  jobTitle: string
-  formerPost: string
-  teamIds: number[]
-  avatar: string
-  birthday: number | null
-  school: string
-  education: string
-  specialDirection: number[]
-  achievement: string
-  softwareSkills: number[]
-  teams?: Array<{ id: number; teamName?: string }>
-}
-
-type ExpertGroup = {
-  echelonLabel: string
-  num: number
-  sort: number
-  experts: ExpertItem[]
-}
-
-const selectedMember = ref('')
-const selectedEducation = ref('')
-const selectedTeam = ref('')
-const drawerVisible = ref(false)
-const activeExpert = ref<ExpertProfile | null>(null)
-
-const resetQueryParams = () => ({
-  deptId: undefined,
-  education: undefined,
-  echelon: undefined,
-  expertName: undefined
-})
-
-const wordCloudWords = [
-  { text: '地质', size: 24, rotate: -8, color: '#7fcd72', left: '8%', top: '52%' },
-  { text: '油藏', size: 30, rotate: 6, color: '#8ea4d8', left: '26%', top: '38%' },
-  { text: '钻井', size: 27, rotate: -10, color: '#f0df62', left: '46%', top: '22%' },
-  { text: '采油工艺', size: 22, rotate: 8, color: '#b26c41', left: '60%', top: '14%' },
-  { text: '压裂', size: 21, rotate: -16, color: '#3d79a5', left: '74%', top: '48%' },
-  { text: '酸化', size: 18, rotate: 12, color: '#f1b477', left: '14%', top: '16%' },
-  { text: '修井', size: 19, rotate: -12, color: '#d26ea8', left: '36%', top: '66%' },
-  { text: '博士', size: 17, rotate: 5, color: '#7e8d64', left: '58%', top: '64%' },
-  { text: '研究生', size: 16, rotate: -5, color: '#7f85b5', left: '79%', top: '28%' }
-]
-
-const selectedTeamNames = computed(() =>
-  selectedTeams.value.map((team) => team.teamName).join('、')
-)
-
-const teamQueryParams = reactive({
-  pageNo: 1,
-  pageSize: 10,
-  teamName: undefined
-})
-
-const rowStylePresets = [
-  { tableWidth: '64%', backgroundColor: '#eef6ff', columnWidths: [1.5, 1.5, 1.5, 1.5, 1.5] },
-  { tableWidth: '75%', backgroundColor: '#f2faef', columnWidths: [1.8, 1.8, 1.8, 1.8, 1.8] },
-  { tableWidth: '100%', backgroundColor: '#fff8e8', columnWidths: [2, 2, 2, 2, 2] }
-]
-const rankOptions = computed(() => getStrDictOptions(DICT_TYPE.RD_EXPERT_RANK))
-
-const getDictLabel = (type: string, value: string | number | undefined | null) => {
-  if (value === undefined || value === null || value === '') return ''
-  return getStrDictOptions(type).find((item) => String(item.value) === String(value))?.label || ''
-}
-
-const getDictLabels = (type: string, values: Array<string | number> | undefined) => {
-  if (!values?.length) return []
-  return values.map((value) => getDictLabel(type, value)).filter(Boolean)
-}
-
-const teamNameMap = computed(() => {
-  const map = new Map<number, string>()
-  ;(teamList.value || []).forEach((team) => {
-    map.set(team.id, team.teamName)
-  })
-  ;(selectedTeams.value || []).forEach((team) => {
-    map.set(team.id, team.teamName)
-  })
-  return map
-})
-
-const deptNameMap = computed(() => {
-  const map = new Map<number, string>()
-  const walk = (nodes: any[]) => {
-    nodes.forEach((node) => {
-      map.set(node.id, node.name)
-      if (node.children?.length) {
-        walk(node.children)
-      }
-    })
-  }
-  walk(deptList2.value || [])
-  return map
-})
-
-const getTeamNames = (teamIds: number[] = [], teams?: Array<{ id: number; teamName?: string }>) => {
-  if (teams?.length) {
-    return teams
-      .map((team) => team.teamName || teamNameMap.value.get(team.id))
-      .filter(Boolean) as string[]
-  }
-  return teamIds.map((id) => teamNameMap.value.get(id)).filter(Boolean) as string[]
-}
-
-const getDeptName = (deptId?: number) => {
-  if (deptId === undefined || deptId === null) return ''
-  return deptNameMap.value.get(deptId) || ''
-}
-
-const getExpertAge = (birthday: number | null) => {
-  if (!birthday) return 0
-  const today = new Date()
-  const birth = new Date(birthday)
-  let age = today.getFullYear() - birth.getFullYear()
-  const monthDiff = today.getMonth() - birth.getMonth()
-  if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
-    age--
-  }
-  return Math.max(age, 0)
-}
-
-const formatBirthday = (birthday: number | null) => {
-  if (!birthday) return ''
-  const date = new Date(birthday)
-  if (Number.isNaN(date.getTime())) return ''
-  const year = date.getFullYear()
-  const month = String(date.getMonth() + 1).padStart(2, '0')
-  const day = String(date.getDate()).padStart(2, '0')
-  return `${year}-${month}-${day}`
-}
-
-const buildExpertProfile = (expert: ExpertItem, groupLabel?: string): ExpertProfile => {
-  const rankLabels = getDictLabels(DICT_TYPE.RD_EXPERT_RANK, expert.expertRank)
-  const directionLabels = getDictLabels(DICT_TYPE.RD_EXPERT_TECHNICAL, expert.specialDirection)
-  const softwareSkillLabels = getDictLabels(DICT_TYPE.RD_EXPERT_SOFTWARE, expert.softwareSkills)
-  const teamIds = expert.teams?.length ? expert.teams.map((team) => team.id) : expert.teamIds || []
-  const teamNames = getTeamNames(teamIds, expert.teams)
-  return {
-    id: expert.id,
-    name: expert.expertName,
-    avatar: expert.avatar || '',
-    role: rankLabels.join('、') || '专家',
-    team: groupLabel || getDictLabel(DICT_TYPE.RD_EXPERT_ECHELON, expert.echelon) || '未分组',
-    company: getDeptName(expert.deptId) || '-',
-    age: getExpertAge(expert.birthday),
-    education: getDictLabel(DICT_TYPE.RD_EXPERT_EDUCATION, expert.education) || '-',
-    direction: directionLabels.length ? directionLabels : ['暂无'],
-    softwareSkills: softwareSkillLabels,
-    teamNames,
-    teamIds,
-    school: expert.school || '',
-    birthdayText: formatBirthday(expert.birthday),
-    formerPost: expert.formerPost || '',
-    jobTitle: expert.jobTitle || '',
-    professionalTitle: expert.professionalTitle || '',
-    tags: [expert.professionalTitle].filter(Boolean),
-    achievement: expert.achievement || '暂无',
-    records: []
-  }
-}
-
-const getColumnPercent = (row: TableRow, index: number) => {
-  if (!row.columnWidths || row.columnWidths.length === 0) {
-    return '0'
-  }
-  const total = row.columnWidths.reduce((sum, width) => sum + width, 0)
-  const value = row.columnWidths[index] ?? 0
-  if (total === 0) {
-    return '0'
-  }
-  return ((value / total) * 100).toFixed(4)
-}
-
-const tableData = computed<TableRow[]>(() => {
-  return (list.value as ExpertGroup[]).map((group, index) => {
-    const preset = rowStylePresets[index] || rowStylePresets[rowStylePresets.length - 1]
-    const bookmarks = rankOptions.value.map((item) => item.label)
-    const columnWidths =
-      preset.columnWidths.length === rankOptions.value.length
-        ? preset.columnWidths
-        : Array(rankOptions.value.length).fill(1)
-    const educationLabels = Array.from(
-      new Set(
-        group.experts.map((expert) => getDictLabel(DICT_TYPE.RD_EXPERT_EDUCATION, expert.education))
-      )
-    ).filter(Boolean)
-    const searchText = group.experts
-      .map((expert) =>
-        [
-          expert.expertName,
-          expert.school,
-          expert.jobTitle,
-          expert.formerPost,
-          expert.achievement
-        ].join(' ')
-      )
-      .join(' ')
-      .toLowerCase()
-    const cells: CellItem[] = rankOptions.value.map((option) => ({
-      type: 'members',
-      members: group.experts.filter((expert) =>
-        (expert.expertRank || []).some((rank) => String(rank) === String(option.value))
-      )
-    }))
-
-    return {
-      teamName: group.echelonLabel,
-      education: educationLabels.join('、') || '-',
-      searchText,
-      bookmarks,
-      tableWidth: preset.tableWidth,
-      backgroundColor: preset.backgroundColor,
-      columnWidths,
-      cells
-    }
-  })
-})
-
-const filteredTableData = computed(() => {
-  return tableData.value.filter((row) => {
-    const educationMatch = selectedEducation.value
-      ? row.education === selectedEducation.value
-      : true
-    const selectedTeamMatch = selectedTeam.value ? row.teamName === selectedTeam.value : true
-
-    if (!educationMatch || !selectedTeamMatch) {
-      return false
-    }
-    return true
-  })
-})
-
-const handleMemberClick = async (member: ExpertItem) => {
-  selectedMember.value = member.expertName
-  drawerVisible.value = true
-  drawerLoading.value = true
-  try {
-    const detail = await getExpertDetail(member.id)
-    currentExpertDetail.value = detail
-    activeExpert.value = buildExpertProfile(detail)
-  } finally {
-    drawerLoading.value = false
-  }
-}
-
-const handleWordClick = (word: string) => {
-  queryParams.expertName = word
-}
-
-const handleTeamSelectionChange = (rows: any[]) => {
-  if (syncingTeamSelection.value) return
-  const currentPageIds = teamList.value.map((item) => item.id)
-  const nextMap = { ...tempSelectedTeamMap.value }
-  currentPageIds.forEach((id) => {
-    delete nextMap[id]
-  })
-  rows.forEach((row) => {
-    nextMap[row.id] = row
-  })
-  tempSelectedTeamMap.value = nextMap
-  tempSelectedTeams.value = Object.values(nextMap)
-}
-
-const syncTeamSelection = async () => {
-  await nextTick()
-  syncingTeamSelection.value = true
-  try {
-    teamTableRef.value?.clearSelection()
-    const selectedTeamIds = new Set(Object.keys(tempSelectedTeamMap.value).map((id) => Number(id)))
-    teamList.value.forEach((row) => {
-      if (selectedTeamIds.has(row.id)) {
-        teamTableRef.value?.toggleRowSelection(row, true)
-      }
-    })
-  } finally {
-    syncingTeamSelection.value = false
-  }
-}
-
-const getTeamOptionsList = async () => {
-  teamLoading.value = true
-  try {
-    const res = await getTeamList(teamQueryParams)
-    teamList.value = res.list || []
-    teamTotal.value = res.total || 0
-    await syncTeamSelection()
-  } finally {
-    teamLoading.value = false
-  }
-}
-
-const handleTeamQuery = async () => {
-  teamQueryParams.pageNo = 1
-  await getTeamOptionsList()
-}
-
-const resetTeamQuery = async () => {
-  teamQueryParams.pageNo = 1
-  teamQueryParams.teamName = undefined
-  await getTeamOptionsList()
-}
-
-const openTeamDialog = async () => {
-  teamDialogMode.value = 'form'
-  teamDialogVisible.value = true
-  tempSelectedTeams.value = [...selectedTeams.value]
-  tempSelectedTeamMap.value = Object.fromEntries(selectedTeams.value.map((team) => [team.id, team]))
-  await getTeamOptionsList()
-}
-
-const ensureSelectedTeams = async (teamIds: number[]) => {
-  if (!teamIds?.length) {
-    selectedTeams.value = []
-    tempSelectedTeams.value = []
-    tempSelectedTeamMap.value = {}
-    return
-  }
-
-  const teamMap = new Map<number, any>()
-  ;[...selectedTeams.value, ...teamList.value].forEach((team) => {
-    teamMap.set(team.id, team)
-  })
-
-  const missingIds = teamIds.filter((id) => !teamMap.has(id))
-  if (missingIds.length) {
-    const res = await getTeamList({ pageNo: 1, pageSize: 10 })
-    ;(res.list || []).forEach((team) => {
-      teamMap.set(team.id, team)
-    })
-  }
-
-  selectedTeams.value = teamIds.map((id) => teamMap.get(id)).filter(Boolean)
-  tempSelectedTeams.value = [...selectedTeams.value]
-  tempSelectedTeamMap.value = Object.fromEntries(selectedTeams.value.map((team) => [team.id, team]))
-}
-
-const handleJoinTeam = async () => {
-  if (!activeExpert.value) return
-
-  teamDialogMode.value = 'drawer'
-  const currentTeams = currentExpertDetail.value?.teams || []
-  if (currentTeams.length) {
-    selectedTeams.value = [...currentTeams]
-    tempSelectedTeams.value = [...currentTeams]
-    tempSelectedTeamMap.value = Object.fromEntries(currentTeams.map((team) => [team.id, team]))
-  } else {
-    const currentTeamIds = activeExpert.value.teamIds || []
-    await ensureSelectedTeams(currentTeamIds)
-  }
-  teamDialogVisible.value = true
-  await getTeamOptionsList()
-}
-
-const handleEdit = () => {
-  if (!currentExpertDetail.value) return
-  isEdit.value = true
-  dialogTitle.value = '编辑专家'
-  formData.value = buildFormDataFromExpertDetail(currentExpertDetail.value)
-  dialogVisible.value = true
-}
-
-const confirmTeamSelection = async () => {
-  selectedTeams.value = [...tempSelectedTeams.value]
-  const teamIds = selectedTeams.value.map((team) => team.id)
-
-  if (teamDialogMode.value === 'drawer') {
-    if (!currentExpertDetail.value || !activeExpert.value) return
-
-    await updateExpert({
-      ...currentExpertDetail.value,
-      teams: selectedTeams.value,
-      teamIds
-    })
-
-    currentExpertDetail.value = {
-      ...currentExpertDetail.value,
-      teams: selectedTeams.value,
-      teamIds
-    }
-    activeExpert.value = buildExpertProfile(currentExpertDetail.value)
-    teamDialogVisible.value = false
-    ElMessage.success('加入团队成功')
-    await getList()
-    return
-  }
-
-  formData.value.teamIds = teamIds
-  teamDialogVisible.value = false
-}
-
-// 显示新增对话框
-const handleAdd = () => {
-  isEdit.value = false
-  dialogTitle.value = '新增专家'
-  resetForm()
-  dialogVisible.value = true
-}
-
-let loading = ref(false)
-const list = ref([]) // 列表的数据
-const queryParams = reactive({
-  deptId: undefined,
-  education: undefined,
-  echelon: undefined,
-  expertName: undefined
-})
-
-const handleReset = async () => {
-  Object.assign(queryParams, resetQueryParams())
-  await getList()
-}
-
-const submitExpertForm = async () => {
-  if (!formRef.value) return
-
-  const valid = await formRef.value.validate().catch(() => false)
-  if (!valid) return
-
-  submitLoading.value = true
-  try {
-    const payload = {
-      ...formData.value,
-      avatar:
-        typeof formData.value.avatar === 'string'
-          ? formData.value.avatar
-          : formData.value.avatar?.path || ''
-    }
-
-    if (isEdit.value) {
-      await updateExpert(payload)
-      ElMessage.success('修改成功')
-      if (payload.id) {
-        const detail = await getExpertDetail(payload.id)
-        currentExpertDetail.value = detail
-        activeExpert.value = buildExpertProfile(detail)
-      }
-    } else {
-      await createExpert(payload)
-      ElMessage.success('新增成功')
-    }
-
-    closeDialog()
-    await getList()
-  } catch (error) {
-    console.error(error)
-  } finally {
-    submitLoading.value = false
-  }
-}
-
-const getList = async () => {
-  loading.value = true
-  try {
-    const data = await getExpertList({
-      ...queryParams,
-      expertName: queryParams.expertName?.trim() || undefined
-    })
-    list.value = data
-  } finally {
-    loading.value = false
-  }
-}
-
-onMounted(async () => {
-  getList()
-
-  deptList2.value = await DeptApi.getSimpleDeptList()
-})
-</script>
-
-<style scoped lang="scss">
-.expert-step-table {
-  --ink: #182536;
-  --muted-ink: #718092;
-  --line: rgba(28, 44, 62, 0.18);
-  width: 100%;
-  max-width: none;
-  margin: 0 auto;
-  // padding: 34px;
-  overflow: hidden;
-  box-sizing: border-box;
-
-  color: var(--ink);
-}
-
-.expert-step-table el-card {
-  width: 100%;
-}
-
-.team-select-field {
-  display: flex;
-  width: 100%;
-  gap: 12px;
-}
-
-.team-select-field :deep(.el-input) {
-  flex: 1;
-}
-
-// .team-picker-table-wrap {
-//   margin-top: 8px;
-//   margin-bottom: 12px;
-// }
-
-.team-picker-pagination-wrap {
-  margin-top: 16px;
-}
-
-:deep(.team-picker-dialog .el-dialog__footer) {
-  border-top: none;
-  padding-top: 8px;
-}
-
-:deep(.team-picker-table) {
-  margin-bottom: 0;
-}
-
-:deep(.team-picker-pagination) {
-  margin-top: 0;
-}
-
-.team-picker-footer {
-  display: flex;
-  justify-content: flex-end;
-  gap: 12px;
-  width: 100%;
-  padding-top: 12px;
-}
-
-.search-layout {
-  display: grid;
-  grid-template-columns: minmax(220px, 0.34fr) minmax(0, 1fr);
-  gap: 18px;
-  margin-bottom: 15px;
-}
-
-.filter-panel,
-.word-cloud-panel {
-  min-width: 0;
-  padding: 12px;
-  border: 1px solid rgba(28, 44, 62, 0.1);
-  border-radius: 8px;
-  box-sizing: border-box;
-}
-
-.filter-panel {
-  background: linear-gradient(160deg, rgba(237, 245, 249, 0.95), rgba(247, 249, 248, 0.9)), #f4f7f8;
-}
-
-.word-cloud-panel {
-  position: relative;
-  overflow: hidden;
-  background: radial-gradient(circle at 84% 20%, rgba(177, 205, 210, 0.3), transparent 32%), #f8f7f2;
-}
-
-.word-cloud-panel::after {
-  position: absolute;
-  right: -70px;
-  bottom: -90px;
-  width: 220px;
-  height: 220px;
-  border: 1px solid rgba(161, 94, 63, 0.16);
-  border-radius: 50%;
-  content: '';
-  box-shadow:
-    0 0 0 20px rgba(161, 94, 63, 0.04),
-    0 0 0 42px rgba(161, 94, 63, 0.03);
-  pointer-events: none;
-}
-
-.panel-kicker {
-  margin-bottom: 8px;
-  color: #8a98a5;
-  font-family: 'Courier New', monospace;
-  font-size: 10px;
-  font-weight: 700;
-  letter-spacing: 0.14em;
-}
-
-.filter-panel h2,
-.word-cloud-panel h2 {
-  margin: 0;
-  color: var(--ink);
-  font-size: 20px;
-  font-weight: 700;
-  letter-spacing: -0.03em;
-}
-
-.panel-description {
-  margin: 8px 0 18px;
-  color: var(--muted-ink);
-  font-size: 12px;
-  line-height: 1.65;
-}
-
-.filter-fields {
-  display: flex;
-  flex-direction: column;
-  gap: 10px;
-}
-
-.filter-fields :deep(.el-input__wrapper),
-.filter-fields :deep(.el-select__wrapper) {
-  min-height: 38px;
-  border-radius: 7px;
-  background: rgba(255, 255, 255, 0.78);
-  box-shadow: 0 0 0 1px rgba(28, 44, 62, 0.09) inset;
-}
-
-.filter-fields :deep(.el-input__wrapper.is-focus),
-.filter-fields :deep(.el-select__wrapper.is-focused) {
-  box-shadow: 0 0 0 1px #467da4 inset;
-}
-
-.filter-status {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  margin-top: 18px;
-  padding-top: 14px;
-  border-top: 1px dashed rgba(28, 44, 62, 0.15);
-  color: #718092;
-  font-size: 11px;
-}
-
-.status-dot {
-  width: 6px;
-  height: 6px;
-  border-radius: 50%;
-  background: #6f9878;
-  box-shadow: 0 0 0 4px rgba(111, 152, 120, 0.13);
-}
-
-.cloud-heading {
-  position: relative;
-  z-index: 1;
-  display: flex;
-  align-items: flex-start;
-  justify-content: space-between;
-  gap: 20px;
-}
-
-.cloud-hint {
-  padding-top: 18px;
-  color: #9aa6b3;
-  font-size: 11px;
-}
-
-.word-cloud {
-  position: relative;
-  z-index: 1;
-  min-height: 180px;
-  margin-top: 4px;
-  border-radius: 12px;
-  background: radial-gradient(circle at 24% 20%, rgba(255, 255, 255, 0.72), transparent 28%),
-    linear-gradient(180deg, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.18));
-  overflow: hidden;
-}
-
-.cloud-word {
-  position: absolute;
-  border: 0;
-  padding: 0;
-  background: transparent;
-  cursor: pointer;
-  // font-weight: 600;
-  letter-spacing: -0.03em;
-  line-height: 1;
-  white-space: nowrap;
-  transform-origin: center;
-  transition:
-    transform 180ms ease,
-    opacity 180ms ease,
-    text-shadow 180ms ease;
-}
-
-.cloud-word:hover {
-  opacity: 0.72;
-  text-shadow: 0 5px 12px rgba(49, 91, 115, 0.18);
-  transform: scale(1.06) rotate(var(--rotate, 0deg));
-}
-
-.cloud-word:focus-visible {
-  outline: 2px solid #a15e3f;
-  outline-offset: 5px;
-}
-
-.table-divider {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin: 4px 2px 14px;
-  color: var(--ink);
-  font-size: 14px;
-  font-weight: 700;
-}
-
-.result-count {
-  color: #9aa6b3;
-  font-size: 10px;
-  letter-spacing: 0.1em;
-  text-transform: uppercase;
-}
-
-.table-intro {
-  display: flex;
-  align-items: flex-end;
-  justify-content: space-between;
-  gap: 32px;
-  margin-bottom: 28px;
-  padding: 0 4px 22px;
-  border-bottom: 1px solid rgba(28, 44, 62, 0.16);
-}
-
-.eyebrow {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  margin-bottom: 12px;
-  color: #758496;
-  font-size: 10px;
-  font-weight: 700;
-  letter-spacing: 0.18em;
-}
-
-.eyebrow-dot,
-.legend-dot {
-  display: inline-block;
-  width: 7px;
-  height: 7px;
-  border-radius: 50%;
-  background: #b26d42;
-  box-shadow: 0 0 0 4px rgba(178, 109, 66, 0.13);
-}
-
-.table-intro h1 {
-  margin: 0;
-  color: var(--ink);
-  font-size: clamp(30px, 4vw, 46px);
-  font-weight: 500;
-  letter-spacing: -0.045em;
-  line-height: 1;
-}
-
-.table-intro p {
-  max-width: 420px;
-  margin: 12px 0 0;
-  color: var(--muted-ink);
-  font-size: 13px;
-  line-height: 1.7;
-}
-
-.intro-meta {
-  display: flex;
-  align-items: center;
-  gap: 20px;
-  padding-bottom: 4px;
-  color: #647285;
-  font-size: 11px;
-  font-weight: 700;
-  letter-spacing: 0.08em;
-}
-
-.legend-item {
-  display: inline-flex;
-  align-items: center;
-  gap: 9px;
-  letter-spacing: 0;
-}
-
-.legend-dot {
-  width: 6px;
-  height: 6px;
-  background: #467da4;
-  box-shadow: 0 0 0 4px rgba(70, 125, 164, 0.13);
-}
-
-.tier-count {
-  color: #9aa6b3;
-}
-
-.table-list {
-  width: 100%;
-  overflow: hidden;
-}
-
-.ladder-row {
-  display: flex;
-  width: 100%;
-  max-width: 100%;
-  min-width: 0;
-  min-height: 78px;
-
-  transform: translateX(0);
-  animation: row-in 0.55s both;
-  transition:
-    transform 220ms ease,
-    filter 220ms ease;
-}
-
-.ladder-row:nth-child(2) {
-  animation-delay: 80ms;
-}
-
-.ladder-row:nth-child(3) {
-  animation-delay: 160ms;
-}
-
-.ladder-row:hover {
-  transform: translateX(5px);
-  filter: saturate(1.04);
-}
-
-.ladder-row + .ladder-row {
-  margin-top: -1px;
-}
-
-.team-cell {
-  display: flex;
-  flex: 0 0 104px;
-  align-items: center;
-  justify-content: flex-start;
-  gap: 10px;
-  min-height: 78px;
-  padding: 12px 14px;
-  border: 1px solid var(--line);
-  box-sizing: border-box;
-  background-color: var(--row-bg);
-  color: var(--ink);
-  font-size: 13px;
-  font-weight: 700;
-}
-
-.team-index {
-  color: rgba(24, 37, 54, 0.38);
-  font-size: 10px;
-  font-weight: 700;
-  letter-spacing: 0.08em;
-}
-
-.team-name {
-  white-space: nowrap;
-}
-
-.content-table {
-  flex: 1 1 auto;
-  width: 0;
-  min-width: 0;
-  table-layout: fixed;
-  border-collapse: collapse;
-  border-spacing: 0;
-  background: transparent;
-}
-
-.content-table td {
-  height: 78px;
-  padding: 10px 14px 10px 26px;
-  border: 1px solid var(--line);
-  box-sizing: border-box;
-  background-color: var(--row-bg);
-}
-
-.step-cell {
-  position: relative;
-  text-align: center;
-  vertical-align: middle;
-  overflow-wrap: anywhere;
-  word-break: break-all;
-}
-
-.cell-bookmark {
-  position: absolute;
-  top: 10px;
-  left: -1px;
-  display: inline-flex;
-  align-items: center;
-  justify-content: center;
-  min-width: 18px;
-  height: 48px;
-  padding: 4px 2px;
-  border: 1px solid rgba(70, 125, 164, 0.28);
-  border-left: 0;
-  border-radius: 0 6px 6px 0;
-  background: linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(230, 238, 244, 0.92));
-  box-shadow: 0 6px 14px rgba(24, 37, 54, 0.08);
-  color: #47657e;
-  font-size: 10px;
-  font-weight: 700;
-  letter-spacing: 0.08em;
-  line-height: 1.1;
-  writing-mode: vertical-rl;
-  text-orientation: upright;
-}
-
-.notice-cell {
-  background-image: linear-gradient(135deg, rgba(178, 109, 66, 0.09), transparent 60%);
-}
-
-.member-link {
-  display: inline-flex;
-  align-items: center;
-  margin: 3px 2px;
-  padding: 4px 7px;
-  border: 1px solid rgba(70, 125, 164, 0.24);
-  border-radius: 4px;
-  background: rgba(255, 255, 255, 0.42);
-  color: #356783;
-  cursor: pointer;
-  font-size: 12px;
-  font-weight: 600;
-  line-height: 1.2;
-  transition:
-    transform 180ms ease,
-    border-color 180ms ease,
-    background-color 180ms ease,
-    color 180ms ease;
-}
-
-.member-link:hover {
-  border-color: #467da4;
-  background: #467da4;
-  color: #fff;
-  transform: translateY(-2px);
-}
-
-.member-link:focus-visible {
-  outline: 2px solid #b26d42;
-  outline-offset: 2px;
-}
-
-.cell-text {
-  color: #687789;
-  font-size: 12px;
-  line-height: 1.65;
-}
-
-.is-danger {
-  color: #a95f3c;
-  font-weight: 600;
-}
-
-.member-detail {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-  margin-top: 22px;
-  padding: 12px 16px;
-  border: 1px solid rgba(70, 125, 164, 0.2);
-  border-left: 3px solid #467da4;
-  background: rgba(255, 255, 255, 0.62);
-  color: #637285;
-  font-size: 12px;
-}
-
-.member-detail strong {
-  color: var(--ink);
-  font-size: 14px;
-}
-
-.detail-label {
-  color: #467da4;
-  font-size: 10px;
-  font-weight: 700;
-  letter-spacing: 0.12em;
-}
-
-.detail-copy {
-  color: #8b97a5;
-}
-
-@keyframes row-in {
-  from {
-    opacity: 0;
-    transform: translateY(10px);
-  }
-  to {
-    opacity: 1;
-    transform: translateY(0);
-  }
-}
-
-@media (max-width: 700px) {
-  .expert-step-table {
-    padding: 22px 14px;
-  }
-
-  .table-intro {
-    align-items: flex-start;
-    flex-direction: column;
-    gap: 18px;
-  }
-
-  .search-layout {
-    grid-template-columns: 1fr;
-  }
-
-  .word-cloud {
-    min-height: 160px;
-  }
-
-  .intro-meta {
-    width: 100%;
-    justify-content: space-between;
-  }
-
-  .team-cell {
-    flex-basis: 86px;
-    padding: 10px;
-  }
-
-  .content-table td {
-    padding: 8px 6px 8px 22px;
-  }
-
-  .member-link {
-    padding: 4px 5px;
-    font-size: 11px;
-  }
-
-  .cell-bookmark {
-    top: 8px;
-    min-width: 16px;
-    height: 42px;
-    font-size: 9px;
-  }
-
-  .member-detail {
-    flex-wrap: wrap;
-    gap: 8px;
-  }
-}
-
-:deep(.expert-drawer) {
-  --el-drawer-padding-primary: 0;
-}
-
-:deep(.expert-drawer.el-drawer.rtl) {
-  overflow: hidden;
-}
-
-:deep(.el-drawer__wrapper) {
-  overflow: hidden !important;
-}
-
-:deep(.el-overlay) {
-  overflow: hidden !important;
-}
-
-:deep(.expert-drawer .el-drawer__body) {
-  padding: 0;
-  background: #eef2ef;
-  overflow-x: hidden !important;
-}
-
-.drawer-shell {
-  min-height: 100%;
-  background: #eef2ef;
-  overflow-x: hidden !important;
-}
-
-.drawer-header {
-  padding: 16px 22px 14px;
-  border-bottom: 1px solid rgba(24, 37, 54, 0.08);
-  background: #fff;
-}
-
-.drawer-header h2 {
-  margin: 0;
-  color: #102234;
-  font-size: 18px;
-  font-weight: 800;
-}
-
-.drawer-header p {
-  margin: 6px 0 0;
-  color: #6f7f8e;
-  font-size: 13px;
-  font-weight: 600;
-}
-
-.drawer-profile {
-  display: grid;
-  grid-template-columns: 150px minmax(0, 1fr);
-  gap: 16px;
-  margin: 18px;
-  padding: 16px;
-  border: 1px solid rgba(28, 44, 62, 0.08);
-  border-radius: 14px;
-  background: #fff;
-  box-shadow: 0 8px 30px rgba(28, 44, 62, 0.06);
-}
-
-.profile-aside {
-  display: flex;
-  flex-direction: column;
-  gap: 10px;
-}
-
-.profile-avatar {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  min-height: 150px;
-  max-height: 150px;
-  border-radius: 14px;
-  background: linear-gradient(145deg, #b7d3cd, #e8f0ee);
-  color: #0f5b58;
-  font-size: 46px;
-  font-weight: 800;
-  letter-spacing: -0.08em;
-  overflow: hidden;
-}
-
-.profile-avatar img {
-  width: 100%;
-  height: 100%;
-  object-fit: cover;
-  display: block;
-}
-
-.profile-tags {
-  display: flex;
-  gap: 8px;
-  flex-wrap: wrap;
-}
-
-.profile-tag {
-  display: inline-flex;
-  align-items: center;
-  padding: 6px 10px;
-  border-radius: 999px;
-  background: #f1f4f2;
-  color: #6e7d7a;
-  font-size: 12px;
-  font-weight: 700;
-}
-
-.profile-main {
-  display: flex;
-  flex-direction: column;
-  gap: 10px;
-}
-
-.info-block {
-  padding: 12px 14px;
-  border: 1px solid #dbe3df;
-  border-radius: 12px;
-  background: #fdfefe;
-}
-
-.info-label {
-  display: block;
-  margin-bottom: 5px;
-  color: #7a8a89;
-  font-size: 12px;
-  // font-weight: 700;
-}
-
-.info-block strong {
-  color: #203130;
-  font-size: 13px;
-  font-weight: 800;
-  line-height: 1.55;
-}
-
-.profile-actions {
-  display: grid;
-  grid-template-columns: repeat(3, minmax(0, 1fr));
-  gap: 10px;
-}
-
-.profile-actions :deep(.el-button) {
-  height: 42px;
-  border-radius: 10px;
-  font-size: 14px;
-  font-weight: 700;
-}
-
-.profile-actions :deep(.el-button--success) {
-  background: #237b63;
-  border-color: #237b63;
-}
-
-.record-panel {
-  margin: 0 26px 26px;
-  border: 1px solid rgba(28, 44, 62, 0.08);
-  border-radius: 16px;
-  overflow: hidden;
-  background: #fff;
-  box-shadow: 0 8px 30px rgba(28, 44, 62, 0.05);
-}
-
-.record-header {
-  padding: 22px 24px 18px;
-  border-bottom: 1px solid rgba(28, 44, 62, 0.08);
-}
-
-.record-header h3 {
-  margin: 0;
-  color: #1e2d3a;
-  font-size: 18px;
-  font-weight: 800;
-}
-
-.record-header p {
-  margin: 6px 0 0;
-  color: #8a97a3;
-  font-size: 13px;
-}
-
-.record-table :deep(.el-table__header th) {
-  background: #f7faf8;
-  color: #556574;
-  font-weight: 700;
-}
-
-.record-table :deep(.el-table__inner-wrapper) {
-  overflow-x: hidden;
-}
-
-.record-table :deep(.el-table__row td) {
-  padding-top: 14px;
-  padding-bottom: 14px;
-}
-
-.record-badge {
-  display: inline-flex;
-  align-items: center;
-  justify-content: center;
-  min-width: 88px;
-  padding: 7px 12px;
-  border-radius: 999px;
-  font-size: 13px;
-  font-weight: 700;
-}
-
-.record-badge.success {
-  background: #e4f4ec;
-  color: #1f8a61;
-}
-
-.record-badge.primary {
-  background: #e8f0fb;
-  color: #3d6ea8;
-}
-
-@media (max-width: 900px) {
-  .drawer-profile {
-    grid-template-columns: 1fr;
-  }
-
-  .profile-avatar {
-    min-height: 180px;
-    font-size: 64px;
-  }
-
-  .profile-actions {
-    grid-template-columns: 1fr;
-  }
-}
-
-.team-logo {
-  width: 52px;
-  height: 52px;
-  border-radius: 8px;
-  border: 1px solid #e5e7eb;
-}
-</style>