refactor(文书纸): 统一 A4 纸规格并收敛各渲染场景的纸宽与页边距

This commit is contained in:
yelan
2026-09-27 13:54:15 +08:00
parent 635e998946
commit 431dd2cd6f
8 changed files with 150 additions and 52 deletions
@@ -279,7 +279,9 @@ defineExpose({ getDocumentElement })
<template>
<!-- eslint-disable vue/no-v-html -- 内容为本系统定稿的文书 HTML(编辑器产出、仅医务人员可改),非任意用户输入 -->
<article class="doc-paper" :class="{ 'doc-paper--card': variant === 'card' }">
<!-- doc-paper--a4:无论哪种 variant 都是「一页 A4」(比例 + 一般文书页边距),
variant 只决定盒子装饰 —— card 自带圆角阴影,plain 交给调用方 -->
<article class="doc-paper doc-paper--a4" :class="{ 'doc-paper--card': variant === 'card' }">
<div
ref="containerRef"
class="doc-paper__body"
@@ -302,9 +304,10 @@ defineExpose({ getDocumentElement })
min-width: 0;
}
/* card:预览弹窗里那张白纸 */
/* card:预览弹窗里那张白纸。
比例与页边距由 .doc-paper--a4 给(见 utils/document-paper.ts 的 A4 规格),
这里只补「纸」的观感 —— 圆角与投影。 */
.doc-paper--card {
padding: 26px 32px 30px;
border-radius: var(--r);
box-shadow: var(--sh);
}
@@ -349,12 +349,13 @@ defineExpose({ getDocumentElement })
background: #525659;
}
/* 舞台上的纸**不再单独收窄**(曾按原型的 640px 覆盖过)。
640px 配 A4 页边距只剩 446px 版心,而「四、用药信息」那张表的
min-content 就有 508px → 表宽溢出纸面、「药品名称」被挤成 28px 宽的竖排(已踩)。
现在统一走 :root 的 820px:版心 572px,与工作台/编辑器完全一致(所见即所签)。
外壳宽度由调用方给(已签署预览传 width=960 → 舞台内宽 912 ≥ 820)。 */
.preview-body--stage :deep(.doc-paper) {
width: 640px;
max-width: 100%;
min-height: 905px;
flex: none;
padding: 34px 40px;
box-shadow: 0 6px 22px rgb(0 0 0 / 45%);
}
@@ -108,20 +108,24 @@ defineExpose({ getDocumentElement })
<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)"
/>
<!-- 纸桌:灰底 + 内滚,纸(一页 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>
@@ -202,13 +206,15 @@ defineExpose({ getDocumentElement })
background: #8a9aa3;
}
/* 纸的盒子:DocumentPaper 只给排版与白底(variant=plain),
内边距与滚动由这里给。类名通过 fallthrough 落到纸的根元素上。 */
/* 纸桌:灰底 + 内滚,纸(一页 A4)居中摆在上面。
纸自己的比例与页边距走统一 A4 规格(.doc-paper--a4,见 utils/document-paper.ts),
这里只留「桌子」—— 四周留一圈灰边,纸才不会顶到卡片边缘。 */
.sign-doc-body {
flex: 1;
min-height: 0;
padding: 26px 32px 30px;
padding: 16px 20px 20px;
overflow: auto;
background: var(--bg);
}
.sign-doc-footer {
+10
View File
@@ -7,6 +7,16 @@ import App from './App.vue'
import { registerDirectives } from './directives'
import router from './router'
import './styles/index.css'
import { ensureDocumentPaperStyle } from './utils/document-paper'
/**
* 文书纸的基础排版 + A4 规格变量(`--doc-paper-*`)在启动时就注入。
*
* DocumentPaper 自己在 setup 期也会幂等注入一次,但**编辑器**的编辑面不是
* `DocumentPaper`(是 WangEditor 的 `[data-slate-editor]`),它也要吃同一套页边距 ——
* 只在组件里注入的话,先进编辑器(还没挂载过任何 DocumentPaper)会拿不到变量。
*/
ensureDocumentPaperStyle()
const app = createApp(App)
const pinia = createPinia()
+40
View File
@@ -14,12 +14,52 @@
* (setup 期,避免首帧"纸没排版"的闪烁),且规则文本同时供导出 PDF 之类的场景复用。
*
* 选择器一律以 `.doc-paper` 开头,挂在容器 div 上。
* 例外只有一处:A4 规格的 `--doc-paper-*` 变量放在 `:root`(见下),
* 因为编辑器里的编辑面不是 `.doc-paper`(是 WangEditor 的 `[data-slate-editor]`),
* 它也要吃同一套页边距。
*
* 注意:填写项胶囊 / 选项 ☑☐ / 系统页眉的样式**不在这里**,
* 它们与「签署态」语义强相关,统一在 styles/signing-document.css。
*/
export const DOCUMENT_PAPER_CSS = `
/* ============ A4 纸规格(全项目唯一一份) ============
A4 = 210mm × 297mm。页边距:
- 左右 3.18cm(Word 中文默认的「一般文书」留白)→ 占纸宽 15.14%
- 上下 1.4cm(原型工作台 #previewBox .paper 的 56px 那一档)→ 占纸宽 6.83%
上下比左右紧是刻意的:820px 宽的纸上 2.54cm(Word 默认)会在顶部空出一大片,
屏幕上很显眼;原型用的就是这一档。
纸宽上限 820px 时对应的 px 值(124px / 56px)就是 clamp 的上界。
为什么用 clamp 而不是裸百分比:% padding 以**包含块宽度**为基准,不是自身宽。
纸在窄容器里 width:100% 时两者相等(裸百分比正好);纸一旦被 max-width 收窄,
百分比就会按更宽的包含块算 → 页边距偏大。clamp 让「纸 == 容器」和「纸被收窄」
两种情形都得到同一套页边距。
⚠️ 换纸宽时两个 --doc-paper-pad-* 要跟着改(与 --doc-paper-max-w 是一组)。
⚠️ 本段在模板字符串里,注释内**不要写反引号**,会提前结束字符串。 */
:root {
--doc-paper-max-w: 820px;
--doc-paper-pad-x: clamp(20px, 15.14%, 124px);
--doc-paper-pad-y: clamp(16px, 6.83%, 56px);
/* 同一套比例,用**容器查询单位**表达:给「纸壳宽度 == 纸宽」的场景(编辑器)用。
那种场景下 cqw 精确等于纸宽的 1%,不需要 clamp 的两段兜底。
⚠️ 这三个与上面那三个必须同步改;只在有 container-type:inline-size 祖先的元素上用,
否则 cqw 会退化成相对视口算。 */
--doc-paper-pad-x-cqw: 15.14cqw;
--doc-paper-pad-y-cqw: 6.83cqw;
--doc-paper-min-h-cqw: 141.43cqw; /* 297 / 210 */
}
/* 一页 A4:比例 + 一般文书页边距。
aspect-ratio 只是**下限** —— 内容超过一页时盒子会长高(实测过),不裁切、不内滚。 */
.doc-paper--a4 {
width: 100%;
max-width: var(--doc-paper-max-w);
margin: 0 auto;
aspect-ratio: 210 / 297;
padding: var(--doc-paper-pad-y) var(--doc-paper-pad-x);
}
.doc-paper {
color: #1c2b33;
font-family: 'SimSun', 'Songti SC', 'STSong', serif;
@@ -996,15 +996,10 @@ function requestClose() {
border-radius: 10px;
}
/* 纸的比例与页边距由 DocumentPaper 的 .doc-paper--a4 给(统一 A4 规格,
见 utils/document-paper.ts),这里只留「桌子」和纸的观感 —— 别再写 max-width/padding,
写了就会和 A4 规格打架(% 页边距以包含块宽度为基准,纸一被收窄就算错)。 */
.preview-paper {
max-width: 820px;
min-height: 460px;
padding: 34px 44px;
margin: 0 auto;
color: #1c2b33;
font-size: 13.5px;
line-height: 1.9;
background: #fff;
border-radius: var(--r);
box-shadow: var(--sh);
}
@@ -247,14 +247,23 @@ onBeforeUnmount(() => {
<!-- 工具栏的第二行(原型 mdDocEd 里「插入签署占位符」那一行)。
放在工具栏与正文之间,让"插入什么"与"排版成什么样"在同一条视觉带里。 -->
<slot name="toolbar-extra" />
<Editor
class="pane-content"
:model-value="props.modelValue"
:default-config="editorConfig"
:mode="editorMode"
@update:model-value="emit('update:modelValue', $event)"
@on-created="handleCreated"
/>
<!-- 纸桌 → 纸壳 → 纸 三层,缺一不可:
`.pane-desk` 提供灰底与唯一滚动条;
`.pane-sheet` 负责限宽居中 —— % 页边距以**包含块**宽度为基准,
限宽必须落在这里,落到纸上就会按更宽的桌面算(页边距偏大);
`.pane-content` 才是白纸本身(A4 比例 + 页边距)。 -->
<div class="pane-desk">
<div class="pane-sheet">
<Editor
class="pane-content"
:model-value="props.modelValue"
:default-config="editorConfig"
:mode="editorMode"
@update:model-value="emit('update:modelValue', $event)"
@on-created="handleCreated"
/>
</div>
</div>
</div>
</template>
@@ -273,27 +282,59 @@ onBeforeUnmount(() => {
border-bottom: 1px solid var(--line);
}
.pane-content {
/* 编辑区左右页边距,模拟 Word 文档版心:宽屏约 96px,窄窗口自动收窄 */
--pane-gutter: clamp(20px, 8%, 96px);
/* 纸桌:灰底 + **唯一**的滚动条(纸自己不许再滚,否则会出现两个滚动条) */
.pane-desk {
flex: 1;
min-height: 340px;
padding: 16px 20px;
overflow: auto;
background: #eef3f6;
}
/* WangEditor 默认 [data-slate-editor] 只有 padding: 0 10px,正文会贴着边框;
这里补上左右留白,粘贴的 Word 正文才呈现"纸张版心"的观感 */
/* 纸壳:只负责限宽居中,并作为**容器查询上下文**。
它的宽度 == 纸宽,所以纸里的 `cqw` 就等于「纸宽的 1%」——
用 `min-height: 141.43cqw`(= 297/210)表达「至少一页 A4」,纸窄时按纸宽自动缩,
不必再为「纸比容器窄」写第二套数字。 */
.pane-sheet {
width: 100%;
max-width: var(--doc-paper-max-w);
margin: 0 auto;
container-type: inline-size;
}
/* 白纸:A4 比例(下限)+ 一般文书页边距,走统一 A4 规格(utils/document-paper.ts)。
原来那套 `--pane-gutter: clamp(20px, 8%, 96px)` 只是"看着像版心",
与签署预览的页边距对不上,编辑时看到的换行位置和签出来不一样。
**用 min-height 而不是 aspect-ratio**:aspect-ratio 会被 WangEditor 内层的
滚动容器吃掉(正文 1574px 而纸定死在 1160px,正文直接露到灰桌上);
min-height 只是下限,正文多了纸自己长高,滚动交给纸桌。 */
.pane-content {
width: 100%;
min-height: var(--doc-paper-min-h-cqw);
padding: var(--doc-paper-pad-y-cqw) var(--doc-paper-pad-x-cqw);
background: #fff;
box-shadow: var(--sh);
}
/* 编辑面本身不再要 padding —— 页边距已由纸(.pane-content)给;
WangEditor 默认的 `padding: 0 10px` 也要一并清掉 */
.pane-content :deep([data-slate-editor]) {
padding: 0 var(--pane-gutter);
padding: 0;
}
/* 空文档提示绝对定位于纸的内层容器,WangEditor 自带一点偏移(实测右下各偏 ~10/17px),
这里拉回版心原点,否则空文档时提示文字不与正文左边界对齐 */
.pane-content :deep(.w-e-text-container) {
padding: 0;
}
/* 占位符绝对定位于 .w-e-text-container(而非编辑区),不跟随上面的 padding,
需单独对齐到同一版心,否则空文档时提示文字会贴左边框 */
.pane-content :deep(.w-e-text-placeholder) {
right: var(--pane-gutter);
left: var(--pane-gutter);
top: 0;
right: 0;
left: 0;
width: auto;
margin: 0;
padding: 0;
}
/* 粘贴的 Word 表格:解除绝对列宽、允许单元格换行,
@@ -46,6 +46,8 @@ const documentData = computed<SigningDocumentData>(() => ({
</script>
<template>
<!-- 宽度用 960 而不是 900:舞台内宽 960−48=912,820px 的纸才放得下、两侧留白也舒服。
纸窄了(原 640)版心只剩 446px,用药信息表会被挤坏 -->
<DocumentPreviewDialog
:visible="visible && Boolean(task)"
title="在线预览(PDF)"
@@ -53,7 +55,7 @@ const documentData = computed<SigningDocumentData>(() => ({
:content-html="task?.documentContentHtml ?? ''"
:data="documentData"
stage="stage"
:width="900"
:width="960"
@close="emit('close')"
>
<!-- 正文取不到时用带任务信息的回退纸样,而不是一句「暂无正文」: