- 模板状态按后端原始状态展示与筛选,待审核与已驳回分开展示 - 仅已发布模板可发起签署,新增提交审核/审核通过/驳回等动作 - 新增版本历史弹窗,复用统一的模板时间格式化方法 - 演示数据补齐已签署任务的签名图,并提供获取签名 dataURL 的接口 - 修复编辑器插入占位符时未定位光标导致内容落到文首的问题
227 lines
8.0 KiB
TypeScript
227 lines
8.0 KiB
TypeScript
/**
|
||
* 文档库的演示数据与展示文案。
|
||
*
|
||
* 状态标签放在这里而不是随演示数据一起删掉:标签是纯展示映射,
|
||
* 接真实接口后照样要用,不能和"演示数据"捆在一起。
|
||
*
|
||
* 演示文书库的种子与读写逻辑在 utils/mock-document-library:
|
||
* 文档库页面和签署工作台必须读同一份数据,否则会出现「文档库里维护的模板,签署端选不到」,
|
||
* 或者签署端混着一批文档库里看不到、也无法维护的模板。
|
||
*/
|
||
import type { DocumentRecord } from '@/api/management/types'
|
||
import type { LocalDocument, LocalDepartment } from '@/utils/mock-document-library'
|
||
import { readDocumentLibrary, writeDocumentLibrary } from '@/utils/mock-document-library'
|
||
|
||
import type { DocumentDepartment, DocumentBackendStatus, DocumentStatus } from './types'
|
||
|
||
/**
|
||
* 演示文库与页面模型之间的归一。
|
||
*
|
||
* utils/mock-document-library 刻意只声明自己用到的字段(不反向依赖 views 的类型),
|
||
* 它返回的文书缺了页面模型要求的 category / updatedBy / updatedAt,code 也可能为空。
|
||
* 两种口径不一致会在 loadLibrary / saveDocumentLibrary 处直接报类型错误,
|
||
* 因此在这一层做一次显式补齐——而不是让 utils 反过来依赖 views,
|
||
* 也不是把页面的必填字段放宽成可选(放宽会掩盖真实接口返回缺字段的问题)。
|
||
*
|
||
* 补齐用的值都标注了是演示数据的缺省值,不冒充真实字段:
|
||
* updatedAt 留空串,页面会显示成"—",不会假装有一份真实的更新时间。
|
||
*/
|
||
function toDocumentRecord(document: LocalDocument, categoryName: string): DocumentRecord {
|
||
return {
|
||
...document,
|
||
code: document.code ?? '',
|
||
category: categoryName,
|
||
updatedBy: '演示数据',
|
||
updatedAt: document.updatedAt ?? '',
|
||
}
|
||
}
|
||
|
||
export function toDocumentDepartment(department: LocalDepartment): DocumentDepartment {
|
||
return {
|
||
...department,
|
||
categories: department.categories.map((category) => ({
|
||
...category,
|
||
documents: category.documents.map((document) => toDocumentRecord(document, category.name)),
|
||
})),
|
||
}
|
||
}
|
||
|
||
/** 读取演示文书库:优先用本地保存的,没有则用种子(调用方负责再克隆一份使用) */
|
||
export function loadDocumentLibrary(): DocumentDepartment[] {
|
||
return readDocumentLibrary().map(toDocumentDepartment)
|
||
}
|
||
|
||
/**
|
||
* 保存演示文书库。
|
||
* 落本地时要把 category 摘掉(LocalDocument 没有 category 字段,
|
||
* 分类信息在 categories 这一层的 name 上),否则多存一个字段会在下次读取时对不上。
|
||
* updatedBy 同理:它是页面模型为「必须有人维护」补的展示字段,不是演示文库的数据。
|
||
*/
|
||
export function saveDocumentLibrary(library: DocumentDepartment[]): void {
|
||
writeDocumentLibrary(
|
||
library.map((department) => ({
|
||
...department,
|
||
categories: department.categories.map((category) => ({
|
||
name: category.name,
|
||
documents: category.documents.map(toLocalDocument),
|
||
})),
|
||
})),
|
||
)
|
||
}
|
||
|
||
/**
|
||
* 页面模型的文书 → 演示文库存储结构。
|
||
*
|
||
* 逐字段列出而不是解构剔除:解构剔除(`const { category, ...rest }`)会因为
|
||
* 被剔除的变量未使用而触发 lint,且多加一个页面专用字段时容易忘掉同步。
|
||
* 这里明确列出演示文库认识哪些字段,多出来的页面字段(category / updatedBy)自然被丢掉。
|
||
*/
|
||
function toLocalDocument(document: DocumentRecord): LocalDocument {
|
||
return {
|
||
id: document.id,
|
||
name: document.name,
|
||
code: document.code,
|
||
version: document.version,
|
||
status: document.status,
|
||
backendStatus: document.backendStatus,
|
||
description: document.description ?? '',
|
||
versionId: document.versionId,
|
||
departmentId: document.departmentId,
|
||
departmentName: document.departmentName,
|
||
campusId: document.campusId,
|
||
campusName: document.campusName,
|
||
updatedAt: document.updatedAt,
|
||
contentHtml: document.contentHtml,
|
||
contentSha256: document.contentSha256,
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 状态标签。键集合与后端模板状态枚举一一对应
|
||
* (见 api/management/types.ts 的 BackendTemplateStatus 与 documents.ts 的 normalizeDocumentStatus),
|
||
* 不再有后端枚举里不存在的 enabled。
|
||
*/
|
||
export const documentStatusLabels: Record<DocumentStatus, string> = {
|
||
draft: '草稿',
|
||
review: '需复审',
|
||
published: '已发布',
|
||
archived: '已归档',
|
||
}
|
||
|
||
/**
|
||
* 后端原始状态 → 中文标签。
|
||
*
|
||
* 卡片与预览弹窗用它,而不是归一后的 documentStatusLabels:归一过程把
|
||
* PENDING_REVIEW(待审核)和 REJECTED(已驳回)都并成了 review,
|
||
* 只看「需复审」分不清是「还等着审」还是「已经被打回」。
|
||
*/
|
||
export const backendStatusLabels: Record<DocumentBackendStatus, string> = {
|
||
DRAFT: '草稿',
|
||
PENDING_REVIEW: '待审核',
|
||
REJECTED: '已驳回',
|
||
APPROVED: '审核通过',
|
||
PUBLISHED: '已发布',
|
||
DISABLED: '已停用',
|
||
ARCHIVED: '已归档',
|
||
}
|
||
|
||
/**
|
||
* 状态页签的固定展示顺序:按文书生命周期排列(草稿 → 送审 → 驳回/通过 → 发布 → 停用 → 归档)。
|
||
* 固定顺序而不是按接口返回顺序,否则同一个库里状态一多,页签会来回跳。
|
||
*/
|
||
export const backendStatusOrder: DocumentBackendStatus[] = [
|
||
'DRAFT',
|
||
'PENDING_REVIEW',
|
||
'REJECTED',
|
||
'APPROVED',
|
||
'PUBLISHED',
|
||
'DISABLED',
|
||
'ARCHIVED',
|
||
]
|
||
|
||
/** 取状态标签:优先用后端原始状态(更精确),拿不到再退回归一后的状态 */
|
||
export function readStatusLabel(template: {
|
||
status: DocumentStatus
|
||
backendStatus?: string
|
||
}): string {
|
||
const backendLabel = template.backendStatus
|
||
? backendStatusLabels[template.backendStatus as DocumentBackendStatus]
|
||
: undefined
|
||
|
||
return backendLabel ?? documentStatusLabels[template.status]
|
||
}
|
||
|
||
/**
|
||
* 按后端状态取标签。
|
||
* 版本历史只有后端状态,没有归一后的 status,因此单独开一个入口。
|
||
* 遇到未知状态原样显示,不显示成空白——后端新增状态时至少能看见它是什么。
|
||
*/
|
||
export function readBackendStatusLabel(status: string | null | undefined): string {
|
||
if (!status) {
|
||
return '—'
|
||
}
|
||
|
||
return backendStatusLabels[status as DocumentBackendStatus] ?? status
|
||
}
|
||
|
||
/**
|
||
* 状态色调:决定状态徽标用哪一套配色。
|
||
*
|
||
* 不能直接用归一后的 DocumentStatus 当配色键——PENDING_REVIEW 与 REJECTED
|
||
* 归一后都是 review,两者会共用同一个颜色,页签与文字已经区分开了、颜色却还糊在一起。
|
||
*/
|
||
export type DocumentStatusTone = 'draft' | 'review' | 'rejected' | 'published' | 'archived'
|
||
|
||
export function readStatusTone(template: {
|
||
status: DocumentStatus
|
||
backendStatus?: string
|
||
}): DocumentStatusTone {
|
||
switch (readBackendStatus(template)) {
|
||
case 'PENDING_REVIEW':
|
||
return 'review'
|
||
case 'REJECTED':
|
||
return 'rejected'
|
||
case 'APPROVED':
|
||
case 'PUBLISHED':
|
||
return 'published'
|
||
case 'DISABLED':
|
||
case 'ARCHIVED':
|
||
return 'archived'
|
||
case 'DRAFT':
|
||
default:
|
||
return 'draft'
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 取文书的后端原始状态,供状态页签归类。
|
||
*
|
||
* 真实模式的列表接口会给 backendStatus;演示库与部分历史数据可能没有,
|
||
* 这时按归一后的 status 反推一个等价状态,避免这类文书在页签里被漏掉。
|
||
*
|
||
* 反推对 'review' 是有损的:归一过程把 PENDING_REVIEW 与 REJECTED 合并了,
|
||
* 只能归到 PENDING_REVIEW。这只是缺字段时的兜底,不改变页签按真实状态过滤的口径。
|
||
*/
|
||
export function readBackendStatus(template: {
|
||
status: DocumentStatus
|
||
backendStatus?: string
|
||
}): DocumentBackendStatus {
|
||
const known = backendStatusOrder.find((status) => status === template.backendStatus)
|
||
|
||
if (known) {
|
||
return known
|
||
}
|
||
|
||
switch (template.status) {
|
||
case 'published':
|
||
return 'PUBLISHED'
|
||
case 'review':
|
||
return 'PENDING_REVIEW'
|
||
case 'archived':
|
||
return 'ARCHIVED'
|
||
case 'draft':
|
||
default:
|
||
return 'DRAFT'
|
||
}
|
||
}
|