- 用 breadcrumb (首页 / 人员管理) 替代 page-sub 副标题 - 删 .page-sub / .filter-tip 死样式 - 跟 sponsor/SponsorPeople.vue 一致 Co-Authored-By: Claude <noreply@anthropic.com>
231 lines
11 KiB
Vue
231 lines
11 KiB
Vue
<template>
|
|
<div class="page-card executor-people">
|
|
<div class="breadcrumb">首页 / 人员管理</div>
|
|
|
|
<!-- ========== 筛选区 (按原型 人员管理_1.html top=162) ========== -->
|
|
<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:160px" /></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="loadList">查找</el-button>
|
|
<el-button @click="reset">重置</el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<!-- ========== 批量按钮 (top=203: 新建人员 / 批量导入) ========== -->
|
|
<div class="batch-bar">
|
|
<el-button type="primary" @click="goNew">新建人员</el-button>
|
|
<el-button @click="onImport">批量导入</el-button>
|
|
</div>
|
|
|
|
<!-- ========== 表格 (按原型 列头 top=270: 姓名/手机号/工作单位/部门/职务/角色/状态/操作) ========== -->
|
|
<el-table :data="list" border stripe v-loading="loading">
|
|
<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 prop="role" label="角色" width="100">
|
|
<template #default="{ row }">
|
|
<el-tag size="small" :type="row.role === 'meetingExecutor' ? 'warning' : 'info'">{{ translatePersonRole(row.role) }}</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="状态" width="80">
|
|
<template #default="{ row }">
|
|
<el-tag :type="row.status === '0' ? 'success' : 'danger'" disable-transitions>
|
|
{{ row.status === '0' ? '正常' : '禁用' }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="220" fixed="right">
|
|
<template #default="{ row }">
|
|
<el-button size="small" link type="primary" @click="onView(row)">查看</el-button>
|
|
<el-button size="small" link type="primary" @click="goEdit(row)">编辑</el-button>
|
|
<!-- 本人不显示禁用/恢复按钮 (跟 sponsor 一样, 避免主账号把自己禁用) -->
|
|
<template v-if="row.userId !== store.user?.userId">
|
|
<el-button v-if="(row.status || '正常') === '正常'" size="small" link type="danger" @click="onToggleStatus(row, '禁用')">禁用</el-button>
|
|
<el-button v-else size="small" link type="success" @click="onToggleStatus(row, '恢复')">恢复</el-button>
|
|
</template>
|
|
</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="loadList" @size-change="loadList" />
|
|
</div>
|
|
|
|
<!-- 新增/编辑: 跳转独立页面 /executor/people/new 或 /edit/:id, 不再是 dialog -->
|
|
<!-- (旧的 formOpen dialog 已删) -->
|
|
|
|
<!-- ========== Dialog: 批量导入 ========== -->
|
|
<el-dialog v-model="importOpen" title="批量导入 (Excel)" width="520px">
|
|
<el-form label-width="80px">
|
|
<el-form-item label="选择文件">
|
|
<!-- 对齐 manager/admin/sponsor: el-upload :auto-upload=false 自管 file, footer 按钮触发提交 -->
|
|
<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>
|
|
<span class="hint-link" @click="downloadTemplate">批量导入模板下载 (.xlsx)</span>
|
|
</el-form-item>
|
|
</el-form>
|
|
<template #footer>
|
|
<el-button @click="importOpen = false">关闭</el-button>
|
|
<el-button type="primary" :loading="importing" :disabled="!importFile" @click="submitImport">开始导入</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
|
|
<!-- 导入结果 dialog (用 table 展示每行成功/失败明细) -->
|
|
<el-dialog v-model="resultVisible" :title="`导入结果 (成功 ${resultOk}/${resultTotal})`" width="780px">
|
|
<el-table :data="resultRows" stripe border max-height="480">
|
|
<el-table-column prop="rowNo" label="行号" width="80" align="center" />
|
|
<el-table-column prop="name" label="姓名" width="120" />
|
|
<el-table-column prop="phone" label="手机号" width="130" />
|
|
<el-table-column label="状态" width="90" align="center">
|
|
<template #default="{ row }">
|
|
<el-tag :type="row.ok ? 'success' : 'danger'" disable-transitions size="small">
|
|
{{ row.ok ? '成功' : '失败' }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="message" label="错误信息" show-overflow-tooltip />
|
|
</el-table>
|
|
<template #footer>
|
|
<el-button type="primary" @click="resultVisible = false">关闭</el-button>
|
|
</template>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { bizUpdate } from '@/api/public'
|
|
import { listExecutorPerson } from '@/api/business/person'
|
|
import { useUserStore } from '@/store/user'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
import { translatePersonRole } from '@/utils/roleMap'
|
|
import request from '@/utils/request'
|
|
|
|
const router = useRouter()
|
|
const store = useUserStore()
|
|
|
|
const list = ref([])
|
|
const loading = ref(false)
|
|
const q = reactive({ name: '', phone: '', orgName: '', department: '', position: '' })
|
|
const page = reactive({ pageNum: 1, pageSize: 10, total: 0 })
|
|
|
|
const importOpen = ref(false)
|
|
|
|
const uploadRef = ref()
|
|
const importFile = ref(null) // el-upload 选中的原始 File 对象 (file.raw)
|
|
const importing = ref(false)
|
|
// 导入结果 (dialog + table)
|
|
const resultVisible = ref(false)
|
|
const resultRows = ref([])
|
|
const resultOk = ref(0)
|
|
const resultTotal = ref(0)
|
|
|
|
async function loadList() {
|
|
loading.value = true
|
|
try {
|
|
// 走 executor 专属接口, SQL 硬编码 unit_type='executor' + parent_user_id=主账号 (前端绕不开)
|
|
const r = await listExecutorPerson({ pageNum: page.pageNum, pageSize: page.pageSize, ...q })
|
|
list.value = r.rows || []
|
|
page.total = r.total || 0
|
|
} finally { loading.value = false }
|
|
}
|
|
|
|
function reset() { Object.assign(q, { name:'',phone:'',orgName:'',department:'',position:'' }); page.pageNum = 1; loadList() }
|
|
|
|
// 新建/编辑走独立页面
|
|
function goNew() { router.push('/executor/people/new') }
|
|
function goEdit(row) { router.push(`/executor/people/edit/${row.personId}`) }
|
|
|
|
function onView(row) { router.push(`/executor/people/detail/${row.personId}`) }
|
|
|
|
async function onToggleStatus(row, action) {
|
|
try {
|
|
await ElMessageBox.confirm(`确定${action}「${row.name}」吗?`, action, { type: 'warning' })
|
|
// sys_user.status 是 CHAR(1), 只能写 '0'/'1' (禁用='1', 启用='0')
|
|
// 必须带 userId: BizPersonServiceImpl.updateByPrimaryKey 仅在 userId != null 时
|
|
// 才同步 status 到 sys_user, 否则只更新 biz_person (而 biz_person.status 已删除)
|
|
const newStatus = action === '禁用' ? '1' : '0'
|
|
await bizUpdate('person', { personId: row.personId, userId: row.userId, status: newStatus })
|
|
ElMessage.success(`${action}成功`)
|
|
loadList()
|
|
} catch (e) { if (e !== 'cancel') ElMessage.error(e?.msg || `${action}失败`) }
|
|
}
|
|
|
|
// 批量导入 (Excel) - 对齐 sponsor/SponsorPeople.vue
|
|
function onImport() {
|
|
importFile.value = null
|
|
// 关闭后下次再打开 el-upload 内部 fileList 可能残留, 用 ref 清掉
|
|
uploadRef.value?.clearFiles()
|
|
importOpen.value = true
|
|
}
|
|
function onFileChange(file) {
|
|
importFile.value = file.raw
|
|
}
|
|
function onFileRemove() {
|
|
importFile.value = null
|
|
}
|
|
async function downloadTemplate() {
|
|
// 后端 ExcelUtil<BizPersonImportVO>.importTemplateExcel 自动生成中文列头 .xlsx
|
|
const res = await request.get('/business/person/executorImportTemplate', { responseType: 'blob' })
|
|
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
|
|
// 优先用后端 Content-Disposition 的 filename* (utf-8) 字段
|
|
const dispo = res.headers?.['content-disposition'] || res.headers?.['Content-Disposition'] || ''
|
|
const m = /filename\*=UTF-8''([^;]+)/i.exec(dispo)
|
|
a.download = m ? decodeURIComponent(m[1]) : 'executor人员批量导入模板.xlsx'
|
|
a.click()
|
|
URL.revokeObjectURL(url)
|
|
ElMessage.success('模板下载完成')
|
|
}
|
|
async function submitImport() {
|
|
if (!importFile.value) { ElMessage.warning('请先选择文件'); return }
|
|
if (!/\.(xlsx|xls)$/i.test(importFile.value.name)) { ElMessage.warning('只支持 .xlsx / .xls 文件'); return }
|
|
importing.value = true
|
|
try {
|
|
const form = new FormData()
|
|
form.append('file', importFile.value)
|
|
const res = await request.post('/business/person/executorImport', form, {
|
|
headers: { 'Content-Type': 'multipart/form-data' }
|
|
})
|
|
const data = res.data || res
|
|
resultOk.value = data.ok || 0
|
|
resultTotal.value = data.total || 0
|
|
resultRows.value = data.results || []
|
|
resultVisible.value = true
|
|
importOpen.value = false
|
|
importFile.value = null
|
|
uploadRef.value?.clearFiles()
|
|
loadList()
|
|
} catch (e) {
|
|
ElMessage.error(e?.msg || '导入失败')
|
|
} finally {
|
|
importing.value = false
|
|
}
|
|
}
|
|
|
|
onMounted(loadList)
|
|
</script>
|
|
|
|
<style scoped>
|
|
.executor-people { padding: 16px; }
|
|
.breadcrumb { font-size: 13px; color: #8c8c8c; margin-bottom: 12px; }
|
|
.batch-bar { margin-bottom: 12px; display: flex; gap: 8px; align-items: center; }
|
|
.hint-link { color: var(--brand-primary); cursor: pointer; font-size: 13px; }
|
|
.hint-link:hover { color: var(--brand-primary-deep, var(--brand-primary)); }
|
|
.pager { display: flex; justify-content: flex-end; margin-top: 12px; }
|
|
.filter-form { margin-bottom: 12px; }
|
|
</style> |