This commit is contained in:
zlwu committed 2026-09-24 09:59:26 +08:00
1 parent 8248e5ef15
commit b9b4cc2673
57 files changed
+42800 -30473

No files matched your search

@@ -1,4 +1,4 @@
<!-- 旧「采集协议号管理」菜单入口:重定向至细胞采集基础信息维护 -->
<!-- 旧「采集协议号管理」菜单入口:重定向至采集地区及医院(历史入口保留) -->
<template>
<div />
</template>
@@ -3,8 +3,10 @@
<template>
<div class="page-container">
<section class="panel">
<el-tabs v-model="activeTab" class="panel-pill-tabs panel-pill-tabs--lg">
<el-tab-pane label="采集地区及医院" name="region">
<div class="panel-top">
<h2 class="title-bar-heading">地区列表</h2>
</div>
<div class="filter-block">
<el-form :model="searchForm" class="filter-form" @submit.prevent>
<div class="filter-grid">
@@ -89,15 +91,27 @@
</el-table-column>
<el-table-column
label="工作人员"
min-width="160"
align="left"
min-width="240"
align="center"
header-align="center"
class-name="hospital-info-col"
>
<template #default="{ row }">
<template v-if="row.staffNames && row.staffNames.length">
<div v-for="(name, idx) in row.staffNames" :key="idx" class="hospital-cell-line">
{{ name }}
<div class="staff-names-grid">
<div
v-for="(col, colIdx) in splitStaffNameColumns(row.staffNames)"
:key="colIdx"
class="staff-names-col"
>
<div
v-for="(name, idx) in col"
:key="idx"
class="hospital-cell-line"
>
{{ name }}
</div>
</div>
</div>
</template>
<span v-else class="muted">—</span>
@@ -257,96 +271,107 @@
<div class="hospital-dialog-head">
<span class="muted">当前地区:{{ currentRegionName }}</span>
</div>
<div class="list-toolbar hospital-toolbar">
<div class="list-toolbar-row">
<div class="list-toolbar-selected">
已选 <span class="sel-num">{{ hospitalSelected.length }}</span> 项
<section class="panel dialog-nested-list">
<div class="list-toolbar">
<div class="list-toolbar-row">
<div class="list-toolbar-selected">
已选 <span class="sel-num">{{ hospitalSelected.length }}</span> 项
</div>
<el-button class="btn-main" type="primary" @click="handleAddHospital">
<el-icon><Plus /></el-icon>
新增医院
</el-button>
<el-button
class="btn-ghost"
type="danger"
:disabled="hospitalSelected.length === 0"
@click="handleBatchDeleteHospital"
>
<el-icon><Delete /></el-icon>
批量删除
</el-button>
</div>
<el-button class="btn-main" type="primary" @click="handleAddHospital">
<el-icon><Plus /></el-icon>
新增医院
</el-button>
<el-button
class="btn-ghost"
type="danger"
:disabled="hospitalSelected.length === 0"
@click="handleBatchDeleteHospital"
>
批量删除
</el-button>
</div>
</div>
<div class="list-body list-wrap hospital-table-wrap">
<el-table
class="list-table"
:data="hospitalTable"
v-loading="hospitalLoading"
stripe
row-key="id"
max-height="360"
@selection-change="onHospitalSelectionChange"
>
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
/>
<el-table-column
prop="hospitalName"
label="医院名称"
min-width="200"
align="center"
header-align="center"
/>
<el-table-column
prop="remark"
label="备注"
min-width="160"
align="center"
header-align="center"
show-overflow-tooltip
<div class="list-body list-wrap">
<el-table
class="list-table"
:data="hospitalTable"
v-loading="hospitalLoading"
stripe
row-key="id"
max-height="360"
@selection-change="onHospitalSelectionChange"
>
<template #default="{ row }">
<span v-if="row.remark">{{ row.remark }}</span>
<span v-else class="muted">—</span>
</template>
</el-table-column>
<el-table-column
label="操作"
min-width="260"
fixed="right"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-button class="btn-op-item primary" @click="handleEditHospital(row)"
>编辑</el-button
>
<el-button class="btn-op-item" @click="handleDeleteHospital(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination list-pager-bar">
<el-pagination
:current-page="hospitalPagination.page"
:page-size="hospitalPagination.pageSize"
:total="hospitalPagination.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
@size-change="
(val) => (
(hospitalPagination.page = 1),
(hospitalPagination.pageSize = val),
loadHospitals()
)
"
@current-change="(val) => ((hospitalPagination.page = val), loadHospitals())"
/>
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
:index="
(index) =>
(hospitalPagination.page - 1) * hospitalPagination.pageSize + index + 1
"
/>
<el-table-column
prop="hospitalName"
label="医院名称"
min-width="200"
align="center"
header-align="center"
/>
<el-table-column
prop="remark"
label="备注"
min-width="160"
align="center"
header-align="center"
show-overflow-tooltip
>
<template #default="{ row }">
<span v-if="row.remark">{{ row.remark }}</span>
<span v-else class="muted">—</span>
</template>
</el-table-column>
<el-table-column
label="操作"
min-width="260"
fixed="right"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-button class="btn-op-item primary" @click="handleEditHospital(row)">
<el-icon><Edit /></el-icon>
编辑
</el-button>
<el-button class="btn-op-item" @click="handleDeleteHospital(row)">
<el-icon><Delete /></el-icon>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination list-pager-bar">
<el-pagination
:current-page="hospitalPagination.page"
:page-size="hospitalPagination.pageSize"
:total="hospitalPagination.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
@size-change="
(val) => (
(hospitalPagination.page = 1),
(hospitalPagination.pageSize = val),
loadHospitals()
)
"
@current-change="(val) => ((hospitalPagination.page = val), loadHospitals())"
/>
</div>
</div>
</div>
</section>
<el-dialog
v-model="hospitalFormVisible"
@@ -404,8 +429,8 @@
<el-dialog
v-model="staffDialogVisible"
:title="staffDialogTitle"
width="860px"
class="dialog-form-layout"
width="1100px"
class="dialog-form-layout staff-config-dialog"
align-center
append-to-body
modal-class="protocol-dialog-mask"
@@ -416,124 +441,127 @@
<div class="hospital-dialog-head">
<span class="muted">当前地区:{{ currentRegionName }}</span>
</div>
<div class="list-toolbar hospital-toolbar">
<div class="list-toolbar-row">
<div class="list-toolbar-selected">
已选 <span class="sel-num">{{ staffSelected.length }}</span> 项
<section class="panel dialog-nested-list">
<div class="list-toolbar">
<div class="list-toolbar-row">
<div class="list-toolbar-selected">
已选 <span class="sel-num">{{ staffSelected.length }}</span> 项
</div>
<el-button class="btn-main" type="primary" @click="handleAddStaff">
<el-icon><Plus /></el-icon>
添加人员
</el-button>
<el-button
class="btn-ghost"
type="danger"
:disabled="staffSelected.length === 0"
@click="handleBatchDeleteStaff"
>
<el-icon><Delete /></el-icon>
批量移除
</el-button>
</div>
<el-button class="btn-main" type="primary" @click="handleAddStaff">
<el-icon><Plus /></el-icon>
添加人员
</el-button>
<el-button
class="btn-ghost"
type="danger"
:disabled="staffSelected.length === 0"
@click="handleBatchDeleteStaff"
>
<el-icon><Delete /></el-icon>
批量移除
</el-button>
</div>
</div>
<div class="list-body list-wrap hospital-table-wrap">
<el-table
class="list-table"
:data="staffTable"
v-loading="staffLoading"
stripe
row-key="id"
max-height="360"
@selection-change="onStaffSelectionChange"
>
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
/>
<el-table-column
prop="userName"
label="姓名"
min-width="120"
align="center"
header-align="center"
/>
<el-table-column
prop="userNo"
label="工号"
min-width="120"
align="center"
header-align="center"
/>
<el-table-column
prop="deptName"
label="部门"
min-width="140"
align="center"
header-align="center"
<div class="list-body list-wrap">
<el-table
class="list-table"
:data="staffTable"
v-loading="staffLoading"
stripe
row-key="id"
max-height="560"
@selection-change="onStaffSelectionChange"
>
<template #default="{ row }">
<span v-if="row.deptName">{{ row.deptName }}</span>
<span v-else class="muted">—</span>
</template>
</el-table-column>
<el-table-column
label="是否负责人"
min-width="120"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-tag v-if="row.isLeader" type="success" size="small">负责人</el-tag>
<el-tag v-else type="info" size="small">否</el-tag>
</template>
</el-table-column>
<el-table-column
label="操作"
min-width="260"
fixed="right"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-button class="btn-op-item primary" @click="toggleStaffLeader(row)">
<el-icon>
<StarFilled v-if="row.isLeader" />
<Star v-else />
</el-icon>
{{ row.isLeader ? '取消负责人' : '设为负责人' }}
</el-button>
<el-button class="btn-op-item" @click="handleDeleteStaff(row)">
<el-icon><Delete /></el-icon>
移除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination list-pager-bar">
<el-pagination
:current-page="staffPagination.page"
:page-size="staffPagination.pageSize"
:total="staffPagination.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
@size-change="
(val) => (
(staffPagination.page = 1), (staffPagination.pageSize = val), loadStaff()
)
"
@current-change="(val) => ((staffPagination.page = val), loadStaff())"
/>
<el-table-column type="selection" min-width="55" align="center" header-align="center" />
<el-table-column
type="index"
label="序号"
min-width="68"
align="center"
header-align="center"
:index="(index) => (staffPagination.page - 1) * staffPagination.pageSize + index + 1"
/>
<el-table-column
prop="userName"
label="姓名"
min-width="120"
align="center"
header-align="center"
/>
<el-table-column
prop="userNo"
label="工号"
min-width="120"
align="center"
header-align="center"
/>
<el-table-column
prop="deptName"
label="部门"
min-width="140"
align="center"
header-align="center"
>
<template #default="{ row }">
<span v-if="row.deptName">{{ row.deptName }}</span>
<span v-else class="muted">—</span>
</template>
</el-table-column>
<el-table-column
label="是否负责人"
min-width="120"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-tag v-if="row.isLeader" type="success" size="small">负责人</el-tag>
<el-tag v-else type="info" size="small">否</el-tag>
</template>
</el-table-column>
<el-table-column
label="操作"
min-width="260"
fixed="right"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-button class="btn-op-item primary" @click="toggleStaffLeader(row)">
<el-icon>
<StarFilled v-if="row.isLeader" />
<Star v-else />
</el-icon>
{{ row.isLeader ? '取消负责人' : '设为负责人' }}
</el-button>
<el-button class="btn-op-item" @click="handleDeleteStaff(row)">
<el-icon><Delete /></el-icon>
移除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination list-pager-bar">
<el-pagination
:current-page="staffPagination.page"
:page-size="staffPagination.pageSize"
:total="staffPagination.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
@size-change="
(val) => (
(staffPagination.page = 1), (staffPagination.pageSize = val), loadStaff()
)
"
@current-change="(val) => ((staffPagination.page = val), loadStaff())"
/>
</div>
</div>
</div>
</section>
<el-dialog
v-model="staffAddVisible"
title="添加人员"
width="520px"
width="640px"
class="dialog-form-layout"
align-center
append-to-body
@@ -544,13 +572,17 @@
<el-form label-position="top">
<el-form-item label="系统人员" required>
<el-select
v-model="staffAddForm.userId"
v-model="staffAddForm.userIds"
multiple
filterable
remote
clearable
collapse-tags
collapse-tags-tooltip
:max-collapse-tags="4"
:remote-method="remoteSearchUsers"
:loading="staffUserLoading"
placeholder="搜索姓名/账号"
placeholder="搜索并选择一人或多名人员"
style="width: 100%"
>
<el-option
@@ -561,7 +593,11 @@
/>
</el-select>
</el-form-item>
<el-form-item label="设为负责人">
<el-form-item>
<template #label>
设为负责人
<span class="form-label-tip">勾选后,本次添加的人员均标记为负责人</span>
</template>
<el-switch v-model="staffAddForm.isLeader" />
</el-form-item>
</el-form>
@@ -596,8 +632,6 @@
:meta="viewRow?.createTime ? '创建时间:' + viewRow.createTime : ''"
:sections="viewSections"
/>
</el-tab-pane>
</el-tabs>
</section>
</div>
</template>
@@ -641,7 +675,14 @@
} from '@/api/admin'
defineOptions({ name: 'BizCollectionRegion' })
const activeTab = ref('region')
/** 工作人员两列展示:左列 ceil(n/2),右列剩余(如 5 人 → 3+2) */
function splitStaffNameColumns(names: string[]): [string[], string[]] {
const list = names || []
if (list.length <= 1) return [list, []]
const mid = Math.ceil(list.length / 2)
return [list.slice(0, mid), list.slice(mid)]
}
const searchForm = reactive<{ keyword: string }>({ keyword: '' })
const tableData = ref<Api.BizCollectionRegion.RegionItem[]>([])
const selectedRows = ref<Api.BizCollectionRegion.RegionItem[]>([])
@@ -943,8 +984,8 @@
const staffPagination = reactive({ page: 1, pageSize: 10, total: 0 })
const staffAddVisible = ref(false)
const staffAddLoading = ref(false)
const staffAddForm = reactive<{ userId: string; isLeader: boolean }>({
userId: '',
const staffAddForm = reactive<{ userIds: string[]; isLeader: boolean }>({
userIds: [],
isLeader: false
})
const staffUserOptions = ref<Api.User.UserItem[]>([])
@@ -971,11 +1012,20 @@
return
}
try {
const res = await fetchRegionStaffList(currentRegionId.value, {
page: 1,
pageSize: 500
})
boundStaffUserIds.value = new Set((res.list || []).map((s) => s.userId))
// 后端 pageSize 上限为 100,分页拉全量已绑定人员用于「添加人员」下拉去重
const pageSize = 100
const ids = new Set<string>()
let page = 1
let total = 0
do {
const res = await fetchRegionStaffList(currentRegionId.value, { page, pageSize })
for (const s of res.list || []) {
if (s.userId) ids.add(s.userId)
}
total = res.total ?? 0
page += 1
} while (ids.size < total)
boundStaffUserIds.value = ids
} catch {
boundStaffUserIds.value = new Set(staffTable.value.map((s) => s.userId))
}
@@ -1005,7 +1055,7 @@
}
function handleAddStaff() {
staffAddForm.userId = ''
staffAddForm.userIds = []
staffAddForm.isLeader = false
staffUserOptions.value = []
staffAddVisible.value = true
@@ -1017,36 +1067,52 @@
try {
const res = await fetchUserList({
page: 1,
pageSize: 20,
pageSize: 50,
keyword: query || undefined,
status: '1'
})
const bound = boundStaffUserIds.value
staffUserOptions.value = (res.list || []).filter((u) => !bound.has(u.id))
const selectedIds = new Set(staffAddForm.userIds)
const keep = staffUserOptions.value.filter((u) => selectedIds.has(u.id))
const keepIds = new Set(keep.map((u) => u.id))
const searched = (res.list || []).filter((u) => !bound.has(u.id) && !keepIds.has(u.id))
staffUserOptions.value = [...keep, ...searched]
} finally {
staffUserLoading.value = false
}
}
async function submitAddStaff() {
if (!staffAddForm.userId) {
const userIds = [...staffAddForm.userIds]
if (userIds.length === 0) {
ElMessage.warning('请选择系统人员')
return
}
staffAddLoading.value = true
try {
await fetchCreateRegionStaff(currentRegionId.value, {
userId: staffAddForm.userId,
isLeader: staffAddForm.isLeader
})
ElMessage.success('添加成功')
let ok = 0
let fail = 0
for (const userId of userIds) {
try {
await fetchCreateRegionStaff(currentRegionId.value, {
userId,
isLeader: staffAddForm.isLeader
})
ok += 1
} catch {
fail += 1
}
}
if (ok === 0) {
ElMessage.error('添加失败')
return
}
ElMessage.success(fail > 0 ? `成功添加 ${ok} 人,${fail} 人失败` : `成功添加 ${ok} 人`)
staffAddVisible.value = false
staffAddForm.userId = ''
staffAddForm.userIds = []
staffAddForm.isLeader = false
await loadStaff()
await loadData()
} catch {
/* */
} finally {
staffAddLoading.value = false
}
@@ -1115,12 +1181,10 @@
color: var(--mint-text-muted);
}
.hospital-toolbar {
margin-bottom: 0;
}
.hospital-table-wrap {
padding-top: 0;
/* 弹窗内嵌标准列表面板:工具栏与表格由全局 list-toolbar / list-body 分隔 */
.dialog-nested-list {
margin-top: 0;
min-height: 480px;
}
:deep(.hospital-info-col .cell) {
@@ -1128,6 +1192,19 @@
line-height: 1.5;
}
.staff-names-grid {
display: flex;
justify-content: center;
align-items: flex-start;
gap: 6px;
width: 100%;
}
.staff-names-col {
min-width: 0;
text-align: center;
}
.hospital-cell-line {
line-height: 1.8;
}
@@ -1140,3 +1217,15 @@
color: var(--mint-text-muted);
}
</style>
<style lang="scss">
/* append-to-body 弹窗需非 scoped,才能提高可视高度 */
.el-dialog.staff-config-dialog.dialog-form-layout {
width: 1100px;
max-height: calc(100vh - 48px);
}
.el-dialog.staff-config-dialog.dialog-form-layout .el-dialog__body {
min-height: 560px;
}
</style>