refactor(文书预览): 抽出统一的文书纸与预览弹窗,消除五处重复渲染
预览原先在 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 删)。
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* 文书纸的基础排版(全项目唯一一份)。
|
||||
*
|
||||
* 为什么单独成文件、还要在运行期注入:同一份「文书纸」有**两种渲染通道**——
|
||||
* ① v-html 直接挂进页面(签署工作台、各类预览)→ 只能吃全局样式;
|
||||
* ② iframe srcdoc(文书库的「文书内容」页签,隔离模板自带的 `<style>`)→ 只能吃字符串。
|
||||
* 各写一份必然漂移:改版前实测已经有 5 份副本,表格边框在 `#52646e` 与 `#c8d7dd` 之间
|
||||
* 分叉、单元格内边距 `5px 7px` 与 `5px 9px` 不一致、只有两处带宋体。
|
||||
* 所以这里只维护一份字符串,两条通道共用。
|
||||
*
|
||||
* 选择器一律以 `.doc-paper` 开头:v-html 通道把它挂在容器 div 上,
|
||||
* iframe 通道挂在 `<body>` 上,两边命中同一批规则。
|
||||
*
|
||||
* 注意:填写项胶囊 / 选项 ☑☐ / 系统页眉的样式**不在这里**,
|
||||
* 它们与「签署态」语义强相关,统一在 styles/signing-document.css。
|
||||
*/
|
||||
|
||||
export const DOCUMENT_PAPER_CSS = `
|
||||
.doc-paper {
|
||||
color: #1c2b33;
|
||||
font-family: 'SimSun', 'Songti SC', 'STSong', serif;
|
||||
font-size: 13.5px;
|
||||
line-height: 1.85;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.doc-paper p {
|
||||
margin: 0 0 6px;
|
||||
}
|
||||
|
||||
.doc-paper h1,
|
||||
.doc-paper h2 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 18px;
|
||||
text-align: center;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.doc-paper h3 {
|
||||
margin: 14px 0 6px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.doc-paper h4 {
|
||||
margin: 10px 0 4px;
|
||||
font-size: 13.5px;
|
||||
}
|
||||
|
||||
.doc-paper ol,
|
||||
.doc-paper ul {
|
||||
margin: 6px 0;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.doc-paper li {
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
/* 表格按内容自适应列宽并允许换行:绝对列宽在窄容器里会把整表裁掉 */
|
||||
.doc-paper table {
|
||||
width: 100%;
|
||||
margin: 8px 0 12px;
|
||||
border-collapse: collapse;
|
||||
table-layout: auto;
|
||||
font-size: 12.5px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.doc-paper th,
|
||||
.doc-paper td {
|
||||
padding: 5px 7px;
|
||||
vertical-align: middle;
|
||||
border: 1px solid #52646e;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.doc-paper th {
|
||||
font-weight: 700;
|
||||
background: #f2f6f8;
|
||||
}
|
||||
|
||||
.doc-paper th p,
|
||||
.doc-paper td p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.doc-paper img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* 系统页眉是 flex 排的一行患者信息,纸内边距由容器给,这里只补下间距 */
|
||||
.doc-paper > .sign-doc-header:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
`
|
||||
|
||||
const STYLE_TAG_ID = 'document-paper-base-style'
|
||||
|
||||
/**
|
||||
* 把基础排版注入 `document.head`(幂等)。
|
||||
*
|
||||
* 用运行期注入而不是普通的全局 css 文件,是为了让 v-html 与 iframe 两条通道
|
||||
* 吃**同一份**规则文本;写成 css 文件后 iframe 拿不到(srcdoc 无法引用应用样式表)。
|
||||
*/
|
||||
export function ensureDocumentPaperStyle(): void {
|
||||
if (typeof document === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
if (document.getElementById(STYLE_TAG_ID)) {
|
||||
return
|
||||
}
|
||||
|
||||
const style = document.createElement('style')
|
||||
style.id = STYLE_TAG_ID
|
||||
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('')
|
||||
}
|
||||
Reference in New Issue
Block a user