fix(web): 修正组织停用行为并默认筛选启用中文档
This commit is contained in:
@@ -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))
|
||||
}
|
||||
|
||||
export async function deleteCampus(id: string): Promise<void> {
|
||||
/** DELETE 接口将院区停用,保留记录及下属科室。 */
|
||||
export async function disableCampus(id: string): Promise<void> {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行院区写操作')
|
||||
}
|
||||
@@ -192,7 +193,8 @@ export async function updateDepartment(
|
||||
return mapDepartment(unwrapApiResponse(response))
|
||||
}
|
||||
|
||||
export async function deleteDepartment(id: string): Promise<void> {
|
||||
/** DELETE 接口将当前科室停用,保留记录及下级科室。 */
|
||||
export async function disableDepartment(id: string): Promise<void> {
|
||||
if (useMockData) {
|
||||
throw new Error('Mock 模式不执行科室写操作')
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ const libraryCampusId = computed(() => (allCampusesSelected.value ? '' : appStor
|
||||
const filters = reactive<DocumentFilterForm>({
|
||||
keyword: '',
|
||||
department: null,
|
||||
status: 'all',
|
||||
status: '启用中',
|
||||
})
|
||||
|
||||
/** 预览弹窗 */
|
||||
@@ -1190,8 +1190,8 @@ async function resetMockData() {
|
||||
function clearFilters() {
|
||||
filters.keyword = ''
|
||||
filters.department = null
|
||||
// status 也要一起重置:只按状态页签筛出空结果时,不清它等于「清了个寂寞」
|
||||
filters.status = 'all'
|
||||
// 清除筛选后恢复默认的启用中状态。
|
||||
filters.status = '启用中'
|
||||
// 院区不在这里重置:它是页面的取数口径(文书库按院区向服务端取数),
|
||||
// 不是「筛掉一部分已加载数据」的筛选条件,清掉它得重新拉一遍文库
|
||||
}
|
||||
@@ -1302,7 +1302,7 @@ onMounted(() => {
|
||||
<template v-else>
|
||||
<p>没有符合条件的文书</p>
|
||||
<button
|
||||
v-if="filters.keyword || filters.department || filters.status !== 'all'"
|
||||
v-if="filters.keyword || filters.department || filters.status !== '启用中'"
|
||||
type="button"
|
||||
class="reset-button"
|
||||
@click="clearFilters"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import type { CampusRecord, DepartmentRecord } from '@/api/management/types'
|
||||
|
||||
@@ -11,6 +11,7 @@ const props = defineProps<{
|
||||
collapsed: boolean
|
||||
canManageCampuses: boolean
|
||||
canManageDepartments: boolean
|
||||
disabling: boolean
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -20,13 +21,29 @@ const emit = defineEmits<{
|
||||
addDepartment: []
|
||||
editCampus: [campus: CampusRecord]
|
||||
editDepartment: [department: DepartmentRecord]
|
||||
deleteCampus: [campus: CampusRecord]
|
||||
deleteDepartment: [department: DepartmentRecord]
|
||||
disableCampus: [campus: CampusRecord]
|
||||
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 grouped = new Map<string, CampusRecord[]>()
|
||||
for (const campus of props.campuses) {
|
||||
for (const campus of visibleCampuses.value) {
|
||||
const institutionName = campus.institutionName?.trim() || '新华院区'
|
||||
const group = grouped.get(institutionName) ?? []
|
||||
group.push(campus)
|
||||
@@ -36,7 +53,7 @@ const institutionGroups = computed(() => {
|
||||
})
|
||||
|
||||
function departmentsForCampus(campusId: string) {
|
||||
return props.departments
|
||||
return visibleDepartments.value
|
||||
.filter((department) => department.campusId === campusId)
|
||||
.sort(
|
||||
(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[]) {
|
||||
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) {
|
||||
return departmentsForCampus(campusId).filter(
|
||||
(department) => (department.parentId ?? null) === (parentId ?? null),
|
||||
)
|
||||
const departments = departmentsForCampus(campusId)
|
||||
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 {
|
||||
@@ -82,6 +102,15 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
|
||||
</button>
|
||||
</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
|
||||
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
|
||||
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
|
||||
type="button"
|
||||
@@ -115,6 +147,7 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
|
||||
>
|
||||
<span class="org-tree__icon">🏥</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>
|
||||
</button>
|
||||
<div v-if="canManageCampuses" class="org-tree__actions">
|
||||
@@ -127,12 +160,14 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
v-if="campus.status !== 'DISABLED'"
|
||||
type="button"
|
||||
class="org-tree__remove"
|
||||
:aria-label="`删除院区${campus.name}`"
|
||||
@click="emit('deleteCampus', campus)"
|
||||
class="org-tree__disable"
|
||||
:disabled="disabling"
|
||||
:aria-label="`停用院区${campus.name}`"
|
||||
@click="emit('disableCampus', campus)"
|
||||
>
|
||||
删除
|
||||
停用
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -144,7 +179,10 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
|
||||
<template v-for="department in childrenFor(null, campus.id)" :key="department.id">
|
||||
<div
|
||||
class="org-tree__node org-tree__department"
|
||||
:class="{ 'is-selected': isDescendantSelected(department) }"
|
||||
:class="{
|
||||
'is-selected': isDescendantSelected(department),
|
||||
'is-disabled': department.status === 'DISABLED',
|
||||
}"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
@@ -152,6 +190,9 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
|
||||
@click="emit('selectDepartment', department)"
|
||||
>
|
||||
<span class="org-tree__name">🗂 {{ department.name }}</span>
|
||||
<span v-if="department.status === 'DISABLED'" class="org-tree__status"
|
||||
>已停用</span
|
||||
>
|
||||
</button>
|
||||
<div v-if="canManageDepartments" class="org-tree__actions">
|
||||
<button
|
||||
@@ -163,12 +204,14 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
v-if="department.status !== 'DISABLED'"
|
||||
type="button"
|
||||
class="org-tree__remove"
|
||||
:aria-label="`删除科室${department.name}`"
|
||||
@click="emit('deleteDepartment', department)"
|
||||
class="org-tree__disable"
|
||||
:disabled="disabling"
|
||||
:aria-label="`停用科室${department.name}`"
|
||||
@click="emit('disableDepartment', department)"
|
||||
>
|
||||
删除
|
||||
停用
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -176,7 +219,10 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
|
||||
v-for="child in childrenFor(department.id, campus.id)"
|
||||
:key="child.id"
|
||||
class="org-tree__node org-tree__child-department"
|
||||
:class="{ 'is-selected': isDescendantSelected(child) }"
|
||||
:class="{
|
||||
'is-selected': isDescendantSelected(child),
|
||||
'is-disabled': child.status === 'DISABLED',
|
||||
}"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
@@ -184,6 +230,7 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
|
||||
@click="emit('selectDepartment', child)"
|
||||
>
|
||||
<span class="org-tree__name">{{ child.name }}</span>
|
||||
<span v-if="child.status === 'DISABLED'" class="org-tree__status">已停用</span>
|
||||
</button>
|
||||
<div v-if="canManageDepartments" class="org-tree__actions">
|
||||
<button
|
||||
@@ -195,24 +242,26 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
v-if="child.status !== 'DISABLED'"
|
||||
type="button"
|
||||
class="org-tree__remove"
|
||||
:aria-label="`删除科室${child.name}`"
|
||||
@click="emit('deleteDepartment', child)"
|
||||
class="org-tree__disable"
|
||||
:disabled="disabling"
|
||||
:aria-label="`停用科室${child.name}`"
|
||||
@click="emit('disableDepartment', child)"
|
||||
>
|
||||
删除
|
||||
停用
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<p v-if="!childrenFor(null, campus.id).length" class="org-tree__empty">
|
||||
该院区还没有维护科室
|
||||
当前状态下暂无科室
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="!campuses.length" class="org-tree__empty">暂无可用院区</p>
|
||||
<p v-if="!visibleCampuses.length" class="org-tree__empty">当前状态下暂无院区</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -274,6 +323,24 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
|
||||
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 {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
@@ -408,7 +475,7 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
|
||||
}
|
||||
|
||||
.org-tree__edit,
|
||||
.org-tree__remove {
|
||||
.org-tree__disable {
|
||||
padding: 3px 7px;
|
||||
font-size: 10.5px;
|
||||
background: #fff;
|
||||
@@ -421,19 +488,39 @@ function isDescendantSelected(department: DepartmentRecord): boolean {
|
||||
border: 1px solid #d3e7ee;
|
||||
}
|
||||
|
||||
.org-tree__remove {
|
||||
.org-tree__disable {
|
||||
margin-right: 6px;
|
||||
color: var(--err);
|
||||
border: 1px solid #f0cdcd;
|
||||
}
|
||||
|
||||
.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__remove {
|
||||
.org-tree__node:focus-within .org-tree__disable {
|
||||
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 {
|
||||
padding: 10px;
|
||||
margin: 0;
|
||||
|
||||
@@ -5,8 +5,8 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
createCampus,
|
||||
createDepartment,
|
||||
deleteCampus,
|
||||
deleteDepartment,
|
||||
disableCampus,
|
||||
disableDepartment,
|
||||
getAllDepartments,
|
||||
getCampuses,
|
||||
updateCampus,
|
||||
@@ -68,6 +68,7 @@ const organizationDialogVisible = ref(false)
|
||||
const organizationDialogKind = ref<'campus' | 'department'>('campus')
|
||||
const organizationDialogInitial = ref<CampusRecord | DepartmentRecord | null>(null)
|
||||
const organizationSaving = ref(false)
|
||||
const organizationDisabling = ref(false)
|
||||
let userRequestId = 0
|
||||
|
||||
const canEditUsers = computed(() => isUserMockEnabled || loginStore.hasPermission('system:org:emp'))
|
||||
@@ -302,7 +303,7 @@ function mapMockUsers() {
|
||||
userTotal.value = userRows.value.length
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
async function loadUsers(notifyOnError = true): Promise<boolean> {
|
||||
const requestId = ++userRequestId
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -321,7 +322,7 @@ async function loadUsers() {
|
||||
return matchesCampus && matchesDepartment && matchesKeyword
|
||||
})
|
||||
userTotal.value = filtered.length
|
||||
return
|
||||
return true
|
||||
}
|
||||
|
||||
const result = await getUsers({
|
||||
@@ -332,7 +333,7 @@ async function loadUsers() {
|
||||
campusId: selectedCampusId.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 departmentNames = new Map(
|
||||
@@ -340,17 +341,20 @@ async function loadUsers() {
|
||||
)
|
||||
userRows.value = result.records.map((user) => mapUserRow(user, campusNames, departmentNames))
|
||||
userTotal.value = result.total
|
||||
return true
|
||||
} catch {
|
||||
if (requestId !== userRequestId) return
|
||||
if (requestId !== userRequestId) return false
|
||||
userRows.value = []
|
||||
userTotal.value = 0
|
||||
ElMessage.error('员工列表加载失败,请稍后重试')
|
||||
if (notifyOnError) ElMessage.error('员工列表加载失败,请稍后重试')
|
||||
return false
|
||||
} finally {
|
||||
if (requestId === userRequestId) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOrganization() {
|
||||
async function loadOrganization(notifyOnError = true): Promise<boolean> {
|
||||
let complete = true
|
||||
loading.value = true
|
||||
try {
|
||||
if (isUserMockEnabled) {
|
||||
@@ -363,15 +367,16 @@ async function loadOrganization() {
|
||||
])
|
||||
if (campusResult.status === 'rejected') throw campusResult.reason
|
||||
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 : []
|
||||
roleColorMap.value = Object.fromEntries(
|
||||
roleOptions.value.map((role) => [role.name, role.color ?? '#8a9aa3']),
|
||||
)
|
||||
if (canAssignRoles.value && rolesResult.status === 'rejected') {
|
||||
if (canAssignRoles.value && rolesResult.status === 'rejected' && notifyOnError) {
|
||||
ElMessage.warning('员工角色选项加载失败,角色绑定暂不可用')
|
||||
}
|
||||
if (departmentResult.status === 'rejected') {
|
||||
if (departmentResult.status === 'rejected' && notifyOnError) {
|
||||
ElMessage.warning('科室架构加载失败,员工科室筛选暂不可用')
|
||||
}
|
||||
}
|
||||
@@ -390,10 +395,12 @@ async function loadOrganization() {
|
||||
campusCollapsed.value = false
|
||||
if (preferredCampus) appStore.selectCampus(preferredCampus)
|
||||
userPage.value = 1
|
||||
await loadUsers()
|
||||
const usersLoaded = await loadUsers(notifyOnError)
|
||||
return complete && usersLoaded
|
||||
} catch {
|
||||
loading.value = false
|
||||
ElMessage.error('组织架构加载失败,请稍后重试')
|
||||
if (notifyOnError) ElMessage.error('组织架构加载失败,请稍后重试')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -647,7 +654,6 @@ async function saveOrganizationNode(form: OrganizationNodeSave) {
|
||||
const campus = form.id
|
||||
? await updateCampus(form.id, form.payload)
|
||||
: await createCampus(form.payload)
|
||||
await appStore.loadCampuses(true)
|
||||
if (!form.id) selectedCampusId.value = campus.id
|
||||
} else {
|
||||
const department = form.id
|
||||
@@ -657,8 +663,9 @@ async function saveOrganizationNode(form: OrganizationNodeSave) {
|
||||
if (!form.id) selectedDepartmentId.value = department.id
|
||||
}
|
||||
organizationDialogVisible.value = false
|
||||
await loadOrganization()
|
||||
ElMessage.success(form.id ? '组织信息已更新' : '组织节点已创建')
|
||||
const refreshed = await refreshOrganizationAfterMutation(form.kind)
|
||||
if (refreshed) ElMessage.success(form.id ? '组织信息已更新' : '组织节点已创建')
|
||||
else ElMessage.warning('组织信息已保存,但页面刷新失败,请刷新页面重试')
|
||||
} catch {
|
||||
ElMessage.error('组织信息保存失败,请检查编码是否重复或所属院区是否有效')
|
||||
} finally {
|
||||
@@ -666,54 +673,77 @@ async function saveOrganizationNode(form: OrganizationNodeSave) {
|
||||
}
|
||||
}
|
||||
|
||||
async function removeCampus(campus: CampusRecord) {
|
||||
if (!canManageCampuses.value) return
|
||||
const departmentCount = departments.value.filter(
|
||||
(department) => department.campusId === campus.id,
|
||||
).length
|
||||
const confirmed = await ElMessageBox.confirm(
|
||||
departmentCount
|
||||
? `删除院区“${campus.name}”会同时移除其下 ${departmentCount} 个科室,且无法恢复,是否继续?`
|
||||
: `删除院区“${campus.name}”后无法恢复,是否继续?`,
|
||||
'删除院区',
|
||||
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' },
|
||||
)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
if (!confirmed) return
|
||||
|
||||
async function refreshOrganizationAfterMutation(kind: 'campus' | 'department'): Promise<boolean> {
|
||||
try {
|
||||
await deleteCampus(campus.id)
|
||||
if (selectedCampusId.value === campus.id) selectedDepartmentId.value = null
|
||||
await appStore.loadCampuses(true)
|
||||
await loadOrganization()
|
||||
ElMessage.success('院区已删除')
|
||||
} catch (error) {
|
||||
ElMessage.error(toUserMessage(error, '院区删除失败,请稍后重试'))
|
||||
if (kind === 'campus') await appStore.loadCampuses(true)
|
||||
const refreshed = await loadOrganization(false)
|
||||
return refreshed && (kind !== 'campus' || !appStore.campusLoadFailed)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function removeDepartment(department: DepartmentRecord) {
|
||||
if (!canManageDepartments.value) return
|
||||
const childCount = departments.value.filter((item) => item.parentId === department.id).length
|
||||
async function handleDisableCampus(campus: CampusRecord) {
|
||||
if (!canManageCampuses.value || campus.status === 'DISABLED' || organizationDisabling.value)
|
||||
return
|
||||
organizationDisabling.value = true
|
||||
try {
|
||||
const confirmed = await ElMessageBox.confirm(
|
||||
childCount
|
||||
? `删除科室“${department.name}”会同时移除其下 ${childCount} 个下级科室,且无法恢复,是否继续?`
|
||||
: `删除科室“${department.name}”后无法恢复,是否继续?`,
|
||||
'删除科室',
|
||||
{ type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' },
|
||||
`停用院区“${campus.name}”只修改该院区的状态,不会停用或删除下属科室。组织记录和历史数据保留,可通过编辑重新启用。是否继续?`,
|
||||
'停用院区',
|
||||
{ type: 'warning', confirmButtonText: '停用', cancelButtonText: '取消' },
|
||||
)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
if (!confirmed) return
|
||||
|
||||
try {
|
||||
await deleteDepartment(department.id)
|
||||
if (selectedDepartmentId.value === department.id) selectedDepartmentId.value = null
|
||||
await loadOrganization()
|
||||
ElMessage.success('科室已删除')
|
||||
await disableCampus(campus.id)
|
||||
} catch (error) {
|
||||
ElMessage.error(toUserMessage(error, '科室删除失败,请稍后重试'))
|
||||
ElMessage.error(toUserMessage(error, '院区停用失败,请稍后重试'))
|
||||
return
|
||||
}
|
||||
campus.status = 'DISABLED'
|
||||
if (selectedCampusId.value === campus.id) selectedDepartmentId.value = null
|
||||
const refreshed = await refreshOrganizationAfterMutation('campus')
|
||||
if (refreshed) ElMessage.success('院区已停用')
|
||||
else ElMessage.warning('院区已停用,但页面刷新失败,请刷新页面重试')
|
||||
} finally {
|
||||
organizationDisabling.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDisableDepartment(department: DepartmentRecord) {
|
||||
if (
|
||||
!canManageDepartments.value ||
|
||||
department.status === 'DISABLED' ||
|
||||
organizationDisabling.value
|
||||
)
|
||||
return
|
||||
organizationDisabling.value = true
|
||||
try {
|
||||
const confirmed = await ElMessageBox.confirm(
|
||||
`停用科室“${department.name}”只修改当前科室的状态,不会停用或删除下级科室。组织记录和历史数据保留,可通过编辑重新启用。是否继续?`,
|
||||
'停用科室',
|
||||
{ type: 'warning', confirmButtonText: '停用', cancelButtonText: '取消' },
|
||||
)
|
||||
.then(() => true)
|
||||
.catch(() => false)
|
||||
if (!confirmed) return
|
||||
|
||||
try {
|
||||
await disableDepartment(department.id)
|
||||
} catch (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"
|
||||
:can-manage-campuses="canManageCampuses"
|
||||
:can-manage-departments="canManageDepartments"
|
||||
:disabling="organizationDisabling"
|
||||
@select-campus="selectCampus"
|
||||
@select-department="selectDepartment"
|
||||
@add-campus="openAddCampus"
|
||||
@add-department="openAddDepartment"
|
||||
@edit-campus="editCampus"
|
||||
@edit-department="editDepartment"
|
||||
@delete-campus="removeCampus"
|
||||
@delete-department="removeDepartment"
|
||||
@disable-campus="handleDisableCampus"
|
||||
@disable-department="handleDisableDepartment"
|
||||
/>
|
||||
|
||||
<section class="employee-card">
|
||||
|
||||
Reference in New Issue
Block a user