refactor(文书预览): 移除文书内容页签与 raw 渲染模式,统一走签署态预览
This commit is contained in:
@@ -30,18 +30,12 @@ import {
|
||||
type SigningFieldProgress,
|
||||
type SigningSignatureRequest,
|
||||
} from '@/utils/signing-document'
|
||||
import { buildPaperSrcDoc, ensureDocumentPaperStyle } from '@/utils/document-paper'
|
||||
import { ensureDocumentPaperStyle } from '@/utils/document-paper'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
/** 文书正文(模板定稿 / 任务固化快照) */
|
||||
contentHtml: string
|
||||
/**
|
||||
* 渲染模式。
|
||||
* - `signing`(默认):按签署态渲染(占位符回填、勾选项交互化、签名域)
|
||||
* - `raw`:原样展示定稿(iframe 隔离模板自带的 `<style>`),仅文书库的「文书内容」页签用
|
||||
*/
|
||||
mode?: 'signing' | 'raw'
|
||||
/** 回填上下文:患者信息 / 签名 / 勾选 / 已填取值 */
|
||||
data?: SigningDocumentData
|
||||
/** 填写项可否改动、签名位可否点击采集 */
|
||||
@@ -68,7 +62,6 @@ const props = withDefaults(
|
||||
variant?: 'plain' | 'card'
|
||||
}>(),
|
||||
{
|
||||
mode: 'signing',
|
||||
data: () => ({}),
|
||||
interactive: false,
|
||||
signatureActionable: undefined,
|
||||
@@ -110,9 +103,6 @@ const processedHtml = computed(() =>
|
||||
}),
|
||||
)
|
||||
|
||||
/** raw 模式的 iframe 文档(自带完整版式的正文原样返回) */
|
||||
const rawSrcDoc = computed(() => buildPaperSrcDoc(props.contentHtml))
|
||||
|
||||
/**
|
||||
* 从渲染后的 DOM 读回填写项取值与勾选清单并上报。
|
||||
* 生成 HTML 时已把取值写成属性/文本,这里再按取值同步一次 DOM 状态,
|
||||
@@ -288,17 +278,8 @@ defineExpose({ getDocumentElement })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- raw 模式:模板定稿可能自带 <style>,必须隔离,否则会污染整个应用 -->
|
||||
<iframe
|
||||
v-if="mode === 'raw'"
|
||||
class="doc-paper-frame"
|
||||
:srcdoc="rawSrcDoc"
|
||||
sandbox=""
|
||||
title="文书原始版式预览"
|
||||
/>
|
||||
|
||||
<!-- eslint-disable vue/no-v-html -- 内容为本系统定稿的文书 HTML(编辑器产出、仅医务人员可改),非任意用户输入 -->
|
||||
<article v-else class="doc-paper" :class="{ 'doc-paper--card': variant === 'card' }">
|
||||
<article class="doc-paper" :class="{ 'doc-paper--card': variant === 'card' }">
|
||||
<div
|
||||
ref="containerRef"
|
||||
class="doc-paper__body"
|
||||
@@ -314,7 +295,7 @@ defineExpose({ getDocumentElement })
|
||||
|
||||
<style scoped>
|
||||
/* 基础排版(p / h2 / h3 / table / th / td / img)在 utils/document-paper.ts 的
|
||||
DOCUMENT_PAPER_CSS 里,运行期注入 head —— 那里是唯一一份,v-html 与 iframe 共用。
|
||||
DOCUMENT_PAPER_CSS 里,运行期注入 head —— 那里是唯一一份。
|
||||
这里只保留容器自己的盒子。 */
|
||||
|
||||
.doc-paper {
|
||||
@@ -331,13 +312,4 @@ defineExpose({ getDocumentElement })
|
||||
.doc-paper__body {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.doc-paper-frame {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 560px;
|
||||
background: #fff;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -35,8 +35,6 @@ const props = withDefaults(
|
||||
activeTab?: string
|
||||
/** 正文(模板定稿 / 任务固化快照) */
|
||||
contentHtml: string
|
||||
/** 渲染模式,透传给 DocumentPaper */
|
||||
mode?: 'signing' | 'raw'
|
||||
data?: SigningDocumentData
|
||||
interactive?: boolean
|
||||
signatureActionable?: boolean
|
||||
@@ -71,7 +69,6 @@ const props = withDefaults(
|
||||
badges: () => [],
|
||||
tabs: undefined,
|
||||
activeTab: '',
|
||||
mode: 'signing',
|
||||
data: () => ({}),
|
||||
interactive: false,
|
||||
signatureActionable: undefined,
|
||||
@@ -168,7 +165,6 @@ defineExpose({ getDocumentElement })
|
||||
v-if="hasPaper"
|
||||
ref="paperRef"
|
||||
:content-html="contentHtml"
|
||||
:mode="mode"
|
||||
:data="data"
|
||||
:interactive="interactive"
|
||||
:signature-actionable="signatureActionable"
|
||||
@@ -362,14 +358,6 @@ defineExpose({ getDocumentElement })
|
||||
box-shadow: 0 6px 22px rgb(0 0 0 / 45%);
|
||||
}
|
||||
|
||||
/* 深灰底上纸要自己撑满高度,否则短文书会缩成一条 */
|
||||
.preview-body--stage :deep(.doc-paper-frame) {
|
||||
width: 640px;
|
||||
max-width: 100%;
|
||||
height: 905px;
|
||||
box-shadow: 0 6px 22px rgb(0 0 0 / 45%);
|
||||
}
|
||||
|
||||
.preview-state {
|
||||
display: grid;
|
||||
min-height: 420px;
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
/**
|
||||
* 文书纸的基础排版(全项目唯一一份)。
|
||||
*
|
||||
* 为什么单独成文件、还要在运行期注入:同一份「文书纸」有**两种渲染通道**——
|
||||
* 为什么单独成文件、还要在运行期注入:同一份「文书纸」原先有**两种渲染通道**——
|
||||
* ① v-html 直接挂进页面(签署工作台、各类预览)→ 只能吃全局样式;
|
||||
* ② iframe srcdoc(文书库的「文书内容」页签,隔离模板自带的 `<style>`)→ 只能吃字符串。
|
||||
* 各写一份必然漂移:改版前实测已经有 5 份副本,表格边框在 `#52646e` 与 `#c8d7dd` 之间
|
||||
* 分叉、单元格内边距 `5px 7px` 与 `5px 9px` 不一致、只有两处带宋体。
|
||||
* 所以这里只维护一份字符串,两条通道共用。
|
||||
* 所以这里只维护一份字符串。
|
||||
*
|
||||
* 选择器一律以 `.doc-paper` 开头:v-html 通道把它挂在容器 div 上,
|
||||
* iframe 通道挂在 `<body>` 上,两边命中同一批规则。
|
||||
* **2026-09-27 起只剩 ① 一条通道**:文书库预览去掉了「文书内容」页签,
|
||||
* iframe/srcdoc 那条路连同 `DocumentPaper` 的 `raw` 模式一起删除。
|
||||
* 仍然保留「运行期注入」而不是普通 css 文件,是因为注入的时机可控
|
||||
* (setup 期,避免首帧"纸没排版"的闪烁),且规则文本同时供导出 PDF 之类的场景复用。
|
||||
*
|
||||
* 选择器一律以 `.doc-paper` 开头,挂在容器 div 上。
|
||||
*
|
||||
* 注意:填写项胶囊 / 选项 ☑☐ / 系统页眉的样式**不在这里**,
|
||||
* 它们与「签署态」语义强相关,统一在 styles/signing-document.css。
|
||||
@@ -100,8 +104,9 @@ const STYLE_TAG_ID = 'document-paper-base-style'
|
||||
/**
|
||||
* 把基础排版注入 `document.head`(幂等)。
|
||||
*
|
||||
* 用运行期注入而不是普通的全局 css 文件,是为了让 v-html 与 iframe 两条通道
|
||||
* 吃**同一份**规则文本;写成 css 文件后 iframe 拿不到(srcdoc 无法引用应用样式表)。
|
||||
* 用运行期注入而不是普通的全局 css 文件,是为了控制注入时机
|
||||
* (setup 期而不是 onMounted,避免首帧"纸没排版"的闪烁),
|
||||
* 并且让规则文本有唯一一份来源。
|
||||
*/
|
||||
export function ensureDocumentPaperStyle(): void {
|
||||
if (typeof document === 'undefined') {
|
||||
@@ -117,21 +122,3 @@ export function ensureDocumentPaperStyle(): void {
|
||||
style.textContent = DOCUMENT_PAPER_CSS
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
|
||||
/**
|
||||
* 把一段正文包成可直接塞进 iframe 的完整文档。
|
||||
*
|
||||
* 正文本身已经是完整 HTML 文档(预置模板自带版式)时原样返回——
|
||||
* 那种情况下再套一层 body 会破坏它自己的 `html/body` 样式。
|
||||
*/
|
||||
export function buildPaperSrcDoc(contentHtml: string): string {
|
||||
if (/^\s*<!doctype html|^\s*<html[\s>]/i.test(contentHtml)) {
|
||||
return contentHtml
|
||||
}
|
||||
|
||||
return [
|
||||
'<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8" />',
|
||||
`<style>${DOCUMENT_PAPER_CSS}</style></head>`,
|
||||
`<body class="doc-paper">${contentHtml}</body></html>`,
|
||||
].join('')
|
||||
}
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
/**
|
||||
* 文书库的模板预览。
|
||||
*
|
||||
* 它只是**统一预览弹窗**在文书库这一处的配置:两个页签(文书内容 / 签署预览)+
|
||||
* 「用此模板发起签署」出口。纸的渲染、外框、页脚按钮都在 components/document/ 里,
|
||||
* 它只是**统一预览弹窗**在文书库这一处的配置:签署预览 + 「用此模板发起签署」出口。
|
||||
* 纸的渲染、外框、页脚按钮都在 components/document/ 里,
|
||||
* 这里不再实现任何渲染逻辑 —— 改版前这一处自己抄了一份基础排版(表格边框、字号、宋体),
|
||||
* 与签署工作台那份已经分叉。
|
||||
*
|
||||
* 两个页签的区别只在渲染模式:
|
||||
* - 文书内容:`mode="raw"`,原样展示定稿(iframe 隔离模板自带的 `<style>`);
|
||||
* - 签署预览:`mode="signing"`,按签署时效果渲染,占位符用示例患者填充、☑/☐ 可直接点。
|
||||
* **只有签署预览一种形态**(2026-09-27 去掉了原来的「文书内容」页签):
|
||||
* 按签署时效果渲染,占位符用示例患者填充、☑ / ☐ 可直接点。
|
||||
* 原来的「文书内容」走 `DocumentPaper` 的 `raw` 模式(iframe 原样展示定稿),
|
||||
* 与签署态并排会让科室看到两份长得不一样的同一份文书,
|
||||
* 也让人不知道该以哪一份为准 —— 已连同 `raw` 模式一起删除。
|
||||
* 要看原始定稿请走卡片的「编辑正文」。
|
||||
*/
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
@@ -38,11 +41,8 @@ const emit = defineEmits<{
|
||||
use: [template: DocumentTemplate]
|
||||
}>()
|
||||
|
||||
/** 预览页签:「文书内容」看原始定稿,「签署预览」看占位符填充与勾选交互化后的样子 */
|
||||
const activeTab = ref('content')
|
||||
|
||||
/**
|
||||
* 签署预览里的试填结果,仅用于预览反馈,不作为签署证据。
|
||||
* 预览里的试填结果,仅用于预览反馈,不作为签署证据。
|
||||
* 只在页脚展示一行汇总,所以只留计数与清单,不留整份取值。
|
||||
*/
|
||||
const filledCount = ref(0)
|
||||
@@ -58,8 +58,7 @@ watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
// 每次打开都回到「文书内容」,并把上一次的试填结果清掉
|
||||
activeTab.value = 'content'
|
||||
// 每次打开都把上一次的试填结果清掉
|
||||
filledCount.value = 0
|
||||
totalCount.value = 0
|
||||
selectedLabels.value = []
|
||||
@@ -67,7 +66,7 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
/** 状态口径统一走 library.ts:卡片、页签、预览三处必须是同一档 */
|
||||
/** 状态口径统一走 library.ts:卡片、徽标、预览三处必须是同一档 */
|
||||
const libraryStatus = computed<DocumentLibraryStatus>(() =>
|
||||
props.template ? readLibraryStatus(props.template) : '草稿',
|
||||
)
|
||||
@@ -91,26 +90,12 @@ const canInitiate = computed(
|
||||
() => Boolean(props.template?.contentHtml) && canInitiateSigning(libraryStatus.value),
|
||||
)
|
||||
|
||||
const tabs = computed(() => [
|
||||
{ key: 'content', label: '文书内容' },
|
||||
{
|
||||
key: 'signing',
|
||||
label: '签署预览',
|
||||
disabled: !props.template?.contentHtml,
|
||||
hint: '按签署时的效果渲染:占位符填充、勾选表交互化、签名域',
|
||||
},
|
||||
])
|
||||
|
||||
const badges = computed(() => [
|
||||
{ text: '类 Word 版式', tone: 'brand' as const },
|
||||
{ text: libraryStatus.value, tone: statusTone.value },
|
||||
])
|
||||
|
||||
const note = computed(() =>
|
||||
activeTab.value === 'signing'
|
||||
? `按签署时效果渲染:占位符已用示例患者「${PREVIEW_PATIENT.patientName}」填充,空白处是待补录的下划线、☑ / ☐ 可直接点击确认交互效果(预览不保存结果)。`
|
||||
: '',
|
||||
)
|
||||
const note = `按签署时效果渲染:占位符已用示例患者「${PREVIEW_PATIENT.patientName}」填充,空白处是待补录的下划线、☑ / ☐ 可直接点击确认交互效果(预览不保存结果)。`
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -118,18 +103,14 @@ const note = computed(() =>
|
||||
:visible="visible && Boolean(template)"
|
||||
:title="template?.name ?? ''"
|
||||
:badges="badges"
|
||||
:tabs="tabs"
|
||||
:active-tab="activeTab"
|
||||
:mode="activeTab === 'signing' ? 'signing' : 'raw'"
|
||||
:content-html="template?.contentHtml ?? ''"
|
||||
:data="PREVIEW_PATIENT"
|
||||
:interactive="activeTab === 'signing'"
|
||||
:interactive="true"
|
||||
:note="note"
|
||||
:loading="loading"
|
||||
:error="error ? '模板内容加载失败,请关闭后重试。' : ''"
|
||||
:empty-text="`「${template?.name ?? ''}」暂无结构化正文`"
|
||||
@close="emit('close')"
|
||||
@update:active-tab="activeTab = $event"
|
||||
@field-change="handleFieldChange"
|
||||
@selection-change="selectedLabels = $event"
|
||||
>
|
||||
@@ -176,7 +157,7 @@ const note = computed(() =>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<span v-if="activeTab === 'signing' && totalCount" class="preview-note-footer">
|
||||
<span v-if="totalCount" class="preview-note-footer">
|
||||
填写项 {{ filledCount }} / {{ totalCount }} 项已填<template v-if="selectedLabels.length"
|
||||
>・已勾选 {{ selectedLabels.length }} 项:{{ selectedLabels.join('、') }}</template
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user