@tmagic/editor 1.4.4 → 1.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/style.css +2 -2
  2. package/dist/tmagic-editor.js +2393 -2242
  3. package/dist/tmagic-editor.umd.cjs +2403 -2248
  4. package/package.json +27 -27
  5. package/src/editorProps.ts +5 -1
  6. package/src/fields/DataSourceFieldSelect.vue +3 -41
  7. package/src/fields/EventSelect.vue +30 -9
  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 +12 -2
  12. package/src/layouts/workspace/viewer/Stage.vue +7 -6
  13. package/src/services/codeBlock.ts +77 -9
  14. package/src/services/dataSource.ts +53 -4
  15. package/src/services/editor.ts +2 -2
  16. package/src/services/props.ts +10 -5
  17. package/src/services/stageOverlay.ts +1 -0
  18. package/src/type.ts +1 -0
  19. package/src/utils/content-menu.ts +7 -3
  20. package/src/utils/data-source/index.ts +39 -3
  21. package/src/utils/editor.ts +16 -7
  22. package/types/components/CodeBlockEditor.vue.d.ts +21 -34
  23. package/types/components/CodeParams.vue.d.ts +2 -2
  24. package/types/components/ContentMenu.vue.d.ts +1 -1
  25. package/types/components/FloatingBox.vue.d.ts +41 -38
  26. package/types/components/ScrollViewer.vue.d.ts +1 -1
  27. package/types/components/ToolButton.vue.d.ts +4 -4
  28. package/types/components/Tree.vue.d.ts +2 -2
  29. package/types/editorProps.d.ts +5 -1
  30. package/types/fields/CodeLink.vue.d.ts +2 -2
  31. package/types/fields/DataSourceFields.vue.d.ts +32 -78
  32. package/types/fields/DataSourceInput.vue.d.ts +2 -2
  33. package/types/fields/DataSourceMethods.vue.d.ts +2 -2
  34. package/types/fields/DataSourceMocks.vue.d.ts +31 -76
  35. package/types/fields/KeyValue.vue.d.ts +2 -2
  36. package/types/fields/UISelect.vue.d.ts +2 -2
  37. package/types/hooks/use-code-block-edit.d.ts +18 -6
  38. package/types/hooks/use-data-source-edit.d.ts +18 -6
  39. package/types/hooks/use-data-source-method.d.ts +18 -6
  40. package/types/index.d.ts +1 -0
  41. package/types/layouts/CodeEditor.vue.d.ts +1 -1
  42. package/types/layouts/PropsPanel.vue.d.ts +14 -14
  43. package/types/layouts/sidebar/data-source/DataSourceConfigPanel.vue.d.ts +20 -30
  44. package/types/layouts/sidebar/layer/LayerMenu.vue.d.ts +6 -6
  45. package/types/layouts/workspace/viewer/Stage.vue.d.ts +4 -4
  46. package/types/layouts/workspace/viewer/ViewerMenu.vue.d.ts +6 -6
  47. package/types/services/codeBlock.d.ts +22 -2
  48. package/types/services/dataSource.d.ts +13 -1
  49. package/types/services/ui.d.ts +1 -1
  50. package/types/type.d.ts +1 -0
  51. package/types/utils/data-source/index.d.ts +3 -2
  52. package/types/utils/editor.d.ts +2 -0
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.4.4",
2
+ "version": "1.4.6",
3
3
  "name": "@tmagic/editor",
4
4
  "type": "module",
5
5
  "sideEffects": [
@@ -40,16 +40,7 @@
40
40
  "typescript"
41
41
  ],
42
42
  "dependencies": {
43
- "@babel/core": "^7.18.0",
44
43
  "@element-plus/icons-vue": "^2.3.1",
45
- "@tmagic/core": "1.4.4",
46
- "@tmagic/dep": "1.4.4",
47
- "@tmagic/design": "1.4.4",
48
- "@tmagic/form": "1.4.4",
49
- "@tmagic/schema": "1.4.4",
50
- "@tmagic/stage": "1.4.4",
51
- "@tmagic/table": "1.4.4",
52
- "@tmagic/utils": "1.4.4",
53
44
  "buffer": "^6.0.3",
54
45
  "color": "^3.1.3",
55
46
  "emmet-monaco-es": "^5.3.0",
@@ -57,37 +48,46 @@
57
48
  "gesto": "^1.19.1",
58
49
  "keycon": "^1.4.0",
59
50
  "lodash-es": "^4.17.21",
60
- "monaco-editor": "^0.47.0",
61
- "moveable": "^0.51.1",
51
+ "moveable": "^0.53.0",
62
52
  "serialize-javascript": "^6.0.0",
63
- "vue": "^3.4.21"
64
- },
65
- "peerDependencies": {
66
- "@tmagic/design": "1.4.4",
67
- "@tmagic/form": "1.4.4",
68
- "monaco-editor": "^0.47.0",
69
- "vue": "^3.4.21"
53
+ "@tmagic/dep": "1.4.6",
54
+ "@tmagic/table": "1.4.6"
70
55
  },
71
56
  "devDependencies": {
72
57
  "@types/events": "^3.0.0",
73
58
  "@types/lodash-es": "^4.17.4",
74
59
  "@types/node": "^18.19.0",
75
60
  "@types/serialize-javascript": "^5.0.1",
76
- "@vitejs/plugin-vue": "^4.5.2",
77
- "@vue/compiler-sfc": "^3.4.21",
78
- "@vue/test-utils": "^2.4.4",
61
+ "@vitejs/plugin-vue": "^5.0.4",
62
+ "@vue/compiler-sfc": "^3.4.27",
63
+ "@vue/test-utils": "^2.4.6",
79
64
  "rimraf": "^3.0.2",
80
- "sass": "^1.35.1",
65
+ "sass": "^1.77.0",
81
66
  "tsc-alias": "^1.8.5",
82
67
  "type-fest": "^4.10.3",
83
- "typescript": "^5.4.2",
84
- "vite": "^5.1.6",
85
- "vue-tsc": "^2.0.6"
68
+ "vite": "^5.2.11",
69
+ "vue-tsc": "^2.0.16"
70
+ },
71
+ "peerDependencies": {
72
+ "monaco-editor": "^0.48.0",
73
+ "vue": "^3.4.27",
74
+ "typescript": "*",
75
+ "@tmagic/form": "1.4.6",
76
+ "@tmagic/schema": "1.4.6",
77
+ "@tmagic/design": "1.4.6",
78
+ "@tmagic/core": "1.4.6",
79
+ "@tmagic/utils": "1.4.6",
80
+ "@tmagic/stage": "1.4.6"
81
+ },
82
+ "peerDependenciesMeta": {
83
+ "typescript": {
84
+ "optional": true
85
+ }
86
86
  },
87
87
  "scripts": {
88
88
  "build": "npm run build:type && vite build",
89
89
  "build:type": "npm run clear:type && vue-tsc --declaration --emitDeclarationOnly --project tsconfig.build.json && tsc-alias -p tsconfig.build.json",
90
90
  "clear:type": "rimraf ./types",
91
- "type:check": "vue-tsc --noEmit"
91
+ "check:type": "vue-tsc --noEmit --project tsconfig.build.json"
92
92
  }
93
93
  }
@@ -70,8 +70,12 @@ export interface EditorProps {
70
70
  codeOptions?: { [key: string]: any };
71
71
  /** 禁用鼠标左键按下时就开始拖拽,需要先选中再可以拖拽 */
72
72
  disabledDragStart?: boolean;
73
- /** 自定义依赖收集器,复制组件时会将关联依赖一并复制 */
73
+ /** 自定义依赖收集器,复制组件时会将关联组件一并复制 */
74
74
  collectorOptions?: CustomTargetOptions;
75
+ /** 自定义依赖收集器,复制组件时会将关联代码块一并复制 */
76
+ collectorOptionsForCode?: CustomTargetOptions;
77
+ /** 自定义依赖收集器,复制组件时会将关联数据源一并复制 */
78
+ collectorOptionsForDataSource?: CustomTargetOptions;
75
79
  /** 标尺配置 */
76
80
  guidesOptions?: Partial<GuidesOptions>;
77
81
  /** 禁止多选 */
@@ -1,7 +1,6 @@
1
1
  <template>
2
2
  <div class="m-fields-data-source-field-select">
3
3
  <component
4
- style="width: 100%"
5
4
  :is="tagName"
6
5
  :config="showDataSourceFieldSelect || !config.fieldConfig ? cascaderConfig : config.fieldConfig"
7
6
  :model="model"
@@ -39,14 +38,14 @@ import { computed, inject, ref, resolveComponent, watch } from 'vue';
39
38
  import { Coin, Edit, View } from '@element-plus/icons-vue';
40
39
 
41
40
  import { TMagicButton } from '@tmagic/design';
42
- import type { CascaderConfig, CascaderOption, FieldProps, FormState } from '@tmagic/form';
41
+ import type { CascaderConfig, FieldProps, FormState } from '@tmagic/form';
43
42
  import { filterFunction, MCascader } from '@tmagic/form';
44
- import type { DataSchema, DataSourceFieldType } from '@tmagic/schema';
45
43
  import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
46
44
 
47
45
  import MIcon from '@editor/components/Icon.vue';
48
46
  import type { DataSourceFieldSelectConfig, EventBus, Services } from '@editor/type';
49
47
  import { SideItemKey } from '@editor/type';
48
+ import { getCascaderOptionsFromFields } from '@editor/utils';
50
49
 
51
50
  defineOptions({
52
51
  name: 'MFieldsDataSourceFieldSelect',
@@ -77,43 +76,6 @@ const selectedDataSourceId = computed(() => {
77
76
 
78
77
  const dataSources = computed(() => services?.dataSourceService.get('dataSources'));
79
78
 
80
- const getOptionChildren = (
81
- fields: DataSchema[] = [],
82
- dataSourceFieldType: DataSourceFieldType[] = ['any'],
83
- ): CascaderOption[] => {
84
- const child: CascaderOption[] = [];
85
- fields.forEach((field) => {
86
- if (!dataSourceFieldType.length) {
87
- dataSourceFieldType.push('any');
88
- }
89
-
90
- const children = getOptionChildren(field.fields, dataSourceFieldType);
91
-
92
- const item = {
93
- label: field.title || field.name,
94
- value: field.name,
95
- children,
96
- };
97
-
98
- const fieldType = field.type || 'any';
99
- if (dataSourceFieldType.includes('any') || dataSourceFieldType.includes(fieldType)) {
100
- child.push(item);
101
- return;
102
- }
103
-
104
- if (!dataSourceFieldType.includes(fieldType) && !['array', 'object', 'any'].includes(fieldType)) {
105
- return;
106
- }
107
-
108
- if (!children.length && ['object', 'array', 'any'].includes(field.type || '')) {
109
- return;
110
- }
111
-
112
- child.push(item);
113
- });
114
- return child;
115
- };
116
-
117
79
  const cascaderConfig = computed<CascaderConfig>(() => {
118
80
  const valueIsKey = props.config.value === 'key';
119
81
 
@@ -126,7 +88,7 @@ const cascaderConfig = computed<CascaderConfig>(() => {
126
88
  dataSources.value?.map((ds) => ({
127
89
  label: ds.title || ds.id,
128
90
  value: valueIsKey ? ds.id : `${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}${ds.id}`,
129
- children: getOptionChildren(ds.fields, props.config.dataSourceFieldType),
91
+ children: getCascaderOptionsFromFields(ds.fields, props.config.dataSourceFieldType),
130
92
  })) || [];
131
93
  return options.filter((option) => option.children.length);
132
94
  },
@@ -55,11 +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 { FieldProps, FormState, PanelConfig } from '@tmagic/form';
58
+ import type { CascaderOption, FieldProps, FormState, PanelConfig } from '@tmagic/form';
59
59
  import { MContainer, MPanel } from '@tmagic/form';
60
60
  import { ActionType } from '@tmagic/schema';
61
+ import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX } from '@tmagic/utils';
61
62
 
62
63
  import type { CodeSelectColConfig, DataSourceMethodSelectConfig, EventSelectConfig, Services } from '@editor/type';
64
+ import { getCascaderOptionsFromFields } from '@editor/utils';
63
65
 
64
66
  defineOptions({
65
67
  name: 'MFieldsEventSelect',
@@ -78,26 +80,45 @@ const codeBlockService = services?.codeBlockService;
78
80
 
79
81
  // 事件名称下拉框表单配置
80
82
  const eventNameConfig = computed(() => {
83
+ const fieldType = props.config.src === 'component' ? 'select' : 'cascader';
81
84
  const defaultEventNameConfig = {
82
85
  name: 'name',
83
86
  text: '事件',
84
- type: 'select',
87
+ type: fieldType,
85
88
  labelWidth: '40px',
89
+ checkStrictly: true,
90
+ valueSeparator: '.',
86
91
  options: (mForm: FormState, { formValue }: any) => {
87
- let events: EventOption[] = [];
92
+ let events: EventOption[] | CascaderOption[] = [];
88
93
 
89
94
  if (!eventsService || !dataSourceService) return events;
90
95
 
91
96
  if (props.config.src === 'component') {
92
97
  events = eventsService.getEvent(formValue.type);
93
- } else if (props.config.src === 'datasource') {
94
- events = dataSourceService.getFormEvent(formValue.type);
98
+ return events.map((option) => ({
99
+ text: option.label,
100
+ value: option.value,
101
+ }));
95
102
  }
103
+ if (props.config.src === 'datasource') {
104
+ // 从数据源类型中获取到相关事件
105
+ events = dataSourceService.getFormEvent(formValue.type);
106
+ // 从数据源类型和实例中分别获取数据以追加数据变化的事件
107
+ const dataSource = dataSourceService.getDataSourceById(formValue.id);
108
+ const fields = dataSource?.fields || [];
109
+ if (fields.length > 0) {
110
+ return [
111
+ ...events,
112
+ {
113
+ label: '数据变化',
114
+ value: DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX,
115
+ children: getCascaderOptionsFromFields(fields),
116
+ },
117
+ ];
118
+ }
96
119
 
97
- return events.map((option) => ({
98
- text: option.label,
99
- value: option.value,
100
- }));
120
+ return events;
121
+ }
101
122
  },
102
123
  };
103
124
  return { ...defaultEventNameConfig, ...props.config.eventNameConfig };
@@ -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,7 +7,7 @@ import {
7
7
  createDataSourceCondTarget,
8
8
  createDataSourceMethodTarget,
9
9
  createDataSourceTarget,
10
- createRelatedCompTarget,
10
+ createRelatedTargetForCopy,
11
11
  DepTargetType,
12
12
  Target,
13
13
  } from '@tmagic/dep';
@@ -412,7 +412,17 @@ export const initServiceEvents = (
412
412
 
413
413
  // 初始化复制组件相关的依赖收集器
414
414
  if (props.collectorOptions && !depService.hasTarget(DepTargetType.RELATED_COMP_WHEN_COPY)) {
415
- depService.addTarget(createRelatedCompTarget(props.collectorOptions));
415
+ depService.addTarget(createRelatedTargetForCopy(props.collectorOptions, DepTargetType.RELATED_COMP_WHEN_COPY));
416
+ }
417
+ if (props.collectorOptionsForCode && !depService.hasTarget(DepTargetType.RELATED_CODE_WHEN_COPY)) {
418
+ depService.addTarget(
419
+ createRelatedTargetForCopy(props.collectorOptionsForCode, DepTargetType.RELATED_CODE_WHEN_COPY),
420
+ );
421
+ }
422
+ if (props.collectorOptionsForDataSource && !depService.hasTarget(DepTargetType.RELATED_DS_WHEN_COPY)) {
423
+ depService.addTarget(
424
+ createRelatedTargetForCopy(props.collectorOptionsForDataSource, DepTargetType.RELATED_DS_WHEN_COPY),
425
+ );
416
426
  }
417
427
 
418
428
  onBeforeUnmount(() => {
@@ -45,7 +45,8 @@ import { computed, inject, markRaw, nextTick, onBeforeUnmount, onMounted, ref, t
45
45
  import { cloneDeep } from 'lodash-es';
46
46
 
47
47
  import type { MApp, MContainer } from '@tmagic/schema';
48
- import StageCore, { calcValueByFontsize, getOffset, Runtime } from '@tmagic/stage';
48
+ import StageCore, { getOffset, Runtime } from '@tmagic/stage';
49
+ import { calcValueByFontsize } from '@tmagic/utils';
49
50
 
50
51
  import ScrollViewer from '@editor/components/ScrollViewer.vue';
51
52
  import { useStage } from '@editor/hooks/use-stage';
@@ -223,18 +224,18 @@ const dropHandler = async (e: DragEvent) => {
223
224
  top = e.clientY - containerRect.top + scrollTop;
224
225
  left = e.clientX - containerRect.left + scrollLeft;
225
226
 
226
- if (parentEl && doc) {
227
+ if (parentEl) {
227
228
  const { left: parentLeft, top: parentTop } = getOffset(parentEl);
228
- left = left - calcValueByFontsize(doc, parentLeft) * zoom.value;
229
- top = top - calcValueByFontsize(doc, parentTop) * zoom.value;
229
+ left = left - parentLeft * zoom.value;
230
+ top = top - parentTop * zoom.value;
230
231
  }
231
232
  }
232
233
 
233
234
  config.data.style = {
234
235
  ...style,
235
236
  position,
236
- top: top / zoom.value,
237
- left: left / zoom.value,
237
+ top: calcValueByFontsize(doc, top / zoom.value),
238
+ left: calcValueByFontsize(doc, left / zoom.value),
238
239
  };
239
240
 
240
241
  config.data.inputEvent = e;
@@ -17,21 +17,26 @@
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 { DepTargetType } 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 depService from '@editor/services/dep';
28
+ import editorService from '@editor/services/editor';
29
+ import storageService, { Protocol } from '@editor/services/storage';
26
30
  import type { AsyncHookPlugin, CodeState } from '@editor/type';
27
31
  import { CODE_DRAFT_STORAGE_KEY } from '@editor/type';
28
32
  import { getConfig } from '@editor/utils/config';
33
+ import { COPY_CODE_STORAGE_KEY } from '@editor/utils/editor';
29
34
 
30
35
  import BaseService from './BaseService';
31
36
 
32
37
  const canUsePluginMethods = {
33
38
  async: ['setCodeDslById', 'setEditStatus', 'setCombineIds', 'setUndeleteableList', 'deleteCodeDslByIds'] as const,
34
- sync: [],
39
+ sync: ['setCodeDslByIdSync'],
35
40
  };
36
41
 
37
42
  type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>;
@@ -46,7 +51,10 @@ class CodeBlock extends BaseService {
46
51
  });
47
52
 
48
53
  constructor() {
49
- super(canUsePluginMethods.async.map((methodName) => ({ name: methodName, isAsync: true })));
54
+ super([
55
+ ...canUsePluginMethods.async.map((methodName) => ({ name: methodName, isAsync: true })),
56
+ ...canUsePluginMethods.sync.map((methodName) => ({ name: methodName, isAsync: false })),
57
+ ]);
50
58
  }
51
59
 
52
60
  /**
@@ -88,20 +96,32 @@ class CodeBlock extends BaseService {
88
96
  * @returns {void}
89
97
  */
90
98
  public async setCodeDslById(id: Id, codeConfig: Partial<CodeBlockContent>): Promise<void> {
99
+ this.setCodeDslByIdSync(id, codeConfig, true);
100
+ }
101
+
102
+ /**
103
+ * 为了兼容历史原因
104
+ * 设置代码块ID和代码内容到源dsl
105
+ * @param {Id} id 代码块id
106
+ * @param {CodeBlockContent} codeConfig 代码块内容配置信息
107
+ * @param {boolean} force 是否强制写入,默认true
108
+ * @returns {void}
109
+ */
110
+ public setCodeDslByIdSync(id: Id, codeConfig: Partial<CodeBlockContent>, force = true): void {
91
111
  const codeDsl = this.getCodeDsl();
92
112
 
93
113
  if (!codeDsl) {
94
114
  throw new Error('dsl中没有codeBlocks');
95
115
  }
116
+ if (codeDsl[id] && !force) return;
96
117
 
97
- const codeConfigProcessed = codeConfig;
98
- if (codeConfig.content) {
118
+ const codeConfigProcessed = cloneDeep(codeConfig);
119
+ if (codeConfigProcessed.content) {
99
120
  // 在保存的时候转换代码内容
100
121
  const parseDSL = getConfig('parseDSL');
101
- if (typeof codeConfig.content === 'string') {
102
- codeConfig.content = parseDSL<(...args: any[]) => any>(codeConfig.content);
122
+ if (typeof codeConfigProcessed.content === 'string') {
123
+ codeConfigProcessed.content = parseDSL<(...args: any[]) => any>(codeConfigProcessed.content);
103
124
  }
104
- codeConfigProcessed.content = codeConfig.content;
105
125
  }
106
126
 
107
127
  const existContent = codeDsl[id] || {};
@@ -233,6 +253,54 @@ class CodeBlock extends BaseService {
233
253
  return await this.getUniqueId();
234
254
  }
235
255
 
256
+ /**
257
+ * 复制时会带上组件关联的代码块
258
+ * @param config 组件节点配置
259
+ * @returns
260
+ */
261
+ public copyWithRelated(config: MNode | MNode[]): void {
262
+ const copyNodes: MNode[] = Array.isArray(config) ? config : [config];
263
+ // 关联的代码块也一并复制
264
+ depService.clearByType(DepTargetType.RELATED_CODE_WHEN_COPY);
265
+ depService.collect(copyNodes, true, DepTargetType.RELATED_CODE_WHEN_COPY);
266
+ const customTarget = depService.getTarget(
267
+ DepTargetType.RELATED_CODE_WHEN_COPY,
268
+ DepTargetType.RELATED_CODE_WHEN_COPY,
269
+ );
270
+ const copyData: CodeBlockDSL = {};
271
+ if (customTarget) {
272
+ Object.keys(customTarget.deps).forEach((nodeId: Id) => {
273
+ const node = editorService.getNodeById(nodeId);
274
+ if (!node) return;
275
+ customTarget!.deps[nodeId].keys.forEach((key) => {
276
+ const relateCodeId = get(node, key);
277
+ const isExist = Object.keys(copyData).find((codeId: Id) => codeId === relateCodeId);
278
+ if (!isExist) {
279
+ const relateCode = this.getCodeContentById(relateCodeId);
280
+ if (relateCode) {
281
+ copyData[relateCodeId] = relateCode;
282
+ }
283
+ }
284
+ });
285
+ });
286
+ }
287
+ storageService.setItem(COPY_CODE_STORAGE_KEY, copyData, {
288
+ protocol: Protocol.OBJECT,
289
+ });
290
+ }
291
+
292
+ /**
293
+ * 粘贴代码块
294
+ * @returns
295
+ */
296
+ public paste() {
297
+ const codeDsl: CodeBlockDSL = storageService.getItem(COPY_CODE_STORAGE_KEY);
298
+ Object.keys(codeDsl).forEach((codeId: Id) => {
299
+ // 不覆盖同样id的代码块
300
+ this.setCodeDslByIdSync(codeId, codeDsl[codeId], false);
301
+ });
302
+ }
303
+
236
304
  public resetState() {
237
305
  this.state.codeDsl = null;
238
306
  this.state.editable = true;
@@ -1,14 +1,19 @@
1
1
  import { reactive } from 'vue';
2
- import { cloneDeep } from 'lodash-es';
2
+ import { cloneDeep, get } from 'lodash-es';
3
3
  import { Writable } from 'type-fest';
4
4
 
5
5
  import type { EventOption } from '@tmagic/core';
6
+ import { DepTargetType } from '@tmagic/dep';
6
7
  import type { FormConfig } from '@tmagic/form';
7
- import type { DataSourceSchema } from '@tmagic/schema';
8
+ import type { DataSourceSchema, Id, MNode } from '@tmagic/schema';
8
9
  import { guid, toLine } from '@tmagic/utils';
9
10
 
11
+ import depService from '@editor/services/dep';
12
+ import editorService from '@editor/services/editor';
13
+ import storageService, { Protocol } from '@editor/services/storage';
10
14
  import type { DatasourceTypeOption, SyncHookPlugin } from '@editor/type';
11
15
  import { getFormConfig, getFormValue } from '@editor/utils/data-source';
16
+ import { COPY_DS_STORAGE_KEY } from '@editor/utils/editor';
12
17
 
13
18
  import BaseService from './BaseService';
14
19
 
@@ -102,7 +107,7 @@ class DataSource extends BaseService {
102
107
  public add(config: DataSourceSchema) {
103
108
  const newConfig = {
104
109
  ...config,
105
- id: this.createId(),
110
+ id: config.id && !this.getDataSourceById(config.id) ? config.id : this.createId(),
106
111
  };
107
112
 
108
113
  this.get('dataSources').push(newConfig);
@@ -116,7 +121,6 @@ class DataSource extends BaseService {
116
121
  const dataSources = this.get('dataSources');
117
122
 
118
123
  const index = dataSources.findIndex((ds) => ds.id === config.id);
119
-
120
124
  dataSources[index] = cloneDeep(config);
121
125
 
122
126
  this.emit('update', config);
@@ -153,6 +157,51 @@ class DataSource extends BaseService {
153
157
  public usePlugin(options: SyncHookPlugin<SyncMethodName, DataSource>): void {
154
158
  super.usePlugin(options);
155
159
  }
160
+
161
+ /**
162
+ * 复制时会带上组件关联的数据源
163
+ * @param config 组件节点配置
164
+ * @returns
165
+ */
166
+ public copyWithRelated(config: MNode | MNode[]): void {
167
+ const copyNodes: MNode[] = Array.isArray(config) ? config : [config];
168
+ depService.clearByType(DepTargetType.RELATED_DS_WHEN_COPY);
169
+ depService.collect(copyNodes, true, DepTargetType.RELATED_DS_WHEN_COPY);
170
+ const customTarget = depService.getTarget(DepTargetType.RELATED_DS_WHEN_COPY, DepTargetType.RELATED_DS_WHEN_COPY);
171
+ const copyData: DataSourceSchema[] = [];
172
+ if (customTarget) {
173
+ Object.keys(customTarget.deps).forEach((nodeId: Id) => {
174
+ const node = editorService.getNodeById(nodeId);
175
+ if (!node) return;
176
+ customTarget!.deps[nodeId].keys.forEach((key) => {
177
+ const [relateDsId] = get(node, key);
178
+ const isExist = copyData.find((dsItem) => dsItem.id === relateDsId);
179
+ if (!isExist) {
180
+ const relateDs = this.getDataSourceById(relateDsId);
181
+ if (relateDs) {
182
+ copyData.push(relateDs);
183
+ }
184
+ }
185
+ });
186
+ });
187
+ }
188
+ storageService.setItem(COPY_DS_STORAGE_KEY, copyData, {
189
+ protocol: Protocol.OBJECT,
190
+ });
191
+ }
192
+ /**
193
+ * 粘贴数据源
194
+ * @returns
195
+ */
196
+ public paste() {
197
+ const dataSource: DataSourceSchema[] = storageService.getItem(COPY_DS_STORAGE_KEY);
198
+ dataSource.forEach((item: DataSourceSchema) => {
199
+ // 不覆盖同样id的数据源
200
+ if (!this.getDataSourceById(item.id)) {
201
+ this.add(item);
202
+ }
203
+ });
204
+ }
156
205
  }
157
206
 
158
207
  export type DataSourceService = DataSource;
@@ -23,7 +23,7 @@ import { Writable } from 'type-fest';
23
23
  import { DepTargetType } from '@tmagic/dep';
24
24
  import type { Id, MApp, MComponent, MContainer, MNode, MPage, MPageFragment } from '@tmagic/schema';
25
25
  import { NodeType } from '@tmagic/schema';
26
- import { getNodePath, isNumber, isPage, isPageFragment, isPop } from '@tmagic/utils';
26
+ import { calcValueByFontsize, getNodePath, isNumber, isPage, isPageFragment, isPop } from '@tmagic/utils';
27
27
 
28
28
  import BaseService from '@editor/services//BaseService';
29
29
  import propsService from '@editor/services//props';
@@ -741,7 +741,7 @@ class Editor extends BaseService {
741
741
  const el = doc.getElementById(`${node.id}`);
742
742
  const parentEl = layout === Layout.FIXED ? doc.body : el?.offsetParent;
743
743
  if (parentEl && el) {
744
- node.style.left = (parentEl.clientWidth - el.clientWidth) / 2;
744
+ node.style.left = calcValueByFontsize(doc, (parentEl.clientWidth - el.clientWidth) / 2);
745
745
  node.style.right = '';
746
746
  }
747
747
  } else if (parent.style && isNumber(parent.style?.width) && isNumber(node.style?.width)) {
@@ -23,7 +23,7 @@ import { Writable } from 'type-fest';
23
23
  import { DepTargetType } from '@tmagic/dep';
24
24
  import type { FormConfig } from '@tmagic/form';
25
25
  import type { Id, MComponent, MNode } from '@tmagic/schema';
26
- import { getValueByKeyPath, guid, setValueByKeyPath, toLine } from '@tmagic/utils';
26
+ import { getNodePath, getValueByKeyPath, guid, setValueByKeyPath, toLine } from '@tmagic/utils';
27
27
 
28
28
  import depService from '@editor/services/dep';
29
29
  import editorService from '@editor/services/editor';
@@ -199,13 +199,14 @@ class Props extends BaseService {
199
199
  public replaceRelateId(originConfigs: MNode[], targetConfigs: MNode[]) {
200
200
  const relateIdMap = this.getRelateIdMap();
201
201
  if (Object.keys(relateIdMap).length === 0) return;
202
-
202
+ depService.clearByType(DepTargetType.RELATED_COMP_WHEN_COPY);
203
+ depService.collect(originConfigs, true, DepTargetType.RELATED_COMP_WHEN_COPY);
203
204
  const target = depService.getTarget(DepTargetType.RELATED_COMP_WHEN_COPY, DepTargetType.RELATED_COMP_WHEN_COPY);
204
205
  if (!target) return;
205
-
206
206
  originConfigs.forEach((config: MNode) => {
207
207
  const newId = relateIdMap[config.id];
208
- const targetConfig = targetConfigs.find((targetConfig) => targetConfig.id === newId);
208
+ const path = getNodePath(newId, targetConfigs);
209
+ const targetConfig = path[path.length - 1];
209
210
 
210
211
  if (!targetConfig) return;
211
212
 
@@ -213,9 +214,13 @@ class Props extends BaseService {
213
214
  const relateOriginId = getValueByKeyPath(fullKey, config);
214
215
  const relateTargetId = relateIdMap[relateOriginId];
215
216
  if (!relateTargetId) return;
216
-
217
217
  setValueByKeyPath(fullKey, relateTargetId, targetConfig);
218
218
  });
219
+
220
+ // 递归items
221
+ if (config.items && Array.isArray(config.items)) {
222
+ this.replaceRelateId(config.items, targetConfigs);
223
+ }
219
224
  });
220
225
  }
221
226
 
@@ -90,6 +90,7 @@ class StageOverlay extends BaseService {
90
90
  public createStage(stageOptions: StageOptions = {}) {
91
91
  return useStage({
92
92
  ...stageOptions,
93
+ zoom: 1,
93
94
  runtimeUrl: '',
94
95
  autoScrollIntoView: false,
95
96
  render: async (stage: StageCore) => {
package/src/type.ts CHANGED
@@ -150,6 +150,7 @@ export interface StageOptions {
150
150
  renderType?: RenderType;
151
151
  guidesOptions?: Partial<GuidesOptions>;
152
152
  disabledMultiSelect?: boolean;
153
+ zoom?: number;
153
154
  }
154
155
 
155
156
  export interface StoreState {
@@ -2,7 +2,7 @@ import { computed, markRaw, Ref } from 'vue';
2
2
  import { CopyDocument, Delete, DocumentCopy } from '@element-plus/icons-vue';
3
3
 
4
4
  import { Id, MContainer, NodeType } from '@tmagic/schema';
5
- import { isPage, isPageFragment } from '@tmagic/utils';
5
+ import { calcValueByFontsize, isPage, isPageFragment } from '@tmagic/utils';
6
6
 
7
7
  import ContentMenu from '@editor/components/ContentMenu.vue';
8
8
  import type { MenuButton, Services } from '@editor/type';
@@ -46,8 +46,12 @@ export const usePasteMenu = (menu?: Ref<InstanceType<typeof ContentMenu> | undef
46
46
  const stage = services?.editorService?.get('stage');
47
47
  const rect = menu.value.$el.getBoundingClientRect();
48
48
  const parentRect = stage?.container?.getBoundingClientRect();
49
- const initialLeft = (rect.left || 0) - (parentRect?.left || 0);
50
- const initialTop = (rect.top || 0) - (parentRect?.top || 0);
49
+ const initialLeft =
50
+ calcValueByFontsize(stage?.renderer.getDocument(), (rect.left || 0) - (parentRect?.left || 0)) /
51
+ services.uiService.get('zoom');
52
+ const initialTop =
53
+ calcValueByFontsize(stage?.renderer.getDocument(), (rect.top || 0) - (parentRect?.top || 0)) /
54
+ services.uiService.get('zoom');
51
55
  services?.editorService?.paste({ left: initialLeft, top: initialTop });
52
56
  } else {
53
57
  services?.editorService?.paste();