@tmagic/editor 1.0.0-beta.8 → 1.0.0-rc.10

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.
Files changed (73) hide show
  1. package/README.md +1 -0
  2. package/dist/style.css +94 -45
  3. package/dist/tmagic-editor.es.js +1622 -2069
  4. package/dist/tmagic-editor.es.js.map +1 -1
  5. package/dist/tmagic-editor.umd.js +1621 -2071
  6. package/dist/tmagic-editor.umd.js.map +1 -1
  7. package/dist/types/src/Editor.vue.d.ts +50 -2
  8. package/dist/types/src/fields/Code.vue.d.ts +4 -4
  9. package/dist/types/src/fields/CodeLink.vue.d.ts +1 -3
  10. package/dist/types/src/index.d.ts +1 -0
  11. package/dist/types/src/layouts/CodeEditor.vue.d.ts +13 -4
  12. package/dist/types/src/layouts/Framework.vue.d.ts +15 -3
  13. package/dist/types/src/layouts/PropsPanel.vue.d.ts +4 -5
  14. package/dist/types/src/layouts/sidebar/ComponentListPanel.vue.d.ts +2 -1
  15. package/dist/types/src/layouts/sidebar/Sidebar.vue.d.ts +2 -3
  16. package/dist/types/src/layouts/sidebar/TabPane.vue.d.ts +14 -0
  17. package/dist/types/src/layouts/workspace/Workspace.vue.d.ts +5 -31
  18. package/dist/types/src/services/BaseService.d.ts +5 -2
  19. package/dist/types/src/services/editor.d.ts +16 -6
  20. package/dist/types/src/services/history.d.ts +2 -18
  21. package/dist/types/src/services/props.d.ts +8 -2
  22. package/dist/types/src/services/ui.d.ts +2 -0
  23. package/dist/types/src/type.d.ts +25 -9
  24. package/dist/types/src/utils/editor.d.ts +5 -10
  25. package/dist/types/src/utils/polyfills.d.ts +0 -0
  26. package/dist/types/src/utils/props.d.ts +8 -1
  27. package/package.json +35 -12
  28. package/src/Editor.vue +44 -9
  29. package/src/components/ContentMenu.vue +140 -0
  30. package/src/components/ScrollViewer.vue +1 -1
  31. package/src/components/ToolButton.vue +72 -31
  32. package/src/fields/UISelect.vue +14 -6
  33. package/src/index.ts +2 -0
  34. package/src/layouts/AddPageBox.vue +3 -1
  35. package/src/layouts/CodeEditor.vue +20 -13
  36. package/src/layouts/Framework.vue +20 -7
  37. package/src/layouts/PropsPanel.vue +5 -4
  38. package/src/layouts/sidebar/ComponentListPanel.vue +25 -3
  39. package/src/layouts/sidebar/LayerMenu.vue +93 -95
  40. package/src/layouts/sidebar/LayerPanel.vue +80 -59
  41. package/src/layouts/sidebar/Sidebar.vue +13 -52
  42. package/src/layouts/sidebar/TabPane.vue +68 -0
  43. package/src/layouts/workspace/PageBar.vue +145 -17
  44. package/src/layouts/workspace/Stage.vue +65 -91
  45. package/src/layouts/workspace/ViewerMenu.vue +122 -68
  46. package/src/layouts/workspace/Workspace.vue +125 -42
  47. package/src/services/BaseService.ts +71 -23
  48. package/src/services/editor.ts +231 -69
  49. package/src/services/history.ts +7 -0
  50. package/src/services/props.ts +64 -9
  51. package/src/services/ui.ts +35 -5
  52. package/src/theme/common/var.scss +3 -2
  53. package/src/theme/component-list-panel.scss +2 -0
  54. package/src/theme/content-menu.scss +33 -26
  55. package/src/theme/layer-panel.scss +10 -0
  56. package/src/theme/nav-menu.scss +6 -2
  57. package/src/theme/page-bar.scss +42 -2
  58. package/src/theme/props-panel.scss +19 -17
  59. package/src/theme/workspace.scss +4 -0
  60. package/src/type.ts +27 -9
  61. package/src/utils/editor.ts +37 -56
  62. package/src/utils/polyfills.ts +8 -0
  63. package/src/utils/props.ts +50 -8
  64. package/LICENSE +0 -5432
  65. package/dist/types/src/components/ToolButton.vue.d.ts +0 -35
  66. package/dist/types/src/fields/UISelect.vue.d.ts +0 -32
  67. package/dist/types/src/layouts/sidebar/LayerMenu.vue.d.ts +0 -31
  68. package/dist/types/src/layouts/sidebar/LayerPanel.vue.d.ts +0 -973
  69. package/dist/types/src/layouts/workspace/PageBar.vue.d.ts +0 -11
  70. package/dist/types/src/layouts/workspace/Stage.vue.d.ts +0 -192
  71. package/dist/types/src/layouts/workspace/ViewerMenu.vue.d.ts +0 -18
  72. package/tsconfig.json +0 -9
  73. package/vite.config.ts +0 -30
@@ -21,8 +21,9 @@ import { cloneDeep, mergeWith } from 'lodash-es';
21
21
  import serialize from 'serialize-javascript';
22
22
 
23
23
  import type { Id, MApp, MComponent, MContainer, MNode, MPage } from '@tmagic/schema';
24
- import type StageCore from '@tmagic/stage';
25
- import { getNodePath, isPop } from '@tmagic/utils';
24
+ import { NodeType } from '@tmagic/schema';
25
+ import StageCore from '@tmagic/stage';
26
+ import { getNodePath, isNumber, isPage, isPop } from '@tmagic/utils';
26
27
 
27
28
  import historyService, { StepValue } from '@editor/services/history';
28
29
  import propsService from '@editor/services/props';
@@ -32,11 +33,11 @@ import {
32
33
  change2Fixed,
33
34
  COPY_STORAGE_KEY,
34
35
  Fixed2Other,
36
+ generatePageNameByApp,
35
37
  getNodeIndex,
36
38
  initPosition,
37
39
  isFixed,
38
40
  setLayout,
39
- setNewItemId,
40
41
  } from '@editor/utils/editor';
41
42
  import { log } from '@editor/utils/logger';
42
43
 
@@ -51,29 +52,37 @@ class Editor extends BaseService {
51
52
  parent: null,
52
53
  node: null,
53
54
  stage: null,
55
+ highlightNode: null,
54
56
  modifiedNodeIds: new Map(),
57
+ pageLength: 0,
55
58
  });
56
59
 
57
60
  constructor() {
58
- super([
59
- 'getLayout',
60
- 'select',
61
- 'add',
62
- 'remove',
63
- 'update',
64
- 'sort',
65
- 'copy',
66
- 'paste',
67
- 'alignCenter',
68
- 'moveLayer',
69
- 'undo',
70
- 'redo',
71
- ]);
61
+ super(
62
+ [
63
+ 'getLayout',
64
+ 'select',
65
+ 'add',
66
+ 'remove',
67
+ 'update',
68
+ 'sort',
69
+ 'copy',
70
+ 'paste',
71
+ 'alignCenter',
72
+ 'moveLayer',
73
+ 'move',
74
+ 'undo',
75
+ 'redo',
76
+ 'highlight',
77
+ ],
78
+ // 需要注意循环依赖问题,如果函数间有相互调用的话,不能设置为串行调用
79
+ ['select', 'update', 'moveLayer'],
80
+ );
72
81
  }
73
82
 
74
83
  /**
75
84
  * 设置当前指点节点配置
76
- * @param name 'root' | 'page' | 'parent' | 'node'
85
+ * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode'
77
86
  * @param value MNode
78
87
  * @returns MNode
79
88
  */
@@ -82,6 +91,7 @@ class Editor extends BaseService {
82
91
  log('store set ', name, ' ', value);
83
92
 
84
93
  if (name === 'root') {
94
+ this.state.pageLength = (value as unknown as MApp)?.items?.length || 0;
85
95
  this.emit('root-change', value);
86
96
  }
87
97
  }
@@ -101,7 +111,7 @@ class Editor extends BaseService {
101
111
  * @returns {EditorNodeInfo}
102
112
  */
103
113
  public getNodeInfo(id: Id): EditorNodeInfo {
104
- const root = this.get<MApp | null>('root');
114
+ const root = toRaw(this.get<MApp | null>('root'));
105
115
  if (!root) return {};
106
116
 
107
117
  if (id === root.id) {
@@ -119,7 +129,7 @@ class Editor extends BaseService {
119
129
  info.parent = path[path.length - 2] as MContainer;
120
130
 
121
131
  path.forEach((item) => {
122
- if (item.type === 'page') {
132
+ if (item.type === NodeType.PAGE) {
123
133
  info.page = item as MPage;
124
134
  return;
125
135
  }
@@ -152,13 +162,15 @@ class Editor extends BaseService {
152
162
  /**
153
163
  * 只有容器拥有布局
154
164
  */
155
- public async getLayout(node: MNode): Promise<Layout> {
156
- if (node.layout) {
157
- return node.layout;
165
+ public async getLayout(parent: MNode, node?: MNode): Promise<Layout> {
166
+ if (node && typeof node !== 'function' && isFixed(node)) return Layout.FIXED;
167
+
168
+ if (parent.layout) {
169
+ return parent.layout;
158
170
  }
159
171
 
160
172
  // 如果该节点没有设置position,则认为是流式布局,例如获取root的布局时
161
- if (!node.style?.position) {
173
+ if (!parent.style?.position) {
162
174
  return Layout.RELATIVE;
163
175
  }
164
176
 
@@ -166,28 +178,12 @@ class Editor extends BaseService {
166
178
  }
167
179
 
168
180
  /**
169
- * 选中指点节点(将指点节点设置成当前选中状态)
170
- * @param config 指点节点配置或者ID
181
+ * 选中指定节点(将指定节点设置成当前选中状态)
182
+ * @param config 指定节点配置或者ID
171
183
  * @returns 当前选中的节点配置
172
184
  */
173
- public async select(config: MNode | Id): Promise<MNode> {
174
- let id: Id;
175
- if (typeof config === 'string' || typeof config === 'number') {
176
- id = config;
177
- } else {
178
- id = config.id;
179
- }
180
- if (!id) {
181
- throw new Error('没有ID,无法选中');
182
- }
183
-
184
- const { node, parent, page } = this.getNodeInfo(id);
185
- if (!node) throw new Error('获取不到组件信息');
186
-
187
- if (node.id === this.state.root?.id) {
188
- throw new Error('不能选根节点');
189
- }
190
-
185
+ public async select(config: MNode | Id): Promise<MNode> | never {
186
+ const { node, page, parent } = this.selectedConfigExceptionHandler(config);
191
187
  this.set('node', node);
192
188
  this.set('page', page || null);
193
189
  this.set('parent', parent || null);
@@ -198,7 +194,68 @@ class Editor extends BaseService {
198
194
  historyService.empty();
199
195
  }
200
196
 
201
- return node;
197
+ if (node?.id) {
198
+ this.get<StageCore>('stage')
199
+ ?.renderer.runtime?.getApp?.()
200
+ ?.emit(
201
+ 'editor:select',
202
+ {
203
+ node,
204
+ page,
205
+ parent,
206
+ },
207
+ getNodePath(node.id, this.get<MApp>('root').items),
208
+ );
209
+ }
210
+
211
+ this.emit('select', node);
212
+
213
+ return node!;
214
+ }
215
+
216
+ public async selectNextNode(): Promise<MNode> | never {
217
+ const node = toRaw(this.get('node'));
218
+
219
+ if (!node || isPage(node) || node.type === NodeType.ROOT) return node;
220
+
221
+ const parent = toRaw(this.getParentById(node.id));
222
+
223
+ if (!parent) return node;
224
+
225
+ const index = getNodeIndex(node, parent);
226
+
227
+ const nextNode = parent.items[index + 1] || parent.items[0];
228
+
229
+ await this.select(nextNode);
230
+ this.get<StageCore>('stage')?.select(nextNode.id);
231
+
232
+ return nextNode;
233
+ }
234
+
235
+ public async selectNextPage(): Promise<MNode> | never {
236
+ const root = toRaw(this.get<MApp>('root'));
237
+ const page = toRaw(this.get('page'));
238
+
239
+ const index = getNodeIndex(page, root);
240
+
241
+ const nextPage = root.items[index + 1] || root.items[0];
242
+
243
+ await this.select(nextPage);
244
+ this.get<StageCore>('stage')?.select(nextPage.id);
245
+
246
+ return nextPage;
247
+ }
248
+
249
+ /**
250
+ * 高亮指定节点
251
+ * @param config 指定节点配置或者ID
252
+ * @returns 当前高亮的节点配置
253
+ */
254
+ public highlight(config: MNode | Id): void {
255
+ const { node } = this.selectedConfigExceptionHandler(config);
256
+ const currentHighlightNode = this.get('highlightNode');
257
+ if (currentHighlightNode === node) return;
258
+ this.set('highlightNode', node);
202
259
  }
203
260
 
204
261
  /**
@@ -207,12 +264,14 @@ class Editor extends BaseService {
207
264
  * @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
208
265
  * @returns 添加后的节点
209
266
  */
210
- public async add({ type, ...config }: AddMNode, parent?: MContainer | null): Promise<MNode> {
267
+ public async add(addNode: AddMNode, parent?: MContainer | null): Promise<MNode> {
268
+ const { type, ...config } = addNode;
211
269
  const curNode = this.get<MContainer>('node');
212
270
 
213
271
  let parentNode: MNode | undefined;
272
+ const isPage = type === NodeType.PAGE;
214
273
 
215
- if (type === 'page') {
274
+ if (isPage) {
216
275
  parentNode = this.get<MApp>('root');
217
276
  // 由于支持中间件扩展,在parent参数为undefined时,parent会变成next函数
218
277
  } else if (parent && typeof parent !== 'function') {
@@ -225,21 +284,38 @@ class Editor extends BaseService {
225
284
 
226
285
  if (!parentNode) throw new Error('未找到父元素');
227
286
 
228
- const layout = await this.getLayout(parentNode);
229
- const newNode = initPosition({ ...toRaw(await propsService.getPropsValue(type)), ...config }, layout);
287
+ const layout = await this.getLayout(toRaw(parentNode), addNode as MNode);
288
+ const newNode = initPosition(
289
+ { ...toRaw(await propsService.getPropsValue(type, config)) },
290
+ layout,
291
+ parentNode,
292
+ this.get<StageCore>('stage'),
293
+ );
230
294
 
231
- if ((parentNode?.type === 'app' || curNode.type === 'app') && newNode.type !== 'page') {
295
+ if ((parentNode?.type === NodeType.ROOT || curNode.type === NodeType.ROOT) && newNode.type !== NodeType.PAGE) {
232
296
  throw new Error('app下不能添加组件');
233
297
  }
234
298
 
235
299
  parentNode?.items?.push(newNode);
236
300
 
237
- await this.get<StageCore>('stage')?.add({ config: cloneDeep(newNode), root: cloneDeep(this.get('root')) });
301
+ const stage = this.get<StageCore | null>('stage');
302
+
303
+ await stage?.add({ config: cloneDeep(newNode), root: cloneDeep(this.get('root')) });
238
304
 
239
305
  await this.select(newNode);
240
306
 
241
307
  this.addModifiedNodeId(newNode.id);
242
- this.pushHistoryState();
308
+ if (!isPage) {
309
+ this.pushHistoryState();
310
+ }
311
+
312
+ stage?.select(newNode.id);
313
+
314
+ if (isPage) {
315
+ this.state.pageLength += 1;
316
+ }
317
+
318
+ this.emit('add', newNode);
243
319
 
244
320
  return newNode;
245
321
  }
@@ -265,17 +341,38 @@ class Editor extends BaseService {
265
341
  if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
266
342
 
267
343
  parent.items?.splice(index, 1);
268
- this.get<StageCore>('stage')?.remove({ id: node.id, root: this.get('root') });
269
-
270
- if (node.type === 'page') {
271
- await this.select(root.items[0] || root);
344
+ const stage = this.get<StageCore | null>('stage');
345
+ stage?.remove({ id: node.id, root: this.get('root') });
346
+
347
+ if (node.type === NodeType.PAGE) {
348
+ this.state.pageLength -= 1;
349
+
350
+ if (root.items[0]) {
351
+ await this.select(root.items[0]);
352
+ stage?.select(root.items[0].id);
353
+ } else {
354
+ this.set('node', null);
355
+ this.set('parent', null);
356
+ this.set('page', null);
357
+ this.set('stage', null);
358
+ this.set('highlightNode', null);
359
+ this.resetModifiedNodeId();
360
+ historyService.reset();
361
+
362
+ this.emit('remove', node);
363
+
364
+ return node;
365
+ }
272
366
  } else {
273
367
  await this.select(parent);
368
+ stage?.select(parent.id);
274
369
  }
275
370
 
276
371
  this.addModifiedNodeId(parent.id);
277
372
  this.pushHistoryState();
278
373
 
374
+ this.emit('remove', node);
375
+
279
376
  return node;
280
377
  }
281
378
 
@@ -303,7 +400,7 @@ class Editor extends BaseService {
303
400
 
304
401
  if (!newConfig.type) throw new Error('配置缺少type值');
305
402
 
306
- if (newConfig.type === 'app') {
403
+ if (newConfig.type === NodeType.ROOT) {
307
404
  this.set('root', newConfig);
308
405
  return newConfig;
309
406
  }
@@ -324,19 +421,21 @@ class Editor extends BaseService {
324
421
 
325
422
  parentNodeItems[index] = newConfig;
326
423
 
327
- if (newConfig.id === this.get('node').id) {
424
+ if (`${newConfig.id}` === `${this.get('node').id}`) {
328
425
  this.set('node', newConfig);
329
426
  }
330
427
 
331
- this.get<StageCore>('stage')?.update({ config: cloneDeep(newConfig), root: this.get('root') });
428
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(newConfig), root: this.get('root') });
332
429
 
333
- if (newConfig.type === 'page') {
430
+ if (newConfig.type === NodeType.PAGE) {
334
431
  this.set('page', newConfig);
335
432
  }
336
433
 
337
434
  this.addModifiedNodeId(newConfig.id);
338
435
  this.pushHistoryState();
339
436
 
437
+ this.emit('update', newConfig);
438
+
340
439
  return newConfig;
341
440
  }
342
441
 
@@ -359,7 +458,7 @@ class Editor extends BaseService {
359
458
  await this.update(parent);
360
459
  await this.select(node);
361
460
 
362
- this.get<StageCore>('stage')?.update({ config: cloneDeep(node), root: this.get('root') });
461
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(node), root: this.get('root') });
363
462
 
364
463
  this.addModifiedNodeId(parent.id);
365
464
  this.pushHistoryState();
@@ -395,7 +494,7 @@ class Editor extends BaseService {
395
494
  return;
396
495
  }
397
496
 
398
- setNewItemId(config, this.get('root'));
497
+ await propsService.setNewItemId(config, this.get('root'));
399
498
  if (config.style) {
400
499
  config.style = {
401
500
  ...config.style,
@@ -403,6 +502,10 @@ class Editor extends BaseService {
403
502
  };
404
503
  }
405
504
 
505
+ if (isPage(config)) {
506
+ config.name = generatePageNameByApp(this.get('root'));
507
+ }
508
+
406
509
  return await this.add(config);
407
510
  }
408
511
 
@@ -414,17 +517,28 @@ class Editor extends BaseService {
414
517
  public async alignCenter(config: MNode): Promise<MNode | void> {
415
518
  const parent = this.get<MContainer>('parent');
416
519
  const node = this.get<MNode>('node');
417
- const layout = await this.getLayout(parent);
520
+ const layout = await this.getLayout(toRaw(parent), toRaw(node));
418
521
  if (layout === Layout.RELATIVE) {
419
522
  return;
420
523
  }
421
524
 
422
- if (parent.style?.width && node.style?.width) {
525
+ if (!node.style) return;
526
+
527
+ const stage = this.get<StageCore>('stage');
528
+ const doc = stage?.renderer.contentWindow?.document;
529
+
530
+ if (doc) {
531
+ const parentEl = doc.getElementById(`${parent.id}`);
532
+ const el = doc.getElementById(`${node.id}`);
533
+ if (parentEl && el) {
534
+ node.style.left = (parentEl.clientWidth - el.clientWidth) / 2;
535
+ }
536
+ } else if (parent.style && isNumber(parent.style?.width) && isNumber(node.style?.width)) {
423
537
  node.style.left = (parent.style.width - node.style.width) / 2;
424
538
  }
425
539
 
426
540
  await this.update(node);
427
- this.get<StageCore>('stage')?.update({ config: cloneDeep(toRaw(node)), root: this.get('root') });
541
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(toRaw(node)), root: this.get('root') });
428
542
  this.addModifiedNodeId(config.id);
429
543
  this.pushHistoryState();
430
544
 
@@ -441,15 +555,15 @@ class Editor extends BaseService {
441
555
  const brothers: MNode[] = parent?.items || [];
442
556
  const index = brothers.findIndex((item) => `${item.id}` === `${node?.id}`);
443
557
 
444
- if (offset === LayerOffset.BOTTOM) {
558
+ if (offset === LayerOffset.TOP) {
445
559
  brothers.splice(brothers.length - 1, 0, brothers.splice(index, 1)[0]);
446
- } else if (offset === LayerOffset.TOP) {
560
+ } else if (offset === LayerOffset.BOTTOM) {
447
561
  brothers.splice(0, 0, brothers.splice(index, 1)[0]);
448
562
  } else {
449
563
  brothers.splice(index + parseInt(`${offset}`, 10), 0, brothers.splice(index, 1)[0]);
450
564
  }
451
565
 
452
- this.get<StageCore>('stage')?.update({ config: cloneDeep(toRaw(parent)), root: this.get('root') });
566
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(toRaw(parent)), root: this.get('root') });
453
567
  }
454
568
 
455
569
  /**
@@ -472,6 +586,26 @@ class Editor extends BaseService {
472
586
  return value;
473
587
  }
474
588
 
589
+ public async move(left: number, top: number) {
590
+ const node = toRaw(this.get('node'));
591
+ if (!node || isPage(node)) return;
592
+
593
+ const { style, id } = node;
594
+ if (!style || style.position !== 'absolute') return;
595
+
596
+ if (top && !isNumber(style.top)) return;
597
+ if (left && !isNumber(style.left)) return;
598
+
599
+ this.update({
600
+ id,
601
+ style: {
602
+ ...style,
603
+ left: Number(style.left) + left,
604
+ top: Number(style.top) + top,
605
+ },
606
+ });
607
+ }
608
+
475
609
  public destroy() {
476
610
  this.removeAllListeners();
477
611
  this.set('root', null);
@@ -508,7 +642,11 @@ class Editor extends BaseService {
508
642
  this.isHistoryStateChange = true;
509
643
  await this.update(value.data);
510
644
  this.set('modifiedNodeIds', value.modifiedNodeIds);
511
- setTimeout(() => value.nodeId && this.select(value.nodeId), 0);
645
+ setTimeout(async () => {
646
+ if (!value.nodeId) return;
647
+ await this.select(value.nodeId);
648
+ this.get<StageCore | null>('stage')?.select(value.nodeId);
649
+ }, 0);
512
650
  }
513
651
 
514
652
  private async toggleFixedPosition(dist: MNode, src: MNode, root: MApp) {
@@ -524,6 +662,30 @@ class Editor extends BaseService {
524
662
 
525
663
  return newConfig;
526
664
  }
665
+
666
+ private selectedConfigExceptionHandler(config: MNode | Id): EditorNodeInfo {
667
+ let id: Id;
668
+ if (typeof config === 'string' || typeof config === 'number') {
669
+ id = config;
670
+ } else {
671
+ id = config.id;
672
+ }
673
+ if (!id) {
674
+ throw new Error('没有ID,无法选中');
675
+ }
676
+
677
+ const { node, parent, page } = this.getNodeInfo(id);
678
+ if (!node) throw new Error('获取不到组件信息');
679
+
680
+ if (node.id === this.state.root?.id) {
681
+ throw new Error('不能选根节点');
682
+ }
683
+ return {
684
+ node,
685
+ parent,
686
+ page,
687
+ };
688
+ }
527
689
  }
528
690
 
529
691
  export type EditorService = Editor;
@@ -51,6 +51,13 @@ class History extends BaseService {
51
51
  this.on('change', this.setCanUndoRedo);
52
52
  }
53
53
 
54
+ public reset() {
55
+ this.state.pageSteps = {};
56
+ this.state.pageId = undefined;
57
+ this.state.canRedo = false;
58
+ this.state.canUndo = false;
59
+ }
60
+
54
61
  public changePage(page: MPage): void {
55
62
  if (!page) return;
56
63
 
@@ -17,11 +17,12 @@
17
17
  */
18
18
 
19
19
  import { reactive } from 'vue';
20
- import { cloneDeep } from 'lodash-es';
20
+ import { cloneDeep, mergeWith, random } from 'lodash-es';
21
21
 
22
22
  import type { FormConfig } from '@tmagic/form';
23
- import type { MComponent, MNode } from '@tmagic/schema';
24
- import { toLine } from '@tmagic/utils';
23
+ import type { Id, MComponent, MNode, MPage } from '@tmagic/schema';
24
+ import { NodeType } from '@tmagic/schema';
25
+ import { isPop, toLine } from '@tmagic/utils';
25
26
 
26
27
  import type { PropsState } from '@editor/type';
27
28
  import { DEFAULT_CONFIG, fillConfig, getDefaultPropsValue } from '@editor/utils/props';
@@ -35,7 +36,7 @@ class Props extends BaseService {
35
36
  });
36
37
 
37
38
  constructor() {
38
- super(['setPropsConfig', 'getPropsConfig', 'setPropsValue', 'getPropsValue']);
39
+ super(['setPropsConfig', 'getPropsConfig', 'setPropsValue', 'getPropsValue', 'createId', 'setNewItemId']);
39
40
  }
40
41
 
41
42
  public setPropsConfigs(configs: Record<string, FormConfig>) {
@@ -87,7 +88,7 @@ class Props extends BaseService {
87
88
  * @param type 组件类型
88
89
  * @returns 组件初始值
89
90
  */
90
- public async getPropsValue(type: string) {
91
+ public async getPropsValue(type: string, defaultValue: Record<string, any> = {}) {
91
92
  if (type === 'area') {
92
93
  const value = (await this.getPropsValue('button')) as MComponent;
93
94
  value.className = 'action-area';
@@ -98,13 +99,67 @@ class Props extends BaseService {
98
99
  return value;
99
100
  }
100
101
 
101
- return cloneDeep({
102
- ...getDefaultPropsValue(type),
103
- ...(this.state.propsValueMap[type] || {}),
104
- });
102
+ const data = cloneDeep(defaultValue as any);
103
+
104
+ await this.setNewItemId(data);
105
+
106
+ return {
107
+ ...getDefaultPropsValue(type, await this.createId(type)),
108
+ ...mergeWith(cloneDeep(this.state.propsValueMap[type] || {}), data),
109
+ };
110
+ }
111
+
112
+ public async createId(type: string | number): Promise<string> {
113
+ return `${type}_${random(10000, false)}`;
114
+ }
115
+
116
+ /**
117
+ * 将组件与组件的子元素配置中的id都设置成一个新的ID
118
+ * @param {Object} config 组件配置
119
+ */
120
+ /* eslint no-param-reassign: ["error", { "props": false }] */
121
+ public async setNewItemId(config: MNode, parent?: MPage) {
122
+ const oldId = config.id;
123
+
124
+ config.id = await this.createId(config.type || 'component');
125
+
126
+ // 只有弹窗在页面下的一级子元素才有效
127
+ if (isPop(config) && parent?.type === NodeType.PAGE) {
128
+ updatePopId(oldId, config.id, parent);
129
+ }
130
+
131
+ if (config.items && Array.isArray(config.items)) {
132
+ for (const item of config.items) {
133
+ await this.setNewItemId(item, config as MPage);
134
+ }
135
+ }
105
136
  }
106
137
  }
107
138
 
139
+ /**
140
+ * 复制页面时,需要把组件下关联的弹窗id换测复制出来的弹窗的id
141
+ * @param {number} oldId 复制的源弹窗id
142
+ * @param {number} popId 新的弹窗id
143
+ * @param {Object} pageConfig 页面配置
144
+ */
145
+ const updatePopId = (oldId: Id, popId: Id, pageConfig: MPage) => {
146
+ pageConfig.items?.forEach((config) => {
147
+ if (config.pop === oldId) {
148
+ config.pop = popId;
149
+ return;
150
+ }
151
+
152
+ if (config.popId === oldId) {
153
+ config.popId = popId;
154
+ return;
155
+ }
156
+
157
+ if (Array.isArray(config.items)) {
158
+ updatePopId(oldId, popId, config as MPage);
159
+ }
160
+ });
161
+ };
162
+
108
163
  export type PropsService = Props;
109
164
 
110
165
  export default new Props();