x-print-designer 0.4.72 → 0.4.97
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 +1883 -1881
- package/dist/{JsBarcode-I6ybi9m3.js → JsBarcode-BmuAwTSm.js} +1 -1
- package/dist/{JsBarcode-1Tz2J3yI.js → JsBarcode-DCwuK6ec.js} +1 -1
- package/dist/{browser-BzDu2ZFc.js → browser-Ch15j3h8.js} +1 -1
- package/dist/{browser-Bt2SLjj3.js → browser-uNeAW4Fj.js} +1 -1
- package/dist/{dom-to-image-more.min-D9UtRRQ5.js → dom-to-image-more.min-D7Mr0Fb7.js} +1 -1
- package/dist/{index-jg5CiiyP.js → index-BVdz-moU.js} +1 -1
- package/dist/{index-BbQvxCw5.js → index-CgktOepo.js} +1763 -697
- package/dist/{index.es-CZCQqIFZ.js → index.es-Di2ovrCT.js} +1 -1
- package/dist/{jspdf.es.min-Dgo2kBfI.js → jspdf.es.min-CiEQ9Qem.js} +1 -1
- package/dist/{jszip.min-DBcbvmL-.js → jszip.min-ClukAAj8.js} +1 -1
- package/dist/print-designer.css +1 -1
- package/dist/print-designer.es.js +1 -1
- package/dist/print-designer.umd.js +39 -38
- package/dist/{vue-CHJWPhbl.js → vue-BKqSErqo.js} +1240 -376
- package/dist/vue.css +1 -1
- package/dist/vue.es.js +1 -1
- package/docs/AI_IMPORT_SKILL.md +1391 -1391
- package/docs/designs/2026-08-12-floating-typography-toolbar-design.md +126 -126
- package/docs/designs/2026-08-17-drag-in-smart-snap-design.md +89 -89
- package/docs/designs/2026-08-18-snap-guides-v2-design.md +77 -77
- package/docs/plans/2026-08-12-floating-typography-toolbar-workflow.md +87 -87
- package/docs/plans/2026-08-17-drag-in-smart-snap-workflow.md +59 -59
- package/docs/plans/2026-08-18-snap-guides-v2-workflow.md +47 -47
- package/docs/reports/2026-09-06-/345/212/237/350/203/275/346/265/213/350/257/225/344/273/273/345/212/241/346/212/245/345/221/212.md +197 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,1881 +1,1883 @@
|
|
|
1
|
-
# X-Print-Designer
|
|
2
|
-
|
|
3
|
-
基于 Vue 3 + TypeScript 开发的打印设计器,支持可视化拖拽设计打印模板,集成 ECharts 图表组件。
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 目录
|
|
8
|
-
|
|
9
|
-
- [前置依赖要求](#前置依赖要求)
|
|
10
|
-
- [快速开始](#快速开始)
|
|
11
|
-
- [源码开发](#源码开发)
|
|
12
|
-
- [Vue 组件接入(XPrintDesigner)](#vue-组件接入xprintdesigner)
|
|
13
|
-
- [5 分钟接入(推荐)](#5-分钟接入推荐)
|
|
14
|
-
- [Web Component 接入](#web-component-接入)
|
|
15
|
-
- [核心概念:模板模式 vs 报告模式](#核心概念模板模式-vs-报告模式)
|
|
16
|
-
- [Vue 组件使用说明](#vue-组件使用说明)
|
|
17
|
-
- [Web Component 使用说明](#web-component-使用说明)
|
|
18
|
-
- [完整 API 参考](#完整-api-参考)
|
|
19
|
-
- [场景化模板库](#场景化模板库)
|
|
20
|
-
- [数据驱动模板向导](#数据驱动模板向导)
|
|
21
|
-
- [组合与取消组合](#组合与取消组合)
|
|
22
|
-
- [远程 CRUD 配置](#远程-crud-配置)
|
|
23
|
-
- [传入数据格式](#传入数据格式)
|
|
24
|
-
- [元素数据绑定](#元素数据绑定)
|
|
25
|
-
- [图片元素使用说明](#图片元素使用说明)
|
|
26
|
-
- [保存回调 `onSave`](#保存回调-onsave)
|
|
27
|
-
- [自动保存配置](#自动保存配置)
|
|
28
|
-
- [打印与导出](#打印与导出)
|
|
29
|
-
- [模板数据结构](#模板数据结构)
|
|
30
|
-
- [支持的元素类型](#支持的元素类型)
|
|
31
|
-
- [版本兼容性说明](#版本兼容性说明)
|
|
32
|
-
- [常见问题排查](#常见问题排查)
|
|
33
|
-
- [技术栈](#技术栈)
|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## 前置依赖要求
|
|
38
|
-
|
|
39
|
-
| 依赖 | 最低版本 | 说明 |
|
|
40
|
-
|------|---------|------|
|
|
41
|
-
| **Node.js** | `>= 18` | 运行时环境 |
|
|
42
|
-
| **pnpm** | `>= 8` | 推荐包管理器(项目使用 `pnpm-lock.yaml`) |
|
|
43
|
-
| **Vue** | `^3.5` | 核心框架 |
|
|
44
|
-
|
|
45
|
-
### 浏览器兼容性
|
|
46
|
-
|
|
47
|
-
| 浏览器 | 最低版本 | 说明 |
|
|
48
|
-
|--------|---------|------|
|
|
49
|
-
| Chrome | >= 90 | 完全支持 |
|
|
50
|
-
| Edge | >= 90 | 完全支持 |
|
|
51
|
-
| Firefox | >= 90 | 完全支持 |
|
|
52
|
-
| Safari | >= 15 | 完全支持 |
|
|
53
|
-
|
|
54
|
-
> **说明:** 设计器基于现代浏览器 API(`ResizeObserver`、`CSS Grid`、`Shadow DOM`),不支持 IE 11 及以下版本。
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## 快速开始
|
|
59
|
-
|
|
60
|
-
### 源码开发
|
|
61
|
-
|
|
62
|
-
```bash
|
|
63
|
-
# 克隆仓库
|
|
64
|
-
git clone https://github.com/0ldFive/Vue-Print-Designer.git
|
|
65
|
-
cd x-print-designer
|
|
66
|
-
|
|
67
|
-
# 安装依赖
|
|
68
|
-
pnpm install
|
|
69
|
-
|
|
70
|
-
# 启动开发服务器
|
|
71
|
-
pnpm dev
|
|
72
|
-
|
|
73
|
-
# 构建生产包
|
|
74
|
-
pnpm build
|
|
75
|
-
|
|
76
|
-
# 构建 Web Component 包
|
|
77
|
-
pnpm build:wc
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
启动后浏览器访问 `http://localhost:5173` 即可看到设计器 Demo 页面(`index.html`)。
|
|
81
|
-
|
|
82
|
-
### Vue 组件接入(XPrintDesigner)
|
|
83
|
-
|
|
84
|
-
**适用于:** Vue 3 项目以一等公民组件方式接入。`XPrintDesigner` 是 Web Component 的 Vue 适配层:props 声明式传参、事件自动透传、ref 暴露常用方法,**无需手动管理 `ready` 时序**。
|
|
85
|
-
|
|
86
|
-
#### 基础用法(简化版)
|
|
87
|
-
|
|
88
|
-
使用新增的工具函数,**从 700 行减少到 30 行**:
|
|
89
|
-
|
|
90
|
-
```vue
|
|
91
|
-
<template>
|
|
92
|
-
<XPrintDesigner
|
|
93
|
-
:config="config"
|
|
94
|
-
@checklist="onChecklist"
|
|
95
|
-
@printed="onPrinted"
|
|
96
|
-
/>
|
|
97
|
-
</template>
|
|
98
|
-
|
|
99
|
-
<script setup lang="ts">
|
|
100
|
-
import { ref } from 'vue'
|
|
101
|
-
import { XPrintDesigner, createRestfulAdapter, buildFieldDictionary } from 'x-print-designer'
|
|
102
|
-
import 'x-print-designer/style.css'
|
|
103
|
-
|
|
104
|
-
// ✅ 使用 CRUD 适配器工厂(从 80 行减少到 10 行)
|
|
105
|
-
const crudAdapter = createRestfulAdapter({
|
|
106
|
-
baseURL: '/backend',
|
|
107
|
-
endpoints: {
|
|
108
|
-
list: '/report/template/list',
|
|
109
|
-
get: '/report/template/detail/:id',
|
|
110
|
-
upsert: '/report/template/edit',
|
|
111
|
-
},
|
|
112
|
-
transform: {
|
|
113
|
-
request: (data) => ({
|
|
114
|
-
id: data.id,
|
|
115
|
-
name: data.name,
|
|
116
|
-
templateJson: JSON.stringify(data.data)
|
|
117
|
-
}),
|
|
118
|
-
response: (res) => res.records.map(r => ({
|
|
119
|
-
id: r.id,
|
|
120
|
-
name: r.name,
|
|
121
|
-
data: JSON.parse(r.templateJson)
|
|
122
|
-
}))
|
|
123
|
-
},
|
|
124
|
-
onSave: async (data) => {
|
|
125
|
-
await editTemplate(data)
|
|
126
|
-
ElMessage.success('保存成功')
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
// ✅ 使用字段字典工具函数(从 40 行减少到 3 行)
|
|
131
|
-
const fieldDictionary = ref({})
|
|
132
|
-
async function loadFieldDictionary() {
|
|
133
|
-
const modules = await getReportTemplateFields(['media', 'device', 'wayline'])
|
|
134
|
-
fieldDictionary.value = buildFieldDictionary(modules, { autoResolveConflicts: true })
|
|
135
|
-
}
|
|
136
|
-
loadFieldDictionary()
|
|
137
|
-
|
|
138
|
-
const config = ref({
|
|
139
|
-
mode: 'template',
|
|
140
|
-
testData: { id: 1, title: '报告标题', items: [{ name: '产品A', price: 100 }] },
|
|
141
|
-
crud: {
|
|
142
|
-
mode: 'remote',
|
|
143
|
-
endpoints: crudAdapter.endpoints,
|
|
144
|
-
config: { fetcher: crudAdapter.fetcher }
|
|
145
|
-
},
|
|
146
|
-
fieldDictionary: fieldDictionary.value
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
const onChecklist = (detail) => {
|
|
150
|
-
console.log('接入自检', detail.checks, '全部通过:', detail.allPassed)
|
|
151
|
-
}
|
|
152
|
-
const onPrinted = (detail) => {
|
|
153
|
-
console.log('打印完成', detail)
|
|
154
|
-
}
|
|
155
|
-
</script>
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
#### 完整示例(不使用工具函数)
|
|
159
|
-
|
|
160
|
-
```vue
|
|
161
|
-
<template>
|
|
162
|
-
<XPrintDesigner
|
|
163
|
-
:config="config"
|
|
164
|
-
@checklist="onChecklist"
|
|
165
|
-
@printed="onPrinted"
|
|
166
|
-
/>
|
|
167
|
-
</template>
|
|
168
|
-
|
|
169
|
-
<script setup lang="ts">
|
|
170
|
-
import { ref } from 'vue'
|
|
171
|
-
import { XPrintDesigner } from 'x-print-designer'
|
|
172
|
-
import 'x-print-designer/style.css'
|
|
173
|
-
|
|
174
|
-
const config = ref({
|
|
175
|
-
mode: 'template',
|
|
176
|
-
testData: { id: 1, title: '报告标题', items: [{ name: '产品A', price: 100 }] },
|
|
177
|
-
onSave: async (payload) => {
|
|
178
|
-
await fetch('/api/templates/save', {
|
|
179
|
-
method: 'POST',
|
|
180
|
-
headers: { 'Content-Type': 'application/json' },
|
|
181
|
-
body: JSON.stringify(payload)
|
|
182
|
-
})
|
|
183
|
-
}
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
const onChecklist = (detail) => {
|
|
187
|
-
console.log('接入自检', detail.checks, '全部通过:', detail.allPassed)
|
|
188
|
-
}
|
|
189
|
-
const onPrinted = (detail) => {
|
|
190
|
-
console.log('打印完成', detail)
|
|
191
|
-
}
|
|
192
|
-
</script>
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
**Props:**
|
|
196
|
-
|
|
197
|
-
| 属性 | 类型 | 说明 |
|
|
198
|
-
|------|------|------|
|
|
199
|
-
| `config` | `DesignerInitConfig \| null` | 统一初始化配置(等价 `el.init(config)`),ready 前传入会自动排队 |
|
|
200
|
-
| `mode` | `'template' \| 'report'` | 工作模式 |
|
|
201
|
-
| `uiMode` | `'beginner' \| 'expert'` | 界面模式(简洁/完整) |
|
|
202
|
-
| `viewMode` | `'edit' \| 'preview'` | 视图模式 |
|
|
203
|
-
| `headless` | `boolean` | 无头模式 |
|
|
204
|
-
| `onSave` | `(payload) => void \| Promise<void>` | 保存回调(映射 `setOnSave`) |
|
|
205
|
-
| `theme` | `string` | 主题(映射 `setTheme`) |
|
|
206
|
-
|
|
207
|
-
**事件:** `ready` / `print` / `printed` / `export` / `exported` / `error` / `checklist`,与 Web Component 同名事件一一对应。
|
|
208
|
-
|
|
209
|
-
**ref 暴露方法:** `init(config)`、`setData(payload, options)`、`print(request)`、`export(request)`、`setOnSave(handler)`、`setPreviewMode(options)`。
|
|
210
|
-
|
|
211
|
-
> 更完整的 API(`setTemplateLibrary`、`setCrudEndpoints`、`setVariables` 等)可通过 `ref.value.$el` 拿到底层 `<print-designer>` 元素后直接调用,或使用下方 Web Component 方式。
|
|
212
|
-
|
|
213
|
-
### 5 分钟接入(推荐)
|
|
214
|
-
|
|
215
|
-
**适用于:** 想用最少代码快速接入的宿主(任意前端技术栈)。
|
|
216
|
-
|
|
217
|
-
只需 4 步、**≤10 行代码**,且**无需手动监听 `ready` 时序**:
|
|
218
|
-
|
|
219
|
-
```html
|
|
220
|
-
<!DOCTYPE html>
|
|
221
|
-
<html lang="zh-CN">
|
|
222
|
-
<head>
|
|
223
|
-
<meta charset="UTF-8" />
|
|
224
|
-
<!-- ①
|
|
225
|
-
|
|
226
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
designer.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
</
|
|
253
|
-
</
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
| `
|
|
279
|
-
| `
|
|
280
|
-
| `
|
|
281
|
-
| `
|
|
282
|
-
| `
|
|
283
|
-
| `
|
|
284
|
-
| `
|
|
285
|
-
| `
|
|
286
|
-
| `
|
|
287
|
-
| `
|
|
288
|
-
| `
|
|
289
|
-
| `
|
|
290
|
-
| `
|
|
291
|
-
| `
|
|
292
|
-
| `
|
|
293
|
-
| `
|
|
294
|
-
| `
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
import {
|
|
306
|
-
import 'x-print-designer
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
<
|
|
330
|
-
|
|
331
|
-
<
|
|
332
|
-
<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
designer.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
</
|
|
357
|
-
</
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
|
372
|
-
|
|
|
373
|
-
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
-
|
|
382
|
-
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
-
|
|
393
|
-
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
|
403
|
-
|
|
|
404
|
-
|
|
|
405
|
-
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
//
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
designer.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
//
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
designer.
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
<XPrintDesigner mode="
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
designer.setMode('
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
| `
|
|
476
|
-
| `
|
|
477
|
-
| `
|
|
478
|
-
| `
|
|
479
|
-
| `
|
|
480
|
-
| `
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
<
|
|
547
|
-
|
|
548
|
-
<!-- 或 UMD 方式 -->
|
|
549
|
-
<script src="/dist/print-designer.umd.js"></script>
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
| `
|
|
564
|
-
| `
|
|
565
|
-
| `
|
|
566
|
-
| `
|
|
567
|
-
| `
|
|
568
|
-
| `
|
|
569
|
-
| `
|
|
570
|
-
| `
|
|
571
|
-
| `
|
|
572
|
-
| `
|
|
573
|
-
| `
|
|
574
|
-
| `
|
|
575
|
-
| `
|
|
576
|
-
| `
|
|
577
|
-
| `
|
|
578
|
-
| `
|
|
579
|
-
| `
|
|
580
|
-
| `
|
|
581
|
-
| `
|
|
582
|
-
| `
|
|
583
|
-
| `
|
|
584
|
-
| `
|
|
585
|
-
| `
|
|
586
|
-
| `
|
|
587
|
-
| `
|
|
588
|
-
| `
|
|
589
|
-
| `
|
|
590
|
-
| `
|
|
591
|
-
| `
|
|
592
|
-
| `
|
|
593
|
-
| `
|
|
594
|
-
| `
|
|
595
|
-
| `
|
|
596
|
-
| `
|
|
597
|
-
| `
|
|
598
|
-
| `
|
|
599
|
-
| `
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
| `
|
|
609
|
-
| `
|
|
610
|
-
| `
|
|
611
|
-
| `
|
|
612
|
-
| `
|
|
613
|
-
| `
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
{ name: '产品
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
| `
|
|
733
|
-
| `
|
|
734
|
-
| `
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
-
|
|
781
|
-
-
|
|
782
|
-
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
>
|
|
794
|
-
>
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
{
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
| `
|
|
853
|
-
| `
|
|
854
|
-
| `
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
-
|
|
860
|
-
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
},
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
{ fieldKey: '
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
{ fieldKey: '
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
{
|
|
990
|
-
'device.
|
|
991
|
-
'
|
|
992
|
-
'task.
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
//
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
//
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
| `presets.
|
|
1058
|
-
| `presets.
|
|
1059
|
-
| `presets.
|
|
1060
|
-
| `presets.
|
|
1061
|
-
| `presets.
|
|
1062
|
-
| `presets.
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
|
1155
|
-
|
|
|
1156
|
-
|
|
|
1157
|
-
|
|
|
1158
|
-
|
|
|
1159
|
-
|
|
|
1160
|
-
|
|
|
1161
|
-
|
|
|
1162
|
-
|
|
|
1163
|
-
|
|
|
1164
|
-
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
| `
|
|
1193
|
-
| `
|
|
1194
|
-
| `
|
|
1195
|
-
|
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
"
|
|
1205
|
-
"
|
|
1206
|
-
"
|
|
1207
|
-
|
|
1208
|
-
"
|
|
1209
|
-
"
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
"
|
|
1227
|
-
"
|
|
1228
|
-
"
|
|
1229
|
-
"
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
|
1248
|
-
|
|
|
1249
|
-
|
|
|
1250
|
-
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
| `
|
|
1260
|
-
| `
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
{ "name": "产品
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
{ "field": "
|
|
1271
|
-
{ "field": "
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
{ "category": "
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
- **
|
|
1293
|
-
- **
|
|
1294
|
-
- **
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
"
|
|
1302
|
-
"
|
|
1303
|
-
"
|
|
1304
|
-
"
|
|
1305
|
-
|
|
1306
|
-
"
|
|
1307
|
-
"
|
|
1308
|
-
"
|
|
1309
|
-
"
|
|
1310
|
-
"
|
|
1311
|
-
"
|
|
1312
|
-
"
|
|
1313
|
-
"
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
"
|
|
1317
|
-
"
|
|
1318
|
-
"
|
|
1319
|
-
"
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
{ "category": "
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
- `
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
-
|
|
1343
|
-
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
|
1370
|
-
|
|
|
1371
|
-
|
|
|
1372
|
-
|
|
|
1373
|
-
|
|
|
1374
|
-
|
|
|
1375
|
-
|
|
|
1376
|
-
|
|
|
1377
|
-
|
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
-
|
|
1396
|
-
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
"
|
|
1414
|
-
"
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
|
1427
|
-
|
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
| `
|
|
1452
|
-
| `
|
|
1453
|
-
| `
|
|
1454
|
-
| `
|
|
1455
|
-
| `
|
|
1456
|
-
| `
|
|
1457
|
-
| `
|
|
1458
|
-
| `
|
|
1459
|
-
| `
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
:
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
import {
|
|
1477
|
-
import 'x-print-designer
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
| `autoSave.
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
designer.
|
|
1540
|
-
designer.setAutoSave(
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
| `Ctrl+
|
|
1606
|
-
| `Ctrl+
|
|
1607
|
-
| `Ctrl+Shift+
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
"
|
|
1621
|
-
"
|
|
1622
|
-
"
|
|
1623
|
-
|
|
1624
|
-
"
|
|
1625
|
-
"
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
"
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
"
|
|
1635
|
-
"
|
|
1636
|
-
"
|
|
1637
|
-
"
|
|
1638
|
-
"
|
|
1639
|
-
"
|
|
1640
|
-
"
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
"
|
|
1645
|
-
"
|
|
1646
|
-
"
|
|
1647
|
-
"
|
|
1648
|
-
"
|
|
1649
|
-
"
|
|
1650
|
-
"
|
|
1651
|
-
"
|
|
1652
|
-
"
|
|
1653
|
-
"
|
|
1654
|
-
"
|
|
1655
|
-
|
|
1656
|
-
"
|
|
1657
|
-
"
|
|
1658
|
-
"
|
|
1659
|
-
"
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
"
|
|
1667
|
-
"
|
|
1668
|
-
"
|
|
1669
|
-
"
|
|
1670
|
-
"
|
|
1671
|
-
"
|
|
1672
|
-
"
|
|
1673
|
-
"
|
|
1674
|
-
"
|
|
1675
|
-
"
|
|
1676
|
-
"
|
|
1677
|
-
"
|
|
1678
|
-
"
|
|
1679
|
-
|
|
1680
|
-
"
|
|
1681
|
-
"
|
|
1682
|
-
"
|
|
1683
|
-
"
|
|
1684
|
-
"
|
|
1685
|
-
"
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
"
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
"
|
|
1697
|
-
"
|
|
1698
|
-
|
|
1699
|
-
{ "id": "
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
}
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
| `
|
|
1713
|
-
| `
|
|
1714
|
-
| `
|
|
1715
|
-
| `
|
|
1716
|
-
| `
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
| `
|
|
1724
|
-
| `
|
|
1725
|
-
| `
|
|
1726
|
-
| `
|
|
1727
|
-
| `
|
|
1728
|
-
| `
|
|
1729
|
-
| `
|
|
1730
|
-
| `
|
|
1731
|
-
| `
|
|
1732
|
-
| `
|
|
1733
|
-
| `
|
|
1734
|
-
| `
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
|
1744
|
-
|
|
|
1745
|
-
|
|
|
1746
|
-
|
|
|
1747
|
-
|
|
|
1748
|
-
|
|
|
1749
|
-
|
|
|
1750
|
-
|
|
|
1751
|
-
|
|
|
1752
|
-
|
|
|
1753
|
-
|
|
|
1754
|
-
|
|
|
1755
|
-
|
|
|
1756
|
-
|
|
|
1757
|
-
|
|
|
1758
|
-
|
|
|
1759
|
-
|
|
|
1760
|
-
|
|
|
1761
|
-
|
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
| `
|
|
1773
|
-
| `
|
|
1774
|
-
| `
|
|
1775
|
-
| `
|
|
1776
|
-
| `
|
|
1777
|
-
| `
|
|
1778
|
-
| `
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
-
|
|
1784
|
-
- **
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
|
1870
|
-
|
|
1871
|
-
|
|
|
1872
|
-
|
|
|
1873
|
-
|
|
|
1874
|
-
|
|
|
1875
|
-
|
|
|
1876
|
-
|
|
|
1877
|
-
|
|
|
1878
|
-
|
|
|
1879
|
-
|
|
|
1880
|
-
|
|
|
1881
|
-
|
|
|
1
|
+
# X-Print-Designer
|
|
2
|
+
|
|
3
|
+
基于 Vue 3 + TypeScript 开发的打印设计器,支持可视化拖拽设计打印模板,集成 ECharts 图表组件。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 目录
|
|
8
|
+
|
|
9
|
+
- [前置依赖要求](#前置依赖要求)
|
|
10
|
+
- [快速开始](#快速开始)
|
|
11
|
+
- [源码开发](#源码开发)
|
|
12
|
+
- [Vue 组件接入(XPrintDesigner)](#vue-组件接入xprintdesigner)
|
|
13
|
+
- [5 分钟接入(推荐)](#5-分钟接入推荐)
|
|
14
|
+
- [Web Component 接入](#web-component-接入)
|
|
15
|
+
- [核心概念:模板模式 vs 报告模式](#核心概念模板模式-vs-报告模式)
|
|
16
|
+
- [Vue 组件使用说明](#vue-组件使用说明)
|
|
17
|
+
- [Web Component 使用说明](#web-component-使用说明)
|
|
18
|
+
- [完整 API 参考](#完整-api-参考)
|
|
19
|
+
- [场景化模板库](#场景化模板库)
|
|
20
|
+
- [数据驱动模板向导](#数据驱动模板向导)
|
|
21
|
+
- [组合与取消组合](#组合与取消组合)
|
|
22
|
+
- [远程 CRUD 配置](#远程-crud-配置)
|
|
23
|
+
- [传入数据格式](#传入数据格式)
|
|
24
|
+
- [元素数据绑定](#元素数据绑定)
|
|
25
|
+
- [图片元素使用说明](#图片元素使用说明)
|
|
26
|
+
- [保存回调 `onSave`](#保存回调-onsave)
|
|
27
|
+
- [自动保存配置](#自动保存配置)
|
|
28
|
+
- [打印与导出](#打印与导出)
|
|
29
|
+
- [模板数据结构](#模板数据结构)
|
|
30
|
+
- [支持的元素类型](#支持的元素类型)
|
|
31
|
+
- [版本兼容性说明](#版本兼容性说明)
|
|
32
|
+
- [常见问题排查](#常见问题排查)
|
|
33
|
+
- [技术栈](#技术栈)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 前置依赖要求
|
|
38
|
+
|
|
39
|
+
| 依赖 | 最低版本 | 说明 |
|
|
40
|
+
|------|---------|------|
|
|
41
|
+
| **Node.js** | `>= 18` | 运行时环境 |
|
|
42
|
+
| **pnpm** | `>= 8` | 推荐包管理器(项目使用 `pnpm-lock.yaml`) |
|
|
43
|
+
| **Vue** | `^3.5` | 核心框架 |
|
|
44
|
+
|
|
45
|
+
### 浏览器兼容性
|
|
46
|
+
|
|
47
|
+
| 浏览器 | 最低版本 | 说明 |
|
|
48
|
+
|--------|---------|------|
|
|
49
|
+
| Chrome | >= 90 | 完全支持 |
|
|
50
|
+
| Edge | >= 90 | 完全支持 |
|
|
51
|
+
| Firefox | >= 90 | 完全支持 |
|
|
52
|
+
| Safari | >= 15 | 完全支持 |
|
|
53
|
+
|
|
54
|
+
> **说明:** 设计器基于现代浏览器 API(`ResizeObserver`、`CSS Grid`、`Shadow DOM`),不支持 IE 11 及以下版本。
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 快速开始
|
|
59
|
+
|
|
60
|
+
### 源码开发
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# 克隆仓库
|
|
64
|
+
git clone https://github.com/0ldFive/Vue-Print-Designer.git
|
|
65
|
+
cd x-print-designer
|
|
66
|
+
|
|
67
|
+
# 安装依赖
|
|
68
|
+
pnpm install
|
|
69
|
+
|
|
70
|
+
# 启动开发服务器
|
|
71
|
+
pnpm dev
|
|
72
|
+
|
|
73
|
+
# 构建生产包
|
|
74
|
+
pnpm build
|
|
75
|
+
|
|
76
|
+
# 构建 Web Component 包
|
|
77
|
+
pnpm build:wc
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
启动后浏览器访问 `http://localhost:5173` 即可看到设计器 Demo 页面(`index.html`)。
|
|
81
|
+
|
|
82
|
+
### Vue 组件接入(XPrintDesigner)
|
|
83
|
+
|
|
84
|
+
**适用于:** Vue 3 项目以一等公民组件方式接入。`XPrintDesigner` 是 Web Component 的 Vue 适配层:props 声明式传参、事件自动透传、ref 暴露常用方法,**无需手动管理 `ready` 时序**。
|
|
85
|
+
|
|
86
|
+
#### 基础用法(简化版)
|
|
87
|
+
|
|
88
|
+
使用新增的工具函数,**从 700 行减少到 30 行**:
|
|
89
|
+
|
|
90
|
+
```vue
|
|
91
|
+
<template>
|
|
92
|
+
<XPrintDesigner
|
|
93
|
+
:config="config"
|
|
94
|
+
@checklist="onChecklist"
|
|
95
|
+
@printed="onPrinted"
|
|
96
|
+
/>
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<script setup lang="ts">
|
|
100
|
+
import { ref } from 'vue'
|
|
101
|
+
import { XPrintDesigner, createRestfulAdapter, buildFieldDictionary } from 'x-print-designer'
|
|
102
|
+
import 'x-print-designer/style.css'
|
|
103
|
+
|
|
104
|
+
// ✅ 使用 CRUD 适配器工厂(从 80 行减少到 10 行)
|
|
105
|
+
const crudAdapter = createRestfulAdapter({
|
|
106
|
+
baseURL: '/backend',
|
|
107
|
+
endpoints: {
|
|
108
|
+
list: '/report/template/list',
|
|
109
|
+
get: '/report/template/detail/:id',
|
|
110
|
+
upsert: '/report/template/edit',
|
|
111
|
+
},
|
|
112
|
+
transform: {
|
|
113
|
+
request: (data) => ({
|
|
114
|
+
id: data.id,
|
|
115
|
+
name: data.name,
|
|
116
|
+
templateJson: JSON.stringify(data.data)
|
|
117
|
+
}),
|
|
118
|
+
response: (res) => res.records.map(r => ({
|
|
119
|
+
id: r.id,
|
|
120
|
+
name: r.name,
|
|
121
|
+
data: JSON.parse(r.templateJson)
|
|
122
|
+
}))
|
|
123
|
+
},
|
|
124
|
+
onSave: async (data) => {
|
|
125
|
+
await editTemplate(data)
|
|
126
|
+
ElMessage.success('保存成功')
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// ✅ 使用字段字典工具函数(从 40 行减少到 3 行)
|
|
131
|
+
const fieldDictionary = ref({})
|
|
132
|
+
async function loadFieldDictionary() {
|
|
133
|
+
const modules = await getReportTemplateFields(['media', 'device', 'wayline'])
|
|
134
|
+
fieldDictionary.value = buildFieldDictionary(modules, { autoResolveConflicts: true })
|
|
135
|
+
}
|
|
136
|
+
loadFieldDictionary()
|
|
137
|
+
|
|
138
|
+
const config = ref({
|
|
139
|
+
mode: 'template',
|
|
140
|
+
testData: { id: 1, title: '报告标题', items: [{ name: '产品A', price: 100 }] },
|
|
141
|
+
crud: {
|
|
142
|
+
mode: 'remote',
|
|
143
|
+
endpoints: crudAdapter.endpoints,
|
|
144
|
+
config: { fetcher: crudAdapter.fetcher }
|
|
145
|
+
},
|
|
146
|
+
fieldDictionary: fieldDictionary.value
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const onChecklist = (detail) => {
|
|
150
|
+
console.log('接入自检', detail.checks, '全部通过:', detail.allPassed)
|
|
151
|
+
}
|
|
152
|
+
const onPrinted = (detail) => {
|
|
153
|
+
console.log('打印完成', detail)
|
|
154
|
+
}
|
|
155
|
+
</script>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### 完整示例(不使用工具函数)
|
|
159
|
+
|
|
160
|
+
```vue
|
|
161
|
+
<template>
|
|
162
|
+
<XPrintDesigner
|
|
163
|
+
:config="config"
|
|
164
|
+
@checklist="onChecklist"
|
|
165
|
+
@printed="onPrinted"
|
|
166
|
+
/>
|
|
167
|
+
</template>
|
|
168
|
+
|
|
169
|
+
<script setup lang="ts">
|
|
170
|
+
import { ref } from 'vue'
|
|
171
|
+
import { XPrintDesigner } from 'x-print-designer'
|
|
172
|
+
import 'x-print-designer/style.css'
|
|
173
|
+
|
|
174
|
+
const config = ref({
|
|
175
|
+
mode: 'template',
|
|
176
|
+
testData: { id: 1, title: '报告标题', items: [{ name: '产品A', price: 100 }] },
|
|
177
|
+
onSave: async (payload) => {
|
|
178
|
+
await fetch('/api/templates/save', {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: { 'Content-Type': 'application/json' },
|
|
181
|
+
body: JSON.stringify(payload)
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
const onChecklist = (detail) => {
|
|
187
|
+
console.log('接入自检', detail.checks, '全部通过:', detail.allPassed)
|
|
188
|
+
}
|
|
189
|
+
const onPrinted = (detail) => {
|
|
190
|
+
console.log('打印完成', detail)
|
|
191
|
+
}
|
|
192
|
+
</script>
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Props:**
|
|
196
|
+
|
|
197
|
+
| 属性 | 类型 | 说明 |
|
|
198
|
+
|------|------|------|
|
|
199
|
+
| `config` | `DesignerInitConfig \| null` | 统一初始化配置(等价 `el.init(config)`),ready 前传入会自动排队 |
|
|
200
|
+
| `mode` | `'template' \| 'report'` | 工作模式 |
|
|
201
|
+
| `uiMode` | `'beginner' \| 'expert'` | 界面模式(简洁/完整) |
|
|
202
|
+
| `viewMode` | `'edit' \| 'preview'` | 视图模式 |
|
|
203
|
+
| `headless` | `boolean` | 无头模式 |
|
|
204
|
+
| `onSave` | `(payload) => void \| Promise<void>` | 保存回调(映射 `setOnSave`) |
|
|
205
|
+
| `theme` | `string` | 主题(映射 `setTheme`) |
|
|
206
|
+
|
|
207
|
+
**事件:** `ready` / `print` / `printed` / `export` / `exported` / `error` / `checklist`,与 Web Component 同名事件一一对应。
|
|
208
|
+
|
|
209
|
+
**ref 暴露方法:** `init(config)`、`setData(payload, options)`、`print(request)`、`export(request)`、`setOnSave(handler)`、`setPreviewMode(options)`。
|
|
210
|
+
|
|
211
|
+
> 更完整的 API(`setTemplateLibrary`、`setCrudEndpoints`、`setVariables` 等)可通过 `ref.value.$el` 拿到底层 `<print-designer>` 元素后直接调用,或使用下方 Web Component 方式。
|
|
212
|
+
|
|
213
|
+
### 5 分钟接入(推荐)
|
|
214
|
+
|
|
215
|
+
**适用于:** 想用最少代码快速接入的宿主(任意前端技术栈)。
|
|
216
|
+
|
|
217
|
+
只需 4 步、**≤10 行代码**,且**无需手动监听 `ready` 时序**:
|
|
218
|
+
|
|
219
|
+
```html
|
|
220
|
+
<!DOCTYPE html>
|
|
221
|
+
<html lang="zh-CN">
|
|
222
|
+
<head>
|
|
223
|
+
<meta charset="UTF-8" />
|
|
224
|
+
<!-- ① 引入脚本:全部样式(Tailwind 工具类 + 组件样式)已内联进 JS,
|
|
225
|
+
宿主无需使用 Tailwind,也无需手动引入 CSS;link 仅按需可选 -->
|
|
226
|
+
<script type="module" src="/dist/print-designer.es.js"></script>
|
|
227
|
+
<!-- 可选:<link rel="stylesheet" href="/dist/print-designer.css" /> -->
|
|
228
|
+
</head>
|
|
229
|
+
<body>
|
|
230
|
+
<!-- ② 放置组件标签 -->
|
|
231
|
+
<print-designer id="designer"></print-designer>
|
|
232
|
+
|
|
233
|
+
<script>
|
|
234
|
+
// ③ 一次性传入全部配置(init 方法通道)
|
|
235
|
+
const designer = document.getElementById('designer')
|
|
236
|
+
designer.init({
|
|
237
|
+
mode: 'template',
|
|
238
|
+
testData: { id: 1, title: '报告标题', items: [{ name: '产品A', price: 100 }] },
|
|
239
|
+
onSave: (payload) => {
|
|
240
|
+
fetch('/api/templates/save', {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
headers: { 'Content-Type': 'application/json' },
|
|
243
|
+
body: JSON.stringify(payload)
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// ④ 可选:监听接入自检结果
|
|
249
|
+
designer.addEventListener('checklist', (e) => {
|
|
250
|
+
console.log('接入自检', e.detail.checks, '全部通过:', e.detail.allPassed)
|
|
251
|
+
})
|
|
252
|
+
</script>
|
|
253
|
+
</body>
|
|
254
|
+
</html>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
**方法通道 vs 属性通道(二选一):**
|
|
258
|
+
|
|
259
|
+
- **方法通道(推荐)**:直接调用 `el.init(config)`。`ready` 前调用会自动排队,`ready` 后自动应用,无需自己等事件。
|
|
260
|
+
- **属性通道**:通过 `config` 属性声明式传入 JSON,效果等价:
|
|
261
|
+
|
|
262
|
+
```html
|
|
263
|
+
<print-designer
|
|
264
|
+
config='{"mode":"template","testData":{"id":1,"title":"报告标题"},"onSave":null}'
|
|
265
|
+
></print-designer>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
> 若 `config` 属性 JSON 解析失败,组件会派发 `error` 事件(`detail.scope === 'config'`)且不影响默认 UI。
|
|
269
|
+
|
|
270
|
+
**接入自检 `checklist` 事件:** 组件在 `ready` 时自动派发,`detail` 形如 `{ checks: [{ name, passed, hint? }...], allPassed }`,检查项包括 `ready`、`dataChannel`、`printApi`、`exportApi`、`onSaveBound`、`monacoLoader`。若存在未通过项,控制台会出现 `[PrintDesigner] 接入自检未全部通过:...` 的 `warn` 提示(例如未配置 `onSave` 时 `onSaveBound` 为 `false`),便于快速定位遗漏。
|
|
271
|
+
|
|
272
|
+
#### `init(config)` 完整配置字段
|
|
273
|
+
|
|
274
|
+
`DesignerInitConfig` 一次调用即可完成全部初始化;`ready` 前调用自动排队,`ready` 后立即应用。**显式调用过的 setter 优先级高于 init 配置**(不会双写冲突)。
|
|
275
|
+
|
|
276
|
+
| 字段 | 类型 | 说明 |
|
|
277
|
+
|------|------|------|
|
|
278
|
+
| `mode` | `'template' \| 'report'` | 工作模式 |
|
|
279
|
+
| `viewMode` | `'edit' \| 'preview'` | 视图模式 |
|
|
280
|
+
| `uiMode` | `'beginner' \| 'expert' \| null` | 界面模式(简洁/完整) |
|
|
281
|
+
| `language` | `string` | 语言(`'zh'` / `'en'`) |
|
|
282
|
+
| `headless` | `boolean` | 无头模式 |
|
|
283
|
+
| `data` | `Record<string, any>` | 数据(走 `setData` 默认通道) |
|
|
284
|
+
| `testData` | `Record<string, any>` | 测试/预览数据 |
|
|
285
|
+
| `variables` | `Record<string, any>` | 运行时变量(打印/导出时覆盖 testData) |
|
|
286
|
+
| `onSave` | `(payload) => void \| Promise<void> \| null` | 保存回调 |
|
|
287
|
+
| `autoSave` | `{ enabled, intervalMs? }` | 自动保存 |
|
|
288
|
+
| `fieldDictionary` | `DesignerFieldDictionary \| null` | 字段字典(变量面板中文标签) |
|
|
289
|
+
| `textPolishHandler` | `(text) => Promise<string> \| null` | 文本润色处理器(宿主 AI 能力) |
|
|
290
|
+
| `textPolishConfig` | `TextPolishConfig \| null` | 内置 DeepSeek 润色配置 |
|
|
291
|
+
| `branding` | `{ title?, logoUrl?, showTitle?, showLogo? }` | 品牌标识 |
|
|
292
|
+
| `theme` | `string` | 主题(`'light'` / `'dark'` / `'system'`) |
|
|
293
|
+
| `printDefaults` | `DesignerPrintDefaults` | 默认打印/导出选项与连接设置 |
|
|
294
|
+
| `previewMode` | `DesignerPreviewModeOptions` | 独立预览模式 |
|
|
295
|
+
| `crud` | `{ mode?, endpoints?, config? }` | 远程 CRUD 配置(见[远程 CRUD 配置](#远程-crud-配置)) |
|
|
296
|
+
|
|
297
|
+
**Vue 宿主 5 分钟接入(XPrintDesigner):** 用法与 Web Component 完全等价,仅换为组件写法:
|
|
298
|
+
|
|
299
|
+
```vue
|
|
300
|
+
<template>
|
|
301
|
+
<XPrintDesigner :config="config" />
|
|
302
|
+
</template>
|
|
303
|
+
|
|
304
|
+
<script setup lang="ts">
|
|
305
|
+
import { ref } from 'vue'
|
|
306
|
+
import { XPrintDesigner } from 'x-print-designer'
|
|
307
|
+
import 'x-print-designer/style.css'
|
|
308
|
+
|
|
309
|
+
const config = ref({
|
|
310
|
+
mode: 'template',
|
|
311
|
+
testData: { id: 1, title: '报告标题', items: [{ name: '产品A', price: 100 }] },
|
|
312
|
+
onSave: async (payload) => {
|
|
313
|
+
await fetch('/api/templates/save', {
|
|
314
|
+
method: 'POST',
|
|
315
|
+
headers: { 'Content-Type': 'application/json' },
|
|
316
|
+
body: JSON.stringify(payload)
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
})
|
|
320
|
+
</script>
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Web Component 接入
|
|
324
|
+
|
|
325
|
+
**适用于:** 任意前端技术栈(React / Angular / 原生 HTML),以自定义标签方式嵌入。
|
|
326
|
+
|
|
327
|
+
```html
|
|
328
|
+
<!DOCTYPE html>
|
|
329
|
+
<html lang="zh-CN">
|
|
330
|
+
<head>
|
|
331
|
+
<meta charset="UTF-8" />
|
|
332
|
+
<script type="module" src="/dist/print-designer.es.js"></script>
|
|
333
|
+
<!-- 样式已内联,无需宿主引入 Tailwind;print-designer.css 可选 -->
|
|
334
|
+
</head>
|
|
335
|
+
<body>
|
|
336
|
+
<print-designer id="designer"></print-designer>
|
|
337
|
+
|
|
338
|
+
<script>
|
|
339
|
+
const designer = document.getElementById('designer')
|
|
340
|
+
|
|
341
|
+
designer.addEventListener('ready', () => {
|
|
342
|
+
designer.setMode('template')
|
|
343
|
+
designer.setTestData({
|
|
344
|
+
id: 1,
|
|
345
|
+
title: '报告标题',
|
|
346
|
+
items: [{ name: '产品A', price: 100 }]
|
|
347
|
+
})
|
|
348
|
+
designer.setOnSave((payload) => {
|
|
349
|
+
fetch('/api/templates/save', {
|
|
350
|
+
method: 'POST',
|
|
351
|
+
headers: { 'Content-Type': 'application/json' },
|
|
352
|
+
body: JSON.stringify(payload)
|
|
353
|
+
})
|
|
354
|
+
})
|
|
355
|
+
})
|
|
356
|
+
</script>
|
|
357
|
+
</body>
|
|
358
|
+
</html>
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## 核心概念:模板模式 vs 报告模式
|
|
364
|
+
|
|
365
|
+
设计器支持两种工作模式,通过 `mode` 属性/方法进行切换。
|
|
366
|
+
|
|
367
|
+
### 概念说明
|
|
368
|
+
|
|
369
|
+
| 概念 | 说明 |
|
|
370
|
+
|------|------|
|
|
371
|
+
| **模板 (Template)** | 页面布局设计,包含元素位置、样式、画布大小等 |
|
|
372
|
+
| **数据 (Data)** | 业务数据,如订单信息、报告内容、用户信息等 |
|
|
373
|
+
| **模板 ID** | 模板的唯一标识,用于关联和加载模板 |
|
|
374
|
+
| **数据 ID** | 业务数据的唯一标识,如订单 ID、报告 ID |
|
|
375
|
+
|
|
376
|
+
### 模板模式(`template`)— 默认
|
|
377
|
+
|
|
378
|
+
**设计理念:** 模板与数据分离,一个模板可绑定多个不同的数据。
|
|
379
|
+
|
|
380
|
+
**适用场景:**
|
|
381
|
+
- 订单打印(同一订单模板,打印不同订单)
|
|
382
|
+
- 标签打印(同一标签模板,打印不同商品)
|
|
383
|
+
- 快递单据(同一单据模板,打印不同收件人)
|
|
384
|
+
|
|
385
|
+
**保存行为:** 保存时**只保存页面布局**(元素位置、样式、画布大小等),**不保存** `testData`。
|
|
386
|
+
|
|
387
|
+
### 报告模式(`report`)
|
|
388
|
+
|
|
389
|
+
**设计理念:** 数据与布局绑定,保存的是完整的报告快照。
|
|
390
|
+
|
|
391
|
+
**适用场景:**
|
|
392
|
+
- 检测报告(每次检测结果 + 固定格式的报告模板)
|
|
393
|
+
- 数据报表(当前统计数据的可视化呈现)
|
|
394
|
+
- 档案文件(包含具体数据的完整文档)
|
|
395
|
+
|
|
396
|
+
**保存行为:** 保存时**同时保存页面布局 + testData**。
|
|
397
|
+
|
|
398
|
+
### 两种模式对比
|
|
399
|
+
|
|
400
|
+
| 特性 | `template` 模板模式 | `report` 报告模式 |
|
|
401
|
+
|------|-------------------|------------------|
|
|
402
|
+
| 保存布局 | ✅ | ✅ |
|
|
403
|
+
| 保存 testData | ❌ | ✅ |
|
|
404
|
+
| 数据与模板关系 | 分离,运行时注入 | 绑定,一起保存 |
|
|
405
|
+
| 模板复用性 | 高,可用于多个数据 | 低,针对当前数据 |
|
|
406
|
+
| 典型用途 | 订单打印、标签 | 检测报告、数据报表 |
|
|
407
|
+
|
|
408
|
+
### 使用示例
|
|
409
|
+
|
|
410
|
+
**模板模式(推荐用于可复用模板):**
|
|
411
|
+
|
|
412
|
+
```javascript
|
|
413
|
+
// 1. 编辑模板时传入预览数据
|
|
414
|
+
const sampleData = {
|
|
415
|
+
id: null, // 无需数据 ID,仅用于预览
|
|
416
|
+
orderNo: 'PREVIEW-001',
|
|
417
|
+
customerName: '预览客户'
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// 2. 保存模板(只保存布局,不含 testData)
|
|
421
|
+
// payload.data 中不包含 testData
|
|
422
|
+
|
|
423
|
+
// 3. 打印时动态注入真实数据
|
|
424
|
+
designer.loadTemplate('tpl-order')
|
|
425
|
+
designer.setVariables({
|
|
426
|
+
orderNo: 'ORD-2024-001',
|
|
427
|
+
customerName: '张三'
|
|
428
|
+
})
|
|
429
|
+
designer.print({ mode: 'browser', options: { silent: true } })
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**报告模式(推荐用于完整报告):**
|
|
433
|
+
|
|
434
|
+
```javascript
|
|
435
|
+
// 1. 编辑报告时传入完整数据
|
|
436
|
+
const sampleData = {
|
|
437
|
+
id: 8, // 报告 ID
|
|
438
|
+
title: '2024年第一季度销售报告',
|
|
439
|
+
totalAmount: 50000,
|
|
440
|
+
items: [...]
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// 2. 保存报告(布局 + 数据一起保存)
|
|
444
|
+
// payload.data 中包含完整的 testData
|
|
445
|
+
|
|
446
|
+
// 3. 加载后直接打印,无需再次注入数据
|
|
447
|
+
designer.loadTemplate(8)
|
|
448
|
+
designer.print({ mode: 'browser', options: { silent: true } })
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### 切换模式
|
|
452
|
+
|
|
453
|
+
```vue
|
|
454
|
+
<!-- Vue 组件(XPrintDesigner) -->
|
|
455
|
+
<XPrintDesigner mode="template" /> <!-- 模板模式(默认) -->
|
|
456
|
+
<XPrintDesigner mode="report" /> <!-- 报告模式 -->
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
```javascript
|
|
460
|
+
// Web Component
|
|
461
|
+
designer.setMode('template')
|
|
462
|
+
designer.setMode('report')
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## Vue 组件使用说明
|
|
468
|
+
|
|
469
|
+
### 组件 Props
|
|
470
|
+
|
|
471
|
+
`XPrintDesigner` 是 Web Component 的 Vue 适配层(从 `x-print-designer` 导出)。除下表 props 外,其余配置(数据、CRUD、品牌、主题等)统一通过 `config` 传入。
|
|
472
|
+
|
|
473
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
474
|
+
|------|------|--------|------|
|
|
475
|
+
| `config` | `DesignerInitConfig \| null` | `null` | 统一初始化配置(等价 `el.init(config)`),见[init 完整配置](#initconfig-完整配置字段) |
|
|
476
|
+
| `mode` | `'template'` \| `'report'` | — | 工作模式,影响保存行为 |
|
|
477
|
+
| `uiMode` | `'beginner'` \| `'expert'` | — | 界面模式(简洁/完整) |
|
|
478
|
+
| `viewMode` | `'edit'` \| `'preview'` | — | 视图模式,`'preview'` 时只渲染打印内容 |
|
|
479
|
+
| `headless` | `boolean` | `false` | 无头模式,隐藏顶部工具栏和侧边栏 |
|
|
480
|
+
| `onSave` | `(payload) => void \| Promise<void>` | — | 保存回调,详见[保存回调](#保存回调-onsave)(映射 `setOnSave`) |
|
|
481
|
+
| `theme` | `string` | — | 主题(映射 `setTheme`) |
|
|
482
|
+
|
|
483
|
+
### Props 详细说明
|
|
484
|
+
|
|
485
|
+
#### `config`
|
|
486
|
+
|
|
487
|
+
**宿主接入的首选入口**:所有配置(数据、保存、CRUD、品牌、主题等)一次传入,等价于调用 `el.init(config)`。`ready` 前传入自动排队,`ready` 后自动应用。
|
|
488
|
+
|
|
489
|
+
```vue
|
|
490
|
+
<XPrintDesigner :config="{
|
|
491
|
+
mode: 'report',
|
|
492
|
+
testData: { id: 1, title: '检测报告', items: [{ name: '产品A', price: 100 }] },
|
|
493
|
+
onSave: async (payload) => { await fetch('/api/reports/save', { method: 'POST', body: JSON.stringify(payload) }) },
|
|
494
|
+
autoSave: { enabled: true, intervalMs: 120000 },
|
|
495
|
+
crud: { mode: 'remote', endpoints: { /* ... */ } }
|
|
496
|
+
}" />
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
#### `mode`
|
|
500
|
+
|
|
501
|
+
工作模式,决定保存时是否附带 `testData`。
|
|
502
|
+
|
|
503
|
+
```vue
|
|
504
|
+
<XPrintDesigner mode="report" />
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
#### `viewMode`
|
|
508
|
+
|
|
509
|
+
切换编辑/预览视图。设为 `'preview'` 时隐藏所有编辑器 UI,仅渲染打印页面。适用于嵌入第三方页面时只展示打印效果。
|
|
510
|
+
|
|
511
|
+
```vue
|
|
512
|
+
<XPrintDesigner view-mode="preview" />
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
#### `headless`
|
|
516
|
+
|
|
517
|
+
无头模式,隐藏顶部工具栏和侧边属性面板。用于嵌入式场景仅保留画布区域。
|
|
518
|
+
|
|
519
|
+
```vue
|
|
520
|
+
<XPrintDesigner headless />
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
#### `onSave`
|
|
524
|
+
|
|
525
|
+
保存回调函数。传入后设计器**不再执行内置保存逻辑**,完全由用户自行处理持久化。也可通过 `config.onSave` 传入,效果一致。
|
|
526
|
+
|
|
527
|
+
```vue
|
|
528
|
+
<XPrintDesigner :on-save="(payload) => mySaveApi(payload)" />
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
> 不传 `onSave` 时,设计器使用内置 CRUD 逻辑(根据配置调远程接口或存 localStorage)。
|
|
532
|
+
|
|
533
|
+
#### 预览数据与自动保存
|
|
534
|
+
|
|
535
|
+
- 预览数据:通过 `config.testData`(等价 Web Component 的 `setTestData`,详见 [传入数据格式](#传入数据格式))。
|
|
536
|
+
- 自动保存:通过 `config.autoSave = { enabled, intervalMs? }` 开启(详见 [自动保存配置](#自动保存配置))。
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
## Web Component 使用说明
|
|
541
|
+
|
|
542
|
+
### 加载方式
|
|
543
|
+
|
|
544
|
+
```html
|
|
545
|
+
<!-- ES Module 方式(推荐):样式已全部内联进 JS,宿主无需 Tailwind / 手动引入 CSS -->
|
|
546
|
+
<script type="module" src="/dist/print-designer.es.js"></script>
|
|
547
|
+
|
|
548
|
+
<!-- 或 UMD 方式 -->
|
|
549
|
+
<script src="/dist/print-designer.umd.js"></script>
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
```html
|
|
553
|
+
<print-designer id="designer"></print-designer>
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### 完整 API 参考
|
|
557
|
+
|
|
558
|
+
所有方法均通过 DOM 元素直接调用。**必须在 `ready` 事件触发后调用。**
|
|
559
|
+
|
|
560
|
+
| 方法 | 参数 | 返回值 | 说明 |
|
|
561
|
+
|------|------|--------|------|
|
|
562
|
+
| `init(config)` | `DesignerInitConfig` | `void` | 统一初始化入口(见[init 完整配置](#initconfig-完整配置字段))。ready 前调用自动排队,ready 后自动应用 |
|
|
563
|
+
| `setMode(mode)` | `'template'` \| `'report'` | `void` | 设置工作模式 |
|
|
564
|
+
| `setViewMode(viewMode)` | `'edit'` \| `'preview'` | `void` | 切换视图模式 |
|
|
565
|
+
| `setUIMode(mode)` | `'beginner'` \| `'expert'` | `void` | 切换界面模式(简洁/完整) |
|
|
566
|
+
| `setOnSave(handler)` | `(payload) => void \| Promise<void>` | `void` | 设置保存回调 |
|
|
567
|
+
| `setAutoSave(enabled, intervalMs?)` | `boolean, number?` | `void` | 开启/关闭自动保存,`intervalMs` 默认 120000 |
|
|
568
|
+
| `setTestData(data)` | `Record<string, any>` | `Promise<void>` | 设置测试/预览数据(等价 Vue 组件通过 `config.testData` 传入) |
|
|
569
|
+
| `setSampleData(data)` | `Record<string, any>` | `Promise<void>` | `setTestData` 的别名 |
|
|
570
|
+
| `setVariables(vars)` | `Record<string, any>` | `Promise<void>` | 打印/导出时注入真实变量(覆盖 testData 中的同名字段) |
|
|
571
|
+
| `setTemplateVariables(vars)` | `Record<string, any>` | `Promise<void>` | 设置模板变量(按 `@键` 规范化后写入 variables) |
|
|
572
|
+
| `setData(data, opts?)` | `Record<string, any>, DesignerDataOptions?` | `Promise<void>` | **统一数据注入入口**。`opts.channel` 可选 `variables`(默认)/`testData`/`sampleData`/`availableVariables`,读取优先级 `runtimeVariables > testData > sampleData`,见[数据通道与优先级](#数据通道与优先级) |
|
|
573
|
+
| `getTestData()` | — | `Record<string, any>` | 获取当前测试数据 |
|
|
574
|
+
| `getVariables()` | — | `Record<string, any>` | 获取当前运行时变量 |
|
|
575
|
+
| `getTemplateVariables()` | — | `Record<string, any>` | 获取当前模板变量 |
|
|
576
|
+
| `setFieldDictionary(dict)` | `Record<string, FieldItem>` | `void` | 设置字段字典(变量面板中显示中文标签) |
|
|
577
|
+
| `setAvailableVariables(vars)` | `VariableTreeItem[]` | `void` | 设置可用变量树(字段选择提示) |
|
|
578
|
+
| `setDataLoading(loading)` | `boolean` | `void` | 设置外部数据加载状态(展示 loading 遮罩) |
|
|
579
|
+
| `setTextPolishHandler(handler)` | `(text) => Promise<string>` | `void` | 注入文本润色处理器(宿主侧 AI 能力,优先于内置 DeepSeek) |
|
|
580
|
+
| `setTextPolishConfig(config)` | `TextPolishConfig` | `void` | 配置内置 DeepSeek 润色(`{ apiKey, baseURL?, model? }`) |
|
|
581
|
+
| `setTemplates(list, opts?)` | `Array<Template>, object?` | `Promise<void>` | 设置预设模板列表并加载当前模板。`opts` 可选:`{ currentTemplateId?: string }`。返回的 Promise 在画布 pages 填充完成后 resolve,可据此消除时序竞态 |
|
|
582
|
+
| `loadTemplate(id)` | `string` | `Promise<boolean>` | 加载指定模板到画布。resolve 时 pages 已填充;返回 `false` 表示因前置条件(如正在编辑自定义元素)未触发 |
|
|
583
|
+
| `loadExampleTemplate(id)` | `string` | `Promise<boolean>` | 加载内置示例模板(复制为可编辑模板并加载;同名模板存在时直接加载) |
|
|
584
|
+
| `setTemplateLibrary(list, opts?)` | `DesignerTemplateLibraryItem[], { mode?: 'replace' \| 'merge' }?` | `void` | 替换或合并示例模板库(场景化模板注入)。`mode: 'replace'`(默认)整体替换内置库;`'merge'` 同 id 覆盖、新 id 追加。影响空态与模板下拉的示例模板区。`category` 可复用内置场景 key(`device-run`/`inspection`/`event-feedback`/`other`)或自定义任意值 |
|
|
585
|
+
| `loadTemplateData(data)` | `Record<string, any>` | `boolean` | 直接加载模板 JSON 数据到画布,返回是否成功 |
|
|
586
|
+
| `getTemplateData()` | — | `Record<string, any>` | 获取当前模板完整数据 |
|
|
587
|
+
| `getPreviewHtml()` | — | `Promise<string>` | 获取预览 HTML 字符串 |
|
|
588
|
+
| `print(options?)` | `PrintOptions` | `Promise<void>` | 执行打印,详见[打印与导出](#打印与导出) |
|
|
589
|
+
| `export(options?)` | `ExportOptions` | `Promise<void>` | 导出 PDF / 图片 / HTML,详见[打印与导出](#打印与导出) |
|
|
590
|
+
| `setPreviewMode(options)` | `PreviewModeOptions` | `void` | 进入独立预览面板模式。传入的 `pages` 若含循环页(`loopDataVariable`),预览时会自动按数据展开成 N 页 |
|
|
591
|
+
| `exitPreviewMode()` | — | `void` | 退出预览模式,返回编辑器 |
|
|
592
|
+
| `setBranding(config)` | `{ title?, logoUrl?, showTitle?, showLogo? }` | `void` | 设置品牌标识 |
|
|
593
|
+
| `setLanguage(lang)` | `'zh'` \| `'en'` | `void` | 切换语言 |
|
|
594
|
+
| `setCrudMode(mode, opts?)` | `'local'` \| `'remote'`, `{ skipAutoLoad?: boolean }` | `void` | 切换 CRUD 模式。`skipAutoLoad: true` 跳过 remote 模式下自动 `loadTemplates + loadTemplate` 的链式加载,适用于已通过 `setTemplates` 提供数据的场景,避免双次加载闪烁 |
|
|
595
|
+
| `setCrudEndpoints(endpoints, opts?)` | `CrudEndpoints, { baseUrl?, headers?, fetcher? }` | `void` | 配置远程 CRUD 端点(templates/customElements 的 list/get/upsert/delete),详见[远程 CRUD 配置](#远程-crud-配置) |
|
|
596
|
+
| `setAutoCollapseLoopPages(enabled)` | `boolean` | `void` | 切换保存时自动折叠循环页(默认开启)。关闭后需自行调用 `collapseLoopPages` |
|
|
597
|
+
| `expandLoopPages(pages, ctx?)` | `Page[], LoopExpandContext?` | `Page[]` | 循环页展开:按 `page.loopDataVariable` + 数据数组,把 1 个模板页展开成 N 页。不传 `ctx` 时默认使用当前 store 数据(导出语义) |
|
|
598
|
+
| `hasLoopPage(pages)` | `Page[]?` | `boolean` | 判断页面数组中是否含循环页 |
|
|
599
|
+
| `collapseLoopPages(currentPages, originalPages)` | `Page[], Page[]` | `Page[]` | 折叠已展开的循环页,还原为带 `loopDataVariable` 的单个模板页。保存流程已自动折叠,通常无需手动调用 |
|
|
600
|
+
|
|
601
|
+
### 事件参考
|
|
602
|
+
|
|
603
|
+
Web Component 元素会派发以下自定义事件,可通过 `addEventListener` 监听:
|
|
604
|
+
|
|
605
|
+
| 事件名 | 触发时机 | `detail` 结构 |
|
|
606
|
+
|--------|---------|--------------|
|
|
607
|
+
| `ready` | 组件挂载完成、API 可调用时(下一个 tick 派发) | 无 |
|
|
608
|
+
| `templateloaded` | 模板加载完成、画布 pages 已填充时 | `{ id: string, mode: 'template' \| 'report' }` |
|
|
609
|
+
| `print` | 开始执行打印前 | `{ request: DesignerPrintRequest }` |
|
|
610
|
+
| `printed` | 打印成功完成 | `{ request, result }` |
|
|
611
|
+
| `export` | 开始导出前 | `{ request: DesignerExportRequest }` |
|
|
612
|
+
| `exported` | 导出成功完成(pdf/html/images/pdfBlob/imageBlob) | `{ request }` 或 `{ request, blob }`(Blob 类型) |
|
|
613
|
+
| `error` | 打印/导出/内部渲染出错 | `{ scope: 'print' \| 'export' \| 'getPreviewHtml' \| 'render', error, info? }` |
|
|
614
|
+
|
|
615
|
+
> **时序提示**:若需要在模板加载完成后执行操作(如注入运行时数据),监听 `templateloaded` 事件,或 `await` `setTemplates` / `loadTemplate` 返回的 Promise,二者等价且都比轮询 `store.pages` 更可靠。
|
|
616
|
+
|
|
617
|
+
### 基本使用示例
|
|
618
|
+
|
|
619
|
+
```javascript
|
|
620
|
+
const designer = document.getElementById('designer')
|
|
621
|
+
|
|
622
|
+
designer.addEventListener('ready', () => {
|
|
623
|
+
// 设置模式
|
|
624
|
+
designer.setMode('template')
|
|
625
|
+
|
|
626
|
+
// 设置数据
|
|
627
|
+
designer.setTestData({
|
|
628
|
+
id: 1,
|
|
629
|
+
orderNo: 'ORD-001',
|
|
630
|
+
customerName: '张三',
|
|
631
|
+
items: [
|
|
632
|
+
{ name: '产品A', price: 100, quantity: 2 },
|
|
633
|
+
{ name: '产品B', price: 200, quantity: 3 }
|
|
634
|
+
]
|
|
635
|
+
})
|
|
636
|
+
|
|
637
|
+
// 保存回调
|
|
638
|
+
designer.setOnSave(async (payload) => {
|
|
639
|
+
console.log('保存:', payload)
|
|
640
|
+
await fetch('/api/templates/save', {
|
|
641
|
+
method: 'POST',
|
|
642
|
+
headers: { 'Content-Type': 'application/json' },
|
|
643
|
+
body: JSON.stringify(payload)
|
|
644
|
+
})
|
|
645
|
+
})
|
|
646
|
+
|
|
647
|
+
// 开启自动保存(每 2 分钟)
|
|
648
|
+
designer.setAutoSave(true, 120000)
|
|
649
|
+
})
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
### 打印时注入真实数据
|
|
653
|
+
|
|
654
|
+
使用 `setVariables` 在打印/导出时覆盖预览数据中的字段:
|
|
655
|
+
|
|
656
|
+
```javascript
|
|
657
|
+
const designer = document.querySelector('print-designer')
|
|
658
|
+
|
|
659
|
+
// 加载模板
|
|
660
|
+
await designer.loadTemplate('tpl-order')
|
|
661
|
+
|
|
662
|
+
// 注入真实打印数据
|
|
663
|
+
designer.setVariables({
|
|
664
|
+
orderNo: 'REAL-2026-001',
|
|
665
|
+
customerName: '李四',
|
|
666
|
+
totalAmount: 5999.00
|
|
667
|
+
})
|
|
668
|
+
|
|
669
|
+
// 静默打印
|
|
670
|
+
await designer.print({ mode: 'browser', options: { silent: true } })
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
### 独立预览面板
|
|
674
|
+
|
|
675
|
+
当设计器嵌入第三方页面时,可切换到独立的**纯预览模式**——只渲染打印内容,不显示编辑器 UI。
|
|
676
|
+
|
|
677
|
+
**适用于:** 插件/第三方页面嵌入时的打印预览、用户填写表单后的预览、无需编辑功能的只读场景。
|
|
678
|
+
|
|
679
|
+
#### `setPreviewMode(options)`
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
interface PreviewModeOptions {
|
|
683
|
+
pages: Page[] // 页面元素布局(必填)
|
|
684
|
+
canvasSize: { width: number; height: number } // 画布尺寸(必填)
|
|
685
|
+
canvasBackground?: string // 画布背景色,默认 '#ffffff'
|
|
686
|
+
testData?: Record<string, any> // 测试数据
|
|
687
|
+
variables?: Record<string, any> // 额外变量映射
|
|
688
|
+
watermark?: WatermarkSettings // 水印配置
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
#### 使用示例
|
|
693
|
+
|
|
694
|
+
```javascript
|
|
695
|
+
async function openPreview(reportId) {
|
|
696
|
+
const designer = document.querySelector('print-designer')
|
|
697
|
+
|
|
698
|
+
// 从后端获取模板数据
|
|
699
|
+
const template = await fetch(`/api/templates/${reportId}`).then(r => r.json())
|
|
700
|
+
|
|
701
|
+
// 进入预览模式
|
|
702
|
+
designer.setPreviewMode({
|
|
703
|
+
pages: template.data.pages,
|
|
704
|
+
canvasSize: template.data.canvasSize,
|
|
705
|
+
canvasBackground: template.data.canvasBackground || '#ffffff',
|
|
706
|
+
testData: {
|
|
707
|
+
customerName: document.getElementById('name').value,
|
|
708
|
+
orderDate: document.getElementById('date').value,
|
|
709
|
+
items: getOrderItems()
|
|
710
|
+
},
|
|
711
|
+
watermark: template.data.watermark
|
|
712
|
+
})
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// 返回编辑
|
|
716
|
+
function backToEdit() {
|
|
717
|
+
designer.exitPreviewMode()
|
|
718
|
+
}
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
> `exitPreviewMode()` 会自动恢复进入预览前的编辑器状态(模板选择、编辑进度等)。
|
|
722
|
+
|
|
723
|
+
---
|
|
724
|
+
|
|
725
|
+
## 场景化模板库
|
|
726
|
+
|
|
727
|
+
为降低业务人员上手门槛,模板下拉与空态页按**业务场景**分组展示内置示例模板,并支持搜索过滤。内置场景围绕无人机业务:
|
|
728
|
+
|
|
729
|
+
| 场景 key | 场景名称 | 内置模板 |
|
|
730
|
+
|----------|---------|---------|
|
|
731
|
+
| `device-run` | 设备运行 | 设备档案卡、飞行任务记录、设备状态、电池电量、航程统计 |
|
|
732
|
+
| `inspection` | 巡检记录 | 巡检报告、巡检任务明细、巡检缺陷记录、飞行日志 |
|
|
733
|
+
| `event-feedback` | 事件反馈 | 告警汇总单、事件上报单、异常反馈单、处置结果单 |
|
|
734
|
+
| `other` | 其他 | 通用示例(送货单等) |
|
|
735
|
+
|
|
736
|
+
### 宿主注入模板库 `setTemplateLibrary`
|
|
737
|
+
|
|
738
|
+
宿主可将自定义模板注入示例库:`mode: 'replace'`(默认)整体替换内置库;`mode: 'merge'` 同 `id` 覆盖、新 `id` 追加。注入后模板下拉与空态**响应式刷新**。
|
|
739
|
+
|
|
740
|
+
```ts
|
|
741
|
+
el.setTemplateLibrary([
|
|
742
|
+
{
|
|
743
|
+
id: 'biz-device-card',
|
|
744
|
+
name: '设备档案卡(自定义)',
|
|
745
|
+
description: '用于设备入库归档打印',
|
|
746
|
+
category: 'device-run', // 复用内置场景 key,或传自定义字符串(如 'biz')
|
|
747
|
+
data: { pages: [/* ... */], canvasSize: { width: 794, height: 1123 } }
|
|
748
|
+
}
|
|
749
|
+
], { mode: 'merge' })
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
> 模板数据 `data` 需包含 `pages`(非空元素数组)与 `canvasSize`;缺 `id`/`name` 或结构不合法的项会被自动丢弃。
|
|
753
|
+
|
|
754
|
+
### 加载内置示例 `loadExampleTemplate`
|
|
755
|
+
|
|
756
|
+
```ts
|
|
757
|
+
// 按 id 加载内置示例模板:复制为可编辑模板并填充画布(同名模板已存在时直接加载)
|
|
758
|
+
await el.loadExampleTemplate('example-device-status')
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
---
|
|
762
|
+
|
|
763
|
+
## 数据驱动模板向导
|
|
764
|
+
|
|
765
|
+
不熟悉画布设计的用户可直接从样例数据生成模板骨架:在模板下拉中选择 **"从数据生成模板"**,粘贴样例 JSON(或使用内置示例数据),设计器会:
|
|
766
|
+
|
|
767
|
+
1. **推断字段结构**:识别标量字段(文本/数字/日期)与数组字段(对象数组 → 表格)。
|
|
768
|
+
2. **生成基础骨架**:自动创建"标题 + 每字段一行 label + 数据表格"布局,表格自动绑定数组字段并渲染行。
|
|
769
|
+
3. **加载进画布微调**:骨架加载后变量可正常解析,用户只需调整样式与排版。
|
|
770
|
+
|
|
771
|
+
> 定位为**骨架生成**:不做像素级智能排版;生成的布局可在画布中自由微调,也可通过 `setTemplateLibrary` 沉淀为团队模板。
|
|
772
|
+
|
|
773
|
+
---
|
|
774
|
+
|
|
775
|
+
## 组合与取消组合
|
|
776
|
+
|
|
777
|
+
画布支持将多个元素**组合为一个整体**操作(移动、缩放、复制、删除):
|
|
778
|
+
|
|
779
|
+
- **组合**:按住 `Ctrl`(Mac 为 `Cmd`)多选元素后,点击顶部工具栏 **"组合"** 按钮(或快捷键)。组合后属性面板显示"组合包含 N 个元素"。
|
|
780
|
+
- **整体操作**:组合作为一个整体参与拖拽移动、八方向缩放(子元素等比联动)、复制粘贴(粘贴后子元素 id 自动再生,不会冲突)。
|
|
781
|
+
- **取消组合**:选中组合后,属性面板点击 **"取消组合"**,成员恢复为独立元素(还原绝对坐标)。
|
|
782
|
+
- **限制**:组合至少需要 2 个元素;已锁定元素不能参与组合。
|
|
783
|
+
|
|
784
|
+
---
|
|
785
|
+
|
|
786
|
+
## 数据通道与优先级
|
|
787
|
+
|
|
788
|
+
设计器存在多条数据注入通道,为避免使用混淆,**读取时统一按以下优先级解析**(同 key 冲突时,优先级高的通道胜出):
|
|
789
|
+
|
|
790
|
+
```
|
|
791
|
+
runtimeVariables(运行时变量,setVariables / setData variables)
|
|
792
|
+
> testData(模板测试数据,setTestData / setSampleData / setData testData)
|
|
793
|
+
> sampleData(宿主静态示例数据,仅兜底取值)
|
|
794
|
+
> ext.availableVariables(模板字段树声明,仅用于字段提示,不参与取值)
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
- **建议**:新接入的宿主统一使用 [`setData(data, options)`](#完整-api-参考) 一个入口,用 `options.channel` 选择写入通道;旧 4 个方法(`setTestData` / `setSampleData` / `setVariables` / `setTemplateVariables`)保留并作为 `setData` 的薄包装,行为不变。
|
|
798
|
+
- **原则**:只统一"读取时"的 fallback 链,不合并存储,编辑态 / 打印态差异保持不变。
|
|
799
|
+
|
|
800
|
+
```js
|
|
801
|
+
// 打印/导出时覆盖预览数据(运行时变量优先级最高)
|
|
802
|
+
designer.setData(
|
|
803
|
+
{ items: [{ name: '巡检无人机', qty: 2, total: 12800 }] },
|
|
804
|
+
{ channel: 'variables', merge: true }
|
|
805
|
+
);
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
## 远程 CRUD 配置
|
|
811
|
+
|
|
812
|
+
不传 `onSave` 时,设计器走内置 CRUD:`local` 模式存 localStorage(默认),`remote` 模式调后端接口。宿主接入云存储需两步:
|
|
813
|
+
|
|
814
|
+
### 1. 配置端点 `setCrudEndpoints`
|
|
815
|
+
|
|
816
|
+
端点支持字符串(默认方法:list 用 GET、upsert 用 POST)或对象(自定义 `url` + `method`)。URL 支持相对路径(自动拼接 `baseUrl`)或**绝对路径**(忽略 `baseUrl`,便于对接第三方/微服务):
|
|
817
|
+
|
|
818
|
+
```ts
|
|
819
|
+
el.setCrudEndpoints({
|
|
820
|
+
baseUrl: 'https://api.example.com', // 也可在 options 中传
|
|
821
|
+
templates: {
|
|
822
|
+
list: '/print/templates', // GET https://api.example.com/print/templates
|
|
823
|
+
get: '/print/templates/{id}', // {id} 占位符会被真实 id 替换
|
|
824
|
+
upsert: '/print/templates', // POST(body 为模板对象)
|
|
825
|
+
delete: '/print/templates/{id}'
|
|
826
|
+
},
|
|
827
|
+
customElements: {
|
|
828
|
+
list: '/print/custom-elements',
|
|
829
|
+
get: '/print/custom-elements/{id}',
|
|
830
|
+
upsert: '/print/custom-elements',
|
|
831
|
+
delete: '/print/custom-elements/{id}'
|
|
832
|
+
}
|
|
833
|
+
}, {
|
|
834
|
+
baseUrl: 'https://your-domain.com',
|
|
835
|
+
headers: { Authorization: 'Bearer xxx', 'X-Tenant-ID': '123' }
|
|
836
|
+
})
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
### 2. 切换远程模式 `setCrudMode`
|
|
840
|
+
|
|
841
|
+
```ts
|
|
842
|
+
el.setCrudMode('remote') // 保存/删除/加载走远程 API;local 为默认(localStorage)
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
> 若已通过 `setTemplates` 自行提供数据,可传 `setCrudMode('remote', { skipAutoLoad: true })` 跳过 remote 模式下的自动链式加载,避免双次加载闪烁。
|
|
846
|
+
|
|
847
|
+
### 后端接口约定(4 个端点 / 资源)
|
|
848
|
+
|
|
849
|
+
| 端点 | 请求 | 响应要求 |
|
|
850
|
+
|------|------|---------|
|
|
851
|
+
| `list` | GET | 返回**模板/自定义元素数组**;渲染按返回顺序 |
|
|
852
|
+
| `get` | GET `{id}` | 返回详情:模板含 `data` 与 `ext.availableVariables`;自定义元素含 `element` |
|
|
853
|
+
| `upsert` | POST | 创建/更新;**必须返回真实 `id`** |
|
|
854
|
+
| `delete` | DELETE `{id}` | 返回 `{ success: boolean }` |
|
|
855
|
+
|
|
856
|
+
**通用约定:**
|
|
857
|
+
|
|
858
|
+
- 每个资源(template / customElement)都有 `id`、`name`、`permissions`(`{ editable, deletable, copyable }`)、`updatedAt`、`ext` 字段。
|
|
859
|
+
- **扩展字段一律放 `ext` 下**(如 `ext.category`、`ext.templateTags`、`ext.availableVariables`),不支持根级扁平自定义字段;前端按 key 合并对象、数组"后者覆盖"。
|
|
860
|
+
- `permissions.editable: false` 时设计器进入只读模式(UI 与 Store/API 双层拦截)。
|
|
861
|
+
|
|
862
|
+
---
|
|
863
|
+
|
|
864
|
+
## 工具函数(v0.4.39+ 新增)
|
|
865
|
+
|
|
866
|
+
为了降低宿主接入门槛,插件提供了开箱即用的工具函数,**将常见配置从数百行简化到几行**。
|
|
867
|
+
|
|
868
|
+
### 1. CRUD 适配器工厂 `createRestfulAdapter`
|
|
869
|
+
|
|
870
|
+
**问题:** 宿主需要写 80+ 行 `fetcher` 逻辑来适配后端 API。
|
|
871
|
+
|
|
872
|
+
**解决:** 使用适配器工厂,**从 80 行减少到 10 行**。
|
|
873
|
+
|
|
874
|
+
```ts
|
|
875
|
+
import { createRestfulAdapter, adapterPresets } from 'x-print-designer'
|
|
876
|
+
|
|
877
|
+
// 方式一:使用预设(Spring Boot / Laravel / Express)
|
|
878
|
+
const adapter = createRestfulAdapter({
|
|
879
|
+
...adapterPresets.springBoot('/api'), // 自动配置端点路径
|
|
880
|
+
transform: {
|
|
881
|
+
request: (data) => ({
|
|
882
|
+
id: data.id,
|
|
883
|
+
name: data.name,
|
|
884
|
+
templateJson: JSON.stringify(data.data)
|
|
885
|
+
}),
|
|
886
|
+
response: (res) => res.records.map(r => ({
|
|
887
|
+
id: r.id,
|
|
888
|
+
name: r.name,
|
|
889
|
+
data: JSON.parse(r.templateJson)
|
|
890
|
+
}))
|
|
891
|
+
},
|
|
892
|
+
onSave: async (data) => {
|
|
893
|
+
await editTemplate(data)
|
|
894
|
+
ElMessage.success('保存成功')
|
|
895
|
+
}
|
|
896
|
+
})
|
|
897
|
+
|
|
898
|
+
// 方式二:自定义端点路径
|
|
899
|
+
const adapter = createRestfulAdapter({
|
|
900
|
+
baseURL: '/backend',
|
|
901
|
+
endpoints: {
|
|
902
|
+
list: '/report/template/list',
|
|
903
|
+
get: '/report/template/detail/:id', // 支持 :id 占位符
|
|
904
|
+
upsert: '/report/template/edit',
|
|
905
|
+
delete: '/report/template/delete/:id'
|
|
906
|
+
},
|
|
907
|
+
transform: { /* ... */ },
|
|
908
|
+
onSave: async (data) => { /* ... */ },
|
|
909
|
+
onError: (error, operation) => {
|
|
910
|
+
console.error(`Operation ${operation} failed:`, error)
|
|
911
|
+
}
|
|
912
|
+
})
|
|
913
|
+
|
|
914
|
+
// 应用到设计器
|
|
915
|
+
designerEl.setCrudEndpoints(adapter.endpoints, { fetcher: adapter.fetcher })
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
**API:**
|
|
919
|
+
|
|
920
|
+
```ts
|
|
921
|
+
interface RestfulAdapterConfig {
|
|
922
|
+
baseURL: string
|
|
923
|
+
endpoints: {
|
|
924
|
+
list: string
|
|
925
|
+
get: string // 支持 :id 占位符
|
|
926
|
+
upsert: string
|
|
927
|
+
delete?: string
|
|
928
|
+
}
|
|
929
|
+
transform?: {
|
|
930
|
+
request?: (data: any) => any // 请求数据转换(保存时)
|
|
931
|
+
response?: (data: any) => any // 响应数据转换(列表/详情)
|
|
932
|
+
}
|
|
933
|
+
onSave?: (data: any) => void | Promise<void>
|
|
934
|
+
onDelete?: (id: string) => void | Promise<void>
|
|
935
|
+
headers?: Record<string, string>
|
|
936
|
+
onError?: (error: Error, operation: 'list' | 'get' | 'upsert' | 'delete') => void
|
|
937
|
+
}
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
### 2. 字段字典工具 `buildFieldDictionary`
|
|
941
|
+
|
|
942
|
+
**问题:** 需要手动构建 40+ 行字典映射逻辑,处理字段冲突、模块前缀等。
|
|
943
|
+
|
|
944
|
+
**解决:** 使用工具函数,**从 40 行减少到 3 行**。
|
|
945
|
+
|
|
946
|
+
```ts
|
|
947
|
+
import { buildFieldDictionary } from 'x-print-designer'
|
|
948
|
+
|
|
949
|
+
// 获取字段列表
|
|
950
|
+
const modules = await getReportTemplateFields(['media', 'device', 'wayline', 'task', 'event'])
|
|
951
|
+
|
|
952
|
+
// 一行构建字典(自动处理字段名冲突)
|
|
953
|
+
const dictionary = buildFieldDictionary(modules, {
|
|
954
|
+
autoResolveConflicts: true, // 自动添加模块前缀解决冲突
|
|
955
|
+
prefixStrategy: 'auto', // 'auto' | 'always' | 'never'
|
|
956
|
+
prefixSeparator: '.', // 分隔符
|
|
957
|
+
filter: (field, module) => field.fieldType !== 'internal' // 可选过滤器
|
|
958
|
+
})
|
|
959
|
+
|
|
960
|
+
// 应用到设计器
|
|
961
|
+
designerEl.setFieldDictionary(dictionary)
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
**示例输入/输出:**
|
|
965
|
+
|
|
966
|
+
```ts
|
|
967
|
+
// 输入
|
|
968
|
+
const modules = [
|
|
969
|
+
{
|
|
970
|
+
moduleKey: 'device',
|
|
971
|
+
moduleName: '设备信息',
|
|
972
|
+
fields: [
|
|
973
|
+
{ fieldKey: 'id', labelCn: '设备ID', primaryKey: true },
|
|
974
|
+
{ fieldKey: 'name', labelCn: '设备名称' }
|
|
975
|
+
]
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
moduleKey: 'task',
|
|
979
|
+
moduleName: '任务信息',
|
|
980
|
+
fields: [
|
|
981
|
+
{ fieldKey: 'id', labelCn: '任务ID', primaryKey: true },
|
|
982
|
+
{ fieldKey: 'name', labelCn: '任务名称' }
|
|
983
|
+
]
|
|
984
|
+
}
|
|
985
|
+
]
|
|
986
|
+
|
|
987
|
+
// 输出(自动解决 id 和 name 字段冲突)
|
|
988
|
+
{
|
|
989
|
+
'device.id': { fieldKey: 'id', labelCn: '设备ID', moduleKey: 'device', ... },
|
|
990
|
+
'device.name': { fieldKey: 'name', labelCn: '设备名称', moduleKey: 'device', ... },
|
|
991
|
+
'task.id': { fieldKey: 'id', labelCn: '任务ID', moduleKey: 'task', ... },
|
|
992
|
+
'task.name': { fieldKey: 'name', labelCn: '任务名称', moduleKey: 'task', ... }
|
|
993
|
+
}
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
**辅助函数:**
|
|
997
|
+
|
|
998
|
+
```ts
|
|
999
|
+
import { mergeFieldDictionaries, getFieldLabel } from 'x-print-designer'
|
|
1000
|
+
|
|
1001
|
+
// 合并多个字典
|
|
1002
|
+
const merged = mergeFieldDictionaries([dict1, dict2, dict3], 'last') // 'first' | 'last'
|
|
1003
|
+
|
|
1004
|
+
// 根据语言获取标签
|
|
1005
|
+
const label = getFieldLabel(dictionary['device.name'], 'zh') // '设备名称'
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
### 3. 自动样式注入
|
|
1009
|
+
|
|
1010
|
+
**问题:** 宿主需要手动列举并注入 287 行 Tailwind/UnoCSS 样式类。
|
|
1011
|
+
|
|
1012
|
+
> 注:Web Component 构建已把全部样式(Tailwind 工具类 + 组件 scoped 样式)内联进 JS 并注入 shadow root,宿主不使用 Tailwind 时编辑面板与弹层同样完整渲染;自动注入仅作为兼容宿主样式的补充。
|
|
1013
|
+
|
|
1014
|
+
**解决:** 插件自动检测并注入,**0 行代码**。
|
|
1015
|
+
|
|
1016
|
+
```vue
|
|
1017
|
+
<template>
|
|
1018
|
+
<!-- ✅ 无需任何样式注入代码,插件自动检测 Tailwind/UnoCSS/Element Plus 并注入 -->
|
|
1019
|
+
<print-designer />
|
|
1020
|
+
</template>
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
**原理:** 插件在 `connectedCallback` 时自动扫描 `document.querySelectorAll('style')`,智能检测主流 CSS 框架并克隆到 Shadow DOM。
|
|
1024
|
+
|
|
1025
|
+
**可选:禁用自动注入**
|
|
1026
|
+
|
|
1027
|
+
```html
|
|
1028
|
+
<print-designer no-auto-style></print-designer>
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
### 4. 配置预设 `presets`
|
|
1032
|
+
|
|
1033
|
+
**问题:** 每次都要从头配置模式、UI、主题等基础设置。
|
|
1034
|
+
|
|
1035
|
+
**解决:** 使用场景预设,**1 行配置搞定**。
|
|
1036
|
+
|
|
1037
|
+
```ts
|
|
1038
|
+
import { presets, mergePreset } from 'x-print-designer'
|
|
1039
|
+
|
|
1040
|
+
// 方式一:直接使用预设
|
|
1041
|
+
const config = {
|
|
1042
|
+
...presets.uavReport, // 无人机报告场景
|
|
1043
|
+
onSave: handleSave
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// 方式二:合并预设和自定义配置
|
|
1047
|
+
const config = mergePreset(presets.expressLabel, {
|
|
1048
|
+
branding: { title: '我的快递单设计器' },
|
|
1049
|
+
autoSave: { enabled: false }
|
|
1050
|
+
})
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
**可用预设:**
|
|
1054
|
+
|
|
1055
|
+
| 预设名 | 场景 | 特点 |
|
|
1056
|
+
|--------|------|------|
|
|
1057
|
+
| `presets.uavReport` | 无人机报告 | report 模式 + beginner UI + 30s 自动保存 |
|
|
1058
|
+
| `presets.expressLabel` | 物流快递单 | template 模式 + expert UI + 60s 自动保存 |
|
|
1059
|
+
| `presets.invoice` | 发票/票据 | template 模式 + expert UI + 60s 自动保存 |
|
|
1060
|
+
| `presets.label` | 标签/吊牌 | template 模式 + beginner UI + 60s 自动保存 |
|
|
1061
|
+
| `presets.certificate` | 证书/证明 | template 模式 + beginner UI + 不自动保存 |
|
|
1062
|
+
| `presets.contract` | 合同/协议 | template 模式 + expert UI + 30s 自动保存 |
|
|
1063
|
+
| `presets.generalReport` | 通用报告 | report 模式 + beginner UI + 30s 自动保存 |
|
|
1064
|
+
|
|
1065
|
+
### 5. Vue Composable `useDesigner`
|
|
1066
|
+
|
|
1067
|
+
**问题:** Vue 宿主仍需手动管理 ref、ready、初始化等。
|
|
1068
|
+
|
|
1069
|
+
**解决:** 使用 composable,**自动处理一切**。
|
|
1070
|
+
|
|
1071
|
+
```vue
|
|
1072
|
+
<template>
|
|
1073
|
+
<print-designer ref="designerRef" />
|
|
1074
|
+
</template>
|
|
1075
|
+
|
|
1076
|
+
<script setup lang="ts">
|
|
1077
|
+
import { useDesigner, createRestfulAdapter } from 'x-print-designer/vue'
|
|
1078
|
+
|
|
1079
|
+
const { designerRef, ready, setData, exportPdf } = useDesigner({
|
|
1080
|
+
mode: 'template',
|
|
1081
|
+
crudAdapter: createRestfulAdapter({
|
|
1082
|
+
baseURL: '/api',
|
|
1083
|
+
endpoints: { list: '/templates', get: '/templates/:id', upsert: '/templates' },
|
|
1084
|
+
onSave: async (data) => { await saveTemplate(data) }
|
|
1085
|
+
}),
|
|
1086
|
+
fieldModules: ['media', 'device'],
|
|
1087
|
+
fieldLoader: async (modules) => {
|
|
1088
|
+
return await getReportTemplateFields(modules)
|
|
1089
|
+
},
|
|
1090
|
+
onError: (error) => {
|
|
1091
|
+
ElMessage.error('设计器错误: ' + error.message)
|
|
1092
|
+
}
|
|
1093
|
+
})
|
|
1094
|
+
|
|
1095
|
+
// ✅ 使用非常简单
|
|
1096
|
+
const handleExport = async () => {
|
|
1097
|
+
await exportPdf('我的模板.pdf')
|
|
1098
|
+
}
|
|
1099
|
+
</script>
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
**API:**
|
|
1103
|
+
|
|
1104
|
+
```ts
|
|
1105
|
+
interface UseDesignerOptions {
|
|
1106
|
+
mode?: 'template' | 'report'
|
|
1107
|
+
uiMode?: 'beginner' | 'expert'
|
|
1108
|
+
crudAdapter?: ReturnType<typeof createRestfulAdapter>
|
|
1109
|
+
fieldModules?: string[] // 自动加载字段字典
|
|
1110
|
+
fieldLoader?: (modules: string[]) => Promise<FieldDictionaryModule[]>
|
|
1111
|
+
onSave?: (data: any) => void | Promise<void>
|
|
1112
|
+
onError?: (error: any) => void
|
|
1113
|
+
autoInit?: boolean // 默认 true
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
interface UseDesignerReturn {
|
|
1117
|
+
designerRef: Ref<any>
|
|
1118
|
+
ready: Ref<boolean>
|
|
1119
|
+
setData: (data: any) => void
|
|
1120
|
+
setTestData: (data: any) => void
|
|
1121
|
+
setVariables: (data: any) => void
|
|
1122
|
+
exportPdf: (filename?: string) => Promise<void>
|
|
1123
|
+
exportImage: (filename?: string) => Promise<void>
|
|
1124
|
+
print: (request?: any) => Promise<void>
|
|
1125
|
+
getDesignerElement: () => any
|
|
1126
|
+
init: () => Promise<void>
|
|
1127
|
+
}
|
|
1128
|
+
```
|
|
1129
|
+
|
|
1130
|
+
### 6. 诊断工具 `diagnoseIntegration`
|
|
1131
|
+
|
|
1132
|
+
**问题:** 集成出问题时不知道哪里配置错了。
|
|
1133
|
+
|
|
1134
|
+
**解决:** 使用诊断工具,**自动检查 12 项配置**。
|
|
1135
|
+
|
|
1136
|
+
```ts
|
|
1137
|
+
import { diagnoseIntegration, exportDiagnosticReport } from 'x-print-designer/diagnostics'
|
|
1138
|
+
|
|
1139
|
+
// 运行诊断
|
|
1140
|
+
const diagnosis = diagnoseIntegration(designerEl)
|
|
1141
|
+
|
|
1142
|
+
if (!diagnosis.allPassed) {
|
|
1143
|
+
console.warn('集成检查未通过:', diagnosis.failedChecks)
|
|
1144
|
+
|
|
1145
|
+
// 导出诊断报告
|
|
1146
|
+
exportDiagnosticReport(diagnosis, 'my-diagnostic.md')
|
|
1147
|
+
}
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
**检查项:**
|
|
1151
|
+
|
|
1152
|
+
| 检查项 | 级别 | 说明 |
|
|
1153
|
+
|--------|------|------|
|
|
1154
|
+
| Element Exists | 🔴 关键 | 元素是否存在 |
|
|
1155
|
+
| Ready State | 🔴 关键 | 是否已就绪 |
|
|
1156
|
+
| Shadow DOM | 🔴 关键 | Shadow DOM 是否初始化 |
|
|
1157
|
+
| Styles Injected | 🟡 警告 | 样式是否注入 |
|
|
1158
|
+
| CRUD Configured | 🟡 警告 | CRUD 是否配置 |
|
|
1159
|
+
| Save Handler | 🟡 警告 | 保存回调是否配置 |
|
|
1160
|
+
| Data API | ℹ️ 信息 | 数据 API 是否可用 |
|
|
1161
|
+
| Print API | ℹ️ 信息 | 打印 API 是否可用 |
|
|
1162
|
+
| Export API | ℹ️ 信息 | 导出 API 是否可用 |
|
|
1163
|
+
| Monaco Loader | ℹ️ 信息 | 代码编辑器是否可用 |
|
|
1164
|
+
| Field Dictionary | ℹ️ 信息 | 字段字典是否配置 |
|
|
1165
|
+
| Auto Style Injection | ℹ️ 信息 | 自动样式注入是否生效 |
|
|
1166
|
+
|
|
1167
|
+
**诊断结果示例:**
|
|
1168
|
+
|
|
1169
|
+
```
|
|
1170
|
+
✅ [PrintDesigner] 集成检查全部通过
|
|
1171
|
+
|
|
1172
|
+
或
|
|
1173
|
+
|
|
1174
|
+
❌ 关键问题: Ready State, Shadow DOM
|
|
1175
|
+
- Ready State: 设计器尚未就绪,请等待 ready 事件
|
|
1176
|
+
- Shadow DOM: Shadow DOM 未初始化
|
|
1177
|
+
|
|
1178
|
+
⚠️ 警告: Save Handler
|
|
1179
|
+
- Save Handler: 未配置 onSave 回调,保存功能将不可用
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
---
|
|
1183
|
+
|
|
1184
|
+
## 传入数据格式
|
|
1185
|
+
|
|
1186
|
+
### 基本要求
|
|
1187
|
+
|
|
1188
|
+
`config.testData` / `setTestData(data)` 接收一个普通的 JSON 对象。**唯一必填字段是 `id`**。
|
|
1189
|
+
|
|
1190
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
1191
|
+
|------|------|------|------|
|
|
1192
|
+
| `id` | `number` \| `string` | ✅ 是 | 业务数据的唯一标识,如报告 ID、订单 ID |
|
|
1193
|
+
| `templateId` | `string` | 否 | 绑定的模板 ID,不传则用当前已选模板 |
|
|
1194
|
+
| `name` | `string` | 否 | 数据名称 |
|
|
1195
|
+
| `variables` | `object` | 否 | 业务变量对象(推荐,用于组织数据) |
|
|
1196
|
+
| 任意自定义字段 | `any` | 否 | 也可直接在根级别存放变量 |
|
|
1197
|
+
|
|
1198
|
+
### 两种组织方式(任选其一,自动兼容)
|
|
1199
|
+
|
|
1200
|
+
#### 方式一:带 `variables` 包装(推荐)
|
|
1201
|
+
|
|
1202
|
+
```json
|
|
1203
|
+
{
|
|
1204
|
+
"id": 8,
|
|
1205
|
+
"name": "测试报告",
|
|
1206
|
+
"templateId": "tpl_report",
|
|
1207
|
+
"variables": {
|
|
1208
|
+
"title": "报告标题",
|
|
1209
|
+
"customerName": "张三",
|
|
1210
|
+
"items": [
|
|
1211
|
+
{ "name": "产品A", "price": 100, "quantity": 2 }
|
|
1212
|
+
],
|
|
1213
|
+
"chartData": [
|
|
1214
|
+
{ "category": "一月", "value": 1200 }
|
|
1215
|
+
]
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
```
|
|
1219
|
+
|
|
1220
|
+
模板绑定:`@title`、`@customerName`、`@items`、`@chartData`
|
|
1221
|
+
|
|
1222
|
+
#### 方式二:扁平数据
|
|
1223
|
+
|
|
1224
|
+
```json
|
|
1225
|
+
{
|
|
1226
|
+
"id": 8,
|
|
1227
|
+
"name": "测试报告",
|
|
1228
|
+
"title": "报告标题",
|
|
1229
|
+
"customerName": "张三",
|
|
1230
|
+
"items": [
|
|
1231
|
+
{ "name": "产品A", "price": 100, "quantity": 2 }
|
|
1232
|
+
],
|
|
1233
|
+
"chartData": [
|
|
1234
|
+
{ "category": "一月", "value": 1200 }
|
|
1235
|
+
]
|
|
1236
|
+
}
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
模板绑定:`@title`、`@customerName`、`@items`、`@chartData`
|
|
1240
|
+
|
|
1241
|
+
> 两种方式等效。设计器内部优先在 `variables` 中查找,未找到则在根级别查找。
|
|
1242
|
+
|
|
1243
|
+
### 支持的数据类型
|
|
1244
|
+
|
|
1245
|
+
| 类型 | 示例 | 模板绑定 |
|
|
1246
|
+
|------|------|---------|
|
|
1247
|
+
| 字符串 | `"张三"` | `@customerName` |
|
|
1248
|
+
| 数字 | `100` | `@totalAmount` |
|
|
1249
|
+
| 布尔值 | `true` | `@isPaid` |
|
|
1250
|
+
| 数组 | `[{...}, {...}]` | `@items` |
|
|
1251
|
+
| 嵌套对象 | `{ user: { name: "李四" } }` | `@user.name` |
|
|
1252
|
+
|
|
1253
|
+
### 表格数据
|
|
1254
|
+
|
|
1255
|
+
表格元素需要三个维度的变量绑定:
|
|
1256
|
+
|
|
1257
|
+
| 绑定属性 | 变量名示例 | 数据格式 | 说明 |
|
|
1258
|
+
|---------|-----------|---------|------|
|
|
1259
|
+
| `variable` | `@items` | `Array<Object>` | 表格行数据 |
|
|
1260
|
+
| `columnsVariable` | `@tableCols` | `Array<{field, header, width}>` | 列定义 |
|
|
1261
|
+
| `footerDataVariable` | `@tableFooter` | `Array<Object>` | 页脚汇总行 |
|
|
1262
|
+
|
|
1263
|
+
```json
|
|
1264
|
+
{
|
|
1265
|
+
"items": [
|
|
1266
|
+
{ "name": "产品A", "price": 100, "quantity": 2 },
|
|
1267
|
+
{ "name": "产品B", "price": 200, "quantity": 3 }
|
|
1268
|
+
],
|
|
1269
|
+
"tableCols": [
|
|
1270
|
+
{ "field": "name", "header": "产品名称", "width": 120 },
|
|
1271
|
+
{ "field": "price", "header": "单价", "width": 80 },
|
|
1272
|
+
{ "field": "quantity", "header": "数量", "width": 80 }
|
|
1273
|
+
],
|
|
1274
|
+
"tableFooter": [
|
|
1275
|
+
{ "name": { "value": "合计" }, "price": { "value": "300" } }
|
|
1276
|
+
]
|
|
1277
|
+
}
|
|
1278
|
+
```
|
|
1279
|
+
|
|
1280
|
+
### 图表数据
|
|
1281
|
+
|
|
1282
|
+
```json
|
|
1283
|
+
{
|
|
1284
|
+
"chartData": [
|
|
1285
|
+
{ "category": "一月", "value": 1200 },
|
|
1286
|
+
{ "category": "二月", "value": 1500 }
|
|
1287
|
+
]
|
|
1288
|
+
}
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
图表元素需在属性面板中配置:
|
|
1292
|
+
- **chartDataVariable**:`@chartData`
|
|
1293
|
+
- **chartXField**:`category`(X 轴字段名)
|
|
1294
|
+
- **chartYField**:`value`(Y 轴字段名)
|
|
1295
|
+
- **chartNameField**:`category`(饼图名称字段)
|
|
1296
|
+
|
|
1297
|
+
### 完整示例数据
|
|
1298
|
+
|
|
1299
|
+
```json
|
|
1300
|
+
{
|
|
1301
|
+
"id": 8,
|
|
1302
|
+
"name": "宝安机场飞行任务记录",
|
|
1303
|
+
"templateId": "tpl_report_drone",
|
|
1304
|
+
"createTime": "2026-05-01",
|
|
1305
|
+
"variables": {
|
|
1306
|
+
"dockname": "宝安国际无人机机场",
|
|
1307
|
+
"dronesn": "6EE5NC104E0PY6",
|
|
1308
|
+
"dronename": "M300 RTK",
|
|
1309
|
+
"reportname": "宝安机场飞行任务记录报告",
|
|
1310
|
+
"tasktype": "航线巡检",
|
|
1311
|
+
"totalcount": 4,
|
|
1312
|
+
"totaldistance": "48.6 km",
|
|
1313
|
+
"conclusion": "本轮飞行任务覆盖宝安区重点航线,数据采集完整。",
|
|
1314
|
+
"flightRecords": [
|
|
1315
|
+
{
|
|
1316
|
+
"time": "2026-04-02 09:30",
|
|
1317
|
+
"taskname": "宝安港区A线巡检",
|
|
1318
|
+
"dockname": "宝安国际无人机机场",
|
|
1319
|
+
"distance": "12.5 km",
|
|
1320
|
+
"image": "https://picsum.photos/id/1/200/80"
|
|
1321
|
+
}
|
|
1322
|
+
],
|
|
1323
|
+
"chartData": [
|
|
1324
|
+
{ "category": "一月", "value": 1200 },
|
|
1325
|
+
{ "category": "二月", "value": 1500 }
|
|
1326
|
+
]
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
```
|
|
1330
|
+
|
|
1331
|
+
### 注意事项
|
|
1332
|
+
|
|
1333
|
+
1. **`id` 字段**
|
|
1334
|
+
- 业务数据的唯一标识,在保存回调中用于识别当前编辑的数据
|
|
1335
|
+
- 不传则保存时 `payload.id` 为 `null`
|
|
1336
|
+
|
|
1337
|
+
2. **`id` 与 `templateId` 的区别**
|
|
1338
|
+
- `id`:业务数据的标识(报告中用作报告 ID)
|
|
1339
|
+
- `templateId`:打印模板的标识,两者是完全不同的概念
|
|
1340
|
+
|
|
1341
|
+
3. **变量命名**
|
|
1342
|
+
- 使用 camelCase 命名,如 `customerName` 而非 `c`
|
|
1343
|
+
- 避免特殊字符和空格
|
|
1344
|
+
- 嵌套路径使用点号:`@user.profile.name`
|
|
1345
|
+
|
|
1346
|
+
4. **数组数据**
|
|
1347
|
+
- 表格和图表需要数组类型
|
|
1348
|
+
- 数组元素应包含一致的字段结构:`items: [{name: "A", price: 100}, {name: "B", price: 200}]`
|
|
1349
|
+
|
|
1350
|
+
5. **动态更新**
|
|
1351
|
+
- Web Component 通过 `setSampleData()` / `setTestData()` 更新
|
|
1352
|
+
- 数据更新后,画布中的变量绑定会自动重新解析
|
|
1353
|
+
|
|
1354
|
+
---
|
|
1355
|
+
|
|
1356
|
+
## 元素数据绑定
|
|
1357
|
+
|
|
1358
|
+
### 变量语法
|
|
1359
|
+
|
|
1360
|
+
| 语法 | 示例 | 说明 |
|
|
1361
|
+
|-----|------|------|
|
|
1362
|
+
| `@变量名` | `@customerName` | 直接引用变量 |
|
|
1363
|
+
| `{#变量路径}` | `{#order.items[0].name}` | 支持嵌套路径和数组索引 |
|
|
1364
|
+
|
|
1365
|
+
### 各元素可绑定字段
|
|
1366
|
+
|
|
1367
|
+
| 元素 | 绑定属性 | 示例 |
|
|
1368
|
+
|------|---------|------|
|
|
1369
|
+
| 文本 / 长文本 | `content` | `订单号: {#orderNo}` |
|
|
1370
|
+
| 图片 | `variable` | `@imageUrl` |
|
|
1371
|
+
| 表格 | `variable` + `columnsVariable` + `footerDataVariable` | `@items`, `@tableCols`, `@tableFooter` |
|
|
1372
|
+
| 自定义表格 | `customTableCells[].field` | `@customerName`(单元格字段) |
|
|
1373
|
+
| 条形码 | `variable` 或 `content` | `@barcode` |
|
|
1374
|
+
| 二维码 | `variable` 或 `content` | `@url` |
|
|
1375
|
+
| 图表 | `chartDataVariable` + `chartXField` + `chartYField` | `@chartData`, `category`, `value` |
|
|
1376
|
+
| 数据卡片 | `variable` | `@totalAmount` |
|
|
1377
|
+
| 进度条 | `variable` | `@progress` |
|
|
1378
|
+
| 日期 | `variable` | `@createTime` |
|
|
1379
|
+
|
|
1380
|
+
---
|
|
1381
|
+
|
|
1382
|
+
## 图片元素使用说明
|
|
1383
|
+
|
|
1384
|
+
### 方式 1:直接输入图片 URL
|
|
1385
|
+
|
|
1386
|
+
在属性面板的"图片地址"输入框填写 URL(支持 HTTP/HTTPS)。
|
|
1387
|
+
|
|
1388
|
+
```
|
|
1389
|
+
https://example.com/logo.png
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
### 方式 2:本地上传(自动转 Base64)
|
|
1393
|
+
|
|
1394
|
+
点击属性面板中的 **"上传图片"** 按钮或**双击画布上的图片元素**,从本地选择图片文件:
|
|
1395
|
+
- 自动转为 Base64 并填入图片地址
|
|
1396
|
+
- 限制最大 **2MB**
|
|
1397
|
+
- 支持 PNG、JPG、GIF、WebP 等格式
|
|
1398
|
+
|
|
1399
|
+
### 方式 3:绑定变量
|
|
1400
|
+
|
|
1401
|
+
将 `variable` 绑定到变量名,图片地址从数据中动态读取。
|
|
1402
|
+
|
|
1403
|
+
| 绑定方式 | 示例 | 数据值要求 |
|
|
1404
|
+
|---------|------|-----------|
|
|
1405
|
+
| 静态变量 | `@logoUrl` | 字符串 URL 或 Base64 |
|
|
1406
|
+
| 数组元素 | `@flightRecords[0].image` | 该字段值为 URL 或 Base64 |
|
|
1407
|
+
|
|
1408
|
+
**对应数据:**
|
|
1409
|
+
|
|
1410
|
+
```json
|
|
1411
|
+
{
|
|
1412
|
+
"variables": {
|
|
1413
|
+
"logoUrl": "https://example.com/logo.png",
|
|
1414
|
+
"avatarBase64": "data:image/png;base64,iVBORw0KGgo...",
|
|
1415
|
+
"flightRecords": [
|
|
1416
|
+
{ "image": "https://picsum.photos/id/1/200/80" }
|
|
1417
|
+
]
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
```
|
|
1421
|
+
|
|
1422
|
+
### 方式对比
|
|
1423
|
+
|
|
1424
|
+
| 方式 | 适用场景 | 存储内容 |
|
|
1425
|
+
|------|---------|---------|
|
|
1426
|
+
| URL 输入 | 固定图片、CDN 资源 | URL 字符串 |
|
|
1427
|
+
| 本地上传 | 无公网链接的图片 | Base64 编码 |
|
|
1428
|
+
| 变量绑定 | 每条数据图片不同 | URL 或 Base64 |
|
|
1429
|
+
|
|
1430
|
+
---
|
|
1431
|
+
|
|
1432
|
+
## 保存回调 `onSave`
|
|
1433
|
+
|
|
1434
|
+
传入 `onSave` 后,用户保存(`Ctrl+S` 或工具栏保存按钮)时设计器不再自动持久化,而是将组装好的数据通过回调交给用户自行处理。
|
|
1435
|
+
|
|
1436
|
+
### 回调参数
|
|
1437
|
+
|
|
1438
|
+
```typescript
|
|
1439
|
+
interface SavePayload {
|
|
1440
|
+
id: string | null // 模板 ID,null 表示新建
|
|
1441
|
+
name: string // 模板名称
|
|
1442
|
+
data: Record<string, any> // 模板数据(含 pages、canvasSize、watermark 等)
|
|
1443
|
+
isNew: boolean // 是否新建模板
|
|
1444
|
+
}
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
### 不同模式下 data 内容差异
|
|
1448
|
+
|
|
1449
|
+
| 字段 | 模板模式 | 报告模式 |
|
|
1450
|
+
|------|---------|---------|
|
|
1451
|
+
| `pages` | ✅ | ✅ |
|
|
1452
|
+
| `canvasSize` | ✅ | ✅ |
|
|
1453
|
+
| `watermark` | ✅ | ✅ |
|
|
1454
|
+
| `guides` | ✅ | ✅ |
|
|
1455
|
+
| `unit` | ✅ | ✅ |
|
|
1456
|
+
| `showGrid` | ✅ | ✅ |
|
|
1457
|
+
| `headerHeight` / `footerHeight` | ✅ | ✅ |
|
|
1458
|
+
| `canvasBackground` | ✅ | ✅ |
|
|
1459
|
+
| `pageSpacingX` / `pageSpacingY` | ✅ | ✅ |
|
|
1460
|
+
| `testData` | ❌ | ✅ |
|
|
1461
|
+
|
|
1462
|
+
### 使用示例
|
|
1463
|
+
|
|
1464
|
+
```vue
|
|
1465
|
+
<template>
|
|
1466
|
+
<XPrintDesigner
|
|
1467
|
+
:mode="mode"
|
|
1468
|
+
:config="{
|
|
1469
|
+
testData: sampleData,
|
|
1470
|
+
onSave: handleSave
|
|
1471
|
+
}"
|
|
1472
|
+
/>
|
|
1473
|
+
</template>
|
|
1474
|
+
|
|
1475
|
+
<script setup lang="ts">
|
|
1476
|
+
import { ref } from 'vue'
|
|
1477
|
+
import { XPrintDesigner } from 'x-print-designer'
|
|
1478
|
+
import 'x-print-designer/style.css'
|
|
1479
|
+
|
|
1480
|
+
const mode = ref<'template' | 'report'>('template')
|
|
1481
|
+
|
|
1482
|
+
const sampleData = ref({
|
|
1483
|
+
id: 1,
|
|
1484
|
+
title: '报告标题',
|
|
1485
|
+
items: [{ name: '产品A', price: 100 }]
|
|
1486
|
+
})
|
|
1487
|
+
|
|
1488
|
+
const handleSave = async (payload) => {
|
|
1489
|
+
// 根据模式决定 API 端点
|
|
1490
|
+
const endpoint = mode.value === 'template'
|
|
1491
|
+
? '/api/templates/save'
|
|
1492
|
+
: '/api/reports/save'
|
|
1493
|
+
|
|
1494
|
+
const res = await fetch(endpoint, {
|
|
1495
|
+
method: 'POST',
|
|
1496
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1497
|
+
body: JSON.stringify(payload)
|
|
1498
|
+
})
|
|
1499
|
+
|
|
1500
|
+
if (!res.ok) throw new Error('保存失败')
|
|
1501
|
+
|
|
1502
|
+
// 新建时获取后端返回的 ID
|
|
1503
|
+
const result = await res.json()
|
|
1504
|
+
if (payload.isNew && result.id) {
|
|
1505
|
+
console.log('新模板 ID:', result.id)
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
</script>
|
|
1509
|
+
```
|
|
1510
|
+
|
|
1511
|
+
> 不传 `onSave` 时,设计器走内置默认保存逻辑(根据 CRUD 配置调远程接口或存 localStorage)。
|
|
1512
|
+
|
|
1513
|
+
---
|
|
1514
|
+
|
|
1515
|
+
## 自动保存配置
|
|
1516
|
+
|
|
1517
|
+
传入 `onSave` 后可同时开启自动保存。右下角会显示"最近保存: HH:MM:SS"。
|
|
1518
|
+
|
|
1519
|
+
### Vue 组件
|
|
1520
|
+
|
|
1521
|
+
自动保存通过 `config.autoSave` 配置(`XPrintDesigner` 无独立 props,统一走 `config`):
|
|
1522
|
+
|
|
1523
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
1524
|
+
|------|------|--------|------|
|
|
1525
|
+
| `autoSave.enabled` | `boolean` | `false` | 是否开启 |
|
|
1526
|
+
| `autoSave.intervalMs` | `number` | `120000` | 间隔(毫秒),默认 2 分钟 |
|
|
1527
|
+
|
|
1528
|
+
```vue
|
|
1529
|
+
<XPrintDesigner :config="{
|
|
1530
|
+
testData: sampleData,
|
|
1531
|
+
onSave: handleSave,
|
|
1532
|
+
autoSave: { enabled: true, intervalMs: 120000 }
|
|
1533
|
+
}" />
|
|
1534
|
+
```
|
|
1535
|
+
|
|
1536
|
+
### Web Component
|
|
1537
|
+
|
|
1538
|
+
```javascript
|
|
1539
|
+
designer.setOnSave(handleSave)
|
|
1540
|
+
designer.setAutoSave(true, 120000) // 每 2 分钟自动保存
|
|
1541
|
+
designer.setAutoSave(false) // 关闭
|
|
1542
|
+
```
|
|
1543
|
+
|
|
1544
|
+
> 自动保存仅在内容有变更时触发,连续未变更的周期内不会重复保存。
|
|
1545
|
+
|
|
1546
|
+
---
|
|
1547
|
+
|
|
1548
|
+
## 打印与导出
|
|
1549
|
+
|
|
1550
|
+
### Web Component API
|
|
1551
|
+
|
|
1552
|
+
#### `print(options)`
|
|
1553
|
+
|
|
1554
|
+
```typescript
|
|
1555
|
+
interface PrintOptions {
|
|
1556
|
+
mode: 'browser' | 'silent'
|
|
1557
|
+
options?: {
|
|
1558
|
+
silent?: boolean // 静默打印(跳过浏览器打印对话框)
|
|
1559
|
+
printerName?: string // 指定打印机名称
|
|
1560
|
+
copies?: number // 打印份数,默认 1
|
|
1561
|
+
duplex?: boolean // 双面打印
|
|
1562
|
+
pageRanges?: string // 页码范围,如 '1-3'
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
```
|
|
1566
|
+
|
|
1567
|
+
```javascript
|
|
1568
|
+
// 浏览器打印
|
|
1569
|
+
await designer.print({ mode: 'browser' })
|
|
1570
|
+
|
|
1571
|
+
// 静默打印到指定打印机
|
|
1572
|
+
await designer.print({
|
|
1573
|
+
mode: 'silent',
|
|
1574
|
+
options: {
|
|
1575
|
+
printerName: 'HP-LaserJet',
|
|
1576
|
+
copies: 2
|
|
1577
|
+
}
|
|
1578
|
+
})
|
|
1579
|
+
```
|
|
1580
|
+
|
|
1581
|
+
#### `export(options)`
|
|
1582
|
+
|
|
1583
|
+
```typescript
|
|
1584
|
+
interface ExportOptions {
|
|
1585
|
+
type: 'pdf' | 'image' | 'html'
|
|
1586
|
+
filename?: string // 输出文件名(不含扩展名)
|
|
1587
|
+
}
|
|
1588
|
+
```
|
|
1589
|
+
|
|
1590
|
+
```javascript
|
|
1591
|
+
// 导出 PDF
|
|
1592
|
+
await designer.export({ type: 'pdf', filename: '订单-2024' })
|
|
1593
|
+
|
|
1594
|
+
// 导出图片
|
|
1595
|
+
await designer.export({ type: 'image', filename: '订单-2024' })
|
|
1596
|
+
|
|
1597
|
+
// 导出 HTML
|
|
1598
|
+
await designer.export({ type: 'html', filename: '订单-2024' })
|
|
1599
|
+
```
|
|
1600
|
+
|
|
1601
|
+
### 快捷键
|
|
1602
|
+
|
|
1603
|
+
| 快捷键 | 功能 |
|
|
1604
|
+
|--------|------|
|
|
1605
|
+
| `Ctrl+S` | 保存 |
|
|
1606
|
+
| `Ctrl+P` | 打印 |
|
|
1607
|
+
| `Ctrl+Shift+P` | 预览 |
|
|
1608
|
+
| `Ctrl+Shift+E` | 导出 PDF |
|
|
1609
|
+
|
|
1610
|
+
---
|
|
1611
|
+
|
|
1612
|
+
## 模板数据结构
|
|
1613
|
+
|
|
1614
|
+
当需要直接保存/加载完整模板时,使用以下数据结构。
|
|
1615
|
+
|
|
1616
|
+
### 完整 JSON 结构
|
|
1617
|
+
|
|
1618
|
+
```json
|
|
1619
|
+
{
|
|
1620
|
+
"id": "tpl_001",
|
|
1621
|
+
"name": "A4 订单模板",
|
|
1622
|
+
"updatedAt": 1715251200000,
|
|
1623
|
+
"permissions": {
|
|
1624
|
+
"editable": true,
|
|
1625
|
+
"deletable": true,
|
|
1626
|
+
"copyable": true
|
|
1627
|
+
},
|
|
1628
|
+
"data": {
|
|
1629
|
+
"pages": [
|
|
1630
|
+
{
|
|
1631
|
+
"id": "page_1",
|
|
1632
|
+
"elements": [
|
|
1633
|
+
{
|
|
1634
|
+
"id": "el_text_1",
|
|
1635
|
+
"type": "text",
|
|
1636
|
+
"x": 40,
|
|
1637
|
+
"y": 40,
|
|
1638
|
+
"width": 200,
|
|
1639
|
+
"height": 24,
|
|
1640
|
+
"content": "订单号: {#orderNo}",
|
|
1641
|
+
"style": { "fontSize": 14, "fontWeight": "bold", "color": "#111827" }
|
|
1642
|
+
},
|
|
1643
|
+
{
|
|
1644
|
+
"id": "el_table_1",
|
|
1645
|
+
"type": "table",
|
|
1646
|
+
"x": 40,
|
|
1647
|
+
"y": 100,
|
|
1648
|
+
"width": 500,
|
|
1649
|
+
"height": 200,
|
|
1650
|
+
"variable": "@items",
|
|
1651
|
+
"columnsVariable": "@tableCols",
|
|
1652
|
+
"showFooter": true,
|
|
1653
|
+
"footerDataVariable": "@tableFooter",
|
|
1654
|
+
"autoPaginate": true,
|
|
1655
|
+
"style": {
|
|
1656
|
+
"fontSize": 12,
|
|
1657
|
+
"color": "#000000",
|
|
1658
|
+
"headerBackgroundColor": "#f3f4f6",
|
|
1659
|
+
"borderColor": "#d1d5db",
|
|
1660
|
+
"borderWidth": 1
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
]
|
|
1664
|
+
}
|
|
1665
|
+
],
|
|
1666
|
+
"canvasSize": { "width": 794, "height": 1123 },
|
|
1667
|
+
"guides": [],
|
|
1668
|
+
"zoom": 1,
|
|
1669
|
+
"showGrid": true,
|
|
1670
|
+
"headerHeight": 0,
|
|
1671
|
+
"footerHeight": 0,
|
|
1672
|
+
"showHeaderLine": false,
|
|
1673
|
+
"showFooterLine": false,
|
|
1674
|
+
"showMinimap": false,
|
|
1675
|
+
"canvasBackground": "#ffffff",
|
|
1676
|
+
"pageSpacingX": 0,
|
|
1677
|
+
"pageSpacingY": 0,
|
|
1678
|
+
"unit": "mm",
|
|
1679
|
+
"watermark": {
|
|
1680
|
+
"enabled": false,
|
|
1681
|
+
"text": "",
|
|
1682
|
+
"angle": -30,
|
|
1683
|
+
"color": "#000000",
|
|
1684
|
+
"opacity": 0.1,
|
|
1685
|
+
"size": 24,
|
|
1686
|
+
"density": 160
|
|
1687
|
+
},
|
|
1688
|
+
"testData": {
|
|
1689
|
+
"orderNo": "A001",
|
|
1690
|
+
"items": [{ "name": "产品A", "price": 100, "quantity": 2 }]
|
|
1691
|
+
}
|
|
1692
|
+
},
|
|
1693
|
+
"ext": {
|
|
1694
|
+
"availableVariables": [
|
|
1695
|
+
{
|
|
1696
|
+
"id": "order",
|
|
1697
|
+
"label": "订单信息",
|
|
1698
|
+
"children": [
|
|
1699
|
+
{ "id": "orderNo", "label": "订单号" },
|
|
1700
|
+
{ "id": "items", "label": "订单明细", "isArray": true }
|
|
1701
|
+
]
|
|
1702
|
+
}
|
|
1703
|
+
]
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
```
|
|
1707
|
+
|
|
1708
|
+
### 顶层字段
|
|
1709
|
+
|
|
1710
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
1711
|
+
|-----|------|-----|------|
|
|
1712
|
+
| `id` | `string` | ✅ 是 | 模板唯一 ID |
|
|
1713
|
+
| `name` | `string` | ✅ 是 | 模板名称 |
|
|
1714
|
+
| `updatedAt` | `number` | 否 | 更新时间戳(毫秒) |
|
|
1715
|
+
| `permissions` | `object` | 否 | `{ editable, deletable, copyable }` |
|
|
1716
|
+
| `data` | `object` | ✅ 是 | 模板设计数据 |
|
|
1717
|
+
| `ext` | `object` | 否 | 扩展数据(变量树等) |
|
|
1718
|
+
|
|
1719
|
+
### `data` 字段
|
|
1720
|
+
|
|
1721
|
+
| 字段 | 类型 | 必填 | 说明 |
|
|
1722
|
+
|-----|------|-----|------|
|
|
1723
|
+
| `pages` | `Page[]` | ✅ 是 | 页面列表,每页含 `elements` 数组 |
|
|
1724
|
+
| `canvasSize` | `{ width, height }` | ✅ 是 | 画布尺寸 px(默认 A4: 794×1123) |
|
|
1725
|
+
| `unit` | `'mm' \| 'px' \| 'pt' \| 'in' \| 'cm'` | 否 | 单位,默认 `'mm'` |
|
|
1726
|
+
| `watermark` | `WatermarkSettings` | 否 | 水印配置 |
|
|
1727
|
+
| `guides` | `Guide[]` | 否 | 辅助参考线 |
|
|
1728
|
+
| `zoom` | `number` | 否 | 缩放比例,默认 `1` |
|
|
1729
|
+
| `showGrid` | `boolean` | 否 | 显示网格 |
|
|
1730
|
+
| `headerHeight` | `number` | 否 | 页眉高度 px |
|
|
1731
|
+
| `footerHeight` | `number` | 否 | 页脚高度 px |
|
|
1732
|
+
| `pageSpacingX` | `number` | 否 | 水平页边距 px |
|
|
1733
|
+
| `pageSpacingY` | `number` | 否 | 垂直页边距 px |
|
|
1734
|
+
| `canvasBackground` | `string` | 否 | 画布背景色 |
|
|
1735
|
+
| `testData` | `object` | 否 | 测试数据(报告模式会保存,模板模式不保存) |
|
|
1736
|
+
|
|
1737
|
+
---
|
|
1738
|
+
|
|
1739
|
+
## 支持的元素类型
|
|
1740
|
+
|
|
1741
|
+
| 类型 | 标识 | 说明 |
|
|
1742
|
+
|------|------|------|
|
|
1743
|
+
| 文本 | `text` | 静态文本或变量内容 |
|
|
1744
|
+
| 长文本 | `longtext` | 多行自动扩展文本,支持彩色标记语法 `[color=#xxx]` |
|
|
1745
|
+
| 富文本 | `richtext` | HTML 富文本格式化内容 |
|
|
1746
|
+
| 图片 | `image` | 图片显示(URL / Base64 / 变量绑定) |
|
|
1747
|
+
| 表格 | `table` | 数据表格,支持自动分页、页脚汇总、自定义脚本 |
|
|
1748
|
+
| 自定义表格 | `customTable` | 自由合并单元格、单元格内嵌图片/字段、逐格自定义样式 |
|
|
1749
|
+
| 嵌套表格 | `nestedtable` | 分组嵌套数据展示(父级标签 + 子级行列表) |
|
|
1750
|
+
| 条形码 | `barcode` | 一维条形码(CODE128、EAN13、UPC 等) |
|
|
1751
|
+
| 二维码 | `qrcode` | 二维码(支持 L/M/Q/H 纠错级别) |
|
|
1752
|
+
| 图表 | `chart` | ECharts 图表(柱状图/折线图/饼图/环形图/面积图) |
|
|
1753
|
+
| 数据卡片 | `datacard` | 单个关键指标数据展示(含趋势箭头) |
|
|
1754
|
+
| 进度条 | `progress` | 百分比进度展示 |
|
|
1755
|
+
| 列表 | `list` | 编号列表数据展示(横向/纵向布局) |
|
|
1756
|
+
| 日期 | `date` | 日期时间显示(可绑定变量或显示当前时间) |
|
|
1757
|
+
| 页码 | `pageNumber` | 页码显示(支持多种格式) |
|
|
1758
|
+
| 签名/印章 | `signature` | 印章图片或二维码,支持透明度 |
|
|
1759
|
+
| 线条 | `line` | 直线(支持实线/虚线/点线) |
|
|
1760
|
+
| 矩形 | `rect` | 矩形(支持圆角) |
|
|
1761
|
+
| 圆形 | `circle` | 圆形 |
|
|
1762
|
+
| 组合 | `group` | 多个元素的组合容器,作为整体移动/缩放/复制/删除(见[组合与取消组合](#组合与取消组合)) |
|
|
1763
|
+
|
|
1764
|
+
---
|
|
1765
|
+
|
|
1766
|
+
## 版本兼容性说明
|
|
1767
|
+
|
|
1768
|
+
### 依赖版本矩阵
|
|
1769
|
+
|
|
1770
|
+
| 组件包 | 要求版本 | 说明 |
|
|
1771
|
+
|--------|---------|------|
|
|
1772
|
+
| `vue` | `^3.5.0` | 核心框架 |
|
|
1773
|
+
| `pinia` | `^2.3.0` | 状态管理 |
|
|
1774
|
+
| `echarts` | `^6.0.0` | 图表库(按需引入) |
|
|
1775
|
+
| `lodash` | `^4.17.0` | 工具函数 |
|
|
1776
|
+
| `qrcode` | `^1.5.0` | 二维码生成 |
|
|
1777
|
+
| `jsbarcode` | `^3.12.0` | 条形码生成 |
|
|
1778
|
+
| `jspdf` | `4.2.x` | PDF 导出 |
|
|
1779
|
+
| `tailwindcss` | `^3.x` | CSS 框架(构建时依赖) |
|
|
1780
|
+
|
|
1781
|
+
### 升级注意事项
|
|
1782
|
+
|
|
1783
|
+
- **从 `0.2.x` 升级到 `0.3.x`**:`onSave` 回调签名变化,`payload.data` 中 `testData` 的保存规则改为由 `mode` 决定
|
|
1784
|
+
- **Vue 版本**:仅支持 Vue 3.5+,不支持 Vue 2.x
|
|
1785
|
+
- **Node 版本**:`vite build` 和 `vue-tsc` 需要 Node >= 18
|
|
1786
|
+
|
|
1787
|
+
---
|
|
1788
|
+
|
|
1789
|
+
## 常见问题排查
|
|
1790
|
+
|
|
1791
|
+
### 1. 设计器加载后空白 / 无渲染
|
|
1792
|
+
|
|
1793
|
+
**原因:** `ready` 事件未触发,或 Vue 集成方式未引入样式文件。
|
|
1794
|
+
|
|
1795
|
+
**排查步骤:**
|
|
1796
|
+
1. Web Component 方式:样式已全部内联进 JS(含 Tailwind 工具类与组件样式),无需宿主引入任何 CSS;仍空白请检查控制台报错
|
|
1797
|
+
2. Vue 集成方式(`x-print-designer/vue`):确认已引入 `x-print-designer/style.css`
|
|
1798
|
+
2. Web Component 方式需在 `ready` 事件后调用 API
|
|
1799
|
+
3. 检查浏览器控制台是否有报错
|
|
1800
|
+
|
|
1801
|
+
### 2. 变量绑定不生效,显示为空或变量名
|
|
1802
|
+
|
|
1803
|
+
**原因:** 变量路径与数据字段不匹配。
|
|
1804
|
+
|
|
1805
|
+
**排查步骤:**
|
|
1806
|
+
1. 确认 `sampleData` / `setTestData` 中包含对应字段
|
|
1807
|
+
2. 检查变量名大小写是否一致(`@customerName` vs `customername`)
|
|
1808
|
+
3. 嵌套路径使用点号分隔:`@user.name`
|
|
1809
|
+
4. 确认数据格式:表格需要数组,图表需要 `Array<{category, value}>`
|
|
1810
|
+
|
|
1811
|
+
### 3. 表格数据显示为空
|
|
1812
|
+
|
|
1813
|
+
**原因:** 表格 `variable` 未绑定或数据格式不匹配。
|
|
1814
|
+
|
|
1815
|
+
**排查步骤:**
|
|
1816
|
+
1. 确认表格元素的 `variable` 属性绑定了变量名
|
|
1817
|
+
2. 确认数据中该变量值为数组格式
|
|
1818
|
+
3. 如果使用 `columnsVariable`,确保列定义数组字段存在且包含 `{field, header}`
|
|
1819
|
+
|
|
1820
|
+
### 4. 打印/导出 PDF 时样式错乱
|
|
1821
|
+
|
|
1822
|
+
**原因:** 打印管线依赖 iframe 渲染 + 计算样式内联,部分样式可能未正确捕获。
|
|
1823
|
+
|
|
1824
|
+
**排查步骤:**
|
|
1825
|
+
1. 优先使用预览(`Ctrl+Shift+P`)确认渲染效果
|
|
1826
|
+
2. 确认 PDF 导出使用的是 `export({ type: 'pdf' })` 而非浏览器打印
|
|
1827
|
+
3. 检查控制台是否有 `Failed to load` 相关错误
|
|
1828
|
+
|
|
1829
|
+
### 5. 自定义表格预览时样式丢失
|
|
1830
|
+
|
|
1831
|
+
**原因:** 列宽未持久化或表格容器 `overflow: hidden` 约束未解锁。
|
|
1832
|
+
|
|
1833
|
+
**排查步骤:**
|
|
1834
|
+
1. 确认已保存模板(列宽需持久化到 `customTableColWidths`)
|
|
1835
|
+
2. 升级到最新版本(v0.3.1+ 已修复预览模式下列宽持久化和容器高度问题)
|
|
1836
|
+
|
|
1837
|
+
### 6. 图表在预览/导出时显示空白
|
|
1838
|
+
|
|
1839
|
+
**原因:** ECharts 异步渲染未完成,预览管线已截取快照。
|
|
1840
|
+
|
|
1841
|
+
**排查步骤:**
|
|
1842
|
+
1. 升级到最新版本(已修复 ChartElement 异步渲染注册问题)
|
|
1843
|
+
2. 确认 `chartDataVariable` 绑定的数据格式正确
|
|
1844
|
+
3. 在编辑模式下确认图表正常显示后再预览
|
|
1845
|
+
|
|
1846
|
+
### 7. `onSave` 回调未触发
|
|
1847
|
+
|
|
1848
|
+
**原因:** 未正确传入 `onSave` 属性。
|
|
1849
|
+
|
|
1850
|
+
**排查步骤:**
|
|
1851
|
+
1. Vue 组件确认 `:on-save="handleSave"` 正确绑定
|
|
1852
|
+
2. Web Component 确认在 `ready` 事件后调用 `setOnSave`
|
|
1853
|
+
3. 检查 `handleSave` 函数是否存在且无语法错误
|
|
1854
|
+
4. 确认未同时传入自建的 CRUD 配置(`onSave` 会覆盖内置保存逻辑)
|
|
1855
|
+
|
|
1856
|
+
### 8. 自动保存不生效
|
|
1857
|
+
|
|
1858
|
+
**原因:** 自动保存需要 `onSave` 回调。
|
|
1859
|
+
|
|
1860
|
+
**排查步骤:**
|
|
1861
|
+
1. 确认同时传入了 `onSave` 和 `config.autoSave = { enabled: true }`(Vue 组件)或调用了 `setAutoSave(true)`
|
|
1862
|
+
2. 自动保存仅在内容有变更时触发,未修改时不会保存
|
|
1863
|
+
3. 检查 `autoSave.intervalMs` / `setAutoSave` 的间隔是否设置过短(频繁保存可能被节流)
|
|
1864
|
+
|
|
1865
|
+
---
|
|
1866
|
+
|
|
1867
|
+
## 技术栈
|
|
1868
|
+
|
|
1869
|
+
| 技术 | 用途 |
|
|
1870
|
+
|------|------|
|
|
1871
|
+
| Vue 3 + TypeScript | 核心框架 |
|
|
1872
|
+
| Pinia | 状态管理 |
|
|
1873
|
+
| Tailwind CSS | 原子化 CSS |
|
|
1874
|
+
| ECharts 6 | 图表渲染 |
|
|
1875
|
+
| Monaco Editor | 代码/JSON 编辑器 |
|
|
1876
|
+
| jsPDF | PDF 生成 |
|
|
1877
|
+
| jsBarcode | 条形码生成 |
|
|
1878
|
+
| qrcode | 二维码生成 |
|
|
1879
|
+
| canvg | SVG → Canvas 转换 |
|
|
1880
|
+
| dom-to-image-more | DOM → 图片导出 |
|
|
1881
|
+
| jszip | ZIP 打包 |
|
|
1882
|
+
| vue-i18n | 国际化(中文/English) |
|
|
1883
|
+
| Vite | 构建工具 |
|