@tmagic/editor 1.3.4 → 1.3.6

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 +73 -51
  2. package/dist/tmagic-editor.js +1171 -1102
  3. package/dist/tmagic-editor.js.map +1 -1
  4. package/dist/tmagic-editor.umd.cjs +1188 -1119
  5. package/dist/tmagic-editor.umd.cjs.map +1 -1
  6. package/package.json +17 -17
  7. package/src/Editor.vue +5 -0
  8. package/src/components/FloatingBox.vue +117 -0
  9. package/src/components/Tree.vue +5 -0
  10. package/src/components/TreeNode.vue +8 -2
  11. package/src/editorProps.ts +7 -0
  12. package/src/hooks/use-filter.ts +55 -0
  13. package/src/hooks/use-node-status.ts +48 -0
  14. package/src/hooks/use-stage.ts +1 -0
  15. package/src/initService.ts +28 -12
  16. package/src/layouts/sidebar/Sidebar.vue +7 -2
  17. package/src/layouts/sidebar/code-block/CodeBlockList.vue +55 -65
  18. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +2 -1
  19. package/src/layouts/sidebar/data-source/DataSourceList.vue +63 -59
  20. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +1 -1
  21. package/src/layouts/sidebar/layer/LayerPanel.vue +24 -7
  22. package/src/layouts/sidebar/layer/use-node-status.ts +8 -11
  23. package/src/layouts/workspace/viewer/NodeListMenu.vue +67 -128
  24. package/src/layouts/workspace/viewer/Stage.vue +2 -1
  25. package/src/layouts/workspace/viewer/ViewerMenu.vue +1 -6
  26. package/src/services/BaseService.ts +63 -26
  27. package/src/services/codeBlock.ts +7 -1
  28. package/src/services/dep.ts +12 -8
  29. package/src/services/editor.ts +66 -30
  30. package/src/services/props.ts +70 -12
  31. package/src/services/storage.ts +23 -13
  32. package/src/services/ui.ts +4 -1
  33. package/src/theme/floating-box.scss +30 -0
  34. package/src/theme/layer-panel.scss +19 -0
  35. package/src/theme/stage.scss +24 -0
  36. package/src/theme/theme.scss +1 -1
  37. package/src/theme/tree.scss +12 -1
  38. package/src/type.ts +6 -3
  39. package/src/utils/compose.ts +25 -6
  40. package/src/utils/content-menu.ts +4 -5
  41. package/src/utils/editor.ts +12 -3
  42. package/src/utils/operator.ts +1 -2
  43. package/types/components/CodeBlockEditor.vue.d.ts +6 -2
  44. package/types/components/CodeParams.vue.d.ts +3 -1
  45. package/types/components/ContentMenu.vue.d.ts +13 -5
  46. package/types/components/FloatingBox.vue.d.ts +78 -0
  47. package/types/components/Resizer.vue.d.ts +6 -1
  48. package/types/components/ScrollBar.vue.d.ts +3 -1
  49. package/types/components/ScrollViewer.vue.d.ts +3 -3
  50. package/types/components/SearchInput.vue.d.ts +3 -1
  51. package/types/components/SplitView.vue.d.ts +8 -4
  52. package/types/components/ToolButton.vue.d.ts +1 -1
  53. package/types/components/Tree.vue.d.ts +27 -8
  54. package/types/components/TreeNode.vue.d.ts +24 -7
  55. package/types/editorProps.d.ts +7 -1
  56. package/types/fields/Code.vue.d.ts +6 -2
  57. package/types/fields/CodeLink.vue.d.ts +5 -3
  58. package/types/fields/CodeSelect.vue.d.ts +3 -1
  59. package/types/fields/CodeSelectCol.vue.d.ts +3 -1
  60. package/types/fields/DataSourceFieldSelect.vue.d.ts +3 -1
  61. package/types/fields/DataSourceFields.vue.d.ts +5 -3
  62. package/types/fields/DataSourceInput.vue.d.ts +8 -4
  63. package/types/fields/DataSourceMethodSelect.vue.d.ts +3 -1
  64. package/types/fields/DataSourceMethods.vue.d.ts +5 -3
  65. package/types/fields/DataSourceMocks.vue.d.ts +5 -3
  66. package/types/fields/DataSourceSelect.vue.d.ts +5 -3
  67. package/types/fields/EventSelect.vue.d.ts +3 -1
  68. package/types/fields/KeyValue.vue.d.ts +2 -2
  69. package/types/fields/UISelect.vue.d.ts +3 -1
  70. package/types/hooks/use-code-block-edit.d.ts +14 -5
  71. package/types/hooks/use-data-source-method.d.ts +14 -5
  72. package/types/hooks/use-filter.d.ts +7 -0
  73. package/types/hooks/use-node-status.d.ts +6 -0
  74. package/types/layouts/CodeEditor.vue.d.ts +4 -1
  75. package/types/layouts/Framework.vue.d.ts +1 -1
  76. package/types/layouts/PropsPanel.vue.d.ts +10 -4
  77. package/types/layouts/sidebar/ComponentListPanel.vue.d.ts +1 -1
  78. package/types/layouts/sidebar/Sidebar.vue.d.ts +5 -5
  79. package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +9 -3
  80. package/types/layouts/sidebar/code-block/CodeBlockListPanel.vue.d.ts +1 -1
  81. package/types/layouts/sidebar/data-source/DataSourceConfigPanel.vue.d.ts +3 -1
  82. package/types/layouts/sidebar/data-source/DataSourceList.vue.d.ts +8 -2
  83. package/types/layouts/sidebar/data-source/DataSourceListPanel.vue.d.ts +1 -1
  84. package/types/layouts/sidebar/layer/LayerMenu.vue.d.ts +6 -2
  85. package/types/layouts/sidebar/layer/LayerPanel.vue.d.ts +1 -1
  86. package/types/layouts/sidebar/layer/use-click.d.ts +14 -5
  87. package/types/layouts/sidebar/layer/use-node-status.d.ts +3 -4
  88. package/types/layouts/workspace/Workspace.vue.d.ts +1 -1
  89. package/types/layouts/workspace/viewer/NodeListMenu.vue.d.ts +1 -14
  90. package/types/layouts/workspace/viewer/ViewerMenu.vue.d.ts +1 -1
  91. package/types/services/BaseService.d.ts +4 -1
  92. package/types/services/dep.d.ts +6 -5
  93. package/types/services/editor.d.ts +9 -3
  94. package/types/services/props.d.ts +25 -1
  95. package/types/services/storage.d.ts +7 -7
  96. package/types/type.d.ts +8 -6
  97. package/types/utils/compose.d.ts +1 -1
  98. package/types/utils/editor.d.ts +5 -1
  99. package/src/layouts/sidebar/layer/use-filter.ts +0 -59
  100. package/src/layouts/workspace/viewer/NodeListMenuTitle.vue +0 -68
  101. package/src/theme/dep-list.scss +0 -59
  102. package/types/layouts/sidebar/layer/use-filter.d.ts +0 -7
  103. package/types/layouts/workspace/viewer/NodeListMenuTitle.vue.d.ts +0 -15
@@ -1,112 +1,68 @@
1
1
  <template>
2
- <ContentMenu
3
- ref="menu"
4
- class="magic-editor-node-list-menu"
5
- style="max-width: 280px"
6
- :menu-data="menuData"
7
- :active="node?.id"
8
- :auto-hide="!pinned"
9
- @mouseenter="mouseenterHandler()"
2
+ <TMagicTooltip v-if="page && buttonVisible" content="点击查看当前位置下的组件">
3
+ <div ref="button" class="m-editor-stage-float-button" @click="visible = true">可选组件</div>
4
+ </TMagicTooltip>
5
+ <FloatingBox
6
+ v-if="page && nodeStatusMap && buttonVisible"
7
+ ref="box"
8
+ v-model:visible="visible"
9
+ title="当前位置下的组件"
10
+ :position="menuPosition"
10
11
  >
11
- <template #title>
12
- <NodeListMenuTitle v-model:pinned="pinned" @change="dragMenuHandler" @close="closeHandler"></NodeListMenuTitle>
12
+ <template #body>
13
+ <Tree
14
+ class="m-editor-node-list-menu magic-editor-layer-tree"
15
+ :data="nodeData"
16
+ :node-status-map="nodeStatusMap"
17
+ @node-click="clickHandler"
18
+ ></Tree>
13
19
  </template>
14
- </ContentMenu>
20
+ </FloatingBox>
15
21
  </template>
16
22
 
17
23
  <script lang="ts" setup>
18
- import { Component, computed, inject, ref, watch } from 'vue';
19
- import type { OnDrag } from 'gesto';
24
+ import { computed, inject, nextTick, ref, watch } from 'vue';
20
25
 
26
+ import { TMagicTooltip } from '@tmagic/design';
21
27
  import type { MNode } from '@tmagic/schema';
22
- import { StageDragStatus } from '@tmagic/stage';
23
- import { getNodes } from '@tmagic/utils';
24
28
 
25
- import ContentMenu from '@editor/components/ContentMenu.vue';
26
- import type { ComponentItem, MenuButton, Services } from '@editor/type';
27
-
28
- import NodeListMenuTitle from './NodeListMenuTitle.vue';
29
-
30
- const PINNED_STATUE_CACHE_KEY = 'tmagic-pinned-node-list-pinned-status';
31
-
32
- const props = defineProps<{ isMultiSelect?: boolean }>();
33
-
34
- const menu = ref<InstanceType<typeof ContentMenu>>();
35
- const nodeList = ref<MNode[]>([]);
36
- const pinned = ref(Boolean(globalThis.localStorage.getItem(PINNED_STATUE_CACHE_KEY)));
37
- const firstShow = ref(true);
29
+ import FloatingBox from '@editor/components/FloatingBox.vue';
30
+ import Tree from '@editor/components/Tree.vue';
31
+ import { useFilter } from '@editor/hooks/use-filter';
32
+ import { useNodeStatus } from '@editor/layouts/sidebar/layer/use-node-status';
33
+ import type { Services, TreeNodeData } from '@editor/type';
38
34
 
39
35
  const services = inject<Services>('services');
40
36
  const editorService = services?.editorService;
41
- const componentListService = services?.componentListService;
37
+
38
+ const visible = ref(false);
39
+ const buttonVisible = ref(false);
40
+ const button = ref<HTMLDivElement>();
41
+ const box = ref<InstanceType<typeof FloatingBox>>();
42
42
 
43
43
  const stage = computed(() => editorService?.get('stage'));
44
44
  const page = computed(() => editorService?.get('page'));
45
- const node = computed(() => editorService?.get('node'));
45
+ const nodes = computed(() => editorService?.get('nodes') || []);
46
+ const nodeData = computed<TreeNodeData[]>(() => (!page.value ? [] : [page.value]));
46
47
 
47
- let timeout: NodeJS.Timeout | null = null;
48
+ const { nodeStatusMap } = useNodeStatus(services);
48
49
 
49
- const cancel = () => {
50
- if (timeout) {
51
- globalThis.clearTimeout(timeout);
52
- }
50
+ const filterNodeMethod = (value: string, data: MNode): boolean => data.id === value;
53
51
 
54
- if (pinned.value) {
55
- return;
56
- }
57
-
58
- nodeList.value = [];
59
- menu.value?.hide();
60
- };
61
-
62
- const clearTimeoutLazy = () => {
63
- globalThis.setTimeout(() => {
64
- if (timeout) {
65
- globalThis.clearTimeout(timeout);
66
- }
67
- }, 300);
68
- };
52
+ const { filterTextChangeHandler } = useFilter(nodeData, nodeStatusMap, filterNodeMethod);
69
53
 
70
54
  const unWatch = watch(
71
55
  stage,
72
56
  (stage) => {
73
57
  if (!stage) return;
74
58
 
75
- stage.on('drag-start', () => {
76
- cancel();
77
- });
78
-
79
- stage.on('mousemove', (event: MouseEvent) => {
80
- cancel();
81
-
82
- if (props.isMultiSelect || stage.getDragStatus() !== StageDragStatus.END) {
83
- return;
84
- }
85
-
86
- timeout = globalThis.setTimeout(() => {
87
- const els = stage.renderer.getElementsFromPoint(event) || [];
59
+ stage.on('select', (el: HTMLElement, event: MouseEvent) => {
60
+ const els = stage.renderer.getElementsFromPoint(event) || [];
61
+ const ids = els.map((el) => el.id).filter((id) => Boolean(id));
88
62
 
89
- const nodes = getNodes(
90
- els.map((el) => el.id),
91
- page.value?.items,
92
- );
63
+ buttonVisible.value = ids.length > 3;
93
64
 
94
- if (pinned.value && nodes.length === 0) {
95
- return;
96
- }
97
-
98
- nodeList.value = nodes;
99
-
100
- if (nodeList.value.length > 1) {
101
- menu.value?.show(pinned.value && !firstShow.value ? undefined : event);
102
- firstShow.value = false;
103
- }
104
- }, 1500);
105
- });
106
-
107
- stage.on('mouseleave', () => {
108
- // mouseleave后,大概率还有最后一个mousemove事件,这里延迟清除
109
- clearTimeoutLazy();
65
+ filterTextChangeHandler(ids);
110
66
  });
111
67
 
112
68
  unWatch();
@@ -116,60 +72,43 @@ const unWatch = watch(
116
72
  },
117
73
  );
118
74
 
119
- const componentMap = computed(() => {
120
- const map: Record<string, ComponentItem> = {};
121
- componentListService?.getList().forEach((group) => {
122
- group.items.forEach((item) => {
123
- map[item.type] = item;
124
- });
125
- });
126
- return map;
127
- });
75
+ watch(
76
+ nodes,
77
+ (nodes) => {
78
+ if (!nodeStatusMap.value) return;
128
79
 
129
- const menuData = computed<MenuButton[]>(() =>
130
- nodeList.value.map((node: MNode) => {
131
- let text = node.name;
132
- let icon: string | Component<{}, {}, any> | undefined;
133
- if (node.type) {
134
- const item = componentMap.value[node.type];
135
- text += ` (${item?.text})`;
136
- icon = item?.icon;
80
+ for (const [id, status] of nodeStatusMap.value.entries()) {
81
+ status.selected = nodes.some((node) => node.id === id);
137
82
  }
138
-
139
- return {
140
- type: 'button',
141
- text,
142
- id: node.id,
143
- icon,
144
- handler: async () => {
145
- await editorService?.select(node);
146
- stage.value?.select(node.id);
147
- },
148
- };
149
- }),
83
+ },
84
+ {
85
+ immediate: true,
86
+ },
150
87
  );
151
88
 
152
- const mouseenterHandler = () => {
153
- // menu的mouseenter后,大概率还有最后一个mousemove事件,这里延迟清除
154
- clearTimeoutLazy();
89
+ const clickHandler = async (event: MouseEvent, data: TreeNodeData) => {
90
+ await editorService?.select(data.id);
91
+ stage.value?.select(data.id);
155
92
  };
156
93
 
157
- const dragMenuHandler = ({ deltaY, deltaX }: OnDrag) => {
158
- if (!menu.value) return;
94
+ const menuPosition = ref({
95
+ left: 0,
96
+ top: 0,
97
+ });
159
98
 
160
- const { menuPosition } = menu.value;
99
+ watch(visible, async (visible) => {
100
+ if (!button.value || !visible) {
101
+ return;
102
+ }
161
103
 
162
- menu.value?.setPosition({
163
- clientY: menuPosition.top + deltaY,
164
- clientX: menuPosition.left + deltaX,
165
- });
166
- };
104
+ await nextTick();
167
105
 
168
- const closeHandler = () => {
169
- menu.value?.hide();
170
- };
106
+ const rect = button.value.getBoundingClientRect();
107
+ const height = box.value?.target?.clientHeight || 0;
171
108
 
172
- watch(pinned, () => {
173
- globalThis.localStorage.setItem(PINNED_STATUE_CACHE_KEY, pinned.value.toString());
109
+ menuPosition.value = {
110
+ left: rect.left + rect.width + 5,
111
+ top: rect.top - height / 2 + rect.height / 2,
112
+ };
174
113
  });
175
114
  </script>
@@ -22,9 +22,10 @@
22
22
  @drop="dropHandler"
23
23
  @dragover="dragoverHandler"
24
24
  ></div>
25
+ <NodeListMenu></NodeListMenu>
26
+
25
27
  <Teleport to="body">
26
28
  <ViewerMenu ref="menu" :is-multi-select="isMultiSelect" :stage-content-menu="stageContentMenu"></ViewerMenu>
27
- <NodeListMenu ref="nodeList" :is-multi-select="isMultiSelect"></NodeListMenu>
28
29
  </Teleport>
29
30
  </ScrollViewer>
30
31
  </template>
@@ -11,10 +11,8 @@ import { isPage } from '@tmagic/utils';
11
11
 
12
12
  import ContentMenu from '@editor/components/ContentMenu.vue';
13
13
  import CenterIcon from '@editor/icons/CenterIcon.vue';
14
- import storageService from '@editor/services/storage';
15
14
  import { LayerOffset, Layout, MenuButton, MenuComponent, Services } from '@editor/type';
16
15
  import { useCopyMenu, useDeleteMenu, useMoveToMenu, usePasteMenu } from '@editor/utils/content-menu';
17
- import { COPY_STORAGE_KEY } from '@editor/utils/editor';
18
16
 
19
17
  defineOptions({
20
18
  name: 'MEditorViewerMenu',
@@ -28,7 +26,6 @@ const props = withDefaults(
28
26
  const services = inject<Services>('services');
29
27
  const editorService = services?.editorService;
30
28
  const menu = ref<InstanceType<typeof ContentMenu>>();
31
- const canPaste = ref(false);
32
29
  const canCenter = ref(false);
33
30
 
34
31
  const node = computed(() => editorService?.get('node'));
@@ -127,10 +124,8 @@ watch(
127
124
  { immediate: true },
128
125
  );
129
126
 
130
- const show = async (e: MouseEvent) => {
127
+ const show = (e: MouseEvent) => {
131
128
  menu.value?.show(e);
132
- const data = await storageService.getItem(COPY_STORAGE_KEY);
133
- canPaste.value = data !== 'undefined' && !!data;
134
129
  };
135
130
 
136
131
  defineExpose({ show });
@@ -24,13 +24,48 @@ const methodName = (prefix: string, name: string) => `${prefix}${name[0].toUpper
24
24
 
25
25
  const isError = (error: any): boolean => Object.prototype.toString.call(error) === '[object Error]';
26
26
 
27
- const doAction = async (
27
+ const doAction = (
28
28
  args: any[],
29
29
  scope: any,
30
30
  sourceMethod: any,
31
31
  beforeMethodName: string,
32
32
  afterMethodName: string,
33
- fn: (args: any[], next?: Function | undefined) => Promise<void>,
33
+ fn: (args: any[], next?: Function | undefined) => void,
34
+ ) => {
35
+ try {
36
+ let beforeArgs = args;
37
+
38
+ for (const beforeMethod of scope.pluginOptionsList[beforeMethodName]) {
39
+ beforeArgs = beforeMethod(...beforeArgs) || [];
40
+
41
+ if (isError(beforeArgs)) throw beforeArgs;
42
+
43
+ if (!Array.isArray(beforeArgs)) {
44
+ beforeArgs = [beforeArgs];
45
+ }
46
+ }
47
+
48
+ let returnValue: any = fn(beforeArgs, sourceMethod.bind(scope));
49
+
50
+ for (const afterMethod of scope.pluginOptionsList[afterMethodName]) {
51
+ returnValue = afterMethod(returnValue, ...beforeArgs);
52
+
53
+ if (isError(returnValue)) throw returnValue;
54
+ }
55
+
56
+ return returnValue;
57
+ } catch (error) {
58
+ throw error;
59
+ }
60
+ };
61
+
62
+ const doAsyncAction = async (
63
+ args: any[],
64
+ scope: any,
65
+ sourceMethod: any,
66
+ beforeMethodName: string,
67
+ afterMethodName: string,
68
+ fn: (args: any[], next?: Function | undefined) => Promise<void> | void,
34
69
  ) => {
35
70
  try {
36
71
  let beforeArgs = args;
@@ -112,10 +147,10 @@ export default class extends EventEmitter {
112
147
  private taskList: (() => Promise<void>)[] = [];
113
148
  private doingTask = false;
114
149
 
115
- constructor(methods: string[] = [], serialMethods: string[] = []) {
150
+ constructor(methods: { name: string; isAsync: boolean }[] = [], serialMethods: string[] = []) {
116
151
  super();
117
152
 
118
- methods.forEach((propertyName: string) => {
153
+ methods.forEach(({ name: propertyName, isAsync }) => {
119
154
  const scope = this as any;
120
155
 
121
156
  const sourceMethod = scope[propertyName];
@@ -127,32 +162,34 @@ export default class extends EventEmitter {
127
162
  this.pluginOptionsList[afterMethodName] = [];
128
163
  this.middleware[propertyName] = [];
129
164
 
130
- const fn = compose(this.middleware[propertyName]);
165
+ const fn = compose(this.middleware[propertyName], isAsync);
131
166
  Object.defineProperty(scope, propertyName, {
132
- value: async (...args: any[]) => {
133
- if (!serialMethods.includes(propertyName)) {
134
- return doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
135
- }
136
-
137
- // 由于async await,所以会出现函数执行到await时让出线程,导致执行顺序出错,例如调用了select(1) -> update -> select(2),这个时候就有可能出现update了2;
138
- // 这里保证函数调用严格按顺序执行;
139
- const promise = new Promise<any>((resolve, reject) => {
140
- this.taskList.push(async () => {
141
- try {
142
- const value = await doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
143
- resolve(value);
144
- } catch (e) {
145
- reject(e);
167
+ value: isAsync
168
+ ? async (...args: any[]) => {
169
+ if (!serialMethods.includes(propertyName)) {
170
+ return doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
146
171
  }
147
- });
148
- });
149
172
 
150
- if (!this.doingTask) {
151
- this.doTask();
152
- }
173
+ // 由于async await,所以会出现函数执行到await时让出线程,导致执行顺序出错,例如调用了select(1) -> update -> select(2),这个时候就有可能出现update了2;
174
+ // 这里保证函数调用严格按顺序执行;
175
+ const promise = new Promise<any>((resolve, reject) => {
176
+ this.taskList.push(async () => {
177
+ try {
178
+ const value = await doAsyncAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn);
179
+ resolve(value);
180
+ } catch (e) {
181
+ reject(e);
182
+ }
183
+ });
184
+ });
185
+
186
+ if (!this.doingTask) {
187
+ this.doTask();
188
+ }
153
189
 
154
- return promise;
155
- },
190
+ return promise;
191
+ }
192
+ : (...args: any[]) => doAction(args, scope, sourceMethod, beforeMethodName, afterMethodName, fn),
156
193
  });
157
194
  });
158
195
  }
@@ -38,7 +38,13 @@ class CodeBlock extends BaseService {
38
38
  });
39
39
 
40
40
  constructor() {
41
- super(['setCodeDslById', 'setEditStatus', 'setCombineIds', 'setUndeleteableList', 'deleteCodeDslByIds']);
41
+ super([
42
+ { name: 'setCodeDslById', isAsync: true },
43
+ { name: 'setEditStatus', isAsync: true },
44
+ { name: 'setCombineIds', isAsync: true },
45
+ { name: 'setUndeleteableList', isAsync: true },
46
+ { name: 'deleteCodeDslByIds', isAsync: true },
47
+ ]);
42
48
  }
43
49
 
44
50
  /**
@@ -35,8 +35,8 @@ class Dep extends BaseService {
35
35
  return this.watcher.getTargets(type);
36
36
  }
37
37
 
38
- public getTarget(id: Id) {
39
- return this.watcher.getTarget(id);
38
+ public getTarget(id: Id, type: string = DepTargetType.DEFAULT) {
39
+ return this.watcher.getTarget(id, type);
40
40
  }
41
41
 
42
42
  public addTarget(target: Target) {
@@ -44,8 +44,8 @@ class Dep extends BaseService {
44
44
  this.emit('add-target', target);
45
45
  }
46
46
 
47
- public removeTarget(id: Id) {
48
- this.watcher.removeTarget(id);
47
+ public removeTarget(id: Id, type: string = DepTargetType.DEFAULT) {
48
+ this.watcher.removeTarget(id, type);
49
49
  this.emit('remove-target');
50
50
  }
51
51
 
@@ -53,8 +53,8 @@ class Dep extends BaseService {
53
53
  this.watcher.clearTargets();
54
54
  }
55
55
 
56
- public collect(nodes: MNode[], deep = false) {
57
- this.watcher.collect(nodes, deep);
56
+ public collect(nodes: MNode[], deep = false, type?: DepTargetType) {
57
+ this.watcher.collect(nodes, deep, type);
58
58
  this.emit('collected', nodes, deep);
59
59
  }
60
60
 
@@ -62,8 +62,12 @@ class Dep extends BaseService {
62
62
  return this.watcher.clear(nodes);
63
63
  }
64
64
 
65
- public hasTarget(id: Id) {
66
- return this.watcher.hasTarget(id);
65
+ public hasTarget(id: Id, type: string = DepTargetType.DEFAULT) {
66
+ return this.watcher.hasTarget(id, type);
67
+ }
68
+
69
+ public hasSpecifiedTypeTarget(type: string = DepTargetType.DEFAULT): boolean {
70
+ return this.watcher.hasSpecifiedTypeTarget(type);
67
71
  }
68
72
  }
69
73
 
@@ -17,13 +17,15 @@
17
17
  */
18
18
 
19
19
  import { reactive, toRaw } from 'vue';
20
- import { cloneDeep, isObject, mergeWith, uniq } from 'lodash-es';
20
+ import { cloneDeep, get, isObject, mergeWith, uniq } from 'lodash-es';
21
21
 
22
+ import { DepTargetType } from '@tmagic/dep';
22
23
  import type { Id, MApp, MComponent, MContainer, MNode, MPage } from '@tmagic/schema';
23
24
  import { NodeType } from '@tmagic/schema';
24
25
  import StageCore from '@tmagic/stage';
25
26
  import { getNodePath, isNumber, isPage, isPop } from '@tmagic/utils';
26
27
 
28
+ import depService from '@editor/services/dep';
27
29
  import historyService from '@editor/services/history';
28
30
  import storageService, { Protocol } from '@editor/services/storage';
29
31
  import type { AddMNode, EditorNodeInfo, PastePosition, StepValue, StoreState, StoreStateKey } from '@editor/type';
@@ -61,27 +63,27 @@ class Editor extends BaseService {
61
63
  constructor() {
62
64
  super(
63
65
  [
64
- 'getLayout',
65
- 'select',
66
- 'doAdd',
67
- 'add',
68
- 'doRemove',
69
- 'remove',
70
- 'doUpdate',
71
- 'update',
72
- 'sort',
73
- 'copy',
74
- 'paste',
75
- 'doPaste',
76
- 'doAlignCenter',
77
- 'alignCenter',
78
- 'moveLayer',
79
- 'moveToContainer',
80
- 'move',
81
- 'undo',
82
- 'redo',
83
- 'highlight',
84
- 'dragTo',
66
+ { name: 'getLayout', isAsync: true },
67
+ { name: 'select', isAsync: true },
68
+ { name: 'doAdd', isAsync: true },
69
+ { name: 'add', isAsync: true },
70
+ { name: 'doRemove', isAsync: true },
71
+ { name: 'remove', isAsync: true },
72
+ { name: 'doUpdate', isAsync: true },
73
+ { name: 'update', isAsync: true },
74
+ { name: 'sort', isAsync: true },
75
+ { name: 'copy', isAsync: true },
76
+ { name: 'paste', isAsync: true },
77
+ { name: 'doPaste', isAsync: true },
78
+ { name: 'doAlignCenter', isAsync: true },
79
+ { name: 'alignCenter', isAsync: true },
80
+ { name: 'moveLayer', isAsync: true },
81
+ { name: 'moveToContainer', isAsync: true },
82
+ { name: 'move', isAsync: true },
83
+ { name: 'undo', isAsync: true },
84
+ { name: 'redo', isAsync: true },
85
+ { name: 'highlight', isAsync: true },
86
+ { name: 'dragTo', isAsync: true },
85
87
  ],
86
88
  // 需要注意循环依赖问题,如果函数间有相互调用的话,不能设置为串行调用
87
89
  ['select', 'update', 'moveLayer'],
@@ -593,12 +595,47 @@ class Editor extends BaseService {
593
595
  }
594
596
 
595
597
  /**
596
- * 将组将节点配置转化成string,然后存储到localStorage中
598
+ * 将组件节点配置存储到localStorage中
597
599
  * @param config 组件节点配置
598
- * @returns 组件节点配置
600
+ * @returns
599
601
  */
600
- public async copy(config: MNode | MNode[]): Promise<void> {
601
- await storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], {
602
+ public copy(config: MNode | MNode[]): void {
603
+ storageService.setItem(COPY_STORAGE_KEY, Array.isArray(config) ? config : [config], {
604
+ protocol: Protocol.OBJECT,
605
+ });
606
+ }
607
+
608
+ /**
609
+ * 复制时会带上组件关联的依赖
610
+ * @param config 组件节点配置
611
+ * @returns
612
+ */
613
+ public copyWithRelated(config: MNode | MNode[]): void {
614
+ const copyNodes: MNode[] = Array.isArray(config) ? config : [config];
615
+ // 关联的组件也一并复制
616
+ depService.getTarget(DepTargetType.RELATED_COMP_WHEN_COPY, DepTargetType.RELATED_COMP_WHEN_COPY)?.removeDep();
617
+ depService.collect(copyNodes, true, DepTargetType.RELATED_COMP_WHEN_COPY);
618
+ const customTarget = depService.getTarget(
619
+ DepTargetType.RELATED_COMP_WHEN_COPY,
620
+ DepTargetType.RELATED_COMP_WHEN_COPY,
621
+ );
622
+ if (customTarget) {
623
+ Object.keys(customTarget.deps).forEach((nodeId: Id) => {
624
+ const node = this.getNodeById(nodeId);
625
+ if (!node) return;
626
+ customTarget!.deps[nodeId].keys.forEach((key) => {
627
+ const relateNodeId = get(node, key);
628
+ const isExist = copyNodes.find((node) => node.id === relateNodeId);
629
+ if (!isExist) {
630
+ const relateNode = this.getNodeById(relateNodeId);
631
+ if (relateNode) {
632
+ copyNodes.push(relateNode);
633
+ }
634
+ }
635
+ });
636
+ });
637
+ }
638
+ storageService.setItem(COPY_STORAGE_KEY, copyNodes, {
602
639
  protocol: Protocol.OBJECT,
603
640
  });
604
641
  }
@@ -609,8 +646,7 @@ class Editor extends BaseService {
609
646
  * @returns 添加后的组件节点配置
610
647
  */
611
648
  public async paste(position: PastePosition = {}): Promise<MNode | MNode[] | void> {
612
- const config: MNode[] = await storageService.getItem(COPY_STORAGE_KEY);
613
-
649
+ const config: MNode[] = storageService.getItem(COPY_STORAGE_KEY);
614
650
  if (!Array.isArray(config)) return;
615
651
 
616
652
  const node = this.get('node');
@@ -623,13 +659,13 @@ class Editor extends BaseService {
623
659
  parent = this.get('page');
624
660
  }
625
661
  }
626
-
627
662
  const pasteConfigs = await this.doPaste(config, position);
628
-
663
+ propsService.replaceRelateId(config, pasteConfigs);
629
664
  return this.add(pasteConfigs, parent);
630
665
  }
631
666
 
632
667
  public async doPaste(config: MNode[], position: PastePosition = {}): Promise<MNode[]> {
668
+ propsService.clearRelateId();
633
669
  const pasteConfigs = await beforePaste(position, cloneDeep(config));
634
670
  return pasteConfigs;
635
671
  }