feat(signing): 勾选项默认不带名称并支持空名称字段
This commit is contained in:
@@ -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',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 选中态:点一下即整块选中,给个明确的反色,让人知道接下来的退格会删掉整块 */
|
/* 选中态:点一下即整块选中,给个明确的反色,让人知道接下来的退格会删掉整块 */
|
||||||
|
|||||||
Reference in New Issue
Block a user