feat(签署文书): 统一签署态文书样式并精简演示种子为单份麻精同意书

This commit is contained in:
yelan
2026-09-24 17:08:12 +08:00
parent 9a6ac93e21
commit 5ea79178cb
30 changed files with 3787 additions and 2878 deletions
@@ -3,14 +3,17 @@ import { computed, nextTick, ref, watch } from 'vue'
import {
buildSigningDocumentHtml,
collectSelectedItemsFromRoot,
describeFieldProgress,
OPTION_TICK_SELECTOR,
readAnswersFromRoot,
type SigningDocumentData,
toggleOptionAt,
type SigningFieldProgress,
} from '@/utils/signing-document'
import { readStatusLabel, readStatusTone } from '../mock'
import type { DocumentTemplate } from '../types'
import { canInitiateSigning, readLibraryStatus, readStatusTone } from '../library'
import { PREVIEW_PATIENT as previewPatient } from '../sample-patient'
import type { DocumentLibraryStatus, DocumentTemplate } from '../types'
const props = defineProps<{
visible: boolean
@@ -25,22 +28,8 @@ const emit = defineEmits<{
}>()
/**
* 签署预览用的示例患者快照(脱敏演示数据)。
*
* 创建文书时还没有真实签署任务,用固定示例值让科室看到占位符被填充后的版式;
* 真实签署仍以任务创建时的患者/就诊快照为准。
* 这份数据只服务于「签署预览」这个演示页签,因此留在组件内,
* 不再从页面 mock.ts 导出——那会让"演示数据"渗透到文书库的其它部分。
* 签署预览用的示例患者快照见 ../sample-patient(编辑器弹窗的「签署预览」共用同一份)。
*/
const previewPatient: SigningDocumentData = {
patientName: '李某某',
sex: '男',
age: 46,
visitNo: 'V20260914001',
visitDate: '2026-09-14',
phone: '138****5678',
signedAt: '',
}
/** 预览页签:「文书内容」看原始定稿,「签署预览」看占位符填充与勾选交互化后的样子 */
const activeTab = ref<'content' | 'signing'>('content')
@@ -81,17 +70,22 @@ watch(
},
)
const statusLabel = computed(() => (props.template ? readStatusLabel(props.template) : ''))
/** 状态口径统一走 library.ts:卡片、页签、预览三处必须是同一档 */
const libraryStatus = computed<DocumentLibraryStatus>(() =>
props.template ? readLibraryStatus(props.template) : '草稿',
)
/** 徽标配色与文书卡片同口径:草稿/待审核/已驳回/已发布/已归档各不相同 */
const statusTone = computed(() => (props.template ? readStatusTone(props.template) : 'draft'))
const statusLabel = computed(() => libraryStatus.value)
/** 徽标配色与文书卡片同口径(见 library.ts 的 STATUS_TONES) */
const statusTone = computed(() => readStatusTone(libraryStatus.value))
/**
* 与文书卡片保持同一口径:只有已发布(后端 APPROVED / PUBLISHED)才能发起签署。
* 不放行 review —— 它同时承载待审核与已驳回,两者都未定稿。
* 与文书卡片保持同一口径:只有「启用中」的文书能发起签署。
* 「需复审」不放行——它同时承载待审核与已驳回,两者都未定稿。
*/
const canInitiate = computed(
() => Boolean(props.template?.contentHtml) && props.template?.status === 'published',
() => Boolean(props.template?.contentHtml) && canInitiateSigning(libraryStatus.value),
)
// 预置文书的 contentHtml 是完整 HTML 文档(自带版式样式);
@@ -160,9 +154,27 @@ function syncPreviewFields() {
const answers = readAnswersFromRoot(body)
signingProgress.value = describeFieldProgress(body, answers)
signingSelection.value = Array.from(
body.querySelectorAll<HTMLInputElement>('input.sign-doc-checkbox:checked'),
).map((input) => input.getAttribute('data-sign-item') ?? '')
signingSelection.value = collectSelectedItemsFromRoot(body)
}
/**
* 预览里的 ☑ / ☐ 就地切换。
* 选项渲染成文本(原型口径),不会触发 change 事件,必须自己接 click;
* 切换判据在 utils/signing-document 的 toggleOptionAt 里,与签署端共用一份。
*/
function handlePreviewClick(event: MouseEvent) {
const body = signingBodyRef.value
const target = event.target
if (!body || !(target instanceof Element)) {
return
}
const option = target.closest<HTMLElement>(OPTION_TICK_SELECTOR)
if (option && toggleOptionAt(body, option)) {
syncPreviewFields()
}
}
</script>
@@ -224,7 +236,7 @@ function syncPreviewFields() {
<template v-else-if="template.contentHtml && activeTab === 'signing'">
<p class="preview-note">
按签署时效果渲染:占位符已用示例患者「{{ previewPatient.patientName }}」填充,
表头含「勾选」的表格已转为勾选框,可直接勾选确认交互效果(预览不保存勾选结果)。
空白处是待补录的下划线、☑ / ☐ 可直接点击确认交互效果(预览不保存结果)。
</p>
<!-- eslint-disable vue/no-v-html -- 内容为本系统定稿的文书 HTML(编辑器产出、仅医务人员可改),非任意用户输入 -->
<div
@@ -232,6 +244,7 @@ function syncPreviewFields() {
class="signing-preview"
@change="syncPreviewFields"
@input="syncPreviewFields"
@click="handlePreviewClick"
v-html="signingPreviewHtml"
/>
<!-- eslint-enable vue/no-v-html -->
@@ -382,9 +395,10 @@ function syncPreviewFields() {
background: var(--brand-l);
}
/* 状态徽标配色:与文书卡片同一套色调,草稿/待审核/已驳回不再一律显示成绿色 */
/* 状态徽标配色:与文书卡片同一套色调(见 library.ts 的 STATUS_TONES) */
.status-badge.tone-draft,
.status-badge.tone-archived {
.status-badge.tone-archived,
.status-badge.tone-stopped {
color: #8a9aa3;
background: #eceef0;
}
@@ -394,12 +408,7 @@ function syncPreviewFields() {
background: var(--warn-l);
}
.status-badge.tone-rejected {
color: var(--err);
background: var(--err-l);
}
.status-badge.tone-published {
.status-badge.tone-enabled {
color: var(--ok);
background: var(--ok-l);
}
@@ -503,37 +512,6 @@ function syncPreviewFields() {
max-width: 100%;
}
/* 固定页眉:患者基本信息由 HIS 自动带出,与正文之间用一条横线分隔。
四项要在一行内放下,取值下划线的宽度由字段定义给出(内联 min-width),样式里不再写死 */
.signing-preview :deep(.sign-doc-header) {
display: flex;
flex-wrap: wrap;
gap: 10px 18px;
padding-bottom: 10px;
margin-bottom: 16px;
border-bottom: 1.5px solid #52646e;
}
.signing-preview :deep(.sign-doc-header__item) {
display: inline-flex;
gap: 3px;
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) {
padding: 0 5px 1px;
color: #1c2b33;
font-size: 12.5px;
text-align: center;
border-bottom: 1px solid #52646e;
}
.signing-preview :deep(table) {
width: 100%;
margin: 8px 0;
@@ -557,123 +535,9 @@ function syncPreviewFields() {
margin: 0;
}
.signing-preview :deep(.sign-doc-checkbox) {
width: 15px;
height: 15px;
accent-color: var(--brand);
cursor: pointer;
}
/* 正文行内选项(□否 □是):用 label 包住方框与选项文字,点文字也能选中 */
.signing-preview :deep(.sign-doc-option) {
display: inline-flex;
gap: 3px;
align-items: center;
margin-right: 14px;
white-space: nowrap;
cursor: pointer;
}
.signing-preview :deep(.sign-doc-signature) {
max-width: 150px;
max-height: 52px;
vertical-align: middle;
}
/* 填写项:签署预览里按签署时的控件外观渲染,样式与签署端保持一致 */
.signing-preview :deep(.sign-field) {
display: inline-flex;
gap: 4px;
align-items: center;
padding: 2px 7px;
margin: 0 2px;
vertical-align: middle;
background: #f7fbfd;
border: 1px solid #d4e3e8;
border-radius: 5px;
}
.signing-preview :deep(.sign-field__label) {
flex-shrink: 0;
color: #587786;
font-size: 11.5px;
}
.signing-preview :deep(.sign-field__required) {
margin-left: 2px;
color: var(--err);
}
.signing-preview :deep(.sign-field__control) {
display: inline-flex;
gap: 8px;
align-items: center;
}
.signing-preview :deep(.sign-field__input) {
width: 9em;
min-width: 0;
padding: 1px 5px;
color: #1c2b33;
font: inherit;
font-size: 12.5px;
background: #fff;
border: 1px solid #cfe0e6;
border-radius: 4px;
}
.signing-preview :deep(.sign-field__input:focus) {
border-color: var(--brand);
outline: none;
}
.signing-preview :deep(.sign-field__choices) {
display: inline-flex;
flex-wrap: wrap;
gap: 10px;
}
.signing-preview :deep(.sign-field__radio),
.signing-preview :deep(.sign-field__checkbox) {
width: 15px;
height: 15px;
accent-color: var(--brand);
}
.signing-preview :deep(.sign-field__signature-empty) {
padding: 12px 18px;
color: #a96900;
font-size: 11px;
background: #fff9ec;
border: 1px dashed #f0a21a;
border-radius: 4px;
}
.signing-preview :deep(.sign-field--signature) {
padding: 3px 8px;
}
.signing-preview :deep(.sign-field--signature .sign-field__control) {
min-width: 150px;
justify-content: center;
}
.signing-preview :deep(.sign-field--abnormal) {
background: #fdf0f0;
border-color: #efb9bb;
}
.signing-preview :deep(.sign-field--abnormal .sign-field__label) {
color: #a42e34;
}
.signing-preview :deep(.sign-field__badge) {
padding: 1px 6px;
color: #fff;
font-size: 10px;
background: var(--err);
border-radius: 8px;
}
/* 填写项胶囊、选项 ☑ / ☐、系统页眉的样式在 styles/signing-document.css(全局):
同一份签署态文书会在文书库预览、签署工作台与编辑器三处出现,各写一份必然漂移。
这里只保留预览容器自己的版式。 */
.signing-selection {
padding: 8px 12px;