feat(signing): 文书签署页添加 HIS 患者信息页眉
新增固定页眉展示患者姓名、性别、年龄、住院号/门诊号,取值自动来自 HIS 快照,仅作只读展示不参与填写项统计与签署证据。同时按字段键去重填写 项进度统计,避免正文与页眉重复字段导致分母虚高;并在文书编辑器中提 示页眉由系统自动带出、Word 自带页眉不会被转换。
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
FIELD_TOKEN_OPEN,
|
||||
resolveAnswersFromSystem,
|
||||
resolveFieldByLabel,
|
||||
SIGNING_DOCUMENT_HEADER,
|
||||
type SigningSystemSnapshot,
|
||||
} from './signing-fields'
|
||||
|
||||
@@ -454,6 +455,36 @@ function convertInlineOptions(root: Element, interactive: boolean, selected: Set
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成固定页眉:患者姓名 / 性别 / 年龄 / 住院号·门诊号,取值全部来自 HIS。
|
||||
* 只读展示,不挂 data-sign-field —— 页眉不是"待填写项",也不进签署证据,
|
||||
* 患者信息以任务快照为准,页眉只负责把快照呈现出来。
|
||||
*/
|
||||
function buildDocumentHeader(doc: Document, answers: SigningAnswers): HTMLElement {
|
||||
const header = doc.createElement('header')
|
||||
header.className = 'sign-doc-header'
|
||||
|
||||
SIGNING_DOCUMENT_HEADER.forEach(({ id, label }) => {
|
||||
const item = doc.createElement('span')
|
||||
item.className = 'sign-doc-header__item'
|
||||
|
||||
const labelElement = doc.createElement('span')
|
||||
labelElement.className = 'sign-doc-header__label'
|
||||
labelElement.textContent = `${label}:`
|
||||
|
||||
const value = answers[id]
|
||||
const valueElement = doc.createElement('span')
|
||||
valueElement.className = 'sign-doc-header__value'
|
||||
valueElement.textContent = typeof value === 'string' ? value : ''
|
||||
|
||||
item.appendChild(labelElement)
|
||||
item.appendChild(valueElement)
|
||||
header.appendChild(item)
|
||||
})
|
||||
|
||||
return header
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成签署态文书 HTML。
|
||||
* contentHtml 为空时返回空串;解析失败时原样返回,不会抛错。
|
||||
@@ -495,6 +526,9 @@ export function buildSigningDocumentHtml(
|
||||
convertCheckboxes(root, interactive, selected)
|
||||
convertInlineOptions(root, interactive, selected)
|
||||
|
||||
// 页眉最后插入:保证 HIS 取值不会被当成文书内容参与占位符/方框识别
|
||||
root.insertBefore(buildDocumentHeader(doc, answers), root.firstChild)
|
||||
|
||||
return root.innerHTML
|
||||
}
|
||||
|
||||
@@ -537,7 +571,12 @@ export function readAnswersFromRoot(root: HTMLElement): SigningAnswers {
|
||||
return answers
|
||||
}
|
||||
|
||||
/** 统计填写项进度:已填数量、未填的必填项、命中的异常项 */
|
||||
/**
|
||||
* 统计填写项进度:已填数量、未填的必填项、命中的异常项。
|
||||
*
|
||||
* 按字段键去重:同一个字段在文书里可能出现多次(页眉的患者姓名/就诊号与正文重复是常态),
|
||||
* 它们共用一份取值,只能算一个填写项,否则分母会被重复计数撑大。
|
||||
*/
|
||||
export function describeFieldProgress(
|
||||
root: HTMLElement,
|
||||
answers: SigningAnswers,
|
||||
@@ -548,15 +587,18 @@ export function describeFieldProgress(
|
||||
missingRequired: [],
|
||||
abnormal: [],
|
||||
}
|
||||
const seen = new Set<string>()
|
||||
|
||||
root.querySelectorAll<HTMLElement>(`[${FIELD_ATTR}]`).forEach((host) => {
|
||||
const id = host.getAttribute(FIELD_ATTR)
|
||||
const label = host.getAttribute(FIELD_LABEL_ATTR) ?? id ?? ''
|
||||
|
||||
if (!id) {
|
||||
if (!id || seen.has(id)) {
|
||||
return
|
||||
}
|
||||
|
||||
seen.add(id)
|
||||
|
||||
const value = answers[id]
|
||||
const empty = value === undefined || value === '' || value === false
|
||||
const risk = host.getAttribute(FIELD_RISK_ATTR)
|
||||
|
||||
@@ -44,6 +44,24 @@ export const SIGNING_FIELD_PRESETS: SigningFieldDef[] = [
|
||||
{ id: 'doctor', label: '医师签名', kind: 'signature', required: false, options: [] },
|
||||
]
|
||||
|
||||
/**
|
||||
* 文书固定页眉。
|
||||
*
|
||||
* 页眉不由模板正文承载,也不给操作人员编辑:内容全部来自 HIS,
|
||||
* 签署时按字段键自动填充,正式 PDF 由服务端按页重复同一份页眉。
|
||||
* 字段键复用 SIGNING_FIELD_PRESETS,取值映射与正文共用一套,不会出现两处口径。
|
||||
*
|
||||
* 因为页眉是系统渲染的只读展示(不挂 data-sign-field),
|
||||
* 它既不会出现在"填写项"统计里,也不会被写进签署证据——
|
||||
* 患者信息以任务快照为准,页眉只是把快照展示出来。
|
||||
*/
|
||||
export const SIGNING_DOCUMENT_HEADER: Array<{ id: string; label: string }> = [
|
||||
{ id: 'patient', label: '患者姓名' },
|
||||
{ id: 'sex', label: '性别' },
|
||||
{ id: 'age', label: '年龄' },
|
||||
{ id: 'visit', label: '住院号/门诊号' },
|
||||
]
|
||||
|
||||
/**
|
||||
* 同一字段在不同文书里的常见写法。
|
||||
* 各科室历史文书用词不统一(患者签名 / 患方签名 / 患者(或家属)签名),
|
||||
|
||||
@@ -455,6 +455,37 @@ function syncPreviewFields() {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 固定页眉:患者基本信息由 HIS 自动带出,与正文之间用一条横线分隔 */
|
||||
.signing-preview :deep(.sign-doc-header) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 26px;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1.5px solid #52646e;
|
||||
}
|
||||
|
||||
.signing-preview :deep(.sign-doc-header__item) {
|
||||
display: inline-flex;
|
||||
gap: 4px;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.signing-preview :deep(.sign-doc-header__label) {
|
||||
flex-shrink: 0;
|
||||
color: #52646e;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
|
||||
.signing-preview :deep(.sign-doc-header__value) {
|
||||
min-width: 6.5em;
|
||||
padding: 0 6px 1px;
|
||||
color: #1c2b33;
|
||||
font-size: 12.5px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #52646e;
|
||||
}
|
||||
|
||||
.signing-preview :deep(table) {
|
||||
width: 100%;
|
||||
margin: 8px 0;
|
||||
|
||||
@@ -339,6 +339,11 @@ function save() {
|
||||
中全选复制文书后直接粘贴到下方编辑区;需要患者勾选的内容可点上方「插入勾选框」按钮插入(光标停在表格单元格里就插到该单元格),或用表头含「勾选」列的表格承载。
|
||||
</span>
|
||||
</div>
|
||||
<p class="docx-header-note">
|
||||
患者姓名 / 性别 / 年龄 / 住院号·门诊号由系统在页眉自动带出(签署时按 HIS
|
||||
数据填充),正文不必再写这几项; 导入 .docx 时 Word 自带的页眉 /
|
||||
页脚不会被转换,请勿依赖它承载患者信息。
|
||||
</p>
|
||||
<div class="editor-area">
|
||||
<EditorWangPane
|
||||
ref="paneRef"
|
||||
@@ -616,6 +621,17 @@ function save() {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 患者基本信息由系统页眉承载、且 Word 页眉不会被 mammoth 转换,这条提示常驻在编辑区上方 */
|
||||
.docx-header-note {
|
||||
padding: 6px 9px;
|
||||
margin: 0 0 8px;
|
||||
color: var(--warn);
|
||||
font-size: 11.5px;
|
||||
line-height: 1.7;
|
||||
background: var(--warn-l);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.editor-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -292,6 +292,37 @@ onMounted(() => {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 固定页眉:患者基本信息由 HIS 自动带出,与正文之间用一条横线分隔 */
|
||||
.sign-doc-body :deep(.sign-doc-header) {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 26px;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1.5px solid #52646e;
|
||||
}
|
||||
|
||||
.sign-doc-body :deep(.sign-doc-header__item) {
|
||||
display: inline-flex;
|
||||
gap: 4px;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.sign-doc-body :deep(.sign-doc-header__label) {
|
||||
flex-shrink: 0;
|
||||
color: #52646e;
|
||||
font-size: 12.5px;
|
||||
}
|
||||
|
||||
.sign-doc-body :deep(.sign-doc-header__value) {
|
||||
min-width: 6.5em;
|
||||
padding: 0 6px 1px;
|
||||
color: #1c2b33;
|
||||
font-size: 12.5px;
|
||||
text-align: center;
|
||||
border-bottom: 1px solid #52646e;
|
||||
}
|
||||
|
||||
.sign-doc-body :deep(.sign-doc-checkbox) {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
|
||||
Reference in New Issue
Block a user