@tmagic/editor 1.0.3 → 1.1.0-beta.0

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.
@@ -3,7 +3,7 @@ import { PropType } from 'vue';
3
3
  import type { FormConfig } from '@tmagic/form';
4
4
  import type { MApp, MNode } from '@tmagic/schema';
5
5
  import type StageCore from '@tmagic/stage';
6
- import type { MoveableOptions } from '@tmagic/stage';
6
+ import { MoveableOptions } from '@tmagic/stage';
7
7
  import type { ComponentGroup, MenuBarData, MenuItem, Services, SideBarData, StageRect } from './type';
8
8
  declare const _default: DefineComponent<{
9
9
  /** 页面初始值 */
@@ -72,6 +72,18 @@ declare const _default: DefineComponent<{
72
72
  type: PropType<(el: HTMLElement) => boolean | Promise<boolean>>;
73
73
  default: (el: HTMLElement) => boolean;
74
74
  };
75
+ isContainer: {
76
+ type: PropType<(el: HTMLElement) => boolean | Promise<boolean>>;
77
+ default: (el: HTMLElement) => boolean;
78
+ };
79
+ containerHighlightClassName: {
80
+ type: StringConstructor;
81
+ default: string;
82
+ };
83
+ containerHighlightDuration: {
84
+ type: NumberConstructor;
85
+ default: number;
86
+ };
75
87
  stageRect: {
76
88
  type: PropType<StageRect>;
77
89
  };
@@ -149,6 +161,18 @@ declare const _default: DefineComponent<{
149
161
  type: PropType<(el: HTMLElement) => boolean | Promise<boolean>>;
150
162
  default: (el: HTMLElement) => boolean;
151
163
  };
164
+ isContainer: {
165
+ type: PropType<(el: HTMLElement) => boolean | Promise<boolean>>;
166
+ default: (el: HTMLElement) => boolean;
167
+ };
168
+ containerHighlightClassName: {
169
+ type: StringConstructor;
170
+ default: string;
171
+ };
172
+ containerHighlightDuration: {
173
+ type: NumberConstructor;
174
+ default: number;
175
+ };
152
176
  stageRect: {
153
177
  type: PropType<StageRect>;
154
178
  };
@@ -174,5 +198,8 @@ declare const _default: DefineComponent<{
174
198
  propsValues: Record<string, MNode>;
175
199
  eventMethodList: Record<string, any>;
176
200
  canSelect: (el: HTMLElement) => boolean | Promise<boolean>;
201
+ isContainer: (el: HTMLElement) => boolean | Promise<boolean>;
202
+ containerHighlightClassName: string;
203
+ containerHighlightDuration: number;
177
204
  }>;
178
205
  export default _default;
@@ -10,5 +10,7 @@ declare const _default: DefineComponent<{}, {
10
10
  }[] | undefined>;
11
11
  appendComponent({ text, type, data }: ComponentItem): void;
12
12
  dragstartHandler({ text, type, data }: ComponentItem, e: DragEvent): void;
13
+ dragendHandler(): void;
14
+ dragHandler(e: DragEvent): void;
13
15
  }, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, EmitsOptions, string, VNodeProps & AllowedComponentProps & ComponentCustomProps, Readonly<ExtractPropTypes<{}>>, {}>;
14
16
  export default _default;
@@ -111,6 +111,12 @@ declare class Editor extends BaseService {
111
111
  * @param offset 偏移量
112
112
  */
113
113
  moveLayer(offset: number | LayerOffset): Promise<void>;
114
+ /**
115
+ * 移动到指定容器中
116
+ * @param config 需要移动的节点
117
+ * @param targetId 容器ID
118
+ */
119
+ moveToContainer(config: MNode, targetId: Id): Promise<MNode | undefined>;
114
120
  /**
115
121
  * 撤销当前操作
116
122
  * @returns 上一次数据
@@ -25,9 +25,12 @@ export interface Services {
25
25
  export interface StageOptions {
26
26
  runtimeUrl: string;
27
27
  autoScrollIntoView: boolean;
28
+ containerHighlightClassName: string;
29
+ containerHighlightDuration: number;
28
30
  render: () => HTMLDivElement;
29
31
  moveableOptions: MoveableOptions | ((core?: StageCore) => MoveableOptions);
30
32
  canSelect: (el: HTMLElement) => boolean | Promise<boolean>;
33
+ isContainer: (el: HTMLElement) => boolean | Promise<boolean>;
31
34
  updateDragEl: (el: HTMLDivElement) => void;
32
35
  }
33
36
  export interface StoreState {
@@ -32,9 +32,13 @@ export declare const generatePageNameByApp: (app: MApp) => string;
32
32
  */
33
33
  export declare const isFixed: (node: MNode) => boolean;
34
34
  export declare const getNodeIndex: (node: MNode, parent: MContainer | MApp) => number;
35
- export declare const toRelative: (node: MNode) => MNode;
36
- export declare const initPosition: (node: MNode, layout: Layout, parentNode: MNode, stage: StageCore) => MNode;
35
+ export declare const getRelativeStyle: (style?: Record<string, any>) => Record<string, any>;
36
+ export declare const getInitPositionStyle: (style: Record<string, any> | undefined, layout: Layout, parentNode: MNode, stage: StageCore) => Record<string, any>;
37
37
  export declare const setLayout: (node: MNode, layout: Layout) => MNode;
38
- export declare const change2Fixed: (node: MNode, root: MApp) => MNode;
39
- export declare const Fixed2Other: (node: MNode, root: MApp, getLayout: (parent: MNode, node?: MNode | undefined) => Promise<Layout>) => Promise<MNode>;
38
+ export declare const change2Fixed: (node: MNode, root: MApp) => {
39
+ left: number;
40
+ top: number;
41
+ };
42
+ export declare const Fixed2Other: (node: MNode, root: MApp, getLayout: (parent: MNode, node?: MNode | undefined) => Promise<Layout>) => Promise<Record<string, any>>;
40
43
  export declare const getGuideLineFromCache: (key: string) => number[];
44
+ export declare const fixNodeLeft: (config: MNode, parent: MContainer, doc?: Document | undefined) => any;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.0.3",
2
+ "version": "1.1.0-beta.0",
3
3
  "name": "@tmagic/editor",
4
4
  "sideEffects": [
5
5
  "dist/*",
@@ -43,11 +43,11 @@
43
43
  "dependencies": {
44
44
  "@babel/core": "^7.18.0",
45
45
  "@element-plus/icons": "0.0.11",
46
- "@tmagic/core": "1.0.3",
47
- "@tmagic/form": "1.0.3",
48
- "@tmagic/schema": "1.0.3",
49
- "@tmagic/stage": "1.0.3",
50
- "@tmagic/utils": "1.0.3",
46
+ "@tmagic/core": "1.1.0-beta.0",
47
+ "@tmagic/form": "1.1.0-beta.0",
48
+ "@tmagic/schema": "1.1.0-beta.0",
49
+ "@tmagic/stage": "1.1.0-beta.0",
50
+ "@tmagic/utils": "1.1.0-beta.0",
51
51
  "buffer": "^6.0.3",
52
52
  "color": "^3.1.3",
53
53
  "element-plus": "^2.2.6",
@@ -60,7 +60,7 @@
60
60
  "vue": "^3.2.0"
61
61
  },
62
62
  "peerDependencies": {
63
- "@tmagic/form": "1.0.3",
63
+ "@tmagic/form": "1.1.0-beta.0",
64
64
  "element-plus": "^2.2.6",
65
65
  "monaco-editor": "^0.32.1",
66
66
  "vue": "^3.2.0"
package/src/Editor.vue CHANGED
@@ -6,7 +6,15 @@
6
6
 
7
7
  <template #sidebar>
8
8
  <slot name="sidebar" :editorService="editorService">
9
- <sidebar :data="sidebar"></sidebar>
9
+ <sidebar :data="sidebar">
10
+ <template #layer-panel-header>
11
+ <slot name="layer-panel-header"></slot>
12
+ </template>
13
+
14
+ <template #component-list-panel-header>
15
+ <slot name="component-list-panel-header"></slot>
16
+ </template>
17
+ </sidebar>
10
18
  </slot>
11
19
  </template>
12
20
 
@@ -20,9 +28,13 @@
20
28
  </slot>
21
29
  </template>
22
30
 
23
- <template #propsPanel>
24
- <slot name="propsPanel">
25
- <props-panel ref="propsPanel" @mounted="(instance) => $emit('props-panel-mounted', instance)"></props-panel>
31
+ <template #props-panel>
32
+ <slot name="props-panel">
33
+ <props-panel ref="propsPanel" @mounted="(instance) => $emit('props-panel-mounted', instance)">
34
+ <template #props-panel-header>
35
+ <slot name="props-panel-header"></slot>
36
+ </template>
37
+ </props-panel>
26
38
  </slot>
27
39
  </template>
28
40
 
@@ -37,7 +49,7 @@ import { EventOption } from '@tmagic/core';
37
49
  import type { FormConfig } from '@tmagic/form';
38
50
  import type { MApp, MNode } from '@tmagic/schema';
39
51
  import type StageCore from '@tmagic/stage';
40
- import type { MoveableOptions } from '@tmagic/stage';
52
+ import { CONTAINER_HIGHLIGHT_CLASS, MoveableOptions } from '@tmagic/stage';
41
53
 
42
54
  import Framework from '@editor/layouts/Framework.vue';
43
55
  import NavMenu from '@editor/layouts/NavMenu.vue';
@@ -142,6 +154,21 @@ export default defineComponent({
142
154
  default: (el: HTMLElement) => Boolean(el.id),
143
155
  },
144
156
 
157
+ isContainer: {
158
+ type: Function as PropType<(el: HTMLElement) => boolean | Promise<boolean>>,
159
+ default: (el: HTMLElement) => el.classList.contains('magic-ui-container'),
160
+ },
161
+
162
+ containerHighlightClassName: {
163
+ type: String,
164
+ default: CONTAINER_HIGHLIGHT_CLASS,
165
+ },
166
+
167
+ containerHighlightDuration: {
168
+ type: Number,
169
+ default: 800,
170
+ },
171
+
145
172
  stageRect: {
146
173
  type: [String, Object] as PropType<StageRect>,
147
174
  },
@@ -257,6 +284,9 @@ export default defineComponent({
257
284
  moveableOptions: props.moveableOptions,
258
285
  canSelect: props.canSelect,
259
286
  updateDragEl: props.updateDragEl,
287
+ isContainer: props.isContainer,
288
+ containerHighlightClassName: props.containerHighlightClassName,
289
+ containerHighlightDuration: props.containerHighlightDuration,
260
290
  }),
261
291
  );
262
292
 
@@ -26,7 +26,7 @@
26
26
 
27
27
  <div class="m-editor-framework-right" :style="`width: ${columnWidth?.right}px`">
28
28
  <el-scrollbar>
29
- <slot name="propsPanel"></slot>
29
+ <slot name="props-panel"></slot>
30
30
  </el-scrollbar>
31
31
  </div>
32
32
  </template>
@@ -1,13 +1,16 @@
1
1
  <template>
2
- <m-form
3
- :class="`m-editor-props-panel ${propsPanelSize}`"
4
- :popper-class="`m-editor-props-panel-popper ${propsPanelSize}`"
5
- ref="configForm"
6
- :size="propsPanelSize"
7
- :init-values="values"
8
- :config="curFormConfig"
9
- @change="submit"
10
- ></m-form>
2
+ <div class="`m-editor-props-panel">
3
+ <slot name="props-panel-header"></slot>
4
+ <m-form
5
+ :class="`m-editor-props-panel ${propsPanelSize}`"
6
+ :popper-class="`m-editor-props-panel-popper ${propsPanelSize}`"
7
+ ref="configForm"
8
+ :size="propsPanelSize"
9
+ :init-values="values"
10
+ :config="curFormConfig"
11
+ @change="submit"
12
+ ></m-form>
13
+ </div>
11
14
  </template>
12
15
 
13
16
  <script lang="ts">
@@ -1,5 +1,7 @@
1
1
  <template>
2
2
  <el-scrollbar>
3
+ <slot name="component-list-panel-header"></slot>
4
+
3
5
  <el-collapse class="ui-component-panel" :model-value="collapseValue">
4
6
  <el-input
5
7
  prefix-icon="el-icon-search"
@@ -19,6 +21,8 @@
19
21
  :key="item.type"
20
22
  @click="appendComponent(item)"
21
23
  @dragstart="dragstartHandler(item, $event)"
24
+ @dragend="dragendHandler"
25
+ @drag="dragHandler"
22
26
  >
23
27
  <m-icon :icon="item.icon"></m-icon>
24
28
 
@@ -36,8 +40,12 @@
36
40
  import { computed, defineComponent, inject, ref } from 'vue';
37
41
  import serialize from 'serialize-javascript';
38
42
 
43
+ import type StageCore from '@tmagic/stage';
44
+ import { GHOST_EL_ID_PREFIX } from '@tmagic/stage';
45
+ import { addClassName, removeClassNameByClassName } from '@tmagic/utils';
46
+
39
47
  import MIcon from '@editor/components/Icon.vue';
40
- import type { ComponentGroup, ComponentItem, Services } from '@editor/type';
48
+ import type { ComponentGroup, ComponentItem, Services, StageOptions } from '@editor/type';
41
49
 
42
50
  export default defineComponent({
43
51
  name: 'ui-component-panel',
@@ -47,6 +55,9 @@ export default defineComponent({
47
55
  setup() {
48
56
  const searchText = ref('');
49
57
  const services = inject<Services>('services');
58
+ const stageOptions = inject<StageOptions>('stageOptions');
59
+
60
+ const stage = computed(() => services?.editorService.get<StageCore>('stage'));
50
61
  const list = computed(() =>
51
62
  services?.componentListService.getList().map((group: ComponentGroup) => ({
52
63
  ...group,
@@ -59,6 +70,10 @@ export default defineComponent({
59
70
  .map((x, i) => i),
60
71
  );
61
72
 
73
+ let timeout: NodeJS.Timeout | undefined;
74
+ let clientX: number;
75
+ let clientY: number;
76
+
62
77
  return {
63
78
  searchText,
64
79
  collapseValue,
@@ -85,6 +100,45 @@ export default defineComponent({
85
100
  );
86
101
  }
87
102
  },
103
+
104
+ dragendHandler() {
105
+ if (timeout) {
106
+ globalThis.clearTimeout(timeout);
107
+ timeout = undefined;
108
+ }
109
+ const doc = stage.value?.renderer.contentWindow?.document;
110
+ if (doc && stageOptions) {
111
+ removeClassNameByClassName(doc, stageOptions.containerHighlightClassName);
112
+ }
113
+ clientX = 0;
114
+ clientY = 0;
115
+ },
116
+
117
+ dragHandler(e: DragEvent) {
118
+ if (e.clientX !== clientX || e.clientY !== clientY) {
119
+ clientX = e.clientX;
120
+ clientY = e.clientY;
121
+ if (timeout) {
122
+ globalThis.clearTimeout(timeout);
123
+ timeout = undefined;
124
+ }
125
+ return;
126
+ }
127
+
128
+ if (timeout) return;
129
+
130
+ timeout = globalThis.setTimeout(async () => {
131
+ if (!stageOptions || !stage.value) return;
132
+ const doc = stage.value.renderer.contentWindow?.document;
133
+ const els = stage.value.getElementsFromPoint(e);
134
+ for (const el of els) {
135
+ if (doc && !el.id.startsWith(GHOST_EL_ID_PREFIX) && (await stageOptions.isContainer(el))) {
136
+ addClassName(el, doc, stageOptions?.containerHighlightClassName);
137
+ break;
138
+ }
139
+ }
140
+ }, stageOptions?.containerHighlightDuration);
141
+ },
88
142
  };
89
143
  },
90
144
  });
@@ -1,5 +1,7 @@
1
1
  <template>
2
2
  <el-scrollbar class="magic-editor-layer-panel">
3
+ <slot name="layer-panel-header"></slot>
4
+
3
5
  <el-input
4
6
  class="filterInput"
5
7
  size="small"
@@ -6,7 +6,15 @@
6
6
  type="card"
7
7
  tab-position="left"
8
8
  >
9
- <tab-pane v-for="(item, index) in data.items" :key="index" :data="item"></tab-pane>
9
+ <tab-pane v-for="(item, index) in data.items" :key="index" :data="item">
10
+ <template #layer-panel-header v-if="item === 'layer'">
11
+ <slot name="layer-panel-header"></slot>
12
+ </template>
13
+
14
+ <template #component-list-panel-header v-if="item === 'component-list'">
15
+ <slot name="component-list-panel-header"></slot>
16
+ </template>
17
+ </tab-pane>
10
18
  </el-tabs>
11
19
  </template>
12
20
 
@@ -8,6 +8,16 @@
8
8
  </template>
9
9
 
10
10
  <component :is="config.component" v-bind="config.props || {}" v-on="config?.listeners || {}">
11
+ <template #component-list-panel-header v-if="data === 'component-list' || config.slots?.componentListPanelHeader">
12
+ <slot v-if="data === 'component-list'" name="component-list-panel-header"></slot>
13
+ <component v-else-if="config.slots?.componentListPanelHeader" :is="config.slots.componentListPanelHeader" />
14
+ </template>
15
+
16
+ <template #layer-panel-header v-if="data === 'layer' || config.slots?.layerPanelHeader">
17
+ <slot v-if="data === 'layer'" name="layer-panel-header"></slot>
18
+ <component v-else-if="config.slots?.layerPanelHeader" :is="config.slots.layerPanelHeader" />
19
+ </template>
20
+
11
21
  <template #layer-node-content="{ data, node }" v-if="config.slots?.layerNodeContent">
12
22
  <component :is="config.slots?.layerNodeContent" :data="data" :node="node" />
13
23
  </template>
@@ -35,8 +35,15 @@ import {
35
35
  } from 'vue';
36
36
  import { cloneDeep } from 'lodash-es';
37
37
 
38
- import type { MApp, MNode, MPage } from '@tmagic/schema';
39
- import StageCore, { GuidesType, Runtime, SortEventData, UpdateEventData } from '@tmagic/stage';
38
+ import type { MApp, MContainer, MNode, MPage } from '@tmagic/schema';
39
+ import StageCore, {
40
+ calcValueByFontsize,
41
+ getOffset,
42
+ GuidesType,
43
+ Runtime,
44
+ SortEventData,
45
+ UpdateEventData,
46
+ } from '@tmagic/stage';
40
47
 
41
48
  import ScrollViewer from '@editor/components/ScrollViewer.vue';
42
49
  import {
@@ -90,6 +97,9 @@ export default defineComponent({
90
97
  runtimeUrl: stageOptions.runtimeUrl,
91
98
  zoom: zoom.value,
92
99
  autoScrollIntoView: stageOptions.autoScrollIntoView,
100
+ isContainer: stageOptions.isContainer,
101
+ containerHighlightClassName: stageOptions.containerHighlightClassName,
102
+ containerHighlightDuration: stageOptions.containerHighlightDuration,
93
103
  canSelect: (el, event, stop) => {
94
104
  const elCanSelect = stageOptions.canSelect(el);
95
105
  // 在组件联动过程中不能再往下选择,返回并触发 ui-select
@@ -122,6 +132,10 @@ export default defineComponent({
122
132
  });
123
133
 
124
134
  stage?.on('update', (ev: UpdateEventData) => {
135
+ if (ev.parentEl) {
136
+ services?.editorService.moveToContainer({ id: ev.el.id, style: ev.style }, ev.parentEl.id);
137
+ return;
138
+ }
125
139
  services?.editorService.update({ id: ev.el.id, style: ev.style });
126
140
  });
127
141
 
@@ -207,23 +221,50 @@ export default defineComponent({
207
221
 
208
222
  async dropHandler(e: DragEvent) {
209
223
  e.preventDefault();
210
- if (e.dataTransfer && page.value && stageContainer.value && stage) {
224
+
225
+ const doc = stage?.renderer.contentWindow?.document;
226
+ const parentEl: HTMLElement | null | undefined = doc?.querySelector(
227
+ `.${stageOptions?.containerHighlightClassName}`,
228
+ );
229
+
230
+ let parent: MContainer | undefined = page.value;
231
+ if (parentEl) {
232
+ parent = services?.editorService.getNodeById(parentEl.id, false) as MContainer;
233
+ }
234
+
235
+ if (e.dataTransfer && parent && stageContainer.value && stage) {
211
236
  // eslint-disable-next-line no-eval
212
237
  const config = eval(`(${e.dataTransfer.getData('data')})`);
213
- const layout = await services?.editorService.getLayout(page.value);
238
+ const layout = await services?.editorService.getLayout(parent);
214
239
 
215
240
  const containerRect = stageContainer.value.getBoundingClientRect();
216
241
  const { scrollTop, scrollLeft } = stage.mask;
242
+ const { style = {} } = config;
243
+
244
+ let top = 0;
245
+ let left = 0;
246
+ let position = 'relative';
247
+
217
248
  if (layout === Layout.ABSOLUTE) {
218
- config.style = {
219
- ...(config.style || {}),
220
- position: 'absolute',
221
- top: e.clientY - containerRect.top + scrollTop,
222
- left: e.clientX - containerRect.left + scrollLeft,
223
- };
249
+ position = 'absolute';
250
+ top = e.clientY - containerRect.top + scrollTop;
251
+ left = e.clientX - containerRect.left + scrollLeft;
252
+
253
+ if (parentEl && doc) {
254
+ const { left: parentLeft, top: parentTop } = getOffset(parentEl);
255
+ left = left - calcValueByFontsize(doc, parentLeft);
256
+ top = top - calcValueByFontsize(doc, parentTop);
257
+ }
224
258
  }
225
259
 
226
- services?.editorService.add(config, page.value);
260
+ config.style = {
261
+ ...style,
262
+ position,
263
+ top,
264
+ left,
265
+ };
266
+
267
+ services?.editorService.add(config, parent);
227
268
  }
228
269
  },
229
270
  };
@@ -33,9 +33,10 @@ import {
33
33
  change2Fixed,
34
34
  COPY_STORAGE_KEY,
35
35
  Fixed2Other,
36
+ fixNodeLeft,
36
37
  generatePageNameByApp,
38
+ getInitPositionStyle,
37
39
  getNodeIndex,
38
- initPosition,
39
40
  isFixed,
40
41
  setLayout,
41
42
  } from '@editor/utils/editor';
@@ -70,6 +71,7 @@ class Editor extends BaseService {
70
71
  'paste',
71
72
  'alignCenter',
72
73
  'moveLayer',
74
+ 'moveToContainer',
73
75
  'move',
74
76
  'undo',
75
77
  'redo',
@@ -274,7 +276,7 @@ class Editor extends BaseService {
274
276
  const { type, ...config } = addNode;
275
277
  const curNode = this.get<MContainer>('node');
276
278
 
277
- let parentNode: MNode | undefined;
279
+ let parentNode: MContainer | undefined;
278
280
  const isPage = type === NodeType.PAGE;
279
281
 
280
282
  if (isPage) {
@@ -291,12 +293,8 @@ class Editor extends BaseService {
291
293
  if (!parentNode) throw new Error('未找到父元素');
292
294
 
293
295
  const layout = await this.getLayout(toRaw(parentNode), addNode as MNode);
294
- const newNode = initPosition(
295
- { ...toRaw(await propsService.getPropsValue(type, config)) },
296
- layout,
297
- parentNode,
298
- this.get<StageCore>('stage'),
299
- );
296
+ const newNode = { ...toRaw(await propsService.getPropsValue(type, config)) };
297
+ newNode.style = getInitPositionStyle(newNode.style, layout, parentNode, this.get<StageCore>('stage'));
300
298
 
301
299
  if ((parentNode?.type === NodeType.ROOT || curNode.type === NodeType.ROOT) && newNode.type !== NodeType.PAGE) {
302
300
  throw new Error('app下不能添加组件');
@@ -305,8 +303,17 @@ class Editor extends BaseService {
305
303
  parentNode?.items?.push(newNode);
306
304
 
307
305
  const stage = this.get<StageCore | null>('stage');
306
+ const root = this.get<MApp>('root');
307
+
308
+ await stage?.add({ config: cloneDeep(newNode), root: cloneDeep(root) });
308
309
 
309
- await stage?.add({ config: cloneDeep(newNode), root: cloneDeep(this.get('root')) });
310
+ if (layout === Layout.ABSOLUTE) {
311
+ const fixedLeft = fixNodeLeft(newNode, parentNode, stage?.renderer.contentWindow?.document);
312
+ if (typeof fixedLeft !== 'undefined') {
313
+ newNode.style.left = fixedLeft;
314
+ await stage?.update({ config: cloneDeep(newNode), root: cloneDeep(root) });
315
+ }
316
+ }
310
317
 
311
318
  await this.select(newNode);
312
319
 
@@ -348,7 +355,7 @@ class Editor extends BaseService {
348
355
 
349
356
  parent.items?.splice(index, 1);
350
357
  const stage = this.get<StageCore | null>('stage');
351
- stage?.remove({ id: node.id, root: this.get('root') });
358
+ stage?.remove({ id: node.id, root: cloneDeep(this.get('root')) });
352
359
 
353
360
  if (node.type === NodeType.PAGE) {
354
361
  this.state.pageLength -= 1;
@@ -431,7 +438,7 @@ class Editor extends BaseService {
431
438
  this.set('node', newConfig);
432
439
  }
433
440
 
434
- this.get<StageCore | null>('stage')?.update({ config: cloneDeep(newConfig), root: this.get('root') });
441
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(newConfig), root: cloneDeep(this.get('root')) });
435
442
 
436
443
  if (newConfig.type === NodeType.PAGE) {
437
444
  this.set('page', newConfig);
@@ -464,7 +471,7 @@ class Editor extends BaseService {
464
471
  await this.update(parent);
465
472
  await this.select(node);
466
473
 
467
- this.get<StageCore | null>('stage')?.update({ config: cloneDeep(node), root: this.get('root') });
474
+ this.get<StageCore | null>('stage')?.update({ config: cloneDeep(node), root: cloneDeep(this.get('root')) });
468
475
 
469
476
  this.addModifiedNodeId(parent.id);
470
477
  this.pushHistoryState();
@@ -544,7 +551,10 @@ class Editor extends BaseService {
544
551
  }
545
552
 
546
553
  await this.update(node);
547
- this.get<StageCore | null>('stage')?.update({ config: cloneDeep(toRaw(node)), root: this.get('root') });
554
+ this.get<StageCore | null>('stage')?.update({
555
+ config: cloneDeep(toRaw(node)),
556
+ root: cloneDeep(this.get<MApp>('root')),
557
+ });
548
558
  this.addModifiedNodeId(config.id);
549
559
  this.pushHistoryState();
550
560
 
@@ -569,7 +579,54 @@ class Editor extends BaseService {
569
579
  brothers.splice(index + parseInt(`${offset}`, 10), 0, brothers.splice(index, 1)[0]);
570
580
  }
571
581
 
572
- this.get<StageCore | null>('stage')?.update({ config: cloneDeep(toRaw(parent)), root: this.get('root') });
582
+ this.get<StageCore | null>('stage')?.update({
583
+ config: cloneDeep(toRaw(parent)),
584
+ root: cloneDeep(this.get<MApp>('root')),
585
+ });
586
+ }
587
+
588
+ /**
589
+ * 移动到指定容器中
590
+ * @param config 需要移动的节点
591
+ * @param targetId 容器ID
592
+ */
593
+ public async moveToContainer(config: MNode, targetId: Id): Promise<MNode | undefined> {
594
+ const { node, parent } = this.getNodeInfo(config.id, false);
595
+ const target = this.getNodeById(targetId, false) as MContainer;
596
+
597
+ const stage = this.get<StageCore | null>('stage');
598
+
599
+ if (node && parent && stage) {
600
+ const root = cloneDeep(this.get<MApp>('root'));
601
+ const index = getNodeIndex(node, parent);
602
+ parent.items?.splice(index, 1);
603
+
604
+ await stage.remove({ id: node.id, root });
605
+
606
+ const layout = await this.getLayout(target);
607
+
608
+ const newConfig = mergeWith(cloneDeep(node), config, (objValue, srcValue) => {
609
+ if (Array.isArray(srcValue)) {
610
+ return srcValue;
611
+ }
612
+ });
613
+ newConfig.style = getInitPositionStyle(newConfig.style, layout, target, stage);
614
+
615
+ target.items.push(newConfig);
616
+
617
+ await stage.select(targetId);
618
+
619
+ await stage.update({ config: cloneDeep(target), root });
620
+
621
+ await this.select(newConfig);
622
+ stage.select(newConfig.id);
623
+
624
+ this.addModifiedNodeId(target.id);
625
+ this.addModifiedNodeId(parent.id);
626
+ this.pushHistoryState();
627
+
628
+ return newConfig;
629
+ }
573
630
  }
574
631
 
575
632
  /**
@@ -656,13 +713,13 @@ class Editor extends BaseService {
656
713
  }
657
714
 
658
715
  private async toggleFixedPosition(dist: MNode, src: MNode, root: MApp) {
659
- let newConfig = cloneDeep(dist);
716
+ const newConfig = cloneDeep(dist);
660
717
 
661
718
  if (!isPop(src) && newConfig.style?.position) {
662
719
  if (isFixed(newConfig) && !isFixed(src)) {
663
- newConfig = change2Fixed(newConfig, root);
720
+ newConfig.style = change2Fixed(newConfig, root);
664
721
  } else if (!isFixed(newConfig) && isFixed(src)) {
665
- newConfig = await Fixed2Other(newConfig, root, this.getLayout);
722
+ newConfig.style = await Fixed2Other(newConfig, root, this.getLayout);
666
723
  }
667
724
  }
668
725