feat(documents): 文书库筛选条按院区-科室-状态三级重构

This commit is contained in:
yelan
2026-10-07 11:34:15 +08:00
parent ee7e90ab01
commit 3ab71e4231
5 changed files with 235 additions and 76 deletions
@@ -50,6 +50,7 @@ import {
import { loadDocumentLibrary, saveDocumentLibrary } from './mock'
import { DOC_TEMPLATE_PERMISSIONS, workflowPermission } from './permissions'
import type {
CampusTab,
DepartmentTab,
DocumentContentForm,
DocumentDepartment,
@@ -76,6 +77,9 @@ const loginStore = useLoginStore()
const useMockLibrary = mockFlags.documents
/** 没有绑定科室的文书归到这一组(全院通用),不属于任何一个科室页签 */
const GENERAL_GROUP_KEY = '__all__'
/**
* 页面操作的服务端校验兜底。
*
@@ -155,15 +159,67 @@ const allTemplates = computed<DocumentTemplate[]>(() =>
),
)
const departmentTabs = computed<DepartmentTab[]>(() => [
{ key: null, label: '全部', count: allTemplates.value.length },
...library.value.map((department) => ({
key: department.key,
label: department.name,
count: allTemplates.value.filter((template) => template.departmentKey === department.key)
.length,
})),
])
/**
* 院区页签:与「组织架构」共用同一份院区字典和同一个排序(sortNo),
* 因此这一行的顺序与架构树里的院区顺序一致。
*
* 演示文库没有院区维度(loadLibrary 直接读本地数据、不看院区),
* 渲染一行点了不生效的院区页签比不渲染更糟,所以演示模式返回空数组、整行隐藏。
*/
const campusTabs = computed<CampusTab[]>(() =>
useMockLibrary
? []
: [...campuses.value]
.filter((campus) => campus.status !== 'DISABLED')
.sort(
(left, right) =>
left.sortNo - right.sortNo || left.name.localeCompare(right.name, 'zh-CN'),
)
.map((campus) => ({ key: campus.id, label: campus.name })),
)
/**
* 科室页签:**与「组织架构」同一份字典** —— 当前院区下的全部科室,按 sortNo 排。
*
* 刻意不按「有没有文书」过滤:两个页面的科室清单必须一致。组织架构里有的科室在
* 文档库里查不到,科室会以为文书库漏了;反过来只显示有文书的科室,也看不出
* 「这个科室还没建过文书」。
*
* 「全院通用」**不是科室**(它不挂在任何院区下,是「没有归属科室」的文书),
* 因此不管当前选中哪个院区都必须有,固定排在「全部」之后。
*
* 字典里查不到的孤儿分组(科室被删掉、演示文库的占位键)补在最后 —— 不补的话
* 这些文书只能在「全部」里看到,点不到具体科室。
*/
const departmentTabs = computed<DepartmentTab[]>(() => {
const countOf = (key: string) =>
allTemplates.value.filter((template) => template.departmentKey === key).length
const campusId = appStore.selectedCampusId
const campusDepartments = [...departments.value]
.filter((department) => department.status !== 'DISABLED')
// 演示文库没有院区维度(科室字典的 campusId 是演示值,与全局院区对不上),不按院区过滤
.filter((department) => useMockLibrary || !campusId || department.campusId === campusId)
.sort(
(left, right) => left.sortNo - right.sortNo || left.name.localeCompare(right.name, 'zh-CN'),
)
const knownKeys = new Set(campusDepartments.map((department) => department.id))
const orphans = library.value.filter(
(group) => group.key !== GENERAL_GROUP_KEY && !knownKeys.has(group.key),
)
return [
{ key: null, label: '全部', count: allTemplates.value.length },
{ key: GENERAL_GROUP_KEY, label: '全院通用', count: countOf(GENERAL_GROUP_KEY) },
...campusDepartments.map((department) => ({
key: department.id,
label: department.name,
count: countOf(department.id),
})),
...orphans.map((group) => ({ key: group.key, label: group.name, count: countOf(group.key) })),
]
})
/**
* 状态页签只有两组(启用中 / 停用),外加「全部」。卡片按需显示待发布等具体版本状态;
@@ -231,7 +287,7 @@ function groupDocuments(
)
records.forEach((record) => {
const departmentKey = record.departmentId ?? '__all__'
const departmentKey = record.departmentId ?? GENERAL_GROUP_KEY
const departmentName =
record.departmentName ??
(record.departmentId ? (departmentNames.get(record.departmentId) ?? '未知科室') : '全院通用')
@@ -256,11 +312,11 @@ function groupDocuments(
// 全院通用的模板排最前,其余按科室名排序,保证每次进页面分组顺序稳定
return [...grouped.values()].sort((left, right) => {
if (left.key === '__all__') {
if (left.key === GENERAL_GROUP_KEY) {
return -1
}
if (right.key === '__all__') {
if (right.key === GENERAL_GROUP_KEY) {
return 1
}
@@ -500,7 +556,7 @@ function addLocalTemplate(form: DocumentPropsForm, departmentId: string | null):
const department = departments.value.find((item) => item.id === departmentId)
const departmentName = department?.name ?? '全院通用'
// 真实模式用 departmentId 当分组键,演示模式没有 UUID,用品名保持同样语义
const departmentKey = department?.id ?? '__all__'
const departmentKey = department?.id ?? GENERAL_GROUP_KEY
let departmentGroup = library.value.find((item) => item.key === departmentKey)
if (!departmentGroup) {
@@ -558,8 +614,8 @@ function addLocalTemplate(form: DocumentPropsForm, departmentId: string | null):
function updateLocalProps(form: DocumentPropsForm, departmentId: string | null) {
const target = departments.value.find((item) => item.id === departmentId)
const targetName = target?.name ?? '全院通用'
// 与 addLocalTemplate 同一套分组键口径:演示模式没有 UUID 时落 __all__
const targetKey = target?.id ?? '__all__'
// 与 addLocalTemplate 同一套分组键口径:演示模式没有 UUID 时落 GENERAL_GROUP_KEY
const targetKey = target?.id ?? GENERAL_GROUP_KEY
const located = locateLocalDocument(form.id ?? '')
if (!located) {
@@ -1077,6 +1133,23 @@ function clearFilters() {
filters.department = null
// status 也要一起重置:只按状态页签筛出空结果时,不清它等于「清了个寂寞」
filters.status = 'all'
// 院区不在这里重置:它是页面的取数口径(文书库按院区向服务端取数),
// 不是「筛掉一部分已加载数据」的筛选条件,清掉它得重新拉一遍文库
}
/**
* 切换院区。
*
* 走全局院区状态而不是页面自己存一份:右上角的院区选择器读的就是这个值,
* 两处共用一份状态,就不会出现「右上角写着本部院区、筛选条却选中东院区」。
* 重新取数由下面的 watch 负责,这里只改状态。
*/
function handleCampusSelect(campusId: string) {
if (campusId === appStore.selectedCampusId) {
return
}
appStore.selectCampus(campusId)
}
function useTemplate(template: DocumentTemplate) {
@@ -1097,8 +1170,16 @@ async function initializePage() {
watch(
() => appStore.selectedCampusId,
(campusId, previousCampusId) => {
// 演示模式没有院区维度,切换院区不需要重新拉文库
if (useMockLibrary || !campusId || !previousCampusId || campusId === previousCampusId) {
if (!campusId || campusId === previousCampusId) {
return
}
// 院区变了,原先选中的科室不一定属于新院区,清掉以免筛出一片空结果
filters.department = null
// 演示模式没有院区维度;首次进页面时院区从空值变成第一个院区,
// 那一次由 initializePage 负责取数,这里跳过,避免重复拉一遍文库
if (useMockLibrary || !previousCampusId) {
return
}
@@ -1125,8 +1206,11 @@ onMounted(() => {
<DocumentFilterTabs
v-model:department="filters.department"
v-model:status="filters.status"
:campus="appStore.selectedCampusId"
:campuses="campusTabs"
:departments="departmentTabs"
:statuses="statusTabs"
@select-campus="handleCampusSelect"
/>
<p v-if="libraryLoading" class="library-state">正在加载文书库…</p>
@@ -1153,7 +1237,8 @@ onMounted(() => {
<div v-else class="empty-state">
<div class="empty-icon" aria-hidden="true">⌕</div>
<template v-if="allTemplates.length === 0">
<p>文书库为空,点击右上角「新增文档」创建第一份文书</p>
<!-- 文库按院区取数,空的是「当前院区」而不是整个文书库,别让科室以为全院都没文书 -->
<p>当前院区暂无文书,点击右上角「新增文档」创建第一份文书</p>
</template>
<template v-else>
<p>没有符合条件的文书</p>