@tmagic/editor 1.2.0-beta.2 → 1.2.0-beta.21

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 (101) hide show
  1. package/dist/style.css +41 -28
  2. package/dist/{tmagic-editor.mjs → tmagic-editor.js} +1693 -1272
  3. package/dist/tmagic-editor.js.map +1 -0
  4. package/dist/{tmagic-editor.umd.js → tmagic-editor.umd.cjs} +1700 -1274
  5. package/dist/tmagic-editor.umd.cjs.map +1 -0
  6. package/package.json +16 -14
  7. package/src/Editor.vue +54 -37
  8. package/src/components/CodeDraftEditor.vue +145 -0
  9. package/src/components/ContentMenu.vue +4 -4
  10. package/src/components/FunctionEditor.vue +145 -0
  11. package/src/components/Icon.vue +7 -5
  12. package/src/components/Layout.vue +149 -0
  13. package/src/components/ScrollBar.vue +1 -1
  14. package/src/components/ScrollViewer.vue +10 -1
  15. package/src/components/ToolButton.vue +30 -15
  16. package/src/fields/Code.vue +1 -1
  17. package/src/fields/CodeLink.vue +1 -1
  18. package/src/fields/CodeSelect.vue +89 -110
  19. package/src/fields/UISelect.vue +9 -8
  20. package/src/index.ts +1 -0
  21. package/src/layouts/AddPageBox.vue +3 -2
  22. package/src/layouts/CodeEditor.vue +2 -3
  23. package/src/layouts/Framework.vue +62 -40
  24. package/src/layouts/NavMenu.vue +1 -1
  25. package/src/layouts/PropsPanel.vue +11 -13
  26. package/src/layouts/Resizer.vue +1 -1
  27. package/src/layouts/sidebar/ComponentListPanel.vue +15 -13
  28. package/src/layouts/sidebar/LayerMenu.vue +9 -7
  29. package/src/layouts/sidebar/LayerPanel.vue +277 -177
  30. package/src/layouts/sidebar/Sidebar.vue +113 -27
  31. package/src/layouts/sidebar/code-block/CodeBlockEditor.vue +34 -85
  32. package/src/layouts/sidebar/code-block/CodeBlockList.vue +55 -89
  33. package/src/layouts/workspace/Breadcrumb.vue +32 -0
  34. package/src/layouts/workspace/PageBar.vue +12 -11
  35. package/src/layouts/workspace/PageBarScrollContainer.vue +1 -1
  36. package/src/layouts/workspace/Stage.vue +26 -9
  37. package/src/layouts/workspace/ViewerMenu.vue +11 -9
  38. package/src/layouts/workspace/Workspace.vue +10 -3
  39. package/src/services/BaseService.ts +6 -1
  40. package/src/services/codeBlock.ts +124 -125
  41. package/src/services/componentList.ts +6 -1
  42. package/src/services/editor.ts +43 -19
  43. package/src/services/events.ts +6 -1
  44. package/src/services/history.ts +7 -16
  45. package/src/services/props.ts +7 -2
  46. package/src/services/storage.ts +1 -0
  47. package/src/services/ui.ts +12 -0
  48. package/src/theme/breadcrumb.scss +6 -0
  49. package/src/theme/code-block.scss +30 -21
  50. package/src/theme/component-list-panel.scss +3 -7
  51. package/src/theme/layer-panel.scss +7 -2
  52. package/src/theme/theme.scss +1 -0
  53. package/src/type.ts +76 -50
  54. package/src/utils/editor.ts +9 -1
  55. package/src/utils/index.ts +1 -0
  56. package/src/utils/props.ts +2 -2
  57. package/src/utils/scroll-viewer.ts +18 -2
  58. package/src/utils/stage.ts +9 -2
  59. package/types/Editor.vue.d.ts +42 -29
  60. package/types/components/CodeDraftEditor.vue.d.ts +148 -0
  61. package/types/components/ContentMenu.vue.d.ts +1 -3
  62. package/types/components/FunctionEditor.vue.d.ts +120 -0
  63. package/types/components/Icon.vue.d.ts +1 -3
  64. package/types/{layouts → components}/Layout.vue.d.ts +12 -1
  65. package/types/components/ScrollBar.vue.d.ts +1 -3
  66. package/types/components/ScrollViewer.vue.d.ts +45 -1
  67. package/types/components/ToolButton.vue.d.ts +1 -3
  68. package/types/fields/Code.vue.d.ts +1 -3
  69. package/types/fields/CodeLink.vue.d.ts +1 -3
  70. package/types/fields/CodeSelect.vue.d.ts +10 -12
  71. package/types/fields/UISelect.vue.d.ts +1 -3
  72. package/types/index.d.ts +1 -0
  73. package/types/layouts/AddPageBox.vue.d.ts +1 -3
  74. package/types/layouts/CodeEditor.vue.d.ts +6 -15
  75. package/types/layouts/NavMenu.vue.d.ts +1 -3
  76. package/types/layouts/sidebar/LayerMenu.vue.d.ts +25 -9
  77. package/types/layouts/sidebar/LayerPanel.vue.d.ts +38 -886
  78. package/types/layouts/sidebar/Sidebar.vue.d.ts +10 -5
  79. package/types/layouts/sidebar/code-block/CodeBlockEditor.vue.d.ts +1 -1
  80. package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +6 -5
  81. package/types/layouts/{sidebar/TabPane.vue.d.ts → workspace/Breadcrumb.vue.d.ts} +5 -43
  82. package/types/layouts/workspace/Stage.vue.d.ts +25 -8
  83. package/types/layouts/workspace/ViewerMenu.vue.d.ts +6 -3
  84. package/types/layouts/workspace/Workspace.vue.d.ts +23 -5
  85. package/types/services/BaseService.d.ts +2 -1
  86. package/types/services/codeBlock.d.ts +57 -36
  87. package/types/services/componentList.d.ts +1 -0
  88. package/types/services/editor.d.ts +5 -6
  89. package/types/services/events.d.ts +1 -0
  90. package/types/services/history.d.ts +5 -15
  91. package/types/services/props.d.ts +3 -2
  92. package/types/services/ui.d.ts +2 -1
  93. package/types/type.d.ts +64 -48
  94. package/types/utils/editor.d.ts +1 -0
  95. package/types/utils/index.d.ts +1 -0
  96. package/types/utils/props.d.ts +1 -1
  97. package/types/utils/scroll-viewer.d.ts +5 -0
  98. package/dist/tmagic-editor.mjs.map +0 -1
  99. package/dist/tmagic-editor.umd.js.map +0 -1
  100. package/src/layouts/Layout.vue +0 -100
  101. package/src/layouts/sidebar/TabPane.vue +0 -116
@@ -1,25 +1,29 @@
1
1
  <template>
2
- <el-scrollbar class="magic-editor-layer-panel">
2
+ <TMagicScrollbar class="magic-editor-layer-panel">
3
3
  <slot name="layer-panel-header"></slot>
4
4
 
5
- <el-input
6
- class="filterInput"
5
+ <TMagicInput
6
+ v-model="filterText"
7
+ class="search-input"
7
8
  size="small"
8
9
  placeholder="输入关键字进行过滤"
9
10
  clearable
10
- v-model="filterText"
11
+ :prefix-icon="Search"
11
12
  @change="filterTextChangeHandler"
12
- ></el-input>
13
+ ></TMagicInput>
13
14
 
14
- <el-tree
15
+ <TMagicTree
15
16
  v-if="values.length"
17
+ tabindex="-1"
18
+ class="magic-editor-layer-tree"
16
19
  ref="tree"
17
20
  node-key="id"
18
21
  empty-text="页面空荡荡的"
19
22
  draggable
20
- :default-expanded-keys="expandedKeys"
23
+ :default-expanded-keys="defaultExpandedKeys"
21
24
  :load="loadItems"
22
25
  :data="values"
26
+ :default-expand-all="true"
23
27
  :expand-on-click-node="false"
24
28
  :highlight-current="true"
25
29
  :props="{
@@ -27,20 +31,24 @@
27
31
  }"
28
32
  :filter-node-method="filterNode"
29
33
  :allow-drop="allowDrop"
34
+ :show-checkbox="isMultiSelectStatus || selectedIds.length > 1"
30
35
  @node-click="clickHandler"
31
36
  @node-contextmenu="contextmenu"
32
37
  @node-drag-end="handleDragEnd"
33
38
  @node-collapse="handleCollapse"
34
39
  @node-expand="handleExpand"
40
+ @check="multiClickHandler"
41
+ @mousedown="toggleClickFlag"
42
+ @mouseup="toggleClickFlag"
43
+ @mouseenter="mouseenterHandler"
44
+ @mouseleave="mouseleaveHandler"
35
45
  >
36
46
  <template #default="{ node, data }">
37
47
  <div
38
48
  :id="data.id"
39
49
  class="cus-tree-node"
40
- @mousedown="toggleClickFlag"
41
- @mouseup="toggleClickFlag"
42
50
  @mouseenter="highlightHandler(data)"
43
- :class="{ 'cus-tree-node-hover': canHighlight && data.id === highlightNode?.id }"
51
+ :class="{ 'cus-tree-node-hover': canHighlight(data) }"
44
52
  >
45
53
  <slot name="layer-node-content" :node="node" :data="data">
46
54
  <span>
@@ -49,32 +57,61 @@
49
57
  </slot>
50
58
  </div>
51
59
  </template>
52
- </el-tree>
60
+ </TMagicTree>
53
61
 
54
- <teleport to="body">
55
- <layer-menu ref="menu"></layer-menu>
56
- </teleport>
57
- </el-scrollbar>
62
+ <Teleport to="body">
63
+ <LayerMenu ref="menu" :layer-content-menu="layerContentMenu"></LayerMenu>
64
+ </Teleport>
65
+ </TMagicScrollbar>
58
66
  </template>
59
67
 
60
- <script lang="ts">
61
- import { computed, defineComponent, inject, Ref, ref, watchEffect } from 'vue';
62
- import type { ElTree } from 'element-plus';
68
+ <script lang="ts" setup name="MEditorLayerPanel">
69
+ import { computed, inject, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
70
+ import { Search } from '@element-plus/icons-vue';
71
+ import KeyController from 'keycon';
63
72
  import { throttle } from 'lodash-es';
64
73
 
74
+ import { TMagicInput, TMagicScrollbar, TMagicTree } from '@tmagic/design';
65
75
  import type { Id, MNode, MPage } from '@tmagic/schema';
66
76
  import { MContainer, NodeType } from '@tmagic/schema';
67
77
  import StageCore from '@tmagic/stage';
68
78
 
69
- import type { EditorService } from '../../services/editor';
70
- import type { Services } from '../../type';
79
+ import type { MenuButton, MenuComponent, Services } from '../../type';
71
80
  import { Layout } from '../../type';
72
81
 
73
82
  import LayerMenu from './LayerMenu.vue';
74
83
 
84
+ defineProps<{
85
+ layerContentMenu: (MenuButton | MenuComponent)[];
86
+ }>();
87
+
75
88
  const throttleTime = 150;
89
+ const services = inject<Services>('services');
90
+ const tree = ref<InstanceType<typeof TMagicTree>>();
91
+ const menu = ref<InstanceType<typeof LayerMenu>>();
92
+ const editorService = services?.editorService;
93
+ const page = computed(() => editorService?.get('page'));
94
+ const values = computed(() => (page.value ? [page.value] : []));
95
+ // 多选选中的节点数组
96
+ const selectedNodes = ref<MNode[]>([]);
97
+ // 多选选中的组件id数组
98
+ const selectedIds = computed(() => selectedNodes.value.map((node: MNode) => node.id));
99
+ // 是否多选
100
+ const isMultiSelectStatus = ref(false);
101
+ // 多选场景 取消选中的那个节点id
102
+ const spliceNodeKey = ref<Id>();
103
+ const filterText = ref('');
104
+ // 默认展开节点
105
+ const defaultExpandedKeys = computed(() => (selectedIds.value.length > 0 ? selectedIds.value : []));
106
+
107
+ editorService?.on('remove', () => {
108
+ setTimeout(() => {
109
+ tree.value?.getNode(editorService.get('node').id)?.updateChildren();
110
+ }, 0);
111
+ });
76
112
 
77
- const select = async (data: MNode, editorService?: EditorService) => {
113
+ // 触发画布单选
114
+ const select = async (data: MNode) => {
78
115
  if (!data.id) {
79
116
  throw new Error('没有id');
80
117
  }
@@ -83,7 +120,14 @@ const select = async (data: MNode, editorService?: EditorService) => {
83
120
  editorService?.get<StageCore>('stage')?.select(data.id);
84
121
  };
85
122
 
86
- const highlight = (data: MNode, editorService?: EditorService) => {
123
+ // 触发画布多选
124
+ const multiSelect = async (data: Id[]) => {
125
+ await editorService?.multiSelect(data);
126
+ editorService?.get<StageCore>('stage')?.multiSelect(data);
127
+ };
128
+
129
+ // 触发画布高亮
130
+ const highlight = (data: MNode) => {
87
131
  if (!data?.id) {
88
132
  throw new Error('没有id');
89
133
  }
@@ -91,182 +135,238 @@ const highlight = (data: MNode, editorService?: EditorService) => {
91
135
  editorService?.get<StageCore>('stage')?.highlight(data.id);
92
136
  };
93
137
 
94
- const useDrop = (tree: Ref<InstanceType<typeof ElTree> | undefined>, editorService?: EditorService) => ({
95
- allowDrop: (draggingNode: any, dropNode: any, type: string): boolean => {
96
- const { data } = dropNode || {};
97
- const { data: ingData } = draggingNode;
138
+ const expandedKeys = new Map<Id, Id>();
98
139
 
99
- const { type: ingType } = ingData;
140
+ // tree方法:拖拽时判定目标节点能否成为拖动目标位置
141
+ const allowDrop = (draggingNode: any, dropNode: any, type: string): boolean => {
142
+ const { data } = dropNode || {};
143
+ const { data: ingData } = draggingNode;
100
144
 
101
- if (ingType !== NodeType.PAGE && data.type === NodeType.PAGE) return false;
102
- if (ingType === NodeType.PAGE && data.type !== NodeType.PAGE) return false;
103
- if (!data || !data.type) return false;
104
- if (['prev', 'next'].includes(type)) return true;
105
- if (data.items || data.type === 'container') return true;
145
+ const { type: ingType } = ingData;
106
146
 
107
- return false;
108
- },
147
+ if (ingType !== NodeType.PAGE && data.type === NodeType.PAGE) return false;
148
+ if (ingType === NodeType.PAGE && data.type !== NodeType.PAGE) return false;
149
+ if (!data || !data.type) return false;
150
+ if (['prev', 'next'].includes(type)) return true;
151
+ if (data.items || data.type === 'container') return true;
109
152
 
110
- async handleDragEnd(e: any) {
111
- if (!tree.value) return;
112
- const { data: node } = e;
113
- const parent = editorService?.getParentById(node.id, false) as MContainer;
114
- const layout = await editorService?.getLayout(parent);
115
- node.style.position = layout;
116
- if (layout === Layout.RELATIVE) {
117
- node.style.top = 0;
118
- node.style.left = 0;
119
- }
120
- const { data } = tree.value;
121
- const [page] = data as [MPage];
122
- editorService?.update(page);
123
- },
124
- });
125
-
126
- const useStatus = (tree: Ref<InstanceType<typeof ElTree> | undefined>, editorService?: EditorService) => {
127
- const node = ref<MNode>();
128
- const page = computed(() => editorService?.get('page'));
129
- const expandedKeys = new Map<Id, Id>();
153
+ return false;
154
+ };
130
155
 
131
- const expandNodes = () => {
132
- expandedKeys.forEach((key) => {
133
- if (!tree.value) return;
134
- tree.value.getNode(key)?.expand();
135
- });
136
- };
156
+ // tree事件:拖拽结束时(可能未成功)触发的事件
157
+ const handleDragEnd = async (e: any) => {
158
+ if (!tree.value) return;
159
+ const { data: node } = e;
160
+ const parent = editorService?.getParentById(node.id, false) as MContainer;
161
+ const layout = await editorService?.getLayout(parent);
162
+ node.style.position = layout;
163
+ if (layout === Layout.RELATIVE) {
164
+ node.style.top = 0;
165
+ node.style.left = 0;
166
+ }
167
+ const data = tree.value.getData();
168
+ const [page] = data as [MPage];
169
+ editorService?.update(page);
170
+ };
137
171
 
138
- watchEffect(() => {
139
- if (!tree.value) return;
140
- if (!editorService) return;
141
- node.value = editorService.get('node');
172
+ // tree方法: 加载子树数据的方法
173
+ const loadItems = (node: any, resolve: Function) => {
174
+ if (Array.isArray(node.data)) {
175
+ return resolve(node.data);
176
+ }
177
+ if (Array.isArray(node.data?.items)) {
178
+ return resolve(node.data?.items);
179
+ }
180
+ resolve([]);
181
+ };
142
182
 
143
- if (!node.value) return;
183
+ // tree事件:节点被关闭时触发的事件
184
+ const handleCollapse = (data: MNode) => {
185
+ expandedKeys.delete(data.id);
186
+ };
144
187
 
145
- tree.value.setCurrentKey(node.value.id, true);
188
+ // tree事件:节点被展开时触发的事件
189
+ const handleExpand = (data: MNode) => {
190
+ const parent = editorService?.getParentById(data.id);
191
+ if (!parent?.id) return;
192
+ expandedKeys.set(parent.id, parent.id);
193
+ };
146
194
 
147
- const parent = editorService.get('parent');
148
- if (!parent?.id) return;
195
+ // tree方法:对树节点进行筛选时执行的方法
196
+ const filterNode = (value: string, data: MNode): boolean => {
197
+ if (!value) {
198
+ return true;
199
+ }
200
+ let name = '';
201
+ if (data.name) {
202
+ name = data.name;
203
+ } else if (data.items) {
204
+ name = 'container';
205
+ }
206
+ return `${data.id}${name}${data.type}`.indexOf(value) !== -1;
207
+ };
149
208
 
150
- const treeNode = tree.value.getNode(parent.id);
151
- treeNode?.updateChildren();
209
+ // 过滤关键字
210
+ const filterTextChangeHandler = (val: string) => {
211
+ tree.value?.filter(val);
212
+ };
152
213
 
153
- setTimeout(() => {
154
- tree.value &&
155
- Object.entries(tree.value.store.nodesMap).forEach(([id, node]) => {
156
- if (node.expanded && node.data.items) {
157
- expandedKeys.set(id, id);
158
- }
159
- });
160
- expandNodes();
214
+ // 树节点更新后展开上次展开过的节点
215
+ const expandNodes = async () => {
216
+ if (!tree.value) return;
217
+ await nextTick();
218
+ tree.value &&
219
+ Object.entries(tree.value.getStore().nodesMap).forEach(([id, node]: [string, any]) => {
220
+ if (node.expanded && node.data.items) {
221
+ expandedKeys.set(id, id);
222
+ }
161
223
  });
224
+ expandedKeys.forEach((key) => {
225
+ if (!tree.value) return;
226
+ tree.value.getNode(key)?.expand();
162
227
  });
228
+ };
163
229
 
164
- return {
165
- values: computed(() => (page.value ? [page.value] : [])),
166
-
167
- loadItems: (node: any, resolve: Function) => {
168
- if (Array.isArray(node.data)) {
169
- return resolve(node.data);
170
- }
171
- if (Array.isArray(node.data?.items)) {
172
- return resolve(node.data?.items);
173
- }
174
- resolve([]);
175
- },
176
-
177
- highlightNode: computed(() => editorService?.get('highlightNode')),
178
- clickNode: node,
179
- expandedKeys: computed(() => (node.value ? [node.value.id] : [])),
180
-
181
- handleCollapse: (data: MNode) => {
182
- expandedKeys.delete(data.id);
183
- },
184
-
185
- handleExpand: (data: MNode) => {
186
- const parent = editorService?.getParentById(data.id);
187
- if (!parent?.id) return;
188
- expandedKeys.set(parent.id, parent.id);
189
- },
190
- };
230
+ watch(
231
+ () => editorService?.get<MNode[]>('nodes'),
232
+ (nodes) => {
233
+ selectedNodes.value = nodes ?? [];
234
+ },
235
+ );
236
+
237
+ // 设置树节点选中状态
238
+ const setTreeKeyStatus = () => {
239
+ if (!tree.value) return;
240
+ if (selectedIds.value.length === 0) {
241
+ tree.value.setCheckedKeys([]);
242
+ tree.value.setCurrentKey();
243
+ } else if (selectedIds.value.length === 1 && !isMultiSelectStatus.value) {
244
+ // 选中1个
245
+ tree.value.setCurrentKey(selectedIds.value[0], true);
246
+ tree.value.setCheckedKeys([]);
247
+ } else {
248
+ // 多选框选中多个
249
+ tree.value.setCheckedKeys(selectedIds.value);
250
+ tree.value.setCurrentKey();
251
+ }
191
252
  };
192
253
 
193
- const useFilter = (tree: Ref<InstanceType<typeof ElTree> | undefined>) => ({
194
- filterText: ref(''),
254
+ watch([selectedIds, tree], async () => {
255
+ if (!tree.value || !editorService) return;
256
+ const parent = editorService.get('parent');
257
+ if (!parent?.id) return;
195
258
 
196
- filterNode: (value: string, data: MNode): boolean => {
197
- if (!value) {
198
- return true;
199
- }
200
- let name = '';
201
- if (data.name) {
202
- name = data.name;
203
- } else if (data.items) {
204
- name = 'container';
205
- }
206
- return `${data.id}${name}${data.type}`.indexOf(value) !== -1;
207
- },
259
+ const treeNode = tree.value.getNode(parent.id);
260
+ treeNode?.updateChildren();
261
+ await expandNodes();
208
262
 
209
- filterTextChangeHandler(val: string) {
210
- tree.value?.filter(val);
211
- },
263
+ // 设置高亮节点操作一定要在刷新展开状态之后,否则可能导致设置的高亮无效
264
+ setTreeKeyStatus();
212
265
  });
213
266
 
214
- export default defineComponent({
215
- name: 'magic-editor-layer-panel',
216
-
217
- components: { LayerMenu },
218
-
219
- setup() {
220
- const services = inject<Services>('services');
221
- const tree = ref<InstanceType<typeof ElTree>>();
222
- const menu = ref<InstanceType<typeof LayerMenu>>();
223
- const clicked = ref(false);
224
- const editorService = services?.editorService;
225
- const highlightHandler = throttle((data: MNode) => {
226
- highlight(data, editorService);
227
- }, throttleTime);
228
-
229
- const toggleClickFlag = () => {
230
- clicked.value = !clicked.value;
231
- };
232
-
233
- const statusData = useStatus(tree, editorService);
234
- const canHighlight = computed(
235
- () => statusData.highlightNode.value?.id !== statusData.clickNode.value?.id && !clicked.value,
236
- );
237
-
238
- editorService?.on('remove', () => {
239
- setTimeout(() => {
240
- tree.value?.getNode(editorService.get('node').id)?.updateChildren();
241
- }, 0);
267
+ const mouseenterHandler = () => {
268
+ tree.value?.$el.focus();
269
+ };
270
+
271
+ const mouseleaveHandler = () => {
272
+ tree.value?.$el.blur();
273
+ isMultiSelectStatus.value = false;
274
+ };
275
+
276
+ let keycon: KeyController;
277
+
278
+ onMounted(() => {
279
+ keycon = new KeyController(tree.value?.$el);
280
+ const isMac = /mac os x/.test(navigator.userAgent.toLowerCase());
281
+ const ctrl = isMac ? 'meta' : 'ctrl';
282
+
283
+ keycon
284
+ .keydown(ctrl, (e) => {
285
+ e.inputEvent.preventDefault();
286
+ isMultiSelectStatus.value = true;
287
+ })
288
+ .keyup(ctrl, (e) => {
289
+ e.inputEvent.preventDefault();
290
+ isMultiSelectStatus.value = false;
242
291
  });
292
+ });
243
293
 
244
- return {
245
- tree,
246
- menu,
247
- ...statusData,
248
- ...useDrop(tree, editorService),
249
- ...useFilter(tree),
250
-
251
- highlightHandler,
252
- toggleClickFlag,
253
- canHighlight,
254
-
255
- clickHandler(data: MNode): void {
256
- if (services?.uiService.get<boolean>('uiSelectMode')) {
257
- document.dispatchEvent(new CustomEvent('ui-select', { detail: data }));
258
- return;
259
- }
260
- tree.value?.setCurrentKey(data.id);
261
- select(data, editorService);
262
- },
263
-
264
- async contextmenu(event: MouseEvent, data: MNode) {
265
- event.preventDefault();
266
- await select(data, editorService);
267
- menu.value?.show(event);
268
- },
269
- };
270
- },
294
+ onUnmounted(() => {
295
+ keycon.destroy();
296
+ });
297
+
298
+ // 鼠标是否按下标志,用于高亮状态互斥
299
+ const clicked = ref(false);
300
+ // 高亮的节点
301
+ const highlightNode = computed(() => editorService?.get('highlightNode'));
302
+
303
+ // 鼠标在组件树移动触发高亮
304
+ const highlightHandler = throttle((data: MNode) => {
305
+ highlight(data);
306
+ }, throttleTime);
307
+
308
+ const toggleClickFlag = () => {
309
+ clicked.value = !clicked.value;
310
+ };
311
+
312
+ // 是否满足展示高亮
313
+ const canHighlight = (data: MNode) => {
314
+ if (clicked.value) return false;
315
+ return (
316
+ data.id === highlightNode?.value?.id && !selectedIds.value.includes(data.id) && spliceNodeKey.value !== data.id
317
+ );
318
+ };
319
+
320
+ // 监听选择模式,针对多选情况做一些处理
321
+ watch(isMultiSelectStatus, () => {
322
+ // 多选模式如果已存在第一个选中的元素是页面(magic-ui-page) 剔除页面选中状态
323
+ if (isMultiSelectStatus.value && selectedNodes.value.length === 1 && selectedNodes.value[0].type === NodeType.PAGE) {
324
+ selectedNodes.value = [];
325
+ }
271
326
  });
327
+
328
+ // 选择节点多选框
329
+ const multiClickHandler = (data: MNode): void => {
330
+ if (!data?.id) {
331
+ throw new Error('没有id');
332
+ }
333
+
334
+ // 页面(magic-ui-page)不可选中
335
+ if (data.type === NodeType.PAGE) {
336
+ tree.value?.setCheckedKeys([]);
337
+ return;
338
+ }
339
+
340
+ const index = selectedNodes.value.findIndex((node) => node.id === data.id);
341
+ if (index !== -1) {
342
+ // 已经包含就移除掉
343
+ selectedNodes.value.splice(index, 1);
344
+ spliceNodeKey.value = data.id;
345
+ } else {
346
+ selectedNodes.value = [...selectedNodes.value, data];
347
+ }
348
+ tree.value?.setCheckedKeys(selectedIds.value);
349
+ multiSelect(selectedIds.value);
350
+ };
351
+
352
+ // 点击节点
353
+ const clickHandler = (data: MNode): void => {
354
+ if (!isMultiSelectStatus.value) {
355
+ if (services?.uiService.get<boolean>('uiSelectMode')) {
356
+ document.dispatchEvent(new CustomEvent('ui-select', { detail: data }));
357
+ return;
358
+ }
359
+ tree.value?.setCurrentKey(data.id);
360
+ select(data);
361
+ } else {
362
+ multiClickHandler(data);
363
+ }
364
+ };
365
+
366
+ // 右键菜单
367
+ const contextmenu = async (event: MouseEvent, data: MNode): Promise<void> => {
368
+ event.preventDefault();
369
+ await select(data);
370
+ menu.value?.show(event);
371
+ };
272
372
  </script>