@tmagic/editor 1.3.0-beta.2 → 1.3.0-beta.4

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 (78) hide show
  1. package/dist/style.css +52 -60
  2. package/dist/tmagic-editor.js +1634 -1065
  3. package/dist/tmagic-editor.js.map +1 -1
  4. package/dist/tmagic-editor.umd.cjs +1659 -1087
  5. package/dist/tmagic-editor.umd.cjs.map +1 -1
  6. package/package.json +10 -10
  7. package/src/Editor.vue +70 -65
  8. package/src/components/ContentMenu.vue +5 -1
  9. package/src/components/SearchInput.vue +5 -1
  10. package/src/editorProps.ts +51 -129
  11. package/src/fields/Code.vue +9 -3
  12. package/src/fields/DataSourceFieldSelect.vue +28 -16
  13. package/src/fields/DataSourceFields.vue +95 -7
  14. package/src/fields/DataSourceMethodSelect.vue +23 -14
  15. package/src/fields/DataSourceMocks.vue +237 -0
  16. package/src/fields/EventSelect.vue +18 -11
  17. package/src/index.ts +4 -1
  18. package/src/initService.ts +50 -20
  19. package/src/layouts/CodeEditor.vue +20 -4
  20. package/src/layouts/Framework.vue +3 -1
  21. package/src/layouts/PropsPanel.vue +3 -1
  22. package/src/layouts/sidebar/ComponentListPanel.vue +18 -8
  23. package/src/layouts/sidebar/Sidebar.vue +7 -10
  24. package/src/layouts/sidebar/code-block/CodeBlockList.vue +3 -1
  25. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +3 -1
  26. package/src/layouts/sidebar/layer/LayerNode.vue +206 -0
  27. package/src/layouts/sidebar/layer/LayerNodeTool.vue +32 -0
  28. package/src/layouts/sidebar/layer/LayerPanel.vue +85 -0
  29. package/src/layouts/sidebar/layer/use-drag.ts +157 -0
  30. package/src/layouts/sidebar/layer/use-filter.ts +70 -0
  31. package/src/layouts/sidebar/layer/use-keybinding.ts +47 -0
  32. package/src/layouts/sidebar/layer/use-node-status.ts +103 -0
  33. package/src/layouts/workspace/Workspace.vue +3 -1
  34. package/src/layouts/workspace/viewer/NodeListMenu.vue +1 -1
  35. package/src/layouts/workspace/viewer/Stage.vue +19 -17
  36. package/src/services/dataSource.ts +10 -0
  37. package/src/services/editor.ts +45 -5
  38. package/src/theme/common/var.scss +2 -2
  39. package/src/theme/framework.scss +2 -0
  40. package/src/theme/layer-panel.scss +63 -63
  41. package/src/theme/props-panel.scss +8 -0
  42. package/src/theme/sidebar.scss +1 -5
  43. package/src/type.ts +60 -0
  44. package/src/utils/data-source/index.ts +11 -0
  45. package/src/utils/dep.ts +26 -11
  46. package/src/utils/editor.ts +14 -3
  47. package/src/utils/props.ts +3 -0
  48. package/types/editorProps.d.ts +61 -107
  49. package/types/fields/Code.vue.d.ts +10 -6
  50. package/types/fields/DataSourceMocks.vue.d.ts +32 -0
  51. package/types/index.d.ts +2 -1
  52. package/types/initService.d.ts +5 -4
  53. package/types/layouts/CodeEditor.vue.d.ts +5 -0
  54. package/types/layouts/Framework.vue.d.ts +2 -12
  55. package/types/layouts/PropsPanel.vue.d.ts +2 -3
  56. package/types/layouts/sidebar/ComponentListPanel.vue.d.ts +2 -7
  57. package/types/layouts/sidebar/Sidebar.vue.d.ts +2 -17
  58. package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +2 -7
  59. package/types/layouts/sidebar/code-block/CodeBlockListPanel.vue.d.ts +2 -9
  60. package/types/layouts/sidebar/{LayerMenu.vue.d.ts → layer/LayerMenu.vue.d.ts} +1 -1
  61. package/types/layouts/sidebar/layer/LayerNode.vue.d.ts +50 -0
  62. package/types/layouts/sidebar/{LayerNode.vue.d.ts → layer/LayerNodeTool.vue.d.ts} +1 -1
  63. package/types/layouts/sidebar/{LayerPanel.vue.d.ts → layer/LayerPanel.vue.d.ts} +2 -8
  64. package/types/layouts/sidebar/layer/use-drag.d.ts +14 -0
  65. package/types/layouts/sidebar/layer/use-filter.d.ts +8 -0
  66. package/types/layouts/sidebar/layer/use-keybinding.d.ts +4 -0
  67. package/types/layouts/sidebar/layer/use-node-status.d.ts +7 -0
  68. package/types/layouts/workspace/Workspace.vue.d.ts +2 -11
  69. package/types/layouts/workspace/viewer/Stage.vue.d.ts +1 -1
  70. package/types/services/dataSource.d.ts +3 -0
  71. package/types/services/editor.d.ts +1 -0
  72. package/types/type.d.ts +60 -0
  73. package/types/utils/dep.d.ts +1 -1
  74. package/types/utils/editor.d.ts +3 -2
  75. package/src/layouts/sidebar/LayerNode.vue +0 -40
  76. package/src/layouts/sidebar/LayerPanel.vue +0 -369
  77. package/types/Editor.vue.d.ts +0 -233
  78. /package/src/layouts/sidebar/{LayerMenu.vue → layer/LayerMenu.vue} +0 -0
@@ -0,0 +1,85 @@
1
+ <template>
2
+ <TMagicScrollbar class="magic-editor-layer-panel">
3
+ <slot name="layer-panel-header"></slot>
4
+
5
+ <SearchInput @search="filterTextChangeHandler"></SearchInput>
6
+
7
+ <div class="magic-editor-layer-tree" tabindex="-1" @dragover="handleDragOver">
8
+ <LayerNode
9
+ v-if="page && root"
10
+ :data="page"
11
+ :filter-text="filterText"
12
+ :is-ctrl-key-down="isCtrlKeyDown"
13
+ @node-contextmenu="contextmenu"
14
+ >
15
+ <template #layer-node-content="{ data: nodeData }">
16
+ <slot name="layer-node-content" :data="nodeData"> </slot>
17
+ </template>
18
+ </LayerNode>
19
+ </div>
20
+
21
+ <Teleport to="body">
22
+ <LayerMenu ref="menu" :layer-content-menu="layerContentMenu" @collapse-all="collapseAllHandler"></LayerMenu>
23
+ </Teleport>
24
+ </TMagicScrollbar>
25
+ </template>
26
+
27
+ <script setup lang="ts">
28
+ import { computed, inject, provide, ref } from 'vue';
29
+
30
+ import { TMagicScrollbar } from '@tmagic/design';
31
+
32
+ import SearchInput from '@editor/components/SearchInput.vue';
33
+ import type { LayerPanelSlots, MenuButton, MenuComponent, Services } from '@editor/type';
34
+
35
+ import LayerMenu from './LayerMenu.vue';
36
+ import LayerNode from './LayerNode.vue';
37
+ import { useDrag } from './use-drag';
38
+ import { useFilter } from './use-filter';
39
+ import { useKeybinding } from './use-keybinding';
40
+ import { useNodeStatus } from './use-node-status';
41
+
42
+ defineSlots<LayerPanelSlots>();
43
+
44
+ defineOptions({
45
+ name: 'MEditorLayerPanel',
46
+ });
47
+
48
+ defineProps<{
49
+ layerContentMenu: (MenuButton | MenuComponent)[];
50
+ }>();
51
+
52
+ const services = inject<Services>('services');
53
+ const editorService = services?.editorService;
54
+
55
+ const page = computed(() => editorService?.get('page'));
56
+ const root = computed(() => editorService?.get('root'));
57
+
58
+ const { nodeStatusMap } = useNodeStatus(services, page);
59
+ const { isCtrlKeyDown } = useKeybinding(services);
60
+
61
+ provide('nodeStatusMap', nodeStatusMap);
62
+
63
+ const { filterText, filterTextChangeHandler } = useFilter(nodeStatusMap, page);
64
+
65
+ const collapseAllHandler = () => {
66
+ if (!page.value || !nodeStatusMap.value) return;
67
+ const items = nodeStatusMap.value.entries();
68
+ for (const [id, status] of items) {
69
+ if (id === page.value.id) {
70
+ continue;
71
+ }
72
+ status.expand = false;
73
+ }
74
+ };
75
+
76
+ // 右键菜单
77
+ const menu = ref<InstanceType<typeof LayerMenu>>();
78
+ const contextmenu = (event: MouseEvent): void => {
79
+ event.preventDefault();
80
+
81
+ menu.value?.show(event);
82
+ };
83
+
84
+ const { handleDragOver } = useDrag(services);
85
+ </script>
@@ -0,0 +1,157 @@
1
+ import type { Id, MContainer, MNode } from '@tmagic/schema';
2
+ import { addClassName, removeClassName } from '@tmagic/utils';
3
+
4
+ import { DragType, type Services } from '@editor/type';
5
+ import { getNodeIndex } from '@editor/utils';
6
+
7
+ export declare type NodeDropType = 'before' | 'after' | 'inner' | 'none';
8
+
9
+ const dragState: {
10
+ dragOverNodeId: Id;
11
+ dropType: NodeDropType | '';
12
+ container: HTMLElement | null;
13
+ } = {
14
+ dragOverNodeId: '',
15
+ dropType: '',
16
+ container: null,
17
+ };
18
+
19
+ const getNodeEl = (el: HTMLElement): HTMLElement | void => {
20
+ if (el.dataset.nodeId) {
21
+ return el;
22
+ }
23
+
24
+ if (el.parentElement) {
25
+ return getNodeEl(el.parentElement);
26
+ }
27
+ };
28
+
29
+ const removeStatusClass = (el: HTMLElement | null) => {
30
+ if (!el) return;
31
+
32
+ ['drag-before', 'drag-after', 'drag-inner'].forEach((className) => {
33
+ el.querySelectorAll(`.${className}`).forEach((el) => {
34
+ removeClassName(el, className);
35
+ });
36
+ });
37
+ };
38
+
39
+ /**
40
+ * dragstart/dragleave/dragend 属于源节点
41
+ * dragover 属于目标节点
42
+ * 这些方法并不是同一个dom事件触发的
43
+ */
44
+ export const useDrag = (services: Services | undefined) => {
45
+ const handleDragStart = (event: DragEvent) => {
46
+ if (!event.dataTransfer || !event.target || !event.currentTarget) return;
47
+
48
+ const targetEl = getNodeEl(event.target as HTMLElement);
49
+
50
+ if (!targetEl || targetEl !== event.currentTarget) return;
51
+
52
+ event.dataTransfer.effectAllowed = 'move';
53
+
54
+ try {
55
+ event.dataTransfer.setData(
56
+ 'text/json',
57
+ JSON.stringify({
58
+ dragType: DragType.LAYER_TREE,
59
+ }),
60
+ );
61
+ } catch {}
62
+ };
63
+
64
+ const handleDragOver = (event: DragEvent) => {
65
+ if (!event.target) return;
66
+
67
+ const targetEl = getNodeEl(event.target as HTMLElement);
68
+ if (!targetEl) return;
69
+
70
+ const labelEl = targetEl.children[0];
71
+ if (!labelEl) return;
72
+
73
+ const { top: targetTop, height: targetHeight } = labelEl.getBoundingClientRect();
74
+
75
+ const distance = event.clientY - targetTop;
76
+ const isContainer = targetEl.dataset.isContainer === 'true';
77
+
78
+ if (distance < targetHeight / 3) {
79
+ dragState.dropType = 'before';
80
+ addClassName(labelEl, globalThis.document, 'drag-before');
81
+ removeClassName(labelEl, 'drag-after', 'drag-inner');
82
+ } else if (distance > (targetHeight * 2) / 3) {
83
+ dragState.dropType = 'after';
84
+ addClassName(labelEl, globalThis.document, 'drag-after');
85
+ removeClassName(labelEl, 'drag-before', 'drag-inner');
86
+ } else if (isContainer) {
87
+ dragState.dropType = 'inner';
88
+ addClassName(labelEl, globalThis.document, 'drag-inner');
89
+ removeClassName(labelEl, 'drag-before', 'drag-after');
90
+ }
91
+
92
+ if (!dragState.dropType) {
93
+ return;
94
+ }
95
+
96
+ dragState.dragOverNodeId = targetEl.dataset.nodeId || '';
97
+ dragState.container = event.currentTarget as HTMLElement;
98
+
99
+ event.preventDefault();
100
+ };
101
+
102
+ const handleDragLeave = (event: DragEvent) => {
103
+ if (!event.target || !event.currentTarget) return;
104
+
105
+ const targetEl = getNodeEl(event.target as HTMLElement);
106
+
107
+ if (!targetEl || targetEl !== event.currentTarget) return;
108
+
109
+ const labelEl = targetEl.children[0];
110
+
111
+ removeClassName(labelEl, 'drag-before', 'drag-after', 'drag-inner');
112
+ };
113
+
114
+ const handleDragEnd = (event: DragEvent, node: MNode) => {
115
+ if (!event.target || !event.currentTarget) return;
116
+
117
+ const targetEl = getNodeEl(event.target as HTMLElement);
118
+
119
+ if (!targetEl || targetEl !== event.currentTarget) return;
120
+
121
+ removeStatusClass(dragState.container);
122
+
123
+ if (node && dragState.dragOverNodeId && dragState.dropType && services) {
124
+ const targetInfo = services.editorService.getNodeInfo(dragState.dragOverNodeId, false);
125
+ const targetNode = targetInfo.node;
126
+ let targetParent = targetInfo.parent;
127
+
128
+ if (!targetParent || !targetNode) return;
129
+
130
+ let targetIndex = -1;
131
+
132
+ if (Array.isArray(targetNode.items) && dragState.dropType === 'inner') {
133
+ targetIndex = targetNode.items.length;
134
+ targetParent = targetNode as MContainer;
135
+ } else {
136
+ targetIndex = getNodeIndex(dragState.dragOverNodeId, targetParent);
137
+ }
138
+
139
+ if (dragState.dropType === 'after') {
140
+ targetIndex += 1;
141
+ }
142
+
143
+ services?.editorService.dragTo(node, targetParent, targetIndex);
144
+ }
145
+
146
+ dragState.dragOverNodeId = '';
147
+ dragState.dropType = '';
148
+ dragState.container = null;
149
+ };
150
+
151
+ return {
152
+ handleDragStart,
153
+ handleDragEnd,
154
+ handleDragLeave,
155
+ handleDragOver,
156
+ };
157
+ };
@@ -0,0 +1,70 @@
1
+ import { type ComputedRef, ref } from 'vue';
2
+
3
+ import { Id, MNode, MPage } from '@tmagic/schema';
4
+ import { getKeys } from '@tmagic/utils';
5
+
6
+ import { LayerNodeStatus } from '@editor/type';
7
+ import { traverseNode } from '@editor/utils';
8
+
9
+ export const updateStatus = (nodeStatusMap: Map<Id, LayerNodeStatus>, id: Id, status: Partial<LayerNodeStatus>) => {
10
+ const nodeStatus = nodeStatusMap.get(id);
11
+
12
+ if (!nodeStatus) return;
13
+ getKeys(status).forEach((key) => {
14
+ if (nodeStatus[key] !== undefined && status[key] !== undefined) {
15
+ nodeStatus[key] = Boolean(status[key]);
16
+ }
17
+ });
18
+ };
19
+
20
+ export const useFilter = (
21
+ nodeStatusMap: ComputedRef<Map<Id, LayerNodeStatus> | undefined>,
22
+ page: ComputedRef<MPage | null | undefined>,
23
+ ) => {
24
+ // tree方法:对树节点进行筛选时执行的方法
25
+ const filterIsMatch = (value: string, data: MNode): boolean => {
26
+ if (!value) {
27
+ return true;
28
+ }
29
+ let name = '';
30
+ if (data.name) {
31
+ name = data.name;
32
+ } else if (data.items) {
33
+ name = 'container';
34
+ }
35
+ return `${data.id}${name}${data.type}`.includes(value);
36
+ };
37
+
38
+ const filterNode = (text: string) => (node: MNode, parents: MNode[]) => {
39
+ if (!nodeStatusMap.value) return;
40
+
41
+ const visible = filterIsMatch(text, node);
42
+ if (visible && parents.length) {
43
+ parents.forEach((parent) => {
44
+ updateStatus(nodeStatusMap.value!, parent.id, {
45
+ visible,
46
+ expand: true,
47
+ });
48
+ });
49
+ }
50
+
51
+ updateStatus(nodeStatusMap.value, node.id, {
52
+ visible,
53
+ });
54
+ };
55
+
56
+ const filter = (text: string) => {
57
+ if (!page.value?.items?.length) return;
58
+
59
+ page.value.items.forEach((node) => {
60
+ traverseNode(node, filterNode(text));
61
+ });
62
+ };
63
+
64
+ return {
65
+ filterText: ref(''),
66
+ filterTextChangeHandler(text: string) {
67
+ filter(text);
68
+ },
69
+ };
70
+ };
@@ -0,0 +1,47 @@
1
+ import { onBeforeUnmount, onMounted, ref } from 'vue';
2
+
3
+ import type { Services } from '@editor/type';
4
+
5
+ export const useKeybinding = (services: Services | undefined) => {
6
+ const keybindingService = services?.keybindingService;
7
+
8
+ // 是否多选
9
+ const isCtrlKeyDown = ref(false);
10
+
11
+ const windowBlurHandler = () => {
12
+ isCtrlKeyDown.value = false;
13
+ };
14
+
15
+ keybindingService?.registeCommand('layer-panel-global-keyup', () => {
16
+ isCtrlKeyDown.value = false;
17
+ });
18
+
19
+ keybindingService?.registeCommand('layer-panel-global-keydwon', () => {
20
+ isCtrlKeyDown.value = true;
21
+ });
22
+
23
+ keybindingService?.registe([
24
+ {
25
+ command: 'layer-panel-global-keydwon',
26
+ keybinding: 'ctrl',
27
+ when: [['global', 'keydown']],
28
+ },
29
+ {
30
+ command: 'layer-panel-global-keyup',
31
+ keybinding: 'ctrl',
32
+ when: [['global', 'keyup']],
33
+ },
34
+ ]);
35
+
36
+ onMounted(() => {
37
+ globalThis.addEventListener('blur', windowBlurHandler);
38
+ });
39
+
40
+ onBeforeUnmount(() => {
41
+ globalThis.removeEventListener('blur', windowBlurHandler);
42
+ });
43
+
44
+ return {
45
+ isCtrlKeyDown,
46
+ };
47
+ };
@@ -0,0 +1,103 @@
1
+ import { computed, type ComputedRef, ref, watch } from 'vue';
2
+
3
+ import type { Id, MNode, MPage } from '@tmagic/schema';
4
+ import { getNodePath } from '@tmagic/utils';
5
+
6
+ import { LayerNodeStatus, Services } from '@editor/type';
7
+ import { traverseNode } from '@editor/utils';
8
+
9
+ import { updateStatus } from './use-filter';
10
+
11
+ const createPageNodeStatus = (services: Services | undefined, pageId: Id) => {
12
+ const map = new Map<Id, LayerNodeStatus>();
13
+
14
+ map.set(pageId, {
15
+ visible: true,
16
+ expand: true,
17
+ selected: true,
18
+ });
19
+
20
+ services?.editorService.getNodeById(pageId)?.items.forEach((node: MNode) =>
21
+ traverseNode(node, (node) => {
22
+ map.set(node.id, {
23
+ visible: true,
24
+ expand: false,
25
+ selected: false,
26
+ });
27
+ }),
28
+ );
29
+
30
+ return map;
31
+ };
32
+
33
+ export const useNodeStatus = (services: Services | undefined, page: ComputedRef<MPage | null | undefined>) => {
34
+ const nodes = computed(() => services?.editorService.get('nodes') || []);
35
+
36
+ /** 所有页面的节点状态 */
37
+ const nodeStatusMaps = ref(new Map<Id, Map<Id, LayerNodeStatus>>());
38
+
39
+ /** 当前页面的节点状态 */
40
+ const nodeStatusMap = computed(() =>
41
+ page.value ? nodeStatusMaps.value.get(page.value.id) : new Map<Id, LayerNodeStatus>(),
42
+ );
43
+
44
+ // 切换页面,重新生成节点状态
45
+ watch(
46
+ () => page.value?.id,
47
+ (pageId) => {
48
+ // 已经存在,不需要重新生成
49
+ if (!pageId || nodeStatusMaps.value.has(pageId)) {
50
+ return;
51
+ }
52
+
53
+ // 新增页面,生成节点状态
54
+ nodeStatusMaps.value.set(pageId, createPageNodeStatus(services, pageId));
55
+ },
56
+ {
57
+ immediate: true,
58
+ },
59
+ );
60
+
61
+ // 选中状态变化,更新节点状态
62
+ watch(
63
+ nodes,
64
+ (nodes) => {
65
+ if (!nodeStatusMap.value) return;
66
+
67
+ for (const [id, status] of nodeStatusMap.value.entries()) {
68
+ status.selected = nodes.some((node) => node.id === id);
69
+ if (status.selected) {
70
+ getNodePath(id, page.value?.items).forEach((node) => {
71
+ updateStatus(nodeStatusMap.value!, node.id, {
72
+ expand: true,
73
+ });
74
+ });
75
+ }
76
+ }
77
+ },
78
+ {
79
+ immediate: true,
80
+ },
81
+ );
82
+
83
+ services?.editorService.on('add', (newNodes: MNode[]) => {
84
+ newNodes.forEach((node) => {
85
+ nodeStatusMap.value?.set(node.id, {
86
+ visible: true,
87
+ expand: Array.isArray(node.items),
88
+ selected: true,
89
+ });
90
+ });
91
+ });
92
+
93
+ services?.editorService.on('remove', (nodes: MNode[]) => {
94
+ nodes.forEach((node) => {
95
+ nodeStatusMap.value?.delete(node.id);
96
+ });
97
+ });
98
+
99
+ return {
100
+ nodeStatusMaps,
101
+ nodeStatusMap,
102
+ };
103
+ };
@@ -18,12 +18,14 @@
18
18
  <script lang="ts" setup>
19
19
  import { computed, inject } from 'vue';
20
20
 
21
- import { MenuButton, MenuComponent, Services } from '@editor/type';
21
+ import type { MenuButton, MenuComponent, Services, WorkspaceSlots } from '@editor/type';
22
22
 
23
23
  import MagicStage from './viewer/Stage.vue';
24
24
  import Breadcrumb from './Breadcrumb.vue';
25
25
  import PageBar from './PageBar.vue';
26
26
 
27
+ defineSlots<WorkspaceSlots>();
28
+
27
29
  defineOptions({
28
30
  name: 'MEditorWorkspace',
29
31
  });
@@ -80,7 +80,7 @@ const unWatch = watch(
80
80
  }
81
81
 
82
82
  timeout = globalThis.setTimeout(() => {
83
- const els = stage?.renderer.getElementsFromPoint(event);
83
+ const els = stage.renderer.getElementsFromPoint(event) || [];
84
84
 
85
85
  const nodes = getNodes(
86
86
  els.map((el) => el.id),
@@ -38,7 +38,7 @@ import StageCore, { calcValueByFontsize, getOffset, Runtime } from '@tmagic/stag
38
38
 
39
39
  import ScrollViewer from '@editor/components/ScrollViewer.vue';
40
40
  import { useStage } from '@editor/hooks/use-stage';
41
- import { Layout, MenuButton, MenuComponent, Services, StageOptions } from '@editor/type';
41
+ import { DragType, Layout, type MenuButton, type MenuComponent, type Services, type StageOptions } from '@editor/type';
42
42
  import { getConfig } from '@editor/utils/config';
43
43
 
44
44
  import NodeListMenu from './NodeListMenu.vue';
@@ -142,18 +142,30 @@ onUnmounted(() => {
142
142
  services?.keybindingService.unregisteEl('stage');
143
143
  });
144
144
 
145
+ const parseDSL = getConfig('parseDSL');
146
+
145
147
  const contextmenuHandler = (e: MouseEvent) => {
146
148
  e.preventDefault();
147
149
  menu.value?.show(e);
148
150
  };
149
151
 
150
152
  const dragoverHandler = (e: DragEvent) => {
151
- e.preventDefault();
152
153
  if (!e.dataTransfer) return;
154
+ e.preventDefault();
153
155
  e.dataTransfer.dropEffect = 'move';
154
156
  };
155
157
 
156
158
  const dropHandler = async (e: DragEvent) => {
159
+ if (!e.dataTransfer) return;
160
+
161
+ const data = e.dataTransfer.getData('text/json');
162
+
163
+ if (!data) return;
164
+
165
+ const config = parseDSL(`(${data})`);
166
+
167
+ if (!config || config.dragType !== DragType.COMPONENT_LIST) return;
168
+
157
169
  e.preventDefault();
158
170
 
159
171
  const doc = stage?.renderer.contentWindow?.document;
@@ -164,22 +176,12 @@ const dropHandler = async (e: DragEvent) => {
164
176
  parent = services?.editorService.getNodeById(parentEl.id, false) as MContainer;
165
177
  }
166
178
 
167
- if (e.dataTransfer && parent && stageContainer.value && stage) {
168
- const parseDSL = getConfig('parseDSL');
169
-
170
- const data = e.dataTransfer.getData('text/json');
171
-
172
- if (!data) return;
173
-
174
- const config = parseDSL(`(${data})`);
175
-
176
- if (!config) return;
177
-
179
+ if (parent && stageContainer.value && stage) {
178
180
  const layout = await services?.editorService.getLayout(parent);
179
181
 
180
182
  const containerRect = stageContainer.value.getBoundingClientRect();
181
183
  const { scrollTop, scrollLeft } = stage.mask;
182
- const { style = {} } = config;
184
+ const { style = {} } = config.data;
183
185
 
184
186
  let top = 0;
185
187
  let left = 0;
@@ -197,16 +199,16 @@ const dropHandler = async (e: DragEvent) => {
197
199
  }
198
200
  }
199
201
 
200
- config.style = {
202
+ config.data.style = {
201
203
  ...style,
202
204
  position,
203
205
  top: top / zoom.value,
204
206
  left: left / zoom.value,
205
207
  };
206
208
 
207
- config.inputEvent = e;
209
+ config.data.inputEvent = e;
208
210
 
209
- services?.editorService.add(config, parent);
211
+ services?.editorService.add(config.data, parent);
210
212
  }
211
213
  };
212
214
  </script>
@@ -18,6 +18,7 @@ interface State {
18
18
  configs: Record<string, FormConfig>;
19
19
  values: Record<string, Partial<DataSourceSchema>>;
20
20
  events: Record<string, EventOption[]>;
21
+ methods: Record<string, EventOption[]>;
21
22
  }
22
23
 
23
24
  type StateKey = keyof State;
@@ -29,6 +30,7 @@ class DataSource extends BaseService {
29
30
  configs: {},
30
31
  values: {},
31
32
  events: {},
33
+ methods: {},
32
34
  });
33
35
 
34
36
  public set<K extends StateKey, T extends State[K]>(name: K, value: T) {
@@ -63,6 +65,14 @@ class DataSource extends BaseService {
63
65
  this.get('events')[type] = value;
64
66
  }
65
67
 
68
+ public getFormMethod(type = 'base') {
69
+ return this.get('methods')[type] || [];
70
+ }
71
+
72
+ public setFormMethod(type: string, value: EventOption[] = []) {
73
+ this.get('methods')[type] = value;
74
+ }
75
+
66
76
  public add(config: DataSourceSchema) {
67
77
  const newConfig = {
68
78
  ...config,
@@ -80,6 +80,7 @@ class Editor extends BaseService {
80
80
  'undo',
81
81
  'redo',
82
82
  'highlight',
83
+ 'dragTo',
83
84
  ],
84
85
  // 需要注意循环依赖问题,如果函数间有相互调用的话,不能设置为串行调用
85
86
  ['select', 'update', 'moveLayer'],
@@ -253,7 +254,7 @@ class Editor extends BaseService {
253
254
 
254
255
  if (!parent) return node;
255
256
 
256
- const index = getNodeIndex(node, parent);
257
+ const index = getNodeIndex(node.id, parent);
257
258
 
258
259
  const nextNode = parent.items[index + 1] || parent.items[0];
259
260
 
@@ -270,7 +271,7 @@ class Editor extends BaseService {
270
271
  if (!page) throw new Error('page不能为空');
271
272
  if (!root) throw new Error('root不能为空');
272
273
 
273
- const index = getNodeIndex(page, root);
274
+ const index = getNodeIndex(page.id, root);
274
275
 
275
276
  const nextPage = root.items[index + 1] || root.items[0];
276
277
 
@@ -418,7 +419,7 @@ class Editor extends BaseService {
418
419
 
419
420
  if (!parent || !curNode) throw new Error('找不要删除的节点');
420
421
 
421
- const index = getNodeIndex(curNode, parent);
422
+ const index = getNodeIndex(curNode.id, parent);
422
423
 
423
424
  if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
424
425
 
@@ -503,7 +504,7 @@ class Editor extends BaseService {
503
504
  if (!parent) throw new Error('获取不到父级节点');
504
505
 
505
506
  const parentNodeItems = parent.items;
506
- const index = getNodeIndex(newConfig, parent);
507
+ const index = getNodeIndex(newConfig.id, parent);
507
508
 
508
509
  if (!parentNodeItems || typeof index === 'undefined' || index === -1) throw new Error('更新的节点未找到');
509
510
 
@@ -737,7 +738,7 @@ class Editor extends BaseService {
737
738
 
738
739
  const stage = this.get('stage');
739
740
  if (root && node && parent && stage) {
740
- const index = getNodeIndex(node, parent);
741
+ const index = getNodeIndex(node.id, parent);
741
742
  parent.items?.splice(index, 1);
742
743
 
743
744
  await stage.remove({ id: node.id, parentId: parent.id, root });
@@ -769,6 +770,45 @@ class Editor extends BaseService {
769
770
  }
770
771
  }
771
772
 
773
+ public async dragTo(config: MNode, targetParent: MContainer, targetIndex: number) {
774
+ if (!targetParent || !Array.isArray(targetParent.items)) return;
775
+
776
+ const { parent, node: curNode } = this.getNodeInfo(config.id, false);
777
+ if (!parent || !curNode) throw new Error('找不要删除的节点');
778
+
779
+ const index = getNodeIndex(curNode.id, parent);
780
+
781
+ if (typeof index !== 'number' || index === -1) throw new Error('找不要删除的节点');
782
+
783
+ if (parent.id === targetParent.id) {
784
+ if (index === targetIndex) return;
785
+
786
+ if (index < targetIndex) {
787
+ targetIndex -= 1;
788
+ }
789
+ }
790
+
791
+ parent.items?.splice(index, 1);
792
+ targetParent.items?.splice(targetIndex, 0, config);
793
+
794
+ const page = this.get('page');
795
+ const root = this.get('root');
796
+ const stage = this.get('stage');
797
+
798
+ if (stage && page && root) {
799
+ stage.update({
800
+ config: cloneDeep(page),
801
+ parentId: root.id,
802
+ root: cloneDeep(root),
803
+ });
804
+ }
805
+
806
+ this.addModifiedNodeId(config.id);
807
+ this.addModifiedNodeId(parent.id);
808
+
809
+ this.pushHistoryState();
810
+ }
811
+
772
812
  /**
773
813
  * 撤销当前操作
774
814
  * @returns 上一次数据