feat: 合并 admin/manager 重复页 + 公示意向 + 劳务签署
页合并 (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)
This commit is contained in:
@@ -4,11 +4,11 @@
|
||||
|
||||
<!-- ========== 筛选区 ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="项目编号"><el-input v-model="q.projectNo" clearable /></el-form-item>
|
||||
<el-form-item label="项目名称"><el-input v-model="q.projectName" clearable /></el-form-item>
|
||||
<el-form-item label="姓名"><el-input v-model="q.name" clearable /></el-form-item>
|
||||
<el-form-item label="工作单位"><el-input v-model="q.workUnit" clearable /></el-form-item>
|
||||
<el-form-item label="手机号"><el-input v-model="q.phone" clearable /></el-form-item>
|
||||
<el-form-item label="项目编号"><el-input v-model="q.projectNo" placeholder="输入项目编号" clearable /></el-form-item>
|
||||
<el-form-item label="项目名称"><el-input v-model="q.projectName" placeholder="输入项目名称" clearable /></el-form-item>
|
||||
<el-form-item label="姓名"><el-input v-model="q.name" placeholder="输入姓名" clearable /></el-form-item>
|
||||
<el-form-item label="工作单位"><el-input v-model="q.workUnit" placeholder="输入工作单位" clearable /></el-form-item>
|
||||
<el-form-item label="手机号"><el-input v-model="q.phone" placeholder="输入手机号" clearable /></el-form-item>
|
||||
<el-form-item label="审核状态">
|
||||
<el-select v-model="q.intentStatus" clearable style="width: 130px">
|
||||
<el-option label="待审核" value="待审核" />
|
||||
@@ -19,13 +19,12 @@
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
<el-button type="success" @click="exportCsv">导出CSV</el-button>
|
||||
<el-button type="success" @click="doExport">导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 表格 ========== -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSelChange">
|
||||
<el-table-column type="selection" width="46" />
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="projectNo" label="意向项目编号" width="140" />
|
||||
<el-table-column prop="projectName" label="意向项目名称" min-width="180" show-overflow-tooltip />
|
||||
@@ -39,13 +38,13 @@
|
||||
<el-tag :type="row.userId ? 'success' : 'warning'">{{ row.userId ? '存在' : '不存在' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审核状态" width="100" align="center">
|
||||
<el-table-column v-if="false" label="审核状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.intentStatus)">{{ row.intentStatus || '待审核' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" />
|
||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||||
<el-table-column v-if="false" label="操作" width="220" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.intentStatus !== '已通过'" type="primary" link size="small" @click="changeStatus(row, '已通过')">通过</el-button>
|
||||
<el-button v-if="row.intentStatus !== '已拒绝'" type="danger" link size="small" @click="changeStatus(row, '已拒绝')">拒绝</el-button>
|
||||
@@ -55,9 +54,6 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<div class="pager-left">
|
||||
<el-button type="danger" :disabled="!selected.length" @click="removeBatch">批量删除 ({{ selected.length }})</el-button>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
@@ -69,12 +65,12 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
listPublicityExecutionIntent,
|
||||
updatePublicityExecutionIntent,
|
||||
deletePublicityExecutionIntent
|
||||
deletePublicityExecutionIntent,
|
||||
exportPublicityExecutionIntent
|
||||
} from '@/api/public'
|
||||
|
||||
const q = ref({ projectNo: '', projectName: '', name: '', workUnit: '', phone: '', intentStatus: '' })
|
||||
const rows = ref([])
|
||||
const selected = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
@@ -97,8 +93,6 @@ function reset() {
|
||||
load()
|
||||
}
|
||||
|
||||
function onSelChange(arr) { selected.value = arr }
|
||||
|
||||
function statusTagType(s) {
|
||||
if (s === '已通过') return 'success'
|
||||
if (s === '已拒绝') return 'danger'
|
||||
@@ -128,39 +122,23 @@ async function removeOne(row) {
|
||||
}
|
||||
}
|
||||
|
||||
async function removeBatch() {
|
||||
if (!selected.value.length) return
|
||||
// 后端导出 .xlsx (按当前筛选条件全量下载)
|
||||
async function doExport() {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认删除 ${selected.value.length} 条执行意向?`, '提示', { type: 'warning' })
|
||||
} catch { return }
|
||||
try {
|
||||
const ids = selected.value.map(r => r.intentId)
|
||||
await deletePublicityExecutionIntent(ids)
|
||||
ElMessage.success('已删除')
|
||||
load()
|
||||
const res = await exportPublicityExecutionIntent({ ...q.value })
|
||||
const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `执行意向_${new Date().toISOString().slice(0, 10)}.xlsx`
|
||||
document.body.appendChild(a); a.click(); document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
ElMessage.success('导出成功')
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || '删除失败')
|
||||
ElMessage.error(e?.msg || e?.message || '导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
function exportCsv() {
|
||||
if (!rows.value.length) return ElMessage.warning('当前列表无数据')
|
||||
const headers = ['项目编号', '项目名称', '姓名', '工作单位', '部门', '职务', '手机号', '账号状态', '审核状态', '创建时间']
|
||||
const csvRows = rows.value.map(r => [
|
||||
r.projectNo, r.projectName, r.name, r.workUnit, r.department, r.position,
|
||||
r.phone, r.userId ? '存在' : '不存在', r.intentStatus || '待审核', r.createTime
|
||||
].map(v => `"` + String(v ?? '').replace(/"/g, '""') + `"`).join(','))
|
||||
const csv = '' + [headers.join(','), ...csvRows].join('\r\n')
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `执行意向_${new Date().toISOString().slice(0, 10)}.csv`
|
||||
document.body.appendChild(a); a.click(); document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
ElMessage.success(`已导出 ${rows.value.length} 条`)
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
@@ -168,6 +146,5 @@ onMounted(load)
|
||||
.manager-exec-intent { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: space-between; align-items: center; margin-top: 12px; }
|
||||
.pager-left { display: flex; gap: 8px; }
|
||||
.pager { display: flex; justify-content: flex-end; align-items: center; margin-top: 12px; }
|
||||
</style>
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟨 合规 服务机构查看 (manager/executor-orgs)
|
||||
说明: 原 manager/Orgs.vue 拆出 executor 一支, 仅查看
|
||||
接口: bizList('org', q)
|
||||
-->
|
||||
<div class="page-card manager-executor-orgs">
|
||||
<div class="breadcrumb">首页 / 服务机构管理</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="公司名称">
|
||||
<el-input v-model="q.orgName" placeholder="输入公司名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="税号/统一社会信用代码">
|
||||
<el-input v-model="q.taxNo" placeholder="输入税号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="合作状态">
|
||||
<el-select v-model="q.status" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="正常" value="0" />
|
||||
<el-option label="禁用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="orgName" label="公司名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="address" label="公司地址" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="taxNo" label="税号/统一社会信用代码" min-width="180" />
|
||||
<el-table-column label="合作状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status === '1' ? '禁用' : '正常' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建日期" width="120" align="center">
|
||||
<template #default="{ row }">{{ row.createTime ? String(row.createTime).slice(0,10) : '' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contactName" label="联系人" width="100" align="center" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" width="130" align="center" />
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="view(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="goPeople(row)">人员管理</el-button>
|
||||
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
|
||||
{{ isDisabled(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>
|
||||
|
||||
<el-dialog v-model="detailOpen" title="服务机构详情" width="640px" v-if="currentRow">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="公司类型">
|
||||
<el-tag type="primary">执行方</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="公司名称">{{ currentRow.orgName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="公司地址">{{ currentRow.address }}</el-descriptions-item>
|
||||
<el-descriptions-item label="税号/统一社会信用代码">{{ currentRow.taxNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合作状态">
|
||||
<el-tag :type="statusTagType(currentRow.status)">{{ currentRow.status === '1' ? '禁用' : '正常' }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建日期">{{ currentRow.createTime ? String(currentRow.createTime).slice(0,10) : '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系人">{{ currentRow.contactName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ currentRow.contactPhone || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="detailOpen=false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { bizList, toggleOrgStatus } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const q = ref({ orgType: 'executor', orgName: '', taxNo: '', status: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
const currentRow = ref(null)
|
||||
const detailOpen = ref(false)
|
||||
|
||||
function statusTagType(s) {
|
||||
// 统一: 0=正常(success), 1=禁用(danger)
|
||||
return s === '1' ? 'danger' : 'success'
|
||||
}
|
||||
function isDisabled(row) {
|
||||
return row.status === '1'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await bizList('org', { ...q.value, pageNum: page.pageNum, pageSize: page.pageSize })
|
||||
rows.value = data?.rows || []; page.total = data?.total || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function reset() { q.value = { orgType: 'executor', orgName: '', taxNo: '', status: '' }; page.pageNum = 1; load() }
|
||||
function view(row) { currentRow.value = row; detailOpen.value = true }
|
||||
function goPeople(row) {
|
||||
router.push({ path: '/manager/executor-people', query: { orgId: row.orgId, orgName: row.orgName } })
|
||||
}
|
||||
|
||||
// 仅禁用主账号 (走 BizOrgServiceImpl.toggleStatus: 同步 biz_org.status + sys_user.status)
|
||||
// 子账号由 BizAuthController.smsLogin 4.5 节校验主账号状态拦截
|
||||
async function onToggleStatus(row) {
|
||||
const disabled = isDisabled(row)
|
||||
const newStatus = disabled ? '0' : '1' // 0=正常, 1=禁用
|
||||
const label = disabled ? '恢复' : '禁用'
|
||||
if (!disabled) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要${label}「${row.orgName}」吗? ${label}后该企业主账号无法登录, 企业下子账号也将被拦截`,
|
||||
'确认',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
}
|
||||
try {
|
||||
await toggleOrgStatus(row.orgId, newStatus)
|
||||
ElMessage.success('已' + label)
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manager-executor-orgs { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -1,228 +0,0 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟨 合规 服务机构下人员 (manager/executor-people)
|
||||
入口: org 表格行内「人员管理」按钮 (router.push 带 orgId/orgName)
|
||||
接口: bizList('person', q) / bizUpdate / bizAdd / executorImport / executorImportTemplate
|
||||
-->
|
||||
<div class="page-card manager-executor-people">
|
||||
<div class="breadcrumb">
|
||||
首页 / 服务机构管理 / 人员管理{{ orgName ? ' (' + orgName + ')' : '' }}
|
||||
</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="q.name" placeholder="姓名" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="q.phone" placeholder="手机号" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="企业名称">
|
||||
<el-input v-model="q.orgName" placeholder="企业名称" clearable style="width:180px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门">
|
||||
<el-input v-model="q.department" placeholder="部门" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职务">
|
||||
<el-input v-model="q.position" placeholder="职务" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="goNew">新建人员</el-button>
|
||||
<el-button @click="openImport">批量导入</el-button>
|
||||
<span class="filter-tip">*批量导入模板与列表表项一致</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column prop="name" label="姓名" min-width="100" />
|
||||
<el-table-column prop="phone" label="手机号" min-width="130" />
|
||||
<el-table-column prop="orgName" label="企业名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="department" label="部门" min-width="100" />
|
||||
<el-table-column prop="position" label="职务" min-width="100" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status === '1' ? '禁用' : '正常' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
|
||||
{{ isDisabled(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>
|
||||
|
||||
<el-dialog v-model="importOpen" title="批量导入" width="520px" :close-on-click-modal="false">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="选择文件">
|
||||
<el-upload ref="uploadRef" :auto-upload="false" :limit="1" accept=".xls,.xlsx" :on-change="onFileChange" :on-remove="onFileRemove">
|
||||
<el-button>选择文件</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="">
|
||||
<el-button link type="primary" @click="downloadTemplate">批量导入模板下载</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item><span class="filter-tip">*导入列: 姓名/手机号/所属公司/部门/职务/角色</span></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="importOpen = false">取消</el-button>
|
||||
<el-button type="primary" :loading="importing" @click="submitImport">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { bizList, bizUpdate, importPerson, downloadImportTemplate } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const orgId = computed(() => route.query.orgId ? Number(route.query.orgId) : null)
|
||||
const orgName = computed(() => route.query.orgName || '')
|
||||
|
||||
const q = reactive({ name: '', phone: '', orgName: '', department: '', position: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
function statusTagType(s) {
|
||||
// 统一: 0=正常(success), 1=禁用(danger)
|
||||
return s === '1' ? 'danger' : 'success'
|
||||
}
|
||||
function isDisabled(row) {
|
||||
return row.status === '1'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
...q,
|
||||
orgType: 'executor',
|
||||
...(orgId.value ? { orgId: orgId.value } : {}),
|
||||
pageNum: page.pageNum,
|
||||
pageSize: page.pageSize,
|
||||
}
|
||||
const { data } = await bizList('person', params)
|
||||
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: '', orgName: '', department: '', position: '' })
|
||||
page.pageNum = 1
|
||||
load()
|
||||
}
|
||||
|
||||
async function onToggleStatus(row) {
|
||||
const disabled = isDisabled(row)
|
||||
// sys_user.status 是 CHAR(1), 只能写 '0'/'1' (禁用='1', 启用='0')
|
||||
// 必须带 userId: BizPersonServiceImpl.updateByPrimaryKey 仅在 userId != null 时
|
||||
// 才同步 status 到 sys_user, 否则只更新 biz_person (而 biz_person.status 已删除)
|
||||
const newStatus = disabled ? '0' : '1'
|
||||
const label = disabled ? '启用' : '禁用'
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要${label}「${row.name}」吗?`,
|
||||
'确认',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
try {
|
||||
await bizUpdate('person', { personId: row.personId, userId: row.userId, status: newStatus })
|
||||
ElMessage.success('已' + label)
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
|
||||
}
|
||||
|
||||
// ========== 新建人员 ==========
|
||||
function goNew() {
|
||||
router.push({
|
||||
path: '/manager/executor-people/new',
|
||||
query: {
|
||||
...(orgId.value ? { orgId: orgId.value } : {}),
|
||||
...(orgName.value ? { orgName: orgName.value } : {})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 批量导入 ==========
|
||||
const importOpen = ref(false)
|
||||
const importing = ref(false)
|
||||
const uploadRef = ref()
|
||||
const importFile = ref(null)
|
||||
|
||||
function openImport() { importFile.value = null; importOpen.value = true }
|
||||
function onFileChange(file) { importFile.value = file.raw }
|
||||
function onFileRemove() { importFile.value = null }
|
||||
async function downloadTemplate() {
|
||||
try {
|
||||
const res = await downloadImportTemplate('executor')
|
||||
const blob = new Blob([res.data], { type: res.data.type || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = '服务机构人员导入模板.xlsx'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch { ElMessage.error('模板下载失败') }
|
||||
}
|
||||
async function submitImport() {
|
||||
if (!importFile.value) { ElMessage.warning('请先选择文件'); return }
|
||||
importing.value = true
|
||||
try {
|
||||
const res = await importPerson('executor', importFile.value)
|
||||
const payload = res.data || res
|
||||
const ok = payload.ok ?? 0
|
||||
const total = payload.total ?? 0
|
||||
const failed = (payload.results || []).filter(r => !r.ok)
|
||||
if (failed.length === 0) {
|
||||
ElMessage.success(`导入成功 ${ok}/${total}`)
|
||||
} else {
|
||||
ElMessageBox.alert(
|
||||
`成功 ${ok} 条, 失败 ${failed.length} 条:\n` +
|
||||
failed.map(f => ` 第${f.rowNo}行 ${f.name || ''}: ${f.message}`).join('\n'),
|
||||
'导入结果', { type: 'warning' }
|
||||
)
|
||||
}
|
||||
importOpen.value = false
|
||||
load()
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || '导入失败')
|
||||
} finally {
|
||||
importing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manager-executor-people { 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; }
|
||||
.filter-tip { color: #909399; font-size: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -1,173 +0,0 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟨 合规 服务机构下人员 新建/编辑 (manager/executor-people/new, manager/executor-people/edit/:id)
|
||||
结构对齐 admin/ExpertNew.vue
|
||||
unitType 硬编码 'executor'
|
||||
角色固定为「会议执行」(执行方子类型, 暂唯一)
|
||||
-->
|
||||
<div class="page-card manager-executor-person-new">
|
||||
<div class="breadcrumb">
|
||||
<a @click="goBack">服务机构下人员</a> > {{ isEdit ? '编辑人员' : '新建人员' }}{{ orgName ? ' (' + orgName + ')' : '' }}
|
||||
</div>
|
||||
|
||||
<el-card v-loading="loadingDetail" shadow="never" class="form-card nested">
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
||||
<el-form-item label="用户名" prop="userName">
|
||||
<el-input v-model="form.userName" placeholder="登录用户名" maxlength="50" :disabled="isEdit" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="form.name" placeholder="姓名" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="手机号" maxlength="11" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属公司" prop="orgName">
|
||||
<el-input v-model="form.orgName" placeholder="所属公司" maxlength="200" :disabled="!!orgId" />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门">
|
||||
<el-input v-model="form.department" placeholder="部门" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职务">
|
||||
<el-input v-model="form.position" placeholder="职务" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色">
|
||||
<span style="color:#303133">会议执行 <span style="color:#909399;font-size:12px">(执行方人员固定, 暂唯一子类型)</span></span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div class="form-actions">
|
||||
<el-button type="primary" :loading="saving" @click="onSave">保存</el-button>
|
||||
<el-button @click="confirmCancel">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, computed, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { bizAdd, bizUpdate, bizGet } from '@/api/public'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const formRef = ref(null)
|
||||
const saving = ref(false)
|
||||
const loadingDetail = ref(false)
|
||||
const isEdit = !!route.params.id
|
||||
const personId = route.params.id || null
|
||||
|
||||
const orgName = computed(() => route.query.orgName || '')
|
||||
const orgId = computed(() => route.query.orgId ? Number(route.query.orgId) : null)
|
||||
|
||||
const form = reactive({
|
||||
personId: null,
|
||||
userName: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
orgName: '',
|
||||
department: '',
|
||||
position: ''
|
||||
})
|
||||
|
||||
const rules = {
|
||||
userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^1\d{10}$/, message: '手机号格式错误', trigger: 'blur' }
|
||||
],
|
||||
orgName: [{ required: true, message: '请输入所属公司', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
router.push('/manager/executor-people')
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
if (!personId) return
|
||||
loadingDetail.value = true
|
||||
try {
|
||||
const res = await bizGet('person', personId)
|
||||
if (res?.code === 200 && res.data) {
|
||||
const d = res.data
|
||||
Object.assign(form, {
|
||||
personId: d.personId,
|
||||
userName: d.userName || '',
|
||||
name: d.name || '',
|
||||
phone: d.phone || '',
|
||||
orgName: d.orgName || '',
|
||||
department: d.department || '',
|
||||
position: d.position || ''
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '加载失败')
|
||||
goBack()
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || e?.message || '加载失败')
|
||||
goBack()
|
||||
} finally {
|
||||
loadingDetail.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function confirmCancel() {
|
||||
const hasContent = form.name || form.phone || form.userName || form.orgName
|
||||
if (!isEdit && !hasContent) { goBack(); return }
|
||||
ElMessageBox.confirm('确定取消?未保存的内容将丢失', '提示', { type: 'warning' })
|
||||
.then(() => goBack())
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
try { await formRef.value.validate() } catch { return }
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = { ...form, unitType: 'executor', role: 'meetingExecutor' }
|
||||
if (!isEdit && orgId.value) payload.orgId = orgId.value
|
||||
if (isEdit) {
|
||||
delete payload.personId
|
||||
await bizUpdate('person', payload)
|
||||
ElMessage.success('修改成功')
|
||||
} else {
|
||||
payload.status = '0'
|
||||
payload.password = '123456' // @JsonProperty("password") → loginPassword
|
||||
await bizAdd('person', payload)
|
||||
ElMessage.success('新建成功,默认密码 123456')
|
||||
}
|
||||
goBack()
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || e?.message || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 预填 orgName (从 org 页面跳来时)
|
||||
if (!isEdit && orgName.value && !form.orgName) {
|
||||
form.orgName = orgName.value
|
||||
}
|
||||
if (isEdit) loadDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manager-executor-person-new { max-width: 1000px; }
|
||||
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 16px; }
|
||||
.breadcrumb a { color: var(--brand-primary); cursor: pointer; text-decoration: none; }
|
||||
.breadcrumb a:hover { color: var(--brand-primary-deep, var(--brand-primary)); }
|
||||
|
||||
.form-card.nested { background: transparent; border: none; box-shadow: none; }
|
||||
:deep(.form-card .el-card__body) { padding: 24px 28px; }
|
||||
|
||||
.form-actions { margin-top: 16px; display: flex; gap: 8px; }
|
||||
|
||||
:deep(.el-button--primary) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; }
|
||||
:deep(.el-button--primary:hover) { background: var(--brand-primary-deep, var(--brand-primary)); border-color: var(--brand-primary-deep, var(--brand-primary)); }
|
||||
:deep(.el-button) { border-radius: 4px; }
|
||||
:deep(.el-input__wrapper) { border-radius: 4px; }
|
||||
</style>
|
||||
@@ -1,336 +0,0 @@
|
||||
<template>
|
||||
<!--
|
||||
manager 视角专家新建/编辑独立页
|
||||
1:1 抄 admin/ExpertNew.vue, 仅 goBack 跳 /manager/experts
|
||||
通过 route.params.id 区分新建/编辑
|
||||
-->
|
||||
<div class="page-card expert-new">
|
||||
<div class="breadcrumb">
|
||||
<a @click="goBack">专家审核</a> > {{ isEdit ? '编辑专家' : '新建专家' }}
|
||||
</div>
|
||||
|
||||
<el-card v-loading="loadingDetail" shadow="never" class="form-card nested">
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入姓名" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="请输入手机号" maxlength="11" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="工作单位" prop="workUnit">
|
||||
<el-input v-model="form.workUnit" placeholder="请输入工作单位" maxlength="200" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="科室" prop="department">
|
||||
<doctor-dept-select
|
||||
v-model="form.department"
|
||||
value-field="label"
|
||||
placeholder="输入关键词搜索"
|
||||
filterable
|
||||
style="width:100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="职称" prop="title">
|
||||
<doctor-title-select
|
||||
v-model="form.title"
|
||||
value-field="label"
|
||||
placeholder="输入关键词搜索"
|
||||
filterable
|
||||
style="width:100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="地区">
|
||||
<area-cascader
|
||||
v-model="form.region"
|
||||
format="string"
|
||||
join-sep="/"
|
||||
placeholder="请选择省/市/区"
|
||||
style="width:100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="执业医师证书">
|
||||
<oss-image-uploader
|
||||
v-model="form.practiceCertUrl"
|
||||
dir="ry8080/expert/practice/"
|
||||
placeholder="+"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="职称证明">
|
||||
<oss-image-uploader
|
||||
v-model="form.titleCertUrl"
|
||||
dir="ry8080/expert/title/"
|
||||
placeholder="+"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="身份证件号码">
|
||||
<el-input v-model="form.idCard" placeholder="请输入身份证件号码" maxlength="18" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="身份证件照片">
|
||||
<id-card-uploader
|
||||
v-model:front-url="form.idCardFrontUrl"
|
||||
v-model:back-url="form.idCardBackUrl"
|
||||
dir="ry8080/expert/idcard/"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="银行卡号">
|
||||
<el-input v-model="form.bankCard" placeholder="请输入银行卡号" maxlength="19" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="银行名称">
|
||||
<el-input v-model="form.bankName" placeholder="如: 中国工商银行" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户行省份">
|
||||
<el-input v-model="form.bankProvince" placeholder="如: 北京市" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开户行城市">
|
||||
<el-input v-model="form.bankCity" placeholder="如: 北京市" maxlength="50" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="12">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="开户行地址">
|
||||
<el-input v-model="form.bankAddress" placeholder="请输入开户行详细地址" maxlength="200" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div class="form-actions">
|
||||
<el-button type="primary" :loading="saving" @click="onSave">保存</el-button>
|
||||
<el-button @click="confirmCancel">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { bizAdd, bizUpdate, bizGet } from '@/api/public'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import DoctorTitleSelect from '@/components/DoctorTitleSelect.vue'
|
||||
import DoctorDeptSelect from '@/components/DoctorDeptSelect.vue'
|
||||
import AreaCascader from '@/components/AreaCascader.vue'
|
||||
import IdCardUploader from '@/components/IdCardUploader.vue'
|
||||
import OssImageUploader from '@/components/OssImageUploader.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const formRef = ref(null)
|
||||
const saving = ref(false)
|
||||
const loadingDetail = ref(false)
|
||||
const isEdit = !!route.params.id
|
||||
const expertId = route.params.id || null
|
||||
|
||||
const form = reactive({
|
||||
expertId: null,
|
||||
name: '',
|
||||
phone: '',
|
||||
workUnit: '',
|
||||
department: '',
|
||||
title: '',
|
||||
region: '',
|
||||
practiceCertUrl: '',
|
||||
titleCertUrl: '',
|
||||
idCard: '',
|
||||
idCardFrontUrl: '',
|
||||
idCardBackUrl: '',
|
||||
bankCard: '',
|
||||
bankName: '',
|
||||
bankProvince: '',
|
||||
bankCity: '',
|
||||
bankAddress: '',
|
||||
})
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^1[0-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' },
|
||||
],
|
||||
workUnit: [{ required: true, message: '请输入工作单位', trigger: 'blur' }],
|
||||
department:[{ required: true, message: '请选择科室', trigger: 'change' }],
|
||||
title: [{ required: true, message: '请选择职称', trigger: 'change' }],
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
router.push('/manager/experts')
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
if (!expertId) return
|
||||
loadingDetail.value = true
|
||||
try {
|
||||
const res = await bizGet('expert', expertId)
|
||||
if (res?.code === 200 && res.data) {
|
||||
const d = res.data
|
||||
Object.assign(form, {
|
||||
expertId: d.expertId,
|
||||
name: d.name || '',
|
||||
phone: d.phone || '',
|
||||
workUnit: d.workUnit || '',
|
||||
department: d.department || '',
|
||||
title: d.title || '',
|
||||
region: d.region || '',
|
||||
practiceCertUrl: d.practiceCertUrl || '',
|
||||
titleCertUrl: d.titleCertUrl || '',
|
||||
idCard: d.idCard || '',
|
||||
idCardFrontUrl: d.idCardFrontUrl || '',
|
||||
idCardBackUrl: d.idCardBackUrl || '',
|
||||
bankCard: d.bankCard || '',
|
||||
bankName: d.bankName || '',
|
||||
bankProvince: d.bankProvince || '',
|
||||
bankCity: d.bankCity || '',
|
||||
bankAddress: d.bankAddress || '',
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '加载失败')
|
||||
goBack()
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || e?.message || '加载失败')
|
||||
goBack()
|
||||
} finally {
|
||||
loadingDetail.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function confirmCancel() {
|
||||
const hasContent = form.name || form.phone || form.workUnit || form.department || form.title
|
||||
if (!isEdit && !hasContent) { goBack(); return }
|
||||
ElMessageBox.confirm('确定取消?未保存的内容将丢失', '提示', { type: 'warning' })
|
||||
.then(() => goBack())
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function isDuplicateError(msg) {
|
||||
return /已存在|重复|已注册|duplicate|uk_expert_phone/i.test(msg || '')
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
} catch {
|
||||
ElMessage.warning('请填写必填项')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = { ...form }
|
||||
if (!isEdit) {
|
||||
// 新建: manager 创建专家, 自动通过审核, 状态正常
|
||||
// 注意: 不要把 expertId 也删掉, 后端 updateByPrimaryKey / insert 都依赖 expert_id
|
||||
payload.auditStatus = '2'
|
||||
payload.auditBy = userStore.user?.userName || userStore.user?.nickName || 'manager'
|
||||
payload.auditTime = new Date().toISOString().slice(0, 19).replace('T', ' ')
|
||||
payload.status = 'Y'
|
||||
}
|
||||
// __silentError: 拦截器不自动 toast, 由本组件根据业务语义选择 黄警 (重复) / 红错 (其它)
|
||||
const opts = { __silentError: true }
|
||||
const res = isEdit
|
||||
? await bizUpdate('expert', payload, opts)
|
||||
: await bizAdd('expert', payload, opts)
|
||||
if (res?.code === 200) {
|
||||
if (!isEdit && res.data) {
|
||||
const u = res.data
|
||||
ElMessageBox.alert(
|
||||
`专家账号已创建\n账号: ${u.userName || form.phone}\n初始密码: ${u.password || form.phone}\n请记录后告知使用人 (密码仅本次显示)`,
|
||||
'创建成功',
|
||||
{ type: 'success', confirmButtonText: '我已记录, 返回' }
|
||||
).then(() => goBack())
|
||||
.catch(() => goBack())
|
||||
} else {
|
||||
ElMessage.success(isEdit ? '修改成功' : '新建成功')
|
||||
goBack()
|
||||
}
|
||||
} else {
|
||||
const msg = res?.msg || '保存失败'
|
||||
if (isDuplicateError(msg)) ElMessage.warning('该手机号已注册,请更换')
|
||||
else ElMessage.error(msg)
|
||||
}
|
||||
} catch (e) {
|
||||
const msg = e?.msg || e?.message || '保存失败'
|
||||
if (isDuplicateError(msg)) ElMessage.warning('该手机号已注册,请更换')
|
||||
else ElMessage.error(msg)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (isEdit) loadDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.expert-new { max-width: 1200px; }
|
||||
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 16px; }
|
||||
.breadcrumb a { color: var(--brand-primary); cursor: pointer; text-decoration: none; }
|
||||
.breadcrumb a:hover { color: var(--brand-primary-deep, var(--brand-primary)); }
|
||||
|
||||
.form-card.nested { background: transparent; border: none; box-shadow: none; }
|
||||
:deep(.form-card .el-card__body) { padding: 24px 28px; }
|
||||
|
||||
.form-actions { margin-top: 16px; display: flex; gap: 8px; }
|
||||
|
||||
:deep(.el-button--primary) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; }
|
||||
:deep(.el-button--primary:hover) { background: var(--brand-primary-deep, var(--brand-primary)); border-color: var(--brand-primary-deep, var(--brand-primary)); }
|
||||
:deep(.el-button) { border-radius: 4px; }
|
||||
:deep(.el-input__wrapper), :deep(.el-textarea__inner) { border-radius: 4px; }
|
||||
</style>
|
||||
@@ -1,472 +0,0 @@
|
||||
<template>
|
||||
<div class="page-card manager-experts">
|
||||
<div class="breadcrumb">首页 / 专家审核</div>
|
||||
|
||||
<!-- ========== 筛选区 (与 People.vue 风格一致) ========== -->
|
||||
<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 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>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 批量按钮区 (与 People.vue 风格一致) ========== -->
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="openAdd">新建专家</el-button>
|
||||
<el-button @click="openImport">批量导入</el-button>
|
||||
<el-button @click="onBatchAudit" :disabled="!selection.length">批量审核</el-button>
|
||||
<el-button @click="openImportPracticeCert">批量导入执业证书/证明</el-button>
|
||||
<el-button @click="openImportTitleCert">批量导入职称证明</el-button>
|
||||
</div>
|
||||
|
||||
<!-- ========== 表格 (按原型 11 列, 按 left 顺序) ========== -->
|
||||
<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="180">
|
||||
<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="180">
|
||||
<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="280" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="onAudit(row)">审核</el-button>
|
||||
<el-button link type="primary" @click="goEdit(row)">修改</el-button>
|
||||
<el-button link type="primary" @click="onView(row)">查看</el-button>
|
||||
<el-button link :type="row.status === 'N' ? 'success' : 'danger'" @click="row.status === 'N' ? onEnable(row) : onDisable(row)">
|
||||
{{ row.status === 'N' ? '恢复' : '禁用' }}
|
||||
</el-button>
|
||||
<el-button link type="info" v-if="row.auditStatus === '3'" @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>
|
||||
</div>
|
||||
|
||||
<!-- ========== Dialog: 新建/编辑专家 (按原型 top=239 确定/取消) ========== -->
|
||||
<el-dialog v-model="editOpen" :title="editForm.expertId ? '编辑专家' : '新建专家'" width="640px">
|
||||
<el-form :model="editForm" label-width="120px" :rules="editRules" ref="editFormRef">
|
||||
<el-form-item label="姓名" prop="name"><el-input v-model="editForm.name" /></el-form-item>
|
||||
<el-form-item label="手机号" prop="phone"><el-input v-model="editForm.phone" maxlength="11" /></el-form-item>
|
||||
<el-form-item label="工作单位" prop="workUnit"><el-input v-model="editForm.workUnit" /></el-form-item>
|
||||
<el-form-item label="科室" prop="department"><el-input v-model="editForm.department" placeholder="如:呼吸科" /></el-form-item>
|
||||
<el-form-item label="职称" prop="title">
|
||||
<el-select v-model="editForm.title" placeholder="请选择" style="width:100%">
|
||||
<el-option label="住院医师" value="住院医师" />
|
||||
<el-option label="主治医师" value="主治医师" />
|
||||
<el-option label="副主任医师" value="副主任医师" />
|
||||
<el-option label="主任医师" value="主任医师" />
|
||||
<el-option label="教授" value="教授" />
|
||||
<el-option label="副教授" value="副教授" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="执业证书 URL"><el-input v-model="editForm.practiceCertUrl" /></el-form-item>
|
||||
<el-form-item label="职称证明 URL"><el-input v-model="editForm.titleCertUrl" /></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="editOpen=false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitting" @click="submitEdit">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ========== Dialog: 审核 (原型 top=404 确认/取消) ========== -->
|
||||
<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>
|
||||
|
||||
<!-- ========== Dialog: 批量导入 (原型 top=239 确定/取消, 含 radio 通过/退回) ========== -->
|
||||
<el-dialog v-model="importOpen" title="批量导入专家" width="520px">
|
||||
<div style="margin-bottom:12px;font-size:13px">
|
||||
<el-link type="primary" @click="downloadImportTpl">批量导入模板下载</el-link>
|
||||
</div>
|
||||
<p style="color:#606266;margin-bottom:8px">*批量导入模板 xls 与列表表项一致</p>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="审核结果">
|
||||
<el-radio-group v-model="importAuditResult">
|
||||
<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="importAuditOpinion" type="textarea" :rows="3" placeholder="*新建和批量导入审核状态直接为审核通过" />
|
||||
</el-form-item>
|
||||
<el-form-item label="选择文件">
|
||||
<el-input v-model="importFileUrl" placeholder="上传文件 URL 或选择文件" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="importOpen=false">取消</el-button>
|
||||
<el-button type="primary" :loading="importing" @click="submitImport">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- ========== Dialog: 批量导入执业证书/证明 (原型 u6124) ========== -->
|
||||
<el-dialog v-model="importCertOpen" title="批量导入执业证书/证明" width="520px">
|
||||
<p style="color:#606266;margin-bottom:8px">*退回后,如专家重新提交,则审核状态变为待审核</p>
|
||||
<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>
|
||||
|
||||
<!-- ========== Dialog: 批量导入职称证明 (原型 u6126) ========== -->
|
||||
<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="viewOpen" title="专家详情" width="640px">
|
||||
<el-descriptions :column="2" border v-if="currentRow">
|
||||
<el-descriptions-item label="姓名">{{ currentRow.name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号">{{ currentRow.phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="工作单位">{{ currentRow.workUnit }}</el-descriptions-item>
|
||||
<el-descriptions-item label="科室">{{ currentRow.department }}</el-descriptions-item>
|
||||
<el-descriptions-item label="职称">{{ currentRow.title }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审核状态"><audit-status-tag :status="currentRow.auditStatus" /></el-descriptions-item>
|
||||
<el-descriptions-item label="执业证书">
|
||||
<el-link v-if="currentRow.practiceCertUrl" type="primary" @click="onPreviewCert(currentRow.practiceCertUrl)">查看证书</el-link>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="职称证明">
|
||||
<el-link v-if="currentRow.titleCertUrl" type="primary" @click="onPreviewCert(currentRow.titleCertUrl)">查看证书</el-link>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审批时间">{{ fmtTime(currentRow.auditTime) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审批人">{{ currentRow.auditBy }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 证书/证明 图片预览 dialog (1:1 抄 admin/Experts.vue) -->
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { bizList, bizAdd, bizUpdate } from '@/api/public'
|
||||
import AuditStatusTag from '@/components/AuditStatusTag.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const q = ref({ name: '', phone: '', workUnit: '', auditStatus: '' })
|
||||
const rows = ref([])
|
||||
const selection = ref([])
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await bizList('expert', { ...q.value, pageNum: page.pageNum, pageSize: page.pageSize })
|
||||
rows.value = data?.rows || []
|
||||
page.total = data?.total || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function reset() {
|
||||
q.value = { name: '', phone: '', workUnit: '', auditStatus: '' }
|
||||
page.pageNum = 1
|
||||
load()
|
||||
}
|
||||
function onSel(rs) { selection.value = rs }
|
||||
|
||||
// auditStatusText / auditStatusClass 已删除: 改用 AuditStatusTag 组件
|
||||
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())}`
|
||||
}
|
||||
|
||||
// ========== 新建/编辑 dialog ==========
|
||||
const editOpen = ref(false)
|
||||
const editFormRef = ref(null)
|
||||
const editForm = reactive({
|
||||
expertId: null, name: '', phone: '', workUnit: '', department: '', title: '',
|
||||
practiceCertUrl: '', titleCertUrl: ''
|
||||
})
|
||||
const editRules = {
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
phone: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
|
||||
workUnit: [{ required: true, message: '请输入工作单位', trigger: 'blur' }],
|
||||
department: [{ required: true, message: '请输入科室', trigger: 'blur' }],
|
||||
title: [{ required: true, message: '请选择职称', trigger: 'change' }]
|
||||
}
|
||||
function openAdd() {
|
||||
Object.assign(editForm, { expertId: null, name: '', phone: '', workUnit: '', department: '', title: '', practiceCertUrl: '', titleCertUrl: '' })
|
||||
editOpen.value = true
|
||||
}
|
||||
// 修改按钮: 跳转独立页 /manager/experts/edit/:id (参考 /admin/experts/new)
|
||||
function goEdit(row) {
|
||||
router.push(`/manager/experts/edit/${row.expertId}`)
|
||||
}
|
||||
async function submitEdit() {
|
||||
await editFormRef.value.validate()
|
||||
submitting.value = true
|
||||
try {
|
||||
if (editForm.expertId) {
|
||||
await bizUpdate('expert', { ...editForm })
|
||||
ElMessage.success('修改成功')
|
||||
} else {
|
||||
await bizAdd('expert', { ...editForm, auditStatus: '2', auditBy: 'admin', auditTime: new Date().toISOString().slice(0, 19).replace('T', ' ') })
|
||||
ElMessage.success('新建成功')
|
||||
}
|
||||
editOpen.value = false
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '保存失败') }
|
||||
finally { submitting.value = false }
|
||||
}
|
||||
|
||||
// ========== 审核 dialog ==========
|
||||
const auditOpen = ref(false)
|
||||
// result: '2'=通过, '3'=拒绝 (BizAuditStatusEnum: 0未提交 1待审核 2通过 3拒绝)
|
||||
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: 'admin', // 后端应取登录用户
|
||||
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 ElMessage.confirm(`确定批量审核选中的 ${selection.value.length} 位专家吗?`, '批量审核', { type: 'warning' }).catch(() => { throw new Error('cancel') }) } 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: 'admin', auditTime: new Date().toISOString().slice(0, 19).replace('T', ' ') }); ok++ } catch {}
|
||||
}
|
||||
submitting.value = false
|
||||
ElMessage.success(`批量审核通过 ${ok}/${selection.value.length} 条`)
|
||||
selection.value = []
|
||||
load()
|
||||
}
|
||||
|
||||
// ========== 禁用/恢复 ==========
|
||||
async function onDisable(row) {
|
||||
try { await bizUpdate('expert', { expertId: row.expertId, status: 'N' }); ElMessage.success('已禁用'); load() } catch { ElMessage.error('禁用失败') }
|
||||
}
|
||||
async function onEnable(row) {
|
||||
try { await bizUpdate('expert', { expertId: row.expertId, status: 'Y' }); ElMessage.success('已恢复'); load() } catch { ElMessage.error('恢复失败') }
|
||||
}
|
||||
|
||||
// ========== 查看详情 ==========
|
||||
const viewOpen = ref(false)
|
||||
const currentRow = ref(null)
|
||||
function onView(row) { currentRow.value = row; viewOpen.value = true }
|
||||
|
||||
// ========== 已拒绝: 查看拒绝意见 ==========
|
||||
function onViewAuditOpinion(row) {
|
||||
ElMessageBox.alert(
|
||||
`拒绝时间: ${fmtTime(row.auditTime) || '-'}\n审批人: ${row.auditBy || '-'}\n拒绝意见: ${row.auditOpinion || '(无)'}`,
|
||||
`专家 ${row.name} 审核已拒绝`,
|
||||
{ type: 'warning', confirmButtonText: '知道了' }
|
||||
).catch(() => {})
|
||||
}
|
||||
|
||||
// ========== 证书查看/下载 ==========
|
||||
// 图片预览: dialog 弹窗直接显示 (用 el-image 自带 preview-src-list 支持点击放大)
|
||||
// 同步自 admin/Experts.vue (2026-08-17)
|
||||
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)
|
||||
}
|
||||
|
||||
// ========== 批量导入 dialog ==========
|
||||
const importOpen = ref(false)
|
||||
const importAuditResult = ref('2')
|
||||
const importAuditOpinion = ref('')
|
||||
const importFileUrl = ref('')
|
||||
const importing = ref(false)
|
||||
function openImport() {
|
||||
importAuditResult.value = '2'
|
||||
importAuditOpinion.value = ''
|
||||
importFileUrl.value = ''
|
||||
importOpen.value = true
|
||||
}
|
||||
function downloadImportTpl() {
|
||||
ElMessage.info('批量导入模板下载 — 待后端提供静态文件')
|
||||
}
|
||||
async function submitImport() {
|
||||
if (!importFileUrl.value) return ElMessage.warning('请选择文件')
|
||||
importing.value = true
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
importing.value = false
|
||||
ElMessage.info(`已提交批量导入任务 (审核结果: ${importAuditResult.value === '2' ? '通过' : '拒绝'}) — 待后端实现`)
|
||||
importOpen.value = false
|
||||
}
|
||||
|
||||
// ========== 批量导入执业证书/证明 ==========
|
||||
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, (newPath) => {
|
||||
if (newPath === '/manager/experts') load()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 整页面板 (与 People.vue 风格一致) */
|
||||
.manager-experts { 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; }
|
||||
|
||||
/* 证书预览 dialog (1:1 抄 admin) */
|
||||
.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; }
|
||||
|
||||
/* stage-* CSS 已删除: 改用 AuditStatusTag 组件 (el-tag 自带颜色) */
|
||||
</style>
|
||||
@@ -1,147 +0,0 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟨 合规 支持单位查看 (manager/sponsor-orgs)
|
||||
说明: 原 manager/Orgs.vue 拆出 sponsor 一支, 仅查看
|
||||
接口: bizList('org', q)
|
||||
-->
|
||||
<div class="page-card manager-sponsor-orgs">
|
||||
<div class="breadcrumb">首页 / 支持单位管理</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="公司名称">
|
||||
<el-input v-model="q.orgName" placeholder="输入公司名称" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="税号/统一社会信用代码">
|
||||
<el-input v-model="q.taxNo" placeholder="输入税号" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="合作状态">
|
||||
<el-select v-model="q.status" placeholder="请选择" clearable style="width:140px">
|
||||
<el-option label="正常" value="0" />
|
||||
<el-option label="禁用" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="orgName" label="公司名称" min-width="200" show-overflow-tooltip />
|
||||
<el-table-column prop="address" label="公司地址" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="taxNo" label="税号/统一社会信用代码" min-width="180" />
|
||||
<el-table-column label="合作状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status === '1' ? '禁用' : '正常' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建日期" width="120" align="center">
|
||||
<template #default="{ row }">{{ row.createTime ? String(row.createTime).slice(0,10) : '' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="contactName" label="联系人" width="100" align="center" />
|
||||
<el-table-column prop="contactPhone" label="联系电话" width="130" align="center" />
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="view(row)">查看</el-button>
|
||||
<el-button link type="primary" @click="goPeople(row)">人员管理</el-button>
|
||||
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
|
||||
{{ isDisabled(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>
|
||||
|
||||
<el-dialog v-model="detailOpen" title="支持单位详情" width="640px" v-if="currentRow">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="公司类型">
|
||||
<el-tag type="success">支持方</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="公司名称">{{ currentRow.orgName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="公司地址">{{ currentRow.address }}</el-descriptions-item>
|
||||
<el-descriptions-item label="税号/统一社会信用代码">{{ currentRow.taxNo }}</el-descriptions-item>
|
||||
<el-descriptions-item label="合作状态">
|
||||
<el-tag :type="statusTagType(currentRow.status)">{{ currentRow.status === '1' ? '禁用' : '正常' }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建日期">{{ currentRow.createTime ? String(currentRow.createTime).slice(0,10) : '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系人">{{ currentRow.contactName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ currentRow.contactPhone || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<template #footer>
|
||||
<el-button @click="detailOpen=false">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { bizList, toggleOrgStatus } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const q = ref({ orgType: 'sponsor', orgName: '', taxNo: '', status: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
const currentRow = ref(null)
|
||||
const detailOpen = ref(false)
|
||||
|
||||
function statusTagType(s) {
|
||||
// 统一: 0=正常(success), 1=禁用(danger)
|
||||
return s === '1' ? 'danger' : 'success'
|
||||
}
|
||||
function isDisabled(row) {
|
||||
return row.status === '1'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await bizList('org', { ...q.value, pageNum: page.pageNum, pageSize: page.pageSize })
|
||||
rows.value = data?.rows || []; page.total = data?.total || 0
|
||||
} catch { rows.value = []; page.total = 0 }
|
||||
finally { loading.value = false }
|
||||
}
|
||||
function reset() { q.value = { orgType: 'sponsor', orgName: '', taxNo: '', status: '' }; page.pageNum = 1; load() }
|
||||
function view(row) { currentRow.value = row; detailOpen.value = true }
|
||||
function goPeople(row) {
|
||||
router.push({ path: '/manager/sponsor-people', query: { orgId: row.orgId, orgName: row.orgName } })
|
||||
}
|
||||
|
||||
// 仅禁用主账号 (走 BizOrgServiceImpl.toggleStatus: 同步 biz_org.status + sys_user.status)
|
||||
// 子账号由 BizAuthController.smsLogin 4.5 节校验主账号状态拦截
|
||||
async function onToggleStatus(row) {
|
||||
const disabled = isDisabled(row)
|
||||
const newStatus = disabled ? '0' : '1' // 0=正常, 1=禁用
|
||||
const label = disabled ? '恢复' : '禁用'
|
||||
if (!disabled) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要${label}「${row.orgName}」吗? ${label}后该企业主账号无法登录, 企业下子账号也将被拦截`,
|
||||
'确认',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
}
|
||||
try {
|
||||
await toggleOrgStatus(row.orgId, newStatus)
|
||||
ElMessage.success('已' + label)
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manager-sponsor-orgs { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -1,228 +0,0 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟨 合规 支持单位下人员 (manager/sponsor-people)
|
||||
入口: org 表格行内「人员管理」按钮 (router.push 带 orgId/orgName)
|
||||
接口: bizList('person', q) / bizUpdate / bizAdd / sponsorImport / sponsorImportTemplate
|
||||
-->
|
||||
<div class="page-card manager-sponsor-people">
|
||||
<div class="breadcrumb">
|
||||
首页 / 支持单位管理 / 人员管理{{ orgName ? ' (' + orgName + ')' : '' }}
|
||||
</div>
|
||||
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="姓名">
|
||||
<el-input v-model="q.name" placeholder="姓名" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号">
|
||||
<el-input v-model="q.phone" placeholder="手机号" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="企业名称">
|
||||
<el-input v-model="q.orgName" placeholder="企业名称" clearable style="width:180px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门">
|
||||
<el-input v-model="q.department" placeholder="部门" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职务">
|
||||
<el-input v-model="q.position" placeholder="职务" clearable style="width:140px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<div class="batch-bar">
|
||||
<el-button type="primary" @click="goNew">新建人员</el-button>
|
||||
<el-button @click="openImport">批量导入</el-button>
|
||||
<span class="filter-tip">*批量导入模板与列表表项一致</span>
|
||||
</div>
|
||||
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column prop="name" label="姓名" min-width="100" />
|
||||
<el-table-column prop="phone" label="手机号" min-width="130" />
|
||||
<el-table-column prop="orgName" label="企业名称" min-width="180" show-overflow-tooltip />
|
||||
<el-table-column prop="department" label="部门" min-width="100" />
|
||||
<el-table-column prop="position" label="职务" min-width="100" />
|
||||
<el-table-column label="状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.status)" disable-transitions>{{ row.status === '1' ? '禁用' : '正常' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link :type="isDisabled(row) ? 'success' : 'danger'" @click="onToggleStatus(row)">
|
||||
{{ isDisabled(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>
|
||||
|
||||
<el-dialog v-model="importOpen" title="批量导入" width="520px" :close-on-click-modal="false">
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="选择文件">
|
||||
<el-upload ref="uploadRef" :auto-upload="false" :limit="1" accept=".xls,.xlsx" :on-change="onFileChange" :on-remove="onFileRemove">
|
||||
<el-button>选择文件</el-button>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
<el-form-item label="">
|
||||
<el-button link type="primary" @click="downloadTemplate">批量导入模板下载</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item><span class="filter-tip">*导入列: 姓名/手机号/所属公司/部门/职务/角色</span></el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="importOpen = false">取消</el-button>
|
||||
<el-button type="primary" :loading="importing" @click="submitImport">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { bizList, bizUpdate, importPerson, downloadImportTemplate } from '@/api/public'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const orgId = computed(() => route.query.orgId ? Number(route.query.orgId) : null)
|
||||
const orgName = computed(() => route.query.orgName || '')
|
||||
|
||||
const q = reactive({ name: '', phone: '', orgName: '', department: '', position: '' })
|
||||
const rows = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
function statusTagType(s) {
|
||||
// 统一: 0=正常(success), 1=禁用(danger)
|
||||
return s === '1' ? 'danger' : 'success'
|
||||
}
|
||||
function isDisabled(row) {
|
||||
return row.status === '1'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const params = {
|
||||
...q,
|
||||
orgType: 'sponsor',
|
||||
...(orgId.value ? { orgId: orgId.value } : {}),
|
||||
pageNum: page.pageNum,
|
||||
pageSize: page.pageSize,
|
||||
}
|
||||
const { data } = await bizList('person', params)
|
||||
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: '', orgName: '', department: '', position: '' })
|
||||
page.pageNum = 1
|
||||
load()
|
||||
}
|
||||
|
||||
async function onToggleStatus(row) {
|
||||
const disabled = isDisabled(row)
|
||||
// sys_user.status 是 CHAR(1), 只能写 '0'/'1' (禁用='1', 启用='0')
|
||||
// 必须带 userId: BizPersonServiceImpl.updateByPrimaryKey 仅在 userId != null 时
|
||||
// 才同步 status 到 sys_user, 否则只更新 biz_person (而 biz_person.status 已删除)
|
||||
const newStatus = disabled ? '0' : '1'
|
||||
const label = disabled ? '启用' : '禁用'
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定要${label}「${row.name}」吗?`,
|
||||
'确认',
|
||||
{ type: 'warning' }
|
||||
)
|
||||
} catch { return }
|
||||
try {
|
||||
await bizUpdate('person', { personId: row.personId, userId: row.userId, status: newStatus })
|
||||
ElMessage.success('已' + label)
|
||||
load()
|
||||
} catch (e) { ElMessage.error(e?.msg || '操作失败') }
|
||||
}
|
||||
|
||||
// ========== 新建人员 ==========
|
||||
function goNew() {
|
||||
router.push({
|
||||
path: '/manager/sponsor-people/new',
|
||||
query: {
|
||||
...(orgId.value ? { orgId: orgId.value } : {}),
|
||||
...(orgName.value ? { orgName: orgName.value } : {})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ========== 批量导入 ==========
|
||||
const importOpen = ref(false)
|
||||
const importing = ref(false)
|
||||
const uploadRef = ref()
|
||||
const importFile = ref(null)
|
||||
|
||||
function openImport() { importFile.value = null; importOpen.value = true }
|
||||
function onFileChange(file) { importFile.value = file.raw }
|
||||
function onFileRemove() { importFile.value = null }
|
||||
async function downloadTemplate() {
|
||||
try {
|
||||
const res = await downloadImportTemplate('sponsor')
|
||||
const blob = new Blob([res.data], { type: res.data.type || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = '支持单位人员导入模板.xlsx'
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
} catch { ElMessage.error('模板下载失败') }
|
||||
}
|
||||
async function submitImport() {
|
||||
if (!importFile.value) { ElMessage.warning('请先选择文件'); return }
|
||||
importing.value = true
|
||||
try {
|
||||
const res = await importPerson('sponsor', importFile.value)
|
||||
const payload = res.data || res
|
||||
const ok = payload.ok ?? 0
|
||||
const total = payload.total ?? 0
|
||||
const failed = (payload.results || []).filter(r => !r.ok)
|
||||
if (failed.length === 0) {
|
||||
ElMessage.success(`导入成功 ${ok}/${total}`)
|
||||
} else {
|
||||
ElMessageBox.alert(
|
||||
`成功 ${ok} 条, 失败 ${failed.length} 条:\n` +
|
||||
failed.map(f => ` 第${f.rowNo}行 ${f.name || ''}: ${f.message}`).join('\n'),
|
||||
'导入结果', { type: 'warning' }
|
||||
)
|
||||
}
|
||||
importOpen.value = false
|
||||
load()
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || '导入失败')
|
||||
} finally {
|
||||
importing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manager-sponsor-people { 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; }
|
||||
.filter-tip { color: #909399; font-size: 12px; }
|
||||
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
||||
</style>
|
||||
@@ -1,172 +0,0 @@
|
||||
<template>
|
||||
<!--
|
||||
角色: 🟨 合规 支持单位下人员 新建/编辑 (manager/sponsor-people/new, manager/sponsor-people/edit/:id)
|
||||
结构对齐 admin/ExpertNew.vue
|
||||
unitType 硬编码 'sponsor'
|
||||
-->
|
||||
<div class="page-card manager-sponsor-person-new">
|
||||
<div class="breadcrumb">
|
||||
<a @click="goBack">支持单位下人员</a> > {{ isEdit ? '编辑人员' : '新建人员' }}{{ orgName ? ' (' + orgName + ')' : '' }}
|
||||
</div>
|
||||
|
||||
<el-card v-loading="loadingDetail" shadow="never" class="form-card nested">
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
|
||||
<el-form-item label="用户名" prop="userName">
|
||||
<el-input v-model="form.userName" placeholder="登录用户名" maxlength="50" :disabled="isEdit" />
|
||||
</el-form-item>
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="form.name" placeholder="姓名" maxlength="20" />
|
||||
</el-form-item>
|
||||
<el-form-item label="手机号" prop="phone">
|
||||
<el-input v-model="form.phone" placeholder="手机号" maxlength="11" />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属公司" prop="orgName">
|
||||
<el-input v-model="form.orgName" placeholder="所属公司" maxlength="200" :disabled="!!orgId" />
|
||||
</el-form-item>
|
||||
<el-form-item label="部门">
|
||||
<el-input v-model="form.department" placeholder="部门" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="职务">
|
||||
<el-input v-model="form.position" placeholder="职务" maxlength="50" />
|
||||
</el-form-item>
|
||||
<el-form-item label="角色">
|
||||
<span style="color:#303133">监察员 <span style="color:#909399;font-size:12px">(支持方人员固定)</span></span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div class="form-actions">
|
||||
<el-button type="primary" :loading="saving" @click="onSave">保存</el-button>
|
||||
<el-button @click="confirmCancel">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, computed, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { bizAdd, bizUpdate, bizGet } from '@/api/public'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const formRef = ref(null)
|
||||
const saving = ref(false)
|
||||
const loadingDetail = ref(false)
|
||||
const isEdit = !!route.params.id
|
||||
const personId = route.params.id || null
|
||||
|
||||
const orgName = computed(() => route.query.orgName || '')
|
||||
const orgId = computed(() => route.query.orgId ? Number(route.query.orgId) : null)
|
||||
|
||||
const form = reactive({
|
||||
personId: null,
|
||||
userName: '',
|
||||
name: '',
|
||||
phone: '',
|
||||
orgName: '',
|
||||
department: '',
|
||||
position: ''
|
||||
})
|
||||
|
||||
const rules = {
|
||||
userName: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
|
||||
phone: [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^1\d{10}$/, message: '手机号格式错误', trigger: 'blur' }
|
||||
],
|
||||
orgName: [{ required: true, message: '请输入所属公司', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
router.push('/manager/sponsor-people')
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
if (!personId) return
|
||||
loadingDetail.value = true
|
||||
try {
|
||||
const res = await bizGet('person', personId)
|
||||
if (res?.code === 200 && res.data) {
|
||||
const d = res.data
|
||||
Object.assign(form, {
|
||||
personId: d.personId,
|
||||
userName: d.userName || '',
|
||||
name: d.name || '',
|
||||
phone: d.phone || '',
|
||||
orgName: d.orgName || '',
|
||||
department: d.department || '',
|
||||
position: d.position || ''
|
||||
})
|
||||
} else {
|
||||
ElMessage.error(res?.msg || '加载失败')
|
||||
goBack()
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || e?.message || '加载失败')
|
||||
goBack()
|
||||
} finally {
|
||||
loadingDetail.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function confirmCancel() {
|
||||
const hasContent = form.name || form.phone || form.userName || form.orgName
|
||||
if (!isEdit && !hasContent) { goBack(); return }
|
||||
ElMessageBox.confirm('确定取消?未保存的内容将丢失', '提示', { type: 'warning' })
|
||||
.then(() => goBack())
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
async function onSave() {
|
||||
try { await formRef.value.validate() } catch { return }
|
||||
saving.value = true
|
||||
try {
|
||||
const payload = { ...form, unitType: 'sponsor', role: 'supervisor' }
|
||||
if (!isEdit && orgId.value) payload.orgId = orgId.value
|
||||
if (isEdit) {
|
||||
delete payload.personId
|
||||
await bizUpdate('person', payload)
|
||||
ElMessage.success('修改成功')
|
||||
} else {
|
||||
payload.status = '0'
|
||||
payload.password = '123456' // @JsonProperty("password") → loginPassword
|
||||
await bizAdd('person', payload)
|
||||
ElMessage.success('新建成功,默认密码 123456')
|
||||
}
|
||||
goBack()
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || e?.message || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 预填 orgName (从 org 页面跳来时)
|
||||
if (!isEdit && orgName.value && !form.orgName) {
|
||||
form.orgName = orgName.value
|
||||
}
|
||||
if (isEdit) loadDetail()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manager-sponsor-person-new { max-width: 1000px; }
|
||||
.page-card { background: #fff; padding: 16px 20px; border-radius: 6px; border: 1px solid #f0f0f0; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 16px; }
|
||||
.breadcrumb a { color: var(--brand-primary); cursor: pointer; text-decoration: none; }
|
||||
.breadcrumb a:hover { color: var(--brand-primary-deep, var(--brand-primary)); }
|
||||
|
||||
.form-card.nested { background: transparent; border: none; box-shadow: none; }
|
||||
:deep(.form-card .el-card__body) { padding: 24px 28px; }
|
||||
|
||||
.form-actions { margin-top: 16px; display: flex; gap: 8px; }
|
||||
|
||||
:deep(.el-button--primary) { background: var(--brand-primary); border-color: var(--brand-primary); border-radius: 4px; }
|
||||
:deep(.el-button--primary:hover) { background: var(--brand-primary-deep, var(--brand-primary)); border-color: var(--brand-primary-deep, var(--brand-primary)); }
|
||||
:deep(.el-button) { border-radius: 4px; }
|
||||
:deep(.el-input__wrapper) { border-radius: 4px; }
|
||||
</style>
|
||||
@@ -2,16 +2,9 @@
|
||||
<div class="page-card manager-support-intent">
|
||||
<div class="breadcrumb">首页 / 支持意向</div>
|
||||
|
||||
<!-- 提示语 -->
|
||||
<div class="hint-list">
|
||||
<p>*项目公示中点击"表达支持意向"后,显示在此</p>
|
||||
<p>*支持意向不需要注册;已注册账号时,自动回填用户状态</p>
|
||||
<p>*根据手机号与系统账号进行匹配</p>
|
||||
</div>
|
||||
|
||||
<!-- ========== 筛选区 ========== -->
|
||||
<el-form inline :model="q" class="filter-form">
|
||||
<el-form-item label="项目编号"><el-input v-model="q.projectNo" placeholder="如 ZH-2026-658" clearable /></el-form-item>
|
||||
<el-form-item label="项目编号"><el-input v-model="q.projectNo" placeholder="输入项目编号" clearable /></el-form-item>
|
||||
<el-form-item label="意向项目名称"><el-input v-model="q.projectName" placeholder="输入意向项目名称" clearable /></el-form-item>
|
||||
<el-form-item label="姓名"><el-input v-model="q.name" placeholder="输入姓名" clearable /></el-form-item>
|
||||
<el-form-item label="工作单位"><el-input v-model="q.workUnit" placeholder="输入工作单位" clearable /></el-form-item>
|
||||
@@ -26,13 +19,12 @@
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="load">查找</el-button>
|
||||
<el-button @click="reset">重置</el-button>
|
||||
<el-button type="success" @click="exportCsv">导出CSV</el-button>
|
||||
<el-button type="success" @click="doExport">导出</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- ========== 表格 ========== -->
|
||||
<el-table :data="rows" v-loading="loading" stripe border @selection-change="onSelChange">
|
||||
<el-table-column type="selection" width="46" />
|
||||
<el-table :data="rows" v-loading="loading" stripe border>
|
||||
<el-table-column type="index" label="#" width="50" />
|
||||
<el-table-column prop="projectNo" label="项目编号" width="140" align="center" />
|
||||
<el-table-column prop="projectName" label="意向项目名称" min-width="200" show-overflow-tooltip />
|
||||
@@ -46,13 +38,13 @@
|
||||
<el-tag :type="row.userId ? 'success' : 'warning'">{{ row.userId ? '存在' : '不存在' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="审核状态" width="100" align="center">
|
||||
<el-table-column v-if="false" label="审核状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row.intentStatus)">{{ row.intentStatus || '待审核' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" align="center" />
|
||||
<el-table-column label="操作" width="220" align="center" fixed="right">
|
||||
<el-table-column v-if="false" label="操作" width="220" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button v-if="row.intentStatus !== '已通过'" type="primary" link size="small" @click="changeStatus(row, '已通过')">通过</el-button>
|
||||
<el-button v-if="row.intentStatus !== '已拒绝'" type="danger" link size="small" @click="changeStatus(row, '已拒绝')">拒绝</el-button>
|
||||
@@ -62,9 +54,6 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pager">
|
||||
<div class="pager-left">
|
||||
<el-button type="danger" :disabled="!selected.length" @click="removeBatch">批量删除 ({{ selected.length }})</el-button>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
@@ -76,12 +65,12 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
listPublicitySupportIntent,
|
||||
updatePublicitySupportIntent,
|
||||
deletePublicitySupportIntent
|
||||
deletePublicitySupportIntent,
|
||||
exportPublicitySupportIntent
|
||||
} from '@/api/public'
|
||||
|
||||
const q = ref({ projectNo: '', projectName: '', name: '', workUnit: '', phone: '', intentStatus: '' })
|
||||
const rows = ref([])
|
||||
const selected = ref([])
|
||||
const loading = ref(false)
|
||||
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
||||
|
||||
@@ -104,8 +93,6 @@ function reset() {
|
||||
load()
|
||||
}
|
||||
|
||||
function onSelChange(arr) { selected.value = arr }
|
||||
|
||||
function statusTagType(s) {
|
||||
if (s === '已通过') return 'success'
|
||||
if (s === '已拒绝') return 'danger'
|
||||
@@ -135,40 +122,23 @@ async function removeOne(row) {
|
||||
}
|
||||
}
|
||||
|
||||
async function removeBatch() {
|
||||
if (!selected.value.length) return
|
||||
// 后端导出 .xlsx (按当前筛选条件全量下载)
|
||||
async function doExport() {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认删除 ${selected.value.length} 条支持意向?`, '提示', { type: 'warning' })
|
||||
} catch { return }
|
||||
try {
|
||||
const ids = selected.value.map(r => r.intentId)
|
||||
await deletePublicitySupportIntent(ids)
|
||||
ElMessage.success('已删除')
|
||||
load()
|
||||
const res = await exportPublicitySupportIntent({ ...q.value })
|
||||
const blob = new Blob([res.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `支持意向_${new Date().toISOString().slice(0, 10)}.xlsx`
|
||||
document.body.appendChild(a); a.click(); document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
ElMessage.success('导出成功')
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.msg || '删除失败')
|
||||
ElMessage.error(e?.msg || e?.message || '导出失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 客户端导出 CSV (UTF-8 BOM 避免 Excel 乱码)
|
||||
function exportCsv() {
|
||||
if (!rows.value.length) return ElMessage.warning('当前列表无数据')
|
||||
const headers = ['项目编号', '项目名称', '姓名', '工作单位', '部门', '职务', '手机号', '账号状态', '审核状态', '创建时间']
|
||||
const csvRows = rows.value.map(r => [
|
||||
r.projectNo, r.projectName, r.name, r.workUnit, r.department, r.position,
|
||||
r.phone, r.userId ? '存在' : '不存在', r.intentStatus || '待审核', r.createTime
|
||||
].map(v => `"` + String(v ?? '').replace(/"/g, '""') + `"`).join(','))
|
||||
const csv = '' + [headers.join(','), ...csvRows].join('\r\n')
|
||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `支持意向_${new Date().toISOString().slice(0, 10)}.csv`
|
||||
document.body.appendChild(a); a.click(); document.body.removeChild(a)
|
||||
URL.revokeObjectURL(url)
|
||||
ElMessage.success(`已导出 ${rows.value.length} 条`)
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
@@ -176,19 +146,6 @@ onMounted(load)
|
||||
.manager-support-intent { padding: 16px; }
|
||||
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
||||
.filter-form { margin-bottom: 12px; }
|
||||
.pager { display: flex; justify-content: space-between; align-items: center; margin-top: 12px; }
|
||||
.pager-left { display: flex; gap: 8px; }
|
||||
.pager { display: flex; justify-content: flex-end; align-items: center; margin-top: 12px; }
|
||||
|
||||
.hint-list {
|
||||
background: #fff;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 8px;
|
||||
padding: 12px 20px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 12px;
|
||||
color: #ff4d4f;
|
||||
line-height: 1.8;
|
||||
}
|
||||
.hint-list p { margin-bottom: 4px; }
|
||||
.hint-list p:last-child { margin-bottom: 0; }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user