medical-form-printer 0.1.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.
@@ -0,0 +1,548 @@
1
+ # medical-form-printer
2
+
3
+ [![npm version](https://img.shields.io/npm/v/medical-form-printer.svg)](https://www.npmjs.com/package/medical-form-printer)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js Version](https://img.shields.io/node/v/medical-form-printer.svg)](https://nodejs.org)
6
+
7
+ 一个基于 Schema 驱动的医疗表单打印渲染库,将结构化表单数据转换为可打印的 HTML 和 PDF 文档。专为医疗健康应用设计,支持复杂布局、智能分页和跨环境一致性渲染。
8
+
9
+ [English Documentation](./README.md)
10
+
11
+ ## 特性
12
+
13
+ - 🖨️ **双环境支持** - 同时支持浏览器和 Node.js 环境
14
+ - 📄 **丰富的区块类型** - 信息网格、数据表格、复选框网格、签名区域、备注等
15
+ - 🎨 **主题定制** - 完全可定制的字体、颜色、间距和尺寸
16
+ - 📑 **PDF 生成** - 通过 Puppeteer 生成高保真 PDF(Node.js)
17
+ - 🔗 **PDF 合并** - 将多个文档合并为单个 PDF
18
+ - 📐 **智能分页** - 自动分页,支持表头重复和溢出处理
19
+ - 🔒 **CSS 隔离** - 内嵌字体和命名空间样式,确保一致渲染
20
+ - 🔌 **可扩展** - 注册自定义区块渲染器以支持特殊内容
21
+ - 📦 **TypeScript 优先** - 完整的类型定义和 JSDoc 文档
22
+
23
+ ## 安装
24
+
25
+ ```bash
26
+ # npm
27
+ 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
+
39
+ 如需在 Node.js 中生成 PDF,请安装 Puppeteer 作为对等依赖:
40
+
41
+ ```bash
42
+ npm install puppeteer
43
+ ```
44
+
45
+ ## 快速开始
46
+
47
+ ### 浏览器使用
48
+
49
+ ```typescript
50
+ import { renderToHtml } from 'medical-form-printer'
51
+
52
+ const printSchema = {
53
+ pageSize: 'A4',
54
+ orientation: 'portrait',
55
+ header: {
56
+ hospital: '示例医院',
57
+ department: '产后康复中心',
58
+ title: '患者评估表',
59
+ },
60
+ sections: [
61
+ {
62
+ type: 'info-grid',
63
+ config: {
64
+ columns: 4,
65
+ rows: [
66
+ {
67
+ cells: [
68
+ { label: '姓名', field: 'name', type: 'text' },
69
+ { label: '年龄', field: 'age', type: 'number' },
70
+ { label: '日期', field: 'admissionDate', type: 'date' },
71
+ { label: '房间', field: 'roomNumber', type: 'text' },
72
+ ]
73
+ }
74
+ ]
75
+ }
76
+ }
77
+ ],
78
+ footer: {
79
+ showPageNumber: true
80
+ }
81
+ }
82
+
83
+ const formData = {
84
+ name: 'Jane Doe',
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
97
+ ```
98
+
99
+ ### Node.js 使用(PDF 生成)
100
+
101
+ ```typescript
102
+ import { renderToPdf, mergePdfs } from 'medical-form-printer/node'
103
+ import fs from 'fs'
104
+
105
+ // 生成单个 PDF
106
+ const pdfBuffer = await renderToPdf(printSchema, formData, {
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)
117
+ ```
118
+
119
+ ## API 参考
120
+
121
+ ### 核心渲染
122
+
123
+ #### `renderToHtml(schema, data, options?)`
124
+
125
+ 将打印 Schema 和表单数据渲染为 HTML 字符串。
126
+
127
+ ```typescript
128
+ import { renderToHtml } from 'medical-form-printer'
129
+
130
+ const html = renderToHtml(printSchema, formData, {
131
+ theme: customTheme,
132
+ watermark: '草稿',
133
+ watermarkOpacity: 0.1
134
+ })
135
+ ```
136
+
137
+ **参数:**
138
+ - `schema: PrintSchema` - 定义布局和区块的打印 Schema
139
+ - `data: FormData` - 要渲染的表单数据
140
+ - `options?: RenderOptions` - 可选的渲染配置
141
+
142
+ **返回:** `string` - 完整的 HTML 文档
143
+
144
+ #### `renderToIsolatedHtml(schema, data, options?)`
145
+
146
+ 使用 CSS 隔离模式渲染,确保跨环境样式一致性。
147
+
148
+ ```typescript
149
+ import { renderToIsolatedHtml } from 'medical-form-printer'
150
+
151
+ const html = renderToIsolatedHtml(printSchema, formData, {
152
+ watermark: '内部使用'
153
+ })
154
+ ```
155
+
156
+ 所有内容都包装在隔离容器中,具有:
157
+ - 命名空间 CSS 类(以 `mpr-` 为前缀)
158
+ - 内嵌思源宋体字体
159
+ - 样式隔离,确保可预测的渲染效果
160
+
161
+ #### `renderToIsolatedFragment(schema, data, options?)`
162
+
163
+ 渲染隔离的 HTML 片段,用于嵌入现有页面。
164
+
165
+ ```typescript
166
+ import { renderToIsolatedFragment } from 'medical-form-printer'
167
+
168
+ const fragment = renderToIsolatedFragment(printSchema, formData)
169
+ document.getElementById('preview').innerHTML = fragment
170
+ ```
171
+
172
+ ### PDF 生成(Node.js)
173
+
174
+ #### `renderToPdf(schema, data, options?)`
175
+
176
+ 从打印 Schema 生成 PDF 缓冲区。
177
+
178
+ ```typescript
179
+ import { renderToPdf } from 'medical-form-printer/node'
180
+
181
+ const pdfBuffer = await renderToPdf(printSchema, formData, {
182
+ watermark: '机密',
183
+ pdfOptions: {
184
+ format: 'A4',
185
+ printBackground: true
186
+ }
187
+ })
188
+ ```
189
+
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。
200
+
201
+ ```typescript
202
+ import { mergePdfs } from 'medical-form-printer/node'
203
+
204
+ const mergedPdf = await mergePdfs([
205
+ { schema: schema1, data: data1 },
206
+ { schema: schema2, data: data2 },
207
+ ], {
208
+ watermark: '完整记录'
209
+ })
210
+ ```
211
+
212
+ ### 自定义区块渲染器
213
+
214
+ #### `registerSectionRenderer(type, renderer)`
215
+
216
+ 注册自定义区块渲染器以支持特殊内容。
217
+
218
+ ```typescript
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
+ ```
231
+
232
+ #### `getSectionRenderer(type)`
233
+
234
+ 获取已注册的区块渲染器。
235
+
236
+ ```typescript
237
+ import { getSectionRenderer } from 'medical-form-printer'
238
+
239
+ const renderer = getSectionRenderer('info-grid')
240
+ ```
241
+
242
+ ### 分页
243
+
244
+ #### `renderPaginatedHtml(config)`
245
+
246
+ 使用智能分页渲染多页内容。
247
+
248
+ ```typescript
249
+ import {
250
+ renderPaginatedHtml,
251
+ calculatePageBreaks,
252
+ PAGE_A4
253
+ } from 'medical-form-printer'
254
+
255
+ const pageBreaks = calculatePageBreaks(measuredItems, {
256
+ pageHeight: PAGE_A4.height,
257
+ headerHeight: 60,
258
+ footerHeight: 40,
259
+ repeatTableHeaders: true
260
+ })
261
+
262
+ const html = renderPaginatedHtml({
263
+ schema: printSchema,
264
+ data: formData,
265
+ pageBreakResult: pageBreaks,
266
+ measuredItems: items,
267
+ config: {
268
+ isolated: true,
269
+ showHeaderOnEachPage: true,
270
+ continuationSuffix: '(续)'
271
+ }
272
+ })
273
+ ```
274
+
275
+ #### 页面尺寸预设
276
+
277
+ ```typescript
278
+ import { PAGE_A4, PAGE_A5, PAGE_16K, PAGE_PRESETS } from 'medical-form-printer'
279
+
280
+ // PAGE_A4: { width: 210, height: 297 } (mm)
281
+ // PAGE_A5: { width: 148, height: 210 } (mm)
282
+ // PAGE_16K: { width: 185, height: 260 } (mm)
283
+ ```
284
+
285
+ #### 单位转换
286
+
287
+ ```typescript
288
+ import { mmToPx, pxToMm, mmToPt, ptToMm } from 'medical-form-printer'
289
+
290
+ const heightPx = mmToPx(297) // 297mm → 像素
291
+ const heightMm = pxToMm(1123) // 像素 → mm
292
+ ```
293
+
294
+ ### 样式
295
+
296
+ #### `generateCss(theme?)`
297
+
298
+ 生成打印渲染的 CSS 样式。
299
+
300
+ ```typescript
301
+ import { generateCss, defaultTheme } from 'medical-form-printer'
302
+
303
+ const css = generateCss(defaultTheme)
304
+ ```
305
+
306
+ #### `generateIsolatedCss(theme?)`
307
+
308
+ 生成带有内嵌字体和命名空间类的隔离 CSS。
309
+
310
+ ```typescript
311
+ import { generateIsolatedCss } from 'medical-form-printer'
312
+
313
+ const css = generateIsolatedCss()
314
+ // 包含 @font-face、隔离容器和所有组件样式
315
+ ```
316
+
317
+ #### 主题定制
318
+
319
+ ```typescript
320
+ import { renderToHtml, mergeTheme, defaultTheme } from 'medical-form-printer'
321
+
322
+ const customTheme = mergeTheme(defaultTheme, {
323
+ fonts: {
324
+ body: '"Microsoft YaHei", "PingFang SC", sans-serif',
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
+ }
341
+ })
342
+
343
+ const html = renderToHtml(schema, data, { theme: customTheme })
344
+ ```
345
+
346
+ ### 格式化工具
347
+
348
+ ```typescript
349
+ import {
350
+ formatDate,
351
+ formatBoolean,
352
+ formatNumber,
353
+ formatValue,
354
+ isChecked
355
+ } from 'medical-form-printer'
356
+
357
+ formatDate('2024-01-15') // '2024-01-15'
358
+ formatDate('2024-01-15', { format: 'YYYY年MM月DD日' }) // '2024年01月15日'
359
+ formatBoolean(true) // '✓'
360
+ formatBoolean(false) // '✗'
361
+ formatNumber(1234.5, { decimals: 2 }) // '1234.50'
362
+ isChecked('yes', ['yes', 'true']) // true
363
+ ```
364
+
365
+ ### HTML 构建工具
366
+
367
+ ```typescript
368
+ import {
369
+ HtmlBuilder,
370
+ h,
371
+ fragment,
372
+ when,
373
+ each,
374
+ escapeHtml
375
+ } from 'medical-form-printer'
376
+
377
+ // 流式 HTML 构建
378
+ const html = h('div', { class: 'container' },
379
+ h('h1', {}, '标题'),
380
+ when(showContent, () => h('p', {}, '内容')),
381
+ each(items, (item) => h('li', {}, item.name))
382
+ )
383
+
384
+ // 安全的 HTML 转义
385
+ const safe = escapeHtml('<script>alert("xss")</script>')
386
+ ```
387
+
388
+ ## PrintSchema 结构
389
+
390
+ ```typescript
391
+ interface PrintSchema {
392
+ pageSize: 'A4' | 'A5' | '16K'
393
+ orientation: 'portrait' | 'landscape'
394
+ header: {
395
+ hospital: string
396
+ department?: string
397
+ title: string
398
+ subtitle?: string
399
+ }
400
+ sections: PrintSection[]
401
+ footer?: {
402
+ showPageNumber?: boolean
403
+ pageNumberFormat?: string
404
+ notes?: string
405
+ }
406
+ }
407
+ ```
408
+
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
+ ## 示例
521
+
522
+ 请参阅 [examples](./examples) 目录获取完整的工作示例:
523
+
524
+ - [浏览器示例](./examples/browser) - 原生 HTML/JS 使用
525
+ - [Node.js 示例](./examples/node) - PDF 生成与文件输出
526
+
527
+ ## Storybook
528
+
529
+ 通过 Storybook 可以查看交互式组件文档:
530
+
531
+ ```bash
532
+ npm run storybook
533
+ ```
534
+
535
+ ## 贡献
536
+
537
+ 欢迎贡献!请阅读我们的[贡献指南](./CONTRIBUTING.md)了解行为准则和提交 Pull Request 的流程。
538
+
539
+ ## 许可证
540
+
541
+ [MIT](./LICENSE) © 2024
542
+
543
+ ## 链接
544
+
545
+ - [GitHub 仓库](https://github.com/wangchengshi-ship-it/medical-form-printer)
546
+ - [npm 包](https://www.npmjs.com/package/medical-form-printer)
547
+ - [问题追踪](https://github.com/wangchengshi-ship-it/medical-form-printer/issues)
548
+ - [更新日志](./CHANGELOG.md)