fix(web): 修正组织停用行为并默认筛选启用中文档

This commit is contained in:
xy
2026-10-07 21:21:20 +08:00
parent 891b59d328
commit c415c29b5e
5 changed files with 217 additions and 97 deletions
+1 -1
View File
@@ -84,7 +84,7 @@ TanStack Vue Query、PDF 预览、报表图表、自动化测试和签字板适
## 当前状态 ## 当前状态
已完成 Vite 基础初始化、路由、Pinia、原型主题 CSS、登录页、首页、签署工作台、签署记录查询、文档库管理页面和「报表分析」四个菜单(签署总览 / 签署量分析 / 签署时效分析 / 患者结构分析)。角色权限与系统设置中的菜单设置已接入 MEDISIGN RBAC 接口;导航、路由和页面操作按授权资源树控制,角色数据范围在角色配置中维护,员工页面只负责绑定角色。组织架构页按机构→院区→科室分组展示员工列表,新增院区默认归属“新华院区”,支持节点筛选、员工维护和组织节点维护。系统参数卡片仍保留 Mock,真实签字板/线上签名回调和打印服务仍需设备或后端能力。 已完成 Vite 基础初始化、路由、Pinia、原型主题 CSS、登录页、首页、签署工作台、签署记录查询、文档库管理页面和「报表分析」四个菜单(签署总览 / 签署量分析 / 签署时效分析 / 患者结构分析)。角色权限与系统设置中的菜单设置已接入 MEDISIGN RBAC 接口;导航、路由和页面操作按授权资源树控制,角色数据范围在角色配置中维护,员工页面只负责绑定角色。组织架构页按机构→院区→科室分组展示员工列表,新增院区默认归属“新华院区”,支持节点筛选、员工维护和组织节点维护。院区与科室的 `DELETE` 接口表示停用当前节点,不级联停用或删除下级;组织树默认筛选“启用中”,可切换为“已停用”或“全部状态”;停用节点在组织树中标记“已停用”,可通过编辑重新启用。操作成功但列表刷新失败时会提示刷新失败,保留已成功的停用状态。系统参数卡片仍保留 Mock,真实签字板/线上签名回调和打印服务仍需设备或后端能力。
文档库管理的真实模式已打通「模板 → 版本 → 正文」三段: 文档库管理的真实模式已打通「模板 → 版本 → 正文」三段:
@@ -154,7 +154,8 @@ export async function updateCampus(
return mapCampus(unwrapApiResponse(response)) return mapCampus(unwrapApiResponse(response))
} }
export async function deleteCampus(id: string): Promise<void> { /** DELETE 接口将院区停用,保留记录及下属科室。 */
export async function disableCampus(id: string): Promise<void> {
if (useMockData) { if (useMockData) {
throw new Error('Mock 模式不执行院区写操作') throw new Error('Mock 模式不执行院区写操作')
} }
@@ -192,7 +193,8 @@ export async function updateDepartment(
return mapDepartment(unwrapApiResponse(response)) return mapDepartment(unwrapApiResponse(response))
} }
export async function deleteDepartment(id: string): Promise<void> { /** DELETE 接口将当前科室停用,保留记录及下级科室。 */
export async function disableDepartment(id: string): Promise<void> {
if (useMockData) { if (useMockData) {
throw new Error('Mock 模式不执行科室写操作') throw new Error('Mock 模式不执行科室写操作')
} }
@@ -121,7 +121,7 @@ const libraryCampusId = computed(() => (allCampusesSelected.value ? '' : appStor
const filters = reactive<DocumentFilterForm>({ const filters = reactive<DocumentFilterForm>({
keyword: '', keyword: '',
department: null, department: null,
status: 'all', status: '启用中',
}) })
/** 预览弹窗 */ /** 预览弹窗 */
@@ -1190,8 +1190,8 @@ async function resetMockData() {
function clearFilters() { function clearFilters() {
filters.keyword = '' filters.keyword = ''
filters.department = null filters.department = null
// status 也要一起重置:只按状态页签筛出空结果时,不清它等于「清了个寂寞」 // 清除筛选后恢复默认的启用中状态。
filters.status = 'all' filters.status = '启用中'
// 院区不在这里重置:它是页面的取数口径(文书库按院区向服务端取数), // 院区不在这里重置:它是页面的取数口径(文书库按院区向服务端取数),
// 不是「筛掉一部分已加载数据」的筛选条件,清掉它得重新拉一遍文库 // 不是「筛掉一部分已加载数据」的筛选条件,清掉它得重新拉一遍文库
} }
@@ -1302,7 +1302,7 @@ onMounted(() => {
<template v-else> <template v-else>
<p>没有符合条件的文书</p> <p>没有符合条件的文书</p>
<button <button
v-if="filters.keyword || filters.department || filters.status !== 'all'" v-if="filters.keyword || filters.department || filters.status !== '启用中'"
type="button" type="button"
class="reset-button" class="reset-button"
@click="clearFilters" @click="clearFilters"
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed, ref } from 'vue'
import type { CampusRecord, DepartmentRecord } from '@/api/management/types' import type { CampusRecord, DepartmentRecord } from '@/api/management/types'
@@ -11,6 +11,7 @@ const props = defineProps<{
collapsed: boolean collapsed: boolean
canManageCampuses: boolean canManageCampuses: boolean
canManageDepartments: boolean canManageDepartments: boolean
disabling: boolean
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
@@ -20,13 +21,29 @@ const emit = defineEmits<{
addDepartment: [] addDepartment: []
editCampus: [campus: CampusRecord] editCampus: [campus: CampusRecord]
editDepartment: [department: DepartmentRecord] editDepartment: [department: DepartmentRecord]
deleteCampus: [campus: CampusRecord] disableCampus: [campus: CampusRecord]
deleteDepartment: [department: DepartmentRecord] disableDepartment: [department: DepartmentRecord]
}>() }>()
const statusFilter = ref<'ENABLED' | 'DISABLED' | 'all'>('ENABLED')
const visibleDepartments = computed(() =>
props.departments.filter(
(department) => statusFilter.value === 'all' || department.status === statusFilter.value,
),
)
const visibleCampuses = computed(() =>
props.campuses.filter(
(campus) =>
statusFilter.value === 'all' ||
campus.status === statusFilter.value ||
(statusFilter.value === 'DISABLED' &&
visibleDepartments.value.some((department) => department.campusId === campus.id)),
),
)
const institutionGroups = computed(() => { const institutionGroups = computed(() => {
const grouped = new Map<string, CampusRecord[]>() const grouped = new Map<string, CampusRecord[]>()
for (const campus of props.campuses) { for (const campus of visibleCampuses.value) {
const institutionName = campus.institutionName?.trim() || '新华院区' const institutionName = campus.institutionName?.trim() || '新华院区'
const group = grouped.get(institutionName) ?? [] const group = grouped.get(institutionName) ?? []
group.push(campus) group.push(campus)
@@ -36,7 +53,7 @@ const institutionGroups = computed(() => {
}) })
function departmentsForCampus(campusId: string) { function departmentsForCampus(campusId: string) {
return props.departments return visibleDepartments.value
.filter((department) => department.campusId === campusId) .filter((department) => department.campusId === campusId)
.sort( .sort(
(left, right) => left.sortNo - right.sortNo || left.name.localeCompare(right.name, 'zh-CN'), (left, right) => left.sortNo - right.sortNo || left.name.localeCompare(right.name, 'zh-CN'),
@@ -45,13 +62,16 @@ function departmentsForCampus(campusId: string) {
function departmentCountForInstitution(campuses: CampusRecord[]) { function departmentCountForInstitution(campuses: CampusRecord[]) {
const campusIds = new Set(campuses.map((campus) => campus.id)) const campusIds = new Set(campuses.map((campus) => campus.id))
return props.departments.filter((department) => campusIds.has(department.campusId)).length return visibleDepartments.value.filter((department) => campusIds.has(department.campusId)).length
} }
function childrenFor(parentId: string | null | undefined, campusId: string) { function childrenFor(parentId: string | null | undefined, campusId: string) {
return departmentsForCampus(campusId).filter( const departments = departmentsForCampus(campusId)
(department) => (department.parentId ?? null) === (parentId ?? null), return departments.filter((department) => {
) const visibleParent = departments.some((item) => item.id === department.parentId)
const effectiveParentId = visibleParent ? department.parentId : null
return (effectiveParentId ?? null) === (parentId ?? null)
})
} }
function isDescendantSelected(department: DepartmentRecord): boolean { function isDescendantSelected(department: DepartmentRecord): boolean {
@@ -82,6 +102,15 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
</button> </button>
</header> </header>
<label class="org-status-filter">
<span>状态</span>
<select v-model="statusFilter" aria-label="组织节点状态">
<option value="ENABLED">启用中</option>
<option value="DISABLED">已停用</option>
<option value="all">全部状态</option>
</select>
</label>
<div class="org-tree"> <div class="org-tree">
<div <div
v-for="institution in institutionGroups" v-for="institution in institutionGroups"
@@ -100,7 +129,10 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
<div v-for="campus in institution.campuses" :key="campus.id" class="org-tree__campus-group"> <div v-for="campus in institution.campuses" :key="campus.id" class="org-tree__campus-group">
<div <div
class="org-tree__node org-tree__campus" class="org-tree__node org-tree__campus"
:class="{ 'is-selected': selectedCampusId === campus.id && !selectedDepartmentId }" :class="{
'is-selected': selectedCampusId === campus.id && !selectedDepartmentId,
'is-disabled': campus.status === 'DISABLED',
}"
> >
<button <button
type="button" type="button"
@@ -115,6 +147,7 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
> >
<span class="org-tree__icon">🏥</span> <span class="org-tree__icon">🏥</span>
<span class="org-tree__name">{{ campus.name }}</span> <span class="org-tree__name">{{ campus.name }}</span>
<span v-if="campus.status === 'DISABLED'" class="org-tree__status">已停用</span>
<span class="org-tree__count">{{ departmentsForCampus(campus.id).length }} 科室</span> <span class="org-tree__count">{{ departmentsForCampus(campus.id).length }} 科室</span>
</button> </button>
<div v-if="canManageCampuses" class="org-tree__actions"> <div v-if="canManageCampuses" class="org-tree__actions">
@@ -127,12 +160,14 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
编辑 编辑
</button> </button>
<button <button
v-if="campus.status !== 'DISABLED'"
type="button" type="button"
class="org-tree__remove" class="org-tree__disable"
:aria-label="`删除院区${campus.name}`" :disabled="disabling"
@click="emit('deleteCampus', campus)" :aria-label="`停用院区${campus.name}`"
@click="emit('disableCampus', campus)"
> >
删除 停用
</button> </button>
</div> </div>
</div> </div>
@@ -144,7 +179,10 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
<template v-for="department in childrenFor(null, campus.id)" :key="department.id"> <template v-for="department in childrenFor(null, campus.id)" :key="department.id">
<div <div
class="org-tree__node org-tree__department" class="org-tree__node org-tree__department"
:class="{ 'is-selected': isDescendantSelected(department) }" :class="{
'is-selected': isDescendantSelected(department),
'is-disabled': department.status === 'DISABLED',
}"
> >
<button <button
type="button" type="button"
@@ -152,6 +190,9 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
@click="emit('selectDepartment', department)" @click="emit('selectDepartment', department)"
> >
<span class="org-tree__name">🗂 {{ department.name }}</span> <span class="org-tree__name">🗂 {{ department.name }}</span>
<span v-if="department.status === 'DISABLED'" class="org-tree__status"
>已停用</span
>
</button> </button>
<div v-if="canManageDepartments" class="org-tree__actions"> <div v-if="canManageDepartments" class="org-tree__actions">
<button <button
@@ -163,12 +204,14 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
编辑 编辑
</button> </button>
<button <button
v-if="department.status !== 'DISABLED'"
type="button" type="button"
class="org-tree__remove" class="org-tree__disable"
:aria-label="`删除科室${department.name}`" :disabled="disabling"
@click="emit('deleteDepartment', department)" :aria-label="`停用科室${department.name}`"
@click="emit('disableDepartment', department)"
> >
删除 停用
</button> </button>
</div> </div>
</div> </div>
@@ -176,7 +219,10 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
v-for="child in childrenFor(department.id, campus.id)" v-for="child in childrenFor(department.id, campus.id)"
:key="child.id" :key="child.id"
class="org-tree__node org-tree__child-department" class="org-tree__node org-tree__child-department"
:class="{ 'is-selected': isDescendantSelected(child) }" :class="{
'is-selected': isDescendantSelected(child),
'is-disabled': child.status === 'DISABLED',
}"
> >
<button <button
type="button" type="button"
@@ -184,6 +230,7 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
@click="emit('selectDepartment', child)" @click="emit('selectDepartment', child)"
> >
<span class="org-tree__name">{{ child.name }}</span> <span class="org-tree__name">{{ child.name }}</span>
<span v-if="child.status === 'DISABLED'" class="org-tree__status">已停用</span>
</button> </button>
<div v-if="canManageDepartments" class="org-tree__actions"> <div v-if="canManageDepartments" class="org-tree__actions">
<button <button
@@ -195,24 +242,26 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
编辑 编辑
</button> </button>
<button <button
v-if="child.status !== 'DISABLED'"
type="button" type="button"
class="org-tree__remove" class="org-tree__disable"
:aria-label="`删除科室${child.name}`" :disabled="disabling"
@click="emit('deleteDepartment', child)" :aria-label="`停用科室${child.name}`"
@click="emit('disableDepartment', child)"
> >
删除 停用
</button> </button>
</div> </div>
</div> </div>
</template> </template>
<p v-if="!childrenFor(null, campus.id).length" class="org-tree__empty"> <p v-if="!childrenFor(null, campus.id).length" class="org-tree__empty">
该院区还没有维护科室 当前状态下暂无科室
</p> </p>
</div> </div>
</div> </div>
</div> </div>
<p v-if="!campuses.length" class="org-tree__empty">暂无可用院区</p> <p v-if="!visibleCampuses.length" class="org-tree__empty">当前状态下暂无院区</p>
</div> </div>
</section> </section>
</template> </template>
@@ -274,6 +323,24 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
border-color: #d3e7ee; border-color: #d3e7ee;
} }
.org-status-filter {
display: flex;
gap: 8px;
align-items: center;
margin: 10px 0;
color: var(--mut);
font-size: 12px;
}
.org-status-filter select {
flex: 1;
padding: 5px 8px;
color: var(--ink);
background: #fff;
border: 1px solid var(--line);
border-radius: 6px;
}
.org-tree { .org-tree {
display: grid; display: grid;
gap: 4px; gap: 4px;
@@ -408,7 +475,7 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
} }
.org-tree__edit, .org-tree__edit,
.org-tree__remove { .org-tree__disable {
padding: 3px 7px; padding: 3px 7px;
font-size: 10.5px; font-size: 10.5px;
background: #fff; background: #fff;
@@ -421,19 +488,39 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
border: 1px solid #d3e7ee; border: 1px solid #d3e7ee;
} }
.org-tree__remove { .org-tree__disable {
margin-right: 6px; margin-right: 6px;
color: var(--err); color: var(--err);
border: 1px solid #f0cdcd; border: 1px solid #f0cdcd;
} }
.org-tree__node:hover .org-tree__edit, .org-tree__node:hover .org-tree__edit,
.org-tree__node:hover .org-tree__remove, .org-tree__node:hover .org-tree__disable,
.org-tree__node:focus-within .org-tree__edit, .org-tree__node:focus-within .org-tree__edit,
.org-tree__node:focus-within .org-tree__remove { .org-tree__node:focus-within .org-tree__disable {
opacity: 1; opacity: 1;
} }
.org-tree__node.is-disabled {
color: var(--mut);
}
.org-tree__status {
flex: 0 0 auto;
padding: 2px 5px;
color: var(--mut);
font-size: 10px;
font-weight: 400;
white-space: nowrap;
background: #f0f2f4;
border: 1px solid var(--line);
border-radius: 4px;
}
.org-tree__disable:disabled {
cursor: not-allowed;
}
.org-tree__empty { .org-tree__empty {
padding: 10px; padding: 10px;
margin: 0; margin: 0;
@@ -5,8 +5,8 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import { import {
createCampus, createCampus,
createDepartment, createDepartment,
deleteCampus, disableCampus,
deleteDepartment, disableDepartment,
getAllDepartments, getAllDepartments,
getCampuses, getCampuses,
updateCampus, updateCampus,
@@ -68,6 +68,7 @@ const organizationDialogVisible = ref(false)
const organizationDialogKind = ref<'campus' | 'department'>('campus') const organizationDialogKind = ref<'campus' | 'department'>('campus')
const organizationDialogInitial = ref<CampusRecord | DepartmentRecord | null>(null) const organizationDialogInitial = ref<CampusRecord | DepartmentRecord | null>(null)
const organizationSaving = ref(false) const organizationSaving = ref(false)
const organizationDisabling = ref(false)
let userRequestId = 0 let userRequestId = 0
const canEditUsers = computed(() => isUserMockEnabled || loginStore.hasPermission('system:org:emp')) const canEditUsers = computed(() => isUserMockEnabled || loginStore.hasPermission('system:org:emp'))
@@ -302,7 +303,7 @@ function mapMockUsers() {
userTotal.value = userRows.value.length userTotal.value = userRows.value.length
} }
async function loadUsers() { async function loadUsers(notifyOnError = true): Promise<boolean> {
const requestId = ++userRequestId const requestId = ++userRequestId
loading.value = true loading.value = true
try { try {
@@ -321,7 +322,7 @@ async function loadUsers() {
return matchesCampus && matchesDepartment && matchesKeyword return matchesCampus && matchesDepartment && matchesKeyword
}) })
userTotal.value = filtered.length userTotal.value = filtered.length
return return true
} }
const result = await getUsers({ const result = await getUsers({
@@ -332,7 +333,7 @@ async function loadUsers() {
campusId: selectedCampusId.value || undefined, campusId: selectedCampusId.value || undefined,
departmentId: selectedDepartmentId.value ?? undefined, departmentId: selectedDepartmentId.value ?? undefined,
}) })
if (requestId !== userRequestId) return if (requestId !== userRequestId) return false
const campusNames = new Map(campuses.value.map((campus) => [campus.id, campus.name])) const campusNames = new Map(campuses.value.map((campus) => [campus.id, campus.name]))
const departmentNames = new Map( const departmentNames = new Map(
@@ -340,17 +341,20 @@ async function loadUsers() {
) )
userRows.value = result.records.map((user) => mapUserRow(user, campusNames, departmentNames)) userRows.value = result.records.map((user) => mapUserRow(user, campusNames, departmentNames))
userTotal.value = result.total userTotal.value = result.total
return true
} catch { } catch {
if (requestId !== userRequestId) return if (requestId !== userRequestId) return false
userRows.value = [] userRows.value = []
userTotal.value = 0 userTotal.value = 0
ElMessage.error('员工列表加载失败,请稍后重试') if (notifyOnError) ElMessage.error('员工列表加载失败,请稍后重试')
return false
} finally { } finally {
if (requestId === userRequestId) loading.value = false if (requestId === userRequestId) loading.value = false
} }
} }
async function loadOrganization() { async function loadOrganization(notifyOnError = true): Promise<boolean> {
let complete = true
loading.value = true loading.value = true
try { try {
if (isUserMockEnabled) { if (isUserMockEnabled) {
@@ -363,15 +367,16 @@ async function loadOrganization() {
]) ])
if (campusResult.status === 'rejected') throw campusResult.reason if (campusResult.status === 'rejected') throw campusResult.reason
campuses.value = campusResult.value campuses.value = campusResult.value
departments.value = departmentResult.status === 'fulfilled' ? departmentResult.value : [] if (departmentResult.status === 'fulfilled') departments.value = departmentResult.value
else complete = false
roleOptions.value = rolesResult.status === 'fulfilled' ? rolesResult.value : [] roleOptions.value = rolesResult.status === 'fulfilled' ? rolesResult.value : []
roleColorMap.value = Object.fromEntries( roleColorMap.value = Object.fromEntries(
roleOptions.value.map((role) => [role.name, role.color ?? '#8a9aa3']), roleOptions.value.map((role) => [role.name, role.color ?? '#8a9aa3']),
) )
if (canAssignRoles.value && rolesResult.status === 'rejected') { if (canAssignRoles.value && rolesResult.status === 'rejected' && notifyOnError) {
ElMessage.warning('员工角色选项加载失败,角色绑定暂不可用') ElMessage.warning('员工角色选项加载失败,角色绑定暂不可用')
} }
if (departmentResult.status === 'rejected') { if (departmentResult.status === 'rejected' && notifyOnError) {
ElMessage.warning('科室架构加载失败,员工科室筛选暂不可用') ElMessage.warning('科室架构加载失败,员工科室筛选暂不可用')
} }
} }
@@ -390,10 +395,12 @@ async function loadOrganization() {
campusCollapsed.value = false campusCollapsed.value = false
if (preferredCampus) appStore.selectCampus(preferredCampus) if (preferredCampus) appStore.selectCampus(preferredCampus)
userPage.value = 1 userPage.value = 1
await loadUsers() const usersLoaded = await loadUsers(notifyOnError)
return complete && usersLoaded
} catch { } catch {
loading.value = false loading.value = false
ElMessage.error('组织架构加载失败,请稍后重试') if (notifyOnError) ElMessage.error('组织架构加载失败,请稍后重试')
return false
} }
} }
@@ -647,7 +654,6 @@ async function saveOrganizationNode(form: OrganizationNodeSave) {
const campus = form.id const campus = form.id
? await updateCampus(form.id, form.payload) ? await updateCampus(form.id, form.payload)
: await createCampus(form.payload) : await createCampus(form.payload)
await appStore.loadCampuses(true)
if (!form.id) selectedCampusId.value = campus.id if (!form.id) selectedCampusId.value = campus.id
} else { } else {
const department = form.id const department = form.id
@@ -657,8 +663,9 @@ async function saveOrganizationNode(form: OrganizationNodeSave) {
if (!form.id) selectedDepartmentId.value = department.id if (!form.id) selectedDepartmentId.value = department.id
} }
organizationDialogVisible.value = false organizationDialogVisible.value = false
await loadOrganization() const refreshed = await refreshOrganizationAfterMutation(form.kind)
ElMessage.success(form.id ? '组织信息已更新' : '组织节点已创建') if (refreshed) ElMessage.success(form.id ? '组织信息已更新' : '组织节点已创建')
else ElMessage.warning('组织信息已保存,但页面刷新失败,请刷新页面重试')
} catch { } catch {
ElMessage.error('组织信息保存失败,请检查编码是否重复或所属院区是否有效') ElMessage.error('组织信息保存失败,请检查编码是否重复或所属院区是否有效')
} finally { } finally {
@@ -666,54 +673,77 @@ async function saveOrganizationNode(form: OrganizationNodeSave) {
} }
} }
async function removeCampus(campus: CampusRecord) { async function refreshOrganizationAfterMutation(kind: 'campus' | 'department'): Promise<boolean> {
if (!canManageCampuses.value) return try {
const departmentCount = departments.value.filter( if (kind === 'campus') await appStore.loadCampuses(true)
(department) => department.campusId === campus.id, const refreshed = await loadOrganization(false)
).length return refreshed && (kind !== 'campus' || !appStore.campusLoadFailed)
} catch {
return false
}
}
async function handleDisableCampus(campus: CampusRecord) {
if (!canManageCampuses.value || campus.status === 'DISABLED' || organizationDisabling.value)
return
organizationDisabling.value = true
try {
const confirmed = await ElMessageBox.confirm( const confirmed = await ElMessageBox.confirm(
departmentCount `停用院区“${campus.name}”只修改该院区的状态,不会停用或删除下属科室。组织记录和历史数据保留,可通过编辑重新启用。是否继续?`,
? `删除院区“${campus.name}”会同时移除其下 ${departmentCount} 个科室,且无法恢复,是否继续?` '停用院区',
: `删除院区“${campus.name}”后无法恢复,是否继续?`, { type: 'warning', confirmButtonText: '停用', cancelButtonText: '取消' },
'删除院区',
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' },
) )
.then(() => true) .then(() => true)
.catch(() => false) .catch(() => false)
if (!confirmed) return if (!confirmed) return
try { try {
await deleteCampus(campus.id) await disableCampus(campus.id)
} catch (error) {
ElMessage.error(toUserMessage(error, '院区停用失败,请稍后重试'))
return
}
campus.status = 'DISABLED'
if (selectedCampusId.value === campus.id) selectedDepartmentId.value = null if (selectedCampusId.value === campus.id) selectedDepartmentId.value = null
await appStore.loadCampuses(true) const refreshed = await refreshOrganizationAfterMutation('campus')
await loadOrganization() if (refreshed) ElMessage.success('院区已停用')
ElMessage.success('院区已删除') else ElMessage.warning('院区已停用,但页面刷新失败,请刷新页面重试')
} catch (error) { } finally {
ElMessage.error(toUserMessage(error, '院区删除失败,请稍后重试')) organizationDisabling.value = false
} }
} }
async function removeDepartment(department: DepartmentRecord) { async function handleDisableDepartment(department: DepartmentRecord) {
if (!canManageDepartments.value) return if (
const childCount = departments.value.filter((item) => item.parentId === department.id).length !canManageDepartments.value ||
department.status === 'DISABLED' ||
organizationDisabling.value
)
return
organizationDisabling.value = true
try {
const confirmed = await ElMessageBox.confirm( const confirmed = await ElMessageBox.confirm(
childCount `停用科室“${department.name}”只修改当前科室的状态,不会停用或删除下级科室。组织记录和历史数据保留,可通过编辑重新启用。是否继续?`,
? `删除科室“${department.name}”会同时移除其下 ${childCount} 个下级科室,且无法恢复,是否继续?` '停用科室',
: `删除科室“${department.name}”后无法恢复,是否继续?`, { type: 'warning', confirmButtonText: '停用', cancelButtonText: '取消' },
'删除科室',
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' },
) )
.then(() => true) .then(() => true)
.catch(() => false) .catch(() => false)
if (!confirmed) return if (!confirmed) return
try { try {
await deleteDepartment(department.id) await disableDepartment(department.id)
if (selectedDepartmentId.value === department.id) selectedDepartmentId.value = null
await loadOrganization()
ElMessage.success('科室已删除')
} catch (error) { } catch (error) {
ElMessage.error(toUserMessage(error, '科室删除失败,请稍后重试')) ElMessage.error(toUserMessage(error, '科室停用失败,请稍后重试'))
return
}
department.status = 'DISABLED'
if (selectedDepartmentId.value === department.id) selectedDepartmentId.value = null
const refreshed = await refreshOrganizationAfterMutation('department')
if (refreshed) ElMessage.success('科室已停用')
else ElMessage.warning('科室已停用,但页面刷新失败,请刷新页面重试')
} finally {
organizationDisabling.value = false
} }
} }
@@ -771,14 +801,15 @@ onMounted(loadOrganization)
:collapsed="campusCollapsed" :collapsed="campusCollapsed"
:can-manage-campuses="canManageCampuses" :can-manage-campuses="canManageCampuses"
:can-manage-departments="canManageDepartments" :can-manage-departments="canManageDepartments"
:disabling="organizationDisabling"
@select-campus="selectCampus" @select-campus="selectCampus"
@select-department="selectDepartment" @select-department="selectDepartment"
@add-campus="openAddCampus" @add-campus="openAddCampus"
@add-department="openAddDepartment" @add-department="openAddDepartment"
@edit-campus="editCampus" @edit-campus="editCampus"
@edit-department="editDepartment" @edit-department="editDepartment"
@delete-campus="removeCampus" @disable-campus="handleDisableCampus"
@delete-department="removeDepartment" @disable-department="handleDisableDepartment"
/> />
<section class="employee-card"> <section class="employee-card">