feat(签署): 支持文书填写项与签名回显

This commit is contained in:
yelan
2026-09-16 15:54:40 +08:00
parent b15df82be2
commit 5361839cf2
10 changed files with 1178 additions and 164 deletions
@@ -1,8 +1,14 @@
<script setup lang="ts">
import { reactive, ref, watch } from 'vue'
import { computed, reactive, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import type { CampusRecord, DepartmentRecord } from '@/api/management/types'
import {
buildFieldToken,
collectFieldDefs,
findDuplicateFields,
SIGNING_FIELD_PRESETS,
} from '@/utils/signing-fields'
import EditorWangPane from './editors/EditorWangPane.vue'
import type { DocumentTemplate, TemplateEditorForm } from '../types'
@@ -35,16 +41,9 @@ const importing = ref(false)
const importMessage = ref('')
const paneRef = ref<EditorPaneHandle | null>(null)
// 占位符在签署时由系统按患者与就诊信息自动填充;签署交互管线同时会识别"勾选"表格。
const placeholders = [
'{患者姓名}',
'{性别}',
'{年龄}',
'{就诊号}',
'{签署日期}',
'{患者签名}',
'{与患者关系}',
]
// 占位符在签署时按字段键取 HIS 与患者就诊信息自动填充,其中签名是独立控件类型:
// 签字后签名图作为该字段取值落回文书原本的签名位,不需要再替换文字。
// 目录见 utils/signing-fields 的 SIGNING_FIELD_PRESETS,与签署端渲染共用同一份。
const form = reactive<TemplateEditorForm>({
templateCode: '',
@@ -108,6 +107,44 @@ function insertContent(text: string) {
paneRef.value?.insertText(text)
}
function insertField(field: (typeof SIGNING_FIELD_PRESETS)[number]) {
insertContent(buildFieldToken(field))
}
/** 已插入的填写项:让科室在保存前确认签名位、日期位有没有漏 */
const detectedFields = computed(() => collectFieldDefs(valueHtml.value))
const placeholderHints = computed(() => {
const hints: string[] = []
const ids = new Set(detectedFields.value.map((field) => field.id))
if (!ids.has('signature')) {
hints.push('文书中没有签名占位符,签署后签名无处落位,建议插入「患者签名」。')
}
if (!ids.has('signedDate')) {
hints.push('文书中没有签署日期占位符,建议插入「签署日期」以便留痕。')
}
const duplicates = findDuplicateFields(valueHtml.value)
if (duplicates.length) {
hints.push(
`以下占位符出现多次:${duplicates.map((field) => field.label).join('、')}(同一字段共用一份取值)。`,
)
}
return hints
})
const kindLabels: Record<string, string> = {
text: '文本',
date: '日期',
choice: '单选',
checkbox: '勾选',
signature: '签名',
}
async function handleDocxChange(event: Event) {
const input = event.target as HTMLInputElement
const file = input.files?.[0]
@@ -163,12 +200,7 @@ function save() {
<template>
<Teleport to="body">
<div
v-if="visible"
class="dialog-mask"
role="presentation"
@click.self="emit('close')"
>
<div v-if="visible" class="dialog-mask" role="presentation" @click.self="emit('close')">
<section
class="word-editor"
role="dialog"
@@ -206,7 +238,11 @@ function save() {
<span>所属科室</span>
<select v-model="form.departmentId">
<option value="">全院通用</option>
<option v-for="department in availableDepartments" :key="department.id" :value="department.id">
<option
v-for="department in availableDepartments"
:key="department.id"
:value="department.id"
>
{{ department.name }}
</option>
</select>
@@ -230,13 +266,15 @@ function save() {
<span>插入签署占位符</span>
<div class="placeholder-row">
<button
v-for="item in placeholders"
:key="item"
v-for="field in SIGNING_FIELD_PRESETS"
:key="field.id"
type="button"
class="placeholder-chip"
@click="insertContent(item)"
:class="{ 'is-signature': field.kind === 'signature' }"
:title="`${kindLabels[field.kind]}字段;签署时按患者与就诊信息自动填充${field.required ? ',必填' : ''}`"
@click="insertField(field)"
>
{{ item }}
{{ field.label }}{{ field.required ? ' *' : '' }}
</button>
</div>
</div>
@@ -257,6 +295,26 @@ function save() {
</div>
</div>
<div class="placeholder-check">
<div class="placeholder-check__head">
<strong>占位符检查</strong>
<span>{{ detectedFields.length }} 个填写项</span>
</div>
<div v-if="detectedFields.length" class="placeholder-check__list">
<span v-for="field in detectedFields" :key="field.id" class="placeholder-check__item">
{{ field.label }}
<em>{{ kindLabels[field.kind] }}</em>
<i v-if="field.required">必填</i>
</span>
</div>
<p v-else class="placeholder-check__empty">
文书中还没有任何填写项,签署时不会采集患者填写内容。
</p>
<ul v-if="placeholderHints.length" class="placeholder-check__hints">
<li v-for="hint in placeholderHints" :key="hint">{{ hint }}</li>
</ul>
</div>
<div class="editor-shell">
<div class="docx-bar">
<button
@@ -277,7 +335,8 @@ function save() {
/>
<span v-if="importMessage" class="docx-message">{{ importMessage }}</span>
<span v-else class="docx-hint">
建议从 Word/WPS 中全选复制文书后直接粘贴到下方编辑区;需要患者勾选的内容可点上方「插入勾选框」按钮插入(光标停在表格单元格里就插到该单元格),或用表头含「勾选」列的表格承载。
建议从 Word/WPS
中全选复制文书后直接粘贴到下方编辑区;需要患者勾选的内容可点上方「插入勾选框」按钮插入(光标停在表格单元格里就插到该单元格),或用表头含「勾选」列的表格承载。
</span>
</div>
<div class="editor-area">
@@ -435,6 +494,77 @@ function save() {
filter: brightness(0.95);
}
/* 签名类占位符用不同颜色区分:它决定签名落位,是文书里最不能漏的一项 */
.placeholder-chip.is-signature {
color: #a96900;
background: #fff4e2;
}
.placeholder-check {
padding: 10px 18px 0;
}
.placeholder-check__head {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 7px;
}
.placeholder-check__head strong {
color: var(--ink);
font-size: 11.5px;
}
.placeholder-check__head span {
color: var(--mut);
font-size: 11px;
}
.placeholder-check__list {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.placeholder-check__item {
display: inline-flex;
gap: 5px;
align-items: center;
padding: 2px 8px;
color: var(--ink);
font-size: 11.5px;
background: #f4f8fa;
border: 1px solid var(--line);
border-radius: 12px;
}
.placeholder-check__item em {
color: var(--brand);
font-size: 10.5px;
font-style: normal;
}
.placeholder-check__item i {
color: var(--err);
font-size: 10.5px;
font-style: normal;
}
.placeholder-check__empty {
margin: 0;
color: var(--mut);
font-size: 11.5px;
}
.placeholder-check__hints {
padding-left: 16px;
margin: 8px 0 0;
color: var(--warn);
font-size: 11.5px;
line-height: 1.7;
}
.editor-shell {
display: flex;
flex-direction: column;