feat(documents): 文档归属按院区分组并改用科室 id 落库

This commit is contained in:
yelan
2026-10-06 19:56:47 +08:00
parent a520f73c49
commit eda13a42c3
4 changed files with 121 additions and 36 deletions
@@ -2,7 +2,7 @@
import { computed, reactive, watch } from 'vue' import { computed, reactive, watch } from 'vue'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import type { DepartmentRecord } from '@/api/management/types' import type { CampusRecord, DepartmentRecord } from '@/api/management/types'
import { DEFAULT_DOCUMENT_TYPE, DOCUMENT_TYPES, GENERAL_OWNERSHIP } from '../library' import { DEFAULT_DOCUMENT_TYPE, DOCUMENT_TYPES, GENERAL_OWNERSHIP } from '../library'
import { DOC_TEMPLATE_PERMISSIONS } from '../permissions' import { DOC_TEMPLATE_PERMISSIONS } from '../permissions'
@@ -14,6 +14,8 @@ const props = defineProps<{
template: DocumentTemplate | null template: DocumentTemplate | null
/** 可归属的科室(真实模式取组织接口,演示模式取演示字典) */ /** 可归属的科室(真实模式取组织接口,演示模式取演示字典) */
departments: DepartmentRecord[] departments: DepartmentRecord[]
/** 院区字典,与组织架构页同源;用来给上面的科室分组 */
campuses: CampusRecord[]
/** /**
* 是否可以把文档设为「通用(全院)」。 * 是否可以把文档设为「通用(全院)」。
* *
@@ -54,19 +56,63 @@ const form = reactive<DocumentPropsForm>({
description: '', description: '',
}) })
/** interface OwnershipOption {
* 归属勾选列表。 /** 落库值:科室取 DepartmentRecord.id,「通用(全院)」取固定哨兵 */
* 顺序与原型 docDeptOpts 一致:科室名在前、「通用(全院)」在最后, value: string
* 勾了通用(全院)就等于全院可见,其余科室勾选不再参与落库。 label: string
*/ }
const ownershipOptions = computed(() => {
const names = props.departments.map((department) => department.name)
return props.canSetGeneral ? [...names, GENERAL_OWNERSHIP] : names interface OwnershipGroup {
key: string
title: string
options: OwnershipOption[]
}
/**
* 归属勾选列表:**按院区分组**,一个院区一个 .ownership 容器。
*
* 组织架构里的科室本来就挂在院区下面(同名科室在不同院区是两条记录),
* 只摊平显示名字的话既看不出属于哪个院区、勾了也定位不到唯一的科室 id,
* 所以这里按科室的 campusId 归到对应院区下:显示院区名 + 科室名,取值用科室 id。
* 「通用(全院)」不是科室、没有院区,单独放最后一组。
*/
const ownershipGroups = computed<OwnershipGroup[]>(() => {
const groups: OwnershipGroup[] = []
for (const campus of props.campuses) {
const options = props.departments
.filter((department) => department.campusId === campus.id)
.map((department) => ({ value: department.id, label: department.name }))
if (options.length) {
groups.push({ key: campus.id, title: campus.name, options })
}
}
// 院区字典里没有的科室单独兜底成一组,否则这些科室在弹窗里直接没得勾
const campusIds = new Set(props.campuses.map((campus) => campus.id))
const unassigned = props.departments
.filter((department) => !campusIds.has(department.campusId))
.map((department) => ({ value: department.id, label: department.name }))
if (unassigned.length) {
groups.push({ key: '__unassigned__', title: '未指定院区', options: unassigned })
}
if (props.canSetGeneral) {
groups.push({
key: '__general__',
title: '全院',
options: [{ value: GENERAL_OWNERSHIP, label: GENERAL_OWNERSHIP }],
})
}
return groups
}) })
const generalSelected = computed(() => form.ownership.includes(GENERAL_OWNERSHIP)) const generalSelected = computed(() => form.ownership.includes(GENERAL_OWNERSHIP))
/** option 是勾选值:科室 id,或 GENERAL_OWNERSHIP */
function toggleOwnership(option: string) { function toggleOwnership(option: string) {
if (option === GENERAL_OWNERSHIP) { if (option === GENERAL_OWNERSHIP) {
// 勾上「通用(全院)」就清掉科室勾选,取消则回到空选择—— // 勾上「通用(全院)」就清掉科室勾选,取消则回到空选择——
@@ -104,7 +150,8 @@ watch(
versionNo: template?.version ?? 'v1.0.0', versionNo: template?.version ?? 'v1.0.0',
ownership: template ownership: template
? template.departmentId ? template.departmentId
? [template.departmentName ?? ''].filter(Boolean) ? // 回填主科室用 departmentId 本身:勾选值就是科室 id,不再经名字中转
[template.departmentId]
: [GENERAL_OWNERSHIP] : [GENERAL_OWNERSHIP]
: // 新增时预选当前用户所属科室(对得上就直接用,对不上就空着让科室自己勾) : // 新增时预选当前用户所属科室(对得上就直接用,对不上就空着让科室自己勾)
[], [],
@@ -176,20 +223,23 @@ function submit() {
<div class="form-field span-2"> <div class="form-field span-2">
<span>文档归属 <i>*</i></span> <span>文档归属 <i>*</i></span>
<div class="ownership"> <div v-for="group in ownershipGroups" :key="group.key" class="ownership-group">
<label <p class="ownership-group-title">{{ group.title }}</p>
v-for="option in ownershipOptions" <div class="ownership">
:key="option" <label
class="ownership-option" v-for="option in group.options"
:class="{ 'is-general': option === GENERAL_OWNERSHIP }" :key="option.value"
> class="ownership-option"
<input :class="{ 'is-general': option.value === GENERAL_OWNERSHIP }"
type="checkbox" >
:checked="form.ownership.includes(option)" <input
@change="toggleOwnership(option)" type="checkbox"
/> :checked="form.ownership.includes(option.value)"
<span>{{ option }}</span> @change="toggleOwnership(option.value)"
</label> />
<span>{{ option.label }}</span>
</label>
</div>
</div> </div>
<p class="field-tip"> <p class="field-tip">
勾选科室设置可见范围,支持多科室与通用(全院)。勾选「通用(全院)」后各科室均可见。 勾选科室设置可见范围,支持多科室与通用(全院)。勾选「通用(全院)」后各科室均可见。
@@ -390,7 +440,21 @@ function submit() {
* 容器是浅底带框的 3 列网格,每项一张白卡(原生 checkbox + 科室名), * 容器是浅底带框的 3 列网格,每项一张白卡(原生 checkbox + 科室名),
* 「通用(全院)」独占一行并用浅蓝底区分(原型 grid-column:1/-1)。 * 「通用(全院)」独占一行并用浅蓝底区分(原型 grid-column:1/-1)。
* 科室名过长时截断:原型同一处就是 nowrap + ellipsis。 * 科室名过长时截断:原型同一处就是 nowrap + ellipsis。
*
* 与原型唯一的差别:这里按院区分成若干个 .ownership(一个院区一个),
* 每组上面带院区标题 —— 科室字典是多院区的,摊平成一列会分不出同名科室属于谁。
*/ */
.ownership-group + .ownership-group {
margin-top: 10px;
}
.ownership-group-title {
margin: 0 0 6px;
color: var(--brand-d);
font-size: 11.5px;
font-weight: 700;
}
.ownership { .ownership {
display: grid; display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr)); grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -41,6 +41,7 @@ import { toHistoryRows } from './history'
import { import {
canInitiateSigning, canInitiateSigning,
DOCUMENT_LIBRARY_STATUSES, DOCUMENT_LIBRARY_STATUSES,
GENERAL_OWNERSHIP,
readLibraryStatus, readLibraryStatus,
resolveOwnershipDepartmentId, resolveOwnershipDepartmentId,
templateWorkflowAction, templateWorkflowAction,
@@ -461,6 +462,19 @@ function generateTemplateCode(name: string) {
return `TPL-ED-${suffix}-${stamp}` return `TPL-ED-${suffix}-${stamp}`
} }
/**
* 归属勾选结果 → 共享科室名清单(演示文库的 sharedDepartments 存的是科室名)。
*
* 勾选值现在是科室 id(同名科室跨院区会重名,名字定位不到唯一一条),
* 这里在落本地前换回名字;「通用(全院)」与主科室本身不进这份清单。
*/
function sharedDepartmentNames(ownership: string[], primaryDepartmentId: string | null): string[] {
return ownership
.filter((value) => value !== GENERAL_OWNERSHIP && value !== primaryDepartmentId)
.map((id) => departments.value.find((department) => department.id === id)?.name)
.filter((name): name is string => Boolean(name))
}
/** /**
* 在本地文库里定位一份文书(连同它所在的分组与类别)。 * 在本地文库里定位一份文书(连同它所在的分组与类别)。
* *
@@ -526,7 +540,7 @@ function addLocalTemplate(form: DocumentPropsForm, departmentId: string | null):
departmentName, departmentName,
campusId: campuses.value[0]?.id ?? '', campusId: campuses.value[0]?.id ?? '',
contentHtml: '', contentHtml: '',
sharedDepartments: form.ownership.filter((name) => name !== departmentName), sharedDepartments: sharedDepartmentNames(form.ownership, departmentId),
} }
category.documents.push(record) category.documents.push(record)
@@ -562,7 +576,7 @@ function updateLocalProps(form: DocumentPropsForm, departmentId: string | null)
document.departmentId = departmentId document.departmentId = departmentId
document.departmentName = targetName document.departmentName = targetName
// 主科室进 departmentId,其余勾选科室留在 sharedDepartments(与新建时同一口径) // 主科室进 departmentId,其余勾选科室留在 sharedDepartments(与新建时同一口径)
document.sharedDepartments = form.ownership.filter((name) => name !== targetName) document.sharedDepartments = sharedDepartmentNames(form.ownership, departmentId)
if (group.key !== targetKey) { if (group.key !== targetKey) {
category.documents = category.documents.filter((item) => item.id !== document.id) category.documents = category.documents.filter((item) => item.id !== document.id)
@@ -593,7 +607,7 @@ function updateLocalProps(form: DocumentPropsForm, departmentId: string | null)
} }
async function handlePropsSubmit(form: DocumentPropsForm) { async function handlePropsSubmit(form: DocumentPropsForm) {
const departmentId = resolveOwnershipDepartmentId(form.ownership, departments.value) const departmentId = resolveOwnershipDepartmentId(form.ownership)
if (useMockLibrary) { if (useMockLibrary) {
if (form.id) { if (form.id) {
@@ -1166,6 +1180,7 @@ onMounted(() => {
:visible="propsDialogVisible" :visible="propsDialogVisible"
:template="propsTemplate" :template="propsTemplate"
:departments="departments" :departments="departments"
:campuses="campuses"
:can-set-general="canSetGeneral" :can-set-general="canSetGeneral"
:is-mock="useMockLibrary" :is-mock="useMockLibrary"
:saving="propsSaving" :saving="propsSaving"
@@ -145,18 +145,18 @@ export function templateWorkflowLabel(
*/ */
export const GENERAL_OWNERSHIP = '通用(全院)' export const GENERAL_OWNERSHIP = '通用(全院)'
/** 归属选项 → 后端可落库的科室 id(勾了通用(全院)就没有科室 id) */ /**
export function resolveOwnershipDepartmentId( * 归属勾选 → 后端可落库的科室 id(勾了通用(全院)就没有科室 id)。
ownership: string[], *
departments: Array<{ id: string; name: string }>, * 勾选值本身就是 DepartmentRecord.id:同名科室在不同院区是两条记录,
): string | null { * 这里不再拿名字去反查(反查只能取到第一条,会把院区指错)。
*/
export function resolveOwnershipDepartmentId(ownership: string[]): string | null {
if (ownership.includes(GENERAL_OWNERSHIP)) { if (ownership.includes(GENERAL_OWNERSHIP)) {
return null return null
} }
const first = ownership.find((name) => name !== GENERAL_OWNERSHIP) return ownership.find((value) => value !== GENERAL_OWNERSHIP) ?? null
return first ? (departments.find((department) => department.name === first)?.id ?? null) : null
} }
/** 归属徽标文案:全院通用用 🌐,科室用 🏥(与原型 tpl-card 的标签一致) */ /** 归属徽标文案:全院通用用 🌐,科室用 🏥(与原型 tpl-card 的标签一致) */
@@ -49,7 +49,13 @@ export interface DocumentPropsForm {
/** 文档类型,取自 library.DOCUMENT_TYPES */ /** 文档类型,取自 library.DOCUMENT_TYPES */
category: string category: string
versionNo: string versionNo: string
/** 文档归属:科室名数组,可能含 GENERAL_OWNERSHIP(通用(全院)) */ /**
* 文档归属的勾选结果。
*
* 科室取 DepartmentRecord.id 而不是科室名:同一个科室名在不同院区是两条独立记录
* (组织架构里就是分开的两条),拿名字既分不出院区、也定位不到唯一的科室。
* 可能含 GENERAL_OWNERSHIP(通用(全院))。
*/
ownership: string[] ownership: string[]
description: string description: string
} }