x-print-designer 0.3.1 → 0.3.3
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 +683 -507
- package/dist/{JsBarcode-D9CnZR1g.js → JsBarcode-D606lSTn.js} +1 -1
- package/dist/{browser-DIuu-Z-y.js → browser-CARBTKXx.js} +1 -1
- package/dist/{dom-to-image-more.min--1GnoLPY.js → dom-to-image-more.min-C8Z-XA0Y.js} +1 -1
- package/dist/{index-DPjE1XnG.js → index-BCjCg5ss.js} +1 -1
- package/dist/{index.es-DJgpzwW7.js → index.es-LBa5dfqs.js} +1 -1
- package/dist/{jspdf.es.min-rgYa4lHx.js → jspdf.es.min-qCmiZN6G.js} +1 -1
- package/dist/{jszip.min-BVgk28Np.js → jszip.min-C2-yecY2.js} +1 -1
- package/dist/print-designer.css +1 -1
- package/dist/print-designer.es.js +1 -1
- package/dist/print-designer.umd.js +1 -1
- package/dist/{web-component-Cu_s_2qS.js → web-component-ExUlxqbL.js} +85 -35
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,21 +6,151 @@
|
|
|
6
6
|
|
|
7
7
|
## 目录
|
|
8
8
|
|
|
9
|
-
- [
|
|
9
|
+
- [前置依赖要求](#前置依赖要求)
|
|
10
10
|
- [快速开始](#快速开始)
|
|
11
|
+
- [源码开发](#源码开发)
|
|
12
|
+
- [Vue 组件接入](#vue-组件接入)
|
|
13
|
+
- [Web Component 接入](#web-component-接入)
|
|
14
|
+
- [核心概念:模板模式 vs 报告模式](#核心概念模板模式-vs-报告模式)
|
|
11
15
|
- [Vue 组件使用说明](#vue-组件使用说明)
|
|
12
16
|
- [Web Component 使用说明](#web-component-使用说明)
|
|
13
17
|
- [传入数据格式](#传入数据格式)
|
|
14
|
-
- [保存回调 `onSave`](#保存回调-onsave)
|
|
15
|
-
- [自动保存配置](#自动保存配置)
|
|
16
|
-
- [模板数据结构(完整格式)](#模板数据结构完整格式)
|
|
17
18
|
- [元素数据绑定](#元素数据绑定)
|
|
18
19
|
- [图片元素使用说明](#图片元素使用说明)
|
|
20
|
+
- [保存回调 `onSave`](#保存回调-onsave)
|
|
21
|
+
- [自动保存配置](#自动保存配置)
|
|
22
|
+
- [打印与导出](#打印与导出)
|
|
23
|
+
- [模板数据结构](#模板数据结构)
|
|
19
24
|
- [支持的元素类型](#支持的元素类型)
|
|
25
|
+
- [版本兼容性说明](#版本兼容性说明)
|
|
26
|
+
- [常见问题排查](#常见问题排查)
|
|
20
27
|
- [技术栈](#技术栈)
|
|
21
28
|
|
|
22
29
|
---
|
|
23
30
|
|
|
31
|
+
## 前置依赖要求
|
|
32
|
+
|
|
33
|
+
| 依赖 | 最低版本 | 说明 |
|
|
34
|
+
|------|---------|------|
|
|
35
|
+
| **Node.js** | `>= 18` | 运行时环境 |
|
|
36
|
+
| **pnpm** | `>= 8` | 推荐包管理器(项目使用 `pnpm-lock.yaml`) |
|
|
37
|
+
| **Vue** | `^3.5` | 核心框架 |
|
|
38
|
+
|
|
39
|
+
### 浏览器兼容性
|
|
40
|
+
|
|
41
|
+
| 浏览器 | 最低版本 | 说明 |
|
|
42
|
+
|--------|---------|------|
|
|
43
|
+
| Chrome | >= 90 | 完全支持 |
|
|
44
|
+
| Edge | >= 90 | 完全支持 |
|
|
45
|
+
| Firefox | >= 90 | 完全支持 |
|
|
46
|
+
| Safari | >= 15 | 完全支持 |
|
|
47
|
+
|
|
48
|
+
> **说明:** 设计器基于现代浏览器 API(`ResizeObserver`、`CSS Grid`、`Shadow DOM`),不支持 IE 11 及以下版本。
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 快速开始
|
|
53
|
+
|
|
54
|
+
### 源码开发
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# 克隆仓库
|
|
58
|
+
git clone https://github.com/0ldFive/Vue-Print-Designer.git
|
|
59
|
+
cd x-print-designer
|
|
60
|
+
|
|
61
|
+
# 安装依赖
|
|
62
|
+
pnpm install
|
|
63
|
+
|
|
64
|
+
# 启动开发服务器
|
|
65
|
+
pnpm dev
|
|
66
|
+
|
|
67
|
+
# 构建生产包
|
|
68
|
+
pnpm build
|
|
69
|
+
|
|
70
|
+
# 构建 Web Component 包
|
|
71
|
+
pnpm build:wc
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
启动后浏览器访问 `http://localhost:5173` 即可看到设计器 Demo 页面(`index.html`)。
|
|
75
|
+
|
|
76
|
+
### Vue 组件接入
|
|
77
|
+
|
|
78
|
+
**适用于:** 将设计器作为子组件嵌入已有 Vue 3 项目。
|
|
79
|
+
|
|
80
|
+
```vue
|
|
81
|
+
<template>
|
|
82
|
+
<PrintDesigner
|
|
83
|
+
mode="template"
|
|
84
|
+
:sample-data="sampleData"
|
|
85
|
+
:on-save="handleSave"
|
|
86
|
+
/>
|
|
87
|
+
</template>
|
|
88
|
+
|
|
89
|
+
<script setup lang="ts">
|
|
90
|
+
import { ref } from 'vue'
|
|
91
|
+
import { PrintDesigner } from 'x-print-designer'
|
|
92
|
+
import 'x-print-designer/style.css'
|
|
93
|
+
|
|
94
|
+
const sampleData = ref({
|
|
95
|
+
id: 1,
|
|
96
|
+
title: '报告标题',
|
|
97
|
+
customerName: '张三',
|
|
98
|
+
items: [
|
|
99
|
+
{ name: '产品A', price: 100, quantity: 2 }
|
|
100
|
+
]
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const handleSave = async (payload) => {
|
|
104
|
+
// payload: { id, name, data, isNew }
|
|
105
|
+
await fetch('/api/templates/save', {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: { 'Content-Type': 'application/json' },
|
|
108
|
+
body: JSON.stringify(payload)
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
</script>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Web Component 接入
|
|
115
|
+
|
|
116
|
+
**适用于:** 任意前端技术栈(React / Angular / 原生 HTML),以自定义标签方式嵌入。
|
|
117
|
+
|
|
118
|
+
```html
|
|
119
|
+
<!DOCTYPE html>
|
|
120
|
+
<html lang="zh-CN">
|
|
121
|
+
<head>
|
|
122
|
+
<meta charset="UTF-8" />
|
|
123
|
+
<script type="module" src="/dist/print-designer.es.js"></script>
|
|
124
|
+
<link rel="stylesheet" href="/dist/print-designer.css" />
|
|
125
|
+
</head>
|
|
126
|
+
<body>
|
|
127
|
+
<print-designer id="designer"></print-designer>
|
|
128
|
+
|
|
129
|
+
<script>
|
|
130
|
+
const designer = document.getElementById('designer')
|
|
131
|
+
|
|
132
|
+
designer.addEventListener('ready', () => {
|
|
133
|
+
designer.setMode('template')
|
|
134
|
+
designer.setTestData({
|
|
135
|
+
id: 1,
|
|
136
|
+
title: '报告标题',
|
|
137
|
+
items: [{ name: '产品A', price: 100 }]
|
|
138
|
+
})
|
|
139
|
+
designer.setOnSave((payload) => {
|
|
140
|
+
fetch('/api/templates/save', {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
headers: { 'Content-Type': 'application/json' },
|
|
143
|
+
body: JSON.stringify(payload)
|
|
144
|
+
})
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
</script>
|
|
148
|
+
</body>
|
|
149
|
+
</html>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
24
154
|
## 核心概念:模板模式 vs 报告模式
|
|
25
155
|
|
|
26
156
|
设计器支持两种工作模式,通过 `mode` 属性/方法进行切换。
|
|
@@ -29,12 +159,12 @@
|
|
|
29
159
|
|
|
30
160
|
| 概念 | 说明 |
|
|
31
161
|
|------|------|
|
|
32
|
-
| **模板 (Template)** |
|
|
162
|
+
| **模板 (Template)** | 页面布局设计,包含元素位置、样式、画布大小等 |
|
|
33
163
|
| **数据 (Data)** | 业务数据,如订单信息、报告内容、用户信息等 |
|
|
34
164
|
| **模板 ID** | 模板的唯一标识,用于关联和加载模板 |
|
|
35
165
|
| **数据 ID** | 业务数据的唯一标识,如订单 ID、报告 ID |
|
|
36
166
|
|
|
37
|
-
### 模板模式(`template`)—
|
|
167
|
+
### 模板模式(`template`)— 默认
|
|
38
168
|
|
|
39
169
|
**设计理念:** 模板与数据分离,一个模板可绑定多个不同的数据。
|
|
40
170
|
|
|
@@ -43,14 +173,7 @@
|
|
|
43
173
|
- 标签打印(同一标签模板,打印不同商品)
|
|
44
174
|
- 快递单据(同一单据模板,打印不同收件人)
|
|
45
175
|
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
模板(保存布局) + 数据(打印时注入) = 最终打印结果
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
**保存行为:**
|
|
52
|
-
- 保存时**只保存页面布局**(元素位置、样式、画布大小等)
|
|
53
|
-
- **不保存** `testData`
|
|
176
|
+
**保存行为:** 保存时**只保存页面布局**(元素位置、样式、画布大小等),**不保存** `testData`。
|
|
54
177
|
|
|
55
178
|
### 报告模式(`report`)
|
|
56
179
|
|
|
@@ -61,14 +184,7 @@
|
|
|
61
184
|
- 数据报表(当前统计数据的可视化呈现)
|
|
62
185
|
- 档案文件(包含具体数据的完整文档)
|
|
63
186
|
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
报告(模板 + 数据 一起保存) = 完整的报告文件
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
**保存行为:**
|
|
70
|
-
- 保存时**同时保存页面布局 + 测试数据**
|
|
71
|
-
- `testData` 会被完整保存到模板数据中
|
|
187
|
+
**保存行为:** 保存时**同时保存页面布局 + testData**。
|
|
72
188
|
|
|
73
189
|
### 两种模式对比
|
|
74
190
|
|
|
@@ -79,46 +195,48 @@
|
|
|
79
195
|
| 数据与模板关系 | 分离,运行时注入 | 绑定,一起保存 |
|
|
80
196
|
| 模板复用性 | 高,可用于多个数据 | 低,针对当前数据 |
|
|
81
197
|
| 典型用途 | 订单打印、标签 | 检测报告、数据报表 |
|
|
82
|
-
| 数据持久化 | 数据需单独存储 | 数据与模板一起存储 |
|
|
83
198
|
|
|
84
199
|
### 使用示例
|
|
85
200
|
|
|
86
201
|
**模板模式(推荐用于可复用模板):**
|
|
202
|
+
|
|
87
203
|
```javascript
|
|
88
|
-
// 1.
|
|
204
|
+
// 1. 编辑模板时传入预览数据
|
|
89
205
|
const sampleData = {
|
|
90
|
-
id: null,
|
|
91
|
-
|
|
92
|
-
customerName:
|
|
93
|
-
}
|
|
206
|
+
id: null, // 无需数据 ID,仅用于预览
|
|
207
|
+
orderNo: 'PREVIEW-001',
|
|
208
|
+
customerName: '预览客户'
|
|
209
|
+
}
|
|
94
210
|
|
|
95
|
-
// 2.
|
|
96
|
-
// payload.data
|
|
211
|
+
// 2. 保存模板(只保存布局,不含 testData)
|
|
212
|
+
// payload.data 中不包含 testData
|
|
97
213
|
|
|
98
|
-
// 3.
|
|
214
|
+
// 3. 打印时动态注入真实数据
|
|
215
|
+
designer.loadTemplate('tpl-order')
|
|
99
216
|
designer.setVariables({
|
|
100
|
-
orderNo:
|
|
101
|
-
customerName:
|
|
102
|
-
})
|
|
103
|
-
designer.print()
|
|
217
|
+
orderNo: 'ORD-2024-001',
|
|
218
|
+
customerName: '张三'
|
|
219
|
+
})
|
|
220
|
+
designer.print({ mode: 'browser', options: { silent: true } })
|
|
104
221
|
```
|
|
105
222
|
|
|
106
223
|
**报告模式(推荐用于完整报告):**
|
|
224
|
+
|
|
107
225
|
```javascript
|
|
108
|
-
// 1.
|
|
226
|
+
// 1. 编辑报告时传入完整数据
|
|
109
227
|
const sampleData = {
|
|
110
|
-
id: 8,
|
|
111
|
-
title:
|
|
228
|
+
id: 8, // 报告 ID
|
|
229
|
+
title: '2024年第一季度销售报告',
|
|
112
230
|
totalAmount: 50000,
|
|
113
|
-
items: [...]
|
|
114
|
-
}
|
|
231
|
+
items: [...]
|
|
232
|
+
}
|
|
115
233
|
|
|
116
234
|
// 2. 保存报告(布局 + 数据一起保存)
|
|
117
|
-
// payload.data
|
|
235
|
+
// payload.data 中包含完整的 testData
|
|
118
236
|
|
|
119
237
|
// 3. 加载后直接打印,无需再次注入数据
|
|
120
|
-
designer.loadTemplate(8)
|
|
121
|
-
designer.print()
|
|
238
|
+
designer.loadTemplate(8)
|
|
239
|
+
designer.print({ mode: 'browser', options: { silent: true } })
|
|
122
240
|
```
|
|
123
241
|
|
|
124
242
|
### 切换模式
|
|
@@ -131,66 +249,23 @@ designer.print();
|
|
|
131
249
|
|
|
132
250
|
```javascript
|
|
133
251
|
// Web Component
|
|
134
|
-
designer.setMode('template')
|
|
135
|
-
designer.setMode('report')
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## 快速开始
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
npm install
|
|
144
|
-
npm run dev
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
### 最简接入
|
|
148
|
-
|
|
149
|
-
```vue
|
|
150
|
-
<template>
|
|
151
|
-
<PrintDesigner
|
|
152
|
-
mode="template"
|
|
153
|
-
:sample-data="sampleData"
|
|
154
|
-
:on-save="handleSave"
|
|
155
|
-
/>
|
|
156
|
-
</template>
|
|
157
|
-
|
|
158
|
-
<script setup>
|
|
159
|
-
import { ref } from 'vue';
|
|
160
|
-
import PrintDesigner from '@/components/PrintDesigner.vue';
|
|
161
|
-
|
|
162
|
-
const sampleData = ref({
|
|
163
|
-
id: 1,
|
|
164
|
-
name: '测试报告',
|
|
165
|
-
templateId: 'tpl_001',
|
|
166
|
-
variables: {
|
|
167
|
-
title: '报告标题',
|
|
168
|
-
customerName: '张三',
|
|
169
|
-
items: [
|
|
170
|
-
{ name: '产品A', price: 100, quantity: 2 }
|
|
171
|
-
]
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
const handleSave = (payload) => {
|
|
176
|
-
console.log('保存数据:', payload);
|
|
177
|
-
// { id: '...', name: '测试报告', data: {...}, isNew: false }
|
|
178
|
-
// 用户自行调用接口保存
|
|
179
|
-
};
|
|
180
|
-
</script>
|
|
252
|
+
designer.setMode('template')
|
|
253
|
+
designer.setMode('report')
|
|
181
254
|
```
|
|
182
255
|
|
|
183
256
|
---
|
|
184
257
|
|
|
185
258
|
## Vue 组件使用说明
|
|
186
259
|
|
|
187
|
-
### 组件 Props
|
|
260
|
+
### 组件 Props
|
|
188
261
|
|
|
189
262
|
| 属性 | 类型 | 默认值 | 说明 |
|
|
190
263
|
|------|------|--------|------|
|
|
191
|
-
| `mode` | `'template'` \| `'report'` | `'template'` |
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
264
|
+
| `mode` | `'template'` \| `'report'` | `'template'` | 工作模式,影响保存行为 |
|
|
265
|
+
| `view-mode` | `'edit'` \| `'preview'` | `'edit'` | 视图模式,`'preview'` 时只渲染打印内容 |
|
|
266
|
+
| `headless` | `boolean` | `false` | 无头模式,隐藏顶部工具栏和侧边栏 |
|
|
267
|
+
| `sample-data` | `Record<string, any>` | — | 传入的示例/预览数据,详见[传入数据格式](#传入数据格式) |
|
|
268
|
+
| `on-save` | `(payload) => void \| Promise<void>` | — | 保存回调,详见[保存回调](#保存回调-onsave) |
|
|
194
269
|
| `auto-save-enabled` | `boolean` | `false` | 是否开启定时自动保存 |
|
|
195
270
|
| `auto-save-interval-ms` | `number` | `120000` | 自动保存间隔(毫秒),默认 2 分钟 |
|
|
196
271
|
|
|
@@ -198,146 +273,178 @@ const handleSave = (payload) => {
|
|
|
198
273
|
|
|
199
274
|
#### `mode`
|
|
200
275
|
|
|
201
|
-
|
|
276
|
+
工作模式,决定保存时是否附带 `testData`。
|
|
202
277
|
|
|
203
278
|
```vue
|
|
204
279
|
<PrintDesigner mode="report" />
|
|
205
280
|
```
|
|
206
281
|
|
|
282
|
+
#### `view-mode`
|
|
283
|
+
|
|
284
|
+
切换编辑/预览视图。设为 `'preview'` 时隐藏所有编辑器 UI,仅渲染打印页面。适用于嵌入第三方页面时只展示打印效果。
|
|
285
|
+
|
|
286
|
+
```vue
|
|
287
|
+
<PrintDesigner view-mode="preview" />
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
#### `headless`
|
|
291
|
+
|
|
292
|
+
无头模式,隐藏顶部工具栏和侧边属性面板。用于嵌入式场景仅保留画布区域。
|
|
293
|
+
|
|
294
|
+
```vue
|
|
295
|
+
<PrintDesigner headless />
|
|
296
|
+
```
|
|
297
|
+
|
|
207
298
|
#### `sample-data`
|
|
208
299
|
|
|
209
|
-
|
|
300
|
+
传入设计器的预览数据。**最关键的属性**。详见 [传入数据格式](#传入数据格式)。
|
|
210
301
|
|
|
211
302
|
```vue
|
|
212
|
-
<PrintDesigner :sample-data="{ id: 1,
|
|
303
|
+
<PrintDesigner :sample-data="{ id: 1, title: '报告', items: [...] }" />
|
|
213
304
|
```
|
|
214
305
|
|
|
215
306
|
#### `on-save`
|
|
216
307
|
|
|
217
|
-
|
|
308
|
+
保存回调函数。传入后设计器**不再执行内置保存逻辑**,完全由用户自行处理持久化。
|
|
218
309
|
|
|
219
310
|
```vue
|
|
220
|
-
<PrintDesigner :on-save="(payload) =>
|
|
311
|
+
<PrintDesigner :on-save="(payload) => mySaveApi(payload)" />
|
|
221
312
|
```
|
|
222
313
|
|
|
223
|
-
|
|
314
|
+
> 不传 `onSave` 时,设计器使用内置 CRUD 逻辑(根据配置调远程接口或存 localStorage)。
|
|
224
315
|
|
|
225
|
-
|
|
316
|
+
#### `auto-save-enabled` / `auto-save-interval-ms`
|
|
226
317
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
自动保存间隔时间(毫秒),默认 `120000`(2 分钟)。
|
|
318
|
+
详见 [自动保存配置](#自动保存配置)。
|
|
230
319
|
|
|
231
320
|
---
|
|
232
321
|
|
|
233
322
|
## Web Component 使用说明
|
|
234
323
|
|
|
235
|
-
###
|
|
324
|
+
### 加载方式
|
|
236
325
|
|
|
237
326
|
```html
|
|
238
|
-
|
|
327
|
+
<!-- ES Module 方式(推荐) -->
|
|
328
|
+
<script type="module" src="/dist/print-designer.es.js"></script>
|
|
329
|
+
<link rel="stylesheet" href="/dist/print-designer.css" />
|
|
239
330
|
|
|
240
|
-
|
|
241
|
-
|
|
331
|
+
<!-- 或 UMD 方式 -->
|
|
332
|
+
<script src="/dist/print-designer.umd.js"></script>
|
|
333
|
+
<link rel="stylesheet" href="/dist/print-designer.css" />
|
|
334
|
+
```
|
|
242
335
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
fetch('/api/save', { method: 'POST', body: JSON.stringify(payload) });
|
|
246
|
-
});
|
|
247
|
-
designer.setAutoSave(true, 120000);
|
|
248
|
-
designer.setTestData({ orderNo: 'ORD-001', items: [...] });
|
|
249
|
-
</script>
|
|
336
|
+
```html
|
|
337
|
+
<print-designer id="designer"></print-designer>
|
|
250
338
|
```
|
|
251
339
|
|
|
252
|
-
### API
|
|
340
|
+
### 完整 API 参考
|
|
253
341
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
|
257
|
-
|
|
258
|
-
| `
|
|
259
|
-
| `
|
|
260
|
-
| `
|
|
261
|
-
| `
|
|
262
|
-
| `
|
|
263
|
-
| `
|
|
264
|
-
| `
|
|
265
|
-
| `
|
|
266
|
-
| `
|
|
267
|
-
| `
|
|
268
|
-
| `
|
|
269
|
-
| `
|
|
270
|
-
| `
|
|
271
|
-
| `
|
|
342
|
+
所有方法均通过 DOM 元素直接调用。**必须在 `ready` 事件触发后调用。**
|
|
343
|
+
|
|
344
|
+
| 方法 | 参数 | 返回值 | 说明 |
|
|
345
|
+
|------|------|--------|------|
|
|
346
|
+
| `setMode(mode)` | `'template'` \| `'report'` | `void` | 设置工作模式 |
|
|
347
|
+
| `setViewMode(viewMode)` | `'edit'` \| `'preview'` | `void` | 切换视图模式 |
|
|
348
|
+
| `setOnSave(handler)` | `(payload) => void \| Promise<void>` | `void` | 设置保存回调 |
|
|
349
|
+
| `setAutoSave(enabled, intervalMs?)` | `boolean, number?` | `void` | 开启/关闭自动保存,`intervalMs` 默认 120000 |
|
|
350
|
+
| `setTestData(data)` | `Record<string, any>` | `void` | 设置测试/预览数据(等价 Vue 组件的 `sample-data`) |
|
|
351
|
+
| `setSampleData(data)` | `Record<string, any>` | `void` | `setTestData` 的别名 |
|
|
352
|
+
| `setVariables(vars)` | `Record<string, any>` | `void` | 打印/导出时注入真实变量(覆盖 testData 中的同名字段) |
|
|
353
|
+
| `setTemplates(list, opts?)` | `Array<Template>, object?` | `void` | 设置预设模板列表。`opts` 可选:`{ currentTemplateId?: string }` |
|
|
354
|
+
| `loadTemplate(id)` | `string` | `Promise<void>` | 加载指定模板到画布 |
|
|
355
|
+
| `loadTemplateData(data)` | `Record<string, any>` | `boolean` | 直接加载模板 JSON 数据到画布,返回是否成功 |
|
|
356
|
+
| `getTemplateData()` | — | `Record<string, any>` | 获取当前模板完整数据 |
|
|
357
|
+
| `getPreviewHtml()` | — | `Promise<string>` | 获取预览 HTML 字符串 |
|
|
358
|
+
| `print(options?)` | `PrintOptions` | `Promise<void>` | 执行打印,详见[打印与导出](#打印与导出) |
|
|
359
|
+
| `export(options?)` | `ExportOptions` | `Promise<void>` | 导出 PDF / 图片 / HTML,详见[打印与导出](#打印与导出) |
|
|
360
|
+
| `setPreviewMode(options)` | `PreviewModeOptions` | `void` | 进入独立预览面板模式 |
|
|
361
|
+
| `exitPreviewMode()` | — | `void` | 退出预览模式,返回编辑器 |
|
|
362
|
+
| `setBranding(config)` | `{ title?, logoUrl?, showTitle?, showLogo? }` | `void` | 设置品牌标识 |
|
|
363
|
+
| `setLang(lang)` | `'zh'` \| `'en'` | `void` | 切换语言 |
|
|
364
|
+
|
|
365
|
+
### 基本使用示例
|
|
366
|
+
|
|
367
|
+
```javascript
|
|
368
|
+
const designer = document.getElementById('designer')
|
|
369
|
+
|
|
370
|
+
designer.addEventListener('ready', () => {
|
|
371
|
+
// 设置模式
|
|
372
|
+
designer.setMode('template')
|
|
373
|
+
|
|
374
|
+
// 设置数据
|
|
375
|
+
designer.setTestData({
|
|
376
|
+
id: 1,
|
|
377
|
+
orderNo: 'ORD-001',
|
|
378
|
+
customerName: '张三',
|
|
379
|
+
items: [
|
|
380
|
+
{ name: '产品A', price: 100, quantity: 2 },
|
|
381
|
+
{ name: '产品B', price: 200, quantity: 3 }
|
|
382
|
+
]
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
// 保存回调
|
|
386
|
+
designer.setOnSave(async (payload) => {
|
|
387
|
+
console.log('保存:', payload)
|
|
388
|
+
await fetch('/api/templates/save', {
|
|
389
|
+
method: 'POST',
|
|
390
|
+
headers: { 'Content-Type': 'application/json' },
|
|
391
|
+
body: JSON.stringify(payload)
|
|
392
|
+
})
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
// 开启自动保存(每 2 分钟)
|
|
396
|
+
designer.setAutoSave(true, 120000)
|
|
397
|
+
})
|
|
398
|
+
```
|
|
272
399
|
|
|
273
400
|
### 打印时注入真实数据
|
|
274
401
|
|
|
275
|
-
|
|
276
|
-
const designer = document.querySelector('print-designer');
|
|
402
|
+
使用 `setVariables` 在打印/导出时覆盖预览数据中的字段:
|
|
277
403
|
|
|
278
|
-
|
|
404
|
+
```javascript
|
|
405
|
+
const designer = document.querySelector('print-designer')
|
|
279
406
|
|
|
407
|
+
// 加载模板
|
|
408
|
+
await designer.loadTemplate('tpl-order')
|
|
409
|
+
|
|
410
|
+
// 注入真实打印数据
|
|
280
411
|
designer.setVariables({
|
|
281
412
|
orderNo: 'REAL-2026-001',
|
|
282
413
|
customerName: '李四',
|
|
283
414
|
totalAmount: 5999.00
|
|
284
|
-
})
|
|
415
|
+
})
|
|
285
416
|
|
|
286
|
-
|
|
417
|
+
// 静默打印
|
|
418
|
+
await designer.print({ mode: 'browser', options: { silent: true } })
|
|
287
419
|
```
|
|
288
420
|
|
|
289
|
-
### 独立预览面板
|
|
290
|
-
|
|
291
|
-
当设计器以 Web Component 方式嵌入时,可切换到独立的**纯预览模式**——只渲染打印内容,不显示编辑器工具栏、属性面板、辅助线等 UI。
|
|
421
|
+
### 独立预览面板
|
|
292
422
|
|
|
293
|
-
|
|
294
|
-
- 插件/第三方页面嵌入时,单独展示打印预览效果
|
|
295
|
-
- 用户填写表单后直接预览生成的打印内容
|
|
296
|
-
- 无需编辑功能的只读预览场景
|
|
423
|
+
当设计器嵌入第三方页面时,可切换到独立的**纯预览模式**——只渲染打印内容,不显示编辑器 UI。
|
|
297
424
|
|
|
298
|
-
|
|
425
|
+
**适用于:** 插件/第三方页面嵌入时的打印预览、用户填写表单后的预览、无需编辑功能的只读场景。
|
|
299
426
|
|
|
300
|
-
|
|
301
|
-
const designer = document.querySelector('print-designer');
|
|
302
|
-
|
|
303
|
-
// 进入预览模式,传入模板和数据
|
|
304
|
-
designer.setPreviewMode({
|
|
305
|
-
pages: templateData.pages, // 页面元素布局
|
|
306
|
-
canvasSize: { width: 794, height: 1123 }, // 画布尺寸
|
|
307
|
-
canvasBackground: '#ffffff', // 背景色
|
|
308
|
-
testData: { // 测试数据
|
|
309
|
-
name: '张三',
|
|
310
|
-
amount: 1000,
|
|
311
|
-
items: [{ name: '产品A', price: 100 }]
|
|
312
|
-
},
|
|
313
|
-
variables: {}, // 变量映射
|
|
314
|
-
watermark: { enabled: false } // 水印设置
|
|
315
|
-
});
|
|
427
|
+
#### `setPreviewMode(options)`
|
|
316
428
|
|
|
317
|
-
|
|
318
|
-
|
|
429
|
+
```typescript
|
|
430
|
+
interface PreviewModeOptions {
|
|
431
|
+
pages: Page[] // 页面元素布局(必填)
|
|
432
|
+
canvasSize: { width: number; height: number } // 画布尺寸(必填)
|
|
433
|
+
canvasBackground?: string // 画布背景色,默认 '#ffffff'
|
|
434
|
+
testData?: Record<string, any> // 测试数据
|
|
435
|
+
variables?: Record<string, any> // 额外变量映射
|
|
436
|
+
watermark?: WatermarkSettings // 水印配置
|
|
437
|
+
}
|
|
319
438
|
```
|
|
320
439
|
|
|
321
|
-
#### 参数说明
|
|
322
|
-
|
|
323
|
-
| 参数 | 类型 | 必填 | 说明 |
|
|
324
|
-
|------|------|------|------|
|
|
325
|
-
| `pages` | `Page[]` | ✅ 是 | 页面元素布局数组 |
|
|
326
|
-
| `canvasSize` | `{ width, height }` | ✅ 是 | 画布尺寸(px) |
|
|
327
|
-
| `canvasBackground` | `string` | 否 | 画布背景色,默认 `'#ffffff'` |
|
|
328
|
-
| `testData` | `object` | 否 | 测试数据,模板中的变量会自动解析 |
|
|
329
|
-
| `variables` | `object` | 否 | 额外变量映射 |
|
|
330
|
-
| `watermark` | `object` | 否 | 水印配置 |
|
|
331
|
-
|
|
332
440
|
#### 使用示例
|
|
333
441
|
|
|
334
442
|
```javascript
|
|
335
|
-
// 场景:用户点击"预览"按钮,传入后端返回的模板和当前表单数据
|
|
336
443
|
async function openPreview(reportId) {
|
|
337
|
-
const designer = document.querySelector('print-designer')
|
|
444
|
+
const designer = document.querySelector('print-designer')
|
|
338
445
|
|
|
339
446
|
// 从后端获取模板数据
|
|
340
|
-
const template = await fetch(`/api/templates/${reportId}`).then(r => r.json())
|
|
447
|
+
const template = await fetch(`/api/templates/${reportId}`).then(r => r.json())
|
|
341
448
|
|
|
342
449
|
// 进入预览模式
|
|
343
450
|
designer.setPreviewMode({
|
|
@@ -350,16 +457,16 @@ async function openPreview(reportId) {
|
|
|
350
457
|
items: getOrderItems()
|
|
351
458
|
},
|
|
352
459
|
watermark: template.data.watermark
|
|
353
|
-
})
|
|
460
|
+
})
|
|
354
461
|
}
|
|
355
462
|
|
|
356
463
|
// 返回编辑
|
|
357
464
|
function backToEdit() {
|
|
358
|
-
designer.exitPreviewMode()
|
|
465
|
+
designer.exitPreviewMode()
|
|
359
466
|
}
|
|
360
467
|
```
|
|
361
468
|
|
|
362
|
-
>
|
|
469
|
+
> `exitPreviewMode()` 会自动恢复进入预览前的编辑器状态(模板选择、编辑进度等)。
|
|
363
470
|
|
|
364
471
|
---
|
|
365
472
|
|
|
@@ -367,24 +474,20 @@ function backToEdit() {
|
|
|
367
474
|
|
|
368
475
|
### 基本要求
|
|
369
476
|
|
|
370
|
-
`sample-data` 接收一个普通的 JSON
|
|
477
|
+
`sample-data` / `setTestData(data)` 接收一个普通的 JSON 对象。**唯一必填字段是 `id`**。
|
|
371
478
|
|
|
372
479
|
| 字段 | 类型 | 必填 | 说明 |
|
|
373
480
|
|------|------|------|------|
|
|
374
|
-
| `id` | `number`
|
|
375
|
-
| `templateId` | `string` | 否 |
|
|
376
|
-
| `name` | `string` | 否 |
|
|
377
|
-
| `variables` | `object` | 否 |
|
|
378
|
-
| 任意自定义字段 | `any` | 否 |
|
|
379
|
-
|
|
380
|
-
### 两种数据传入方式
|
|
481
|
+
| `id` | `number` \| `string` | ✅ 是 | 业务数据的唯一标识,如报告 ID、订单 ID |
|
|
482
|
+
| `templateId` | `string` | 否 | 绑定的模板 ID,不传则用当前已选模板 |
|
|
483
|
+
| `name` | `string` | 否 | 数据名称 |
|
|
484
|
+
| `variables` | `object` | 否 | 业务变量对象(推荐,用于组织数据) |
|
|
485
|
+
| 任意自定义字段 | `any` | 否 | 也可直接在根级别存放变量 |
|
|
381
486
|
|
|
382
|
-
|
|
487
|
+
### 两种组织方式(任选其一,自动兼容)
|
|
383
488
|
|
|
384
489
|
#### 方式一:带 `variables` 包装(推荐)
|
|
385
490
|
|
|
386
|
-
将所有业务变量放在 `variables` 对象中,结构清晰,适合复杂业务场景:
|
|
387
|
-
|
|
388
491
|
```json
|
|
389
492
|
{
|
|
390
493
|
"id": 8,
|
|
@@ -397,18 +500,15 @@ function backToEdit() {
|
|
|
397
500
|
{ "name": "产品A", "price": 100, "quantity": 2 }
|
|
398
501
|
],
|
|
399
502
|
"chartData": [
|
|
400
|
-
{ "category": "一月", "value": 1200 }
|
|
401
|
-
{ "category": "二月", "value": 1500 }
|
|
503
|
+
{ "category": "一月", "value": 1200 }
|
|
402
504
|
]
|
|
403
505
|
}
|
|
404
506
|
}
|
|
405
507
|
```
|
|
406
508
|
|
|
407
|
-
|
|
509
|
+
模板绑定:`@title`、`@customerName`、`@items`、`@chartData`
|
|
408
510
|
|
|
409
|
-
####
|
|
410
|
-
|
|
411
|
-
将所有字段直接放在根级别,无需 `variables` 包装,适合简单场景:
|
|
511
|
+
#### 方式二:扁平数据
|
|
412
512
|
|
|
413
513
|
```json
|
|
414
514
|
{
|
|
@@ -420,29 +520,70 @@ function backToEdit() {
|
|
|
420
520
|
{ "name": "产品A", "price": 100, "quantity": 2 }
|
|
421
521
|
],
|
|
422
522
|
"chartData": [
|
|
423
|
-
{ "category": "一月", "value": 1200 }
|
|
424
|
-
{ "category": "二月", "value": 1500 }
|
|
523
|
+
{ "category": "一月", "value": 1200 }
|
|
425
524
|
]
|
|
426
525
|
}
|
|
427
526
|
```
|
|
428
527
|
|
|
429
|
-
|
|
528
|
+
模板绑定:`@title`、`@customerName`、`@items`、`@chartData`
|
|
430
529
|
|
|
431
|
-
>
|
|
530
|
+
> 两种方式等效。设计器内部优先在 `variables` 中查找,未找到则在根级别查找。
|
|
432
531
|
|
|
433
532
|
### 支持的数据类型
|
|
434
533
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
| 类型 | 示例 | 在模板中绑定 |
|
|
438
|
-
|------|------|-------------|
|
|
534
|
+
| 类型 | 示例 | 模板绑定 |
|
|
535
|
+
|------|------|---------|
|
|
439
536
|
| 字符串 | `"张三"` | `@customerName` |
|
|
440
537
|
| 数字 | `100` | `@totalAmount` |
|
|
441
538
|
| 布尔值 | `true` | `@isPaid` |
|
|
442
539
|
| 数组 | `[{...}, {...}]` | `@items` |
|
|
443
|
-
| 嵌套对象 | `{user: {name: "李四"}}` | `@user.name` |
|
|
540
|
+
| 嵌套对象 | `{ user: { name: "李四" } }` | `@user.name` |
|
|
541
|
+
|
|
542
|
+
### 表格数据
|
|
444
543
|
|
|
445
|
-
|
|
544
|
+
表格元素需要三个维度的变量绑定:
|
|
545
|
+
|
|
546
|
+
| 绑定属性 | 变量名示例 | 数据格式 | 说明 |
|
|
547
|
+
|---------|-----------|---------|------|
|
|
548
|
+
| `variable` | `@items` | `Array<Object>` | 表格行数据 |
|
|
549
|
+
| `columnsVariable` | `@tableCols` | `Array<{field, header, width}>` | 列定义 |
|
|
550
|
+
| `footerDataVariable` | `@tableFooter` | `Array<Object>` | 页脚汇总行 |
|
|
551
|
+
|
|
552
|
+
```json
|
|
553
|
+
{
|
|
554
|
+
"items": [
|
|
555
|
+
{ "name": "产品A", "price": 100, "quantity": 2 },
|
|
556
|
+
{ "name": "产品B", "price": 200, "quantity": 3 }
|
|
557
|
+
],
|
|
558
|
+
"tableCols": [
|
|
559
|
+
{ "field": "name", "header": "产品名称", "width": 120 },
|
|
560
|
+
{ "field": "price", "header": "单价", "width": 80 },
|
|
561
|
+
{ "field": "quantity", "header": "数量", "width": 80 }
|
|
562
|
+
],
|
|
563
|
+
"tableFooter": [
|
|
564
|
+
{ "name": { "value": "合计" }, "price": { "value": "300" } }
|
|
565
|
+
]
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
### 图表数据
|
|
570
|
+
|
|
571
|
+
```json
|
|
572
|
+
{
|
|
573
|
+
"chartData": [
|
|
574
|
+
{ "category": "一月", "value": 1200 },
|
|
575
|
+
{ "category": "二月", "value": 1500 }
|
|
576
|
+
]
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
图表元素需在属性面板中配置:
|
|
581
|
+
- **chartDataVariable**:`@chartData`
|
|
582
|
+
- **chartXField**:`category`(X 轴字段名)
|
|
583
|
+
- **chartYField**:`value`(Y 轴字段名)
|
|
584
|
+
- **chartNameField**:`category`(饼图名称字段)
|
|
585
|
+
|
|
586
|
+
### 完整示例数据
|
|
446
587
|
|
|
447
588
|
```json
|
|
448
589
|
{
|
|
@@ -466,249 +607,201 @@ function backToEdit() {
|
|
|
466
607
|
"dockname": "宝安国际无人机机场",
|
|
467
608
|
"distance": "12.5 km",
|
|
468
609
|
"image": "https://picsum.photos/id/1/200/80"
|
|
469
|
-
},
|
|
470
|
-
{
|
|
471
|
-
"time": "2026-04-08 14:00",
|
|
472
|
-
"taskname": "西乡街道线路巡查",
|
|
473
|
-
"dockname": "罗湖无人机机场",
|
|
474
|
-
"distance": "10.2 km",
|
|
475
|
-
"image": "https://picsum.photos/id/10/200/80"
|
|
476
|
-
}
|
|
477
|
-
],
|
|
478
|
-
"taskRecords": [
|
|
479
|
-
{
|
|
480
|
-
"time": "2026-04-02 09:30",
|
|
481
|
-
"taskname": "宝安港区A线巡检",
|
|
482
|
-
"dockname": "宝安国际无人机机场",
|
|
483
|
-
"warn": 2
|
|
484
|
-
},
|
|
485
|
-
{
|
|
486
|
-
"time": "2026-04-08 14:00",
|
|
487
|
-
"taskname": "西乡街道线路巡查",
|
|
488
|
-
"dockname": "罗湖无人机机场",
|
|
489
|
-
"warn": 1
|
|
490
610
|
}
|
|
491
611
|
],
|
|
492
612
|
"chartData": [
|
|
493
613
|
{ "category": "一月", "value": 1200 },
|
|
494
|
-
{ "category": "二月", "value": 1500 }
|
|
495
|
-
{ "category": "三月", "value": 1800 }
|
|
614
|
+
{ "category": "二月", "value": 1500 }
|
|
496
615
|
]
|
|
497
616
|
}
|
|
498
617
|
}
|
|
499
618
|
```
|
|
500
619
|
|
|
501
|
-
###
|
|
620
|
+
### 注意事项
|
|
502
621
|
|
|
503
|
-
|
|
622
|
+
1. **`id` 字段**
|
|
623
|
+
- 业务数据的唯一标识,在保存回调中用于识别当前编辑的数据
|
|
624
|
+
- 不传则保存时 `payload.id` 为 `null`
|
|
504
625
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
| `variables.reportname` / `reportname` | `@reportname` | 文本 |
|
|
509
|
-
| `variables.flightRecords` / `flightRecords` | `@flightRecords` | 表格、自定义表格 |
|
|
510
|
-
| `variables.chartData` / `chartData` | `@chartData` | 图表 |
|
|
626
|
+
2. **`id` 与 `templateId` 的区别**
|
|
627
|
+
- `id`:业务数据的标识(报告中用作报告 ID)
|
|
628
|
+
- `templateId`:打印模板的标识,两者是完全不同的概念
|
|
511
629
|
|
|
512
|
-
|
|
630
|
+
3. **变量命名**
|
|
631
|
+
- 使用 camelCase 命名,如 `customerName` 而非 `c`
|
|
632
|
+
- 避免特殊字符和空格
|
|
633
|
+
- 嵌套路径使用点号:`@user.profile.name`
|
|
513
634
|
|
|
514
|
-
|
|
635
|
+
4. **数组数据**
|
|
636
|
+
- 表格和图表需要数组类型
|
|
637
|
+
- 数组元素应包含一致的字段结构:`items: [{name: "A", price: 100}, {name: "B", price: 200}]`
|
|
515
638
|
|
|
516
|
-
|
|
639
|
+
5. **动态更新**
|
|
640
|
+
- Web Component 通过 `setSampleData()` / `setTestData()` 更新
|
|
641
|
+
- 数据更新后,画布中的变量绑定会自动重新解析
|
|
517
642
|
|
|
518
|
-
|
|
519
|
-
|---------|-------|------|------|
|
|
520
|
-
| `variable` | `@items` | `Array<Object>` | 表格行数据 |
|
|
521
|
-
| `columnsVariable` | `@tableCols` | `Array<{field, header, width}>` | 列定义 |
|
|
522
|
-
| `footerDataVariable` | `@tableFooter` | `Array<Object>` | 页脚汇总行 |
|
|
643
|
+
---
|
|
523
644
|
|
|
524
|
-
|
|
525
|
-
{
|
|
526
|
-
"items": [
|
|
527
|
-
{ "name": "产品A", "price": 100, "quantity": 2 },
|
|
528
|
-
{ "name": "产品B", "price": 200, "quantity": 3 }
|
|
529
|
-
],
|
|
530
|
-
"tableCols": [
|
|
531
|
-
{ "field": "name", "header": "产品名称", "width": 120 },
|
|
532
|
-
{ "field": "price", "header": "单价", "width": 80 },
|
|
533
|
-
{ "field": "quantity", "header": "数量", "width": 80 }
|
|
534
|
-
],
|
|
535
|
-
"tableFooter": [
|
|
536
|
-
{ "name": { "value": "合计" }, "price": { "value": "300" } }
|
|
537
|
-
]
|
|
538
|
-
}
|
|
539
|
-
```
|
|
645
|
+
## 元素数据绑定
|
|
540
646
|
|
|
541
|
-
###
|
|
647
|
+
### 变量语法
|
|
542
648
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
{ "category": "二月", "value": 1500 }
|
|
548
|
-
]
|
|
549
|
-
}
|
|
550
|
-
```
|
|
649
|
+
| 语法 | 示例 | 说明 |
|
|
650
|
+
|-----|------|------|
|
|
651
|
+
| `@变量名` | `@customerName` | 直接引用变量 |
|
|
652
|
+
| `{#变量路径}` | `{#order.items[0].name}` | 支持嵌套路径和数组索引 |
|
|
551
653
|
|
|
552
|
-
|
|
553
|
-
- **chartDataVariable:** `@chartData`
|
|
554
|
-
- **chartXField:** `category`(X 轴字段名)
|
|
555
|
-
- **chartYField:** `value`(Y 轴字段名)
|
|
556
|
-
- **chartNameField:** `category`(饼图名称字段)
|
|
654
|
+
### 各元素可绑定字段
|
|
557
655
|
|
|
558
|
-
|
|
656
|
+
| 元素 | 绑定属性 | 示例 |
|
|
657
|
+
|------|---------|------|
|
|
658
|
+
| 文本 / 长文本 | `content` | `订单号: {#orderNo}` |
|
|
659
|
+
| 图片 | `variable` | `@imageUrl` |
|
|
660
|
+
| 表格 | `variable` + `columnsVariable` + `footerDataVariable` | `@items`, `@tableCols`, `@tableFooter` |
|
|
661
|
+
| 自定义表格 | `customTableCells[].field` | `@customerName`(单元格字段) |
|
|
662
|
+
| 条形码 | `variable` 或 `content` | `@barcode` |
|
|
663
|
+
| 二维码 | `variable` 或 `content` | `@url` |
|
|
664
|
+
| 图表 | `chartDataVariable` + `chartXField` + `chartYField` | `@chartData`, `category`, `value` |
|
|
665
|
+
| 数据卡片 | `variable` | `@totalAmount` |
|
|
666
|
+
| 进度条 | `variable` | `@progress` |
|
|
667
|
+
| 日期 | `variable` | `@createTime` |
|
|
559
668
|
|
|
560
|
-
|
|
561
|
-
- `id` 是业务数据的唯一标识,用于在保存回调中识别当前编辑的数据
|
|
562
|
-
- 如果不传 `id`,保存时 `payload.id` 将为 `null`,表示新建数据
|
|
669
|
+
---
|
|
563
670
|
|
|
564
|
-
|
|
565
|
-
- `id`:业务数据(如订单、报告)的标识
|
|
566
|
-
- `templateId`:打印模板的标识,两者是完全不同的概念
|
|
671
|
+
## 图片元素使用说明
|
|
567
672
|
|
|
568
|
-
|
|
569
|
-
- 使用有意义的变量名,如 `customerName` 而非 `c`
|
|
570
|
-
- 避免使用特殊字符和空格
|
|
571
|
-
- 嵌套路径使用点号分隔,如 `@user.profile.name`
|
|
673
|
+
### 方式 1:直接输入图片 URL
|
|
572
674
|
|
|
573
|
-
|
|
574
|
-
- 表格和图表需要数组类型的数据
|
|
575
|
-
- 数组元素应包含一致的字段结构
|
|
576
|
-
- 示例:`items: [{name: "A", price: 100}, {name: "B", price: 200}]`
|
|
675
|
+
在属性面板的"图片地址"输入框填写 URL(支持 HTTP/HTTPS)。
|
|
577
676
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
677
|
+
```
|
|
678
|
+
https://example.com/logo.png
|
|
679
|
+
```
|
|
581
680
|
|
|
582
|
-
|
|
583
|
-
- 确保传入的数据字段与模板中绑定的变量名一致
|
|
584
|
-
- 如果变量找不到对应的数据,模板中会显示为空
|
|
681
|
+
### 方式 2:本地上传(自动转 Base64)
|
|
585
682
|
|
|
586
|
-
|
|
683
|
+
点击属性面板中的 **"上传图片"** 按钮或**双击画布上的图片元素**,从本地选择图片文件:
|
|
684
|
+
- 自动转为 Base64 并填入图片地址
|
|
685
|
+
- 限制最大 **2MB**
|
|
686
|
+
- 支持 PNG、JPG、GIF、WebP 等格式
|
|
587
687
|
|
|
588
|
-
|
|
688
|
+
### 方式 3:绑定变量
|
|
589
689
|
|
|
590
|
-
|
|
690
|
+
将 `variable` 绑定到变量名,图片地址从数据中动态读取。
|
|
591
691
|
|
|
592
|
-
|
|
692
|
+
| 绑定方式 | 示例 | 数据值要求 |
|
|
693
|
+
|---------|------|-----------|
|
|
694
|
+
| 静态变量 | `@logoUrl` | 字符串 URL 或 Base64 |
|
|
695
|
+
| 数组元素 | `@flightRecords[0].image` | 该字段值为 URL 或 Base64 |
|
|
593
696
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
697
|
+
**对应数据:**
|
|
698
|
+
|
|
699
|
+
```json
|
|
700
|
+
{
|
|
701
|
+
"variables": {
|
|
702
|
+
"logoUrl": "https://example.com/logo.png",
|
|
703
|
+
"avatarBase64": "data:image/png;base64,iVBORw0KGgo...",
|
|
704
|
+
"flightRecords": [
|
|
705
|
+
{ "image": "https://picsum.photos/id/1/200/80" }
|
|
706
|
+
]
|
|
707
|
+
}
|
|
600
708
|
}
|
|
601
709
|
```
|
|
602
710
|
|
|
603
|
-
###
|
|
711
|
+
### 方式对比
|
|
604
712
|
|
|
605
|
-
|
|
|
606
|
-
|
|
607
|
-
|
|
|
608
|
-
|
|
|
609
|
-
|
|
|
610
|
-
| `guides` | ✅ | ✅ | 辅助线 |
|
|
611
|
-
| `testData` | ❌ | ✅ | 传入的示例数据 |
|
|
612
|
-
| `unit` | ✅ | ✅ | 单位设置 |
|
|
613
|
-
| `showGrid` | ✅ | ✅ | 网格显示 |
|
|
614
|
-
| ... | ... | ... | 其他配置 |
|
|
713
|
+
| 方式 | 适用场景 | 存储内容 |
|
|
714
|
+
|------|---------|---------|
|
|
715
|
+
| URL 输入 | 固定图片、CDN 资源 | URL 字符串 |
|
|
716
|
+
| 本地上传 | 无公网链接的图片 | Base64 编码 |
|
|
717
|
+
| 变量绑定 | 每条数据图片不同 | URL 或 Base64 |
|
|
615
718
|
|
|
616
|
-
|
|
719
|
+
---
|
|
617
720
|
|
|
618
|
-
|
|
619
|
-
```javascript
|
|
620
|
-
{
|
|
621
|
-
id: "tpl_001", // 模板 ID
|
|
622
|
-
name: "订单打印模板",
|
|
623
|
-
data: {
|
|
624
|
-
pages: [...], // 页面布局
|
|
625
|
-
canvasSize: {...}, // 画布尺寸
|
|
626
|
-
watermark: {...}, // 水印设置
|
|
627
|
-
unit: "mm",
|
|
628
|
-
showGrid: true,
|
|
629
|
-
// ❌ 没有 testData 字段
|
|
630
|
-
},
|
|
631
|
-
isNew: false
|
|
632
|
-
}
|
|
633
|
-
```
|
|
721
|
+
## 保存回调 `onSave`
|
|
634
722
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
showGrid: true,
|
|
646
|
-
// ✅ 包含完整的 testData
|
|
647
|
-
testData: {
|
|
648
|
-
id: 8,
|
|
649
|
-
name: "第一季度销售报告",
|
|
650
|
-
totalAmount: 50000,
|
|
651
|
-
items: [
|
|
652
|
-
{ name: "产品A", price: 100, quantity: 200 },
|
|
653
|
-
{ name: "产品B", price: 200, quantity: 150 }
|
|
654
|
-
],
|
|
655
|
-
chartData: [
|
|
656
|
-
{ category: "一月", value: 1200 },
|
|
657
|
-
{ category: "二月", value: 1500 },
|
|
658
|
-
{ category: "三月", value: 1800 }
|
|
659
|
-
]
|
|
660
|
-
}
|
|
661
|
-
},
|
|
662
|
-
isNew: false
|
|
723
|
+
传入 `onSave` 后,用户保存(`Ctrl+S` 或工具栏保存按钮)时设计器不再自动持久化,而是将组装好的数据通过回调交给用户自行处理。
|
|
724
|
+
|
|
725
|
+
### 回调参数
|
|
726
|
+
|
|
727
|
+
```typescript
|
|
728
|
+
interface SavePayload {
|
|
729
|
+
id: string | null // 模板 ID,null 表示新建
|
|
730
|
+
name: string // 模板名称
|
|
731
|
+
data: Record<string, any> // 模板数据(含 pages、canvasSize、watermark 等)
|
|
732
|
+
isNew: boolean // 是否新建模板
|
|
663
733
|
}
|
|
664
734
|
```
|
|
665
735
|
|
|
736
|
+
### 不同模式下 data 内容差异
|
|
737
|
+
|
|
738
|
+
| 字段 | 模板模式 | 报告模式 |
|
|
739
|
+
|------|---------|---------|
|
|
740
|
+
| `pages` | ✅ | ✅ |
|
|
741
|
+
| `canvasSize` | ✅ | ✅ |
|
|
742
|
+
| `watermark` | ✅ | ✅ |
|
|
743
|
+
| `guides` | ✅ | ✅ |
|
|
744
|
+
| `unit` | ✅ | ✅ |
|
|
745
|
+
| `showGrid` | ✅ | ✅ |
|
|
746
|
+
| `headerHeight` / `footerHeight` | ✅ | ✅ |
|
|
747
|
+
| `canvasBackground` | ✅ | ✅ |
|
|
748
|
+
| `pageSpacingX` / `pageSpacingY` | ✅ | ✅ |
|
|
749
|
+
| `testData` | ❌ | ✅ |
|
|
750
|
+
|
|
666
751
|
### 使用示例
|
|
667
752
|
|
|
668
753
|
```vue
|
|
669
|
-
<
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
754
|
+
<template>
|
|
755
|
+
<PrintDesigner
|
|
756
|
+
:mode="mode"
|
|
757
|
+
:sample-data="sampleData"
|
|
758
|
+
:on-save="handleSave"
|
|
759
|
+
/>
|
|
760
|
+
</template>
|
|
673
761
|
|
|
674
|
-
<script setup>
|
|
675
|
-
import { ref } from 'vue'
|
|
762
|
+
<script setup lang="ts">
|
|
763
|
+
import { ref } from 'vue'
|
|
676
764
|
|
|
677
|
-
const mode = ref
|
|
765
|
+
const mode = ref<'template' | 'report'>('template')
|
|
678
766
|
|
|
679
767
|
const handleSave = async (payload) => {
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
768
|
+
// 根据模式决定 API 端点
|
|
769
|
+
const endpoint = mode.value === 'template'
|
|
770
|
+
? '/api/templates/save'
|
|
771
|
+
: '/api/reports/save'
|
|
772
|
+
|
|
773
|
+
const res = await fetch(endpoint, {
|
|
774
|
+
method: 'POST',
|
|
775
|
+
headers: { 'Content-Type': 'application/json' },
|
|
776
|
+
body: JSON.stringify(payload)
|
|
777
|
+
})
|
|
778
|
+
|
|
779
|
+
if (!res.ok) throw new Error('保存失败')
|
|
780
|
+
|
|
781
|
+
// 新建时获取后端返回的 ID
|
|
782
|
+
const result = await res.json()
|
|
783
|
+
if (payload.isNew && result.id) {
|
|
784
|
+
console.log('新模板 ID:', result.id)
|
|
690
785
|
}
|
|
691
|
-
}
|
|
786
|
+
}
|
|
692
787
|
</script>
|
|
693
788
|
```
|
|
694
789
|
|
|
695
|
-
> 不传 `onSave`
|
|
790
|
+
> 不传 `onSave` 时,设计器走内置默认保存逻辑(根据 CRUD 配置调远程接口或存 localStorage)。
|
|
696
791
|
|
|
697
792
|
---
|
|
698
793
|
|
|
699
794
|
## 自动保存配置
|
|
700
795
|
|
|
701
|
-
传入 `onSave`
|
|
796
|
+
传入 `onSave` 后可同时开启自动保存。右下角会显示"最近保存: HH:MM:SS"。
|
|
702
797
|
|
|
703
|
-
###
|
|
798
|
+
### Vue 组件
|
|
704
799
|
|
|
705
800
|
| 属性 | 类型 | 默认值 | 说明 |
|
|
706
801
|
|------|------|--------|------|
|
|
707
802
|
| `auto-save-enabled` | `boolean` | `false` | 是否开启 |
|
|
708
803
|
| `auto-save-interval-ms` | `number` | `120000` | 间隔(毫秒),默认 2 分钟 |
|
|
709
804
|
|
|
710
|
-
### 示例
|
|
711
|
-
|
|
712
805
|
```vue
|
|
713
806
|
<PrintDesigner
|
|
714
807
|
:on-save="handleSave"
|
|
@@ -717,19 +810,87 @@ const handleSave = async (payload) => {
|
|
|
717
810
|
/>
|
|
718
811
|
```
|
|
719
812
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
designer.
|
|
813
|
+
### Web Component
|
|
814
|
+
|
|
815
|
+
```javascript
|
|
816
|
+
designer.setOnSave(handleSave)
|
|
817
|
+
designer.setAutoSave(true, 120000) // 每 2 分钟自动保存
|
|
818
|
+
designer.setAutoSave(false) // 关闭
|
|
724
819
|
```
|
|
725
820
|
|
|
726
|
-
>
|
|
821
|
+
> 自动保存仅在内容有变更时触发,连续未变更的周期内不会重复保存。
|
|
727
822
|
|
|
728
823
|
---
|
|
729
824
|
|
|
730
|
-
##
|
|
825
|
+
## 打印与导出
|
|
826
|
+
|
|
827
|
+
### Web Component API
|
|
828
|
+
|
|
829
|
+
#### `print(options)`
|
|
830
|
+
|
|
831
|
+
```typescript
|
|
832
|
+
interface PrintOptions {
|
|
833
|
+
mode: 'browser' | 'silent'
|
|
834
|
+
options?: {
|
|
835
|
+
silent?: boolean // 静默打印(跳过浏览器打印对话框)
|
|
836
|
+
printerName?: string // 指定打印机名称
|
|
837
|
+
copies?: number // 打印份数,默认 1
|
|
838
|
+
duplex?: boolean // 双面打印
|
|
839
|
+
pageRanges?: string // 页码范围,如 '1-3'
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
```javascript
|
|
845
|
+
// 浏览器打印
|
|
846
|
+
await designer.print({ mode: 'browser' })
|
|
847
|
+
|
|
848
|
+
// 静默打印到指定打印机
|
|
849
|
+
await designer.print({
|
|
850
|
+
mode: 'silent',
|
|
851
|
+
options: {
|
|
852
|
+
printerName: 'HP-LaserJet',
|
|
853
|
+
copies: 2
|
|
854
|
+
}
|
|
855
|
+
})
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
#### `export(options)`
|
|
859
|
+
|
|
860
|
+
```typescript
|
|
861
|
+
interface ExportOptions {
|
|
862
|
+
type: 'pdf' | 'image' | 'html'
|
|
863
|
+
filename?: string // 输出文件名(不含扩展名)
|
|
864
|
+
}
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
```javascript
|
|
868
|
+
// 导出 PDF
|
|
869
|
+
await designer.export({ type: 'pdf', filename: '订单-2024' })
|
|
870
|
+
|
|
871
|
+
// 导出图片
|
|
872
|
+
await designer.export({ type: 'image', filename: '订单-2024' })
|
|
873
|
+
|
|
874
|
+
// 导出 HTML
|
|
875
|
+
await designer.export({ type: 'html', filename: '订单-2024' })
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### 快捷键
|
|
731
879
|
|
|
732
|
-
|
|
880
|
+
| 快捷键 | 功能 |
|
|
881
|
+
|--------|------|
|
|
882
|
+
| `Ctrl+S` | 保存 |
|
|
883
|
+
| `Ctrl+P` | 打印 |
|
|
884
|
+
| `Ctrl+Shift+P` | 预览 |
|
|
885
|
+
| `Ctrl+Shift+E` | 导出 PDF |
|
|
886
|
+
|
|
887
|
+
---
|
|
888
|
+
|
|
889
|
+
## 模板数据结构
|
|
890
|
+
|
|
891
|
+
当需要直接保存/加载完整模板时,使用以下数据结构。
|
|
892
|
+
|
|
893
|
+
### 完整 JSON 结构
|
|
733
894
|
|
|
734
895
|
```json
|
|
735
896
|
{
|
|
@@ -825,158 +986,173 @@ designer.setAutoSave(false); // 关闭
|
|
|
825
986
|
|
|
826
987
|
| 字段 | 类型 | 必填 | 说明 |
|
|
827
988
|
|-----|------|-----|------|
|
|
828
|
-
| `id` | `string` | 是 | 模板唯一 ID |
|
|
829
|
-
| `name` | `string` | 是 | 模板名称 |
|
|
989
|
+
| `id` | `string` | ✅ 是 | 模板唯一 ID |
|
|
990
|
+
| `name` | `string` | ✅ 是 | 模板名称 |
|
|
830
991
|
| `updatedAt` | `number` | 否 | 更新时间戳(毫秒) |
|
|
831
992
|
| `permissions` | `object` | 否 | `{ editable, deletable, copyable }` |
|
|
832
|
-
| `data` | `object` | 是 | 模板设计数据 |
|
|
993
|
+
| `data` | `object` | ✅ 是 | 模板设计数据 |
|
|
833
994
|
| `ext` | `object` | 否 | 扩展数据(变量树等) |
|
|
834
995
|
|
|
835
|
-
### `data`
|
|
996
|
+
### `data` 字段
|
|
836
997
|
|
|
837
998
|
| 字段 | 类型 | 必填 | 说明 |
|
|
838
999
|
|-----|------|-----|------|
|
|
839
|
-
| `pages` | `Page[]` | 是 | 页面列表,每页含 `elements` 数组 |
|
|
840
|
-
| `canvasSize` | `{ width, height }` | 是 |
|
|
841
|
-
| `unit` | `'mm'
|
|
1000
|
+
| `pages` | `Page[]` | ✅ 是 | 页面列表,每页含 `elements` 数组 |
|
|
1001
|
+
| `canvasSize` | `{ width, height }` | ✅ 是 | 画布尺寸 px(默认 A4: 794×1123) |
|
|
1002
|
+
| `unit` | `'mm' \| 'px' \| 'pt' \| 'in' \| 'cm'` | 否 | 单位,默认 `'mm'` |
|
|
842
1003
|
| `watermark` | `WatermarkSettings` | 否 | 水印配置 |
|
|
843
|
-
| `guides` | `Guide[]` | 否 |
|
|
844
|
-
| `zoom` | `number` | 否 |
|
|
1004
|
+
| `guides` | `Guide[]` | 否 | 辅助参考线 |
|
|
1005
|
+
| `zoom` | `number` | 否 | 缩放比例,默认 `1` |
|
|
845
1006
|
| `showGrid` | `boolean` | 否 | 显示网格 |
|
|
846
|
-
| `headerHeight` | `number` | 否 | 页眉高度 |
|
|
847
|
-
| `footerHeight` | `number` | 否 | 页脚高度 |
|
|
848
|
-
| `pageSpacingX` | `number` | 否 |
|
|
849
|
-
| `pageSpacingY` | `number` | 否 |
|
|
1007
|
+
| `headerHeight` | `number` | 否 | 页眉高度 px |
|
|
1008
|
+
| `footerHeight` | `number` | 否 | 页脚高度 px |
|
|
1009
|
+
| `pageSpacingX` | `number` | 否 | 水平页边距 px |
|
|
1010
|
+
| `pageSpacingY` | `number` | 否 | 垂直页边距 px |
|
|
850
1011
|
| `canvasBackground` | `string` | 否 | 画布背景色 |
|
|
851
|
-
| `testData` | `object` | 否 |
|
|
852
|
-
|
|
853
|
-
### 元素基本字段
|
|
854
|
-
|
|
855
|
-
```ts
|
|
856
|
-
interface PrintElement {
|
|
857
|
-
id: string; // 元素唯一 ID
|
|
858
|
-
type: ElementType; // 元素类型: 'text'|'image'|'table'|'customTable'|'barcode'|'qrcode'|'line'|'rect'|'circle'|'chart'|'pageNumber'|'longtext'
|
|
859
|
-
x: number; // X 坐标
|
|
860
|
-
y: number; // Y 坐标
|
|
861
|
-
width: number; // 宽度
|
|
862
|
-
height: number; // 高度
|
|
863
|
-
content?: string; // 文本内容(支持变量占位符)
|
|
864
|
-
variable?: string; // 绑定变量名(如 '@items')
|
|
865
|
-
style: ElementStyle; // 样式对象
|
|
866
|
-
}
|
|
867
|
-
```
|
|
1012
|
+
| `testData` | `object` | 否 | 测试数据(报告模式会保存,模板模式不保存) |
|
|
868
1013
|
|
|
869
1014
|
---
|
|
870
1015
|
|
|
871
|
-
##
|
|
1016
|
+
## 支持的元素类型
|
|
872
1017
|
|
|
873
|
-
|
|
1018
|
+
| 类型 | 标识 | 说明 |
|
|
1019
|
+
|------|------|------|
|
|
1020
|
+
| 文本 | `text` | 静态文本或变量内容 |
|
|
1021
|
+
| 长文本 | `longtext` | 多行自动扩展文本,支持彩色标记语法 `[color=#xxx]` |
|
|
1022
|
+
| 富文本 | `richtext` | HTML 富文本格式化内容 |
|
|
1023
|
+
| 图片 | `image` | 图片显示(URL / Base64 / 变量绑定) |
|
|
1024
|
+
| 表格 | `table` | 数据表格,支持自动分页、页脚汇总、自定义脚本 |
|
|
1025
|
+
| 自定义表格 | `customTable` | 自由合并单元格、单元格内嵌图片/字段、逐格自定义样式 |
|
|
1026
|
+
| 嵌套表格 | `nestedtable` | 分组嵌套数据展示(父级标签 + 子级行列表) |
|
|
1027
|
+
| 条形码 | `barcode` | 一维条形码(CODE128、EAN13、UPC 等) |
|
|
1028
|
+
| 二维码 | `qrcode` | 二维码(支持 L/M/Q/H 纠错级别) |
|
|
1029
|
+
| 图表 | `chart` | ECharts 图表(柱状图/折线图/饼图/环形图/面积图) |
|
|
1030
|
+
| 数据卡片 | `datacard` | 单个关键指标数据展示(含趋势箭头) |
|
|
1031
|
+
| 进度条 | `progress` | 百分比进度展示 |
|
|
1032
|
+
| 列表 | `list` | 编号列表数据展示(横向/纵向布局) |
|
|
1033
|
+
| 日期 | `date` | 日期时间显示(可绑定变量或显示当前时间) |
|
|
1034
|
+
| 页码 | `pageNumber` | 页码显示(支持多种格式) |
|
|
1035
|
+
| 签名/印章 | `signature` | 印章图片或二维码,支持透明度 |
|
|
1036
|
+
| 线条 | `line` | 直线(支持实线/虚线/点线) |
|
|
1037
|
+
| 矩形 | `rect` | 矩形(支持圆角) |
|
|
1038
|
+
| 圆形 | `circle` | 圆形 |
|
|
874
1039
|
|
|
875
|
-
|
|
876
|
-
|-----|------|------|
|
|
877
|
-
| `@变量名` | `@customerName` | 直接引用变量 |
|
|
878
|
-
| `{#变量路径}` | `{#order.items[0].name}` | 支持嵌套路径和数组索引 |
|
|
1040
|
+
---
|
|
879
1041
|
|
|
880
|
-
|
|
1042
|
+
## 版本兼容性说明
|
|
881
1043
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
|
885
|
-
|
|
886
|
-
|
|
|
887
|
-
|
|
|
888
|
-
|
|
|
889
|
-
|
|
|
1044
|
+
### 依赖版本矩阵
|
|
1045
|
+
|
|
1046
|
+
| 组件包 | 要求版本 | 说明 |
|
|
1047
|
+
|--------|---------|------|
|
|
1048
|
+
| `vue` | `^3.5.0` | 核心框架 |
|
|
1049
|
+
| `pinia` | `^2.3.0` | 状态管理 |
|
|
1050
|
+
| `echarts` | `^6.0.0` | 图表库(按需引入) |
|
|
1051
|
+
| `lodash` | `^4.17.0` | 工具函数 |
|
|
1052
|
+
| `qrcode` | `^1.5.0` | 二维码生成 |
|
|
1053
|
+
| `jsbarcode` | `^3.12.0` | 条形码生成 |
|
|
1054
|
+
| `jspdf` | `4.2.x` | PDF 导出 |
|
|
1055
|
+
| `tailwindcss` | `^3.x` | CSS 框架(构建时依赖) |
|
|
1056
|
+
|
|
1057
|
+
### 升级注意事项
|
|
1058
|
+
|
|
1059
|
+
- **从 `0.2.x` 升级到 `0.3.x`**:`onSave` 回调签名变化,`payload.data` 中 `testData` 的保存规则改为由 `mode` 决定
|
|
1060
|
+
- **Vue 版本**:仅支持 Vue 3.5+,不支持 Vue 2.x
|
|
1061
|
+
- **Node 版本**:`vite build` 和 `vue-tsc` 需要 Node >= 18
|
|
890
1062
|
|
|
891
1063
|
---
|
|
892
1064
|
|
|
893
|
-
##
|
|
1065
|
+
## 常见问题排查
|
|
894
1066
|
|
|
895
|
-
|
|
1067
|
+
### 1. 设计器加载后空白 / 无渲染
|
|
896
1068
|
|
|
897
|
-
|
|
1069
|
+
**原因:** 未引入样式文件或 `ready` 事件未触发。
|
|
898
1070
|
|
|
899
|
-
|
|
1071
|
+
**排查步骤:**
|
|
1072
|
+
1. 确认已引入 `x-print-designer/style.css` 或 `dist/print-designer.css`
|
|
1073
|
+
2. Web Component 方式需在 `ready` 事件后调用 API
|
|
1074
|
+
3. 检查浏览器控制台是否有报错
|
|
900
1075
|
|
|
901
|
-
|
|
902
|
-
https://example.com/logo.png
|
|
903
|
-
```
|
|
1076
|
+
### 2. 变量绑定不生效,显示为空或变量名
|
|
904
1077
|
|
|
905
|
-
|
|
1078
|
+
**原因:** 变量路径与数据字段不匹配。
|
|
906
1079
|
|
|
907
|
-
|
|
1080
|
+
**排查步骤:**
|
|
1081
|
+
1. 确认 `sampleData` / `setTestData` 中包含对应字段
|
|
1082
|
+
2. 检查变量名大小写是否一致(`@customerName` vs `customername`)
|
|
1083
|
+
3. 嵌套路径使用点号分隔:`@user.name`
|
|
1084
|
+
4. 确认数据格式:表格需要数组,图表需要 `Array<{category, value}>`
|
|
908
1085
|
|
|
909
|
-
|
|
910
|
-
- 限制最大 **2MB**,超出会提示错误
|
|
911
|
-
- 支持所有常见图片格式(PNG、JPG、GIF、WebP 等)
|
|
1086
|
+
### 3. 表格数据显示为空
|
|
912
1087
|
|
|
913
|
-
|
|
1088
|
+
**原因:** 表格 `variable` 未绑定或数据格式不匹配。
|
|
914
1089
|
|
|
915
|
-
|
|
1090
|
+
**排查步骤:**
|
|
1091
|
+
1. 确认表格元素的 `variable` 属性绑定了变量名
|
|
1092
|
+
2. 确认数据中该变量值为数组格式
|
|
1093
|
+
3. 如果使用 `columnsVariable`,确保列定义数组字段存在且包含 `{field, header}`
|
|
916
1094
|
|
|
917
|
-
|
|
1095
|
+
### 4. 打印/导出 PDF 时样式错乱
|
|
918
1096
|
|
|
919
|
-
|
|
920
|
-
|---------|------|-----------|
|
|
921
|
-
| 静态变量 | `@logoUrl` | 字符串:`"https://..."` 或 Base64 |
|
|
922
|
-
| 数组元素 | `@flightRecords[0].image` | 该字段值需为 URL 或 Base64 |
|
|
1097
|
+
**原因:** 打印管线依赖 iframe 渲染 + 计算样式内联,部分样式可能未正确捕获。
|
|
923
1098
|
|
|
924
|
-
|
|
1099
|
+
**排查步骤:**
|
|
1100
|
+
1. 优先使用预览(`Ctrl+Shift+P`)确认渲染效果
|
|
1101
|
+
2. 确认 PDF 导出使用的是 `export({ type: 'pdf' })` 而非浏览器打印
|
|
1102
|
+
3. 检查控制台是否有 `Failed to load` 相关错误
|
|
925
1103
|
|
|
926
|
-
|
|
927
|
-
{
|
|
928
|
-
"variables": {
|
|
929
|
-
"logoUrl": "https://example.com/logo.png",
|
|
930
|
-
"avatarBase64": "data:image/png;base64,iVBORw0KGgo...",
|
|
931
|
-
"flightRecords": [
|
|
932
|
-
{ "image": "https://picsum.photos/id/1/200/80" }
|
|
933
|
-
]
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
```
|
|
1104
|
+
### 5. 自定义表格预览时样式丢失
|
|
937
1105
|
|
|
938
|
-
|
|
1106
|
+
**原因:** 列宽未持久化或表格容器 `overflow: hidden` 约束未解锁。
|
|
939
1107
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
| 本地上传 | 无公网链接的图片 | Base64 编码字符串 |
|
|
944
|
-
| 变量绑定 | 每条数据图片不同 | URL 或 Base64 |
|
|
1108
|
+
**排查步骤:**
|
|
1109
|
+
1. 确认已保存模板(列宽需持久化到 `customTableColWidths`)
|
|
1110
|
+
2. 升级到最新版本(v0.3.1+ 已修复预览模式下列宽持久化和容器高度问题)
|
|
945
1111
|
|
|
946
|
-
|
|
1112
|
+
### 6. 图表在预览/导出时显示空白
|
|
947
1113
|
|
|
948
|
-
|
|
1114
|
+
**原因:** ECharts 异步渲染未完成,预览管线已截取快照。
|
|
949
1115
|
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
| 富文本 | `richtext` | 富文本格式化内容 |
|
|
955
|
-
| 图片 | `image` | 图片显示(URL / Base64 / 变量绑定) |
|
|
956
|
-
| 表格 | `table` | 数据表格,支持自动分页、页脚脚本 |
|
|
957
|
-
| 自定义表格 | `customTable` | 自由合并单元格、单元格内嵌图片/字段 |
|
|
958
|
-
| 嵌套表格 | `nestedtable` | 分组嵌套数据展示 |
|
|
959
|
-
| 条形码 | `barcode` | 一维条形码 |
|
|
960
|
-
| 二维码 | `qrcode` | 二维码 |
|
|
961
|
-
| 图表 | `chart` | ECharts(柱状图/折线图/饼图/环形图/面积图) |
|
|
962
|
-
| 数据卡片 | `datacard` | 单个关键指标数据展示 |
|
|
963
|
-
| 进度条 | `progress` | 百分比进度展示 |
|
|
964
|
-
| 列表 | `list` | 多行列表数据展示 |
|
|
965
|
-
| 日期 | `date` | 日期时间显示 |
|
|
966
|
-
| 页码 | `pageNumber` | 页码显示 |
|
|
967
|
-
| 签名/印章 | `signature` | 印章图片或二维码 |
|
|
968
|
-
| 线条 | `line` | 直线 |
|
|
969
|
-
| 矩形 | `rect` | 矩形 |
|
|
970
|
-
| 圆形 | `circle` | 圆形 |
|
|
1116
|
+
**排查步骤:**
|
|
1117
|
+
1. 升级到最新版本(已修复 ChartElement 异步渲染注册问题)
|
|
1118
|
+
2. 确认 `chartDataVariable` 绑定的数据格式正确
|
|
1119
|
+
3. 在编辑模式下确认图表正常显示后再预览
|
|
971
1120
|
|
|
972
|
-
|
|
1121
|
+
### 7. `onSave` 回调未触发
|
|
973
1122
|
|
|
974
|
-
|
|
1123
|
+
**原因:** 未正确传入 `onSave` 属性。
|
|
1124
|
+
|
|
1125
|
+
**排查步骤:**
|
|
1126
|
+
1. Vue 组件确认 `:on-save="handleSave"` 正确绑定
|
|
1127
|
+
2. Web Component 确认在 `ready` 事件后调用 `setOnSave`
|
|
1128
|
+
3. 检查 `handleSave` 函数是否存在且无语法错误
|
|
1129
|
+
4. 确认未同时传入自建的 CRUD 配置(`onSave` 会覆盖内置保存逻辑)
|
|
1130
|
+
|
|
1131
|
+
### 8. 自动保存不生效
|
|
975
1132
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1133
|
+
**原因:** 自动保存需要 `onSave` 回调。
|
|
1134
|
+
|
|
1135
|
+
**排查步骤:**
|
|
1136
|
+
1. 确认同时传入了 `onSave` 和 `auto-save-enabled`
|
|
1137
|
+
2. 自动保存仅在内容有变更时触发,未修改时不会保存
|
|
1138
|
+
3. 检查 `auto-save-interval-ms` 是否设置过短(频繁保存可能被节流)
|
|
981
1139
|
|
|
982
1140
|
---
|
|
1141
|
+
|
|
1142
|
+
## 技术栈
|
|
1143
|
+
|
|
1144
|
+
| 技术 | 用途 |
|
|
1145
|
+
|------|------|
|
|
1146
|
+
| Vue 3 + TypeScript | 核心框架 |
|
|
1147
|
+
| Pinia | 状态管理 |
|
|
1148
|
+
| Tailwind CSS | 原子化 CSS |
|
|
1149
|
+
| ECharts 6 | 图表渲染 |
|
|
1150
|
+
| Monaco Editor | 代码/JSON 编辑器 |
|
|
1151
|
+
| jsPDF | PDF 生成 |
|
|
1152
|
+
| jsBarcode | 条形码生成 |
|
|
1153
|
+
| qrcode | 二维码生成 |
|
|
1154
|
+
| canvg | SVG → Canvas 转换 |
|
|
1155
|
+
| dom-to-image-more | DOM → 图片导出 |
|
|
1156
|
+
| jszip | ZIP 打包 |
|
|
1157
|
+
| vue-i18n | 国际化(中文/English) |
|
|
1158
|
+
| Vite | 构建工具 |
|