medical-form-printer 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +243 -360
- package/README.zh-CN.md +248 -365
- package/dist/index.cjs +1553 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1073 -13
- package/dist/index.d.ts +1073 -13
- package/dist/index.js +1533 -45
- package/dist/index.js.map +1 -1
- package/dist/node.cjs +1553 -46
- package/dist/node.cjs.map +1 -1
- package/dist/node.d.cts +1073 -13
- package/dist/node.d.ts +1073 -13
- package/dist/node.js +1533 -45
- package/dist/node.js.map +1 -1
- package/package.json +22 -12
package/README.zh-CN.md
CHANGED
|
@@ -4,39 +4,40 @@
|
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
[](https://nodejs.org)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
基于 Schema 驱动的医疗表单打印渲染库,将结构化表单数据转换为可打印的 HTML 和 PDF 文档。专为医疗健康应用设计,支持复杂布局、智能分页和跨环境一致性渲染。
|
|
8
8
|
|
|
9
9
|
[English Documentation](./README.md)
|
|
10
10
|
|
|
11
|
+
## 目录
|
|
12
|
+
|
|
13
|
+
- [特性](#特性)
|
|
14
|
+
- [安装](#安装)
|
|
15
|
+
- [快速开始](#快速开始)
|
|
16
|
+
- [设计理念](#设计理念)
|
|
17
|
+
- [区块类型](#区块类型)
|
|
18
|
+
- [API 参考](#api-参考)
|
|
19
|
+
- [CSS 隔离](#css-隔离)
|
|
20
|
+
- [示例](#示例)
|
|
21
|
+
|
|
11
22
|
## 特性
|
|
12
23
|
|
|
13
|
-
- 🖨️ **双环境支持** - 同时支持浏览器和 Node.js
|
|
14
|
-
- 📄 **丰富的区块类型** -
|
|
24
|
+
- 🖨️ **双环境支持** - 同时支持浏览器和 Node.js
|
|
25
|
+
- 📄 **丰富的区块类型** - 信息网格、数据表格、复选框网格、签名区域等
|
|
15
26
|
- 🎨 **主题定制** - 完全可定制的字体、颜色、间距和尺寸
|
|
16
27
|
- 📑 **PDF 生成** - 通过 Puppeteer 生成高保真 PDF(Node.js)
|
|
17
28
|
- 🔗 **PDF 合并** - 将多个文档合并为单个 PDF
|
|
18
29
|
- 📐 **智能分页** - 自动分页,支持表头重复和溢出处理
|
|
19
30
|
- 🔒 **CSS 隔离** - 内嵌字体和命名空间样式,确保一致渲染
|
|
20
|
-
- 🔌 **可扩展** -
|
|
31
|
+
- 🔌 **可扩展** - 注册自定义区块渲染器
|
|
21
32
|
- 📦 **TypeScript 优先** - 完整的类型定义和 JSDoc 文档
|
|
22
33
|
|
|
23
34
|
## 安装
|
|
24
35
|
|
|
25
36
|
```bash
|
|
26
|
-
# npm
|
|
27
37
|
npm install medical-form-printer
|
|
28
|
-
|
|
29
|
-
# yarn
|
|
30
|
-
yarn add medical-form-printer
|
|
31
|
-
|
|
32
|
-
# pnpm
|
|
33
|
-
pnpm add medical-form-printer
|
|
34
|
-
|
|
35
|
-
# bun
|
|
36
|
-
bun add medical-form-printer
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
如需在 Node.js 中生成 PDF,请安装 Puppeteer
|
|
40
|
+
如需在 Node.js 中生成 PDF,请安装 Puppeteer:
|
|
40
41
|
|
|
41
42
|
```bash
|
|
42
43
|
npm install puppeteer
|
|
@@ -49,12 +50,11 @@ npm install puppeteer
|
|
|
49
50
|
```typescript
|
|
50
51
|
import { renderToHtml } from 'medical-form-printer'
|
|
51
52
|
|
|
52
|
-
const
|
|
53
|
+
const schema = {
|
|
53
54
|
pageSize: 'A4',
|
|
54
55
|
orientation: 'portrait',
|
|
55
56
|
header: {
|
|
56
57
|
hospital: '示例医院',
|
|
57
|
-
department: '产后康复中心',
|
|
58
58
|
title: '患者评估表',
|
|
59
59
|
},
|
|
60
60
|
sections: [
|
|
@@ -62,487 +62,370 @@ const printSchema = {
|
|
|
62
62
|
type: 'info-grid',
|
|
63
63
|
config: {
|
|
64
64
|
columns: 4,
|
|
65
|
-
rows: [
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
]
|
|
65
|
+
rows: [{
|
|
66
|
+
cells: [
|
|
67
|
+
{ label: '姓名', field: 'name' },
|
|
68
|
+
{ label: '年龄', field: 'age' },
|
|
69
|
+
{ label: '日期', field: 'date', type: 'date' },
|
|
70
|
+
{ label: '房间', field: 'room' },
|
|
71
|
+
]
|
|
72
|
+
}]
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
|
-
]
|
|
78
|
-
footer: {
|
|
79
|
-
showPageNumber: true
|
|
80
|
-
}
|
|
75
|
+
]
|
|
81
76
|
}
|
|
82
77
|
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
age: 28,
|
|
86
|
-
admissionDate: '2024-01-15',
|
|
87
|
-
roomNumber: 'A-101'
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// 渲染为 HTML
|
|
91
|
-
const html = renderToHtml(printSchema, formData, {
|
|
92
|
-
watermark: '内部使用'
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
// 显示在 iframe 或 div 中
|
|
96
|
-
document.getElementById('preview').innerHTML = html
|
|
78
|
+
const data = { name: '张三', age: 28, date: '2024-01-15', room: 'A-101' }
|
|
79
|
+
const html = renderToHtml(schema, data)
|
|
97
80
|
```
|
|
98
81
|
|
|
99
|
-
### Node.js 使用(PDF
|
|
82
|
+
### Node.js 使用(PDF)
|
|
100
83
|
|
|
101
84
|
```typescript
|
|
102
|
-
import { renderToPdf
|
|
85
|
+
import { renderToPdf } from 'medical-form-printer/node'
|
|
103
86
|
import fs from 'fs'
|
|
104
87
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
watermark: '机密文件'
|
|
108
|
-
})
|
|
109
|
-
fs.writeFileSync('assessment.pdf', pdfBuffer)
|
|
110
|
-
|
|
111
|
-
// 合并多个表单为一个 PDF
|
|
112
|
-
const mergedPdf = await mergePdfs([
|
|
113
|
-
{ schema: maternalSchema, data: maternalData },
|
|
114
|
-
{ schema: newbornSchema, data: newbornData },
|
|
115
|
-
])
|
|
116
|
-
fs.writeFileSync('complete-record.pdf', mergedPdf)
|
|
88
|
+
const pdfBuffer = await renderToPdf(schema, data)
|
|
89
|
+
fs.writeFileSync('form.pdf', pdfBuffer)
|
|
117
90
|
```
|
|
118
91
|
|
|
119
|
-
##
|
|
92
|
+
## 设计理念
|
|
120
93
|
|
|
121
|
-
###
|
|
94
|
+
### 为什么选择扁平的 Section 模型?
|
|
122
95
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
将打印 Schema 和表单数据渲染为 HTML 字符串。
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
import { renderToHtml } from 'medical-form-printer'
|
|
96
|
+
许多文档渲染系统使用深度嵌套的组件层级:
|
|
129
97
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
watermark: '草稿',
|
|
133
|
-
watermarkOpacity: 0.1
|
|
134
|
-
})
|
|
98
|
+
```
|
|
99
|
+
Document → Page → Container → Row → Cell → Element
|
|
135
100
|
```
|
|
136
101
|
|
|
137
|
-
|
|
138
|
-
- `schema: PrintSchema` - 定义布局和区块的打印 Schema
|
|
139
|
-
- `data: FormData` - 要渲染的表单数据
|
|
140
|
-
- `options?: RenderOptions` - 可选的渲染配置
|
|
102
|
+
我们刻意选择了**扁平的 section 模型**,原因如下:
|
|
141
103
|
|
|
142
|
-
|
|
104
|
+
#### 1. 打印文档 ≠ UI 组件
|
|
143
105
|
|
|
144
|
-
|
|
106
|
+
打印文档是**静态输出**。医疗表单不需要响应点击的 `<Button>`——它只需要在正确位置渲染复选框符号(☑/□)。嵌套组件树带来的是额外开销而非收益。
|
|
145
107
|
|
|
146
|
-
|
|
108
|
+
#### 2. 领域驱动设计
|
|
147
109
|
|
|
148
|
-
|
|
149
|
-
import { renderToIsolatedHtml } from 'medical-form-printer'
|
|
110
|
+
Section 直接映射到**真实的医疗表单概念**:
|
|
150
111
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
112
|
+
| Section 类型 | 真实概念 |
|
|
113
|
+
|-------------|---------|
|
|
114
|
+
| `info-grid` | 患者基本信息区块 |
|
|
115
|
+
| `table` | 护理记录表格 |
|
|
116
|
+
| `checkbox-grid` | 症状检查清单 |
|
|
117
|
+
| `signature-area` | 审批签名区 |
|
|
155
118
|
|
|
156
|
-
|
|
157
|
-
- 命名空间 CSS 类(以 `mpr-` 为前缀)
|
|
158
|
-
- 内嵌思源宋体字体
|
|
159
|
-
- 样式隔离,确保可预测的渲染效果
|
|
119
|
+
医护人员用这些术语思考,而不是抽象的"容器"和"元素"。
|
|
160
120
|
|
|
161
|
-
####
|
|
121
|
+
#### 3. 分页友好的架构
|
|
162
122
|
|
|
163
|
-
|
|
123
|
+
扁平的 section 使**分页计算可预测**:
|
|
164
124
|
|
|
165
125
|
```typescript
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
126
|
+
type MeasurableItemType =
|
|
127
|
+
| 'header' // 页面头部 - 测量一次
|
|
128
|
+
| 'section' // 原子区块 - 不可拆分
|
|
129
|
+
| 'table-header' // 在续页重复显示
|
|
130
|
+
| 'table-row' // 可以单独分页
|
|
131
|
+
| 'signature' // 通常固定在最后一页
|
|
132
|
+
| 'footer' // 页面底部 - 测量一次
|
|
170
133
|
```
|
|
171
134
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
#### `renderToPdf(schema, data, options?)`
|
|
175
|
-
|
|
176
|
-
从打印 Schema 生成 PDF 缓冲区。
|
|
135
|
+
#### 4. Schema 简洁性
|
|
177
136
|
|
|
178
137
|
```typescript
|
|
179
|
-
|
|
138
|
+
// ❌ 嵌套方式(冗长)
|
|
139
|
+
{
|
|
140
|
+
type: 'container',
|
|
141
|
+
children: [{
|
|
142
|
+
type: 'container',
|
|
143
|
+
children: [
|
|
144
|
+
{ type: 'label', text: '姓名:' },
|
|
145
|
+
{ type: 'field', binding: 'name' }
|
|
146
|
+
]
|
|
147
|
+
}]
|
|
148
|
+
}
|
|
180
149
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
150
|
+
// ✅ 扁平方式(简洁)
|
|
151
|
+
{
|
|
152
|
+
type: 'info-grid',
|
|
153
|
+
config: {
|
|
154
|
+
rows: [{ cells: [{ label: '姓名', field: 'name' }] }]
|
|
186
155
|
}
|
|
187
|
-
}
|
|
156
|
+
}
|
|
188
157
|
```
|
|
189
158
|
|
|
190
|
-
|
|
191
|
-
- `schema: PrintSchema` - 打印 Schema
|
|
192
|
-
- `data: FormData` - 表单数据
|
|
193
|
-
- `options?: RenderOptions & { pdfOptions?: PdfOptions }` - 渲染和 PDF 选项
|
|
194
|
-
|
|
195
|
-
**返回:** `Promise<Buffer>` - PDF 文件缓冲区
|
|
196
|
-
|
|
197
|
-
#### `mergePdfs(documents, options?)`
|
|
198
|
-
|
|
199
|
-
将多个文档合并为单个 PDF。
|
|
159
|
+
#### 5. 简单的可扩展性
|
|
200
160
|
|
|
201
161
|
```typescript
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const mergedPdf = await mergePdfs([
|
|
205
|
-
{ schema: schema1, data: data1 },
|
|
206
|
-
{ schema: schema2, data: data2 },
|
|
207
|
-
], {
|
|
208
|
-
watermark: '完整记录'
|
|
162
|
+
registerSectionRenderer('vital-signs-chart', (config, data) => {
|
|
163
|
+
return '<div class="chart">...</div>'
|
|
209
164
|
})
|
|
210
165
|
```
|
|
211
166
|
|
|
212
|
-
|
|
167
|
+
无需抽象基类或访问者模式。
|
|
213
168
|
|
|
214
|
-
|
|
169
|
+
### 权衡取舍
|
|
215
170
|
|
|
216
|
-
|
|
171
|
+
这个设计专门为**打印文档生成**优化。如需深度嵌套布局或交互式组件,请考虑通用 HTML 模板库或 UI 框架。
|
|
217
172
|
|
|
218
|
-
|
|
219
|
-
import { registerSectionRenderer } from 'medical-form-printer'
|
|
220
|
-
|
|
221
|
-
registerSectionRenderer('vital-signs-chart', (config, data, options) => {
|
|
222
|
-
const values = data[config.dataField] || []
|
|
223
|
-
return `
|
|
224
|
-
<div class="vital-signs-chart">
|
|
225
|
-
<h3>${config.title}</h3>
|
|
226
|
-
<!-- 自定义图表渲染 -->
|
|
227
|
-
</div>
|
|
228
|
-
`
|
|
229
|
-
})
|
|
230
|
-
```
|
|
173
|
+
## 区块类型
|
|
231
174
|
|
|
232
|
-
|
|
175
|
+
| 类型 | 描述 | 使用场景 |
|
|
176
|
+
|------|------|----------|
|
|
177
|
+
| `info-grid` | 键值对网格布局 | 患者基本信息 |
|
|
178
|
+
| `table` | 带列的数据表格 | 护理记录 |
|
|
179
|
+
| `checkbox-grid` | 复选框选项网格 | 症状检查清单 |
|
|
180
|
+
| `signature-area` | 签名字段 | 审批签字 |
|
|
181
|
+
| `notes` | 静态文本内容 | 说明文字 |
|
|
182
|
+
| `free-text` | 多行文本输入 | 备注 |
|
|
233
183
|
|
|
234
|
-
|
|
184
|
+
### 信息网格
|
|
235
185
|
|
|
236
186
|
```typescript
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
187
|
+
{
|
|
188
|
+
type: 'info-grid',
|
|
189
|
+
config: {
|
|
190
|
+
columns: 4,
|
|
191
|
+
rows: [{
|
|
192
|
+
cells: [
|
|
193
|
+
{ label: '姓名', field: 'name' },
|
|
194
|
+
{ label: '年龄', field: 'age', type: 'number' },
|
|
195
|
+
{ label: '状态', field: 'status', type: 'checkbox', options: ['在院'] }
|
|
196
|
+
]
|
|
197
|
+
}]
|
|
198
|
+
}
|
|
199
|
+
}
|
|
240
200
|
```
|
|
241
201
|
|
|
242
|
-
###
|
|
243
|
-
|
|
244
|
-
#### `renderPaginatedHtml(config)`
|
|
245
|
-
|
|
246
|
-
使用智能分页渲染多页内容。
|
|
202
|
+
### 表格
|
|
247
203
|
|
|
248
204
|
```typescript
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
205
|
+
{
|
|
206
|
+
type: 'table',
|
|
207
|
+
title: '护理记录',
|
|
208
|
+
config: {
|
|
209
|
+
dataField: 'records',
|
|
210
|
+
columns: [
|
|
211
|
+
{ header: '日期', field: 'date', type: 'date', width: '20%' },
|
|
212
|
+
{ header: '备注', field: 'notes' }
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
254
217
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
footerHeight: 40,
|
|
259
|
-
repeatTableHeaders: true
|
|
260
|
-
})
|
|
218
|
+
#### 多行表头
|
|
219
|
+
|
|
220
|
+
表格支持复杂的多行表头,可使用 colspan 和 rowspan 进行单元格合并:
|
|
261
221
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
measuredItems: items,
|
|
222
|
+
```typescript
|
|
223
|
+
{
|
|
224
|
+
type: 'table',
|
|
225
|
+
title: '生命体征',
|
|
267
226
|
config: {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
227
|
+
dataField: 'vitalSigns',
|
|
228
|
+
columns: [
|
|
229
|
+
{ header: '日期', field: 'date', type: 'date' },
|
|
230
|
+
{ header: '收缩压', field: 'systolic', type: 'number' },
|
|
231
|
+
{ header: '舒张压', field: 'diastolic', type: 'number' },
|
|
232
|
+
{ header: '体温', field: 'temperature', type: 'number' },
|
|
233
|
+
],
|
|
234
|
+
headerRows: [
|
|
235
|
+
{
|
|
236
|
+
cells: [
|
|
237
|
+
{ text: '日期', rowspan: 2 },
|
|
238
|
+
{ text: '血压 (mmHg)', colspan: 2 },
|
|
239
|
+
{ text: '体温 (℃)', rowspan: 2 },
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
cells: [
|
|
244
|
+
{ text: '收缩压', field: 'systolic' },
|
|
245
|
+
{ text: '舒张压', field: 'diastolic' },
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
]
|
|
271
249
|
}
|
|
272
|
-
}
|
|
250
|
+
}
|
|
273
251
|
```
|
|
274
252
|
|
|
275
|
-
|
|
253
|
+
### 复选框网格
|
|
276
254
|
|
|
277
255
|
```typescript
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
256
|
+
{
|
|
257
|
+
type: 'checkbox-grid',
|
|
258
|
+
config: {
|
|
259
|
+
field: 'symptoms',
|
|
260
|
+
columns: 4,
|
|
261
|
+
options: [
|
|
262
|
+
{ value: 'fever', label: '发热' },
|
|
263
|
+
{ value: 'headache', label: '头痛' }
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
}
|
|
283
267
|
```
|
|
284
268
|
|
|
285
|
-
|
|
269
|
+
### 签名区域
|
|
286
270
|
|
|
287
271
|
```typescript
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
272
|
+
{
|
|
273
|
+
type: 'signature-area',
|
|
274
|
+
config: {
|
|
275
|
+
fields: [
|
|
276
|
+
{ label: '患者签名', field: 'patientSig' },
|
|
277
|
+
{ label: '日期', field: 'sigDate', type: 'date' }
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
}
|
|
292
281
|
```
|
|
293
282
|
|
|
294
|
-
|
|
283
|
+
## API 参考
|
|
284
|
+
|
|
285
|
+
### 核心渲染
|
|
295
286
|
|
|
296
|
-
|
|
287
|
+
| 函数 | 描述 |
|
|
288
|
+
|------|------|
|
|
289
|
+
| `renderToHtml(schema, data, options?)` | 渲染为 HTML 字符串 |
|
|
290
|
+
| `renderToIsolatedHtml(schema, data, options?)` | 使用 CSS 隔离渲染 |
|
|
291
|
+
| `renderToIsolatedFragment(schema, data, options?)` | 渲染隔离片段用于嵌入 |
|
|
297
292
|
|
|
298
|
-
|
|
293
|
+
### PDF 生成(Node.js)
|
|
299
294
|
|
|
300
295
|
```typescript
|
|
301
|
-
import {
|
|
296
|
+
import { renderToPdf, mergePdfs } from 'medical-form-printer/node'
|
|
297
|
+
|
|
298
|
+
// 单个 PDF
|
|
299
|
+
const pdf = await renderToPdf(schema, data, { watermark: '草稿' })
|
|
302
300
|
|
|
303
|
-
|
|
301
|
+
// 合并多个文档
|
|
302
|
+
const merged = await mergePdfs([
|
|
303
|
+
{ schema: schema1, data: data1 },
|
|
304
|
+
{ schema: schema2, data: data2 }
|
|
305
|
+
])
|
|
304
306
|
```
|
|
305
307
|
|
|
306
|
-
|
|
308
|
+
### 分页(策略模式)
|
|
307
309
|
|
|
308
|
-
|
|
310
|
+
```typescript
|
|
311
|
+
import {
|
|
312
|
+
createDefaultPaginationContext,
|
|
313
|
+
SmartPaginationStrategy
|
|
314
|
+
} from 'medical-form-printer'
|
|
315
|
+
|
|
316
|
+
// 自动选择策略
|
|
317
|
+
const context = createDefaultPaginationContext()
|
|
318
|
+
const html = context.render(schema, data, { isolated: true })
|
|
319
|
+
|
|
320
|
+
// 或使用特定策略
|
|
321
|
+
const strategy = new SmartPaginationStrategy()
|
|
322
|
+
if (strategy.shouldApply(schema)) {
|
|
323
|
+
const html = strategy.render(schema, data)
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### 自定义区块渲染器
|
|
309
328
|
|
|
310
329
|
```typescript
|
|
311
|
-
import {
|
|
330
|
+
import { registerSectionRenderer, getSectionRenderer } from 'medical-form-printer'
|
|
312
331
|
|
|
313
|
-
|
|
314
|
-
|
|
332
|
+
registerSectionRenderer('custom-chart', (config, data, options) => {
|
|
333
|
+
return `<div class="chart">${config.title}</div>`
|
|
334
|
+
})
|
|
315
335
|
```
|
|
316
336
|
|
|
317
|
-
|
|
337
|
+
### 主题定制
|
|
318
338
|
|
|
319
339
|
```typescript
|
|
320
340
|
import { renderToHtml, mergeTheme, defaultTheme } from 'medical-form-printer'
|
|
321
341
|
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
heading: '"Microsoft YaHei", "PingFang SC", sans-serif'
|
|
326
|
-
},
|
|
327
|
-
colors: {
|
|
328
|
-
primary: '#1a1a1a',
|
|
329
|
-
border: '#333333',
|
|
330
|
-
background: '#ffffff'
|
|
331
|
-
},
|
|
332
|
-
fontSize: {
|
|
333
|
-
body: '10pt',
|
|
334
|
-
heading: '14pt',
|
|
335
|
-
small: '8pt'
|
|
336
|
-
},
|
|
337
|
-
spacing: {
|
|
338
|
-
section: '12pt',
|
|
339
|
-
cell: '4pt'
|
|
340
|
-
}
|
|
342
|
+
const theme = mergeTheme(defaultTheme, {
|
|
343
|
+
colors: { primary: '#1a1a1a', border: '#333' },
|
|
344
|
+
fontSize: { body: '10pt', heading: '14pt' }
|
|
341
345
|
})
|
|
342
346
|
|
|
343
|
-
const html = renderToHtml(schema, data, { theme
|
|
347
|
+
const html = renderToHtml(schema, data, { theme })
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### 页面尺寸与单位
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import { PAGE_A4, PAGE_A5, PAGE_16K, mmToPx, pxToMm } from 'medical-form-printer'
|
|
354
|
+
|
|
355
|
+
// PAGE_A4: { width: 210, height: 297 } (mm)
|
|
356
|
+
const heightPx = mmToPx(297) // mm → 像素
|
|
344
357
|
```
|
|
345
358
|
|
|
346
359
|
### 格式化工具
|
|
347
360
|
|
|
348
361
|
```typescript
|
|
349
|
-
import {
|
|
350
|
-
formatDate,
|
|
351
|
-
formatBoolean,
|
|
352
|
-
formatNumber,
|
|
353
|
-
formatValue,
|
|
354
|
-
isChecked
|
|
355
|
-
} from 'medical-form-printer'
|
|
362
|
+
import { formatDate, formatBoolean, formatNumber } from 'medical-form-printer'
|
|
356
363
|
|
|
357
|
-
formatDate('2024-01-15')
|
|
364
|
+
formatDate('2024-01-15') // '2024-01-15'
|
|
358
365
|
formatDate('2024-01-15', { format: 'YYYY年MM月DD日' }) // '2024年01月15日'
|
|
359
|
-
formatBoolean(true)
|
|
360
|
-
|
|
361
|
-
formatNumber(1234.5, { decimals: 2 }) // '1234.50'
|
|
362
|
-
isChecked('yes', ['yes', 'true']) // true
|
|
366
|
+
formatBoolean(true) // '✓'
|
|
367
|
+
formatNumber(1234.5, { decimals: 2 }) // '1234.50'
|
|
363
368
|
```
|
|
364
369
|
|
|
365
|
-
|
|
370
|
+
## CSS 隔离
|
|
366
371
|
|
|
367
|
-
|
|
368
|
-
import {
|
|
369
|
-
HtmlBuilder,
|
|
370
|
-
h,
|
|
371
|
-
fragment,
|
|
372
|
-
when,
|
|
373
|
-
each,
|
|
374
|
-
escapeHtml
|
|
375
|
-
} from 'medical-form-printer'
|
|
372
|
+
确保跨环境一致渲染:
|
|
376
373
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
h('h1', {}, '标题'),
|
|
380
|
-
when(showContent, () => h('p', {}, '内容')),
|
|
381
|
-
each(items, (item) => h('li', {}, item.name))
|
|
382
|
-
)
|
|
374
|
+
```typescript
|
|
375
|
+
import { renderToIsolatedHtml, CSS_NAMESPACE } from 'medical-form-printer'
|
|
383
376
|
|
|
384
|
-
|
|
385
|
-
|
|
377
|
+
const html = renderToIsolatedHtml(schema, data)
|
|
378
|
+
// CSS_NAMESPACE = 'mpr'(所有类以 mpr- 为前缀)
|
|
386
379
|
```
|
|
387
380
|
|
|
381
|
+
隔离模式提供:
|
|
382
|
+
- 命名空间 CSS 类(`mpr-` 前缀)
|
|
383
|
+
- 内嵌思源宋体字体
|
|
384
|
+
- CSS 隔离确保可预测渲染
|
|
385
|
+
|
|
388
386
|
## PrintSchema 结构
|
|
389
387
|
|
|
390
388
|
```typescript
|
|
391
389
|
interface PrintSchema {
|
|
392
390
|
pageSize: 'A4' | 'A5' | '16K'
|
|
393
391
|
orientation: 'portrait' | 'landscape'
|
|
392
|
+
baseUnit?: number // 缩放因子(默认: 1)
|
|
394
393
|
header: {
|
|
395
394
|
hospital: string
|
|
396
395
|
department?: string
|
|
397
396
|
title: string
|
|
398
|
-
subtitle?: string
|
|
399
397
|
}
|
|
400
398
|
sections: PrintSection[]
|
|
401
399
|
footer?: {
|
|
402
400
|
showPageNumber?: boolean
|
|
403
|
-
pageNumberFormat?: string
|
|
404
401
|
notes?: string
|
|
405
402
|
}
|
|
406
403
|
}
|
|
407
404
|
```
|
|
408
405
|
|
|
409
|
-
## 区块类型
|
|
410
|
-
|
|
411
|
-
| 类型 | 描述 | 使用场景 |
|
|
412
|
-
|------|------|----------|
|
|
413
|
-
| `info-grid` | 键值对网格布局 | 患者基本信息、人口统计 |
|
|
414
|
-
| `table` | 带列的数据表格 | 护理记录、用药日志 |
|
|
415
|
-
| `checkbox-grid` | 复选框选项网格 | 评估清单、症状选择 |
|
|
416
|
-
| `signature-area` | 带标签的签名字段 | 审批、确认签字 |
|
|
417
|
-
| `notes` | 静态文本内容 | 说明、免责声明 |
|
|
418
|
-
| `free-text` | 多行文本输入 | 备注、观察记录 |
|
|
419
|
-
|
|
420
|
-
### 信息网格区块
|
|
421
|
-
|
|
422
|
-
```typescript
|
|
423
|
-
{
|
|
424
|
-
type: 'info-grid',
|
|
425
|
-
config: {
|
|
426
|
-
columns: 4,
|
|
427
|
-
rows: [
|
|
428
|
-
{
|
|
429
|
-
cells: [
|
|
430
|
-
{ label: '姓名', field: 'name', type: 'text' },
|
|
431
|
-
{ label: '年龄', field: 'age', type: 'number', span: 1 },
|
|
432
|
-
{ label: '日期', field: 'date', type: 'date' },
|
|
433
|
-
{ label: '状态', field: 'status', type: 'checkbox', options: ['在院'] }
|
|
434
|
-
]
|
|
435
|
-
}
|
|
436
|
-
]
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
### 表格区块
|
|
442
|
-
|
|
443
|
-
```typescript
|
|
444
|
-
{
|
|
445
|
-
type: 'table',
|
|
446
|
-
title: '护理记录',
|
|
447
|
-
config: {
|
|
448
|
-
dataField: 'nursingRecords',
|
|
449
|
-
columns: [
|
|
450
|
-
{ header: '日期', field: 'date', type: 'date', width: '15%' },
|
|
451
|
-
{ header: '时间', field: 'time', type: 'text', width: '10%' },
|
|
452
|
-
{ header: '体温', field: 'temperature', type: 'number', width: '15%' },
|
|
453
|
-
{ header: '备注', field: 'notes', type: 'text' }
|
|
454
|
-
]
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
### 复选框网格区块
|
|
460
|
-
|
|
461
|
-
```typescript
|
|
462
|
-
{
|
|
463
|
-
type: 'checkbox-grid',
|
|
464
|
-
title: '症状评估',
|
|
465
|
-
config: {
|
|
466
|
-
field: 'symptoms',
|
|
467
|
-
columns: 4,
|
|
468
|
-
options: [
|
|
469
|
-
{ value: 'fever', label: '发热' },
|
|
470
|
-
{ value: 'headache', label: '头痛' },
|
|
471
|
-
{ value: 'fatigue', label: '乏力' },
|
|
472
|
-
{ value: 'nausea', label: '恶心' }
|
|
473
|
-
]
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
### 签名区域区块
|
|
479
|
-
|
|
480
|
-
```typescript
|
|
481
|
-
{
|
|
482
|
-
type: 'signature-area',
|
|
483
|
-
config: {
|
|
484
|
-
fields: [
|
|
485
|
-
{ label: '患者签名', field: 'patientSignature' },
|
|
486
|
-
{ label: '护士签名', field: 'nurseSignature' },
|
|
487
|
-
{ label: '日期', field: 'signatureDate', type: 'date' }
|
|
488
|
-
]
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
## CSS 隔离
|
|
494
|
-
|
|
495
|
-
为确保跨环境一致渲染,请使用隔离模式:
|
|
496
|
-
|
|
497
|
-
```typescript
|
|
498
|
-
import {
|
|
499
|
-
renderToIsolatedHtml,
|
|
500
|
-
CSS_NAMESPACE,
|
|
501
|
-
ISOLATION_ROOT_CLASS,
|
|
502
|
-
namespaceClass,
|
|
503
|
-
namespaceClasses
|
|
504
|
-
} from 'medical-form-printer'
|
|
505
|
-
|
|
506
|
-
// CSS_NAMESPACE = 'mpr'
|
|
507
|
-
// ISOLATION_ROOT_CLASS = 'mpr-root'
|
|
508
|
-
|
|
509
|
-
// 命名空间工具
|
|
510
|
-
namespaceClass('header') // 'mpr-header'
|
|
511
|
-
namespaceClasses(['header', 'footer']) // ['mpr-header', 'mpr-footer']
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
隔离模式提供:
|
|
515
|
-
- 所有类以 `mpr-` 命名空间为前缀
|
|
516
|
-
- 内嵌思源宋体字体(CJK 字符子集)
|
|
517
|
-
- CSS 隔离(`contain: layout style`)
|
|
518
|
-
- 无论宿主页面样式如何,都能保持一致渲染
|
|
519
|
-
|
|
520
406
|
## 示例
|
|
521
407
|
|
|
522
|
-
|
|
408
|
+
参见 [examples](./examples) 目录:
|
|
523
409
|
|
|
524
|
-
- [浏览器示例](./examples/browser) - 原生 HTML/JS
|
|
525
|
-
- [Node.js 示例](./examples/node) - PDF
|
|
410
|
+
- [浏览器示例](./examples/browser) - 原生 HTML/JS
|
|
411
|
+
- [Node.js 示例](./examples/node) - PDF 生成
|
|
526
412
|
|
|
527
413
|
## Storybook
|
|
528
414
|
|
|
529
|
-
通过 Storybook 可以查看交互式组件文档:
|
|
530
|
-
|
|
531
415
|
```bash
|
|
532
416
|
npm run storybook
|
|
533
417
|
```
|
|
534
418
|
|
|
535
419
|
## 贡献
|
|
536
420
|
|
|
537
|
-
|
|
421
|
+
参见 [CONTRIBUTING.md](./CONTRIBUTING.md)
|
|
538
422
|
|
|
539
423
|
## 许可证
|
|
540
424
|
|
|
541
|
-
[MIT](./LICENSE)
|
|
425
|
+
[MIT](./LICENSE)
|
|
542
426
|
|
|
543
427
|
## 链接
|
|
544
428
|
|
|
545
|
-
- [GitHub
|
|
546
|
-
- [npm
|
|
547
|
-
- [问题追踪](https://github.com/wangchengshi-ship-it/medical-form-printer/issues)
|
|
429
|
+
- [GitHub](https://github.com/wangchengshi-ship-it/medical-form-printer)
|
|
430
|
+
- [npm](https://www.npmjs.com/package/medical-form-printer)
|
|
548
431
|
- [更新日志](./CHANGELOG.md)
|