@tmagic/editor 1.1.0-beta.5 → 1.1.0-beta.8

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 (103) hide show
  1. package/dist/style.css +4 -0
  2. package/dist/{tmagic-editor.es.js → tmagic-editor.mjs} +1417 -1228
  3. package/dist/tmagic-editor.mjs.map +1 -0
  4. package/dist/tmagic-editor.umd.js +1433 -1242
  5. package/dist/tmagic-editor.umd.js.map +1 -1
  6. package/package.json +21 -19
  7. package/src/Editor.vue +14 -12
  8. package/src/components/ContentMenu.vue +87 -99
  9. package/src/components/Icon.vue +1 -1
  10. package/src/components/ScrollViewer.vue +1 -1
  11. package/src/components/ToolButton.vue +150 -167
  12. package/src/fields/UISelect.vue +49 -70
  13. package/src/index.ts +2 -1
  14. package/src/layouts/AddPageBox.vue +2 -2
  15. package/src/layouts/Framework.vue +1 -1
  16. package/src/layouts/NavMenu.vue +2 -2
  17. package/src/layouts/PropsPanel.vue +1 -1
  18. package/src/layouts/Resizer.vue +1 -1
  19. package/src/layouts/sidebar/ComponentListPanel.vue +2 -2
  20. package/src/layouts/sidebar/LayerMenu.vue +2 -2
  21. package/src/layouts/sidebar/LayerPanel.vue +3 -3
  22. package/src/layouts/sidebar/Sidebar.vue +1 -1
  23. package/src/layouts/sidebar/TabPane.vue +2 -2
  24. package/src/layouts/workspace/PageBar.vue +63 -192
  25. package/src/layouts/workspace/PageBarScrollContainer.vue +111 -0
  26. package/src/layouts/workspace/Stage.vue +195 -222
  27. package/src/layouts/workspace/ViewerMenu.vue +140 -147
  28. package/src/layouts/workspace/Workspace.vue +11 -11
  29. package/src/services/BaseService.ts +1 -1
  30. package/src/services/componentList.ts +3 -3
  31. package/src/services/editor.ts +137 -139
  32. package/src/services/events.ts +1 -1
  33. package/src/services/history.ts +1 -1
  34. package/src/services/props.ts +18 -5
  35. package/src/services/storage.ts +133 -0
  36. package/src/services/ui.ts +2 -2
  37. package/src/theme/component-list-panel.scss +5 -0
  38. package/src/type.ts +22 -6
  39. package/src/utils/config.ts +1 -1
  40. package/src/utils/editor.ts +18 -11
  41. package/src/utils/operator.ts +163 -0
  42. package/src/utils/props.ts +2 -2
  43. package/src/utils/scroll-viewer.ts +1 -1
  44. package/tsconfig.build.json +13 -0
  45. package/{dist/types/src → types}/Editor.vue.d.ts +7 -8
  46. package/types/components/ContentMenu.vue.d.ts +116 -0
  47. package/types/components/Icon.vue.d.ts +13 -0
  48. package/types/components/ScrollViewer.vue.d.ts +22 -0
  49. package/types/components/ToolButton.vue.d.ts +109 -0
  50. package/types/fields/Code.vue.d.ts +23 -0
  51. package/{dist/types/src → types}/fields/CodeLink.vue.d.ts +4 -6
  52. package/types/fields/UISelect.vue.d.ts +83 -0
  53. package/{dist/types/src → types}/index.d.ts +4 -1
  54. package/types/layouts/AddPageBox.vue.d.ts +4 -0
  55. package/{dist/types/src → types}/layouts/CodeEditor.vue.d.ts +11 -13
  56. package/types/layouts/Framework.vue.d.ts +22 -0
  57. package/types/layouts/NavMenu.vue.d.ts +25 -0
  58. package/types/layouts/PropsPanel.vue.d.ts +245 -0
  59. package/types/layouts/Resizer.vue.d.ts +12 -0
  60. package/types/layouts/sidebar/ComponentListPanel.vue.d.ts +14 -0
  61. package/types/layouts/sidebar/LayerMenu.vue.d.ts +100 -0
  62. package/types/layouts/sidebar/LayerPanel.vue.d.ts +1099 -0
  63. package/{dist/types/src → types}/layouts/sidebar/Sidebar.vue.d.ts +3 -5
  64. package/types/layouts/sidebar/TabPane.vue.d.ts +14 -0
  65. package/types/layouts/workspace/PageBar.vue.d.ts +54 -0
  66. package/types/layouts/workspace/PageBarScrollContainer.vue.d.ts +48 -0
  67. package/types/layouts/workspace/Stage.vue.d.ts +46 -0
  68. package/types/layouts/workspace/ViewerMenu.vue.d.ts +88 -0
  69. package/types/layouts/workspace/Workspace.vue.d.ts +6 -0
  70. package/{dist/types/src → types}/services/BaseService.d.ts +0 -0
  71. package/types/services/componentList.d.ts +14 -0
  72. package/{dist/types/src → types}/services/editor.d.ts +22 -12
  73. package/{dist/types/src → types}/services/events.d.ts +0 -0
  74. package/{dist/types/src → types}/services/history.d.ts +0 -0
  75. package/{dist/types/src → types}/services/props.d.ts +6 -0
  76. package/types/services/storage.d.ts +56 -0
  77. package/{dist/types/src → types}/services/ui.d.ts +1 -1
  78. package/{dist/types/src → types}/type.d.ts +15 -0
  79. package/{dist/types/src → types}/utils/compose.d.ts +1 -1
  80. package/{dist/types/src → types}/utils/config.d.ts +0 -0
  81. package/{dist/types/src → types}/utils/editor.d.ts +6 -3
  82. package/{dist/types/src → types}/utils/index.d.ts +0 -0
  83. package/{dist/types/src → types}/utils/logger.d.ts +0 -0
  84. package/types/utils/operator.d.ts +27 -0
  85. package/{dist/types/src → types}/utils/polyfills.d.ts +0 -0
  86. package/{dist/types/src → types}/utils/props.d.ts +2 -2
  87. package/{dist/types/src → types}/utils/scroll-viewer.d.ts +0 -0
  88. package/{dist/types/src → types}/utils/undo-redo.d.ts +0 -0
  89. package/dist/tmagic-editor.es.js.map +0 -1
  90. package/dist/types/src/components/Icon.vue.d.ts +0 -14
  91. package/dist/types/src/components/ScrollViewer.vue.d.ts +0 -24
  92. package/dist/types/src/fields/Code.vue.d.ts +0 -25
  93. package/dist/types/src/layouts/AddPageBox.vue.d.ts +0 -5
  94. package/dist/types/src/layouts/Framework.vue.d.ts +0 -24
  95. package/dist/types/src/layouts/NavMenu.vue.d.ts +0 -27
  96. package/dist/types/src/layouts/PropsPanel.vue.d.ts +0 -13
  97. package/dist/types/src/layouts/Resizer.vue.d.ts +0 -14
  98. package/dist/types/src/layouts/sidebar/ComponentListPanel.vue.d.ts +0 -16
  99. package/dist/types/src/layouts/sidebar/TabPane.vue.d.ts +0 -16
  100. package/dist/types/src/layouts/workspace/Workspace.vue.d.ts +0 -8
  101. package/dist/types/src/shims-vue.d.ts +0 -6
  102. package/dist/types/src/vite-env.d.ts +0 -1
  103. package/src/vite-env.d.ts +0 -1
@@ -17,51 +17,52 @@
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,
35
+ fixNodePosition,
38
36
  getInitPositionStyle,
39
37
  getNodeIndex,
40
38
  isFixed,
41
39
  setLayout,
42
- } from '@editor/utils/editor';
40
+ } from '../utils/editor';
41
+ import { beforePaste, beforeRemove, getAddParent } from '../utils/operator';
43
42
 
44
43
  import BaseService from './BaseService';
44
+ import propsService from './props';
45
45
 
46
46
  class Editor extends BaseService {
47
- private isHistoryStateChange = false;
48
-
49
- private state = reactive<StoreState>({
47
+ public state: StoreState = reactive({
50
48
  root: null,
51
49
  page: null,
52
50
  parent: null,
53
51
  node: null,
52
+ nodes: [],
54
53
  stage: null,
55
54
  highlightNode: null,
56
55
  modifiedNodeIds: new Map(),
57
56
  pageLength: 0,
58
57
  });
58
+ private isHistoryStateChange = false;
59
59
 
60
60
  constructor() {
61
61
  super(
62
62
  [
63
63
  'getLayout',
64
64
  'select',
65
+ 'doAdd',
65
66
  'add',
66
67
  'remove',
67
68
  'update',
@@ -83,13 +84,16 @@ class Editor extends BaseService {
83
84
 
84
85
  /**
85
86
  * 设置当前指点节点配置
86
- * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode'
87
+ * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes'
87
88
  * @param value MNode
88
89
  * @returns MNode
89
90
  */
90
91
  public set<T = MNode>(name: keyof StoreState, value: T) {
91
92
  this.state[name] = value as any;
92
-
93
+ // set nodes时将node设置为nodes第一个元素
94
+ if (name === 'nodes') {
95
+ this.set('node', (value as unknown as MNode[])[0]);
96
+ }
93
97
  if (name === 'root') {
94
98
  this.state.pageLength = (value as unknown as MApp)?.items?.length || 0;
95
99
  this.emit('root-change', value);
@@ -98,7 +102,7 @@ class Editor extends BaseService {
98
102
 
99
103
  /**
100
104
  * 获取当前指点节点配置
101
- * @param name 'root' | 'page' | 'parent' | 'node'
105
+ * @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes'
102
106
  * @returns MNode
103
107
  */
104
108
  public get<T = MNode>(name: keyof StoreState): T {
@@ -190,7 +194,7 @@ class Editor extends BaseService {
190
194
  */
191
195
  public async select(config: MNode | Id): Promise<MNode> | never {
192
196
  const { node, page, parent } = this.selectedConfigExceptionHandler(config);
193
- this.set('node', node);
197
+ this.set('nodes', [node]);
194
198
  this.set('page', page || null);
195
199
  this.set('parent', parent || null);
196
200
 
@@ -265,123 +269,113 @@ class Editor extends BaseService {
265
269
  }
266
270
 
267
271
  /**
268
- * 向指点容器添加组件节点
269
- * @param addConfig 将要添加的组件节点配置
270
- * @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
271
- * @returns 添加后的节点
272
+ * 多选
273
+ * @param ids 指定节点ID
274
+ * @returns 加入多选的节点配置
272
275
  */
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;
280
-
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
- }
291
-
292
- if (!parentNode) throw new Error('未找到父元素');
276
+ public multiSelect(ids: Id[]): void {
277
+ const nodes: MNode[] = [];
278
+ const idsUnique = uniq(ids);
279
+ idsUnique.forEach((id) => {
280
+ const { node } = this.getNodeInfo(id);
281
+ if (!node) return;
282
+ nodes.push(node);
283
+ });
284
+ this.set('nodes', nodes);
285
+ }
293
286
 
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'));
287
+ public async doAdd(node: MNode, parent: MContainer): Promise<MNode> {
288
+ const root = this.get<MApp>('root');
289
+ const curNode = this.get<MNode>('node');
290
+ const stage = this.get<StageCore | null>('stage');
297
291
 
298
- if ((parentNode?.type === NodeType.ROOT || curNode.type === NodeType.ROOT) && newNode.type !== NodeType.PAGE) {
292
+ if ((parent?.type === NodeType.ROOT || curNode.type === NodeType.ROOT) && node.type !== NodeType.PAGE) {
299
293
  throw new Error('app下不能添加组件');
300
294
  }
301
295
 
302
- parentNode?.items?.push(newNode);
303
-
304
- const stage = this.get<StageCore | null>('stage');
305
- const root = this.get<MApp>('root');
296
+ const layout = await this.getLayout(toRaw(parent), node as MNode);
297
+ node.style = getInitPositionStyle(node.style, layout);
306
298
 
307
- await stage?.add({ config: cloneDeep(newNode), parent: cloneDeep(parentNode), root: cloneDeep(root) });
299
+ await stage?.add({ config: cloneDeep(node), parent: cloneDeep(parent), root: cloneDeep(root) });
308
300
 
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
- }
301
+ node.style = fixNodePosition(node, parent, stage);
316
302
 
317
- await this.select(newNode);
303
+ await stage?.update({ config: cloneDeep(node), root: cloneDeep(root) });
318
304
 
319
- this.addModifiedNodeId(newNode.id);
320
- if (!isPage) {
321
- this.pushHistoryState();
322
- }
323
-
324
- if (isPage) {
325
- this.state.pageLength += 1;
326
- } else {
327
- // 新增页面,这个时候页面还有渲染出来,此时select会出错,在runtime-ready的时候回去select
328
- stage?.select(newNode.id);
329
- }
305
+ // 新增节点添加到配置中
306
+ parent?.items?.push(node);
330
307
 
331
- this.emit('add', newNode);
308
+ this.addModifiedNodeId(node.id);
332
309
 
333
- return newNode;
310
+ return node;
334
311
  }
335
312
 
336
313
  /**
337
- * 删除组件
338
- * @param {Object} node
339
- * @return {Object} 删除的组件配置
314
+ * 向指点容器添加组件节点
315
+ * @param addConfig 将要添加的组件节点配置
316
+ * @param parent 要添加到的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
317
+ * @returns 添加后的节点
340
318
  */
341
- public async remove(node: MNode): Promise<MNode | void> {
342
- if (!node?.id) return;
319
+ public async add(addNode: AddMNode | MNode[], parent?: MContainer | null): Promise<MNode | MNode[]> {
320
+ const stage = this.get<StageCore | null>('stage');
343
321
 
344
- const root = this.get<MApp | null>('root');
322
+ const parentNode = parent && typeof parent !== 'function' ? parent : getAddParent(addNode);
323
+ if (!parentNode) throw new Error('未找到父元素');
345
324
 
346
- if (!root) throw new Error('没有root');
325
+ // 新增多个组件只存在于粘贴多个组件,粘贴的是一个完整的config,所以不再需要getPropsValue
326
+ const addNodes = [];
327
+ if (!Array.isArray(addNode)) {
328
+ const { type, inputEvent, ...config } = addNode;
347
329
 
348
- const { parent, node: curNode } = this.getNodeInfo(node.id, false);
330
+ if (!type) throw new Error('组件类型不能为空');
349
331
 
350
- if (!parent || !curNode) throw new Error('找不要删除的节点');
332
+ addNodes.push({ ...toRaw(await propsService.getPropsValue(type, config)) });
333
+ } else {
334
+ addNodes.push(...addNode);
335
+ }
351
336
 
352
- const index = getNodeIndex(curNode, parent);
337
+ const newNodes = await Promise.all(addNodes.map((node) => this.doAdd(node, parentNode)));
353
338
 
354
- if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
339
+ if (newNodes.length > 1) {
340
+ const newNodeIds = newNodes.map((node) => node.id);
341
+ // 触发选中样式
342
+ stage?.multiSelect(newNodeIds);
343
+ await this.multiSelect(newNodeIds);
344
+ } else {
345
+ await this.select(newNodes[0]);
355
346
 
356
- parent.items?.splice(index, 1);
357
- const stage = this.get<StageCore | null>('stage');
358
- stage?.remove({ id: node.id, root: cloneDeep(this.get('root')) });
347
+ if (isPage(newNodes[0])) {
348
+ this.state.pageLength += 1;
349
+ } else {
350
+ // 新增页面,这个时候页面还有渲染出来,此时select会出错,在runtime-ready的时候回去select
351
+ stage?.select(newNodes[0].id);
352
+ }
353
+ }
359
354
 
360
- if (node.type === NodeType.PAGE) {
361
- this.state.pageLength -= 1;
355
+ this.pushHistoryState();
362
356
 
363
- if (root.items[0]) {
364
- await this.select(root.items[0]);
365
- stage?.select(root.items[0].id);
366
- } else {
367
- this.set('node', null);
368
- this.set('parent', null);
369
- this.set('page', null);
370
- this.set('stage', null);
371
- this.set('highlightNode', null);
372
- this.resetModifiedNodeId();
373
- historyService.reset();
357
+ this.emit('add', newNodes);
374
358
 
375
- this.emit('remove', node);
359
+ return newNodes.length > 1 ? newNodes[0] : newNodes;
360
+ }
376
361
 
377
- return node;
378
- }
379
- } else {
380
- await this.select(parent);
381
- stage?.select(parent.id);
362
+ /**
363
+ * 删除组件
364
+ * @param {Object} node
365
+ * @return {Object} 删除的组件配置
366
+ */
367
+ public async remove(nodeOrNodeList: MNode | MNode[]): Promise<MNode | MNode[]> {
368
+ if (Array.isArray(nodeOrNodeList)) {
369
+ // 多选批量删除
370
+ const nodes = nodeOrNodeList;
371
+ return this.multiRemove(nodes);
382
372
  }
383
-
384
- this.addModifiedNodeId(parent.id);
373
+ const node = nodeOrNodeList;
374
+ const removeParent = await beforeRemove(node);
375
+ // 删除的是页面
376
+ if (!removeParent) return node;
377
+ // 更新历史记录
378
+ this.addModifiedNodeId(removeParent.id);
385
379
  this.pushHistoryState();
386
380
 
387
381
  this.emit('remove', node);
@@ -389,6 +383,25 @@ class Editor extends BaseService {
389
383
  return node;
390
384
  }
391
385
 
386
+ /**
387
+ * 批量删除
388
+ * @param nodes 批量删除的节点
389
+ * @returns 批量删除的节点
390
+ */
391
+ public async multiRemove(nodes: MNode[]): Promise<MNode[]> {
392
+ await Promise.all(
393
+ nodes.map(async (removeNode) => {
394
+ await beforeRemove(removeNode);
395
+ }),
396
+ );
397
+ const nodeIds = nodes.map((node) => node.id);
398
+ this.addModifiedNodeId(nodeIds.join('-'));
399
+ this.pushHistoryState();
400
+
401
+ this.emit('multiRemove', nodes);
402
+ return nodes;
403
+ }
404
+
392
405
  /**
393
406
  * 更新节点
394
407
  * @param config 新的节点配置,配置中需要有id信息
@@ -434,9 +447,11 @@ class Editor extends BaseService {
434
447
 
435
448
  parentNodeItems[index] = newConfig;
436
449
 
437
- if (`${newConfig.id}` === `${this.get('node').id}`) {
438
- this.set('node', newConfig);
439
- }
450
+ // 将update后的配置更新到nodes中
451
+ const nodes = this.get('nodes');
452
+ const targetIndex = nodes.findIndex((nodeItem: MNode) => `${nodeItem.id}` === `${newConfig.id}`);
453
+ nodes.splice(targetIndex, 1, newConfig);
454
+ this.set('nodes', nodes);
440
455
 
441
456
  this.get<StageCore | null>('stage')?.update({ config: cloneDeep(newConfig), root: cloneDeep(this.get('root')) });
442
457
 
@@ -482,44 +497,25 @@ class Editor extends BaseService {
482
497
  * @param config 组件节点配置
483
498
  * @returns 组件节点配置
484
499
  */
485
- public async copy(config: MNode): Promise<void> {
486
- globalThis.localStorage.setItem(COPY_STORAGE_KEY, serialize(config));
500
+ public async copy(config: MNode | MNode[]): Promise<void> {
501
+ await storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], {
502
+ protocol: Protocol.OBJECT,
503
+ });
487
504
  }
488
505
 
489
506
  /**
490
507
  * 从localStorage中获取节点,然后添加到当前容器中
491
- * @param position 如果设置,指定组件位置
508
+ * @param position 粘贴的坐标
492
509
  * @returns 添加后的组件节点配置
493
510
  */
494
- public async paste(position: { left?: number; top?: number } = {}): Promise<MNode | void> {
495
- const configStr = globalThis.localStorage.getItem(COPY_STORAGE_KEY);
496
- // eslint-disable-next-line prefer-const
497
- let config: any = {};
498
- if (!configStr) {
499
- return;
500
- }
511
+ public async paste(position: PastePosition = {}): Promise<MNode | MNode[] | void> {
512
+ const config: MNode[] = await storageService.getItem(COPY_STORAGE_KEY);
501
513
 
502
- try {
503
- // eslint-disable-next-line no-eval
504
- eval(`config = ${configStr}`);
505
- } catch (e) {
506
- console.error(e);
507
- return;
508
- }
509
-
510
- config = await propsService.setNewItemId(config, this.get('root'));
511
- if (config.style) {
512
- config.style = {
513
- ...config.style,
514
- ...position,
515
- };
516
- }
514
+ if (!Array.isArray(config)) return;
517
515
 
518
- if (isPage(config)) {
519
- config.name = generatePageNameByApp(this.get('root'));
520
- }
516
+ const pasteConfigs = await beforePaste(position, config);
521
517
 
522
- return await this.add(config);
518
+ return this.add(pasteConfigs);
523
519
  }
524
520
 
525
521
  /**
@@ -610,7 +606,7 @@ class Editor extends BaseService {
610
606
  return srcValue;
611
607
  }
612
608
  });
613
- newConfig.style = getInitPositionStyle(newConfig.style, layout, target, stage);
609
+ newConfig.style = getInitPositionStyle(newConfig.style, layout);
614
610
 
615
611
  target.items.push(newConfig);
616
612
 
@@ -653,7 +649,7 @@ class Editor extends BaseService {
653
649
  const node = toRaw(this.get('node'));
654
650
  if (!node || isPage(node)) return;
655
651
 
656
- const { style, id } = node;
652
+ const { style, id, type } = node;
657
653
  if (!style || style.position !== 'absolute') return;
658
654
 
659
655
  if (top && !isNumber(style.top)) return;
@@ -661,6 +657,7 @@ class Editor extends BaseService {
661
657
 
662
658
  this.update({
663
659
  id,
660
+ type,
664
661
  style: {
665
662
  ...style,
666
663
  left: Number(style.left) + left,
@@ -673,6 +670,7 @@ class Editor extends BaseService {
673
670
  this.removeAllListeners();
674
671
  this.set('root', null);
675
672
  this.set('node', null);
673
+ this.set('nodes', []);
676
674
  this.set('page', null);
677
675
  this.set('parent', null);
678
676
  }
@@ -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 } 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
 
@@ -121,12 +121,25 @@ class Props extends BaseService {
121
121
  return {
122
122
  id,
123
123
  ...defaultPropsValue,
124
- ...mergeWith(cloneDeep(this.state.propsValueMap[type] || {}), data),
124
+ ...mergeWith({}, cloneDeep(this.state.propsValueMap[type] || {}), data),
125
125
  };
126
126
  }
127
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
+
128
141
  public async createId(type: string | number): Promise<string> {
129
- return `${type}_${random(10000, false)}`;
142
+ return `${type}_${this.guid()}`;
130
143
  }
131
144
 
132
145
  /**
@@ -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
 
@@ -86,6 +86,11 @@
86
86
  }
87
87
  }
88
88
 
89
+ img {
90
+ max-width: 100%;
91
+ max-height: 100%;
92
+ }
93
+
89
94
  span {
90
95
  font-size: 12px;
91
96
  text-align: center;