预览原先在 5 个地方各写一遍(文书库预览、编辑器签署预览、发起签署弹窗预览、 工作台在线预览、工作台文书区),基础排版已经分叉:表格边框 #52646e 与 #c8d7dd 两套色、单元格内边距 5px 7px 与 5px 9px 两个值、只有两处带宋体。 原型本身是「一处渲染(.paper + fillDocHtml)、两种外框」,这次按那个形状收口。 新增 - utils/document-paper.ts:基础排版的**唯一一份** CSS(p / h1-h4 / 表格 / 宋体)。 运行期幂等注入 head —— 因为同一份纸有两条渲染通道:v-html(吃全局样式)与 iframe srcdoc(隔离模板自带 style,只能吃字符串),各写一份必然漂移。 - components/document/DocumentPaper.vue:唯一的纸渲染器。 signing / raw 两模式、interactive、取值还原、☑☐ 就地切换、签名位请求采集、 填写项与勾选上报、getDocumentElement()(供导出 PDF)。 - components/document/DocumentPreviewDialog.vue:唯一的预览外框。 标题/副标题/徽标、可选页签、加载与错误态、纸舞台(浅色卡片 / 深灰 A4)、 #footer 与 #empty 插槽。 - styles/document-preview.css:页脚按钮等**插槽内容**要用的类。 插槽节点带的是父组件 scope id,弹窗的 scoped 样式命中不了 —— 与 signing-document.css 同一类问题,同一套解法。 迁移 - 文书库预览、编辑器签署预览、发起签署弹窗预览、工作台在线预览全部改走统一组件; SigningDocumentInteractive 的纸换成 DocumentPaper,只留任务外壳 (横幅 / 作废章 / 填写项进度)。 - 删除四处基础排版副本;SimSun 与 :deep(table) 现在各只剩一处。 行为保持 - 文书库预览仍是两个页签(文书内容走 iframe 隔离、签署预览走 v-html), 没有正文时仍给占位版式(占位符写成 {患者姓名} 原始形态,不预填示例值)。 - 工作台预览的深灰 A4 舞台、页脚说明与两个出口按钮不变。 - 作废章原先是纸的兄弟节点、DOM 顺序在前,纸改成自带白底后会被盖住 —— 给章补 z-index,并在探针里加命中测试锁死。 验证 - 新增 probe-doc-paper.cjs:31/31 PASS。核心是**计算样式**断言 (边框色 / 内边距 / 宋体 / border-collapse),并断言文书库预览与编辑器预览 两份纸的样式**逐项一致** —— 只看 DOM 存在证明不了"只有一份排版"。 - 回归:probe-new-task 104/104、probe-workbench-lib 46/46(新增作废章命中测试)、 probe-dept-confirm 36/36、probe-method-domain 14/14、probe-report-analytics 92/92。 - vue-tsc 与 eslint 0 错。 净减 888 行(309 增 / 1197 删)。
249 lines
7.4 KiB
Vue
249 lines
7.4 KiB
Vue
<script setup lang="ts">
|
||
/**
|
||
* 签署工作台的任务态文书外壳。
|
||
*
|
||
* 它只负责**任务专属的那一圈**:顶部横幅(按哪个版本渲染 / 当前签署状态)、
|
||
* 作废章、底部填写项进度。纸本身(排版、占位符回填、勾选交互、签名位采集)
|
||
* 在 components/document/DocumentPaper.vue —— 那是全项目唯一一处,
|
||
* 工作台看到的版式必须与预览、与导出 PDF 完全一致。
|
||
*/
|
||
import { computed, ref } from 'vue'
|
||
|
||
import type { SigningAnswers, SigningTaskRecord } from '@/api/workbench/types'
|
||
import DocumentPaper from '@/components/document/DocumentPaper.vue'
|
||
import type {
|
||
SigningDocumentData,
|
||
SigningFieldProgress,
|
||
SigningSignatureRequest,
|
||
} from '@/utils/signing-document'
|
||
|
||
const props = withDefaults(
|
||
defineProps<{
|
||
task: SigningTaskRecord
|
||
interactive?: boolean
|
||
/**
|
||
* 在文书里点击签名位采集到的签名图(key 是字段键,如 doctor)。
|
||
*
|
||
* 不走 task:task 是服务端快照,这里装的是「本次会话刚采到、还没提交」的取值。
|
||
* 单独一份也让「整篇重渲染」不会把刚采的签名冲掉。
|
||
*/
|
||
signatureInjections?: SigningAnswers
|
||
}>(),
|
||
{ interactive: false, signatureInjections: () => ({}) },
|
||
)
|
||
|
||
const emit = defineEmits<{
|
||
'selection-change': [items: string[]]
|
||
'field-change': [answers: SigningAnswers, progress: SigningFieldProgress]
|
||
/** 请求采集某个签名位,由签署页打开签字板并把结果回灌到 signatureInjections */
|
||
'signature-request': [request: SigningSignatureRequest]
|
||
}>()
|
||
|
||
const paperRef = ref<InstanceType<typeof DocumentPaper> | null>(null)
|
||
|
||
const progress = ref<SigningFieldProgress>({
|
||
total: 0,
|
||
filled: 0,
|
||
missingRequired: [],
|
||
abnormal: [],
|
||
pendingSignatures: [],
|
||
})
|
||
|
||
/**
|
||
* 只挑渲染真正需要的字段显式映射,不直接透传整个任务对象:
|
||
* 渲染数据契约与任务记录是两回事,哪天字段改名也不会静默丢取值。
|
||
*/
|
||
const documentData = computed<SigningDocumentData>(() => ({
|
||
patientName: props.task.patientName,
|
||
sex: props.task.sex,
|
||
age: props.task.age,
|
||
visitNo: props.task.visitNo,
|
||
visitDate: props.task.visitDate,
|
||
signedAt: props.task.signedAt,
|
||
signatureDataUrl: props.task.signatureDataUrl,
|
||
selectedItems: props.task.selectedItems,
|
||
fieldAnswers: props.task.fieldAnswers,
|
||
}))
|
||
|
||
/** 文书里的填写项数量(含只读展示),用于提示「这份文书有几个填写项」 */
|
||
const fieldTotal = computed(() => progress.value.total)
|
||
|
||
/** 签名/状态文案:作废要单独说清楚,不能被当成「待患者签署」 */
|
||
const signatureStateLabel = computed(() => {
|
||
if (props.task.status === 'void') {
|
||
return '已作废'
|
||
}
|
||
|
||
if (props.task.signatureDataUrl) {
|
||
return '已采集签名'
|
||
}
|
||
|
||
return props.task.status === 'signed' || props.task.status === 'confirming'
|
||
? '已签署'
|
||
: '待患者签署'
|
||
})
|
||
|
||
/** 纸把填写项与勾选结果上报上来,这里转给签署页,同时留一份给页脚用 */
|
||
function handleFieldChange(answers: SigningAnswers, next: SigningFieldProgress) {
|
||
progress.value = next
|
||
emit('field-change', answers, next)
|
||
}
|
||
|
||
/** 供「导出 PDF」使用:拿到已渲染的文书容器(含固定页眉与签署态取值) */
|
||
function getDocumentElement() {
|
||
return paperRef.value?.getDocumentElement() ?? null
|
||
}
|
||
|
||
defineExpose({ getDocumentElement })
|
||
</script>
|
||
|
||
<template>
|
||
<article class="sign-doc">
|
||
<div class="sign-doc-banner">
|
||
<span>已按任务固化的文书版本渲染(填写项已按患者信息预填)</span>
|
||
<span class="sign-doc-state" :class="{ 'is-void': task.status === 'void' }">
|
||
{{ signatureStateLabel }}
|
||
</span>
|
||
</div>
|
||
|
||
<div v-if="task.status === 'void'" class="void-stamp">已 作 废</div>
|
||
|
||
<DocumentPaper
|
||
ref="paperRef"
|
||
class="sign-doc-body"
|
||
:content-html="task.documentContentHtml ?? ''"
|
||
:data="documentData"
|
||
:interactive="interactive"
|
||
:selected-items="task.selectedItems"
|
||
:field-answers="task.fieldAnswers"
|
||
:signature-data-url="task.signatureDataUrl"
|
||
:signature-injections="signatureInjections"
|
||
@selection-change="emit('selection-change', $event)"
|
||
@field-change="handleFieldChange"
|
||
@signature-request="emit('signature-request', $event)"
|
||
/>
|
||
|
||
<footer v-if="interactive" class="sign-doc-footer">
|
||
<span>
|
||
填写项 <strong>{{ progress.filled }}</strong> / {{ fieldTotal }} 项已填
|
||
<template v-if="progress.abnormal.length">
|
||
・异常项:<strong class="is-abnormal">{{ progress.abnormal.join('、') }}</strong>
|
||
</template>
|
||
</span>
|
||
<span v-if="progress.missingRequired.length" class="is-missing">
|
||
必填未完成:{{ progress.missingRequired.join('、') }},请补充后再采集签名。
|
||
</span>
|
||
<span v-else-if="progress.pendingSignatures.length" class="is-pending">
|
||
还有签名位待采集:<strong>{{ progress.pendingSignatures.join('、') }}</strong
|
||
>,可直接点击文中签名位完成。
|
||
</span>
|
||
<span v-else
|
||
>完成填写后通过「手写板签署 / 线上签署」采集签名,填写项与勾选结果随签署证据一并提交。</span
|
||
>
|
||
</footer>
|
||
<footer v-else-if="fieldTotal" class="sign-doc-footer">
|
||
<span>
|
||
填写项 <strong>{{ progress.filled }}</strong> / {{ fieldTotal }} 项已填,取值已随签署固化。
|
||
</span>
|
||
</footer>
|
||
</article>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.sign-doc {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 480px;
|
||
background: #fff;
|
||
border-radius: var(--r);
|
||
box-shadow: var(--sh);
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 作废章压在文书上,位置与存量回退纸样(SigningDocumentPreview)保持一致 */
|
||
.void-stamp {
|
||
position: absolute;
|
||
z-index: 2;
|
||
top: 52px;
|
||
right: 32px;
|
||
padding: 4px 12px;
|
||
color: rgb(208 83 79 / 70%);
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
letter-spacing: 4px;
|
||
pointer-events: none;
|
||
border: 2.5px solid rgb(208 83 79 / 55%);
|
||
border-radius: 8px;
|
||
transform: rotate(-14deg);
|
||
}
|
||
|
||
.sign-doc-banner {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 9px 16px;
|
||
color: var(--brand);
|
||
font-size: 11.5px;
|
||
background: var(--brand-l);
|
||
}
|
||
|
||
.sign-doc-state {
|
||
padding: 2px 9px;
|
||
color: #fff;
|
||
font-weight: 700;
|
||
background: var(--brand);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
.sign-doc-state.is-void {
|
||
background: #8a9aa3;
|
||
}
|
||
|
||
/* 纸的盒子:DocumentPaper 只给排版与白底(variant=plain),
|
||
内边距与滚动由这里给。类名通过 fallthrough 落到纸的根元素上。 */
|
||
.sign-doc-body {
|
||
flex: 1;
|
||
min-height: 0;
|
||
padding: 26px 32px 30px;
|
||
overflow: auto;
|
||
}
|
||
|
||
.sign-doc-footer {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
padding: 10px 16px;
|
||
color: var(--mut);
|
||
font-size: 12px;
|
||
background: #f8fbfc;
|
||
border-top: 1px solid var(--line);
|
||
}
|
||
|
||
.sign-doc-footer strong {
|
||
color: var(--brand);
|
||
}
|
||
|
||
.sign-doc-footer .is-abnormal {
|
||
color: var(--err);
|
||
}
|
||
|
||
.sign-doc-footer .is-missing {
|
||
color: var(--err);
|
||
}
|
||
|
||
.sign-doc-footer .is-pending {
|
||
color: #a96900;
|
||
}
|
||
|
||
.sign-doc-footer .is-pending strong {
|
||
color: #a96900;
|
||
}
|
||
|
||
/* 正文字号、表格边框、宋体等基础排版在 components/document/DocumentPaper.vue 的
|
||
DOCUMENT_PAPER_CSS 里(全项目唯一一份)。填写项胶囊、选项 ☑☐、系统页眉在
|
||
styles/signing-document.css。这里只保留"这份文书外壳"自己的东西。 */
|
||
</style>
|