x-print-designer 0.4.14 → 0.4.41
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 +769 -66
- package/dist/{JsBarcode-XYc3_qaJ.js → JsBarcode-CLlw_gbk.js} +1 -1
- package/dist/{JsBarcode-DKThGPUY.js → JsBarcode-tsAzeEWM.js} +1 -1
- package/dist/{browser-C2DOr3jW.js → browser-CRmiemCh.js} +1 -1
- package/dist/{browser-k38foCq0.js → browser-iNycRk4T.js} +1 -1
- package/dist/{dom-to-image-more.min-CmrNolwX.js → dom-to-image-more.min-D0ig0Uh4.js} +1 -1
- package/dist/{index-C6kUsKU-.js → index-AEN4Ym4J.js} +1 -1
- package/dist/{index-BaP1Ck2O.js → index-qEe1XFR1.js} +26288 -19698
- package/dist/{index.es-CpznCo85.js → index.es-B2ZLGl1x.js} +1 -1
- package/dist/{jspdf.es.min-DVOmjmD9.js → jspdf.es.min-Bh2x9vwp.js} +1 -1
- package/dist/{jszip.min-BDBW17Ci.js → jszip.min-DdkLg28T.js} +1 -1
- package/dist/print-designer.css +1 -1
- package/dist/print-designer.es.js +15 -2
- package/dist/print-designer.umd.js +57 -52
- package/dist/{vue-Nq-wK6M_.js → vue-BW3F83GJ.js} +7659 -3390
- package/dist/vue.css +1 -1
- package/dist/vue.d.ts +1 -0
- package/dist/vue.es.js +14 -1
- package/dist/web-component.d.ts +133 -1
- package/docs/designs/2026-08-12-floating-typography-toolbar-design.md +126 -0
- package/docs/designs/2026-08-12-mark3d-flight-player-design.md +148 -0
- package/docs/designs/2026-08-12-onboarding-element-operations-design.md +96 -0
- package/docs/designs/2026-08-12-table-column-picker-design.md +88 -0
- package/docs/designs/2026-08-12-table-data-wizard-design.md +98 -0
- package/docs/designs/2026-08-12-toolbar-context-aware-formatting-design.md +137 -0
- package/docs/designs/2026-08-12-toolbar-usage-fixes-design.md +118 -0
- package/docs/designs/2026-08-12-toolbar-verification-design.md +99 -0
- package/docs/designs/2026-08-12-unify-text-rich-text-design.md +114 -0
- package/docs/designs/onboarding-and-host-integration.md +144 -0
- package/docs/en/guide/web-components-guide.md +154 -1
- package/docs/implementation-plan.md +145 -0
- package/docs/plans/2026-08-11-host-integration-workflow.md +113 -0
- package/docs/plans/2026-08-12-floating-typography-toolbar-workflow.md +87 -0
- package/docs/plans/2026-08-12-mark3d-flight-player-workflow.md +108 -0
- package/docs/plans/2026-08-12-onboarding-element-operations-workflow.md +74 -0
- package/docs/plans/2026-08-12-table-column-picker-workflow.md +121 -0
- package/docs/plans/2026-08-12-table-data-wizard-workflow.md +114 -0
- package/docs/plans/2026-08-12-toolbar-context-aware-formatting-workflow.md +132 -0
- package/docs/plans/2026-08-12-toolbar-usage-fixes-workflow.md +129 -0
- package/docs/plans/2026-08-12-toolbar-verification-workflow.md +172 -0
- package/docs/plans/2026-08-12-unify-text-rich-text-workflow.md +129 -0
- package/docs/reports/2026-08-12-element-overflow-bug-report.md +183 -0
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -9,11 +9,17 @@
|
|
|
9
9
|
- [前置依赖要求](#前置依赖要求)
|
|
10
10
|
- [快速开始](#快速开始)
|
|
11
11
|
- [源码开发](#源码开发)
|
|
12
|
-
- [Vue
|
|
12
|
+
- [Vue 组件接入(XPrintDesigner)](#vue-组件接入xprintdesigner)
|
|
13
|
+
- [5 分钟接入(推荐)](#5-分钟接入推荐)
|
|
13
14
|
- [Web Component 接入](#web-component-接入)
|
|
14
15
|
- [核心概念:模板模式 vs 报告模式](#核心概念模板模式-vs-报告模式)
|
|
15
16
|
- [Vue 组件使用说明](#vue-组件使用说明)
|
|
16
17
|
- [Web Component 使用说明](#web-component-使用说明)
|
|
18
|
+
- [完整 API 参考](#完整-api-参考)
|
|
19
|
+
- [场景化模板库](#场景化模板库)
|
|
20
|
+
- [数据驱动模板向导](#数据驱动模板向导)
|
|
21
|
+
- [组合与取消组合](#组合与取消组合)
|
|
22
|
+
- [远程 CRUD 配置](#远程-crud-配置)
|
|
17
23
|
- [传入数据格式](#传入数据格式)
|
|
18
24
|
- [元素数据绑定](#元素数据绑定)
|
|
19
25
|
- [图片元素使用说明](#图片元素使用说明)
|
|
@@ -73,41 +79,243 @@ pnpm build:wc
|
|
|
73
79
|
|
|
74
80
|
启动后浏览器访问 `http://localhost:5173` 即可看到设计器 Demo 页面(`index.html`)。
|
|
75
81
|
|
|
76
|
-
### Vue
|
|
82
|
+
### Vue 组件接入(XPrintDesigner)
|
|
77
83
|
|
|
78
|
-
**适用于:**
|
|
84
|
+
**适用于:** Vue 3 项目以一等公民组件方式接入。`XPrintDesigner` 是 Web Component 的 Vue 适配层:props 声明式传参、事件自动透传、ref 暴露常用方法,**无需手动管理 `ready` 时序**。
|
|
85
|
+
|
|
86
|
+
#### 基础用法(简化版)
|
|
87
|
+
|
|
88
|
+
使用新增的工具函数,**从 700 行减少到 30 行**:
|
|
79
89
|
|
|
80
90
|
```vue
|
|
81
91
|
<template>
|
|
82
|
-
<
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
92
|
+
<XPrintDesigner
|
|
93
|
+
:config="config"
|
|
94
|
+
@checklist="onChecklist"
|
|
95
|
+
@printed="onPrinted"
|
|
86
96
|
/>
|
|
87
97
|
</template>
|
|
88
98
|
|
|
89
99
|
<script setup lang="ts">
|
|
90
100
|
import { ref } from 'vue'
|
|
91
|
-
import {
|
|
101
|
+
import { XPrintDesigner, createRestfulAdapter, buildFieldDictionary } from 'x-print-designer'
|
|
92
102
|
import 'x-print-designer/style.css'
|
|
93
103
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
+
}
|
|
101
128
|
})
|
|
102
129
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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)
|
|
110
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
|
+
<script type="module" src="/dist/print-designer.es.js"></script>
|
|
226
|
+
<link rel="stylesheet" href="/dist/print-designer.css" />
|
|
227
|
+
</head>
|
|
228
|
+
<body>
|
|
229
|
+
<!-- ② 放置组件标签 -->
|
|
230
|
+
<print-designer id="designer"></print-designer>
|
|
231
|
+
|
|
232
|
+
<script>
|
|
233
|
+
// ③ 一次性传入全部配置(init 方法通道)
|
|
234
|
+
const designer = document.getElementById('designer')
|
|
235
|
+
designer.init({
|
|
236
|
+
mode: 'template',
|
|
237
|
+
testData: { id: 1, title: '报告标题', items: [{ name: '产品A', price: 100 }] },
|
|
238
|
+
onSave: (payload) => {
|
|
239
|
+
fetch('/api/templates/save', {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: { 'Content-Type': 'application/json' },
|
|
242
|
+
body: JSON.stringify(payload)
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
// ④ 可选:监听接入自检结果
|
|
248
|
+
designer.addEventListener('checklist', (e) => {
|
|
249
|
+
console.log('接入自检', e.detail.checks, '全部通过:', e.detail.allPassed)
|
|
250
|
+
})
|
|
251
|
+
</script>
|
|
252
|
+
</body>
|
|
253
|
+
</html>
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**方法通道 vs 属性通道(二选一):**
|
|
257
|
+
|
|
258
|
+
- **方法通道(推荐)**:直接调用 `el.init(config)`。`ready` 前调用会自动排队,`ready` 后自动应用,无需自己等事件。
|
|
259
|
+
- **属性通道**:通过 `config` 属性声明式传入 JSON,效果等价:
|
|
260
|
+
|
|
261
|
+
```html
|
|
262
|
+
<print-designer
|
|
263
|
+
config='{"mode":"template","testData":{"id":1,"title":"报告标题"},"onSave":null}'
|
|
264
|
+
></print-designer>
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
> 若 `config` 属性 JSON 解析失败,组件会派发 `error` 事件(`detail.scope === 'config'`)且不影响默认 UI。
|
|
268
|
+
|
|
269
|
+
**接入自检 `checklist` 事件:** 组件在 `ready` 时自动派发,`detail` 形如 `{ checks: [{ name, passed, hint? }...], allPassed }`,检查项包括 `ready`、`dataChannel`、`printApi`、`exportApi`、`onSaveBound`、`monacoLoader`。若存在未通过项,控制台会出现 `[PrintDesigner] 接入自检未全部通过:...` 的 `warn` 提示(例如未配置 `onSave` 时 `onSaveBound` 为 `false`),便于快速定位遗漏。
|
|
270
|
+
|
|
271
|
+
#### `init(config)` 完整配置字段
|
|
272
|
+
|
|
273
|
+
`DesignerInitConfig` 一次调用即可完成全部初始化;`ready` 前调用自动排队,`ready` 后立即应用。**显式调用过的 setter 优先级高于 init 配置**(不会双写冲突)。
|
|
274
|
+
|
|
275
|
+
| 字段 | 类型 | 说明 |
|
|
276
|
+
|------|------|------|
|
|
277
|
+
| `mode` | `'template' \| 'report'` | 工作模式 |
|
|
278
|
+
| `viewMode` | `'edit' \| 'preview'` | 视图模式 |
|
|
279
|
+
| `uiMode` | `'beginner' \| 'expert' \| null` | 界面模式(简洁/完整) |
|
|
280
|
+
| `language` | `string` | 语言(`'zh'` / `'en'`) |
|
|
281
|
+
| `headless` | `boolean` | 无头模式 |
|
|
282
|
+
| `data` | `Record<string, any>` | 数据(走 `setData` 默认通道) |
|
|
283
|
+
| `testData` | `Record<string, any>` | 测试/预览数据 |
|
|
284
|
+
| `variables` | `Record<string, any>` | 运行时变量(打印/导出时覆盖 testData) |
|
|
285
|
+
| `onSave` | `(payload) => void \| Promise<void> \| null` | 保存回调 |
|
|
286
|
+
| `autoSave` | `{ enabled, intervalMs? }` | 自动保存 |
|
|
287
|
+
| `fieldDictionary` | `DesignerFieldDictionary \| null` | 字段字典(变量面板中文标签) |
|
|
288
|
+
| `textPolishHandler` | `(text) => Promise<string> \| null` | 文本润色处理器(宿主 AI 能力) |
|
|
289
|
+
| `textPolishConfig` | `TextPolishConfig \| null` | 内置 DeepSeek 润色配置 |
|
|
290
|
+
| `branding` | `{ title?, logoUrl?, showTitle?, showLogo? }` | 品牌标识 |
|
|
291
|
+
| `theme` | `string` | 主题(`'light'` / `'dark'` / `'system'`) |
|
|
292
|
+
| `printDefaults` | `DesignerPrintDefaults` | 默认打印/导出选项与连接设置 |
|
|
293
|
+
| `previewMode` | `DesignerPreviewModeOptions` | 独立预览模式 |
|
|
294
|
+
| `crud` | `{ mode?, endpoints?, config? }` | 远程 CRUD 配置(见[远程 CRUD 配置](#远程-crud-配置)) |
|
|
295
|
+
|
|
296
|
+
**Vue 宿主 5 分钟接入(XPrintDesigner):** 用法与 Web Component 完全等价,仅换为组件写法:
|
|
297
|
+
|
|
298
|
+
```vue
|
|
299
|
+
<template>
|
|
300
|
+
<XPrintDesigner :config="config" />
|
|
301
|
+
</template>
|
|
302
|
+
|
|
303
|
+
<script setup lang="ts">
|
|
304
|
+
import { ref } from 'vue'
|
|
305
|
+
import { XPrintDesigner } from 'x-print-designer'
|
|
306
|
+
import 'x-print-designer/style.css'
|
|
307
|
+
|
|
308
|
+
const config = ref({
|
|
309
|
+
mode: 'template',
|
|
310
|
+
testData: { id: 1, title: '报告标题', items: [{ name: '产品A', price: 100 }] },
|
|
311
|
+
onSave: async (payload) => {
|
|
312
|
+
await fetch('/api/templates/save', {
|
|
313
|
+
method: 'POST',
|
|
314
|
+
headers: { 'Content-Type': 'application/json' },
|
|
315
|
+
body: JSON.stringify(payload)
|
|
316
|
+
})
|
|
317
|
+
}
|
|
318
|
+
})
|
|
111
319
|
</script>
|
|
112
320
|
```
|
|
113
321
|
|
|
@@ -242,9 +450,9 @@ designer.print({ mode: 'browser', options: { silent: true } })
|
|
|
242
450
|
### 切换模式
|
|
243
451
|
|
|
244
452
|
```vue
|
|
245
|
-
<!-- Vue
|
|
246
|
-
<
|
|
247
|
-
<
|
|
453
|
+
<!-- Vue 组件(XPrintDesigner) -->
|
|
454
|
+
<XPrintDesigner mode="template" /> <!-- 模板模式(默认) -->
|
|
455
|
+
<XPrintDesigner mode="report" /> <!-- 报告模式 -->
|
|
248
456
|
```
|
|
249
457
|
|
|
250
458
|
```javascript
|
|
@@ -259,63 +467,72 @@ designer.setMode('report')
|
|
|
259
467
|
|
|
260
468
|
### 组件 Props
|
|
261
469
|
|
|
470
|
+
`XPrintDesigner` 是 Web Component 的 Vue 适配层(从 `x-print-designer` 导出)。除下表 props 外,其余配置(数据、CRUD、品牌、主题等)统一通过 `config` 传入。
|
|
471
|
+
|
|
262
472
|
| 属性 | 类型 | 默认值 | 说明 |
|
|
263
473
|
|------|------|--------|------|
|
|
264
|
-
| `
|
|
265
|
-
| `
|
|
474
|
+
| `config` | `DesignerInitConfig \| null` | `null` | 统一初始化配置(等价 `el.init(config)`),见[init 完整配置](#initconfig-完整配置字段) |
|
|
475
|
+
| `mode` | `'template'` \| `'report'` | — | 工作模式,影响保存行为 |
|
|
476
|
+
| `uiMode` | `'beginner'` \| `'expert'` | — | 界面模式(简洁/完整) |
|
|
477
|
+
| `viewMode` | `'edit'` \| `'preview'` | — | 视图模式,`'preview'` 时只渲染打印内容 |
|
|
266
478
|
| `headless` | `boolean` | `false` | 无头模式,隐藏顶部工具栏和侧边栏 |
|
|
267
|
-
| `
|
|
268
|
-
| `
|
|
269
|
-
| `auto-save-enabled` | `boolean` | `false` | 是否开启定时自动保存 |
|
|
270
|
-
| `auto-save-interval-ms` | `number` | `120000` | 自动保存间隔(毫秒),默认 2 分钟 |
|
|
479
|
+
| `onSave` | `(payload) => void \| Promise<void>` | — | 保存回调,详见[保存回调](#保存回调-onsave)(映射 `setOnSave`) |
|
|
480
|
+
| `theme` | `string` | — | 主题(映射 `setTheme`) |
|
|
271
481
|
|
|
272
482
|
### Props 详细说明
|
|
273
483
|
|
|
274
|
-
#### `
|
|
484
|
+
#### `config`
|
|
275
485
|
|
|
276
|
-
|
|
486
|
+
**宿主接入的首选入口**:所有配置(数据、保存、CRUD、品牌、主题等)一次传入,等价于调用 `el.init(config)`。`ready` 前传入自动排队,`ready` 后自动应用。
|
|
277
487
|
|
|
278
488
|
```vue
|
|
279
|
-
<
|
|
489
|
+
<XPrintDesigner :config="{
|
|
490
|
+
mode: 'report',
|
|
491
|
+
testData: { id: 1, title: '检测报告', items: [{ name: '产品A', price: 100 }] },
|
|
492
|
+
onSave: async (payload) => { await fetch('/api/reports/save', { method: 'POST', body: JSON.stringify(payload) }) },
|
|
493
|
+
autoSave: { enabled: true, intervalMs: 120000 },
|
|
494
|
+
crud: { mode: 'remote', endpoints: { /* ... */ } }
|
|
495
|
+
}" />
|
|
280
496
|
```
|
|
281
497
|
|
|
282
|
-
#### `
|
|
498
|
+
#### `mode`
|
|
283
499
|
|
|
284
|
-
|
|
500
|
+
工作模式,决定保存时是否附带 `testData`。
|
|
285
501
|
|
|
286
502
|
```vue
|
|
287
|
-
<
|
|
503
|
+
<XPrintDesigner mode="report" />
|
|
288
504
|
```
|
|
289
505
|
|
|
290
|
-
#### `
|
|
506
|
+
#### `viewMode`
|
|
291
507
|
|
|
292
|
-
|
|
508
|
+
切换编辑/预览视图。设为 `'preview'` 时隐藏所有编辑器 UI,仅渲染打印页面。适用于嵌入第三方页面时只展示打印效果。
|
|
293
509
|
|
|
294
510
|
```vue
|
|
295
|
-
<
|
|
511
|
+
<XPrintDesigner view-mode="preview" />
|
|
296
512
|
```
|
|
297
513
|
|
|
298
|
-
#### `
|
|
514
|
+
#### `headless`
|
|
299
515
|
|
|
300
|
-
|
|
516
|
+
无头模式,隐藏顶部工具栏和侧边属性面板。用于嵌入式场景仅保留画布区域。
|
|
301
517
|
|
|
302
518
|
```vue
|
|
303
|
-
<
|
|
519
|
+
<XPrintDesigner headless />
|
|
304
520
|
```
|
|
305
521
|
|
|
306
|
-
#### `
|
|
522
|
+
#### `onSave`
|
|
307
523
|
|
|
308
|
-
|
|
524
|
+
保存回调函数。传入后设计器**不再执行内置保存逻辑**,完全由用户自行处理持久化。也可通过 `config.onSave` 传入,效果一致。
|
|
309
525
|
|
|
310
526
|
```vue
|
|
311
|
-
<
|
|
527
|
+
<XPrintDesigner :on-save="(payload) => mySaveApi(payload)" />
|
|
312
528
|
```
|
|
313
529
|
|
|
314
530
|
> 不传 `onSave` 时,设计器使用内置 CRUD 逻辑(根据配置调远程接口或存 localStorage)。
|
|
315
531
|
|
|
316
|
-
####
|
|
532
|
+
#### 预览数据与自动保存
|
|
317
533
|
|
|
318
|
-
|
|
534
|
+
- 预览数据:通过 `config.testData`(等价 Web Component 的 `setTestData`,详见 [传入数据格式](#传入数据格式))。
|
|
535
|
+
- 自动保存:通过 `config.autoSave = { enabled, intervalMs? }` 开启(详见 [自动保存配置](#自动保存配置))。
|
|
319
536
|
|
|
320
537
|
---
|
|
321
538
|
|
|
@@ -343,15 +560,29 @@ designer.setMode('report')
|
|
|
343
560
|
|
|
344
561
|
| 方法 | 参数 | 返回值 | 说明 |
|
|
345
562
|
|------|------|--------|------|
|
|
563
|
+
| `init(config)` | `DesignerInitConfig` | `void` | 统一初始化入口(见[init 完整配置](#initconfig-完整配置字段))。ready 前调用自动排队,ready 后自动应用 |
|
|
346
564
|
| `setMode(mode)` | `'template'` \| `'report'` | `void` | 设置工作模式 |
|
|
347
565
|
| `setViewMode(viewMode)` | `'edit'` \| `'preview'` | `void` | 切换视图模式 |
|
|
566
|
+
| `setUIMode(mode)` | `'beginner'` \| `'expert'` | `void` | 切换界面模式(简洁/完整) |
|
|
348
567
|
| `setOnSave(handler)` | `(payload) => void \| Promise<void>` | `void` | 设置保存回调 |
|
|
349
568
|
| `setAutoSave(enabled, intervalMs?)` | `boolean, number?` | `void` | 开启/关闭自动保存,`intervalMs` 默认 120000 |
|
|
350
|
-
| `setTestData(data)` | `Record<string, any>` | `void
|
|
351
|
-
| `setSampleData(data)` | `Record<string, any>` | `void
|
|
352
|
-
| `setVariables(vars)` | `Record<string, any>` | `void
|
|
569
|
+
| `setTestData(data)` | `Record<string, any>` | `Promise<void>` | 设置测试/预览数据(等价 Vue 组件通过 `config.testData` 传入) |
|
|
570
|
+
| `setSampleData(data)` | `Record<string, any>` | `Promise<void>` | `setTestData` 的别名 |
|
|
571
|
+
| `setVariables(vars)` | `Record<string, any>` | `Promise<void>` | 打印/导出时注入真实变量(覆盖 testData 中的同名字段) |
|
|
572
|
+
| `setTemplateVariables(vars)` | `Record<string, any>` | `Promise<void>` | 设置模板变量(按 `@键` 规范化后写入 variables) |
|
|
573
|
+
| `setData(data, opts?)` | `Record<string, any>, DesignerDataOptions?` | `Promise<void>` | **统一数据注入入口**。`opts.channel` 可选 `variables`(默认)/`testData`/`sampleData`/`availableVariables`,读取优先级 `runtimeVariables > testData > sampleData`,见[数据通道与优先级](#数据通道与优先级) |
|
|
574
|
+
| `getTestData()` | — | `Record<string, any>` | 获取当前测试数据 |
|
|
575
|
+
| `getVariables()` | — | `Record<string, any>` | 获取当前运行时变量 |
|
|
576
|
+
| `getTemplateVariables()` | — | `Record<string, any>` | 获取当前模板变量 |
|
|
577
|
+
| `setFieldDictionary(dict)` | `Record<string, FieldItem>` | `void` | 设置字段字典(变量面板中显示中文标签) |
|
|
578
|
+
| `setAvailableVariables(vars)` | `VariableTreeItem[]` | `void` | 设置可用变量树(字段选择提示) |
|
|
579
|
+
| `setDataLoading(loading)` | `boolean` | `void` | 设置外部数据加载状态(展示 loading 遮罩) |
|
|
580
|
+
| `setTextPolishHandler(handler)` | `(text) => Promise<string>` | `void` | 注入文本润色处理器(宿主侧 AI 能力,优先于内置 DeepSeek) |
|
|
581
|
+
| `setTextPolishConfig(config)` | `TextPolishConfig` | `void` | 配置内置 DeepSeek 润色(`{ apiKey, baseURL?, model? }`) |
|
|
353
582
|
| `setTemplates(list, opts?)` | `Array<Template>, object?` | `Promise<void>` | 设置预设模板列表并加载当前模板。`opts` 可选:`{ currentTemplateId?: string }`。返回的 Promise 在画布 pages 填充完成后 resolve,可据此消除时序竞态 |
|
|
354
583
|
| `loadTemplate(id)` | `string` | `Promise<boolean>` | 加载指定模板到画布。resolve 时 pages 已填充;返回 `false` 表示因前置条件(如正在编辑自定义元素)未触发 |
|
|
584
|
+
| `loadExampleTemplate(id)` | `string` | `Promise<boolean>` | 加载内置示例模板(复制为可编辑模板并加载;同名模板存在时直接加载) |
|
|
585
|
+
| `setTemplateLibrary(list, opts?)` | `DesignerTemplateLibraryItem[], { mode?: 'replace' \| 'merge' }?` | `void` | 替换或合并示例模板库(场景化模板注入)。`mode: 'replace'`(默认)整体替换内置库;`'merge'` 同 id 覆盖、新 id 追加。影响空态与模板下拉的示例模板区。`category` 可复用内置场景 key(`device-run`/`inspection`/`event-feedback`/`other`)或自定义任意值 |
|
|
355
586
|
| `loadTemplateData(data)` | `Record<string, any>` | `boolean` | 直接加载模板 JSON 数据到画布,返回是否成功 |
|
|
356
587
|
| `getTemplateData()` | — | `Record<string, any>` | 获取当前模板完整数据 |
|
|
357
588
|
| `getPreviewHtml()` | — | `Promise<string>` | 获取预览 HTML 字符串 |
|
|
@@ -362,9 +593,11 @@ designer.setMode('report')
|
|
|
362
593
|
| `setBranding(config)` | `{ title?, logoUrl?, showTitle?, showLogo? }` | `void` | 设置品牌标识 |
|
|
363
594
|
| `setLanguage(lang)` | `'zh'` \| `'en'` | `void` | 切换语言 |
|
|
364
595
|
| `setCrudMode(mode, opts?)` | `'local'` \| `'remote'`, `{ skipAutoLoad?: boolean }` | `void` | 切换 CRUD 模式。`skipAutoLoad: true` 跳过 remote 模式下自动 `loadTemplates + loadTemplate` 的链式加载,适用于已通过 `setTemplates` 提供数据的场景,避免双次加载闪烁 |
|
|
596
|
+
| `setCrudEndpoints(endpoints, opts?)` | `CrudEndpoints, { baseUrl?, headers?, fetcher? }` | `void` | 配置远程 CRUD 端点(templates/customElements 的 list/get/upsert/delete),详见[远程 CRUD 配置](#远程-crud-配置) |
|
|
597
|
+
| `setAutoCollapseLoopPages(enabled)` | `boolean` | `void` | 切换保存时自动折叠循环页(默认开启)。关闭后需自行调用 `collapseLoopPages` |
|
|
365
598
|
| `expandLoopPages(pages, ctx?)` | `Page[], LoopExpandContext?` | `Page[]` | 循环页展开:按 `page.loopDataVariable` + 数据数组,把 1 个模板页展开成 N 页。不传 `ctx` 时默认使用当前 store 数据(导出语义) |
|
|
366
599
|
| `hasLoopPage(pages)` | `Page[]?` | `boolean` | 判断页面数组中是否含循环页 |
|
|
367
|
-
| `collapseLoopPages(currentPages, originalPages)` | `Page[], Page[]` | `Page[]` | 折叠已展开的循环页,还原为带 `loopDataVariable`
|
|
600
|
+
| `collapseLoopPages(currentPages, originalPages)` | `Page[], Page[]` | `Page[]` | 折叠已展开的循环页,还原为带 `loopDataVariable` 的单个模板页。保存流程已自动折叠,通常无需手动调用 |
|
|
368
601
|
|
|
369
602
|
### 事件参考
|
|
370
603
|
|
|
@@ -490,11 +723,468 @@ function backToEdit() {
|
|
|
490
723
|
|
|
491
724
|
---
|
|
492
725
|
|
|
726
|
+
## 场景化模板库
|
|
727
|
+
|
|
728
|
+
为降低业务人员上手门槛,模板下拉与空态页按**业务场景**分组展示内置示例模板,并支持搜索过滤。内置场景围绕无人机业务:
|
|
729
|
+
|
|
730
|
+
| 场景 key | 场景名称 | 内置模板 |
|
|
731
|
+
|----------|---------|---------|
|
|
732
|
+
| `device-run` | 设备运行 | 设备档案卡、飞行任务记录、设备状态、电池电量、航程统计 |
|
|
733
|
+
| `inspection` | 巡检记录 | 巡检报告、巡检任务明细、巡检缺陷记录、飞行日志 |
|
|
734
|
+
| `event-feedback` | 事件反馈 | 告警汇总单、事件上报单、异常反馈单、处置结果单 |
|
|
735
|
+
| `other` | 其他 | 通用示例(送货单等) |
|
|
736
|
+
|
|
737
|
+
### 宿主注入模板库 `setTemplateLibrary`
|
|
738
|
+
|
|
739
|
+
宿主可将自定义模板注入示例库:`mode: 'replace'`(默认)整体替换内置库;`mode: 'merge'` 同 `id` 覆盖、新 `id` 追加。注入后模板下拉与空态**响应式刷新**。
|
|
740
|
+
|
|
741
|
+
```ts
|
|
742
|
+
el.setTemplateLibrary([
|
|
743
|
+
{
|
|
744
|
+
id: 'biz-device-card',
|
|
745
|
+
name: '设备档案卡(自定义)',
|
|
746
|
+
description: '用于设备入库归档打印',
|
|
747
|
+
category: 'device-run', // 复用内置场景 key,或传自定义字符串(如 'biz')
|
|
748
|
+
data: { pages: [/* ... */], canvasSize: { width: 794, height: 1123 } }
|
|
749
|
+
}
|
|
750
|
+
], { mode: 'merge' })
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
> 模板数据 `data` 需包含 `pages`(非空元素数组)与 `canvasSize`;缺 `id`/`name` 或结构不合法的项会被自动丢弃。
|
|
754
|
+
|
|
755
|
+
### 加载内置示例 `loadExampleTemplate`
|
|
756
|
+
|
|
757
|
+
```ts
|
|
758
|
+
// 按 id 加载内置示例模板:复制为可编辑模板并填充画布(同名模板已存在时直接加载)
|
|
759
|
+
await el.loadExampleTemplate('example-device-status')
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
---
|
|
763
|
+
|
|
764
|
+
## 数据驱动模板向导
|
|
765
|
+
|
|
766
|
+
不熟悉画布设计的用户可直接从样例数据生成模板骨架:在模板下拉中选择 **"从数据生成模板"**,粘贴样例 JSON(或使用内置示例数据),设计器会:
|
|
767
|
+
|
|
768
|
+
1. **推断字段结构**:识别标量字段(文本/数字/日期)与数组字段(对象数组 → 表格)。
|
|
769
|
+
2. **生成基础骨架**:自动创建"标题 + 每字段一行 label + 数据表格"布局,表格自动绑定数组字段并渲染行。
|
|
770
|
+
3. **加载进画布微调**:骨架加载后变量可正常解析,用户只需调整样式与排版。
|
|
771
|
+
|
|
772
|
+
> 定位为**骨架生成**:不做像素级智能排版;生成的布局可在画布中自由微调,也可通过 `setTemplateLibrary` 沉淀为团队模板。
|
|
773
|
+
|
|
774
|
+
---
|
|
775
|
+
|
|
776
|
+
## 组合与取消组合
|
|
777
|
+
|
|
778
|
+
画布支持将多个元素**组合为一个整体**操作(移动、缩放、复制、删除):
|
|
779
|
+
|
|
780
|
+
- **组合**:按住 `Ctrl`(Mac 为 `Cmd`)多选元素后,点击顶部工具栏 **"组合"** 按钮(或快捷键)。组合后属性面板显示"组合包含 N 个元素"。
|
|
781
|
+
- **整体操作**:组合作为一个整体参与拖拽移动、八方向缩放(子元素等比联动)、复制粘贴(粘贴后子元素 id 自动再生,不会冲突)。
|
|
782
|
+
- **取消组合**:选中组合后,属性面板点击 **"取消组合"**,成员恢复为独立元素(还原绝对坐标)。
|
|
783
|
+
- **限制**:组合至少需要 2 个元素;已锁定元素不能参与组合。
|
|
784
|
+
|
|
785
|
+
---
|
|
786
|
+
|
|
787
|
+
## 数据通道与优先级
|
|
788
|
+
|
|
789
|
+
设计器存在多条数据注入通道,为避免使用混淆,**读取时统一按以下优先级解析**(同 key 冲突时,优先级高的通道胜出):
|
|
790
|
+
|
|
791
|
+
```
|
|
792
|
+
runtimeVariables(运行时变量,setVariables / setData variables)
|
|
793
|
+
> testData(模板测试数据,setTestData / setSampleData / setData testData)
|
|
794
|
+
> sampleData(宿主静态示例数据,仅兜底取值)
|
|
795
|
+
> ext.availableVariables(模板字段树声明,仅用于字段提示,不参与取值)
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
- **建议**:新接入的宿主统一使用 [`setData(data, options)`](#完整-api-参考) 一个入口,用 `options.channel` 选择写入通道;旧 4 个方法(`setTestData` / `setSampleData` / `setVariables` / `setTemplateVariables`)保留并作为 `setData` 的薄包装,行为不变。
|
|
799
|
+
- **原则**:只统一"读取时"的 fallback 链,不合并存储,编辑态 / 打印态差异保持不变。
|
|
800
|
+
|
|
801
|
+
```js
|
|
802
|
+
// 打印/导出时覆盖预览数据(运行时变量优先级最高)
|
|
803
|
+
designer.setData(
|
|
804
|
+
{ items: [{ name: '巡检无人机', qty: 2, total: 12800 }] },
|
|
805
|
+
{ channel: 'variables', merge: true }
|
|
806
|
+
);
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
---
|
|
810
|
+
|
|
811
|
+
## 远程 CRUD 配置
|
|
812
|
+
|
|
813
|
+
不传 `onSave` 时,设计器走内置 CRUD:`local` 模式存 localStorage(默认),`remote` 模式调后端接口。宿主接入云存储需两步:
|
|
814
|
+
|
|
815
|
+
### 1. 配置端点 `setCrudEndpoints`
|
|
816
|
+
|
|
817
|
+
端点支持字符串(默认方法:list 用 GET、upsert 用 POST)或对象(自定义 `url` + `method`)。URL 支持相对路径(自动拼接 `baseUrl`)或**绝对路径**(忽略 `baseUrl`,便于对接第三方/微服务):
|
|
818
|
+
|
|
819
|
+
```ts
|
|
820
|
+
el.setCrudEndpoints({
|
|
821
|
+
baseUrl: 'https://api.example.com', // 也可在 options 中传
|
|
822
|
+
templates: {
|
|
823
|
+
list: '/print/templates', // GET https://api.example.com/print/templates
|
|
824
|
+
get: '/print/templates/{id}', // {id} 占位符会被真实 id 替换
|
|
825
|
+
upsert: '/print/templates', // POST(body 为模板对象)
|
|
826
|
+
delete: '/print/templates/{id}'
|
|
827
|
+
},
|
|
828
|
+
customElements: {
|
|
829
|
+
list: '/print/custom-elements',
|
|
830
|
+
get: '/print/custom-elements/{id}',
|
|
831
|
+
upsert: '/print/custom-elements',
|
|
832
|
+
delete: '/print/custom-elements/{id}'
|
|
833
|
+
}
|
|
834
|
+
}, {
|
|
835
|
+
baseUrl: 'https://your-domain.com',
|
|
836
|
+
headers: { Authorization: 'Bearer xxx', 'X-Tenant-ID': '123' }
|
|
837
|
+
})
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
### 2. 切换远程模式 `setCrudMode`
|
|
841
|
+
|
|
842
|
+
```ts
|
|
843
|
+
el.setCrudMode('remote') // 保存/删除/加载走远程 API;local 为默认(localStorage)
|
|
844
|
+
```
|
|
845
|
+
|
|
846
|
+
> 若已通过 `setTemplates` 自行提供数据,可传 `setCrudMode('remote', { skipAutoLoad: true })` 跳过 remote 模式下的自动链式加载,避免双次加载闪烁。
|
|
847
|
+
|
|
848
|
+
### 后端接口约定(4 个端点 / 资源)
|
|
849
|
+
|
|
850
|
+
| 端点 | 请求 | 响应要求 |
|
|
851
|
+
|------|------|---------|
|
|
852
|
+
| `list` | GET | 返回**模板/自定义元素数组**;渲染按返回顺序 |
|
|
853
|
+
| `get` | GET `{id}` | 返回详情:模板含 `data` 与 `ext.availableVariables`;自定义元素含 `element` |
|
|
854
|
+
| `upsert` | POST | 创建/更新;**必须返回真实 `id`** |
|
|
855
|
+
| `delete` | DELETE `{id}` | 返回 `{ success: boolean }` |
|
|
856
|
+
|
|
857
|
+
**通用约定:**
|
|
858
|
+
|
|
859
|
+
- 每个资源(template / customElement)都有 `id`、`name`、`permissions`(`{ editable, deletable, copyable }`)、`updatedAt`、`ext` 字段。
|
|
860
|
+
- **扩展字段一律放 `ext` 下**(如 `ext.category`、`ext.templateTags`、`ext.availableVariables`),不支持根级扁平自定义字段;前端按 key 合并对象、数组"后者覆盖"。
|
|
861
|
+
- `permissions.editable: false` 时设计器进入只读模式(UI 与 Store/API 双层拦截)。
|
|
862
|
+
|
|
863
|
+
---
|
|
864
|
+
|
|
865
|
+
## 工具函数(v0.4.39+ 新增)
|
|
866
|
+
|
|
867
|
+
为了降低宿主接入门槛,插件提供了开箱即用的工具函数,**将常见配置从数百行简化到几行**。
|
|
868
|
+
|
|
869
|
+
### 1. CRUD 适配器工厂 `createRestfulAdapter`
|
|
870
|
+
|
|
871
|
+
**问题:** 宿主需要写 80+ 行 `fetcher` 逻辑来适配后端 API。
|
|
872
|
+
|
|
873
|
+
**解决:** 使用适配器工厂,**从 80 行减少到 10 行**。
|
|
874
|
+
|
|
875
|
+
```ts
|
|
876
|
+
import { createRestfulAdapter, adapterPresets } from 'x-print-designer'
|
|
877
|
+
|
|
878
|
+
// 方式一:使用预设(Spring Boot / Laravel / Express)
|
|
879
|
+
const adapter = createRestfulAdapter({
|
|
880
|
+
...adapterPresets.springBoot('/api'), // 自动配置端点路径
|
|
881
|
+
transform: {
|
|
882
|
+
request: (data) => ({
|
|
883
|
+
id: data.id,
|
|
884
|
+
name: data.name,
|
|
885
|
+
templateJson: JSON.stringify(data.data)
|
|
886
|
+
}),
|
|
887
|
+
response: (res) => res.records.map(r => ({
|
|
888
|
+
id: r.id,
|
|
889
|
+
name: r.name,
|
|
890
|
+
data: JSON.parse(r.templateJson)
|
|
891
|
+
}))
|
|
892
|
+
},
|
|
893
|
+
onSave: async (data) => {
|
|
894
|
+
await editTemplate(data)
|
|
895
|
+
ElMessage.success('保存成功')
|
|
896
|
+
}
|
|
897
|
+
})
|
|
898
|
+
|
|
899
|
+
// 方式二:自定义端点路径
|
|
900
|
+
const adapter = createRestfulAdapter({
|
|
901
|
+
baseURL: '/backend',
|
|
902
|
+
endpoints: {
|
|
903
|
+
list: '/report/template/list',
|
|
904
|
+
get: '/report/template/detail/:id', // 支持 :id 占位符
|
|
905
|
+
upsert: '/report/template/edit',
|
|
906
|
+
delete: '/report/template/delete/:id'
|
|
907
|
+
},
|
|
908
|
+
transform: { /* ... */ },
|
|
909
|
+
onSave: async (data) => { /* ... */ },
|
|
910
|
+
onError: (error, operation) => {
|
|
911
|
+
console.error(`Operation ${operation} failed:`, error)
|
|
912
|
+
}
|
|
913
|
+
})
|
|
914
|
+
|
|
915
|
+
// 应用到设计器
|
|
916
|
+
designerEl.setCrudEndpoints(adapter.endpoints, { fetcher: adapter.fetcher })
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
**API:**
|
|
920
|
+
|
|
921
|
+
```ts
|
|
922
|
+
interface RestfulAdapterConfig {
|
|
923
|
+
baseURL: string
|
|
924
|
+
endpoints: {
|
|
925
|
+
list: string
|
|
926
|
+
get: string // 支持 :id 占位符
|
|
927
|
+
upsert: string
|
|
928
|
+
delete?: string
|
|
929
|
+
}
|
|
930
|
+
transform?: {
|
|
931
|
+
request?: (data: any) => any // 请求数据转换(保存时)
|
|
932
|
+
response?: (data: any) => any // 响应数据转换(列表/详情)
|
|
933
|
+
}
|
|
934
|
+
onSave?: (data: any) => void | Promise<void>
|
|
935
|
+
onDelete?: (id: string) => void | Promise<void>
|
|
936
|
+
headers?: Record<string, string>
|
|
937
|
+
onError?: (error: Error, operation: 'list' | 'get' | 'upsert' | 'delete') => void
|
|
938
|
+
}
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
### 2. 字段字典工具 `buildFieldDictionary`
|
|
942
|
+
|
|
943
|
+
**问题:** 需要手动构建 40+ 行字典映射逻辑,处理字段冲突、模块前缀等。
|
|
944
|
+
|
|
945
|
+
**解决:** 使用工具函数,**从 40 行减少到 3 行**。
|
|
946
|
+
|
|
947
|
+
```ts
|
|
948
|
+
import { buildFieldDictionary } from 'x-print-designer'
|
|
949
|
+
|
|
950
|
+
// 获取字段列表
|
|
951
|
+
const modules = await getReportTemplateFields(['media', 'device', 'wayline', 'task', 'event'])
|
|
952
|
+
|
|
953
|
+
// 一行构建字典(自动处理字段名冲突)
|
|
954
|
+
const dictionary = buildFieldDictionary(modules, {
|
|
955
|
+
autoResolveConflicts: true, // 自动添加模块前缀解决冲突
|
|
956
|
+
prefixStrategy: 'auto', // 'auto' | 'always' | 'never'
|
|
957
|
+
prefixSeparator: '.', // 分隔符
|
|
958
|
+
filter: (field, module) => field.fieldType !== 'internal' // 可选过滤器
|
|
959
|
+
})
|
|
960
|
+
|
|
961
|
+
// 应用到设计器
|
|
962
|
+
designerEl.setFieldDictionary(dictionary)
|
|
963
|
+
```
|
|
964
|
+
|
|
965
|
+
**示例输入/输出:**
|
|
966
|
+
|
|
967
|
+
```ts
|
|
968
|
+
// 输入
|
|
969
|
+
const modules = [
|
|
970
|
+
{
|
|
971
|
+
moduleKey: 'device',
|
|
972
|
+
moduleName: '设备信息',
|
|
973
|
+
fields: [
|
|
974
|
+
{ fieldKey: 'id', labelCn: '设备ID', primaryKey: true },
|
|
975
|
+
{ fieldKey: 'name', labelCn: '设备名称' }
|
|
976
|
+
]
|
|
977
|
+
},
|
|
978
|
+
{
|
|
979
|
+
moduleKey: 'task',
|
|
980
|
+
moduleName: '任务信息',
|
|
981
|
+
fields: [
|
|
982
|
+
{ fieldKey: 'id', labelCn: '任务ID', primaryKey: true },
|
|
983
|
+
{ fieldKey: 'name', labelCn: '任务名称' }
|
|
984
|
+
]
|
|
985
|
+
}
|
|
986
|
+
]
|
|
987
|
+
|
|
988
|
+
// 输出(自动解决 id 和 name 字段冲突)
|
|
989
|
+
{
|
|
990
|
+
'device.id': { fieldKey: 'id', labelCn: '设备ID', moduleKey: 'device', ... },
|
|
991
|
+
'device.name': { fieldKey: 'name', labelCn: '设备名称', moduleKey: 'device', ... },
|
|
992
|
+
'task.id': { fieldKey: 'id', labelCn: '任务ID', moduleKey: 'task', ... },
|
|
993
|
+
'task.name': { fieldKey: 'name', labelCn: '任务名称', moduleKey: 'task', ... }
|
|
994
|
+
}
|
|
995
|
+
```
|
|
996
|
+
|
|
997
|
+
**辅助函数:**
|
|
998
|
+
|
|
999
|
+
```ts
|
|
1000
|
+
import { mergeFieldDictionaries, getFieldLabel } from 'x-print-designer'
|
|
1001
|
+
|
|
1002
|
+
// 合并多个字典
|
|
1003
|
+
const merged = mergeFieldDictionaries([dict1, dict2, dict3], 'last') // 'first' | 'last'
|
|
1004
|
+
|
|
1005
|
+
// 根据语言获取标签
|
|
1006
|
+
const label = getFieldLabel(dictionary['device.name'], 'zh') // '设备名称'
|
|
1007
|
+
```
|
|
1008
|
+
|
|
1009
|
+
### 3. 自动样式注入
|
|
1010
|
+
|
|
1011
|
+
**问题:** 宿主需要手动列举并注入 287 行 Tailwind/UnoCSS 样式类。
|
|
1012
|
+
|
|
1013
|
+
**解决:** 插件自动检测并注入,**0 行代码**。
|
|
1014
|
+
|
|
1015
|
+
```vue
|
|
1016
|
+
<template>
|
|
1017
|
+
<!-- ✅ 无需任何样式注入代码,插件自动检测 Tailwind/UnoCSS/Element Plus 并注入 -->
|
|
1018
|
+
<print-designer />
|
|
1019
|
+
</template>
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
**原理:** 插件在 `connectedCallback` 时自动扫描 `document.querySelectorAll('style')`,智能检测主流 CSS 框架并克隆到 Shadow DOM。
|
|
1023
|
+
|
|
1024
|
+
**可选:禁用自动注入**
|
|
1025
|
+
|
|
1026
|
+
```html
|
|
1027
|
+
<print-designer no-auto-style></print-designer>
|
|
1028
|
+
```
|
|
1029
|
+
|
|
1030
|
+
### 4. 配置预设 `presets`
|
|
1031
|
+
|
|
1032
|
+
**问题:** 每次都要从头配置模式、UI、主题等基础设置。
|
|
1033
|
+
|
|
1034
|
+
**解决:** 使用场景预设,**1 行配置搞定**。
|
|
1035
|
+
|
|
1036
|
+
```ts
|
|
1037
|
+
import { presets, mergePreset } from 'x-print-designer'
|
|
1038
|
+
|
|
1039
|
+
// 方式一:直接使用预设
|
|
1040
|
+
const config = {
|
|
1041
|
+
...presets.uavReport, // 无人机报告场景
|
|
1042
|
+
onSave: handleSave
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// 方式二:合并预设和自定义配置
|
|
1046
|
+
const config = mergePreset(presets.expressLabel, {
|
|
1047
|
+
branding: { title: '我的快递单设计器' },
|
|
1048
|
+
autoSave: { enabled: false }
|
|
1049
|
+
})
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
**可用预设:**
|
|
1053
|
+
|
|
1054
|
+
| 预设名 | 场景 | 特点 |
|
|
1055
|
+
|--------|------|------|
|
|
1056
|
+
| `presets.uavReport` | 无人机报告 | report 模式 + beginner UI + 30s 自动保存 |
|
|
1057
|
+
| `presets.expressLabel` | 物流快递单 | template 模式 + expert UI + 60s 自动保存 |
|
|
1058
|
+
| `presets.invoice` | 发票/票据 | template 模式 + expert UI + 60s 自动保存 |
|
|
1059
|
+
| `presets.label` | 标签/吊牌 | template 模式 + beginner UI + 60s 自动保存 |
|
|
1060
|
+
| `presets.certificate` | 证书/证明 | template 模式 + beginner UI + 不自动保存 |
|
|
1061
|
+
| `presets.contract` | 合同/协议 | template 模式 + expert UI + 30s 自动保存 |
|
|
1062
|
+
| `presets.generalReport` | 通用报告 | report 模式 + beginner UI + 30s 自动保存 |
|
|
1063
|
+
|
|
1064
|
+
### 5. Vue Composable `useDesigner`
|
|
1065
|
+
|
|
1066
|
+
**问题:** Vue 宿主仍需手动管理 ref、ready、初始化等。
|
|
1067
|
+
|
|
1068
|
+
**解决:** 使用 composable,**自动处理一切**。
|
|
1069
|
+
|
|
1070
|
+
```vue
|
|
1071
|
+
<template>
|
|
1072
|
+
<print-designer ref="designerRef" />
|
|
1073
|
+
</template>
|
|
1074
|
+
|
|
1075
|
+
<script setup lang="ts">
|
|
1076
|
+
import { useDesigner, createRestfulAdapter } from 'x-print-designer/vue'
|
|
1077
|
+
|
|
1078
|
+
const { designerRef, ready, setData, exportPdf } = useDesigner({
|
|
1079
|
+
mode: 'template',
|
|
1080
|
+
crudAdapter: createRestfulAdapter({
|
|
1081
|
+
baseURL: '/api',
|
|
1082
|
+
endpoints: { list: '/templates', get: '/templates/:id', upsert: '/templates' },
|
|
1083
|
+
onSave: async (data) => { await saveTemplate(data) }
|
|
1084
|
+
}),
|
|
1085
|
+
fieldModules: ['media', 'device'],
|
|
1086
|
+
fieldLoader: async (modules) => {
|
|
1087
|
+
return await getReportTemplateFields(modules)
|
|
1088
|
+
},
|
|
1089
|
+
onError: (error) => {
|
|
1090
|
+
ElMessage.error('设计器错误: ' + error.message)
|
|
1091
|
+
}
|
|
1092
|
+
})
|
|
1093
|
+
|
|
1094
|
+
// ✅ 使用非常简单
|
|
1095
|
+
const handleExport = async () => {
|
|
1096
|
+
await exportPdf('我的模板.pdf')
|
|
1097
|
+
}
|
|
1098
|
+
</script>
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
**API:**
|
|
1102
|
+
|
|
1103
|
+
```ts
|
|
1104
|
+
interface UseDesignerOptions {
|
|
1105
|
+
mode?: 'template' | 'report'
|
|
1106
|
+
uiMode?: 'beginner' | 'expert'
|
|
1107
|
+
crudAdapter?: ReturnType<typeof createRestfulAdapter>
|
|
1108
|
+
fieldModules?: string[] // 自动加载字段字典
|
|
1109
|
+
fieldLoader?: (modules: string[]) => Promise<FieldDictionaryModule[]>
|
|
1110
|
+
onSave?: (data: any) => void | Promise<void>
|
|
1111
|
+
onError?: (error: any) => void
|
|
1112
|
+
autoInit?: boolean // 默认 true
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
interface UseDesignerReturn {
|
|
1116
|
+
designerRef: Ref<any>
|
|
1117
|
+
ready: Ref<boolean>
|
|
1118
|
+
setData: (data: any) => void
|
|
1119
|
+
setTestData: (data: any) => void
|
|
1120
|
+
setVariables: (data: any) => void
|
|
1121
|
+
exportPdf: (filename?: string) => Promise<void>
|
|
1122
|
+
exportImage: (filename?: string) => Promise<void>
|
|
1123
|
+
print: (request?: any) => Promise<void>
|
|
1124
|
+
getDesignerElement: () => any
|
|
1125
|
+
init: () => Promise<void>
|
|
1126
|
+
}
|
|
1127
|
+
```
|
|
1128
|
+
|
|
1129
|
+
### 6. 诊断工具 `diagnoseIntegration`
|
|
1130
|
+
|
|
1131
|
+
**问题:** 集成出问题时不知道哪里配置错了。
|
|
1132
|
+
|
|
1133
|
+
**解决:** 使用诊断工具,**自动检查 12 项配置**。
|
|
1134
|
+
|
|
1135
|
+
```ts
|
|
1136
|
+
import { diagnoseIntegration, exportDiagnosticReport } from 'x-print-designer/diagnostics'
|
|
1137
|
+
|
|
1138
|
+
// 运行诊断
|
|
1139
|
+
const diagnosis = diagnoseIntegration(designerEl)
|
|
1140
|
+
|
|
1141
|
+
if (!diagnosis.allPassed) {
|
|
1142
|
+
console.warn('集成检查未通过:', diagnosis.failedChecks)
|
|
1143
|
+
|
|
1144
|
+
// 导出诊断报告
|
|
1145
|
+
exportDiagnosticReport(diagnosis, 'my-diagnostic.md')
|
|
1146
|
+
}
|
|
1147
|
+
```
|
|
1148
|
+
|
|
1149
|
+
**检查项:**
|
|
1150
|
+
|
|
1151
|
+
| 检查项 | 级别 | 说明 |
|
|
1152
|
+
|--------|------|------|
|
|
1153
|
+
| Element Exists | 🔴 关键 | 元素是否存在 |
|
|
1154
|
+
| Ready State | 🔴 关键 | 是否已就绪 |
|
|
1155
|
+
| Shadow DOM | 🔴 关键 | Shadow DOM 是否初始化 |
|
|
1156
|
+
| Styles Injected | 🟡 警告 | 样式是否注入 |
|
|
1157
|
+
| CRUD Configured | 🟡 警告 | CRUD 是否配置 |
|
|
1158
|
+
| Save Handler | 🟡 警告 | 保存回调是否配置 |
|
|
1159
|
+
| Data API | ℹ️ 信息 | 数据 API 是否可用 |
|
|
1160
|
+
| Print API | ℹ️ 信息 | 打印 API 是否可用 |
|
|
1161
|
+
| Export API | ℹ️ 信息 | 导出 API 是否可用 |
|
|
1162
|
+
| Monaco Loader | ℹ️ 信息 | 代码编辑器是否可用 |
|
|
1163
|
+
| Field Dictionary | ℹ️ 信息 | 字段字典是否配置 |
|
|
1164
|
+
| Auto Style Injection | ℹ️ 信息 | 自动样式注入是否生效 |
|
|
1165
|
+
|
|
1166
|
+
**诊断结果示例:**
|
|
1167
|
+
|
|
1168
|
+
```
|
|
1169
|
+
✅ [PrintDesigner] 集成检查全部通过
|
|
1170
|
+
|
|
1171
|
+
或
|
|
1172
|
+
|
|
1173
|
+
❌ 关键问题: Ready State, Shadow DOM
|
|
1174
|
+
- Ready State: 设计器尚未就绪,请等待 ready 事件
|
|
1175
|
+
- Shadow DOM: Shadow DOM 未初始化
|
|
1176
|
+
|
|
1177
|
+
⚠️ 警告: Save Handler
|
|
1178
|
+
- Save Handler: 未配置 onSave 回调,保存功能将不可用
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
---
|
|
1182
|
+
|
|
493
1183
|
## 传入数据格式
|
|
494
1184
|
|
|
495
1185
|
### 基本要求
|
|
496
1186
|
|
|
497
|
-
`
|
|
1187
|
+
`config.testData` / `setTestData(data)` 接收一个普通的 JSON 对象。**唯一必填字段是 `id`**。
|
|
498
1188
|
|
|
499
1189
|
| 字段 | 类型 | 必填 | 说明 |
|
|
500
1190
|
|------|------|------|------|
|
|
@@ -772,18 +1462,28 @@ interface SavePayload {
|
|
|
772
1462
|
|
|
773
1463
|
```vue
|
|
774
1464
|
<template>
|
|
775
|
-
<
|
|
1465
|
+
<XPrintDesigner
|
|
776
1466
|
:mode="mode"
|
|
777
|
-
:
|
|
778
|
-
|
|
1467
|
+
:config="{
|
|
1468
|
+
testData: sampleData,
|
|
1469
|
+
onSave: handleSave
|
|
1470
|
+
}"
|
|
779
1471
|
/>
|
|
780
1472
|
</template>
|
|
781
1473
|
|
|
782
1474
|
<script setup lang="ts">
|
|
783
1475
|
import { ref } from 'vue'
|
|
1476
|
+
import { XPrintDesigner } from 'x-print-designer'
|
|
1477
|
+
import 'x-print-designer/style.css'
|
|
784
1478
|
|
|
785
1479
|
const mode = ref<'template' | 'report'>('template')
|
|
786
1480
|
|
|
1481
|
+
const sampleData = ref({
|
|
1482
|
+
id: 1,
|
|
1483
|
+
title: '报告标题',
|
|
1484
|
+
items: [{ name: '产品A', price: 100 }]
|
|
1485
|
+
})
|
|
1486
|
+
|
|
787
1487
|
const handleSave = async (payload) => {
|
|
788
1488
|
// 根据模式决定 API 端点
|
|
789
1489
|
const endpoint = mode.value === 'template'
|
|
@@ -817,17 +1517,19 @@ const handleSave = async (payload) => {
|
|
|
817
1517
|
|
|
818
1518
|
### Vue 组件
|
|
819
1519
|
|
|
820
|
-
|
|
1520
|
+
自动保存通过 `config.autoSave` 配置(`XPrintDesigner` 无独立 props,统一走 `config`):
|
|
1521
|
+
|
|
1522
|
+
| 字段 | 类型 | 默认值 | 说明 |
|
|
821
1523
|
|------|------|--------|------|
|
|
822
|
-
| `
|
|
823
|
-
| `
|
|
1524
|
+
| `autoSave.enabled` | `boolean` | `false` | 是否开启 |
|
|
1525
|
+
| `autoSave.intervalMs` | `number` | `120000` | 间隔(毫秒),默认 2 分钟 |
|
|
824
1526
|
|
|
825
1527
|
```vue
|
|
826
|
-
<
|
|
827
|
-
:
|
|
828
|
-
:
|
|
829
|
-
:
|
|
830
|
-
/>
|
|
1528
|
+
<XPrintDesigner :config="{
|
|
1529
|
+
testData: sampleData,
|
|
1530
|
+
onSave: handleSave,
|
|
1531
|
+
autoSave: { enabled: true, intervalMs: 120000 }
|
|
1532
|
+
}" />
|
|
831
1533
|
```
|
|
832
1534
|
|
|
833
1535
|
### Web Component
|
|
@@ -1056,6 +1758,7 @@ await designer.export({ type: 'html', filename: '订单-2024' })
|
|
|
1056
1758
|
| 线条 | `line` | 直线(支持实线/虚线/点线) |
|
|
1057
1759
|
| 矩形 | `rect` | 矩形(支持圆角) |
|
|
1058
1760
|
| 圆形 | `circle` | 圆形 |
|
|
1761
|
+
| 组合 | `group` | 多个元素的组合容器,作为整体移动/缩放/复制/删除(见[组合与取消组合](#组合与取消组合)) |
|
|
1059
1762
|
|
|
1060
1763
|
---
|
|
1061
1764
|
|
|
@@ -1153,9 +1856,9 @@ await designer.export({ type: 'html', filename: '订单-2024' })
|
|
|
1153
1856
|
**原因:** 自动保存需要 `onSave` 回调。
|
|
1154
1857
|
|
|
1155
1858
|
**排查步骤:**
|
|
1156
|
-
1. 确认同时传入了 `onSave` 和 `
|
|
1859
|
+
1. 确认同时传入了 `onSave` 和 `config.autoSave = { enabled: true }`(Vue 组件)或调用了 `setAutoSave(true)`
|
|
1157
1860
|
2. 自动保存仅在内容有变更时触发,未修改时不会保存
|
|
1158
|
-
3. 检查 `
|
|
1861
|
+
3. 检查 `autoSave.intervalMs` / `setAutoSave` 的间隔是否设置过短(频繁保存可能被节流)
|
|
1159
1862
|
|
|
1160
1863
|
---
|
|
1161
1864
|
|