feat(文档库): 打通模板正文真实模式并新增渲染验证
模板列表接口不返回正文,展示、编辑与发起签署前需再取版本详情获取 contentHtml, 为此新增 getTemplateContent、createTemplateWithContent、updateTemplateContent 与 deleteTemplate(归档),并在签署端按 contentSha256 缓存正文快照。 同时新增 scripts/login.cjs 与 scripts/verify-pages.cjs,通过真实登录与 CDP 渲染截图、断言关键内容并收集运行时错误,并补充 .gitignore、eslint 忽略与文档说明。
This commit is contained in:
@@ -17,30 +17,46 @@ import type { SigningMethod, SigningTemplate } from '@/api/workbench/types'
|
||||
|
||||
import { MOCK_STORAGE_KEYS, readMockState, writeMockState } from './mock-storage'
|
||||
|
||||
/** 与 views/management/documents/types.ts 的 DocumentStatus 保持一致 */
|
||||
type LocalDocumentStatus = 'enabled' | 'draft' | 'archived' | 'review'
|
||||
/**
|
||||
* 演示文书的状态。
|
||||
* 取值与 api/management/types.ts 的 DocumentStatus 一致(对应后端 DRAFT / PENDING_REVIEW /
|
||||
* APPROVED / PUBLISHED / DISABLED / ARCHIVED 的归一结果),不再使用后端没有的 'enabled'。
|
||||
*/
|
||||
type LocalDocumentStatus = 'draft' | 'review' | 'published' | 'archived'
|
||||
/** 与 views/management/documents/types.ts 的 DocumentCampus 保持一致 */
|
||||
type LocalCampus = 'all' | '本部院区' | '东院区' | '西院区'
|
||||
type LocalCampus = string
|
||||
|
||||
interface LocalDocument {
|
||||
export interface LocalDocument {
|
||||
id: string
|
||||
name: string
|
||||
code: string
|
||||
/** 文书编码。与模板接口的 DocumentRecord.code 一样允许缺省,页面统一兜底显示 */
|
||||
code?: string
|
||||
version: string
|
||||
status: LocalDocumentStatus
|
||||
description: string
|
||||
backendStatus?: string
|
||||
/** 文书说明。来源同 code,允许缺省 */
|
||||
description?: string
|
||||
versionId?: string
|
||||
departmentId?: string | null
|
||||
departmentName?: string
|
||||
campusId?: string | null
|
||||
campusName?: string
|
||||
/**
|
||||
* 演示数据的更新时间。
|
||||
* 演示文库的种子不带时间,页面在这一层会显示成"—"而不是伪造一个时间戳;
|
||||
* 页面模型要求这个字段必填,所以在演示数据创建时写一次真实时间。
|
||||
*/
|
||||
updatedAt?: string
|
||||
contentHtml?: string
|
||||
contentSha256?: string
|
||||
}
|
||||
|
||||
interface LocalCategory {
|
||||
export interface LocalCategory {
|
||||
name: string
|
||||
documents: LocalDocument[]
|
||||
}
|
||||
|
||||
interface LocalDepartment {
|
||||
export interface LocalDepartment {
|
||||
key: string
|
||||
name: string
|
||||
campus: LocalCampus
|
||||
@@ -199,10 +215,62 @@ const DEMO_DOCUMENT_SPECS: DemoDocumentSpec[] = [
|
||||
/** 数组顺序即文档库里的科室分组顺序,全院通用排在最前 */
|
||||
const DEMO_DEPARTMENT_ORDER = ['全院通用', '放射科', '消化内科', '心内科', '健康管理中心', '医务处']
|
||||
|
||||
/**
|
||||
* 演示院区字典。
|
||||
*
|
||||
* 文档库的「所属院区」下拉在真实模式下取 api/management/organization 的 getCampuses,
|
||||
* 演示模式下没有接口可调,这里给一份同构的演示院区,键名与 CampusRecord 一致。
|
||||
* 演示 id 带 mock- 前缀,一眼能看出不是真实 UUID,不会误当成可提交给后端的取值。
|
||||
*/
|
||||
export const demoCampuses = [
|
||||
{ id: 'mock-campus-main', code: 'MAIN', name: '本部院区', status: 'ENABLED' as const, sortNo: 1 },
|
||||
{
|
||||
id: 'mock-campus-east',
|
||||
code: 'EAST',
|
||||
name: '东院区',
|
||||
status: 'ENABLED' as const,
|
||||
sortNo: 2,
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* 演示科室字典。
|
||||
* 文档库的科室下拉在演示模式下用它,真实模式下改由 getDepartments(campusId) 提供。
|
||||
* 科室名与演示文书库的分组名刻意保持一致(放射科、消化内科……),
|
||||
* 否则会出现"下拉里选不到文书实际所属的科室"。
|
||||
*/
|
||||
export const demoDepartments = [
|
||||
'医学影像科',
|
||||
'医学检验科',
|
||||
'临床遗传中心',
|
||||
'放射科',
|
||||
'消化内科',
|
||||
'心内科',
|
||||
'健康管理中心',
|
||||
'医务处',
|
||||
].map((name, index) => ({
|
||||
id: `mock-department-${index + 1}`,
|
||||
campusId: '',
|
||||
code: `MOCK-DEPT-${index + 1}`,
|
||||
name,
|
||||
parentId: null,
|
||||
status: 'ENABLED' as const,
|
||||
sortNo: index + 1,
|
||||
}))
|
||||
|
||||
function groupKeyOf(department: string) {
|
||||
return `demo-${DEMO_DEPARTMENT_ORDER.indexOf(department)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示文书库种子。
|
||||
*
|
||||
* updatedAt 用固定的历史时间而不是"当前时间":
|
||||
* 每次构建都取 now 的话,同一份种子文书在每次刷新后更新时间都会变,
|
||||
* 看起来像有人在不停改动文书,反而失真。
|
||||
*/
|
||||
const DEMO_UPDATED_AT = '2026-08-20T09:30:00+08:00'
|
||||
|
||||
function buildDemoLibrary(): LocalDepartment[] {
|
||||
return DEMO_DEPARTMENT_ORDER.map((department) => {
|
||||
const specs = DEMO_DOCUMENT_SPECS.filter((spec) => spec.department === department)
|
||||
@@ -215,11 +283,15 @@ function buildDemoLibrary(): LocalDepartment[] {
|
||||
name: spec.name,
|
||||
code: spec.code,
|
||||
version: spec.version,
|
||||
status: 'enabled',
|
||||
status: 'published',
|
||||
backendStatus: 'PUBLISHED',
|
||||
description: spec.description,
|
||||
versionId: spec.versionId,
|
||||
departmentId: null,
|
||||
departmentName: department,
|
||||
campusId: null,
|
||||
campusName: '全院通用',
|
||||
updatedAt: DEMO_UPDATED_AT,
|
||||
contentHtml: buildDemoDocumentHtml(spec),
|
||||
})
|
||||
categories.set(spec.category, documents)
|
||||
@@ -253,6 +325,10 @@ export function writeDocumentLibrary(library: LocalDepartment[]): void {
|
||||
/**
|
||||
* 把本地保存的文档库摊平成签署模板。
|
||||
* 文档库是模板的唯一来源:签署工作台能选到的模板,一定是在文档库里能维护的那几份。
|
||||
*
|
||||
* contentSha256 一并带出:真实模式下可用模板接口只给哈希不给正文,
|
||||
* 签署端要靠这个哈希去换回正文快照;演示模式虽然不缺正文,但两边字段保持一致,
|
||||
* 页面就不用按模式分叉。
|
||||
*/
|
||||
export function readLocalSigningTemplates(): SigningTemplate[] {
|
||||
return readDocumentLibrary().flatMap((department) =>
|
||||
@@ -261,7 +337,9 @@ export function readLocalSigningTemplates(): SigningTemplate[] {
|
||||
id: document.id,
|
||||
versionId: document.versionId ?? document.id,
|
||||
name: document.name,
|
||||
code: document.code,
|
||||
// SigningTemplate.code 必填而演示文书可能没有编码,这里如实兜底为空串,
|
||||
// 不编一个看起来像真实编码的假值
|
||||
code: document.code ?? '',
|
||||
version: document.version,
|
||||
department: department.name,
|
||||
departmentId: document.departmentId ?? null,
|
||||
@@ -270,6 +348,7 @@ export function readLocalSigningTemplates(): SigningTemplate[] {
|
||||
description: document.description ?? '',
|
||||
supportedMethods: ['pad', 'sms'] as SigningMethod[],
|
||||
contentHtml: document.contentHtml,
|
||||
contentSha256: document.contentSha256,
|
||||
})),
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user