feat(签署文书): 统一签署态文书样式并精简演示种子为单份麻精同意书
This commit is contained in:
@@ -7,14 +7,19 @@
|
||||
*
|
||||
* 这里同时持有演示文书库的种子数据,原因有两点:
|
||||
* ① 文档库页面与签署工作台必须看到同一份模板,不能一边有数据一边空着;
|
||||
* ② 种子文书都带真实正文(含签名位、签署日期、勾选表),
|
||||
* ② 种子文书带真实正文(含签名位、签署日期、勾选/选择项),
|
||||
* 发起签署后能走完整的填写项/签名链路,不会退化成前端写死的回退纸样。
|
||||
*
|
||||
* 种子只保留一份:**麻醉药品、精神药品知情同意书**(原型 V10.2 的 docSeedNar)。
|
||||
* 正文里的占位符用结构化写法(<span data-field>,见 utils/signing-fields),
|
||||
* 与编辑器插入的占位符、签署端渲染共用同一份属性契约。
|
||||
*
|
||||
* 只声明实际用到的字段,不绑定文档库页面的类型,避免 utils 反向依赖 views。
|
||||
*/
|
||||
|
||||
import type { SigningMethod, SigningTemplate } from '@/api/workbench/types'
|
||||
import type { SigningFieldDef, SigningMethod, SigningTemplate } from '@/api/workbench/types'
|
||||
|
||||
import { buildFieldSpanHtml } from './signing-fields'
|
||||
import { MOCK_STORAGE_KEYS, readMockState, writeMockState } from './mock-storage'
|
||||
|
||||
/**
|
||||
@@ -39,6 +44,11 @@ export interface LocalDocument {
|
||||
versionId?: string
|
||||
departmentId?: string | null
|
||||
departmentName?: string
|
||||
/**
|
||||
* 除归属科室外还共享给哪些科室(原型 sharedDepts)。
|
||||
* 后端 CreateTemplateRequest 只接受单个 departmentId,因此这里只在演示文库里有值。
|
||||
*/
|
||||
sharedDepartments?: string[]
|
||||
campusId?: string | null
|
||||
campusName?: string
|
||||
/**
|
||||
@@ -63,162 +73,6 @@ export interface LocalDepartment {
|
||||
categories: LocalCategory[]
|
||||
}
|
||||
|
||||
interface DemoDocumentSpec {
|
||||
id: string
|
||||
versionId: string
|
||||
name: string
|
||||
code: string
|
||||
version: string
|
||||
/** 所属科室(文档库分组名) */
|
||||
department: string
|
||||
category: string
|
||||
description: string
|
||||
/** 主体说明句:各类文书的措辞不同,逐份写清楚,不做模板化拼接 */
|
||||
statement: string
|
||||
/** 需要患者逐项确认的条目,渲染成表头含「勾选」的表格(签署时转为可勾选) */
|
||||
items: string[]
|
||||
/** 补充说明段落 */
|
||||
paragraphs?: string[]
|
||||
/** 需要签署现场填写的额外字段,如代理文书里的「与患者关系」 */
|
||||
extraFields?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成演示文书正文。
|
||||
* 标题、说明段、勾选表、签名位、签署日期都用真实文书的写法,
|
||||
* 其中 {患者签名}{医师签名}{签署日期} 是签署端识别签名位与日期的依据。
|
||||
*
|
||||
* 患者签名与医师签名是两栏:前者是主签名位(手写板/线上签署采集),
|
||||
* 后者是文书上的独立签名位,由医师在签署页点击就地采集,两者互不覆盖。
|
||||
*/
|
||||
function buildDemoDocumentHtml(spec: DemoDocumentSpec): string {
|
||||
const paragraphs = [
|
||||
spec.statement,
|
||||
...(spec.paragraphs ?? []),
|
||||
'本文档采用电子方式签署,患者手写签名图片将合成至本文档签名位,文书原件与签名原图双留存,可随时查验。',
|
||||
]
|
||||
|
||||
const checkboxTable = spec.items.length
|
||||
? `<table><thead><tr><th>项目</th><th>勾选</th></tr></thead><tbody>${spec.items
|
||||
.map((item) => `<tr><td>${item}</td><td></td></tr>`)
|
||||
.join('')}</tbody></table>`
|
||||
: ''
|
||||
|
||||
const extraFields = (spec.extraFields ?? []).map((field) => `<p>${field}:{${field}}</p>`)
|
||||
|
||||
return [
|
||||
`<h1 style="margin:0 0 14px;font-size:18px;letter-spacing:2px;text-align:center">${spec.name}</h1>`,
|
||||
...paragraphs.map((paragraph) => `<p>${paragraph}</p>`),
|
||||
checkboxTable,
|
||||
...extraFields,
|
||||
'<p>患者(或家属)签名:{患者签名}</p>',
|
||||
'<p>医师签名:{医师签名}</p>',
|
||||
'<p>签署日期:{签署日期}</p>',
|
||||
].join('')
|
||||
}
|
||||
|
||||
const DEMO_DOCUMENT_SPECS: DemoDocumentSpec[] = [
|
||||
{
|
||||
id: 'tpl-001',
|
||||
versionId: 'tpl-001-v2',
|
||||
name: '住院患者知情同意书',
|
||||
code: 'DOC-HOS-001',
|
||||
version: 'V2.0',
|
||||
department: '全院通用',
|
||||
category: '住院告知',
|
||||
description: '住院期间诊疗事项、风险与患者权利告知。',
|
||||
statement:
|
||||
'医务人员已就住院期间的诊疗方案、检查与用药安排、可能出现的医疗风险、替代方案及不良后果向本人(或授权委托人)充分说明,本人已完全理解上述内容。',
|
||||
items: ['诊疗方案与风险知情', '自费项目知情确认', '个人信息使用授权', '住院期间安全告知'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-002',
|
||||
versionId: 'tpl-002-v1',
|
||||
name: '住院须知及告知书',
|
||||
code: 'DOC-HOS-002',
|
||||
version: 'V1.0',
|
||||
department: '全院通用',
|
||||
category: '住院告知',
|
||||
description: '住院流程、陪护要求和患者须知确认。',
|
||||
statement: '现将住院期间的相关制度、注意事项与患者权利义务告知如下,请仔细阅读并确认。',
|
||||
items: ['探视与陪护制度', '请假离院风险告知', '费用结算方式确认'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-003',
|
||||
versionId: 'tpl-003-v3',
|
||||
name: '内镜检查知情同意书',
|
||||
code: 'DOC-DIG-001',
|
||||
version: 'V3.0',
|
||||
department: '消化内科',
|
||||
category: '检查告知',
|
||||
description: '内镜检查适应证、风险及替代方案告知。',
|
||||
statement:
|
||||
'医务人员已就内镜检查的目的、操作过程、可能的风险、替代方案及不良后果向本人(或授权委托人)充分说明,本人已完全理解上述内容。',
|
||||
paragraphs: ['本次检查可能采用普通内镜或无痛内镜,若采用无痛内镜需另行完成麻醉评估。'],
|
||||
items: ['肠道准备工作知情', '麻醉方式选择', '病理活检同意', '息肉切除同意'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-004',
|
||||
versionId: 'tpl-004-v1',
|
||||
name: 'MRI 磁共振检查知情同意书',
|
||||
code: 'DOC-RAD-001',
|
||||
version: 'V1.0',
|
||||
department: '放射科',
|
||||
category: '检查告知',
|
||||
description: '磁共振检查注意事项、禁忌证及风险告知。',
|
||||
statement:
|
||||
'医务人员已就磁共振检查的目的、注意事项、可能的风险、替代方案及不良后果向本人(或授权委托人)充分说明,本人已完全理解上述内容。',
|
||||
paragraphs: [
|
||||
'磁共振检查需进入强磁场环境,体外及体内金属植入物可能造成伤害,请务必如实告知。',
|
||||
'如使用钆对比剂,存在过敏反应及肾功能相关风险,检查前后需配合完成评估。',
|
||||
],
|
||||
items: ['体内金属植入物筛查', '幽闭恐惧症告知', '造影剂过敏史确认', '妊娠情况确认'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-005',
|
||||
versionId: 'tpl-005-v2',
|
||||
name: '心脏介入手术知情同意书',
|
||||
code: 'DOC-CARD-001',
|
||||
version: 'V2.0',
|
||||
department: '心内科',
|
||||
category: '手术告知',
|
||||
description: '介入治疗方案、围术期风险及替代方案告知。',
|
||||
statement:
|
||||
'医务人员已就介入手术的方案、操作过程、围术期风险、替代方案及可能的不良后果向本人(或授权委托人)充分说明,本人已完全理解上述内容。',
|
||||
paragraphs: ['介入操作过程中可能根据实际情况调整术式,需家属在院等候。'],
|
||||
items: ['冠状动脉造影同意', '支架植入同意', '术后抗凝方案知情', '穿刺部位并发症告知'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-006',
|
||||
versionId: 'tpl-006-v1',
|
||||
name: '体检报告发放及知情同意书',
|
||||
code: 'DOC-PE-001',
|
||||
version: 'V1.0',
|
||||
department: '健康管理中心',
|
||||
category: '体检告知',
|
||||
description: '体检报告领取方式、隐私保护和异常结果随访告知。',
|
||||
statement: '现将体检报告发放方式、个人隐私保护与异常结果随访安排告知如下,请确认。',
|
||||
items: ['报告领取方式确认', '异常结果随访告知', '个人隐私保护知情'],
|
||||
},
|
||||
{
|
||||
id: 'tpl-007',
|
||||
versionId: 'tpl-007-v1',
|
||||
name: '患者授权委托书',
|
||||
code: 'DOC-GEN-003',
|
||||
version: 'V1.0',
|
||||
department: '医务处',
|
||||
category: '授权文书',
|
||||
description: '患者授权家属或代理人办理相关医疗事项。',
|
||||
statement: '本人因病情或客观原因无法亲自办理下列事项,特授权受托人代为办理。',
|
||||
paragraphs: ['受托人在授权范围内签署的医疗文书,与本人签署具有同等效力。'],
|
||||
items: ['代为签署医疗文书', '代为了解病情与诊疗方案', '代为领取检查报告'],
|
||||
extraFields: ['与患者关系', '受托人姓名'],
|
||||
},
|
||||
]
|
||||
|
||||
/** 数组顺序即文档库里的科室分组顺序,全院通用排在最前 */
|
||||
const DEMO_DEPARTMENT_ORDER = ['全院通用', '放射科', '消化内科', '心内科', '健康管理中心', '医务处']
|
||||
|
||||
/**
|
||||
* 演示院区字典。
|
||||
*
|
||||
@@ -239,19 +93,17 @@ export const demoCampuses = [
|
||||
|
||||
/**
|
||||
* 演示科室字典。
|
||||
* 文档库的科室下拉在演示模式下用它,真实模式下改由 getDepartments(campusId) 提供。
|
||||
* 科室名与演示文书库的分组名刻意保持一致(放射科、消化内科……),
|
||||
* 否则会出现"下拉里选不到文书实际所属的科室"。
|
||||
* 文档库「文档归属」的科室勾选列表在演示模式下用它,真实模式下改由
|
||||
* getAllDepartments() 提供。科室名与演示文书库的分组名刻意保持一致,
|
||||
* 否则会出现「勾选列表里选不到文书实际所属的科室」。
|
||||
*/
|
||||
export const demoDepartments = [
|
||||
'医学影像科',
|
||||
'医学检验科',
|
||||
'临床遗传中心',
|
||||
'放射科',
|
||||
'消化内科',
|
||||
'心内科',
|
||||
'健康管理中心',
|
||||
'麻醉科·精神科',
|
||||
'药学部',
|
||||
'医务处',
|
||||
'医学影像科',
|
||||
'消化内科',
|
||||
'健康管理中心',
|
||||
].map((name, index) => ({
|
||||
id: `mock-department-${index + 1}`,
|
||||
campusId: '',
|
||||
@@ -262,9 +114,10 @@ export const demoDepartments = [
|
||||
sortNo: index + 1,
|
||||
}))
|
||||
|
||||
function groupKeyOf(department: string) {
|
||||
return `demo-${DEMO_DEPARTMENT_ORDER.indexOf(department)}`
|
||||
}
|
||||
/** 演示文书库里唯一的科室分组 */
|
||||
const DEMO_DEPARTMENT = '麻醉科·精神科'
|
||||
/** 演示文书库里的分类(原型 library 里该文书落在「麻精药品」分类下) */
|
||||
const DEMO_CATEGORY = '麻精药品'
|
||||
|
||||
/**
|
||||
* 演示文书库种子。
|
||||
@@ -275,50 +128,150 @@ function groupKeyOf(department: string) {
|
||||
*/
|
||||
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)
|
||||
const categories = new Map<string, LocalDocument[]>()
|
||||
|
||||
specs.forEach((spec) => {
|
||||
const documents = categories.get(spec.category) ?? []
|
||||
documents.push({
|
||||
id: spec.id,
|
||||
name: spec.name,
|
||||
code: spec.code,
|
||||
version: spec.version,
|
||||
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)
|
||||
})
|
||||
|
||||
return {
|
||||
key: groupKeyOf(department),
|
||||
name: department,
|
||||
campus: 'all' as const,
|
||||
categories: [...categories.entries()].map(([name, documents]) => ({ name, documents })),
|
||||
}
|
||||
}).filter((department) => department.categories.length > 0)
|
||||
/** 现场手填的文本框:字段键统一走 custom: 前缀,编辑器据此提示「签署时由患者/家属现场填写」 */
|
||||
function handFilled(label: string): SigningFieldDef {
|
||||
return { id: `custom:${label}`, label, kind: 'text', required: false, options: [] }
|
||||
}
|
||||
|
||||
/** 演示文书库种子:文档库页面与签署工作台读同一份,保证两个页面看到的模板一致 */
|
||||
export const demoDocumentLibrary: LocalDepartment[] = buildDemoLibrary()
|
||||
/**
|
||||
* 麻醉药品、精神药品知情同意书(原型 V10.2 的 docSeedNar)。
|
||||
*
|
||||
* 正文结构与原型逐条对齐:一、患者基本信息 / 二、代办人信息 / 三、身份核验记录 /
|
||||
* 四、用药信息 / 五、风险告知与注意事项 / 六、签字确认。
|
||||
*
|
||||
* 与原型的一处差别:原型里「代办人信息」等处的填空是 Word 导入时留下的静态下划线
|
||||
* (`<span style="border-bottom:…">`),在编辑器里会因属性被清洗而丢失。
|
||||
* 这里改成**文本框占位符**——签署时渲染出来同样是下划线,但内容能被采集,
|
||||
* 也顺便把「现场手填」这条链路演示完整。
|
||||
*/
|
||||
function buildNarcoticConsentHtml(): string {
|
||||
const field = (def: SigningFieldDef) => buildFieldSpanHtml(def)
|
||||
|
||||
const identityVerified: SigningFieldDef = {
|
||||
id: 'identityVerified',
|
||||
label: '已核对',
|
||||
kind: 'checkbox',
|
||||
required: true,
|
||||
options: ['患者本人', '代办人'],
|
||||
multi: true,
|
||||
}
|
||||
const takeAway: SigningFieldDef = {
|
||||
id: 'takeAway',
|
||||
label: '是否带离机构使用',
|
||||
kind: 'choice',
|
||||
required: true,
|
||||
options: ['否(本机构内使用)', '是(带离机构外使用,按单次门诊处方量管理)'],
|
||||
multi: false,
|
||||
}
|
||||
|
||||
const risks = [
|
||||
'麻精药品具有成瘾性(依赖性)风险,须严格按照医嘱使用,不得擅自调整用药剂量或改变用法。',
|
||||
'不得套开麻精药品,不得将药品转借、转售他人。',
|
||||
'转售麻精药品涉嫌毒品犯罪,需承担严重法律后果(第二十七条“特殊属性告知”)。',
|
||||
'使用麻精药品需按医嘱定期复诊(长期使用者至少每 3 个月复诊或电话/视频随诊)。',
|
||||
'过期、未使用的麻精药品及其废贴、空安瓿应当交回医疗机构,由医疗机构按规定销毁,不得自行处理或丢弃。',
|
||||
'注射剂再次领取时须交回此前使用的空安瓿(第二十七条、第四十二条)。',
|
||||
'可能出现的不良反应:恶心、呕吐、便秘、头晕、嗜睡、呼吸抑制等,如有不适及时就医。',
|
||||
'本药品仅供本患者使用,请妥善保管,防止丢失、被盗或被他人误用。',
|
||||
]
|
||||
|
||||
return [
|
||||
'<h2>麻醉药品、精神药品知情同意书</h2>',
|
||||
'<p>(依据国卫医政发〔2026〕21号第二十七条、第六十九条制定)</p>',
|
||||
'<h3>一、患者基本信息</h3>',
|
||||
'<table><tbody>',
|
||||
`<tr><td>患者姓名</td><td>${field({ id: 'patient', label: '患者姓名', kind: 'text', required: true, options: [] })}</td>`,
|
||||
`<td>性别</td><td>${field({ id: 'sex', label: '性别', kind: 'text', required: false, options: [] })}</td></tr>`,
|
||||
`<tr><td>年龄</td><td>${field({ id: 'age', label: '年龄', kind: 'text', required: false, options: [] })}</td>`,
|
||||
`<td>病历号</td><td>${field(handFilled('病历号'))}</td></tr>`,
|
||||
`<tr><td>身份证号</td><td>${field(handFilled('身份证号'))}</td>`,
|
||||
`<td>联系电话</td><td>${field({ id: 'phone', label: '联系电话', kind: 'text', required: false, options: [] })}</td></tr>`,
|
||||
`<tr><td>诊断</td><td>${field(handFilled('诊断'))}</td>`,
|
||||
`<td>就诊科室</td><td>${field(handFilled('就诊科室'))}</td></tr>`,
|
||||
'</tbody></table>',
|
||||
'<h3>二、代办人信息(代办领取时填写)</h3>',
|
||||
'<table><tbody>',
|
||||
`<tr><td>代办人姓名</td><td>${field(handFilled('代办人姓名'))}</td>`,
|
||||
`<td>与患者关系</td><td>${field(handFilled('与患者关系'))}</td></tr>`,
|
||||
`<tr><td>代办人身份证号</td><td>${field(handFilled('代办人身份证号'))}</td>`,
|
||||
`<td>联系电话</td><td>${field(handFilled('代办人联系电话'))}</td></tr>`,
|
||||
'</tbody></table>',
|
||||
'<h3>三、身份核验记录</h3>',
|
||||
'<table><tbody>',
|
||||
`<tr><td>已核对${field(identityVerified)} 身份证明原件:</td>`,
|
||||
`<td>证件类型及号码:${field(handFilled('证件类型及号码'))}</td></tr>`,
|
||||
'<tr><td>医师确认:患者/代办人身份信息与登记一致</td>',
|
||||
`<td>核对医师签名:${field({ id: 'doctor', label: '核对医师签名', kind: 'signature', required: true, options: [] })}</td></tr>`,
|
||||
'</tbody></table>',
|
||||
'<h3>四、用药信息</h3>',
|
||||
'<table><tbody>',
|
||||
`<tr><td>药品名称</td><td>${field(handFilled('药品名称'))}</td></tr>`,
|
||||
`<tr><td>剂型/规格</td><td>${field(handFilled('剂型规格'))}</td></tr>`,
|
||||
`<tr><td>用法用量</td><td>${field(handFilled('用法用量'))}</td></tr>`,
|
||||
`<tr><td>是否带离机构使用</td><td>${field(takeAway)}</td></tr>`,
|
||||
`<tr><td>使用期限</td><td>${field(handFilled('使用期限'))}</td></tr>`,
|
||||
'</tbody></table>',
|
||||
'<h3>五、风险告知与注意事项</h3>',
|
||||
`<ol>${risks.map((risk) => `<li>${risk}</li>`).join('')}</ol>`,
|
||||
'<h3>六、签字确认</h3>',
|
||||
'<table><tbody>',
|
||||
`<tr><td>患者(或监护人/代办人)签名:${field({ id: 'signature', label: '患者签名', kind: 'signature', required: true, options: [] })}</td>`,
|
||||
`<td>签字日期:${field({ id: 'signedDate', label: '签署日期', kind: 'date', required: true, options: [] })}</td></tr>`,
|
||||
'<tr><td>(未成年人须由监护人书面同意并签名)</td><td></td></tr>',
|
||||
`<tr><td>开具医师签名:${field({ id: 'prescribingDoctor', label: '开具医师签名', kind: 'signature', required: true, options: [] })}</td>`,
|
||||
'<td></td></tr>',
|
||||
'</tbody></table>',
|
||||
'<p>注:本知情同意书由医疗机构妥善保存。采用电子形式的,内容须与纸质一致、完整且不可篡改(第六十九条)。</p>',
|
||||
].join('')
|
||||
}
|
||||
|
||||
function buildDemoLibrary(): LocalDepartment[] {
|
||||
const document: LocalDocument = {
|
||||
id: 'tpl-pha-nar-001',
|
||||
name: '麻醉药品、精神药品知情同意书',
|
||||
code: 'TPL-PHA-NAR-001',
|
||||
version: 'v1.0.0',
|
||||
status: 'published',
|
||||
backendStatus: 'PUBLISHED',
|
||||
description: '麻精药品成瘾性风险、特殊属性告知、身份核验、代办人信息与用药信息登记。',
|
||||
versionId: 'tpl-pha-nar-001-v1',
|
||||
departmentId: null,
|
||||
departmentName: DEMO_DEPARTMENT,
|
||||
campusId: null,
|
||||
campusName: '全院通用',
|
||||
updatedAt: DEMO_UPDATED_AT,
|
||||
contentHtml: buildNarcoticConsentHtml(),
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
key: 'demo-0',
|
||||
name: DEMO_DEPARTMENT,
|
||||
campus: 'all' as const,
|
||||
categories: [{ name: DEMO_CATEGORY, documents: [document] }],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 演示文书库种子(惰性构建)。
|
||||
*
|
||||
* 正文里的占位符由 buildFieldSpanHtml 产出,而它要读 document,
|
||||
* 因此在首次取用时才构建:模块被 Node 侧脚本 import 时不会因为缺 DOM 直接抛错。
|
||||
*/
|
||||
let demoLibraryCache: LocalDepartment[] | null = null
|
||||
|
||||
export function getDemoDocumentLibrary(): LocalDepartment[] {
|
||||
demoLibraryCache ??= buildDemoLibrary()
|
||||
return demoLibraryCache
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取演示文书库:优先用本地保存的(科室改过、建过的),没有则用种子。
|
||||
* 调用方负责再克隆一份使用,避免直接改到种子数据。
|
||||
*/
|
||||
export function readDocumentLibrary(): LocalDepartment[] {
|
||||
return readMockState<LocalDepartment[]>(MOCK_STORAGE_KEYS.documentLibrary) ?? demoDocumentLibrary
|
||||
return readMockState<LocalDepartment[]>(MOCK_STORAGE_KEYS.documentLibrary) ?? getDemoDocumentLibrary()
|
||||
}
|
||||
|
||||
/** 保存演示文书库,刷新后仍可继续编辑、预览与发起签署 */
|
||||
|
||||
Reference in New Issue
Block a user