feat(signing): 勾选项默认不带名称并支持空名称字段

This commit is contained in:
yelan
2026-10-07 16:05:44 +08:00
parent 715b9a03ab
commit a0baef1dbf
4 changed files with 61 additions and 27 deletions
+8 -1
View File
@@ -249,6 +249,13 @@ function renderSignField(elemNode: unknown, _children: VNode[] | null, editor: I
elem.fieldRisk ? `命中「${elem.fieldRisk}」记为异常项` : '', elem.fieldRisk ? `命中「${elem.fieldRisk}」记为异常项` : '',
].filter(Boolean) ].filter(Boolean)
// 名称可以为空(勾选项默认无名):无名称时标题去掉「待填写项:」后面的冒号,
// 免得拼出「待填写项:(勾选)」这种读起来像漏字的句子。
const heading = elem.fieldLabel
? `待填写项:${elem.fieldLabel}(${kindLabel})`
: `待填写项(${kindLabel})`
const details = extra.length ? `。${extra.join(',')}` : ''
return h('span', { return h('span', {
attrs: { attrs: {
class: `${SIGN_FIELD_CHIP_CLASS} ${SIGN_FIELD_NODE_CLASS}${selected ? ' is-selected' : ''}`, 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 里留下可落光标的文字(见文件头 ③) // 避免在 DOM 里留下可落光标的文字(见文件头 ③)
'data-sign-label': elem.fieldLabel, 'data-sign-label': elem.fieldLabel,
'data-sign-kind': elem.fieldKind, 'data-sign-kind': elem.fieldKind,
title: `待填写项:${elem.fieldLabel}(${kindLabel}),${source}。${extra.join(',')}。点击选中后可整块删除`, title: `${heading},${source}${details}。点击选中后可整块删除`,
contenteditable: 'false', contenteditable: 'false',
}, },
}) })
+8 -3
View File
@@ -115,7 +115,10 @@ export function buildFieldSpanHtml(field: SigningFieldDef): string {
*/ */
export function readFieldFromElement(element: Element): SigningFieldDef | null { export function readFieldFromElement(element: Element): SigningFieldDef | null {
const id = element.getAttribute(FIELD_ID_ATTR)?.trim() ?? '' 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) { if (!id && !label) {
return null return null
@@ -151,7 +154,7 @@ export function readFieldFromElement(element: Element): SigningFieldDef | null {
return { return {
id: id || (resolved as SigningFieldDef).id, id: id || (resolved as SigningFieldDef).id,
label: label || id, label,
kind: (element.getAttribute(FIELD_KIND_ATTR) ?? resolved?.kind ?? 'text') as SigningFieldKind, kind: (element.getAttribute(FIELD_KIND_ATTR) ?? resolved?.kind ?? 'text') as SigningFieldKind,
required: required:
element.getAttribute(FIELD_REQUIRED_ATTR) === 'true' || element.getAttribute(FIELD_REQUIRED_ATTR) === 'true' ||
@@ -601,7 +604,9 @@ export function collectFieldDefs(contentHtml: string): SigningFieldDef[] {
const fields: SigningFieldDef[] = [] const fields: SigningFieldDef[] = []
collectFieldOccurrences(contentHtml).forEach(({ field }) => { collectFieldOccurrences(contentHtml).forEach(({ field }) => {
if (!field.label || seen.has(field.id)) { // 只按字段键去重:名称可以为空(勾选项默认无名),不该因为没名字就把这一项丢掉——
// 丢掉之后它在「填写项配置」里根本选不中,等于插进去就改不了。
if (seen.has(field.id)) {
return return
} }
@@ -88,7 +88,20 @@ const AUTO_FILL_LABELS = ['患者姓名', '性别', '年龄', '就诊号', '与
* kind 必须逐项写死:这三项在目录里都查不到,兜底出来的类型不会自己变成想要的那个 * kind 必须逐项写死:这三项在目录里都查不到,兜底出来的类型不会自己变成想要的那个
* (findPreset 只认目录与签名),一律兜成 checkbox 会让填写项插出来是个勾选框。 * (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: '签名', label: '签名',
kind: 'signature', kind: 'signature',
@@ -97,7 +110,8 @@ const MANUAL_BUTTONS: Array<{ label: string; kind: SigningFieldKind; title: stri
{ {
label: '勾选项', label: '勾选项',
kind: 'checkbox', kind: 'checkbox',
title: '签署时由患者逐项勾选;点正文里的胶囊可配置多个选项', title: '签署时由患者逐项勾选;默认不带名称,点正文里的胶囊可配名称与多个选项',
fieldLabel: '',
}, },
{ {
label: '填写项', label: '填写项',
@@ -127,12 +141,12 @@ const autoFillButtons = computed(() =>
) )
const manualButtons = computed(() => const manualButtons = computed(() =>
MANUAL_BUTTONS.map(({ label, kind, title }) => ({ MANUAL_BUTTONS.map(({ label, kind, title, fieldLabel }) => ({
label, label,
title, title,
field: findPreset(label) ?? { field: findPreset(label) ?? {
id: `custom:${label}`, id: `custom:${fieldLabel ?? label}`,
label, label: fieldLabel ?? label,
kind, kind,
required: false, required: false,
options: [], options: [],
@@ -313,7 +327,12 @@ function insertField(field: SigningFieldDef) {
required: false, required: false,
} }
: field.id.startsWith('custom:') : field.id.startsWith('custom:')
? { ...field, id: allocateCustomFieldId(valueHtml.value, field.label) } ? {
...field,
// 空名称(勾选项默认无名)也要有一个可读、唯一的字段键做身份:用类型名兜底,
// 避免落成 `custom:` 这种空键——读回时容易被当成名称,也说不清是哪一项。
id: allocateCustomFieldId(valueHtml.value, field.label || field.kind),
}
: field : field
paneRef.value?.insertField(target) paneRef.value?.insertField(target)
@@ -336,7 +355,9 @@ function applyFieldDraft() {
const label = fieldDraft.label.trim() const label = fieldDraft.label.trim()
if (!label) { // 勾选 / 选择类允许无名(正文里只显示一个方框);文本、日期、签名仍必须有名,
// 否则签署端根本认不出这一项是什么。
if (!label && !isOptionKind.value) {
setMessage('请填写填写项名称', 'warn') setMessage('请填写填写项名称', 'warn')
return false return false
} }
@@ -376,7 +397,10 @@ function applyFieldDraft() {
} }
if (fieldDraft.kind === 'choice' && options.length < 2) { if (fieldDraft.kind === 'choice' && options.length < 2) {
setMessage(`选择项「${label}」至少需要两个选项`, 'warn') setMessage(
`选择项「${label || SIGNING_FIELD_KIND_LABELS[fieldDraft.kind]}」至少需要两个选项`,
'warn',
)
return false return false
} }
@@ -397,7 +421,8 @@ function applyFieldDraft() {
}) })
if (!changed) { if (!changed) {
setMessage(`正文里已经没有「${field.label}」了,请重新选择要修改的填写项`, 'warn') const name = field.label || SIGNING_FIELD_KIND_LABELS[field.kind]
setMessage(`正文里已经没有「${name}」了,请重新选择要修改的填写项`, 'warn')
selectedFieldId.value = '' selectedFieldId.value = ''
return false return false
} }
@@ -423,7 +448,8 @@ function removeField() {
const changed = paneRef.value?.removeField(field.id) ?? 0 const changed = paneRef.value?.removeField(field.id) ?? 0
selectedFieldId.value = '' selectedFieldId.value = ''
setMessage(`已删除「${field.label}」(正文里 ${changed} 处)`, 'ok') const name = field.label || SIGNING_FIELD_KIND_LABELS[field.kind]
setMessage(`已删除「${name}」(正文里 ${changed} 处)`, 'ok')
} }
/** 正文字数与填写项个数(原型 deStat) */ /** 正文字数与填写项个数(原型 deStat) */
@@ -447,7 +473,8 @@ function validate(requireSignaturePosition = true): string | null {
) )
if (broken) { 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') const signatures = detectedFields.value.filter((field) => field.kind === 'signature')
@@ -476,19 +476,14 @@ onBeforeUnmount(() => {
border-color: #0f9d6c; border-color: #0f9d6c;
} }
/* 勾选项在正文里就是**一个待勾的方框**,不带名称。 /* 勾选项 / 单选:正文里都是一个**待勾的方框**,名称(有才显示)跟在方框后面。
这是**有意偏离原型 V10.2**(2026-10-06 定):原型给勾选胶囊带【名称】, - 默认不带名称:勾选项插入时名称为空,正文里就只显示一个 ☑;
而这一处在勾什么由旁边的文字交代(表格的其它列、正文的上一句), - 单选与多选不做视觉区分,都用方形(2026-10-07 定)。这一处在勾什么由旁边的
自带一个【名称】只会把版面挤开;签署层渲染出来也只有一个 ☐, 文字交代(表格的其它列、正文的上一句),自带【名称】只会把版面挤开。
编辑层挂着名称会让同一处控件在两层两副长相。 名称仍在 title 里(见 utils/signing-field-node 的 renderSignField),悬停可见。 */
名称仍留在 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='checkbox'])::before {
content: '\2611';
}
/* 单选仍带名称:一组单选(是 / 否)要靠名字才能分辨,去掉就只剩一个认不出的符号 */
.pane-content :deep(.ms-sign-node[data-sign-kind='choice'])::before { .pane-content :deep(.ms-sign-node[data-sign-kind='choice'])::before {
content: '\2611 【' attr(data-sign-label) '】'; content: '\2611 ' attr(data-sign-label);
} }
/* 选中态:点一下即整块选中,给个明确的反色,让人知道接下来的退格会删掉整块 */ /* 选中态:点一下即整块选中,给个明确的反色,让人知道接下来的退格会删掉整块 */