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.md
CHANGED
|
@@ -8,6 +8,17 @@ A schema-driven medical form print renderer that transforms structured form data
|
|
|
8
8
|
|
|
9
9
|
[中文文档](./README.zh-CN.md)
|
|
10
10
|
|
|
11
|
+
## Table of Contents
|
|
12
|
+
|
|
13
|
+
- [Features](#features)
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Quick Start](#quick-start)
|
|
16
|
+
- [Design Philosophy](#design-philosophy)
|
|
17
|
+
- [Section Types](#section-types)
|
|
18
|
+
- [API Reference](#api-reference)
|
|
19
|
+
- [CSS Isolation](#css-isolation)
|
|
20
|
+
- [Examples](#examples)
|
|
21
|
+
|
|
11
22
|
## Features
|
|
12
23
|
|
|
13
24
|
- 🖨️ **Dual Environment** - Works seamlessly in both browser and Node.js
|
|
@@ -23,17 +34,7 @@ A schema-driven medical form print renderer that transforms structured form data
|
|
|
23
34
|
## Installation
|
|
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
40
|
For PDF generation in Node.js, install Puppeteer as a peer dependency:
|
|
@@ -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: 'Sample Hospital',
|
|
57
|
-
department: 'Postpartum Care Center',
|
|
58
58
|
title: 'Patient Assessment Form',
|
|
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: 'Name', field: 'name' },
|
|
68
|
+
{ label: 'Age', field: 'age' },
|
|
69
|
+
{ label: 'Date', field: 'date', type: 'date' },
|
|
70
|
+
{ label: 'Room', 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
|
-
// Render to HTML
|
|
91
|
-
const html = renderToHtml(printSchema, formData, {
|
|
92
|
-
watermark: 'Internal Use Only'
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
// Display in iframe or div
|
|
96
|
-
document.getElementById('preview').innerHTML = html
|
|
78
|
+
const data = { name: 'Jane Doe', age: 28, date: '2024-01-15', room: 'A-101' }
|
|
79
|
+
const html = renderToHtml(schema, data)
|
|
97
80
|
```
|
|
98
81
|
|
|
99
|
-
### Node.js Usage (PDF
|
|
82
|
+
### Node.js Usage (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: 'Confidential'
|
|
108
|
-
})
|
|
109
|
-
fs.writeFileSync('assessment.pdf', pdfBuffer)
|
|
110
|
-
|
|
111
|
-
// Merge multiple forms into one 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
|
+
## Design Philosophy
|
|
120
93
|
|
|
121
|
-
###
|
|
94
|
+
### Why Flat Sections Instead of Nested Components?
|
|
122
95
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Renders a print schema with form data to an HTML string.
|
|
126
|
-
|
|
127
|
-
```typescript
|
|
128
|
-
import { renderToHtml } from 'medical-form-printer'
|
|
96
|
+
Many document rendering systems use deeply nested component hierarchies:
|
|
129
97
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
watermark: 'Draft',
|
|
133
|
-
watermarkOpacity: 0.1
|
|
134
|
-
})
|
|
98
|
+
```
|
|
99
|
+
Document → Page → Container → Row → Cell → Element
|
|
135
100
|
```
|
|
136
101
|
|
|
137
|
-
**
|
|
138
|
-
- `schema: PrintSchema` - The print schema defining layout and sections
|
|
139
|
-
- `data: FormData` - The form data to render
|
|
140
|
-
- `options?: RenderOptions` - Optional rendering configuration
|
|
102
|
+
We deliberately chose a **flat section-based model**. Here's why:
|
|
141
103
|
|
|
142
|
-
|
|
104
|
+
#### 1. Print Documents ≠ UI Components
|
|
143
105
|
|
|
144
|
-
|
|
106
|
+
Print documents are **static output**. A medical form doesn't need a `<Button>` that responds to clicks—it needs a checkbox symbol (☑/□) at the right position. Nested component trees add overhead without benefit.
|
|
145
107
|
|
|
146
|
-
|
|
108
|
+
#### 2. Domain-Driven Design
|
|
147
109
|
|
|
148
|
-
|
|
149
|
-
import { renderToIsolatedHtml } from 'medical-form-printer'
|
|
110
|
+
Sections map directly to **real-world medical form concepts**:
|
|
150
111
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
112
|
+
| Section Type | Real-World Concept |
|
|
113
|
+
|--------------|-------------------|
|
|
114
|
+
| `info-grid` | Patient demographics block |
|
|
115
|
+
| `table` | Nursing records log |
|
|
116
|
+
| `checkbox-grid` | Symptom checklist |
|
|
117
|
+
| `signature-area` | Approval signatures |
|
|
155
118
|
|
|
156
|
-
|
|
157
|
-
- Namespaced CSS classes (prefixed with `mpr-`)
|
|
158
|
-
- Embedded Source Han Serif SC font
|
|
159
|
-
- Style containment for predictable rendering
|
|
119
|
+
Medical staff think in these terms, not abstract "containers" and "elements".
|
|
160
120
|
|
|
161
|
-
####
|
|
121
|
+
#### 3. Pagination-Friendly Architecture
|
|
162
122
|
|
|
163
|
-
|
|
123
|
+
Flat sections enable **predictable pagination**:
|
|
164
124
|
|
|
165
125
|
```typescript
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
126
|
+
type MeasurableItemType =
|
|
127
|
+
| 'header' // Page header - measured once
|
|
128
|
+
| 'section' // Atomic block - never split
|
|
129
|
+
| 'table-header' // Repeats on continuation pages
|
|
130
|
+
| 'table-row' // Can be paginated individually
|
|
131
|
+
| 'signature' // Usually pinned to last page
|
|
132
|
+
| 'footer' // Page footer - measured once
|
|
170
133
|
```
|
|
171
134
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
#### `renderToPdf(schema, data, options?)`
|
|
175
|
-
|
|
176
|
-
Generates a PDF buffer from a print schema.
|
|
135
|
+
#### 4. Schema Simplicity
|
|
177
136
|
|
|
178
137
|
```typescript
|
|
179
|
-
|
|
138
|
+
// ❌ Nested approach (verbose)
|
|
139
|
+
{
|
|
140
|
+
type: 'container',
|
|
141
|
+
children: [{
|
|
142
|
+
type: 'container',
|
|
143
|
+
children: [
|
|
144
|
+
{ type: 'label', text: 'Name:' },
|
|
145
|
+
{ type: 'field', binding: 'name' }
|
|
146
|
+
]
|
|
147
|
+
}]
|
|
148
|
+
}
|
|
180
149
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
150
|
+
// ✅ Flat approach (concise)
|
|
151
|
+
{
|
|
152
|
+
type: 'info-grid',
|
|
153
|
+
config: {
|
|
154
|
+
rows: [{ cells: [{ label: 'Name', field: 'name' }] }]
|
|
186
155
|
}
|
|
187
|
-
}
|
|
156
|
+
}
|
|
188
157
|
```
|
|
189
158
|
|
|
190
|
-
|
|
191
|
-
- `schema: PrintSchema` - The print schema
|
|
192
|
-
- `data: FormData` - The form data
|
|
193
|
-
- `options?: RenderOptions & { pdfOptions?: PdfOptions }` - Rendering and PDF options
|
|
194
|
-
|
|
195
|
-
**Returns:** `Promise<Buffer>` - PDF file buffer
|
|
196
|
-
|
|
197
|
-
#### `mergePdfs(documents, options?)`
|
|
198
|
-
|
|
199
|
-
Merges multiple documents into a single PDF.
|
|
159
|
+
#### 5. Simple Extensibility
|
|
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: 'Complete Record'
|
|
162
|
+
registerSectionRenderer('vital-signs-chart', (config, data) => {
|
|
163
|
+
return '<div class="chart">...</div>'
|
|
209
164
|
})
|
|
210
165
|
```
|
|
211
166
|
|
|
212
|
-
|
|
167
|
+
No abstract base classes or visitor patterns needed.
|
|
213
168
|
|
|
214
|
-
|
|
169
|
+
### Trade-offs
|
|
215
170
|
|
|
216
|
-
|
|
171
|
+
This design optimizes for **print document generation**. For deeply nested layouts or interactive components, consider general-purpose HTML templating or UI frameworks.
|
|
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
|
-
<!-- Custom chart rendering -->
|
|
227
|
-
</div>
|
|
228
|
-
`
|
|
229
|
-
})
|
|
230
|
-
```
|
|
173
|
+
## Section Types
|
|
231
174
|
|
|
232
|
-
|
|
175
|
+
| Type | Description | Use Case |
|
|
176
|
+
|------|-------------|----------|
|
|
177
|
+
| `info-grid` | Grid layout for key-value pairs | Patient demographics |
|
|
178
|
+
| `table` | Data table with columns | Nursing records |
|
|
179
|
+
| `checkbox-grid` | Grid of checkbox options | Symptom checklists |
|
|
180
|
+
| `signature-area` | Signature fields | Approvals |
|
|
181
|
+
| `notes` | Static text content | Instructions |
|
|
182
|
+
| `free-text` | Multi-line text input | Comments |
|
|
233
183
|
|
|
234
|
-
|
|
184
|
+
### Info Grid
|
|
235
185
|
|
|
236
186
|
```typescript
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
187
|
+
{
|
|
188
|
+
type: 'info-grid',
|
|
189
|
+
config: {
|
|
190
|
+
columns: 4,
|
|
191
|
+
rows: [{
|
|
192
|
+
cells: [
|
|
193
|
+
{ label: 'Name', field: 'name' },
|
|
194
|
+
{ label: 'Age', field: 'age', type: 'number' },
|
|
195
|
+
{ label: 'Status', field: 'status', type: 'checkbox', options: ['Active'] }
|
|
196
|
+
]
|
|
197
|
+
}]
|
|
198
|
+
}
|
|
199
|
+
}
|
|
240
200
|
```
|
|
241
201
|
|
|
242
|
-
###
|
|
243
|
-
|
|
244
|
-
#### `renderPaginatedHtml(config)`
|
|
245
|
-
|
|
246
|
-
Renders multi-page content with smart pagination.
|
|
202
|
+
### Table
|
|
247
203
|
|
|
248
204
|
```typescript
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
205
|
+
{
|
|
206
|
+
type: 'table',
|
|
207
|
+
title: 'Nursing Records',
|
|
208
|
+
config: {
|
|
209
|
+
dataField: 'records',
|
|
210
|
+
columns: [
|
|
211
|
+
{ header: 'Date', field: 'date', type: 'date', width: '20%' },
|
|
212
|
+
{ header: 'Notes', field: 'notes' }
|
|
213
|
+
]
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
254
217
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
footerHeight: 40,
|
|
259
|
-
repeatTableHeaders: true
|
|
260
|
-
})
|
|
218
|
+
#### Multi-Row Headers
|
|
219
|
+
|
|
220
|
+
Tables support complex multi-row headers with colspan and rowspan:
|
|
261
221
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
measuredItems: items,
|
|
222
|
+
```typescript
|
|
223
|
+
{
|
|
224
|
+
type: 'table',
|
|
225
|
+
title: 'Vital Signs',
|
|
267
226
|
config: {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
227
|
+
dataField: 'vitalSigns',
|
|
228
|
+
columns: [
|
|
229
|
+
{ header: 'Date', field: 'date', type: 'date' },
|
|
230
|
+
{ header: 'Systolic', field: 'systolic', type: 'number' },
|
|
231
|
+
{ header: 'Diastolic', field: 'diastolic', type: 'number' },
|
|
232
|
+
{ header: 'Temperature', field: 'temperature', type: 'number' },
|
|
233
|
+
],
|
|
234
|
+
headerRows: [
|
|
235
|
+
{
|
|
236
|
+
cells: [
|
|
237
|
+
{ text: 'Date', rowspan: 2 },
|
|
238
|
+
{ text: 'Blood Pressure (mmHg)', colspan: 2 },
|
|
239
|
+
{ text: 'Temperature (℃)', rowspan: 2 },
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
cells: [
|
|
244
|
+
{ text: 'Systolic', field: 'systolic' },
|
|
245
|
+
{ text: 'Diastolic', field: 'diastolic' },
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
]
|
|
271
249
|
}
|
|
272
|
-
}
|
|
250
|
+
}
|
|
273
251
|
```
|
|
274
252
|
|
|
275
|
-
|
|
253
|
+
### Checkbox Grid
|
|
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: 'Fever' },
|
|
263
|
+
{ value: 'headache', label: 'Headache' }
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
}
|
|
283
267
|
```
|
|
284
268
|
|
|
285
|
-
|
|
269
|
+
### Signature Area
|
|
286
270
|
|
|
287
271
|
```typescript
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
272
|
+
{
|
|
273
|
+
type: 'signature-area',
|
|
274
|
+
config: {
|
|
275
|
+
fields: [
|
|
276
|
+
{ label: 'Patient', field: 'patientSig' },
|
|
277
|
+
{ label: 'Date', field: 'sigDate', type: 'date' }
|
|
278
|
+
]
|
|
279
|
+
}
|
|
280
|
+
}
|
|
292
281
|
```
|
|
293
282
|
|
|
294
|
-
|
|
283
|
+
## API Reference
|
|
284
|
+
|
|
285
|
+
### Core Rendering
|
|
295
286
|
|
|
296
|
-
|
|
287
|
+
| Function | Description |
|
|
288
|
+
|----------|-------------|
|
|
289
|
+
| `renderToHtml(schema, data, options?)` | Render to HTML string |
|
|
290
|
+
| `renderToIsolatedHtml(schema, data, options?)` | Render with CSS isolation |
|
|
291
|
+
| `renderToIsolatedFragment(schema, data, options?)` | Render isolated fragment for embedding |
|
|
297
292
|
|
|
298
|
-
|
|
293
|
+
### PDF Generation (Node.js)
|
|
299
294
|
|
|
300
295
|
```typescript
|
|
301
|
-
import {
|
|
296
|
+
import { renderToPdf, mergePdfs } from 'medical-form-printer/node'
|
|
297
|
+
|
|
298
|
+
// Single PDF
|
|
299
|
+
const pdf = await renderToPdf(schema, data, { watermark: 'Draft' })
|
|
302
300
|
|
|
303
|
-
|
|
301
|
+
// Merge multiple documents
|
|
302
|
+
const merged = await mergePdfs([
|
|
303
|
+
{ schema: schema1, data: data1 },
|
|
304
|
+
{ schema: schema2, data: data2 }
|
|
305
|
+
])
|
|
304
306
|
```
|
|
305
307
|
|
|
306
|
-
|
|
308
|
+
### Pagination (Strategy Pattern)
|
|
307
309
|
|
|
308
|
-
|
|
310
|
+
```typescript
|
|
311
|
+
import {
|
|
312
|
+
createDefaultPaginationContext,
|
|
313
|
+
SmartPaginationStrategy
|
|
314
|
+
} from 'medical-form-printer'
|
|
315
|
+
|
|
316
|
+
// Automatic strategy selection
|
|
317
|
+
const context = createDefaultPaginationContext()
|
|
318
|
+
const html = context.render(schema, data, { isolated: true })
|
|
319
|
+
|
|
320
|
+
// Or use specific strategy
|
|
321
|
+
const strategy = new SmartPaginationStrategy()
|
|
322
|
+
if (strategy.shouldApply(schema)) {
|
|
323
|
+
const html = strategy.render(schema, data)
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Custom Section Renderers
|
|
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
|
+
### Theme Customization
|
|
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
|
+
### Page Sizes & Units
|
|
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 → pixels
|
|
344
357
|
```
|
|
345
358
|
|
|
346
359
|
### Formatters
|
|
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 Isolation
|
|
366
371
|
|
|
367
|
-
|
|
368
|
-
import {
|
|
369
|
-
HtmlBuilder,
|
|
370
|
-
h,
|
|
371
|
-
fragment,
|
|
372
|
-
when,
|
|
373
|
-
each,
|
|
374
|
-
escapeHtml
|
|
375
|
-
} from 'medical-form-printer'
|
|
372
|
+
For consistent cross-environment rendering:
|
|
376
373
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
h('h1', {}, 'Title'),
|
|
380
|
-
when(showContent, () => h('p', {}, 'Content')),
|
|
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' (all classes prefixed with mpr-)
|
|
386
379
|
```
|
|
387
380
|
|
|
381
|
+
Isolation mode provides:
|
|
382
|
+
- Namespaced CSS classes (`mpr-` prefix)
|
|
383
|
+
- Embedded Source Han Serif SC font
|
|
384
|
+
- CSS containment for predictable rendering
|
|
385
|
+
|
|
388
386
|
## PrintSchema Structure
|
|
389
387
|
|
|
390
388
|
```typescript
|
|
391
389
|
interface PrintSchema {
|
|
392
390
|
pageSize: 'A4' | 'A5' | '16K'
|
|
393
391
|
orientation: 'portrait' | 'landscape'
|
|
392
|
+
baseUnit?: number // Scaling factor (default: 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
|
-
## Section Types
|
|
410
|
-
|
|
411
|
-
| Type | Description | Use Case |
|
|
412
|
-
|------|-------------|----------|
|
|
413
|
-
| `info-grid` | Grid layout for key-value pairs | Patient demographics, basic info |
|
|
414
|
-
| `table` | Data table with columns | Nursing records, medication logs |
|
|
415
|
-
| `checkbox-grid` | Grid of checkbox options | Assessment checklists, symptoms |
|
|
416
|
-
| `signature-area` | Signature fields with labels | Approvals, acknowledgments |
|
|
417
|
-
| `notes` | Static text content | Instructions, disclaimers |
|
|
418
|
-
| `free-text` | Multi-line text input | Comments, observations |
|
|
419
|
-
|
|
420
|
-
### Info Grid Section
|
|
421
|
-
|
|
422
|
-
```typescript
|
|
423
|
-
{
|
|
424
|
-
type: 'info-grid',
|
|
425
|
-
config: {
|
|
426
|
-
columns: 4,
|
|
427
|
-
rows: [
|
|
428
|
-
{
|
|
429
|
-
cells: [
|
|
430
|
-
{ label: 'Name', field: 'name', type: 'text' },
|
|
431
|
-
{ label: 'Age', field: 'age', type: 'number', span: 1 },
|
|
432
|
-
{ label: 'Date', field: 'date', type: 'date' },
|
|
433
|
-
{ label: 'Status', field: 'status', type: 'checkbox', options: ['Active'] }
|
|
434
|
-
]
|
|
435
|
-
}
|
|
436
|
-
]
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
### Table Section
|
|
442
|
-
|
|
443
|
-
```typescript
|
|
444
|
-
{
|
|
445
|
-
type: 'table',
|
|
446
|
-
title: 'Nursing Records',
|
|
447
|
-
config: {
|
|
448
|
-
dataField: 'nursingRecords',
|
|
449
|
-
columns: [
|
|
450
|
-
{ header: 'Date', field: 'date', type: 'date', width: '15%' },
|
|
451
|
-
{ header: 'Time', field: 'time', type: 'text', width: '10%' },
|
|
452
|
-
{ header: 'Temperature', field: 'temperature', type: 'number', width: '15%' },
|
|
453
|
-
{ header: 'Notes', field: 'notes', type: 'text' }
|
|
454
|
-
]
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
```
|
|
458
|
-
|
|
459
|
-
### Checkbox Grid Section
|
|
460
|
-
|
|
461
|
-
```typescript
|
|
462
|
-
{
|
|
463
|
-
type: 'checkbox-grid',
|
|
464
|
-
title: 'Symptoms Assessment',
|
|
465
|
-
config: {
|
|
466
|
-
field: 'symptoms',
|
|
467
|
-
columns: 4,
|
|
468
|
-
options: [
|
|
469
|
-
{ value: 'fever', label: 'Fever' },
|
|
470
|
-
{ value: 'headache', label: 'Headache' },
|
|
471
|
-
{ value: 'fatigue', label: 'Fatigue' },
|
|
472
|
-
{ value: 'nausea', label: 'Nausea' }
|
|
473
|
-
]
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
### Signature Area Section
|
|
479
|
-
|
|
480
|
-
```typescript
|
|
481
|
-
{
|
|
482
|
-
type: 'signature-area',
|
|
483
|
-
config: {
|
|
484
|
-
fields: [
|
|
485
|
-
{ label: 'Patient Signature', field: 'patientSignature' },
|
|
486
|
-
{ label: 'Nurse Signature', field: 'nurseSignature' },
|
|
487
|
-
{ label: 'Date', field: 'signatureDate', type: 'date' }
|
|
488
|
-
]
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
## CSS Isolation
|
|
494
|
-
|
|
495
|
-
For consistent rendering across different environments, use isolation mode:
|
|
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
|
-
// Namespace utilities
|
|
510
|
-
namespaceClass('header') // 'mpr-header'
|
|
511
|
-
namespaceClasses(['header', 'footer']) // ['mpr-header', 'mpr-footer']
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
Isolation mode provides:
|
|
515
|
-
- All classes prefixed with `mpr-` namespace
|
|
516
|
-
- Embedded Source Han Serif SC font (subset for CJK support)
|
|
517
|
-
- CSS containment (`contain: layout style`)
|
|
518
|
-
- Consistent rendering regardless of host page styles
|
|
519
|
-
|
|
520
406
|
## Examples
|
|
521
407
|
|
|
522
|
-
See the [examples](./examples) directory
|
|
408
|
+
See the [examples](./examples) directory:
|
|
523
409
|
|
|
524
|
-
- [Browser Example](./examples/browser) - Vanilla HTML/JS
|
|
525
|
-
- [Node.js Example](./examples/node) - PDF generation
|
|
410
|
+
- [Browser Example](./examples/browser) - Vanilla HTML/JS
|
|
411
|
+
- [Node.js Example](./examples/node) - PDF generation
|
|
526
412
|
|
|
527
413
|
## Storybook
|
|
528
414
|
|
|
529
|
-
Interactive component documentation is available via Storybook:
|
|
530
|
-
|
|
531
415
|
```bash
|
|
532
416
|
npm run storybook
|
|
533
417
|
```
|
|
534
418
|
|
|
535
419
|
## Contributing
|
|
536
420
|
|
|
537
|
-
|
|
421
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md)
|
|
538
422
|
|
|
539
423
|
## License
|
|
540
424
|
|
|
541
|
-
[MIT](./LICENSE)
|
|
425
|
+
[MIT](./LICENSE)
|
|
542
426
|
|
|
543
427
|
## Links
|
|
544
428
|
|
|
545
|
-
- [GitHub
|
|
546
|
-
- [npm
|
|
547
|
-
- [Issue Tracker](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](./CHANGELOG.md)
|