From 3995c027fc9bd16a0ed5f501f01eb9d95420f8e2 Mon Sep 17 00:00:00 2001 From: yelan Date: Mon, 28 Sep 2026 11:08:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=82=A3=E8=80=85=E7=AD=BE=E7=BD=B2):=20?= =?UTF-8?q?=E7=AD=BE=E7=BD=B2=E8=87=AA=E5=8A=A8=E8=90=BD=E6=AC=BE=E6=97=A5?= =?UTF-8?q?=E6=9C=9F=E5=B9=B6=E6=96=B0=E5=A2=9E=E7=B2=BE=E9=BA=BB=E5=90=8C?= =?UTF-8?q?=E6=84=8F=E4=B9=A6=E6=BC=94=E7=A4=BA=E6=96=87=E4=B9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- patient-h5/src/components/DocumentPaper.vue | 27 ++++- patient-h5/src/composables/useSignFlow.ts | 16 ++- patient-h5/src/mock/demo-narcotic.ts | 104 ++++++++++++++++++++ patient-h5/src/views/signer/SignerView.vue | 16 +++ 4 files changed, 156 insertions(+), 7 deletions(-) create mode 100644 patient-h5/src/mock/demo-narcotic.ts diff --git a/patient-h5/src/components/DocumentPaper.vue b/patient-h5/src/components/DocumentPaper.vue index 5fe636d..908db6a 100644 --- a/patient-h5/src/components/DocumentPaper.vue +++ b/patient-h5/src/components/DocumentPaper.vue @@ -25,11 +25,17 @@ const props = withDefaults( patientSignature?: string | null /** 患者签名位的字段 id(与 clinical-web 的 PRIMARY_SIGNATURE_FIELD_ID 同口径) */ patientFieldId?: string + /** + * 签署日期(`YYYY-MM-DD`)。签了字就该有日期,不该再让患者填一次 —— + * 只在签名已采到时由调用方传入;没传就保持模板里的空占位。 + */ + signedAt?: string | null }>(), { answers: () => ({}), patientSignature: null, patientFieldId: 'signature', + signedAt: null, }, ) @@ -93,7 +99,15 @@ function paint() { const value = (props.answers?.[id] ?? '').trim() - // ② 没有值:保留模板里的标签文字,只标记成空 + // ② 签字日期不劳患者填:签了字,日期就是签署当天。 + // 只在签名已采到时才填 —— 还没签就把日期写上,文书会看起来像已经完成了 + if (!value && kind === 'date' && props.signedAt) { + node.classList.add('is-filled') + node.textContent = props.signedAt + return + } + + // ③ 没有值:保留模板里的标签文字,只标记成空 if (!value) { node.classList.add('is-empty') @@ -106,13 +120,13 @@ function paint() { node.classList.add('is-filled') - // ③ 签名图(医师签名等) + // ④ 签名图(医师签名等) if (kind === 'signature' || isImageValue(value)) { appendSignatureImage(node, value, label || '签名') return } - // ④ 勾选类:单选一律显示已勾,多选/复选按值判 + // ⑤ 勾选类:单选一律显示已勾,多选/复选按值判 if (kind === 'checkbox' || kind === 'choice') { const checked = kind === 'choice' ? true : isChecked(value) // 值是 1/0 这类占位时回落到标签文字,避免正文里出现一个孤零零的「1」 @@ -122,7 +136,7 @@ function paint() { return } - // ⑤ 其余(text / date)按文本显示 + // ⑥ 其余(text / date)按文本显示 node.textContent = value }) } @@ -199,6 +213,9 @@ watch( padding: 8px 10px; vertical-align: top; border: 1px solid #dbe7ea; + /* 中文默认可在任意字符处断行,窄列里会把「签字日期」拆成四个竖排的字。 + keep-all 让浏览器优先保证词不被拆开;真的放不下时由 overflow-wrap 兜底。 */ + word-break: keep-all; } .patient-paper :deep(img) { @@ -229,8 +246,10 @@ watch( border-bottom: 1px dashed #b9d8d2; } +/* 签名图不设宽上限会挤掉旁边的文字列,把「签字日期」这类标签压成竖排 */ .patient-paper :deep(.field-signature) { display: block; + max-width: 120px; max-height: 90px; margin: 2px 0; } diff --git a/patient-h5/src/composables/useSignFlow.ts b/patient-h5/src/composables/useSignFlow.ts index 2b0218a..66bfc20 100644 --- a/patient-h5/src/composables/useSignFlow.ts +++ b/patient-h5/src/composables/useSignFlow.ts @@ -7,6 +7,7 @@ import type { SigningContextResponse, } from '@/api/types' import { DEMO_DOCUMENT_HTML, DEMO_PATIENT_FIELD_ID, DEMO_STAFF_ANSWERS } from '@/mock/demo-document' +import { DEMO_NARCOTIC_HTML, DEMO_NARCOTIC_STAFF_ANSWERS } from '@/mock/demo-narcotic' import type { SignerRelation, SignFlowState } from '@/types/signing' /** @@ -296,10 +297,19 @@ async function loadSigningContext(token: string) { * 「演示模式下好好的、真实模式一片空白」这种最难查的偏差。 */ function applyDemoContext() { - signingState.documentTitle = 'CT 增强扫描知情同意书' + // `?demo=narcotic` 切到精麻同意书:它是演示文库(clinical-web)里唯一真实存在的模板, + // 字段也最多(代办人 / 身份核验 / 用药信息),最能压出占位符渲染的问题 + const useNarcotic = + new URLSearchParams(window.location.search).get('demo') === 'narcotic' + + signingState.documentTitle = useNarcotic + ? '麻醉药品、精神药品知情同意书' + : 'CT 增强扫描知情同意书' signingState.templateVersion = 'v1.0.0' - signingState.documentContentHtml = DEMO_DOCUMENT_HTML - signingState.staffAnswers = { ...DEMO_STAFF_ANSWERS } + signingState.documentContentHtml = useNarcotic ? DEMO_NARCOTIC_HTML : DEMO_DOCUMENT_HTML + signingState.staffAnswers = { + ...(useNarcotic ? DEMO_NARCOTIC_STAFF_ANSWERS : DEMO_STAFF_ANSWERS), + } signingState.fields = [ { id: DEMO_PATIENT_FIELD_ID, label: '患者签名', kind: 'signature', required: true }, ] diff --git a/patient-h5/src/mock/demo-narcotic.ts b/patient-h5/src/mock/demo-narcotic.ts new file mode 100644 index 0000000..b836018 --- /dev/null +++ b/patient-h5/src/mock/demo-narcotic.ts @@ -0,0 +1,104 @@ +/** + * 演示模式的第二份文书:**麻醉药品、精神药品知情同意书**。 + * + * 与 `demo-document.ts` 的 CT 同意书并存,用 `?demo=narcotic` 切换。 + * 为什么单独做一份:精麻这份是演示文库(clinical-web)里**唯一真实存在**的模板, + * 字段也最多(代办人、身份核验、用药信息),占位符渲染最容易在这里暴露问题 —— + * CT 那份只有 11 个占位符,压不出「多行表格 + 成对字段」的排版毛病。 + * + * 占位符属性名与 clinical-web 的 `utils/signing-fields.ts` 一致: + * `data-field` / `data-kind`(text|date|choice|checkbox|signature)/ `data-label`。 + */ + +export const DEMO_NARCOTIC_PATIENT_FIELD_ID = 'signature' + +export const DEMO_NARCOTIC_HTML = [ + '

麻醉药品、精神药品知情同意书

', + '

(依据国卫医政发〔2026〕21 号第二十七条、第六十九条制定)

', + + '

一、患者基本信息

', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '
患者姓名患者姓名性别性别
年龄年龄病历号病历号
身份证号身份证号联系电话联系电话
诊断诊断就诊科室就诊科室
', + + '

二、代办人信息(代办领取时填写)

', + '', + '', + '', + '', + '', + '
代办人姓名代办人姓名与患者关系与患者关系
代办人身份证号代办人身份证号联系电话联系电话
', + + '

三、身份核验记录

', + '

已核对:患者本人 ' + + '代办人 ' + + '身份证原件

', + '', + '', + '', + '', + '
证件类型及号码证件类型及号码
医师确认医师确认核对医师签名核对医师签名
', + + '

四、用药信息

', + '', + '', + '', + '', + '', + '', + '
药品名称药品名称
剂型 / 规格剂型规格用法用量用法用量
是否带离机构使用带离机构使用使用期限使用期限
', + + '

五、风险告知与注意事项

', + '
    ', + '
  1. 麻醉药品、精神药品具有成瘾性,须严格遵照医嘱使用,不得自行加量或延长使用时间。
  2. ', + '
  3. 用药期间可能出现头晕、嗜睡、恶心、便秘等不良反应,如出现呼吸抑制、意识障碍请立即就医。
  4. ', + '
  5. 药品须妥善保管,避免儿童接触;剩余药品应交回医疗机构统一处理。
  6. ', + '
  7. 不得转借、转让或代他人开具,违者依法承担相应责任。
  8. ', + '
', + + '

六、签字确认

', + '', + '' + + '', + '', + '' + + '', + '', + '
患者(或监护人 / 代办人)签名患者签名签字日期签署日期
开具医师签名开具医师签名科室科室
', + + '

注:本知情同意书由医疗机构妥善保存。采用电子形式的,内容须与纸质一致、完整且不可篡改(第六十九条)。

', +].join('') + +/** 演示用的「医生已填」结果。同样**不含**患者签名位 —— 那一格留给患者当场签 */ +export const DEMO_NARCOTIC_STAFF_ANSWERS: Record = { + patient: '李某某', + sex: '男', + age: '46 岁', + visit: 'ZY20260827001', + idCard: '3301**********1234', + phone: '138****5678', + diagnosis: '癌性疼痛', + department: '消化内科', + identityPatient: '1', + idOriginal: '1', + doctorConfirm: '患者身份信息与登记一致', + drugName: '盐酸吗啡缓释片', + drugSpec: '30mg × 10 片 / 盒', + drugUsage: '口服,一次 1 片,每 12 小时一次', + takeAway: '1', + usePeriod: '2026-08-27 至 2026-09-03', + department2: '消化内科', + verifyDoctor: + 'data:image/svg+xml;utf8,' + + '', + prescribingDoctor: + 'data:image/svg+xml;utf8,' + + '', +} diff --git a/patient-h5/src/views/signer/SignerView.vue b/patient-h5/src/views/signer/SignerView.vue index 3e34a87..b709ec1 100644 --- a/patient-h5/src/views/signer/SignerView.vue +++ b/patient-h5/src/views/signer/SignerView.vue @@ -29,6 +29,21 @@ const signerLine = computed(() => { return `${name}(${relation})` }) +/** + * 签字日期:签了字就是签署当天。 + * 没落笔时不传,文书里的日期格保持空占位 —— 否则文书会看起来像已经签完了。 + */ +const signedDate = computed(() => { + if (!signatureDataUrl.value) { + return null + } + + const now = new Date() + const pad = (part: number) => String(part).padStart(2, '0') + + return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}` +}) + function dataUrlToBlob(dataUrl: string) { const [header, encoded] = dataUrl.split(',') const mimeType = header?.match(/data:(.*?);base64/)?.[1] ?? 'image/png' @@ -105,6 +120,7 @@ async function submitSigning() { :content-html="signingState.documentContentHtml" :answers="signingState.staffAnswers" :patient-signature="signatureDataUrl || null" + :signed-at="signedDate" />