feat: 提交

This commit is contained in:
cnxianyi
2026-09-29 18:49:45 +08:00
parent 8d8fa2af47
commit 09bcf20def
17 changed files with 357 additions and 62 deletions
@@ -9,6 +9,8 @@ import {
isGeneralOwnership,
readLibraryStatus,
readStatusTone,
templateWorkflowAction,
templateWorkflowLabel,
} from '../library'
import { DOC_TEMPLATE_PERMISSIONS, workflowPermission } from '../permissions'
import type { DocumentTemplate } from '../types'
@@ -27,7 +29,22 @@ const emit = defineEmits<{
}>()
const status = computed(() => readLibraryStatus(props.template))
const tone = computed(() => readStatusTone(status.value))
const rawStatus = computed(() => props.template.backendStatus?.toUpperCase())
const statusLabel = computed(() => {
if (rawStatus.value === 'DRAFT') {
return props.template.currentVersionId ? '有新版本待发布' : '待发布'
}
if (rawStatus.value === 'APPROVED') return '待发布'
if (rawStatus.value === 'PENDING_REVIEW') return '待审核'
if (rawStatus.value === 'REJECTED') return '已驳回'
if (rawStatus.value === 'ARCHIVED') return '已归档'
return status.value
})
const tone = computed(() =>
['DRAFT', 'APPROVED', 'PENDING_REVIEW', 'REJECTED', 'ARCHIVED'].includes(rawStatus.value ?? '')
? 'stopped'
: readStatusTone(status.value),
)
const dotColor = computed(() => DOCUMENT_STATUS_DOT[tone.value])
const isGeneral = computed(() => isGeneralOwnership(props.template))
@@ -45,18 +62,19 @@ const ownershipLabel = computed(() =>
const sharedCount = computed(() => props.template.sharedDepartments?.length ?? 0)
/**
* 只有「启用中」能发起签署;「停用」已下线。
*
* 这里只看状态,不看正文——真实模式的列表接口不返回正文,按正文判断会让所有
* 真实模板的「发起」按钮都消失。缺正文由发起那一步拦下(见 index.vue 的 toSigningTemplate)。
* 真实模式必须存在 currentVersionId 才能发起签署;演示模式按「启用中 / 停用」判断。
* 列表接口不返回正文,正文完整性由发起那一步检查(见 index.vue 的 toSigningTemplate)。
*/
const canInitiate = computed(() => canInitiateSigning(status.value))
const canInitiate = computed(() =>
props.template.backendStatus
? Boolean(props.template.currentVersionId)
: canInitiateSigning(status.value),
)
const initiateTitle = computed(() =>
canInitiate.value ? '用此文书发起签署' : '仅「启用中」的文书可发起签署',
canInitiate.value ? '用此文书发起签署' : '该文书还没有已发布版本,无法发起签署',
)
/** 两档之间都能来回切,所以这个按钮没有「不适用」的状态 */
const toggleLabel = computed(() => (status.value === '停用' ? '启用' : '停用'))
const toggleLabel = computed(() => templateWorkflowLabel(props.template))
/**
* 检查类型 / 关联项目编码(原型 V10.2 卡片上的两条信息)。
@@ -67,14 +85,15 @@ const toggleLabel = computed(() => (status.value === '停用' ? '启用' : '停
const examType = computed(() => props.template.examType?.trim() ?? '')
const projectCodes = computed(() => (props.template.projectCodes ?? []).filter(Boolean))
/** 停用/启用走的是后端版本动作:停用 → disable,启用 → publish */
const toggleAction = computed<TemplateVersionAction>(() =>
status.value === '停用' ? 'publish' : 'disable',
const toggleAction = computed<TemplateVersionAction | null>(() =>
templateWorkflowAction(props.template),
)
// 按钮文案与权限码的对应关系集中在模板里,避免多处各写一份
const permissions = DOC_TEMPLATE_PERMISSIONS
const togglePermission = computed(() => workflowPermission(toggleAction.value))
const togglePermission = computed(() =>
workflowPermission(toggleAction.value ?? 'disable'),
)
</script>
<template>
@@ -83,7 +102,7 @@ const togglePermission = computed(() => workflowPermission(toggleAction.value))
<h3 class="tpl-title" :title="template.name">{{ template.name }}</h3>
<span class="tpl-status" :class="'tone-' + tone">
<i :style="{ background: dotColor }" aria-hidden="true" />
{{ status }}
{{ statusLabel }}
</span>
</div>
@@ -133,13 +152,16 @@ const togglePermission = computed(() => workflowPermission(toggleAction.value))
<span class="ft-spacer" />
<button
v-if="toggleAction"
v-permission="togglePermission"
type="button"
class="card-button"
:title="
toggleLabel === '停用'
? '停用后各科室不可见、不可发起签署'
: '启用后各科室可见并可发起签署'
? '停用后不可发起新的签署'
: toggleLabel === '发布'
? '发布当前版本后即可发起签署'
: '启用后各科室可见并可发起签署'
"
@click="emit('toggle-status', template)"
>
@@ -43,9 +43,11 @@ import {
DOCUMENT_LIBRARY_STATUSES,
readLibraryStatus,
resolveOwnershipDepartmentId,
templateWorkflowAction,
templateWorkflowLabel,
} from './library'
import { loadDocumentLibrary, saveDocumentLibrary } from './mock'
import { DOC_TEMPLATE_PERMISSIONS } from './permissions'
import { DOC_TEMPLATE_PERMISSIONS, workflowPermission } from './permissions'
import type {
DepartmentTab,
DocumentContentForm,
@@ -163,10 +165,8 @@ const departmentTabs = computed<DepartmentTab[]>(() => [
])
/**
* 状态页签只有两档(启用中 / 停用),外加「全部」。
*
* 不再有草稿 / 归档 / 需复审:审核链路已废弃,后端状态也会重写。
* 也不按后端模板状态枚举做页签——枚举摆到界面上,科室会看到一堆看不懂也点不动的档位。
* 状态页签只有两组(启用中 / 停用),外加「全部」。卡片按需显示待发布等具体版本状态;
* 不按完整后端枚举增加筛选页签。
* 计数按**全库**统计(不受科室页签影响),与原型 libCountByStatus 一致。
*/
const statusTabs = computed<StatusTab[]>(() => [
@@ -763,10 +763,11 @@ async function handleContentSubmit(form: DocumentContentForm) {
* 而 readLibraryStatus 优先读 backendStatus,所以显示口径仍然是准确的。
*/
function toggleLocalStatus(template: DocumentTemplate) {
const action = templateWorkflowAction(template)
const next: Pick<DocumentRecord, 'status' | 'backendStatus'> =
readLibraryStatus(template) === '停用'
? { status: 'published', backendStatus: 'PUBLISHED' }
: { status: 'archived', backendStatus: 'DISABLED' }
action === 'disable'
? { status: 'archived', backendStatus: 'DISABLED' }
: { status: 'published', backendStatus: 'PUBLISHED' }
library.value.forEach((department) =>
department.categories.forEach((category) =>
@@ -782,27 +783,31 @@ function toggleLocalStatus(template: DocumentTemplate) {
/**
* 停用 / 启用。
*
* 真实模式走后端版本动作(停用 → disable,启用 → publish),前端不自行改写 status:
* 真实模式走后端版本动作(草稿/停用 → enable,审核通过 → publish,已发布 → disable):
* 「谁在什么时候把这份文书放行到可签署 / 什么时候下线的」必须由后端留痕。
*
* 这是页面上**唯一**的状态动作:状态只有两档,两档之间都能来回切,因此没有
* 「某些档位不能停用 / 启用」这种分支了。
* 仅在后端支持的状态上显示动作;审核中、驳回和归档版本不能直接切换。
*/
async function handleToggleStatus(template: DocumentTemplate) {
const status = readLibraryStatus(template)
const action = templateWorkflowAction(template)
const nextLabel = templateWorkflowLabel(template)
if (!canToggleTemplate.value) {
ElMessage.warning('当前账号没有停用 / 启用文书的权限')
if (!action) {
ElMessage.info('该文书当前状态不可操作')
return
}
const nextLabel = status === '停用' ? '启用' : '停用'
const action = status === '停用' ? 'publish' : 'disable'
if (!canWriteDocument(workflowPermission(action))) {
ElMessage.warning(`当前账号没有${nextLabel}文书的权限`)
return
}
const confirmed = await ElMessageBox.confirm(
status === '停用'
? `确定启用「${template.name}」?启用后各科室可见,并可发起签署。`
: `确定停用「${template.name}」?停用后不能再发起新的签署,已签署的记录不受影响。`,
nextLabel === '发布'
? `确定发布「${template.name}」${template.version}?发布后该版本可用于发起签署。`
: nextLabel === '启用'
? `确定启用「${template.name}」?启用后各科室可见,并可发起签署。`
: `确定停用「${template.name}」?停用后不能再发起新的签署,已签署的记录不受影响。`,
`确认${nextLabel}`,
{ type: 'warning', confirmButtonText: nextLabel, cancelButtonText: '取消' },
)
@@ -819,13 +824,16 @@ async function handleToggleStatus(template: DocumentTemplate) {
return
}
if (!template.versionId) {
const versionId = action === 'disable'
? template.currentVersionId ?? template.versionId
: template.versionId
if (!versionId) {
ElMessage.error('该文书没有可操作的版本,请刷新后重试')
return
}
try {
await updateTemplateVersionStatus(template.id, template.versionId, action)
await updateTemplateVersionStatus(template.id, versionId, action)
ElMessage.success(`已${nextLabel}`)
await loadLibrary()
} catch {
@@ -997,6 +1005,11 @@ async function openSigningDialog(template: DocumentTemplate) {
return
}
if (!useMockLibrary && !template.currentVersionId) {
ElMessage.info('该文书还没有已发布版本,无法发起签署')
return
}
closePreview()
try {
@@ -1021,7 +1034,7 @@ function closeSigningDialog() {
// 闭环:文档库发起签署后跳到工作台并选中该任务,否则任务创建成功但用户看不到后续
function handleTaskCreated(task: SigningTaskRecord) {
closeSigningDialog()
void router.push({ name: 'workbench-signing', query: { taskId: task.id } })
void router.push({ path: '/sign/workbench', query: { taskId: task.id } })
}
/* ---------------- 页面级 ---------------- */
@@ -1,16 +1,16 @@
/**
* 文档库的展示口径:状态、归属、文档类型。
*
* 状态只有**两档**:启用中 / 停用(2026-09-28 收敛)。
* 原先沿用原型 V10.2 的 libStatuses(启用中 / 草稿 / 归档 / 需复审 / 停用),
* 那套带着「发布 → 审核」的工作流;后端已确认不要审核链路、状态会重写,
* 因此前端先收成两个页签,不再有草稿 / 归档 / 需复审。
* 状态筛选只有**两组**:启用中 / 停用(2026-09-28 收敛)。卡片可以显示「待发布」等
* 具体版本状态,但不为这些状态增加独立页签。
* 后端仍保留完整版本状态;readLibraryStatus 在这里将它们归入两个筛选组。
*
* 后端模板状态枚举(DRAFT / PENDING_REVIEW / APPROVED / PUBLISHED / DISABLED /
* ARCHIVED)在接口重写前仍会出现,因此这里保留一次**折叠**:把原始状态压成两档。
* ARCHIVED)在接口中保留,因此这里提供**折叠**:把原始状态压成两个筛选组。
* 反向映射(口径 → 后端)只保留「按状态过滤」这一处需要,见 toBackendStatusFilter。
*/
import type { DocumentRecord, DocumentStatus } from '@/api/management/types'
import type { TemplateVersionAction } from '@/api/management/documents'
/** 文档库页签与卡片上的状态,取值即显示文案 */
export type DocumentLibraryStatus = '启用中' | '停用'
@@ -93,6 +93,52 @@ export function canInitiateSigning(status: DocumentLibraryStatus): boolean {
return status === '启用中'
}
/**
* 后端版本状态对应的文书操作。DRAFT 和 DISABLED 使用 enable;APPROVED 使用 publish;
* 已发布版本使用 disable。审核中、驳回和归档状态不提供切换。
*/
export function templateWorkflowAction(
record: Pick<DocumentRecord, 'status' | 'backendStatus'>,
): TemplateVersionAction | null {
switch (record.backendStatus?.toUpperCase()) {
case 'DRAFT':
case 'DISABLED':
return 'enable'
case 'APPROVED':
return 'publish'
case 'PUBLISHED':
return 'disable'
case 'PENDING_REVIEW':
case 'REJECTED':
case 'ARCHIVED':
return null
default:
// 演示文库没有后端状态,仍按启用中 / 停用两档切换。
if (record.backendStatus) return null
return readLibraryStatus(record) === '停用' ? 'enable' : 'disable'
}
}
export function templateWorkflowLabel(
record: Pick<DocumentRecord, 'status' | 'backendStatus'>,
): string {
switch (record.backendStatus?.toUpperCase()) {
case 'DRAFT':
case 'APPROVED':
return '发布'
case 'DISABLED':
return '启用'
case 'PUBLISHED':
return '停用'
default:
return record.backendStatus
? ''
: readLibraryStatus(record) === '停用'
? '启用'
: '停用'
}
}
/**
* 文档归属:原型把「通用(全院)」当作科室清单里的一个选项一起勾选,
* 勾中它就等于全院通用,其余科室勾选一并作废(见 docDeptPicks / openDocProps)。
@@ -30,7 +30,7 @@ export const DOC_TEMPLATE_PERMISSIONS = {
/**
* 版本工作流动作的权限码映射。
*
* 页面上只剩**一个**状态动作:停用 / 启用(停用 → disable,启用 → publish)。
* 页面状态动作按当前版本状态选择:草稿/停用 → enable,审核通过 → publish,已发布 → disable。
* 审核链路(提交审核 / 审核通过 / 驳回)已废弃,页面不再有对应入口;
* 归档(archive)页面暂不暴露,但 api 层的 deleteTemplate 仍用它承担删除语义。
*
@@ -45,6 +45,7 @@ export const TEMPLATE_WORKFLOW_PERMISSIONS: Record<TemplateVersionAction, string
reject: DOC_TEMPLATE_PERMISSIONS.publish,
approve: DOC_TEMPLATE_PERMISSIONS.publish,
publish: DOC_TEMPLATE_PERMISSIONS.publish,
enable: DOC_TEMPLATE_PERMISSIONS.publish,
disable: DOC_TEMPLATE_PERMISSIONS.stop,
archive: DOC_TEMPLATE_PERMISSIONS.stop,
}
@@ -1,8 +1,8 @@
/**
* 文档库页面模型。
*
* 状态口径见 ./library:页面只有「启用中 / 停用」两档,
* 后端模板状态枚举只在 API 边界出现一次(readLibraryStatus 折叠)。
* 状态筛选口径见 ./library:页签只有「启用中 / 停用」两组;卡片仍可显示待发布等
* 具体版本状态。后端模板状态枚举只在 API 边界做归类。
*
* 「属性」与「正文」是两份表单、两个弹窗:原型 V10.2 刻意把「修改文档」拆成
* 「编辑属性」与「编辑正文」,属性弹窗不碰正文、正文弹窗不碰属性。
@@ -1013,7 +1013,7 @@ function downloadMockReport() {
}
function handleView(row: ReportTask) {
void router.push({ name: 'workbench-signing', query: { taskId: row.id } })
void router.push({ path: '/sign/workbench', query: { taskId: row.id } })
}
function getReportStatusLabel(status: ReportTaskStatus) {