Files
xh-medical-sign-web/clinical-web/src/components/signing/SigningDocumentInteractive.vue
T

257 lines
7.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>
<!-- 纸桌:灰底 + 内滚,纸(一页 A4)居中摆在上面。
纸不能直接当 flex 子项 —— `flex: 1` 会把高度交给布局,
`aspect-ratio` 在主轴上不生效,A4 比例就没了 -->
<div class="sign-doc-body">
<DocumentPaper
ref="paperRef"
: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)"
/>
</div>
<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;
}
/* 纸桌:灰底 + 内滚,纸(一页 A4)居中摆在上面。
纸自己的 A4 最小高度与页边距走统一 A4 规格(.doc-paper--a4,见 utils/document-paper.ts),
这里只留「桌子」—— 四周留一圈灰边,纸才不会顶到卡片边缘。
`container-type` 是纸的 cqw 下限需要的容器查询上下文。 */
.sign-doc-body {
flex: 1;
min-height: 0;
padding: 16px 20px 20px;
overflow: auto;
background: var(--bg);
container-type: inline-size;
}
.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>