页合并 (admin/manager 共用, route.path 角色感知): - expert: Experts.vue + ExpertNew.vue (list/new/edit/view 一套) - sponsor-orgs: SponsorOrgs.vue - sponsor-people: SponsorPeople.vue + SponsorPersonNew.vue + SponsorPersonDetail.vue - executor-orgs: ExecutorOrgs.vue - executor-people: ExecutorPeople.vue + ExecutorPersonNew.vue + ExecutorPersonDetail.vue - 给 SponsorPeople / ExecutorPeople op 列补上 查看/编辑 按钮 (潜在 bug 修复) - 详情弹窗统一用 el-descriptions 风格 (替代 admin 旧版 ElMessageBox.alert) 后端: - BizSignController + BizSignServiceImpl + PdfService (劳务签署 PDF 流程) - BizPublicityIntentController (公示意向: 支持/执行) - BizPublicitySupportIntent / BizPublicityExecutionIntent ExportVo - BizMeetingAttendee 字段合并 (bank_region 替代 bankProvince/bankCity) - BizExpert 字段合并 (id_card_attachments CSV, bank_region) - Excel 导入模板精简 (开户行 1 列) 前端: - doctor/SignFill + SignContract + SignSuccess (劳务签署 3 页流程) - ImportResultDialog 通用组件 - IdCardUploader 重构 (单 v-model:idCardAttachments, CSV 格式) - AreaCascader 调整 - Login.vue + AdminLayout.vue + PortalShell.vue + Home.vue 适配 DB: 字段合并 (id_card_front/back → id_card_attachments, bank_province/city → bank_region)
473 lines
20 KiB
Vue
473 lines
20 KiB
Vue
<template>
|
||
<!--
|
||
共享专家列表页 (admin/manager 共用, 通过 route.path 区分角色)
|
||
- 面包屑: admin → 专家管理 / manager → 专家审核
|
||
- 状态筛选: 仅 admin 显示
|
||
- 审批时间/审批人 列: 两角色都显示 (审计追溯)
|
||
- 查看按钮: 仅 admin (admin 有独立详情页 /admin/experts/view/:id)
|
||
- 编辑入口: 两角色都跳独立页 (admin/manager/experts/edit/:id), 由 route 决定回跳路径
|
||
- 批量导入: 沿用 manager 版本 (v-if importOpen + onError + 模板下载在框外 + ImportResultDialog)
|
||
-->
|
||
<div class="page-card experts-list">
|
||
<div class="breadcrumb">首页 / {{ isAdmin ? '专家管理' : '专家审核' }}</div>
|
||
|
||
<!-- ========== 筛选区 ========== -->
|
||
<el-form inline :model="q" class="filter-form">
|
||
<el-form-item label="姓名"><el-input v-model="q.name" placeholder="输入姓名" clearable style="width:160px" /></el-form-item>
|
||
<el-form-item label="手机号"><el-input v-model="q.phone" placeholder="输入手机号" clearable maxlength="11" style="width:160px" /></el-form-item>
|
||
<el-form-item label="工作单位"><el-input v-model="q.workUnit" placeholder="输入工作单位" clearable style="width:200px" /></el-form-item>
|
||
<el-form-item label="审核状态">
|
||
<el-select v-model="q.auditStatus" placeholder="请选择" clearable style="width:140px">
|
||
<el-option label="未提交" value="0" />
|
||
<el-option label="待审核" value="1" />
|
||
<el-option label="通过" value="2" />
|
||
<el-option label="拒绝" value="3" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item v-if="isAdmin" label="状态">
|
||
<el-select v-model="q.status" placeholder="全部状态" clearable style="width:120px">
|
||
<el-option label="正常" value="Y" />
|
||
<el-option label="禁用" value="N" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item>
|
||
<el-button type="primary" @click="load">{{ isAdmin ? '查询' : '查找' }}</el-button>
|
||
<el-button @click="reset">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<!-- ========== 批量按钮区 ========== -->
|
||
<!--
|
||
角色分工:
|
||
- 批量导入: 两角色都有 (manager 也可批量入库, 走同一 /importData 端点)
|
||
- 批量审核: 两角色都有 (manager 主业, admin 可兜底)
|
||
- 批量导入执业证书/职称证明: 仅 admin (管理员上传, manager 不管证书)
|
||
-->
|
||
<div class="batch-bar">
|
||
<el-button type="primary" @click="goNew">新建专家</el-button>
|
||
<el-button @click="openImport">批量导入</el-button>
|
||
<el-button :disabled="!selection.length" @click="onBatchAudit">批量审核</el-button>
|
||
<el-button v-if="isAdmin" @click="openImportPracticeCert">批量导入执业证书</el-button>
|
||
<el-button v-if="isAdmin" @click="openImportTitleCert">批量导入职称证明</el-button>
|
||
</div>
|
||
|
||
<!-- ========== 表格 ========== -->
|
||
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSel">
|
||
<el-table-column type="selection" width="44" />
|
||
<el-table-column prop="name" label="姓名" width="100" fixed />
|
||
<el-table-column prop="phone" label="手机号" width="130" />
|
||
<el-table-column prop="workUnit" label="工作单位" min-width="180" show-overflow-tooltip />
|
||
<el-table-column prop="department" label="科室" width="100" />
|
||
<el-table-column prop="title" label="职称" width="100" />
|
||
<el-table-column label="执业证书" width="170">
|
||
<template #default="{ row }">
|
||
<template v-if="row.practiceCertUrl">
|
||
<el-button link type="primary" @click="onPreviewCert(row.practiceCertUrl)">查看</el-button>
|
||
<el-button link type="primary" @click="onDownloadCert(row.practiceCertUrl, row.phone)">下载</el-button>
|
||
</template>
|
||
<span v-else style="color:#c0c4cc">-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="职称证明" width="170">
|
||
<template #default="{ row }">
|
||
<template v-if="row.titleCertUrl">
|
||
<el-button link type="primary" @click="onPreviewCert(row.titleCertUrl)">查看</el-button>
|
||
<el-button link type="primary" @click="onDownloadCert(row.titleCertUrl, row.phone)">下载</el-button>
|
||
</template>
|
||
<span v-else style="color:#c0c4cc">-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="审核状态" width="100" align="center">
|
||
<template #default="{ row }"><audit-status-tag :status="row.auditStatus" /></template>
|
||
</el-table-column>
|
||
<el-table-column label="启用状态" width="100" align="center">
|
||
<template #default="{ row }">
|
||
<el-tag size="small" :type="row.status === 'N' ? 'danger' : 'success'">
|
||
{{ row.status === 'N' ? '禁用' : '正常' }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="auditTime" label="审批时间" width="160" align="center">
|
||
<template #default="{ row }">{{ fmtTime(row.auditTime) }}</template>
|
||
</el-table-column>
|
||
<el-table-column prop="auditBy" label="审批人" width="120" align="center" />
|
||
<el-table-column label="操作" :width="isAdmin ? 320 : 240" fixed="right">
|
||
<template #default="{ row }">
|
||
<!-- 审核 (通过/拒绝): 两角色都有, manager 主业, admin 可兜底 -->
|
||
<el-button link type="primary" @click="onAudit(row)">审核</el-button>
|
||
<!-- 修改: 两角色都有 (独立页编辑, route 自动决定回跳) -->
|
||
<el-button link type="primary" @click="goEdit(row)">修改</el-button>
|
||
<!-- 查看 (只读详情): 仅 admin, manager 直接在审核 dialog 看 -->
|
||
<el-button v-if="isAdmin" link type="primary" @click="goView(row)">查看</el-button>
|
||
<!-- 启用/禁用: 仅 admin, 是账号管理动作 (同步 sys_user.status) -->
|
||
<el-button v-if="isAdmin" link :type="row.status === 'N' ? 'success' : 'danger'" @click="row.status === 'N' ? onEnable(row) : onDisable(row)">
|
||
{{ row.status === 'N' ? '恢复' : '禁用' }}
|
||
</el-button>
|
||
<!-- 已拒绝 (查意见): 仅 auditStatus=3 时显示, 两角色都可见 -->
|
||
<el-button v-if="row.auditStatus === '3'" link type="info" @click="onViewAuditOpinion(row)">已拒绝</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<div class="pager">
|
||
<el-pagination
|
||
v-model:current-page="page.pageNum"
|
||
v-model:page-size="page.pageSize"
|
||
:total="page.total"
|
||
:page-sizes="[10, 20, 50]"
|
||
layout="total, sizes, prev, pager, next, jumper"
|
||
@current-change="load"
|
||
@size-change="load"
|
||
/>
|
||
</div>
|
||
|
||
<!-- ========== 审核 dialog ========== -->
|
||
<el-dialog v-model="auditOpen" title="审核" width="520px">
|
||
<p style="margin-bottom:12px">专家: <b>{{ auditForm.name }}</b> ({{ auditForm.phone }})</p>
|
||
<el-form :model="auditForm" label-width="100px">
|
||
<el-form-item label="审核结果">
|
||
<el-radio-group v-model="auditForm.result">
|
||
<el-radio value="2">通过</el-radio>
|
||
<el-radio value="3">拒绝</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item label="审核意见">
|
||
<el-input v-model="auditForm.opinion" type="textarea" :rows="4" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<template #footer>
|
||
<el-button @click="auditOpen=false">取消</el-button>
|
||
<el-button type="primary" :loading="submitting" @click="submitAudit">确认</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- ========== 批量导入专家 (沿用 manager 模式: v-if + onError + 模板下载在框外 + ImportResultDialog) ========== -->
|
||
<el-dialog v-model="importOpen" title="批量导入专家" width="400px" append-to-body :close-on-click-modal="false">
|
||
<el-upload
|
||
v-if="importOpen"
|
||
ref="uploadRef"
|
||
:limit="1"
|
||
:headers="importHeaders"
|
||
accept=".xlsx, .xls"
|
||
:action="importAction"
|
||
:on-progress="onImportProgress"
|
||
:on-success="onImportSuccess"
|
||
:on-error="onImportError"
|
||
:auto-upload="false"
|
||
drag
|
||
>
|
||
<el-icon class="el-icon--upload"><upload /></el-icon>
|
||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
||
</el-upload>
|
||
<!-- 模板下载: 必须放在 el-upload 框外(下面), 否则点击会被 drag 区域吞掉, 触发的是文件选择器 -->
|
||
<div style="margin-top:10px;font-size:13px;text-align:center">
|
||
<span style="color:#909399;margin-right:8px">仅允许导入 xls、xlsx 格式文件</span>
|
||
<el-link type="primary" :underline="false" @click="downloadImportTpl">下载模板</el-link>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="importOpen=false">取 消</el-button>
|
||
<el-button type="primary" :loading="importing" @click="submitImportFile">确 定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
<ImportResultDialog v-model="importResultOpen" :result="importResult" />
|
||
|
||
<!-- ========== 批量导入执业证书 ========== -->
|
||
<el-dialog v-model="importCertOpen" title="批量导入执业证书" width="520px">
|
||
<p style="color:#606266;margin-bottom:8px">提示: 需将证书文件名称修改为手机号进行导入</p>
|
||
<el-input v-model="importCertUrl" placeholder="上传文件 URL 或选择文件" />
|
||
<template #footer>
|
||
<el-button @click="importCertOpen=false">取消</el-button>
|
||
<el-button type="primary" :loading="importing" @click="submitImportCert">确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- ========== 批量导入职称证明 ========== -->
|
||
<el-dialog v-model="importTitleCertOpen" title="批量导入职称证明" width="520px">
|
||
<p style="color:#606266;margin-bottom:8px">提示: 需将证明文件名称修改为手机号进行导入</p>
|
||
<el-input v-model="importTitleCertUrl" placeholder="上传文件 URL 或选择文件" />
|
||
<template #footer>
|
||
<el-button @click="importTitleCertOpen=false">取消</el-button>
|
||
<el-button type="primary" :loading="importing" @click="submitImportTitleCert">确定</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
|
||
<!-- ========== 证书图片预览 dialog ========== -->
|
||
<el-dialog v-model="previewOpen" title="证书预览" width="640px" :show-close="true" destroy-on-close>
|
||
<div class="cert-preview">
|
||
<el-image
|
||
:src="previewUrl"
|
||
fit="contain"
|
||
:preview-src-list="[previewUrl]"
|
||
style="width:100%;max-height:70vh"
|
||
loading="lazy"
|
||
>
|
||
<template #error>
|
||
<div class="cert-preview-error">图片加载失败, 请<a @click="onDownloadCert(previewUrl)">下载</a>查看</div>
|
||
</template>
|
||
</el-image>
|
||
</div>
|
||
<template #footer>
|
||
<el-button @click="onDownloadCert(previewUrl)">下载</el-button>
|
||
<el-button type="primary" @click="previewOpen=false">关闭</el-button>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||
import { useRouter, useRoute } from 'vue-router'
|
||
import { useUserStore } from '@/store/user'
|
||
import { bizList, bizUpdate } from '@/api/public'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { Upload } from '@element-plus/icons-vue'
|
||
import AuditStatusTag from '@/components/AuditStatusTag.vue'
|
||
import ImportResultDialog from '@/components/ImportResultDialog.vue'
|
||
|
||
const router = useRouter()
|
||
const route = useRoute()
|
||
const userStore = useUserStore()
|
||
|
||
// 角色判定: 用 route.path 前缀区分 admin/manager (不引入 role 字段, 路由本身就是角色来源)
|
||
const isAdmin = computed(() => route.path.startsWith('/admin/'))
|
||
const listBasePath = computed(() => isAdmin.value ? '/admin/experts' : '/manager/experts')
|
||
const currentUserName = computed(() => userStore.user?.userName || userStore.user?.nickName || (isAdmin.value ? 'admin' : 'manager'))
|
||
|
||
const q = reactive({ name: '', phone: '', workUnit: '', auditStatus: '', status: '' })
|
||
const rows = ref([])
|
||
const selection = ref([])
|
||
const loading = ref(false)
|
||
const submitting = ref(false)
|
||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||
|
||
function goNew() { router.push(listBasePath.value + '/new') }
|
||
function goEdit(row) { router.push(listBasePath.value + '/edit/' + row.expertId) }
|
||
function goView(row) { router.push(listBasePath.value + '/view/' + row.expertId) }
|
||
|
||
async function load() {
|
||
loading.value = true
|
||
try {
|
||
const { data } = await bizList('expert', { ...q, pageNum: page.pageNum, pageSize: page.pageSize })
|
||
rows.value = (data && data.rows) || []
|
||
page.total = (data && data.total) || 0
|
||
} catch { rows.value = []; page.total = 0 }
|
||
finally { loading.value = false }
|
||
}
|
||
function reset() {
|
||
Object.assign(q, { name: '', phone: '', workUnit: '', auditStatus: '', status: '' })
|
||
page.pageNum = 1
|
||
load()
|
||
}
|
||
function onSel(rs) { selection.value = rs }
|
||
|
||
function fmtTime(d) {
|
||
if (!d) return ''
|
||
const dt = new Date(d)
|
||
const pad = n => String(n).padStart(2, '0')
|
||
return `${dt.getFullYear()}.${pad(dt.getMonth() + 1)}.${pad(dt.getDate())} ${pad(dt.getHours())}:${pad(dt.getMinutes())}`
|
||
}
|
||
|
||
// ===== 审核 =====
|
||
const auditOpen = ref(false)
|
||
const auditForm = reactive({ expertId: null, name: '', phone: '', result: '2', opinion: '' })
|
||
function onAudit(row) {
|
||
auditForm.expertId = row.expertId
|
||
auditForm.name = row.name
|
||
auditForm.phone = row.phone
|
||
auditForm.result = '2'
|
||
auditForm.opinion = ''
|
||
auditOpen.value = true
|
||
}
|
||
async function submitAudit() {
|
||
submitting.value = true
|
||
try {
|
||
await bizUpdate('expert', {
|
||
expertId: auditForm.expertId,
|
||
auditStatus: auditForm.result,
|
||
auditOpinion: auditForm.opinion,
|
||
auditBy: currentUserName.value,
|
||
auditTime: new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||
})
|
||
ElMessage.success(auditForm.result === '2' ? '审核通过' : '已拒绝')
|
||
auditOpen.value = false
|
||
load()
|
||
} catch { ElMessage.error('审核失败') }
|
||
finally { submitting.value = false }
|
||
}
|
||
|
||
// ===== 批量审核 =====
|
||
async function onBatchAudit() {
|
||
if (!selection.value.length) return ElMessage.warning('请先勾选行')
|
||
try {
|
||
await ElMessageBox.confirm(`确定批量审核选中的 ${selection.value.length} 位专家吗?`, '批量审核', { type: 'warning' })
|
||
} catch { return }
|
||
submitting.value = true
|
||
let ok = 0
|
||
for (const r of selection.value) {
|
||
try {
|
||
await bizUpdate('expert', {
|
||
expertId: r.expertId, auditStatus: '2', auditOpinion: '批量通过',
|
||
auditBy: currentUserName.value,
|
||
auditTime: new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||
})
|
||
ok++
|
||
} catch {}
|
||
}
|
||
submitting.value = false
|
||
ElMessage.success(`批量审核通过 ${ok}/${selection.value.length} 条`)
|
||
selection.value = []
|
||
load()
|
||
}
|
||
|
||
// ===== 启用/禁用: bizUpdate 走 PUT /business/expert, status 是 updateByPrimaryKey 字段 =====
|
||
async function onDisable(row) {
|
||
try {
|
||
await bizUpdate('expert', { expertId: row.expertId, status: 'N' })
|
||
ElMessage.success('已禁用'); load()
|
||
} catch (e) { ElMessage.error(e?.msg || '禁用失败') }
|
||
}
|
||
async function onEnable(row) {
|
||
try {
|
||
await bizUpdate('expert', { expertId: row.expertId, status: 'Y' })
|
||
ElMessage.success('已恢复'); load()
|
||
} catch (e) { ElMessage.error(e?.msg || '恢复失败') }
|
||
}
|
||
|
||
// ===== 已拒绝: 查看拒绝意见 =====
|
||
function onViewAuditOpinion(row) {
|
||
ElMessageBox.alert(
|
||
`拒绝时间: ${fmtTime(row.auditTime) || '-'}\n审批人: ${row.auditBy || '-'}\n拒绝意见: ${row.auditOpinion || '(无)'}`,
|
||
`专家 ${row.name} 审核已拒绝`,
|
||
{ type: 'warning', confirmButtonText: '知道了' }
|
||
).catch(() => {})
|
||
}
|
||
|
||
// ===== 证书查看/下载 =====
|
||
const previewOpen = ref(false)
|
||
const previewUrl = ref('')
|
||
function onPreviewCert(url) {
|
||
if (!url) return
|
||
previewUrl.value = url
|
||
previewOpen.value = true
|
||
}
|
||
function onDownloadCert(url, phone) {
|
||
if (!url) return
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = `${phone || 'cert'}_${Date.now()}`
|
||
document.body.appendChild(a); a.click(); document.body.removeChild(a)
|
||
}
|
||
|
||
// ===== 批量导入专家 (沿用 manager 最佳实践) =====
|
||
const importOpen = ref(false)
|
||
const importing = ref(false)
|
||
const importResultOpen = ref(false)
|
||
const importResult = ref(null)
|
||
const uploadRef = ref(null)
|
||
// el-upload 用原生 XHR, 不走 axios interceptor, 必须显式带 Authorization
|
||
const importHeaders = computed(() => ({ Authorization: 'Bearer ' + (localStorage.getItem('ry_token') || '') }))
|
||
// 后端 baseURL 与 request.js 一致 (/dev-api), 拼绝对路径给 :action
|
||
const importAction = computed(() => '/dev-api/business/expert/importData?updateSupport=false')
|
||
|
||
function openImport() {
|
||
importResult.value = null
|
||
importOpen.value = true
|
||
}
|
||
|
||
// 模板下载: 3 行 fetch + blob (后端 /business/expert/importTemplate)
|
||
function downloadImportTpl() {
|
||
fetch('/dev-api/business/expert/importTemplate', { headers: importHeaders.value })
|
||
.then(r => r.blob())
|
||
.then(blob => {
|
||
const a = document.createElement('a')
|
||
a.href = URL.createObjectURL(blob)
|
||
a.download = '专家批量导入模板.xlsx'
|
||
a.click()
|
||
})
|
||
.catch(() => ElMessage.error('模板下载失败'))
|
||
}
|
||
|
||
// 触发 el-upload 提交 (auto-upload=false 时手动调)
|
||
function submitImportFile() {
|
||
uploadRef.value?.submit()
|
||
}
|
||
|
||
function onImportProgress() { importing.value = true }
|
||
|
||
function onImportSuccess(res) {
|
||
importing.value = false
|
||
// 后端返回 {code,msg,data: ImportResult}; 兼容 res.data 兜底 (防拦截器 unwrap 缺失)
|
||
const result = (res && res.data) ? res.data : res
|
||
// 守卫: 非 ImportResult (后端异常但 HTTP 200, 或 401 重定向回包) 不开 dialog
|
||
if (!result || typeof result.okNum !== 'number') {
|
||
ElMessage.error(res?.msg || '导入失败, 返回数据异常')
|
||
importResult.value = null
|
||
return
|
||
}
|
||
importResult.value = result
|
||
importOpen.value = false
|
||
importResultOpen.value = true
|
||
load()
|
||
}
|
||
|
||
function onImportError(err) {
|
||
importing.value = false
|
||
// el-upload 用原生 XHR, err.response 是后端返回的 JSON 字符串 (RuoYi 拦截器格式 {code,msg})
|
||
let msg = '上传失败, 请重试'
|
||
if (err && err.response != null) {
|
||
try {
|
||
const body = typeof err.response === 'string' ? JSON.parse(err.response) : err.response
|
||
msg = body.msg || msg
|
||
} catch { /* JSON 解析失败, 用兜底 */ }
|
||
} else if (err && err.message) {
|
||
msg = err.message
|
||
}
|
||
ElMessage.error(msg)
|
||
// 清残留, 防止上次成功 result 在空白 dialog 里显示
|
||
importResult.value = null
|
||
}
|
||
|
||
// ===== 批量导入执业证书 =====
|
||
const importCertOpen = ref(false)
|
||
const importCertUrl = ref('')
|
||
function openImportPracticeCert() { importCertUrl.value = ''; importCertOpen.value = true }
|
||
async function submitImportCert() {
|
||
if (!importCertUrl.value) return ElMessage.warning('请选择文件')
|
||
importing.value = true
|
||
await new Promise(r => setTimeout(r, 500))
|
||
importing.value = false
|
||
ElMessage.info('已提交批量导入执业证书任务 — 待后端实现')
|
||
importCertOpen.value = false
|
||
}
|
||
|
||
// ===== 批量导入职称证明 =====
|
||
const importTitleCertOpen = ref(false)
|
||
const importTitleCertUrl = ref('')
|
||
function openImportTitleCert() { importTitleCertUrl.value = ''; importTitleCertOpen.value = true }
|
||
async function submitImportTitleCert() {
|
||
if (!importTitleCertUrl.value) return ElMessage.warning('请选择文件')
|
||
importing.value = true
|
||
await new Promise(r => setTimeout(r, 500))
|
||
importing.value = false
|
||
ElMessage.info('已提交批量导入职称证明任务 — 待后端实现')
|
||
importTitleCertOpen.value = false
|
||
}
|
||
|
||
onMounted(load)
|
||
// 从 ExpertNew 跳回来时刷新列表
|
||
watch(() => route.fullPath, () => {
|
||
if (route.path === listBasePath.value) load()
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.experts-list { padding: 16px; }
|
||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||
.filter-form { margin-bottom: 12px; }
|
||
.batch-bar { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; flex-wrap: wrap; }
|
||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||
|
||
.cert-preview { display: flex; justify-content: center; align-items: center; min-height: 200px; background: #f5f7fa; border-radius: 4px; padding: 8px; }
|
||
.cert-preview-error { color: #909399; padding: 40px 0; }
|
||
</style>
|