x-print-designer 0.4.13 → 0.4.28

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 CHANGED
@@ -9,11 +9,17 @@
9
9
  - [前置依赖要求](#前置依赖要求)
10
10
  - [快速开始](#快速开始)
11
11
  - [源码开发](#源码开发)
12
- - [Vue 组件接入](#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,44 +79,172 @@ pnpm build:wc
73
79
 
74
80
  启动后浏览器访问 `http://localhost:5173` 即可看到设计器 Demo 页面(`index.html`)。
75
81
 
76
- ### Vue 组件接入
82
+ ### Vue 组件接入(XPrintDesigner)
77
83
 
78
- **适用于:** 将设计器作为子组件嵌入已有 Vue 3 项目。
84
+ **适用于:** Vue 3 项目以一等公民组件方式接入。`XPrintDesigner` 是 Web Component 的 Vue 适配层:props 声明式传参、事件自动透传、ref 暴露常用方法,**无需手动管理 `ready` 时序**。
79
85
 
80
86
  ```vue
81
87
  <template>
82
- <PrintDesigner
83
- mode="template"
84
- :sample-data="sampleData"
85
- :on-save="handleSave"
88
+ <XPrintDesigner
89
+ :config="config"
90
+ @checklist="onChecklist"
91
+ @printed="onPrinted"
86
92
  />
87
93
  </template>
88
94
 
89
95
  <script setup lang="ts">
90
96
  import { ref } from 'vue'
91
- import { PrintDesigner } from 'x-print-designer'
97
+ import { XPrintDesigner } from 'x-print-designer'
92
98
  import 'x-print-designer/style.css'
93
99
 
94
- const sampleData = ref({
95
- id: 1,
96
- title: '报告标题',
97
- customerName: '张三',
98
- items: [
99
- { name: '产品A', price: 100, quantity: 2 }
100
- ]
100
+ const config = ref({
101
+ mode: 'template',
102
+ testData: { id: 1, title: '报告标题', items: [{ name: '产品A', price: 100 }] },
103
+ onSave: async (payload) => {
104
+ await fetch('/api/templates/save', {
105
+ method: 'POST',
106
+ headers: { 'Content-Type': 'application/json' },
107
+ body: JSON.stringify(payload)
108
+ })
109
+ }
101
110
  })
102
111
 
103
- const handleSave = async (payload) => {
104
- // payload: { id, name, data, isNew }
105
- await fetch('/api/templates/save', {
106
- method: 'POST',
107
- headers: { 'Content-Type': 'application/json' },
108
- body: JSON.stringify(payload)
109
- })
112
+ const onChecklist = (detail) => {
113
+ console.log('接入自检', detail.checks, '全部通过:', detail.allPassed)
114
+ }
115
+ const onPrinted = (detail) => {
116
+ console.log('打印完成', detail)
110
117
  }
111
118
  </script>
112
119
  ```
113
120
 
121
+ **Props:**
122
+
123
+ | 属性 | 类型 | 说明 |
124
+ |------|------|------|
125
+ | `config` | `DesignerInitConfig \| null` | 统一初始化配置(等价 `el.init(config)`),ready 前传入会自动排队 |
126
+ | `mode` | `'template' \| 'report'` | 工作模式 |
127
+ | `uiMode` | `'beginner' \| 'expert'` | 界面模式(简洁/完整) |
128
+ | `viewMode` | `'edit' \| 'preview'` | 视图模式 |
129
+ | `headless` | `boolean` | 无头模式 |
130
+ | `onSave` | `(payload) => void \| Promise<void>` | 保存回调(映射 `setOnSave`) |
131
+ | `theme` | `string` | 主题(映射 `setTheme`) |
132
+
133
+ **事件:** `ready` / `print` / `printed` / `export` / `exported` / `error` / `checklist`,与 Web Component 同名事件一一对应。
134
+
135
+ **ref 暴露方法:** `init(config)`、`setData(payload, options)`、`print(request)`、`export(request)`、`setOnSave(handler)`、`setPreviewMode(options)`。
136
+
137
+ > 更完整的 API(`setTemplateLibrary`、`setCrudEndpoints`、`setVariables` 等)可通过 `ref.value.$el` 拿到底层 `<print-designer>` 元素后直接调用,或使用下方 Web Component 方式。
138
+
139
+ ### 5 分钟接入(推荐)
140
+
141
+ **适用于:** 想用最少代码快速接入的宿主(任意前端技术栈)。
142
+
143
+ 只需 4 步、**≤10 行代码**,且**无需手动监听 `ready` 时序**:
144
+
145
+ ```html
146
+ <!DOCTYPE html>
147
+ <html lang="zh-CN">
148
+ <head>
149
+ <meta charset="UTF-8" />
150
+ <!-- ① 引入脚本与样式 -->
151
+ <script type="module" src="/dist/print-designer.es.js"></script>
152
+ <link rel="stylesheet" href="/dist/print-designer.css" />
153
+ </head>
154
+ <body>
155
+ <!-- ② 放置组件标签 -->
156
+ <print-designer id="designer"></print-designer>
157
+
158
+ <script>
159
+ // ③ 一次性传入全部配置(init 方法通道)
160
+ const designer = document.getElementById('designer')
161
+ designer.init({
162
+ mode: 'template',
163
+ testData: { id: 1, title: '报告标题', items: [{ name: '产品A', price: 100 }] },
164
+ onSave: (payload) => {
165
+ fetch('/api/templates/save', {
166
+ method: 'POST',
167
+ headers: { 'Content-Type': 'application/json' },
168
+ body: JSON.stringify(payload)
169
+ })
170
+ }
171
+ })
172
+
173
+ // ④ 可选:监听接入自检结果
174
+ designer.addEventListener('checklist', (e) => {
175
+ console.log('接入自检', e.detail.checks, '全部通过:', e.detail.allPassed)
176
+ })
177
+ </script>
178
+ </body>
179
+ </html>
180
+ ```
181
+
182
+ **方法通道 vs 属性通道(二选一):**
183
+
184
+ - **方法通道(推荐)**:直接调用 `el.init(config)`。`ready` 前调用会自动排队,`ready` 后自动应用,无需自己等事件。
185
+ - **属性通道**:通过 `config` 属性声明式传入 JSON,效果等价:
186
+
187
+ ```html
188
+ <print-designer
189
+ config='{"mode":"template","testData":{"id":1,"title":"报告标题"},"onSave":null}'
190
+ ></print-designer>
191
+ ```
192
+
193
+ > 若 `config` 属性 JSON 解析失败,组件会派发 `error` 事件(`detail.scope === 'config'`)且不影响默认 UI。
194
+
195
+ **接入自检 `checklist` 事件:** 组件在 `ready` 时自动派发,`detail` 形如 `{ checks: [{ name, passed, hint? }...], allPassed }`,检查项包括 `ready`、`dataChannel`、`printApi`、`exportApi`、`onSaveBound`、`monacoLoader`。若存在未通过项,控制台会出现 `[PrintDesigner] 接入自检未全部通过:...` 的 `warn` 提示(例如未配置 `onSave` 时 `onSaveBound` 为 `false`),便于快速定位遗漏。
196
+
197
+ #### `init(config)` 完整配置字段
198
+
199
+ `DesignerInitConfig` 一次调用即可完成全部初始化;`ready` 前调用自动排队,`ready` 后立即应用。**显式调用过的 setter 优先级高于 init 配置**(不会双写冲突)。
200
+
201
+ | 字段 | 类型 | 说明 |
202
+ |------|------|------|
203
+ | `mode` | `'template' \| 'report'` | 工作模式 |
204
+ | `viewMode` | `'edit' \| 'preview'` | 视图模式 |
205
+ | `uiMode` | `'beginner' \| 'expert' \| null` | 界面模式(简洁/完整) |
206
+ | `language` | `string` | 语言(`'zh'` / `'en'`) |
207
+ | `headless` | `boolean` | 无头模式 |
208
+ | `data` | `Record<string, any>` | 数据(走 `setData` 默认通道) |
209
+ | `testData` | `Record<string, any>` | 测试/预览数据 |
210
+ | `variables` | `Record<string, any>` | 运行时变量(打印/导出时覆盖 testData) |
211
+ | `onSave` | `(payload) => void \| Promise<void> \| null` | 保存回调 |
212
+ | `autoSave` | `{ enabled, intervalMs? }` | 自动保存 |
213
+ | `fieldDictionary` | `DesignerFieldDictionary \| null` | 字段字典(变量面板中文标签) |
214
+ | `textPolishHandler` | `(text) => Promise<string> \| null` | 文本润色处理器(宿主 AI 能力) |
215
+ | `textPolishConfig` | `TextPolishConfig \| null` | 内置 DeepSeek 润色配置 |
216
+ | `branding` | `{ title?, logoUrl?, showTitle?, showLogo? }` | 品牌标识 |
217
+ | `theme` | `string` | 主题(`'light'` / `'dark'` / `'system'`) |
218
+ | `printDefaults` | `DesignerPrintDefaults` | 默认打印/导出选项与连接设置 |
219
+ | `previewMode` | `DesignerPreviewModeOptions` | 独立预览模式 |
220
+ | `crud` | `{ mode?, endpoints?, config? }` | 远程 CRUD 配置(见[远程 CRUD 配置](#远程-crud-配置)) |
221
+
222
+ **Vue 宿主 5 分钟接入(XPrintDesigner):** 用法与 Web Component 完全等价,仅换为组件写法:
223
+
224
+ ```vue
225
+ <template>
226
+ <XPrintDesigner :config="config" />
227
+ </template>
228
+
229
+ <script setup lang="ts">
230
+ import { ref } from 'vue'
231
+ import { XPrintDesigner } from 'x-print-designer'
232
+ import 'x-print-designer/style.css'
233
+
234
+ const config = ref({
235
+ mode: 'template',
236
+ testData: { id: 1, title: '报告标题', items: [{ name: '产品A', price: 100 }] },
237
+ onSave: async (payload) => {
238
+ await fetch('/api/templates/save', {
239
+ method: 'POST',
240
+ headers: { 'Content-Type': 'application/json' },
241
+ body: JSON.stringify(payload)
242
+ })
243
+ }
244
+ })
245
+ </script>
246
+ ```
247
+
114
248
  ### Web Component 接入
115
249
 
116
250
  **适用于:** 任意前端技术栈(React / Angular / 原生 HTML),以自定义标签方式嵌入。
@@ -242,9 +376,9 @@ designer.print({ mode: 'browser', options: { silent: true } })
242
376
  ### 切换模式
243
377
 
244
378
  ```vue
245
- <!-- Vue 组件 -->
246
- <PrintDesigner mode="template" /> <!-- 模板模式(默认) -->
247
- <PrintDesigner mode="report" /> <!-- 报告模式 -->
379
+ <!-- Vue 组件(XPrintDesigner) -->
380
+ <XPrintDesigner mode="template" /> <!-- 模板模式(默认) -->
381
+ <XPrintDesigner mode="report" /> <!-- 报告模式 -->
248
382
  ```
249
383
 
250
384
  ```javascript
@@ -259,63 +393,72 @@ designer.setMode('report')
259
393
 
260
394
  ### 组件 Props
261
395
 
396
+ `XPrintDesigner` 是 Web Component 的 Vue 适配层(从 `x-print-designer` 导出)。除下表 props 外,其余配置(数据、CRUD、品牌、主题等)统一通过 `config` 传入。
397
+
262
398
  | 属性 | 类型 | 默认值 | 说明 |
263
399
  |------|------|--------|------|
264
- | `mode` | `'template'` \| `'report'` | `'template'` | 工作模式,影响保存行为 |
265
- | `view-mode` | `'edit'` \| `'preview'` | `'edit'` | 视图模式,`'preview'` 时只渲染打印内容 |
400
+ | `config` | `DesignerInitConfig \| null` | `null` | 统一初始化配置(等价 `el.init(config)`),见[init 完整配置](#initconfig-完整配置字段) |
401
+ | `mode` | `'template'` \| `'report'` | — | 工作模式,影响保存行为 |
402
+ | `uiMode` | `'beginner'` \| `'expert'` | — | 界面模式(简洁/完整) |
403
+ | `viewMode` | `'edit'` \| `'preview'` | — | 视图模式,`'preview'` 时只渲染打印内容 |
266
404
  | `headless` | `boolean` | `false` | 无头模式,隐藏顶部工具栏和侧边栏 |
267
- | `sample-data` | `Record<string, any>` | — | 传入的示例/预览数据,详见[传入数据格式](#传入数据格式) |
268
- | `on-save` | `(payload) => void \| Promise<void>` | — | 保存回调,详见[保存回调](#保存回调-onsave) |
269
- | `auto-save-enabled` | `boolean` | `false` | 是否开启定时自动保存 |
270
- | `auto-save-interval-ms` | `number` | `120000` | 自动保存间隔(毫秒),默认 2 分钟 |
405
+ | `onSave` | `(payload) => void \| Promise<void>` | — | 保存回调,详见[保存回调](#保存回调-onsave)(映射 `setOnSave`) |
406
+ | `theme` | `string` | — | 主题(映射 `setTheme`) |
271
407
 
272
408
  ### Props 详细说明
273
409
 
274
- #### `mode`
410
+ #### `config`
275
411
 
276
- 工作模式,决定保存时是否附带 `testData`。
412
+ **宿主接入的首选入口**:所有配置(数据、保存、CRUD、品牌、主题等)一次传入,等价于调用 `el.init(config)`。`ready` 前传入自动排队,`ready` 后自动应用。
277
413
 
278
414
  ```vue
279
- <PrintDesigner mode="report" />
415
+ <XPrintDesigner :config="{
416
+ mode: 'report',
417
+ testData: { id: 1, title: '检测报告', items: [{ name: '产品A', price: 100 }] },
418
+ onSave: async (payload) => { await fetch('/api/reports/save', { method: 'POST', body: JSON.stringify(payload) }) },
419
+ autoSave: { enabled: true, intervalMs: 120000 },
420
+ crud: { mode: 'remote', endpoints: { /* ... */ } }
421
+ }" />
280
422
  ```
281
423
 
282
- #### `view-mode`
424
+ #### `mode`
283
425
 
284
- 切换编辑/预览视图。设为 `'preview'` 时隐藏所有编辑器 UI,仅渲染打印页面。适用于嵌入第三方页面时只展示打印效果。
426
+ 工作模式,决定保存时是否附带 `testData`。
285
427
 
286
428
  ```vue
287
- <PrintDesigner view-mode="preview" />
429
+ <XPrintDesigner mode="report" />
288
430
  ```
289
431
 
290
- #### `headless`
432
+ #### `viewMode`
291
433
 
292
- 无头模式,隐藏顶部工具栏和侧边属性面板。用于嵌入式场景仅保留画布区域。
434
+ 切换编辑/预览视图。设为 `'preview'` 时隐藏所有编辑器 UI,仅渲染打印页面。适用于嵌入第三方页面时只展示打印效果。
293
435
 
294
436
  ```vue
295
- <PrintDesigner headless />
437
+ <XPrintDesigner view-mode="preview" />
296
438
  ```
297
439
 
298
- #### `sample-data`
440
+ #### `headless`
299
441
 
300
- 传入设计器的预览数据。**最关键的属性**。详见 [传入数据格式](#传入数据格式)。
442
+ 无头模式,隐藏顶部工具栏和侧边属性面板。用于嵌入式场景仅保留画布区域。
301
443
 
302
444
  ```vue
303
- <PrintDesigner :sample-data="{ id: 1, title: '报告', items: [...] }" />
445
+ <XPrintDesigner headless />
304
446
  ```
305
447
 
306
- #### `on-save`
448
+ #### `onSave`
307
449
 
308
- 保存回调函数。传入后设计器**不再执行内置保存逻辑**,完全由用户自行处理持久化。
450
+ 保存回调函数。传入后设计器**不再执行内置保存逻辑**,完全由用户自行处理持久化。也可通过 `config.onSave` 传入,效果一致。
309
451
 
310
452
  ```vue
311
- <PrintDesigner :on-save="(payload) => mySaveApi(payload)" />
453
+ <XPrintDesigner :on-save="(payload) => mySaveApi(payload)" />
312
454
  ```
313
455
 
314
456
  > 不传 `onSave` 时,设计器使用内置 CRUD 逻辑(根据配置调远程接口或存 localStorage)。
315
457
 
316
- #### `auto-save-enabled` / `auto-save-interval-ms`
458
+ #### 预览数据与自动保存
317
459
 
318
- 详见 [自动保存配置](#自动保存配置)。
460
+ - 预览数据:通过 `config.testData`(等价 Web Component 的 `setTestData`,详见 [传入数据格式](#传入数据格式))。
461
+ - 自动保存:通过 `config.autoSave = { enabled, intervalMs? }` 开启(详见 [自动保存配置](#自动保存配置))。
319
462
 
320
463
  ---
321
464
 
@@ -343,15 +486,29 @@ designer.setMode('report')
343
486
 
344
487
  | 方法 | 参数 | 返回值 | 说明 |
345
488
  |------|------|--------|------|
489
+ | `init(config)` | `DesignerInitConfig` | `void` | 统一初始化入口(见[init 完整配置](#initconfig-完整配置字段))。ready 前调用自动排队,ready 后自动应用 |
346
490
  | `setMode(mode)` | `'template'` \| `'report'` | `void` | 设置工作模式 |
347
491
  | `setViewMode(viewMode)` | `'edit'` \| `'preview'` | `void` | 切换视图模式 |
492
+ | `setUIMode(mode)` | `'beginner'` \| `'expert'` | `void` | 切换界面模式(简洁/完整) |
348
493
  | `setOnSave(handler)` | `(payload) => void \| Promise<void>` | `void` | 设置保存回调 |
349
494
  | `setAutoSave(enabled, intervalMs?)` | `boolean, number?` | `void` | 开启/关闭自动保存,`intervalMs` 默认 120000 |
350
- | `setTestData(data)` | `Record<string, any>` | `void` | 设置测试/预览数据(等价 Vue 组件的 `sample-data`) |
351
- | `setSampleData(data)` | `Record<string, any>` | `void` | `setTestData` 的别名 |
352
- | `setVariables(vars)` | `Record<string, any>` | `void` | 打印/导出时注入真实变量(覆盖 testData 中的同名字段) |
495
+ | `setTestData(data)` | `Record<string, any>` | `Promise<void>` | 设置测试/预览数据(等价 Vue 组件通过 `config.testData` 传入) |
496
+ | `setSampleData(data)` | `Record<string, any>` | `Promise<void>` | `setTestData` 的别名 |
497
+ | `setVariables(vars)` | `Record<string, any>` | `Promise<void>` | 打印/导出时注入真实变量(覆盖 testData 中的同名字段) |
498
+ | `setTemplateVariables(vars)` | `Record<string, any>` | `Promise<void>` | 设置模板变量(按 `@键` 规范化后写入 variables) |
499
+ | `setData(data, opts?)` | `Record<string, any>, DesignerDataOptions?` | `Promise<void>` | **统一数据注入入口**。`opts.channel` 可选 `variables`(默认)/`testData`/`sampleData`/`availableVariables`,读取优先级 `runtimeVariables > testData > sampleData`,见[数据通道与优先级](#数据通道与优先级) |
500
+ | `getTestData()` | — | `Record<string, any>` | 获取当前测试数据 |
501
+ | `getVariables()` | — | `Record<string, any>` | 获取当前运行时变量 |
502
+ | `getTemplateVariables()` | — | `Record<string, any>` | 获取当前模板变量 |
503
+ | `setFieldDictionary(dict)` | `Record<string, FieldItem>` | `void` | 设置字段字典(变量面板中显示中文标签) |
504
+ | `setAvailableVariables(vars)` | `VariableTreeItem[]` | `void` | 设置可用变量树(字段选择提示) |
505
+ | `setDataLoading(loading)` | `boolean` | `void` | 设置外部数据加载状态(展示 loading 遮罩) |
506
+ | `setTextPolishHandler(handler)` | `(text) => Promise<string>` | `void` | 注入文本润色处理器(宿主侧 AI 能力,优先于内置 DeepSeek) |
507
+ | `setTextPolishConfig(config)` | `TextPolishConfig` | `void` | 配置内置 DeepSeek 润色(`{ apiKey, baseURL?, model? }`) |
353
508
  | `setTemplates(list, opts?)` | `Array<Template>, object?` | `Promise<void>` | 设置预设模板列表并加载当前模板。`opts` 可选:`{ currentTemplateId?: string }`。返回的 Promise 在画布 pages 填充完成后 resolve,可据此消除时序竞态 |
354
509
  | `loadTemplate(id)` | `string` | `Promise<boolean>` | 加载指定模板到画布。resolve 时 pages 已填充;返回 `false` 表示因前置条件(如正在编辑自定义元素)未触发 |
510
+ | `loadExampleTemplate(id)` | `string` | `Promise<boolean>` | 加载内置示例模板(复制为可编辑模板并加载;同名模板存在时直接加载) |
511
+ | `setTemplateLibrary(list, opts?)` | `DesignerTemplateLibraryItem[], { mode?: 'replace' \| 'merge' }?` | `void` | 替换或合并示例模板库(场景化模板注入)。`mode: 'replace'`(默认)整体替换内置库;`'merge'` 同 id 覆盖、新 id 追加。影响空态与模板下拉的示例模板区。`category` 可复用内置场景 key(`device-run`/`inspection`/`event-feedback`/`other`)或自定义任意值 |
355
512
  | `loadTemplateData(data)` | `Record<string, any>` | `boolean` | 直接加载模板 JSON 数据到画布,返回是否成功 |
356
513
  | `getTemplateData()` | — | `Record<string, any>` | 获取当前模板完整数据 |
357
514
  | `getPreviewHtml()` | — | `Promise<string>` | 获取预览 HTML 字符串 |
@@ -362,9 +519,11 @@ designer.setMode('report')
362
519
  | `setBranding(config)` | `{ title?, logoUrl?, showTitle?, showLogo? }` | `void` | 设置品牌标识 |
363
520
  | `setLanguage(lang)` | `'zh'` \| `'en'` | `void` | 切换语言 |
364
521
  | `setCrudMode(mode, opts?)` | `'local'` \| `'remote'`, `{ skipAutoLoad?: boolean }` | `void` | 切换 CRUD 模式。`skipAutoLoad: true` 跳过 remote 模式下自动 `loadTemplates + loadTemplate` 的链式加载,适用于已通过 `setTemplates` 提供数据的场景,避免双次加载闪烁 |
522
+ | `setCrudEndpoints(endpoints, opts?)` | `CrudEndpoints, { baseUrl?, headers?, fetcher? }` | `void` | 配置远程 CRUD 端点(templates/customElements 的 list/get/upsert/delete),详见[远程 CRUD 配置](#远程-crud-配置) |
523
+ | `setAutoCollapseLoopPages(enabled)` | `boolean` | `void` | 切换保存时自动折叠循环页(默认开启)。关闭后需自行调用 `collapseLoopPages` |
365
524
  | `expandLoopPages(pages, ctx?)` | `Page[], LoopExpandContext?` | `Page[]` | 循环页展开:按 `page.loopDataVariable` + 数据数组,把 1 个模板页展开成 N 页。不传 `ctx` 时默认使用当前 store 数据(导出语义) |
366
525
  | `hasLoopPage(pages)` | `Page[]?` | `boolean` | 判断页面数组中是否含循环页 |
367
- | `collapseLoopPages(currentPages, originalPages)` | `Page[], Page[]` | `Page[]` | 折叠已展开的循环页,还原为带 `loopDataVariable` 的单个模板页。适用于报告模式保存前还原模板结构 |
526
+ | `collapseLoopPages(currentPages, originalPages)` | `Page[], Page[]` | `Page[]` | 折叠已展开的循环页,还原为带 `loopDataVariable` 的单个模板页。保存流程已自动折叠,通常无需手动调用 |
368
527
 
369
528
  ### 事件参考
370
529
 
@@ -490,11 +649,150 @@ function backToEdit() {
490
649
 
491
650
  ---
492
651
 
652
+ ## 场景化模板库
653
+
654
+ 为降低业务人员上手门槛,模板下拉与空态页按**业务场景**分组展示内置示例模板,并支持搜索过滤。内置场景围绕无人机业务:
655
+
656
+ | 场景 key | 场景名称 | 内置模板 |
657
+ |----------|---------|---------|
658
+ | `device-run` | 设备运行 | 设备档案卡、飞行任务记录、设备状态、电池电量、航程统计 |
659
+ | `inspection` | 巡检记录 | 巡检报告、巡检任务明细、巡检缺陷记录、飞行日志 |
660
+ | `event-feedback` | 事件反馈 | 告警汇总单、事件上报单、异常反馈单、处置结果单 |
661
+ | `other` | 其他 | 通用示例(送货单等) |
662
+
663
+ ### 宿主注入模板库 `setTemplateLibrary`
664
+
665
+ 宿主可将自定义模板注入示例库:`mode: 'replace'`(默认)整体替换内置库;`mode: 'merge'` 同 `id` 覆盖、新 `id` 追加。注入后模板下拉与空态**响应式刷新**。
666
+
667
+ ```ts
668
+ el.setTemplateLibrary([
669
+ {
670
+ id: 'biz-device-card',
671
+ name: '设备档案卡(自定义)',
672
+ description: '用于设备入库归档打印',
673
+ category: 'device-run', // 复用内置场景 key,或传自定义字符串(如 'biz')
674
+ data: { pages: [/* ... */], canvasSize: { width: 794, height: 1123 } }
675
+ }
676
+ ], { mode: 'merge' })
677
+ ```
678
+
679
+ > 模板数据 `data` 需包含 `pages`(非空元素数组)与 `canvasSize`;缺 `id`/`name` 或结构不合法的项会被自动丢弃。
680
+
681
+ ### 加载内置示例 `loadExampleTemplate`
682
+
683
+ ```ts
684
+ // 按 id 加载内置示例模板:复制为可编辑模板并填充画布(同名模板已存在时直接加载)
685
+ await el.loadExampleTemplate('example-device-status')
686
+ ```
687
+
688
+ ---
689
+
690
+ ## 数据驱动模板向导
691
+
692
+ 不熟悉画布设计的用户可直接从样例数据生成模板骨架:在模板下拉中选择 **"从数据生成模板"**,粘贴样例 JSON(或使用内置示例数据),设计器会:
693
+
694
+ 1. **推断字段结构**:识别标量字段(文本/数字/日期)与数组字段(对象数组 → 表格)。
695
+ 2. **生成基础骨架**:自动创建"标题 + 每字段一行 label + 数据表格"布局,表格自动绑定数组字段并渲染行。
696
+ 3. **加载进画布微调**:骨架加载后变量可正常解析,用户只需调整样式与排版。
697
+
698
+ > 定位为**骨架生成**:不做像素级智能排版;生成的布局可在画布中自由微调,也可通过 `setTemplateLibrary` 沉淀为团队模板。
699
+
700
+ ---
701
+
702
+ ## 组合与取消组合
703
+
704
+ 画布支持将多个元素**组合为一个整体**操作(移动、缩放、复制、删除):
705
+
706
+ - **组合**:按住 `Ctrl`(Mac 为 `Cmd`)多选元素后,点击顶部工具栏 **"组合"** 按钮(或快捷键)。组合后属性面板显示"组合包含 N 个元素"。
707
+ - **整体操作**:组合作为一个整体参与拖拽移动、八方向缩放(子元素等比联动)、复制粘贴(粘贴后子元素 id 自动再生,不会冲突)。
708
+ - **取消组合**:选中组合后,属性面板点击 **"取消组合"**,成员恢复为独立元素(还原绝对坐标)。
709
+ - **限制**:组合至少需要 2 个元素;已锁定元素不能参与组合。
710
+
711
+ ---
712
+
713
+ ## 数据通道与优先级
714
+
715
+ 设计器存在多条数据注入通道,为避免使用混淆,**读取时统一按以下优先级解析**(同 key 冲突时,优先级高的通道胜出):
716
+
717
+ ```
718
+ runtimeVariables(运行时变量,setVariables / setData variables)
719
+ > testData(模板测试数据,setTestData / setSampleData / setData testData)
720
+ > sampleData(宿主静态示例数据,仅兜底取值)
721
+ > ext.availableVariables(模板字段树声明,仅用于字段提示,不参与取值)
722
+ ```
723
+
724
+ - **建议**:新接入的宿主统一使用 [`setData(data, options)`](#完整-api-参考) 一个入口,用 `options.channel` 选择写入通道;旧 4 个方法(`setTestData` / `setSampleData` / `setVariables` / `setTemplateVariables`)保留并作为 `setData` 的薄包装,行为不变。
725
+ - **原则**:只统一"读取时"的 fallback 链,不合并存储,编辑态 / 打印态差异保持不变。
726
+
727
+ ```js
728
+ // 打印/导出时覆盖预览数据(运行时变量优先级最高)
729
+ designer.setData(
730
+ { items: [{ name: '巡检无人机', qty: 2, total: 12800 }] },
731
+ { channel: 'variables', merge: true }
732
+ );
733
+ ```
734
+
735
+ ---
736
+
737
+ ## 远程 CRUD 配置
738
+
739
+ 不传 `onSave` 时,设计器走内置 CRUD:`local` 模式存 localStorage(默认),`remote` 模式调后端接口。宿主接入云存储需两步:
740
+
741
+ ### 1. 配置端点 `setCrudEndpoints`
742
+
743
+ 端点支持字符串(默认方法:list 用 GET、upsert 用 POST)或对象(自定义 `url` + `method`)。URL 支持相对路径(自动拼接 `baseUrl`)或**绝对路径**(忽略 `baseUrl`,便于对接第三方/微服务):
744
+
745
+ ```ts
746
+ el.setCrudEndpoints({
747
+ baseUrl: 'https://api.example.com', // 也可在 options 中传
748
+ templates: {
749
+ list: '/print/templates', // GET https://api.example.com/print/templates
750
+ get: '/print/templates/{id}', // {id} 占位符会被真实 id 替换
751
+ upsert: '/print/templates', // POST(body 为模板对象)
752
+ delete: '/print/templates/{id}'
753
+ },
754
+ customElements: {
755
+ list: '/print/custom-elements',
756
+ get: '/print/custom-elements/{id}',
757
+ upsert: '/print/custom-elements',
758
+ delete: '/print/custom-elements/{id}'
759
+ }
760
+ }, {
761
+ baseUrl: 'https://your-domain.com',
762
+ headers: { Authorization: 'Bearer xxx', 'X-Tenant-ID': '123' }
763
+ })
764
+ ```
765
+
766
+ ### 2. 切换远程模式 `setCrudMode`
767
+
768
+ ```ts
769
+ el.setCrudMode('remote') // 保存/删除/加载走远程 API;local 为默认(localStorage)
770
+ ```
771
+
772
+ > 若已通过 `setTemplates` 自行提供数据,可传 `setCrudMode('remote', { skipAutoLoad: true })` 跳过 remote 模式下的自动链式加载,避免双次加载闪烁。
773
+
774
+ ### 后端接口约定(4 个端点 / 资源)
775
+
776
+ | 端点 | 请求 | 响应要求 |
777
+ |------|------|---------|
778
+ | `list` | GET | 返回**模板/自定义元素数组**;渲染按返回顺序 |
779
+ | `get` | GET `{id}` | 返回详情:模板含 `data` 与 `ext.availableVariables`;自定义元素含 `element` |
780
+ | `upsert` | POST | 创建/更新;**必须返回真实 `id`** |
781
+ | `delete` | DELETE `{id}` | 返回 `{ success: boolean }` |
782
+
783
+ **通用约定:**
784
+
785
+ - 每个资源(template / customElement)都有 `id`、`name`、`permissions`(`{ editable, deletable, copyable }`)、`updatedAt`、`ext` 字段。
786
+ - **扩展字段一律放 `ext` 下**(如 `ext.category`、`ext.templateTags`、`ext.availableVariables`),不支持根级扁平自定义字段;前端按 key 合并对象、数组"后者覆盖"。
787
+ - `permissions.editable: false` 时设计器进入只读模式(UI 与 Store/API 双层拦截)。
788
+
789
+ ---
790
+
493
791
  ## 传入数据格式
494
792
 
495
793
  ### 基本要求
496
794
 
497
- `sample-data` / `setTestData(data)` 接收一个普通的 JSON 对象。**唯一必填字段是 `id`**。
795
+ `config.testData` / `setTestData(data)` 接收一个普通的 JSON 对象。**唯一必填字段是 `id`**。
498
796
 
499
797
  | 字段 | 类型 | 必填 | 说明 |
500
798
  |------|------|------|------|
@@ -772,18 +1070,28 @@ interface SavePayload {
772
1070
 
773
1071
  ```vue
774
1072
  <template>
775
- <PrintDesigner
1073
+ <XPrintDesigner
776
1074
  :mode="mode"
777
- :sample-data="sampleData"
778
- :on-save="handleSave"
1075
+ :config="{
1076
+ testData: sampleData,
1077
+ onSave: handleSave
1078
+ }"
779
1079
  />
780
1080
  </template>
781
1081
 
782
1082
  <script setup lang="ts">
783
1083
  import { ref } from 'vue'
1084
+ import { XPrintDesigner } from 'x-print-designer'
1085
+ import 'x-print-designer/style.css'
784
1086
 
785
1087
  const mode = ref<'template' | 'report'>('template')
786
1088
 
1089
+ const sampleData = ref({
1090
+ id: 1,
1091
+ title: '报告标题',
1092
+ items: [{ name: '产品A', price: 100 }]
1093
+ })
1094
+
787
1095
  const handleSave = async (payload) => {
788
1096
  // 根据模式决定 API 端点
789
1097
  const endpoint = mode.value === 'template'
@@ -817,17 +1125,19 @@ const handleSave = async (payload) => {
817
1125
 
818
1126
  ### Vue 组件
819
1127
 
820
- | 属性 | 类型 | 默认值 | 说明 |
1128
+ 自动保存通过 `config.autoSave` 配置(`XPrintDesigner` 无独立 props,统一走 `config`):
1129
+
1130
+ | 字段 | 类型 | 默认值 | 说明 |
821
1131
  |------|------|--------|------|
822
- | `auto-save-enabled` | `boolean` | `false` | 是否开启 |
823
- | `auto-save-interval-ms` | `number` | `120000` | 间隔(毫秒),默认 2 分钟 |
1132
+ | `autoSave.enabled` | `boolean` | `false` | 是否开启 |
1133
+ | `autoSave.intervalMs` | `number` | `120000` | 间隔(毫秒),默认 2 分钟 |
824
1134
 
825
1135
  ```vue
826
- <PrintDesigner
827
- :on-save="handleSave"
828
- :auto-save-enabled="true"
829
- :auto-save-interval-ms="120000"
830
- />
1136
+ <XPrintDesigner :config="{
1137
+ testData: sampleData,
1138
+ onSave: handleSave,
1139
+ autoSave: { enabled: true, intervalMs: 120000 }
1140
+ }" />
831
1141
  ```
832
1142
 
833
1143
  ### Web Component
@@ -1056,6 +1366,7 @@ await designer.export({ type: 'html', filename: '订单-2024' })
1056
1366
  | 线条 | `line` | 直线(支持实线/虚线/点线) |
1057
1367
  | 矩形 | `rect` | 矩形(支持圆角) |
1058
1368
  | 圆形 | `circle` | 圆形 |
1369
+ | 组合 | `group` | 多个元素的组合容器,作为整体移动/缩放/复制/删除(见[组合与取消组合](#组合与取消组合)) |
1059
1370
 
1060
1371
  ---
1061
1372
 
@@ -1153,9 +1464,9 @@ await designer.export({ type: 'html', filename: '订单-2024' })
1153
1464
  **原因:** 自动保存需要 `onSave` 回调。
1154
1465
 
1155
1466
  **排查步骤:**
1156
- 1. 确认同时传入了 `onSave` 和 `auto-save-enabled`
1467
+ 1. 确认同时传入了 `onSave` 和 `config.autoSave = { enabled: true }`(Vue 组件)或调用了 `setAutoSave(true)`
1157
1468
  2. 自动保存仅在内容有变更时触发,未修改时不会保存
1158
- 3. 检查 `auto-save-interval-ms` 是否设置过短(频繁保存可能被节流)
1469
+ 3. 检查 `autoSave.intervalMs` / `setAutoSave` 的间隔是否设置过短(频繁保存可能被节流)
1159
1470
 
1160
1471
  ---
1161
1472
 
@@ -1,4 +1,4 @@
1
- import { getDefaultExportFromCjs } from "./index-sx0m_xBC.js";
1
+ import { getDefaultExportFromCjs } from "./index-B-wpcX07.js";
2
2
  function _mergeNamespaces(n, m) {
3
3
  for (var i = 0; i < m.length; i++) {
4
4
  const e = m[i];
@@ -1,4 +1,4 @@
1
- import { getDefaultExportFromCjs } from "./vue-D5fIULVq.js";
1
+ import { getDefaultExportFromCjs } from "./vue-DvM51Rdu.js";
2
2
  function _mergeNamespaces(n, m) {
3
3
  for (var i = 0; i < m.length; i++) {
4
4
  const e = m[i];
@@ -1,4 +1,4 @@
1
- import { getDefaultExportFromCjs } from "./index-sx0m_xBC.js";
1
+ import { getDefaultExportFromCjs } from "./index-B-wpcX07.js";
2
2
  function _mergeNamespaces(n, m) {
3
3
  for (var i = 0; i < m.length; i++) {
4
4
  const e = m[i];
@@ -1,4 +1,4 @@
1
- import { getDefaultExportFromCjs } from "./vue-D5fIULVq.js";
1
+ import { getDefaultExportFromCjs } from "./vue-DvM51Rdu.js";
2
2
  function _mergeNamespaces(n, m) {
3
3
  for (var i = 0; i < m.length; i++) {
4
4
  const e = m[i];
@@ -1,4 +1,4 @@
1
- import { getDefaultExportFromCjs } from "./index-sx0m_xBC.js";
1
+ import { getDefaultExportFromCjs } from "./index-B-wpcX07.js";
2
2
  function _mergeNamespaces(n, m) {
3
3
  for (var i = 0; i < m.length; i++) {
4
4
  const e = m[i];