From a0baef1dbfc516a398f94c06039a2a1e162188af Mon Sep 17 00:00:00 2001 From: yelan Date: Wed, 7 Oct 2026 16:05:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(signing):=20=E5=8B=BE=E9=80=89=E9=A1=B9?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E4=B8=8D=E5=B8=A6=E5=90=8D=E7=A7=B0=E5=B9=B6?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E7=A9=BA=E5=90=8D=E7=A7=B0=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- clinical-web/src/utils/signing-field-node.ts | 9 +++- clinical-web/src/utils/signing-fields.ts | 11 +++-- .../documents/components/WordEditorDialog.vue | 49 ++++++++++++++----- .../components/editors/EditorWangPane.vue | 19 +++---- 4 files changed, 61 insertions(+), 27 deletions(-) diff --git a/clinical-web/src/utils/signing-field-node.ts b/clinical-web/src/utils/signing-field-node.ts index 42e49ec..b8c2b5f 100644 --- a/clinical-web/src/utils/signing-field-node.ts +++ b/clinical-web/src/utils/signing-field-node.ts @@ -249,6 +249,13 @@ function renderSignField(elemNode: unknown, _children: VNode[] | null, editor: I elem.fieldRisk ? `命中「${elem.fieldRisk}」记为异常项` : '', ].filter(Boolean) + // 名称可以为空(勾选项默认无名):无名称时标题去掉「待填写项:」后面的冒号, + // 免得拼出「待填写项:(勾选)」这种读起来像漏字的句子。 + const heading = elem.fieldLabel + ? `待填写项:${elem.fieldLabel}(${kindLabel})` + : `待填写项(${kindLabel})` + const details = extra.length ? `。${extra.join(',')}` : '' + return h('span', { attrs: { class: `${SIGN_FIELD_CHIP_CLASS} ${SIGN_FIELD_NODE_CLASS}${selected ? ' is-selected' : ''}`, @@ -256,7 +263,7 @@ function renderSignField(elemNode: unknown, _children: VNode[] | null, editor: I // 避免在 DOM 里留下可落光标的文字(见文件头 ③) 'data-sign-label': elem.fieldLabel, 'data-sign-kind': elem.fieldKind, - title: `待填写项:${elem.fieldLabel}(${kindLabel}),${source}。${extra.join(',')}。点击选中后可整块删除`, + title: `${heading},${source}${details}。点击选中后可整块删除`, contenteditable: 'false', }, }) diff --git a/clinical-web/src/utils/signing-fields.ts b/clinical-web/src/utils/signing-fields.ts index 320b416..2f8f8de 100644 --- a/clinical-web/src/utils/signing-fields.ts +++ b/clinical-web/src/utils/signing-fields.ts @@ -115,7 +115,10 @@ export function buildFieldSpanHtml(field: SigningFieldDef): string { */ export function readFieldFromElement(element: Element): SigningFieldDef | null { const id = element.getAttribute(FIELD_ID_ATTR)?.trim() ?? '' - const label = (element.getAttribute(FIELD_LABEL_ATTR) ?? element.textContent ?? '').trim() + const rawLabel = (element.getAttribute(FIELD_LABEL_ATTR) ?? element.textContent ?? '').trim() + // data-label 属性**存在**时按其值,允许为空——勾选项默认就不带名称(正文里只显示一个方框)。 + // 只有属性缺失(被后端清洗掉)才回落到 id,至少给这一项留个可读标识。 + const label = element.hasAttribute(FIELD_LABEL_ATTR) ? rawLabel : rawLabel || id if (!id && !label) { return null @@ -151,7 +154,7 @@ export function readFieldFromElement(element: Element): SigningFieldDef | null { return { id: id || (resolved as SigningFieldDef).id, - label: label || id, + label, kind: (element.getAttribute(FIELD_KIND_ATTR) ?? resolved?.kind ?? 'text') as SigningFieldKind, required: element.getAttribute(FIELD_REQUIRED_ATTR) === 'true' || @@ -601,7 +604,9 @@ export function collectFieldDefs(contentHtml: string): SigningFieldDef[] { const fields: SigningFieldDef[] = [] collectFieldOccurrences(contentHtml).forEach(({ field }) => { - if (!field.label || seen.has(field.id)) { + // 只按字段键去重:名称可以为空(勾选项默认无名),不该因为没名字就把这一项丢掉—— + // 丢掉之后它在「填写项配置」里根本选不中,等于插进去就改不了。 + if (seen.has(field.id)) { return } diff --git a/clinical-web/src/views/management/documents/components/WordEditorDialog.vue b/clinical-web/src/views/management/documents/components/WordEditorDialog.vue index d7b1875..d8f6e3d 100644 --- a/clinical-web/src/views/management/documents/components/WordEditorDialog.vue +++ b/clinical-web/src/views/management/documents/components/WordEditorDialog.vue @@ -88,7 +88,20 @@ const AUTO_FILL_LABELS = ['患者姓名', '性别', '年龄', '就诊号', '与 * kind 必须逐项写死:这三项在目录里都查不到,兜底出来的类型不会自己变成想要的那个 * (findPreset 只认目录与签名),一律兜成 checkbox 会让填写项插出来是个勾选框。 */ -const MANUAL_BUTTONS: Array<{ label: string; kind: SigningFieldKind; title: string }> = [ +const MANUAL_BUTTONS: Array<{ + /** 按钮文案(也是 findPreset 的查找键) */ + label: string + kind: SigningFieldKind + title: string + /** + * 插入时的默认字段名。 + * + * 勾选项刻意给空串:正文里就只显示一个方框,不挂名称——这一处在勾什么由旁边的 + * 文字交代(表格的其它列、正文的上一句),自带名称只会把版面挤开。想写名字时 + * 在「填写项配置」里填即可(见 applyFieldDraft 对选择类放开名称校验)。 + */ + fieldLabel?: string +}> = [ { label: '签名', kind: 'signature', @@ -97,7 +110,8 @@ const MANUAL_BUTTONS: Array<{ label: string; kind: SigningFieldKind; title: stri { label: '勾选项', kind: 'checkbox', - title: '签署时由患者逐项勾选;点正文里的胶囊可配置多个选项', + title: '签署时由患者逐项勾选;默认不带名称,点正文里的胶囊可配名称与多个选项', + fieldLabel: '', }, { label: '填写项', @@ -127,12 +141,12 @@ const autoFillButtons = computed(() => ) const manualButtons = computed(() => - MANUAL_BUTTONS.map(({ label, kind, title }) => ({ + MANUAL_BUTTONS.map(({ label, kind, title, fieldLabel }) => ({ label, title, field: findPreset(label) ?? { - id: `custom:${label}`, - label, + id: `custom:${fieldLabel ?? label}`, + label: fieldLabel ?? label, kind, required: false, options: [], @@ -313,7 +327,12 @@ function insertField(field: SigningFieldDef) { required: false, } : field.id.startsWith('custom:') - ? { ...field, id: allocateCustomFieldId(valueHtml.value, field.label) } + ? { + ...field, + // 空名称(勾选项默认无名)也要有一个可读、唯一的字段键做身份:用类型名兜底, + // 避免落成 `custom:` 这种空键——读回时容易被当成名称,也说不清是哪一项。 + id: allocateCustomFieldId(valueHtml.value, field.label || field.kind), + } : field paneRef.value?.insertField(target) @@ -336,7 +355,9 @@ function applyFieldDraft() { const label = fieldDraft.label.trim() - if (!label) { + // 勾选 / 选择类允许无名(正文里只显示一个方框);文本、日期、签名仍必须有名, + // 否则签署端根本认不出这一项是什么。 + if (!label && !isOptionKind.value) { setMessage('请填写填写项名称', 'warn') return false } @@ -376,7 +397,10 @@ function applyFieldDraft() { } if (fieldDraft.kind === 'choice' && options.length < 2) { - setMessage(`选择项「${label}」至少需要两个选项`, 'warn') + setMessage( + `选择项「${label || SIGNING_FIELD_KIND_LABELS[fieldDraft.kind]}」至少需要两个选项`, + 'warn', + ) return false } @@ -397,7 +421,8 @@ function applyFieldDraft() { }) if (!changed) { - setMessage(`正文里已经没有「${field.label}」了,请重新选择要修改的填写项`, 'warn') + const name = field.label || SIGNING_FIELD_KIND_LABELS[field.kind] + setMessage(`正文里已经没有「${name}」了,请重新选择要修改的填写项`, 'warn') selectedFieldId.value = '' return false } @@ -423,7 +448,8 @@ function removeField() { const changed = paneRef.value?.removeField(field.id) ?? 0 selectedFieldId.value = '' - setMessage(`已删除「${field.label}」(正文里 ${changed} 处)`, 'ok') + const name = field.label || SIGNING_FIELD_KIND_LABELS[field.kind] + setMessage(`已删除「${name}」(正文里 ${changed} 处)`, 'ok') } /** 正文字数与填写项个数(原型 deStat) */ @@ -447,7 +473,8 @@ function validate(requireSignaturePosition = true): string | null { ) if (broken) { - return `「${broken.label}」只配了一个选项,勾选类至少要两个(或删掉选项改成单个勾选框)` + const name = broken.label || SIGNING_FIELD_KIND_LABELS[broken.kind] + return `「${name}」只配了一个选项,勾选类至少要两个(或删掉选项改成单个勾选框)` } const signatures = detectedFields.value.filter((field) => field.kind === 'signature') diff --git a/clinical-web/src/views/management/documents/components/editors/EditorWangPane.vue b/clinical-web/src/views/management/documents/components/editors/EditorWangPane.vue index 44dcf1f..5dbc522 100644 --- a/clinical-web/src/views/management/documents/components/editors/EditorWangPane.vue +++ b/clinical-web/src/views/management/documents/components/editors/EditorWangPane.vue @@ -476,19 +476,14 @@ onBeforeUnmount(() => { border-color: #0f9d6c; } -/* 勾选项在正文里就是**一个待勾的方框**,不带名称。 - 这是**有意偏离原型 V10.2**(2026-10-06 定):原型给勾选胶囊带【名称】, - 而这一处在勾什么由旁边的文字交代(表格的其它列、正文的上一句), - 自带一个【名称】只会把版面挤开;签署层渲染出来也只有一个 ☐, - 编辑层挂着名称会让同一处控件在两层两副长相。 - 名称仍留在 title 里(见 utils/signing-field-node 的 renderSignField),悬停可见。 */ -.pane-content :deep(.ms-sign-node[data-sign-kind='checkbox'])::before { - content: '\2611'; -} - -/* 单选仍带名称:一组单选(是 / 否)要靠名字才能分辨,去掉就只剩一个认不出的符号 */ +/* 勾选项 / 单选:正文里都是一个**待勾的方框**,名称(有才显示)跟在方框后面。 + - 默认不带名称:勾选项插入时名称为空,正文里就只显示一个 ☑; + - 单选与多选不做视觉区分,都用方形(2026-10-07 定)。这一处在勾什么由旁边的 + 文字交代(表格的其它列、正文的上一句),自带【名称】只会把版面挤开。 + 名称仍在 title 里(见 utils/signing-field-node 的 renderSignField),悬停可见。 */ +.pane-content :deep(.ms-sign-node[data-sign-kind='checkbox'])::before, .pane-content :deep(.ms-sign-node[data-sign-kind='choice'])::before { - content: '\2611 【' attr(data-sign-label) '】'; + content: '\2611 ' attr(data-sign-label); } /* 选中态:点一下即整块选中,给个明确的反色,让人知道接下来的退格会删掉整块 */