|
|
@@ -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>
|