@tmagic/editor 1.4.5 → 1.4.7

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 (38) hide show
  1. package/dist/tmagic-editor.js +2293 -1984
  2. package/dist/tmagic-editor.umd.cjs +2294 -1982
  3. package/package.json +19 -14
  4. package/src/components/TreeNode.vue +3 -1
  5. package/src/editorProps.ts +0 -3
  6. package/src/fields/DataSourceInput.vue +7 -1
  7. package/src/fields/EventSelect.vue +90 -16
  8. package/src/hooks/use-code-block-edit.ts +0 -2
  9. package/src/hooks/use-stage.ts +1 -1
  10. package/src/index.ts +1 -0
  11. package/src/initService.ts +24 -18
  12. package/src/layouts/sidebar/code-block/CodeBlockList.vue +34 -16
  13. package/src/layouts/sidebar/data-source/DataSourceList.vue +17 -5
  14. package/src/services/codeBlock.ts +80 -9
  15. package/src/services/dataSource.ts +60 -4
  16. package/src/services/dep.ts +77 -5
  17. package/src/services/editor.ts +23 -14
  18. package/src/services/props.ts +19 -8
  19. package/src/services/stageOverlay.ts +1 -0
  20. package/src/type.ts +1 -0
  21. package/src/utils/data-source/index.ts +20 -13
  22. package/src/utils/editor.ts +2 -0
  23. package/src/utils/idle-task.ts +72 -0
  24. package/src/utils/operator.ts +6 -6
  25. package/types/editorProps.d.ts +0 -3
  26. package/types/index.d.ts +1 -0
  27. package/types/layouts/PropsPanel.vue.d.ts +33 -33
  28. package/types/layouts/sidebar/code-block/CodeBlockList.vue.d.ts +1 -1
  29. package/types/layouts/sidebar/data-source/DataSourceList.vue.d.ts +1 -1
  30. package/types/services/codeBlock.d.ts +23 -2
  31. package/types/services/dataSource.d.ts +13 -1
  32. package/types/services/dep.d.ts +12 -2
  33. package/types/services/editor.d.ts +3 -2
  34. package/types/services/props.d.ts +2 -1
  35. package/types/type.d.ts +1 -0
  36. package/types/utils/editor.d.ts +2 -0
  37. package/types/utils/idle-task.d.ts +14 -0
  38. package/types/utils/operator.d.ts +2 -2
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.4.5",
2
+ "version": "1.4.7",
3
3
  "name": "@tmagic/editor",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -50,18 +50,8 @@
50
50
  "lodash-es": "^4.17.21",
51
51
  "moveable": "^0.53.0",
52
52
  "serialize-javascript": "^6.0.0",
53
- "@tmagic/dep": "1.4.5",
54
- "@tmagic/table": "1.4.5"
55
- },
56
- "peerDependencies": {
57
- "monaco-editor": "^0.48.0",
58
- "vue": "^3.4.27",
59
- "@tmagic/design": "1.4.5",
60
- "@tmagic/form": "1.4.5",
61
- "@tmagic/schema": "1.4.5",
62
- "@tmagic/stage": "1.4.5",
63
- "@tmagic/utils": "1.4.5",
64
- "@tmagic/core": "1.4.5"
53
+ "@tmagic/dep": "1.4.7",
54
+ "@tmagic/table": "1.4.7"
65
55
  },
66
56
  "devDependencies": {
67
57
  "@types/events": "^3.0.0",
@@ -75,10 +65,25 @@
75
65
  "sass": "^1.77.0",
76
66
  "tsc-alias": "^1.8.5",
77
67
  "type-fest": "^4.10.3",
78
- "typescript": "^5.4.2",
79
68
  "vite": "^5.2.11",
80
69
  "vue-tsc": "^2.0.16"
81
70
  },
71
+ "peerDependencies": {
72
+ "monaco-editor": "^0.48.0",
73
+ "vue": "^3.4.27",
74
+ "typescript": "*",
75
+ "@tmagic/core": "1.4.7",
76
+ "@tmagic/design": "1.4.7",
77
+ "@tmagic/form": "1.4.7",
78
+ "@tmagic/schema": "1.4.7",
79
+ "@tmagic/stage": "1.4.7",
80
+ "@tmagic/utils": "1.4.7"
81
+ },
82
+ "peerDependenciesMeta": {
83
+ "typescript": {
84
+ "optional": true
85
+ }
86
+ },
82
87
  "scripts": {
83
88
  "build": "npm run build:type && vite build",
84
89
  "build:type": "npm run clear:type && vue-tsc --declaration --emitDeclarationOnly --project tsconfig.build.json && tsc-alias -p tsconfig.build.json",
@@ -114,7 +114,9 @@ const selected = computed(() => nodeStatus.value.selected);
114
114
  const visible = computed(() => nodeStatus.value.visible);
115
115
  const draggable = computed(() => nodeStatus.value.draggable);
116
116
 
117
- const hasChildren = computed(() => props.data.items?.some((item) => props.nodeStatusMap.get(item.id)?.visible));
117
+ const hasChildren = computed(
118
+ () => Array.isArray(props.data.items) && props.data.items.some((item) => props.nodeStatusMap.get(item.id)?.visible),
119
+ );
118
120
 
119
121
  const handleDragStart = (event: DragEvent) => {
120
122
  treeEmit?.('node-dragstart', event, props.data);
@@ -1,5 +1,4 @@
1
1
  import type { EventOption } from '@tmagic/core';
2
- import type { CustomTargetOptions } from '@tmagic/dep';
3
2
  import type { FormConfig, FormState } from '@tmagic/form';
4
3
  import type { DataSourceSchema, Id, MApp, MNode } from '@tmagic/schema';
5
4
  import StageCore, {
@@ -70,8 +69,6 @@ export interface EditorProps {
70
69
  codeOptions?: { [key: string]: any };
71
70
  /** 禁用鼠标左键按下时就开始拖拽,需要先选中再可以拖拽 */
72
71
  disabledDragStart?: boolean;
73
- /** 自定义依赖收集器,复制组件时会将关联依赖一并复制 */
74
- collectorOptions?: CustomTargetOptions;
75
72
  /** 标尺配置 */
76
73
  guidesOptions?: Partial<GuidesOptions>;
77
74
  /** 禁止多选 */
@@ -54,6 +54,7 @@ import { Coin } from '@element-plus/icons-vue';
54
54
  import { getConfig, TMagicAutocomplete, TMagicTag } from '@tmagic/design';
55
55
  import type { FieldProps, FormItem } from '@tmagic/form';
56
56
  import type { DataSchema, DataSourceSchema } from '@tmagic/schema';
57
+ import { isNumber } from '@tmagic/utils';
57
58
 
58
59
  import Icon from '@editor/components/Icon.vue';
59
60
  import type { Services } from '@editor/type';
@@ -209,7 +210,7 @@ const fieldQuerySearch = (
209
210
  const dsKey = queryString.substring(leftAngleIndex + 1, dotIndex);
210
211
 
211
212
  // 可能是xx.xx.xx,存在链式调用
212
- const keys = dsKey.split('.');
213
+ const keys = dsKey.replaceAll(/\[(\d+)\]/g, '.$1').split('.');
213
214
 
214
215
  // 最前的是数据源id
215
216
  const dsId = keys.shift();
@@ -224,6 +225,11 @@ const fieldQuerySearch = (
224
225
  // 后面这些是字段
225
226
  let key = keys.shift();
226
227
  while (key) {
228
+ if (isNumber(key)) {
229
+ key = keys.shift();
230
+ continue;
231
+ }
232
+
227
233
  for (const field of fields) {
228
234
  if (field.name === key) {
229
235
  fields = field.fields || [];
@@ -26,14 +26,14 @@
26
26
  @change="onChangeHandler"
27
27
  >
28
28
  <template #header>
29
- <MContainer
29
+ <MFormContainer
30
30
  class="fullWidth"
31
31
  :config="eventNameConfig"
32
32
  :model="cardItem"
33
33
  :disabled="disabled"
34
34
  :size="size"
35
35
  @change="onChangeHandler"
36
- ></MContainer>
36
+ ></MFormContainer>
37
37
  <TMagicButton
38
38
  style="color: #f56c6c"
39
39
  link
@@ -55,13 +55,13 @@ import { has } from 'lodash-es';
55
55
 
56
56
  import type { EventOption } from '@tmagic/core';
57
57
  import { TMagicButton } from '@tmagic/design';
58
- import type { CascaderOption, FieldProps, FormState, PanelConfig } from '@tmagic/form';
59
- import { MContainer, MPanel } from '@tmagic/form';
60
- import { ActionType } from '@tmagic/schema';
58
+ import type { CascaderOption, ChildConfig, FieldProps, FormState, PanelConfig } from '@tmagic/form';
59
+ import { MContainer as MFormContainer, MPanel } from '@tmagic/form';
60
+ import { ActionType, type MComponent, type MContainer } from '@tmagic/schema';
61
61
  import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX } from '@tmagic/utils';
62
62
 
63
63
  import type { CodeSelectColConfig, DataSourceMethodSelectConfig, EventSelectConfig, Services } from '@editor/type';
64
- import { getCascaderOptionsFromFields } from '@editor/utils';
64
+ import { getCascaderOptionsFromFields, traverseNode } from '@editor/utils';
65
65
 
66
66
  defineOptions({
67
67
  name: 'MFieldsEventSelect',
@@ -80,13 +80,20 @@ const codeBlockService = services?.codeBlockService;
80
80
 
81
81
  // 事件名称下拉框表单配置
82
82
  const eventNameConfig = computed(() => {
83
- const fieldType = props.config.src === 'component' ? 'select' : 'cascader';
84
- const defaultEventNameConfig = {
83
+ const defaultEventNameConfig: ChildConfig = {
85
84
  name: 'name',
86
85
  text: '事件',
87
- type: fieldType,
86
+ type: (mForm, { formValue }: any) => {
87
+ if (
88
+ props.config.src !== 'component' ||
89
+ (formValue.type === 'page-fragment-container' && formValue.pageFragmentId)
90
+ ) {
91
+ return 'cascader';
92
+ }
93
+ return 'select';
94
+ },
88
95
  labelWidth: '40px',
89
- checkStrictly: true,
96
+ checkStrictly: () => props.config.src !== 'component',
90
97
  valueSeparator: '.',
91
98
  options: (mForm: FormState, { formValue }: any) => {
92
99
  let events: EventOption[] | CascaderOption[] = [];
@@ -95,11 +102,39 @@ const eventNameConfig = computed(() => {
95
102
 
96
103
  if (props.config.src === 'component') {
97
104
  events = eventsService.getEvent(formValue.type);
105
+
106
+ if (formValue.type === 'page-fragment-container' && formValue.pageFragmentId) {
107
+ const pageFragment = editorService?.get('root')?.items?.find((page) => page.id === formValue.pageFragmentId);
108
+ if (pageFragment) {
109
+ events = [
110
+ {
111
+ label: pageFragment.name || '迭代器容器',
112
+ value: pageFragment.id,
113
+ children: events,
114
+ },
115
+ ];
116
+ pageFragment.items.forEach((node) => {
117
+ traverseNode<MComponent | MContainer>(node, (node) => {
118
+ const nodeEvents = (node.type && eventsService.getEvent(node.type)) || [];
119
+
120
+ events.push({
121
+ label: `${node.name}_${node.id}`,
122
+ value: `${node.id}`,
123
+ children: nodeEvents,
124
+ });
125
+ });
126
+ });
127
+
128
+ return events;
129
+ }
130
+ }
131
+
98
132
  return events.map((option) => ({
99
133
  text: option.label,
100
134
  value: option.value,
101
135
  }));
102
136
  }
137
+
103
138
  if (props.config.src === 'datasource') {
104
139
  // 从数据源类型中获取到相关事件
105
140
  events = dataSourceService.getFormEvent(formValue.type);
@@ -163,24 +198,63 @@ const targetCompConfig = computed(() => {
163
198
  text: '联动组件',
164
199
  type: 'ui-select',
165
200
  display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.actionType === ActionType.COMP,
201
+ onChange: (MForm: FormState, v: string, { model }: any) => {
202
+ model.method = '';
203
+ return v;
204
+ },
166
205
  };
167
206
  return { ...defaultTargetCompConfig, ...props.config.targetCompConfig };
168
207
  });
169
208
 
170
209
  // 联动组件动作配置
171
210
  const compActionConfig = computed(() => {
172
- const defaultCompActionConfig = {
211
+ const defaultCompActionConfig: ChildConfig = {
173
212
  name: 'method',
174
213
  text: '动作',
175
- type: 'select',
176
- display: (mForm: FormState, { model }: { model: Record<any, any> }) => model.actionType === ActionType.COMP,
214
+ type: (mForm, { model }: any) => {
215
+ const to = editorService?.getNodeById(model.to);
216
+
217
+ if (to && to.type === 'page-fragment-container' && to.pageFragmentId) {
218
+ return 'cascader';
219
+ }
220
+
221
+ return 'select';
222
+ },
223
+ checkStrictly: () => props.config.src !== 'component',
224
+ display: (mForm, { model }: any) => model.actionType === ActionType.COMP,
177
225
  options: (mForm: FormState, { model }: any) => {
178
226
  const node = editorService?.getNodeById(model.to);
179
227
  if (!node?.type) return [];
180
228
 
181
- return eventsService?.getMethod(node.type).map((option: any) => ({
182
- text: option.label,
183
- value: option.value,
229
+ let methods: EventOption[] | CascaderOption[] = [];
230
+
231
+ methods = eventsService?.getMethod(node.type) || [];
232
+
233
+ if (node.type === 'page-fragment-container' && node.pageFragmentId) {
234
+ const pageFragment = editorService?.get('root')?.items?.find((page) => page.id === node.pageFragmentId);
235
+ if (pageFragment) {
236
+ methods = [];
237
+ pageFragment.items.forEach((node: MComponent | MContainer) => {
238
+ traverseNode<MComponent | MContainer>(node, (node) => {
239
+ const nodeMethods = (node.type && eventsService?.getMethod(node.type)) || [];
240
+
241
+ if (nodeMethods.length) {
242
+ methods.push({
243
+ label: `${node.name}_${node.id}`,
244
+ value: `${node.id}`,
245
+ children: nodeMethods,
246
+ });
247
+ }
248
+ });
249
+ });
250
+
251
+ return methods;
252
+ }
253
+ }
254
+
255
+ return methods.map((method) => ({
256
+ text: method.label,
257
+ value: method.value,
184
258
  }));
185
259
  },
186
260
  };
@@ -67,8 +67,6 @@ export const useCodeBlockEdit = (codeBlockService?: CodeBlockService) => {
67
67
 
68
68
  await codeBlockService?.setCodeDslById(codeId.value, values);
69
69
 
70
- tMagicMessage.success('代码块保存成功');
71
-
72
70
  codeBlockEditor.value?.hide();
73
71
  };
74
72
 
@@ -24,7 +24,7 @@ export const useStage = (stageOptions: StageOptions) => {
24
24
  const stage = new StageCore({
25
25
  render: stageOptions.render,
26
26
  runtimeUrl: stageOptions.runtimeUrl,
27
- zoom: zoom.value,
27
+ zoom: stageOptions.zoom ?? zoom.value,
28
28
  autoScrollIntoView: stageOptions.autoScrollIntoView,
29
29
  isContainer: stageOptions.isContainer,
30
30
  containerHighlightClassName: stageOptions.containerHighlightClassName,
package/src/index.ts CHANGED
@@ -82,6 +82,7 @@ export { default as LayoutContainer } from './components/SplitView.vue';
82
82
  export { default as SplitView } from './components/SplitView.vue';
83
83
  export { default as Resizer } from './components/Resizer.vue';
84
84
  export { default as CodeBlockEditor } from './components/CodeBlockEditor.vue';
85
+ export { default as FloatingBox } from './components/FloatingBox.vue';
85
86
  export { default as PageFragmentSelect } from './fields/PageFragmentSelect.vue';
86
87
 
87
88
  const defaultInstallOpt: InstallOptions = {
@@ -7,12 +7,11 @@ import {
7
7
  createDataSourceCondTarget,
8
8
  createDataSourceMethodTarget,
9
9
  createDataSourceTarget,
10
- createRelatedCompTarget,
11
10
  DepTargetType,
12
11
  Target,
13
12
  } from '@tmagic/dep';
14
13
  import type { CodeBlockContent, DataSourceSchema, Id, MApp, MNode, MPage, MPageFragment } from '@tmagic/schema';
15
- import { getNodes } from '@tmagic/utils';
14
+ import { getNodes, isPage } from '@tmagic/utils';
16
15
 
17
16
  import PropsPanel from './layouts/PropsPanel.vue';
18
17
  import { EditorProps } from './editorProps';
@@ -268,17 +267,12 @@ export const initServiceEvents = (
268
267
  }
269
268
  };
270
269
 
271
- const depUpdateHandler = (node: MNode) => {
272
- updateNodeWhenDataSourceChange([node]);
273
- };
274
-
275
270
  const collectedHandler = (nodes: MNode[]) => {
276
271
  updateNodeWhenDataSourceChange(nodes);
277
272
  };
278
273
 
279
274
  depService.on('add-target', targetAddHandler);
280
275
  depService.on('remove-target', targetRemoveHandler);
281
- depService.on('dep-update', depUpdateHandler);
282
276
  depService.on('collected', collectedHandler);
283
277
 
284
278
  const initDataSourceDepTarget = (ds: DataSourceSchema) => {
@@ -307,7 +301,9 @@ export const initServiceEvents = (
307
301
  });
308
302
 
309
303
  if (Array.isArray(value.items)) {
310
- depService.collect(value.items, true);
304
+ value.items.forEach((page) => {
305
+ depService.collectIdle([page], { pageId: page.id }, true);
306
+ });
311
307
  } else {
312
308
  depService.clear();
313
309
  delete value.dataSourceDeps;
@@ -334,14 +330,28 @@ export const initServiceEvents = (
334
330
  }
335
331
  };
336
332
 
333
+ const collectIdle = (nodes: MNode[], deep: boolean) => {
334
+ nodes.forEach((node) => {
335
+ let pageId: Id | undefined;
336
+
337
+ if (isPage(node)) {
338
+ pageId = node.id;
339
+ } else {
340
+ const info = editorService.getNodeInfo(node.id);
341
+ pageId = info.page?.id;
342
+ }
343
+ depService.collectIdle([node], { pageId }, deep);
344
+ });
345
+ };
346
+
337
347
  // 新增节点,收集依赖
338
348
  const nodeAddHandler = (nodes: MNode[]) => {
339
- depService.collect(nodes);
349
+ collectIdle(nodes, true);
340
350
  };
341
351
 
342
352
  // 节点更新,收集依赖
343
353
  const nodeUpdateHandler = (nodes: MNode[]) => {
344
- depService.collect(nodes);
354
+ collectIdle(nodes, false);
345
355
  };
346
356
 
347
357
  // 节点删除,清除对齐的依赖收集
@@ -351,7 +361,7 @@ export const initServiceEvents = (
351
361
 
352
362
  // 由于历史记录变化是更新整个page,所以历史记录变化时,需要重新收集依赖
353
363
  const historyChangeHandler = (page: MPage | MPageFragment) => {
354
- depService.collect([page], true);
364
+ depService.collectIdle([page], { pageId: page.id }, true);
355
365
  };
356
366
 
357
367
  editorService.on('history-change', historyChangeHandler);
@@ -386,7 +396,9 @@ export const initServiceEvents = (
386
396
  removeDataSourceTarget(config.id);
387
397
  initDataSourceDepTarget(config);
388
398
 
389
- depService.collect(root?.items || [], true);
399
+ (root?.items || []).forEach((page) => {
400
+ depService.collectIdle([page], { pageId: page.id }, true);
401
+ });
390
402
 
391
403
  const targets = depService.getTargets(DepTargetType.DATA_SOURCE);
392
404
 
@@ -410,15 +422,9 @@ export const initServiceEvents = (
410
422
  dataSourceService.on('update', dataSourceUpdateHandler);
411
423
  dataSourceService.on('remove', dataSourceRemoveHandler);
412
424
 
413
- // 初始化复制组件相关的依赖收集器
414
- if (props.collectorOptions && !depService.hasTarget(DepTargetType.RELATED_COMP_WHEN_COPY)) {
415
- depService.addTarget(createRelatedCompTarget(props.collectorOptions));
416
- }
417
-
418
425
  onBeforeUnmount(() => {
419
426
  depService.off('add-target', targetAddHandler);
420
427
  depService.off('remove-target', targetRemoveHandler);
421
- depService.off('dep-update', depUpdateHandler);
422
428
  depService.off('collected', collectedHandler);
423
429
 
424
430
  editorService.off('history-change', historyChangeHandler);
@@ -58,26 +58,44 @@ const { codeBlockService, depService, editorService } = services || {};
58
58
 
59
59
  // 代码块列表
60
60
  const codeList = computed<TreeNodeData[]>(() =>
61
- Object.values(depService?.getTargets(DepTargetType.CODE_BLOCK) || {}).map((target) => {
61
+ Object.entries(codeBlockService?.getCodeDsl() || {}).map(([codeId, code]) => {
62
+ const target = depService?.getTarget(codeId, DepTargetType.CODE_BLOCK);
63
+
64
+ // 按页面分类显示
65
+ const pageList: TreeNodeData[] =
66
+ editorService?.get('root')?.items.map((page) => ({
67
+ name: page.devconfig?.tabName || page.name,
68
+ type: 'node',
69
+ id: `${codeId}_${page.id}`,
70
+ key: page.id,
71
+ items: [],
72
+ })) || [];
73
+
62
74
  // 组件节点
63
- const compNodes: TreeNodeData[] = Object.entries(target.deps).map(([id, dep]) => ({
64
- name: dep.name,
65
- type: 'node',
66
- id: `${target.id}_${id}`,
67
- key: id,
68
- items: dep.keys.map((key) => {
69
- const data: TreeNodeData = { name: `${key}`, id: `${target.id}_${id}_${key}`, type: 'key' };
70
- return data;
71
- }),
72
- }));
75
+ if (target) {
76
+ Object.entries(target.deps).forEach(([id, dep]) => {
77
+ const page = pageList.find((page) => page.key === dep.data?.pageId);
78
+ page?.items?.push({
79
+ name: dep.name,
80
+ type: 'node',
81
+ id: `${page.id}_${id}`,
82
+ key: id,
83
+ items: dep.keys.map((key) => {
84
+ const data: TreeNodeData = { name: `${key}`, id: `${target.id}_${id}_${key}`, type: 'key' };
85
+ return data;
86
+ }),
87
+ });
88
+ });
89
+ }
73
90
 
74
91
  const data: TreeNodeData = {
75
- id: target.id,
76
- key: target.id,
77
- name: target.name,
92
+ id: codeId,
93
+ key: codeId,
94
+ name: code.name,
78
95
  type: 'code',
79
- codeBlockContent: codeBlockService?.getCodeContentById(target.id),
80
- items: compNodes,
96
+ codeBlockContent: codeBlockService?.getCodeContentById(codeId),
97
+ // 只有一个页面不显示页面分类
98
+ items: pageList.length > 1 ? pageList.filter((page) => page.items?.length) : pageList[0]?.items || [],
81
99
  };
82
100
 
83
101
  return data;
@@ -81,16 +81,19 @@ const getNodeTreeConfig = (id: string, dep: DepData[string], type?: string, pare
81
81
  * @param deps 依赖
82
82
  * @param type 依赖类型
83
83
  */
84
- const mergeChildren = (dsId: Id, items: any[], deps: DepData, type?: string) => {
84
+ const mergeChildren = (dsId: Id, pageItems: any[], deps: DepData, type?: string) => {
85
85
  Object.entries(deps).forEach(([id, dep]) => {
86
+ // 按页面分类显示
87
+ const page = pageItems.find((page) => page.key === dep.data?.pageId);
88
+
86
89
  // 已经生成过的节点
87
- const nodeItem = items.find((item) => item.key === id);
90
+ const nodeItem = page?.items.find((item: any) => item.key === id);
88
91
  // 节点存在,则追加依赖的key
89
92
  if (nodeItem) {
90
93
  nodeItem.items = nodeItem.items.concat(getKeyTreeConfig(dep, type, nodeItem.key));
91
94
  } else {
92
95
  // 节点不存在,则生成
93
- items.push(getNodeTreeConfig(id, dep, type, dsId));
96
+ page?.items.push(getNodeTreeConfig(id, dep, type, page.id));
94
97
  }
95
98
  });
96
99
  };
@@ -101,7 +104,15 @@ const list = computed(() =>
101
104
  const dsMethodDeps = dsMethodDep.value[ds.id]?.deps || {};
102
105
  const dsCondDeps = dsCondDep.value[ds.id]?.deps || {};
103
106
 
104
- const items: any[] = [];
107
+ const items =
108
+ editorService?.get('root')?.items.map((page) => ({
109
+ name: page.devconfig?.tabName || page.name,
110
+ type: 'node',
111
+ id: `${ds.id}_${page.id}`,
112
+ key: page.id,
113
+ items: [],
114
+ })) || [];
115
+
105
116
  // 数据源依赖分为三种类型:key/node、method、cond,是分开存储,这里将其合并展示
106
117
  mergeChildren(ds.id, items, dsDeps);
107
118
  mergeChildren(ds.id, items, dsMethodDeps, 'method');
@@ -112,7 +123,8 @@ const list = computed(() =>
112
123
  key: ds.id,
113
124
  name: ds.title,
114
125
  type: 'ds',
115
- items,
126
+ // 只有一个页面不显示页面分类
127
+ items: items.length > 1 ? items.filter((page) => page.items.length) : items[0]?.items || [],
116
128
  };
117
129
  }),
118
130
  );
@@ -17,21 +17,25 @@
17
17
  */
18
18
 
19
19
  import { reactive } from 'vue';
20
- import { keys, pick } from 'lodash-es';
20
+ import { cloneDeep, get, keys, pick } from 'lodash-es';
21
21
  import type { Writable } from 'type-fest';
22
22
 
23
+ import { Target, type TargetOptions, Watcher } from '@tmagic/dep';
23
24
  import type { ColumnConfig } from '@tmagic/form';
24
- import type { CodeBlockContent, CodeBlockDSL, Id } from '@tmagic/schema';
25
+ import type { CodeBlockContent, CodeBlockDSL, Id, MNode } from '@tmagic/schema';
25
26
 
27
+ import editorService from '@editor/services/editor';
28
+ import storageService, { Protocol } from '@editor/services/storage';
26
29
  import type { AsyncHookPlugin, CodeState } from '@editor/type';
27
30
  import { CODE_DRAFT_STORAGE_KEY } from '@editor/type';
28
31
  import { getConfig } from '@editor/utils/config';
32
+ import { COPY_CODE_STORAGE_KEY } from '@editor/utils/editor';
29
33
 
30
34
  import BaseService from './BaseService';
31
35
 
32
36
  const canUsePluginMethods = {
33
37
  async: ['setCodeDslById', 'setEditStatus', 'setCombineIds', 'setUndeleteableList', 'deleteCodeDslByIds'] as const,
34
- sync: [],
38
+ sync: ['setCodeDslByIdSync'],
35
39
  };
36
40
 
37
41
  type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
@@ -46,7 +50,10 @@ class CodeBlock extends BaseService {
46
50
  });
47
51
 
48
52
  constructor() {
49
- super(canUsePluginMethods.async.map((methodName) => ({ name: methodName, isAsync: true })));
53
+ super([
54
+ ...canUsePluginMethods.async.map((methodName) => ({ name: methodName, isAsync: true })),
55
+ ...canUsePluginMethods.sync.map((methodName) => ({ name: methodName, isAsync: false })),
56
+ ]);
50
57
  }
51
58
 
52
59
  /**
@@ -88,20 +95,32 @@ class CodeBlock extends BaseService {
88
95
  * @returns {void}
89
96
  */
90
97
  public async setCodeDslById(id: Id, codeConfig: Partial<CodeBlockContent>): Promise<void> {
98
+ this.setCodeDslByIdSync(id, codeConfig, true);
99
+ }
100
+
101
+ /**
102
+ * 为了兼容历史原因
103
+ * 设置代码块ID和代码内容到源dsl
104
+ * @param {Id} id 代码块id
105
+ * @param {CodeBlockContent} codeConfig 代码块内容配置信息
106
+ * @param {boolean} force 是否强制写入,默认true
107
+ * @returns {void}
108
+ */
109
+ public setCodeDslByIdSync(id: Id, codeConfig: Partial<CodeBlockContent>, force = true): void {
91
110
  const codeDsl = this.getCodeDsl();
92
111
 
93
112
  if (!codeDsl) {
94
113
  throw new Error('dsl中没有codeBlocks');
95
114
  }
115
+ if (codeDsl[id] && !force) return;
96
116
 
97
- const codeConfigProcessed = codeConfig;
98
- if (codeConfig.content) {
117
+ const codeConfigProcessed = cloneDeep(codeConfig);
118
+ if (codeConfigProcessed.content) {
99
119
  // 在保存的时候转换代码内容
100
120
  const parseDSL = getConfig('parseDSL');
101
- if (typeof codeConfig.content === 'string') {
102
- codeConfig.content = parseDSL<(...args: any[]) => any>(codeConfig.content);
121
+ if (typeof codeConfigProcessed.content === 'string') {
122
+ codeConfigProcessed.content = parseDSL<(...args: any[]) => any>(codeConfigProcessed.content);
103
123
  }
104
- codeConfigProcessed.content = codeConfig.content;
105
124
  }
106
125
 
107
126
  const existContent = codeDsl[id] || {};
@@ -233,6 +252,58 @@ class CodeBlock extends BaseService {
233
252
  return await this.getUniqueId();
234
253
  }
235
254
 
255
+ /**
256
+ * 复制时会带上组件关联的代码块
257
+ * @param config 组件节点配置
258
+ * @returns
259
+ */
260
+ public copyWithRelated(config: MNode | MNode[], collectorOptions?: TargetOptions): void {
261
+ const copyNodes: MNode[] = Array.isArray(config) ? config : [config];
262
+ const copyData: CodeBlockDSL = {};
263
+
264
+ if (collectorOptions && typeof collectorOptions.isTarget === 'function') {
265
+ const customTarget = new Target({
266
+ ...collectorOptions,
267
+ });
268
+
269
+ const coperWatcher = new Watcher();
270
+
271
+ coperWatcher.addTarget(customTarget);
272
+
273
+ coperWatcher.collect(copyNodes, {}, true, collectorOptions.type);
274
+
275
+ Object.keys(customTarget.deps).forEach((nodeId: Id) => {
276
+ const node = editorService.getNodeById(nodeId);
277
+ if (!node) return;
278
+ customTarget!.deps[nodeId].keys.forEach((key) => {
279
+ const relateCodeId = get(node, key);
280
+ const isExist = Object.keys(copyData).find((codeId: Id) => codeId === relateCodeId);
281
+ if (!isExist) {
282
+ const relateCode = this.getCodeContentById(relateCodeId);
283
+ if (relateCode) {
284
+ copyData[relateCodeId] = relateCode;
285
+ }
286
+ }
287
+ });
288
+ });
289
+ }
290
+ storageService.setItem(COPY_CODE_STORAGE_KEY, copyData, {
291
+ protocol: Protocol.OBJECT,
292
+ });
293
+ }
294
+
295
+ /**
296
+ * 粘贴代码块
297
+ * @returns
298
+ */
299
+ public paste() {
300
+ const codeDsl: CodeBlockDSL = storageService.getItem(COPY_CODE_STORAGE_KEY);
301
+ Object.keys(codeDsl).forEach((codeId: Id) => {
302
+ // 不覆盖同样id的代码块
303
+ this.setCodeDslByIdSync(codeId, codeDsl[codeId], false);
304
+ });
305
+ }
306
+
236
307
  public resetState() {
237
308
  this.state.codeDsl = null;
238
309
  this.state.editable = true;