@tmagic/editor 1.1.0-beta.4 → 1.1.0-beta.7

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 (100) hide show
  1. package/dist/{tmagic-editor.es.js → tmagic-editor.mjs} +1361 -1144
  2. package/dist/tmagic-editor.mjs.map +1 -0
  3. package/dist/tmagic-editor.umd.js +1375 -1158
  4. package/dist/tmagic-editor.umd.js.map +1 -1
  5. package/package.json +21 -19
  6. package/src/Editor.vue +14 -12
  7. package/src/components/ContentMenu.vue +87 -99
  8. package/src/components/ScrollViewer.vue +1 -1
  9. package/src/components/ToolButton.vue +150 -167
  10. package/src/fields/UISelect.vue +49 -70
  11. package/src/index.ts +2 -1
  12. package/src/layouts/AddPageBox.vue +2 -2
  13. package/src/layouts/Framework.vue +1 -1
  14. package/src/layouts/NavMenu.vue +2 -2
  15. package/src/layouts/PropsPanel.vue +1 -1
  16. package/src/layouts/Resizer.vue +1 -1
  17. package/src/layouts/sidebar/ComponentListPanel.vue +2 -2
  18. package/src/layouts/sidebar/LayerMenu.vue +2 -2
  19. package/src/layouts/sidebar/LayerPanel.vue +13 -4
  20. package/src/layouts/sidebar/Sidebar.vue +1 -1
  21. package/src/layouts/sidebar/TabPane.vue +2 -2
  22. package/src/layouts/workspace/PageBar.vue +63 -192
  23. package/src/layouts/workspace/PageBarScrollContainer.vue +111 -0
  24. package/src/layouts/workspace/Stage.vue +195 -222
  25. package/src/layouts/workspace/ViewerMenu.vue +142 -146
  26. package/src/layouts/workspace/Workspace.vue +11 -11
  27. package/src/services/BaseService.ts +1 -1
  28. package/src/services/componentList.ts +3 -3
  29. package/src/services/editor.ts +117 -135
  30. package/src/services/events.ts +1 -1
  31. package/src/services/history.ts +1 -1
  32. package/src/services/props.ts +62 -10
  33. package/src/services/storage.ts +133 -0
  34. package/src/services/ui.ts +2 -2
  35. package/src/type.ts +22 -6
  36. package/src/utils/config.ts +1 -1
  37. package/src/utils/editor.ts +1 -1
  38. package/src/utils/operator.ts +210 -0
  39. package/src/utils/props.ts +2 -24
  40. package/src/utils/scroll-viewer.ts +1 -1
  41. package/tsconfig.build.json +13 -0
  42. package/{dist/types/src → types}/Editor.vue.d.ts +7 -8
  43. package/types/components/ContentMenu.vue.d.ts +116 -0
  44. package/types/components/Icon.vue.d.ts +13 -0
  45. package/types/components/ScrollViewer.vue.d.ts +22 -0
  46. package/types/components/ToolButton.vue.d.ts +109 -0
  47. package/types/fields/Code.vue.d.ts +23 -0
  48. package/{dist/types/src → types}/fields/CodeLink.vue.d.ts +4 -6
  49. package/types/fields/UISelect.vue.d.ts +83 -0
  50. package/{dist/types/src → types}/index.d.ts +4 -1
  51. package/types/layouts/AddPageBox.vue.d.ts +4 -0
  52. package/{dist/types/src → types}/layouts/CodeEditor.vue.d.ts +11 -13
  53. package/types/layouts/Framework.vue.d.ts +22 -0
  54. package/types/layouts/NavMenu.vue.d.ts +25 -0
  55. package/types/layouts/PropsPanel.vue.d.ts +245 -0
  56. package/types/layouts/Resizer.vue.d.ts +12 -0
  57. package/types/layouts/sidebar/ComponentListPanel.vue.d.ts +14 -0
  58. package/types/layouts/sidebar/LayerMenu.vue.d.ts +100 -0
  59. package/types/layouts/sidebar/LayerPanel.vue.d.ts +1099 -0
  60. package/{dist/types/src → types}/layouts/sidebar/Sidebar.vue.d.ts +3 -5
  61. package/types/layouts/sidebar/TabPane.vue.d.ts +14 -0
  62. package/types/layouts/workspace/PageBar.vue.d.ts +54 -0
  63. package/types/layouts/workspace/PageBarScrollContainer.vue.d.ts +48 -0
  64. package/types/layouts/workspace/Stage.vue.d.ts +46 -0
  65. package/types/layouts/workspace/ViewerMenu.vue.d.ts +88 -0
  66. package/types/layouts/workspace/Workspace.vue.d.ts +6 -0
  67. package/{dist/types/src → types}/services/BaseService.d.ts +0 -0
  68. package/types/services/componentList.d.ts +14 -0
  69. package/{dist/types/src → types}/services/editor.d.ts +26 -11
  70. package/{dist/types/src → types}/services/events.d.ts +0 -0
  71. package/{dist/types/src → types}/services/history.d.ts +0 -0
  72. package/{dist/types/src → types}/services/props.d.ts +25 -1
  73. package/types/services/storage.d.ts +56 -0
  74. package/{dist/types/src → types}/services/ui.d.ts +1 -1
  75. package/{dist/types/src → types}/type.d.ts +15 -0
  76. package/{dist/types/src → types}/utils/compose.d.ts +1 -1
  77. package/{dist/types/src → types}/utils/config.d.ts +0 -0
  78. package/{dist/types/src → types}/utils/editor.d.ts +2 -2
  79. package/{dist/types/src → types}/utils/index.d.ts +0 -0
  80. package/{dist/types/src → types}/utils/logger.d.ts +0 -0
  81. package/types/utils/operator.d.ts +44 -0
  82. package/{dist/types/src → types}/utils/polyfills.d.ts +0 -0
  83. package/{dist/types/src → types}/utils/props.d.ts +2 -22
  84. package/{dist/types/src → types}/utils/scroll-viewer.d.ts +0 -0
  85. package/{dist/types/src → types}/utils/undo-redo.d.ts +0 -0
  86. package/dist/tmagic-editor.es.js.map +0 -1
  87. package/dist/types/src/components/Icon.vue.d.ts +0 -14
  88. package/dist/types/src/components/ScrollViewer.vue.d.ts +0 -24
  89. package/dist/types/src/fields/Code.vue.d.ts +0 -25
  90. package/dist/types/src/layouts/AddPageBox.vue.d.ts +0 -5
  91. package/dist/types/src/layouts/Framework.vue.d.ts +0 -24
  92. package/dist/types/src/layouts/NavMenu.vue.d.ts +0 -27
  93. package/dist/types/src/layouts/PropsPanel.vue.d.ts +0 -13
  94. package/dist/types/src/layouts/Resizer.vue.d.ts +0 -14
  95. package/dist/types/src/layouts/sidebar/ComponentListPanel.vue.d.ts +0 -16
  96. package/dist/types/src/layouts/sidebar/TabPane.vue.d.ts +0 -16
  97. package/dist/types/src/layouts/workspace/Workspace.vue.d.ts +0 -8
  98. package/dist/types/src/shims-vue.d.ts +0 -6
  99. package/dist/types/src/vite-env.d.ts +0 -1
  100. package/src/vite-env.d.ts +0 -1
@@ -17,45 +17,43 @@
17
17
  */
18
18
 
19
19
  import { reactive, toRaw } from 'vue';
20
- import { cloneDeep, mergeWith } from 'lodash-es';
21
- import serialize from 'serialize-javascript';
20
+ import { cloneDeep, mergeWith, uniq } from 'lodash-es';
22
21
 
23
22
  import type { Id, MApp, MComponent, MContainer, MNode, MPage } from '@tmagic/schema';
24
23
  import { NodeType } from '@tmagic/schema';
25
24
  import StageCore from '@tmagic/stage';
26
25
  import { getNodePath, isNumber, isPage, isPop } from '@tmagic/utils';
27
26
 
28
- import historyService, { StepValue } from '@editor/services/history';
29
- import propsService from '@editor/services/props';
30
- import type { AddMNode, EditorNodeInfo, StoreState } from '@editor/type';
31
- import { LayerOffset, Layout } from '@editor/type';
27
+ import historyService, { StepValue } from '../services/history';
28
+ import storageService, { Protocol } from '../services/storage';
29
+ import type { AddMNode, EditorNodeInfo, PastePosition, StoreState } from '../type';
30
+ import { LayerOffset, Layout } from '../type';
32
31
  import {
33
32
  change2Fixed,
34
33
  COPY_STORAGE_KEY,
35
34
  Fixed2Other,
36
- fixNodeLeft,
37
- generatePageNameByApp,
38
35
  getInitPositionStyle,
39
36
  getNodeIndex,
40
37
  isFixed,
41
38
  setLayout,
42
- } from '@editor/utils/editor';
39
+ } from '../utils/editor';
40
+ import { beforeAdd, beforePaste, beforeRemove, notifyAddToStage } from '../utils/operator';
43
41
 
44
42
  import BaseService from './BaseService';
45
43
 
46
44
  class Editor extends BaseService {
47
- private isHistoryStateChange = false;
48
-
49
- private state = reactive<StoreState>({
45
+ public state: StoreState = reactive({
50
46
  root: null,
51
47
  page: null,
52
48
  parent: null,
53
49
  node: null,
50
+ nodes: [],
54
51
  stage: null,
55
52
  highlightNode: null,
56
53
  modifiedNodeIds: new Map(),
57
54
  pageLength: 0,
58
55
  });
56
+ private isHistoryStateChange = false;
59
57
 
60
58
  constructor() {
61
59
  super(
@@ -83,13 +81,16 @@ class Editor extends BaseService {
83
81
 
84
82
  /**
85
83
  * 设置当前指点节点配置
86
- * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode'
84
+ * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes'
87
85
  * @param value MNode
88
86
  * @returns MNode
89
87
  */
90
88
  public set<T = MNode>(name: keyof StoreState, value: T) {
91
89
  this.state[name] = value as any;
92
-
90
+ // set nodes时将node设置为nodes第一个元素
91
+ if (name === 'nodes') {
92
+ this.set('node', (value as unknown as MNode[])[0]);
93
+ }
93
94
  if (name === 'root') {
94
95
  this.state.pageLength = (value as unknown as MApp)?.items?.length || 0;
95
96
  this.emit('root-change', value);
@@ -98,7 +99,7 @@ class Editor extends BaseService {
98
99
 
99
100
  /**
100
101
  * 获取当前指点节点配置
101
- * @param name 'root' | 'page' | 'parent' | 'node'
102
+ * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes'
102
103
  * @returns MNode
103
104
  */
104
105
  public get<T = MNode>(name: keyof StoreState): T {
@@ -190,7 +191,7 @@ class Editor extends BaseService {
190
191
  */
191
192
  public async select(config: MNode | Id): Promise<MNode> | never {
192
193
  const { node, page, parent } = this.selectedConfigExceptionHandler(config);
193
- this.set('node', node);
194
+ this.set('nodes', [node]);
194
195
  this.set('page', page || null);
195
196
  this.set('parent', parent || null);
196
197
 
@@ -265,66 +266,76 @@ class Editor extends BaseService {
265
266
  }
266
267
 
267
268
  /**
268
- * 向指点容器添加组件节点
269
- * @param addConfig 将要添加的组件节点配置
270
- * @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
271
- * @returns 添加后的节点
269
+ * 多选
270
+ * @param ids 指定节点ID
271
+ * @returns 加入多选的节点配置
272
272
  */
273
- public async add(addNode: AddMNode, parent?: MContainer | null): Promise<MNode> {
274
- // 加入inputEvent是为给业务扩展时可以获取到更多的信息,只有在使用拖拽添加组件时才有改对象
275
- const { type, inputEvent, ...config } = addNode;
276
- const curNode = this.get<MContainer>('node');
277
-
278
- let parentNode: MContainer | undefined;
279
- const isPage = type === NodeType.PAGE;
273
+ public multiSelect(ids: Id[]): void {
274
+ const nodes: MNode[] = [];
275
+ const idsUnique = uniq(ids);
276
+ idsUnique.forEach((id) => {
277
+ const { node } = this.getNodeInfo(id);
278
+ if (!node) return;
279
+ nodes.push(node);
280
+ });
281
+ this.set('nodes', nodes);
282
+ }
280
283
 
281
- if (isPage) {
282
- parentNode = this.get<MApp>('root');
283
- // 由于支持中间件扩展,在parent参数为undefined时,parent会变成next函数
284
- } else if (parent && typeof parent !== 'function') {
285
- parentNode = parent;
286
- } else if (curNode.items) {
287
- parentNode = curNode;
288
- } else {
289
- parentNode = this.getParentById(curNode.id, false);
290
- }
284
+ /**
285
+ * 批量向容器添加节点
286
+ * @param configs 将要添加的节点数组
287
+ * @returns 添加后的节点
288
+ */
289
+ public async multiAdd(configs: MNode[]): Promise<MNode[]> {
290
+ const stage = this.get<StageCore | null>('stage');
291
+ const newNodes: MNode[] = await Promise.all(
292
+ configs.map(async (configItem: MNode): Promise<MNode> => {
293
+ // 新增元素到配置
294
+ const { parentNode, newNode, layout } = await beforeAdd(configItem as AddMNode);
295
+ // 将新增元素事件通知到stage以更新渲染
296
+ await notifyAddToStage(parentNode, newNode, layout);
297
+ return newNode;
298
+ }),
299
+ );
300
+ const newNodeIds: Id[] = newNodes.map((node) => node.id);
291
301
 
292
- if (!parentNode) throw new Error('未找到父元素');
302
+ // 增加历史记录 多选不可能选中page
303
+ this.addModifiedNodeId(newNodeIds.join('-'));
304
+ this.pushHistoryState();
293
305
 
294
- const layout = await this.getLayout(toRaw(parentNode), addNode as MNode);
295
- const newNode = { ...toRaw(await propsService.getPropsValue(type, config)) };
296
- newNode.style = getInitPositionStyle(newNode.style, layout, parentNode, this.get<StageCore>('stage'));
306
+ // 触发选中样式
307
+ stage?.multiSelect(newNodeIds);
297
308
 
298
- if ((parentNode?.type === NodeType.ROOT || curNode.type === NodeType.ROOT) && newNode.type !== NodeType.PAGE) {
299
- throw new Error('app下不能添加组件');
300
- }
309
+ this.emit('multiAdd', newNodes);
301
310
 
302
- parentNode?.items?.push(newNode);
311
+ return newNodes;
312
+ }
303
313
 
314
+ /**
315
+ * 向指点容器添加组件节点
316
+ * @param addConfig 将要添加的组件节点配置
317
+ * @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
318
+ * @returns 添加后的节点
319
+ */
320
+ public async add(addNode: AddMNode, parent?: MContainer | null): Promise<MNode> {
304
321
  const stage = this.get<StageCore | null>('stage');
305
- const root = this.get<MApp>('root');
306
-
307
- await stage?.add({ config: cloneDeep(newNode), parent: cloneDeep(parentNode), root: cloneDeep(root) });
308
-
309
- if (layout === Layout.ABSOLUTE) {
310
- const fixedLeft = fixNodeLeft(newNode, parentNode, stage?.renderer.contentWindow?.document);
311
- if (typeof fixedLeft !== 'undefined') {
312
- newNode.style.left = fixedLeft;
313
- await stage?.update({ config: cloneDeep(newNode), root: cloneDeep(root) });
314
- }
315
- }
316
-
322
+ // 新增元素到配置
323
+ const { parentNode, newNode, layout, isPage } = await beforeAdd(addNode, parent);
324
+ // 将新增元素事件通知到stage以更新渲染
325
+ await notifyAddToStage(parentNode, newNode, layout);
326
+ // 更新编辑器选中元素
317
327
  await this.select(newNode);
318
-
328
+ // 增加历史记录
319
329
  this.addModifiedNodeId(newNode.id);
320
330
  if (!isPage) {
321
331
  this.pushHistoryState();
322
332
  }
323
333
 
324
- stage?.select(newNode.id);
325
-
326
334
  if (isPage) {
327
335
  this.state.pageLength += 1;
336
+ } else {
337
+ // 新增页面,这个时候页面还有渲染出来,此时select会出错,在runtime-ready的时候回去select
338
+ stage?.select(newNode.id);
328
339
  }
329
340
 
330
341
  this.emit('add', newNode);
@@ -337,50 +348,18 @@ class Editor extends BaseService {
337
348
  * @param {Object} node
338
349
  * @return {Object} 删除的组件配置
339
350
  */
340
- public async remove(node: MNode): Promise<MNode | void> {
341
- if (!node?.id) return;
342
-
343
- const root = this.get<MApp | null>('root');
344
-
345
- if (!root) throw new Error('没有root');
346
-
347
- const { parent, node: curNode } = this.getNodeInfo(node.id, false);
348
-
349
- if (!parent || !curNode) throw new Error('找不要删除的节点');
350
-
351
- const index = getNodeIndex(curNode, parent);
352
-
353
- if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
354
-
355
- parent.items?.splice(index, 1);
356
- const stage = this.get<StageCore | null>('stage');
357
- stage?.remove({ id: node.id, root: cloneDeep(this.get('root')) });
358
-
359
- if (node.type === NodeType.PAGE) {
360
- this.state.pageLength -= 1;
361
-
362
- if (root.items[0]) {
363
- await this.select(root.items[0]);
364
- stage?.select(root.items[0].id);
365
- } else {
366
- this.set('node', null);
367
- this.set('parent', null);
368
- this.set('page', null);
369
- this.set('stage', null);
370
- this.set('highlightNode', null);
371
- this.resetModifiedNodeId();
372
- historyService.reset();
373
-
374
- this.emit('remove', node);
375
-
376
- return node;
377
- }
378
- } else {
379
- await this.select(parent);
380
- stage?.select(parent.id);
351
+ public async remove(nodeOrNodeList: MNode | MNode[]): Promise<MNode | MNode[]> {
352
+ if (Array.isArray(nodeOrNodeList)) {
353
+ // 多选批量删除
354
+ const nodes = nodeOrNodeList;
355
+ return this.multiRemove(nodes);
381
356
  }
382
-
383
- this.addModifiedNodeId(parent.id);
357
+ const node = nodeOrNodeList;
358
+ const removeParent = await beforeRemove(node);
359
+ // 删除的是页面
360
+ if (!removeParent) return node;
361
+ // 更新历史记录
362
+ this.addModifiedNodeId(removeParent.id);
384
363
  this.pushHistoryState();
385
364
 
386
365
  this.emit('remove', node);
@@ -388,6 +367,25 @@ class Editor extends BaseService {
388
367
  return node;
389
368
  }
390
369
 
370
+ /**
371
+ * 批量删除
372
+ * @param nodes 批量删除的节点
373
+ * @returns 批量删除的节点
374
+ */
375
+ public async multiRemove(nodes: MNode[]): Promise<MNode[]> {
376
+ await Promise.all(
377
+ nodes.map(async (removeNode) => {
378
+ await beforeRemove(removeNode);
379
+ }),
380
+ );
381
+ const nodeIds = nodes.map((node) => node.id);
382
+ this.addModifiedNodeId(nodeIds.join('-'));
383
+ this.pushHistoryState();
384
+
385
+ this.emit('multiRemove', nodes);
386
+ return nodes;
387
+ }
388
+
391
389
  /**
392
390
  * 更新节点
393
391
  * @param config 新的节点配置,配置中需要有id信息
@@ -433,9 +431,11 @@ class Editor extends BaseService {
433
431
 
434
432
  parentNodeItems[index] = newConfig;
435
433
 
436
- if (`${newConfig.id}` === `${this.get('node').id}`) {
437
- this.set('node', newConfig);
438
- }
434
+ // 将update后的配置更新到nodes中
435
+ const nodes = this.get('nodes');
436
+ const targetIndex = nodes.findIndex((nodeItem: MNode) => `${nodeItem.id}` === `${newConfig.id}`);
437
+ nodes.splice(targetIndex, 1, newConfig);
438
+ this.set('nodes', nodes);
439
439
 
440
440
  this.get<StageCore | null>('stage')?.update({ config: cloneDeep(newConfig), root: cloneDeep(this.get('root')) });
441
441
 
@@ -481,44 +481,25 @@ class Editor extends BaseService {
481
481
  * @param config 组件节点配置
482
482
  * @returns 组件节点配置
483
483
  */
484
- public async copy(config: MNode): Promise<void> {
485
- globalThis.localStorage.setItem(COPY_STORAGE_KEY, serialize(config));
484
+ public async copy(config: MNode | MNode[]): Promise<void> {
485
+ await storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], {
486
+ protocol: Protocol.OBJECT,
487
+ });
486
488
  }
487
489
 
488
490
  /**
489
491
  * 从localStorage中获取节点,然后添加到当前容器中
490
- * @param position 如果设置,指定组件位置
492
+ * @param position 粘贴的坐标
491
493
  * @returns 添加后的组件节点配置
492
494
  */
493
- public async paste(position: { left?: number; top?: number } = {}): Promise<MNode | void> {
494
- const configStr = globalThis.localStorage.getItem(COPY_STORAGE_KEY);
495
- // eslint-disable-next-line prefer-const
496
- let config: any = {};
497
- if (!configStr) {
498
- return;
499
- }
500
-
501
- try {
502
- // eslint-disable-next-line no-eval
503
- eval(`config = ${configStr}`);
504
- } catch (e) {
505
- console.error(e);
506
- return;
507
- }
495
+ public async paste(position: PastePosition = {}): Promise<MNode[] | void> {
496
+ const config = await storageService.getItem(COPY_STORAGE_KEY);
508
497
 
509
- await propsService.setNewItemId(config, this.get('root'));
510
- if (config.style) {
511
- config.style = {
512
- ...config.style,
513
- ...position,
514
- };
515
- }
498
+ if (!config) return;
516
499
 
517
- if (isPage(config)) {
518
- config.name = generatePageNameByApp(this.get('root'));
519
- }
500
+ const pasteConfigs = await beforePaste(position, config);
520
501
 
521
- return await this.add(config);
502
+ return await this.multiAdd(pasteConfigs);
522
503
  }
523
504
 
524
505
  /**
@@ -672,6 +653,7 @@ class Editor extends BaseService {
672
653
  this.removeAllListeners();
673
654
  this.set('root', null);
674
655
  this.set('node', null);
656
+ this.set('nodes', []);
675
657
  this.set('page', null);
676
658
  this.set('parent', null);
677
659
  }
@@ -22,7 +22,7 @@ import { cloneDeep } from 'lodash-es';
22
22
  import { DEFAULT_EVENTS, DEFAULT_METHODS, EventOption } from '@tmagic/core';
23
23
  import { toLine } from '@tmagic/utils';
24
24
 
25
- import type { ComponentGroup } from '@editor/type';
25
+ import type { ComponentGroup } from '../type';
26
26
 
27
27
  import BaseService from './BaseService';
28
28
 
@@ -20,7 +20,7 @@ import { reactive } from 'vue';
20
20
 
21
21
  import type { Id, MPage } from '@tmagic/schema';
22
22
 
23
- import { UndoRedo } from '@editor/utils/undo-redo';
23
+ import { UndoRedo } from '../utils/undo-redo';
24
24
 
25
25
  import BaseService from './BaseService';
26
26
 
@@ -17,15 +17,15 @@
17
17
  */
18
18
 
19
19
  import { reactive } from 'vue';
20
- import { cloneDeep, mergeWith, random } from 'lodash-es';
20
+ import { cloneDeep, mergeWith } from 'lodash-es';
21
21
 
22
22
  import type { FormConfig } from '@tmagic/form';
23
23
  import type { Id, MComponent, MNode, MPage } from '@tmagic/schema';
24
24
  import { NodeType } from '@tmagic/schema';
25
25
  import { isPop, toLine } from '@tmagic/utils';
26
26
 
27
- import type { PropsState } from '@editor/type';
28
- import { DEFAULT_CONFIG, fillConfig, getDefaultPropsValue } from '@editor/utils/props';
27
+ import type { PropsState } from '../type';
28
+ import { DEFAULT_CONFIG, fillConfig } from '../utils/props';
29
29
 
30
30
  import BaseService from './BaseService';
31
31
 
@@ -36,7 +36,15 @@ class Props extends BaseService {
36
36
  });
37
37
 
38
38
  constructor() {
39
- super(['setPropsConfig', 'getPropsConfig', 'setPropsValue', 'getPropsValue', 'createId', 'setNewItemId']);
39
+ super([
40
+ 'setPropsConfig',
41
+ 'getPropsConfig',
42
+ 'setPropsValue',
43
+ 'getPropsValue',
44
+ 'createId',
45
+ 'setNewItemId',
46
+ 'getDefaultPropsValue',
47
+ ]);
40
48
  }
41
49
 
42
50
  public setPropsConfigs(configs: Record<string, FormConfig>) {
@@ -99,18 +107,39 @@ class Props extends BaseService {
99
107
  return value;
100
108
  }
101
109
 
102
- const data = cloneDeep(defaultValue as any);
103
-
104
- await this.setNewItemId(data);
110
+ const [id, defaultPropsValue, data] = await Promise.all([
111
+ this.createId(type),
112
+ this.getDefaultPropsValue(type),
113
+ this.setNewItemId(
114
+ cloneDeep({
115
+ type,
116
+ ...defaultValue,
117
+ } as any),
118
+ ),
119
+ ]);
105
120
 
106
121
  return {
107
- ...getDefaultPropsValue(type, await this.createId(type)),
108
- ...mergeWith(cloneDeep(this.state.propsValueMap[type] || {}), data),
122
+ id,
123
+ ...defaultPropsValue,
124
+ ...mergeWith({}, cloneDeep(this.state.propsValueMap[type] || {}), data),
109
125
  };
110
126
  }
111
127
 
128
+ /**
129
+ * 生成指定位数的GUID,无【-】格式
130
+ * @param digit 位数,默认值8
131
+ * @returns
132
+ */
133
+ guid(digit = 8): string {
134
+ return 'x'.repeat(digit).replace(/[xy]/g, (c) => {
135
+ const r = (Math.random() * 16) | 0;
136
+ const v = c == 'x' ? r : (r & 0x3) | 0x8;
137
+ return v.toString(16);
138
+ });
139
+ }
140
+
112
141
  public async createId(type: string | number): Promise<string> {
113
- return `${type}_${random(10000, false)}`;
142
+ return `${type}_${this.guid()}`;
114
143
  }
115
144
 
116
145
  /**
@@ -133,6 +162,29 @@ class Props extends BaseService {
133
162
  await this.setNewItemId(item, config as MPage);
134
163
  }
135
164
  }
165
+
166
+ return config;
167
+ }
168
+
169
+ /**
170
+ * 获取默认属性配置
171
+ * @param type 组件类型
172
+ * @returns Object
173
+ */
174
+ public async getDefaultPropsValue(type: string) {
175
+ return ['page', 'container'].includes(type)
176
+ ? {
177
+ type,
178
+ layout: 'absolute',
179
+ style: {},
180
+ name: type,
181
+ items: [],
182
+ }
183
+ : {
184
+ type,
185
+ style: {},
186
+ name: type,
187
+ };
136
188
  }
137
189
  }
138
190
 
@@ -0,0 +1,133 @@
1
+ import serialize from 'serialize-javascript';
2
+
3
+ import BaseService from './BaseService';
4
+
5
+ interface Options {
6
+ namespace?: string;
7
+ protocol?: Protocol;
8
+ }
9
+
10
+ export enum Protocol {
11
+ OBJECT = 'object',
12
+ JSON = 'json',
13
+ STRING = 'string',
14
+ NUMBER = 'number',
15
+ BOOLEAN = 'boolean',
16
+ }
17
+
18
+ /**
19
+ * 数据存储服务
20
+ */
21
+ export class WebStorage extends BaseService {
22
+ private storage: Storage = globalThis.localStorage;
23
+ private namespace = 'tmagic';
24
+
25
+ constructor() {
26
+ super(['getStorage', 'getNamespace', 'clear', 'getItem', 'removeItem', 'setItem']);
27
+ }
28
+
29
+ /**
30
+ * 获取数据存储对象,可以通过
31
+ * const storageService = new StorageService();
32
+ * storageService.usePlugin({
33
+ * // 替换存储对象为 sessionStorage
34
+ * async afterGetStorage(): Promise<Storage> {
35
+ * return window.sessionStorage;
36
+ * },
37
+ * });
38
+ */
39
+ public async getStorage(): Promise<Storage> {
40
+ return this.storage;
41
+ }
42
+
43
+ public async getNamespace(): Promise<string> {
44
+ return this.namespace;
45
+ }
46
+
47
+ /**
48
+ * 清理,支持storageService.usePlugin
49
+ */
50
+ public async clear(): Promise<void> {
51
+ const storage = await this.getStorage();
52
+ storage.clear();
53
+ }
54
+ /**
55
+ * 获取存储项,支持storageService.usePlugin
56
+ */
57
+ public async getItem(key: string, options: Options = {}): Promise<any> {
58
+ const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]);
59
+ const { protocol = options.protocol, item } = this.getValueAndProtocol(
60
+ storage.getItem(`${options.namespace || namespace}:${key}`),
61
+ );
62
+
63
+ if (item === null) return null;
64
+
65
+ switch (protocol) {
66
+ case Protocol.OBJECT:
67
+ // eslint-disable-next-line no-eval
68
+ return eval(`(${item})`);
69
+ case Protocol.JSON:
70
+ return JSON.parse(item);
71
+ case Protocol.NUMBER:
72
+ return Number(item);
73
+ case Protocol.BOOLEAN:
74
+ return Boolean(item);
75
+ default:
76
+ return item;
77
+ }
78
+ }
79
+ /**
80
+ * 获取指定索引位置的key
81
+ */
82
+ public async key(index: number): Promise<string | null> {
83
+ const storage = await this.getStorage();
84
+ return storage.key(index);
85
+ }
86
+
87
+ /**
88
+ * 移除存储项,支持storageService.usePlugin
89
+ */
90
+ public async removeItem(key: string, options: Options = {}): Promise<void> {
91
+ const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]);
92
+ storage.removeItem(`${options.namespace || namespace}:${key}`);
93
+ }
94
+
95
+ /**
96
+ * 设置存储项,支持storageService.usePlugin
97
+ */
98
+ public async setItem(key: string, value: any, options: Options = {}): Promise<void> {
99
+ const [storage, namespace] = await Promise.all([this.getStorage(), this.getNamespace()]);
100
+ let item = value;
101
+ const protocol = options.protocol ? `${options.protocol}:` : '';
102
+ if (typeof value === Protocol.STRING || typeof value === Protocol.NUMBER) {
103
+ item = `${protocol}${value}`;
104
+ } else {
105
+ item = `${protocol}${serialize(value)}`;
106
+ }
107
+ storage.setItem(`${options.namespace || namespace}:${key}`, item);
108
+ }
109
+
110
+ private getValueAndProtocol(value: string | null) {
111
+ let protocol = '';
112
+
113
+ if (value === null) {
114
+ return {
115
+ item: value,
116
+ protocol,
117
+ };
118
+ }
119
+
120
+ const item = value.replace(new RegExp(`^(${Object.values(Protocol).join('|')})(:)(.+)`), (_$0, $1, _$2, $3) => {
121
+ protocol = $1;
122
+ return $3;
123
+ });
124
+
125
+ return {
126
+ protocol,
127
+ item,
128
+ };
129
+ }
130
+ }
131
+
132
+ export type StorageService = WebStorage;
133
+ export default new WebStorage();
@@ -20,8 +20,8 @@ import { reactive, toRaw } from 'vue';
20
20
 
21
21
  import type StageCore from '@tmagic/stage';
22
22
 
23
- import editorService from '@editor/services/editor';
24
- import type { GetColumnWidth, SetColumnWidth, StageRect, UiState } from '@editor/type';
23
+ import editorService from '../services/editor';
24
+ import type { GetColumnWidth, SetColumnWidth, StageRect, UiState } from '../type';
25
25
 
26
26
  import BaseService from './BaseService';
27
27