@tmagic/editor 1.8.0-beta.13 → 1.8.0-beta.15

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 (127) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +15 -3
  2. package/dist/es/components/ContentMenu.vue_vue_type_script_setup_true_lang.js +31 -8
  3. package/dist/es/components/FloatingBox.vue_vue_type_script_setup_true_lang.js +7 -2
  4. package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +15 -4
  5. package/dist/es/fields/CodeLink.vue_vue_type_script_setup_true_lang.js +4 -1
  6. package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +80 -55
  7. package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +5 -1
  8. package/dist/es/fields/CondOpSelect.vue_vue_type_script_setup_true_lang.js +4 -1
  9. package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +10 -7
  10. package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +4 -1
  11. package/dist/es/fields/DataSourceInput.vue_vue_type_script_setup_true_lang.js +4 -1
  12. package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +4 -1
  13. package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +4 -1
  14. package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +4 -1
  15. package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +4 -1
  16. package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +8 -2
  17. package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +99 -64
  18. package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +4 -1
  19. package/dist/es/fields/PageFragmentSelect.vue_vue_type_script_setup_true_lang.js +4 -1
  20. package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +5 -4
  21. package/dist/es/fields/StyleSetter/components/BackgroundPosition.vue_vue_type_script_setup_true_lang.js +4 -1
  22. package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +1 -1
  23. package/dist/es/fields/StyleSetter/icons/align-items/Center.js +31 -0
  24. package/dist/es/fields/StyleSetter/icons/align-items/FlexEnd.js +31 -0
  25. package/dist/es/fields/StyleSetter/icons/align-items/FlexStart.js +31 -0
  26. package/dist/es/fields/StyleSetter/icons/align-items/SpaceAround.js +50 -0
  27. package/dist/es/fields/StyleSetter/icons/align-items/SpaceBetween.js +50 -0
  28. package/dist/es/fields/StyleSetter/icons/background-repeat/NoRepeat.js +6 -6
  29. package/dist/es/fields/StyleSetter/icons/background-repeat/Repeat.js +5 -2
  30. package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatX.js +28 -5
  31. package/dist/es/fields/StyleSetter/icons/background-repeat/RepeatY.js +31 -5
  32. package/dist/es/fields/StyleSetter/icons/flex-direction/Column.js +10 -2
  33. package/dist/es/fields/StyleSetter/icons/flex-direction/ColumnReverse.js +10 -2
  34. package/dist/es/fields/StyleSetter/icons/flex-direction/Row.js +10 -2
  35. package/dist/es/fields/StyleSetter/icons/flex-direction/RowReverse.js +10 -2
  36. package/dist/es/fields/StyleSetter/icons/justify-content/Center.js +16 -2
  37. package/dist/es/fields/StyleSetter/icons/justify-content/FlexEnd.js +18 -2
  38. package/dist/es/fields/StyleSetter/icons/justify-content/FlexStart.js +18 -2
  39. package/dist/es/fields/StyleSetter/icons/justify-content/SpaceAround.js +36 -2
  40. package/dist/es/fields/StyleSetter/icons/justify-content/SpaceBetween.js +32 -2
  41. package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +7 -6
  42. package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +6 -3
  43. package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +47 -25
  44. package/dist/es/fields/UISelect.vue_vue_type_script_setup_true_lang.js +4 -1
  45. package/dist/es/icons/DatasourceIcon.js +17 -0
  46. package/dist/es/index.js +2 -2
  47. package/dist/es/layouts/Framework.vue_vue_type_script_setup_true_lang.js +14 -10
  48. package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +13 -4
  49. package/dist/es/layouts/sidebar/Sidebar.vue_vue_type_script_setup_true_lang.js +3 -3
  50. package/dist/es/plugin.js +2 -1
  51. package/dist/es/services/editor.js +86 -24
  52. package/dist/es/services/history.js +16 -0
  53. package/dist/es/services/ui.js +5 -3
  54. package/dist/es/style.css +307 -40
  55. package/dist/es/utils/config.js +4 -1
  56. package/dist/es/utils/const.js +2 -2
  57. package/dist/es/utils/data-source/index.js +4 -2
  58. package/dist/es/utils/props.js +43 -29
  59. package/dist/es/utils/type-match-rules.js +12 -5
  60. package/dist/style.css +307 -40
  61. package/dist/themes/magic-admin.css +3004 -0
  62. package/dist/tmagic-editor.umd.cjs +1161 -476
  63. package/package.json +7 -7
  64. package/src/Editor.vue +22 -1
  65. package/src/components/ContentMenu.vue +44 -8
  66. package/src/components/FloatingBox.vue +8 -2
  67. package/src/editorProps.ts +4 -0
  68. package/src/fields/Code.vue +10 -2
  69. package/src/fields/CodeSelect.vue +54 -43
  70. package/src/fields/CodeSelectCol.vue +1 -0
  71. package/src/fields/DataSourceFieldSelect/Index.vue +14 -5
  72. package/src/fields/DisplayConds.vue +4 -1
  73. package/src/fields/EventSelect.vue +56 -32
  74. package/src/fields/StyleSetter/Index.vue +1 -4
  75. package/src/fields/StyleSetter/components/Border.vue +1 -1
  76. package/src/fields/StyleSetter/icons/align-items/Center.vue +19 -0
  77. package/src/fields/StyleSetter/icons/align-items/FlexEnd.vue +19 -0
  78. package/src/fields/StyleSetter/icons/align-items/FlexStart.vue +19 -0
  79. package/src/fields/StyleSetter/icons/align-items/SpaceAround.vue +34 -0
  80. package/src/fields/StyleSetter/icons/align-items/SpaceBetween.vue +34 -0
  81. package/src/fields/StyleSetter/icons/align-items/index.ts +5 -0
  82. package/src/fields/StyleSetter/icons/background-repeat/NoRepeat.vue +20 -4
  83. package/src/fields/StyleSetter/icons/background-repeat/Repeat.vue +10 -28
  84. package/src/fields/StyleSetter/icons/background-repeat/RepeatX.vue +4 -5
  85. package/src/fields/StyleSetter/icons/background-repeat/RepeatY.vue +4 -5
  86. package/src/fields/StyleSetter/icons/flex-direction/Column.vue +4 -3
  87. package/src/fields/StyleSetter/icons/flex-direction/ColumnReverse.vue +4 -3
  88. package/src/fields/StyleSetter/icons/flex-direction/Row.vue +4 -3
  89. package/src/fields/StyleSetter/icons/flex-direction/RowReverse.vue +4 -3
  90. package/src/fields/StyleSetter/icons/justify-content/Center.vue +5 -2
  91. package/src/fields/StyleSetter/icons/justify-content/FlexEnd.vue +15 -2
  92. package/src/fields/StyleSetter/icons/justify-content/FlexStart.vue +15 -2
  93. package/src/fields/StyleSetter/icons/justify-content/SpaceAround.vue +28 -3
  94. package/src/fields/StyleSetter/icons/justify-content/SpaceBetween.vue +28 -2
  95. package/src/fields/StyleSetter/pro/Background.vue +7 -2
  96. package/src/fields/StyleSetter/pro/Font.vue +3 -3
  97. package/src/fields/StyleSetter/pro/Layout.vue +98 -28
  98. package/src/icons/DatasourceIcon.vue +7 -0
  99. package/src/index.ts +3 -0
  100. package/src/layouts/Framework.vue +7 -1
  101. package/src/layouts/props-panel/PropsPanel.vue +38 -12
  102. package/src/layouts/sidebar/Sidebar.vue +3 -3
  103. package/src/plugin.ts +3 -2
  104. package/src/services/editor.ts +76 -11
  105. package/src/services/history.ts +22 -1
  106. package/src/services/ui.ts +20 -5
  107. package/src/theme/code-block.scss +37 -0
  108. package/src/theme/common/var.scss +1 -1
  109. package/src/theme/component-list-panel.scss +2 -2
  110. package/src/theme/data-source-field.scss +4 -0
  111. package/src/theme/display-conds.scss +11 -0
  112. package/src/theme/event.scss +58 -4
  113. package/src/theme/framework.scss +4 -1
  114. package/src/theme/history-list-panel.scss +9 -9
  115. package/src/theme/nav-menu.scss +5 -1
  116. package/src/theme/props-panel.scss +2 -1
  117. package/src/theme/style-setter/border.scss +14 -5
  118. package/src/theme/theme.scss +1 -0
  119. package/src/theme/themes/magic-admin/index.scss +214 -0
  120. package/src/theme/ui-select.scss +1 -1
  121. package/src/type.ts +33 -6
  122. package/src/utils/config.ts +9 -1
  123. package/src/utils/const.ts +3 -2
  124. package/src/utils/data-source/index.ts +3 -3
  125. package/src/utils/props.ts +22 -9
  126. package/src/utils/type-match-rules.ts +11 -5
  127. package/types/index.d.ts +114 -35
@@ -26,11 +26,19 @@
26
26
  <script lang="ts" setup>
27
27
  import { markRaw } from 'vue';
28
28
 
29
+ import { useTheme } from '@tmagic/design';
29
30
  import type { ContainerChangeEventData } from '@tmagic/form';
30
31
  import { defineFormConfig, MContainer } from '@tmagic/form';
31
32
  import type { StyleSchema } from '@tmagic/schema';
32
33
 
33
34
  import Box from '../components/Box.vue';
35
+ import {
36
+ AlignItemsCenter,
37
+ AlignItemsFlexEnd,
38
+ AlignItemsFlexStart,
39
+ AlignItemsSpaceAround,
40
+ AlignItemsSpaceBetween,
41
+ } from '../icons/align-items';
34
42
  import { DisplayBlock, DisplayFlex, DisplayInline, DisplayInlineBlock, DisplayNone } from '../icons/display';
35
43
  import {
36
44
  FlexDirectionColumn,
@@ -46,12 +54,13 @@ import {
46
54
  JustifyContentSpaceBetween,
47
55
  } from '../icons/justify-content';
48
56
 
49
- defineProps<{
57
+ const props = defineProps<{
50
58
  values: Partial<StyleSchema>;
51
59
  lastValues?: Partial<StyleSchema>;
52
60
  isCompare?: boolean;
53
61
  disabled?: boolean;
54
62
  size?: 'large' | 'default' | 'small';
63
+ theme?: string;
55
64
  prop?: string;
56
65
  }>();
57
66
 
@@ -60,19 +69,42 @@ const emit = defineEmits<{
60
69
  addDiffCount: [];
61
70
  }>();
62
71
 
72
+ const displayTheme = useTheme(props);
73
+
63
74
  const formConfig = defineFormConfig([
64
75
  {
65
76
  name: 'display',
66
77
  text: '模式',
67
78
  type: 'radioGroup',
68
79
  childType: 'button',
69
- labelWidth: '68px',
80
+ labelWidth: '90px',
81
+ iconSize: '24px',
70
82
  options: [
71
- { value: 'inline', icon: markRaw(DisplayInline), tooltip: '内联布局 inline' },
72
- { value: 'flex', icon: markRaw(DisplayFlex), tooltip: '弹性布局 flex' },
73
- { value: 'block', icon: markRaw(DisplayBlock), tooltip: '块级布局 block' },
74
- { value: 'inline-block', icon: markRaw(DisplayInlineBlock), tooltip: '内联块布局 inline-block' },
75
- { value: 'none', icon: markRaw(DisplayNone), tooltip: '隐藏 none' },
83
+ {
84
+ value: 'inline',
85
+ icon: markRaw(DisplayInline),
86
+ tooltip: '内联布局 inline',
87
+ },
88
+ {
89
+ value: 'flex',
90
+ icon: markRaw(DisplayFlex),
91
+ tooltip: '弹性布局 flex',
92
+ },
93
+ {
94
+ value: 'block',
95
+ icon: markRaw(DisplayBlock),
96
+ tooltip: '块级布局 block',
97
+ },
98
+ {
99
+ value: 'inline-block',
100
+ icon: markRaw(DisplayInlineBlock),
101
+ tooltip: '内联块布局 inline-block',
102
+ },
103
+ {
104
+ value: 'none',
105
+ icon: markRaw(DisplayNone),
106
+ tooltip: '隐藏 none',
107
+ },
76
108
  ],
77
109
  },
78
110
  {
@@ -80,11 +112,20 @@ const formConfig = defineFormConfig([
80
112
  text: '主轴方向',
81
113
  type: 'radioGroup',
82
114
  childType: 'button',
83
- labelWidth: '68px',
115
+ labelWidth: '90px',
116
+ iconSize: '24px',
84
117
  options: [
85
118
  { value: 'row', icon: markRaw(FlexDirectionRow), tooltip: '水平方向 起点在左侧 row' },
86
- { value: 'row-reverse', icon: markRaw(FlexDirectionRowReverse), tooltip: '水平方向 起点在右侧 row-reverse' },
87
- { value: 'column', icon: markRaw(FlexDirectionColumn), tooltip: '垂直方向 起点在上沿 column' },
119
+ {
120
+ value: 'row-reverse',
121
+ icon: markRaw(FlexDirectionRowReverse),
122
+ tooltip: '水平方向 起点在右侧 row-reverse',
123
+ },
124
+ {
125
+ value: 'column',
126
+ icon: markRaw(FlexDirectionColumn),
127
+ tooltip: '垂直方向 起点在上沿 column',
128
+ },
88
129
  {
89
130
  value: 'column-reverse',
90
131
  icon: markRaw(FlexDirectionColumnReverse),
@@ -98,13 +139,22 @@ const formConfig = defineFormConfig([
98
139
  text: '主轴对齐',
99
140
  type: 'radioGroup',
100
141
  childType: 'button',
101
- labelWidth: '68px',
142
+ labelWidth: '90px',
143
+ iconSize: '24px',
102
144
  options: [
103
145
  { value: 'flex-start', icon: markRaw(JustifyContentFlexStart), tooltip: '左对齐 flex-start' },
104
146
  { value: 'flex-end', icon: markRaw(JustifyContentFlexEnd), tooltip: '右对齐 flex-end' },
105
147
  { value: 'center', icon: markRaw(JustifyContentCenter), tooltip: '居中 center' },
106
- { value: 'space-between', icon: markRaw(JustifyContentSpaceBetween), tooltip: '两端对齐 space-between' },
107
- { value: 'space-around', icon: markRaw(JustifyContentSpaceAround), tooltip: '横向平分 space-around' },
148
+ {
149
+ value: 'space-between',
150
+ icon: markRaw(JustifyContentSpaceBetween),
151
+ tooltip: '两端对齐 space-between',
152
+ },
153
+ {
154
+ value: 'space-around',
155
+ icon: markRaw(JustifyContentSpaceAround),
156
+ tooltip: '横向平分 space-around',
157
+ },
108
158
  ],
109
159
  display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
110
160
  },
@@ -113,13 +163,22 @@ const formConfig = defineFormConfig([
113
163
  text: '辅轴对齐',
114
164
  type: 'radioGroup',
115
165
  childType: 'button',
116
- labelWidth: '68px',
166
+ labelWidth: '90px',
167
+ iconSize: '24px',
117
168
  options: [
118
- { value: 'flex-start', icon: markRaw(JustifyContentFlexStart), tooltip: '左对齐 flex-start' },
119
- { value: 'flex-end', icon: markRaw(JustifyContentFlexEnd), tooltip: '右对齐 flex-end' },
120
- { value: 'center', icon: markRaw(JustifyContentCenter), tooltip: '居中 center' },
121
- { value: 'space-between', icon: markRaw(JustifyContentSpaceBetween), tooltip: '两端对齐 space-between' },
122
- { value: 'space-around', icon: markRaw(JustifyContentSpaceAround), tooltip: '横向平分 space-around' },
169
+ { value: 'flex-start', icon: markRaw(AlignItemsFlexStart), tooltip: '左对齐 flex-start' },
170
+ { value: 'flex-end', icon: markRaw(AlignItemsFlexEnd), tooltip: '右对齐 flex-end' },
171
+ { value: 'center', icon: markRaw(AlignItemsCenter), tooltip: '居中 center' },
172
+ {
173
+ value: 'space-between',
174
+ icon: markRaw(AlignItemsSpaceBetween),
175
+ tooltip: '两端对齐 space-between',
176
+ },
177
+ {
178
+ value: 'space-around',
179
+ icon: markRaw(AlignItemsSpaceAround),
180
+ tooltip: '横向平分 space-around',
181
+ },
123
182
  ],
124
183
  display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
125
184
  },
@@ -127,8 +186,9 @@ const formConfig = defineFormConfig([
127
186
  name: 'flexWrap',
128
187
  text: '换行',
129
188
  type: 'radioGroup',
130
- childType: 'button',
131
- labelWidth: '68px',
189
+ childType: displayTheme.value !== 'magic-admin' ? 'button' : 'default',
190
+ labelWidth: '90px',
191
+ iconSize: '24px',
132
192
  options: [
133
193
  { value: 'nowrap', text: '不换行', tooltip: '不换行 nowrap' },
134
194
  { value: 'wrap', text: '正换行', tooltip: '第一行在上方 wrap' },
@@ -141,17 +201,22 @@ const formConfig = defineFormConfig([
141
201
  items: [
142
202
  {
143
203
  name: 'width',
144
- text: '宽度',
145
- labelWidth: '68px',
204
+ text: '宽度(px)',
205
+ labelWidth: '90px',
146
206
  type: 'data-source-field-select',
147
207
  fieldConfig: {
148
208
  type: 'text',
149
209
  },
150
210
  },
211
+ ],
212
+ },
213
+ {
214
+ type: 'row',
215
+ items: [
151
216
  {
152
217
  name: 'height',
153
- text: '高度',
154
- labelWidth: '68px',
218
+ text: '高度(px)',
219
+ labelWidth: '90px',
155
220
  type: 'data-source-field-select',
156
221
  fieldConfig: {
157
222
  type: 'text',
@@ -166,7 +231,7 @@ const formConfig = defineFormConfig([
166
231
  type: 'data-source-field-select',
167
232
  text: 'overflow',
168
233
  name: 'overflow',
169
- labelWidth: '68px',
234
+ labelWidth: '90px',
170
235
  checkStrictly: false,
171
236
  dataSourceFieldType: ['string'],
172
237
  fieldConfig: {
@@ -184,11 +249,16 @@ const formConfig = defineFormConfig([
184
249
  ],
185
250
  },
186
251
  },
252
+ ],
253
+ },
254
+ {
255
+ type: 'row',
256
+ items: [
187
257
  {
188
258
  type: 'data-source-field-select',
189
- text: '透明度',
259
+ text: '透明度(%)',
190
260
  name: 'opacity',
191
- labelWidth: '68px',
261
+ labelWidth: '90px',
192
262
  dataSourceFieldType: ['string', 'number'],
193
263
  fieldConfig: {
194
264
  type: 'text',
@@ -0,0 +1,7 @@
1
+ <template>
2
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
3
+ <path
4
+ d="M6.25 9.723C4.392 9.6095 2.804 9.137 1.875 8.4775V9.7495C1.875 10.6165 3.7665 11.3365 6.25 11.4745V12.348C4.392 12.2345 2.804 11.763 1.875 11.102V12.3745H1V4.0625C1 2.371 3.742 1 7.125 1C10.507 1 13.249 2.371 13.249 4.0625V6.8255H12.374V5.6405C11.3025 6.5295 9.352 7.1245 7.1245 7.1245C4.897 7.1245 2.947 6.5295 1.8745 5.6405V7.1245C1.8745 7.9915 3.7665 8.712 6.2495 8.85L6.25 9.723ZM7.125 14.999C3.742 14.999 1 13.8245 1 12.3745H1.875C1.875 13.3415 4.225 14.124 7.125 14.124V14.999ZM7.125 6.2495C10.024 6.2495 12.3745 5.2695 12.3745 4.0625C12.3745 2.855 10.0245 1.875 7.1245 1.875C4.225 1.875 1.8745 2.855 1.8745 4.0625C1.8745 5.2695 4.2255 6.2495 7.125 6.2495ZM14.7385 11.9895L14.1295 11.6345C14.1635 11.4485 14.1855 11.2585 14.1855 11.062C14.1843 10.8697 14.1655 10.6779 14.1295 10.489L14.7385 10.134C14.988 9.987 15.073 9.667 14.929 9.416L14.409 8.507C14.3752 8.44726 14.3299 8.39483 14.2757 8.35275C14.2215 8.31066 14.1595 8.27977 14.0933 8.26186C14.027 8.24395 13.9579 8.23938 13.8898 8.24842C13.8218 8.25745 13.7563 8.27992 13.697 8.3145L13.081 8.6735C12.7931 8.42269 12.4616 8.22694 12.103 8.096V7.6495C12.103 7.36 11.8705 7.1245 11.5825 7.1245H10.5415C10.4728 7.12476 10.4049 7.13855 10.3415 7.16507C10.2782 7.1916 10.2206 7.23034 10.1723 7.2791C10.1239 7.32785 10.0856 7.38565 10.0595 7.4492C10.0335 7.51276 10.0202 7.58082 10.0205 7.6495V8.096C9.66186 8.22694 9.33035 8.42269 9.0425 8.6735L8.4265 8.3145C8.36725 8.27999 8.30174 8.2576 8.23377 8.2486C8.1658 8.23961 8.09672 8.24419 8.03053 8.2621C7.96435 8.28001 7.90238 8.31088 7.84821 8.35292C7.79405 8.39495 7.74877 8.44733 7.715 8.507L7.195 9.417C7.12576 9.53751 7.10688 9.68047 7.14246 9.81482C7.17804 9.94917 7.2652 10.0641 7.385 10.1345L7.9945 10.489C7.9585 10.6775 7.9395 10.8695 7.9385 11.062C7.9385 11.2585 7.96 11.4485 7.9945 11.635L7.3855 11.99C7.1355 12.1365 7.0505 12.4565 7.1945 12.7075L7.715 13.6165C7.74877 13.6762 7.79405 13.7285 7.84821 13.7706C7.90238 13.8126 7.96435 13.8435 8.03053 13.8614C8.09672 13.8793 8.1658 13.8839 8.23377 13.8749C8.30174 13.8659 8.36725 13.8435 8.4265 13.809L9.0425 13.4505C9.33023 13.7015 9.66178 13.8972 10.0205 14.028V14.474C10.0205 14.764 10.2535 14.999 10.5415 14.999H11.5825C11.7211 14.9983 11.8538 14.9427 11.9514 14.8442C12.049 14.7458 12.1035 14.6126 12.103 14.474V14.028C12.4616 13.8971 12.7931 13.7013 13.081 13.4505L13.697 13.809C13.7563 13.8434 13.8218 13.8657 13.8897 13.8746C13.9577 13.8835 14.0267 13.8789 14.0929 13.861C14.159 13.8431 14.221 13.8123 14.2751 13.7703C14.3293 13.7283 14.3746 13.6761 14.4085 13.6165L14.929 12.7065C14.998 12.586 15.0168 12.4431 14.9811 12.3089C14.9454 12.1746 14.8583 12.0599 14.7385 11.9895ZM14.092 12.5065L13.852 12.915C13.8193 12.9693 13.7667 13.0086 13.7054 13.0246C13.6441 13.0406 13.579 13.0319 13.524 13.0005L12.8575 12.623C12.5109 13.0069 12.0497 13.2688 11.5425 13.37V13.889C11.5425 14.0185 11.4345 14.124 11.3015 14.124H10.822C10.7907 14.1243 10.7597 14.1184 10.7308 14.1067C10.7018 14.0949 10.6754 14.0776 10.6531 14.0557C10.6308 14.0338 10.6131 14.0077 10.6009 13.9789C10.5887 13.9502 10.5823 13.9193 10.582 13.888V13.37C10.0746 13.2689 9.61328 13.007 9.2665 12.623L8.6 13C8.54495 13.0316 8.47971 13.0403 8.4183 13.0243C8.35688 13.0083 8.30418 12.9689 8.2715 12.9145L8.0315 12.506C8.01582 12.4792 8.00564 12.4496 8.00158 12.4189C7.99752 12.3881 7.99965 12.3569 8.00785 12.327C8.01605 12.297 8.03016 12.2691 8.04933 12.2447C8.0685 12.2203 8.09236 12.2 8.1195 12.185L8.789 11.805C8.70604 11.5658 8.66313 11.3146 8.662 11.0615C8.662 10.8005 8.709 10.552 8.7895 10.3175L8.1195 9.9375C8.09247 9.92244 8.06872 9.90213 8.04964 9.87777C8.03056 9.85341 8.01654 9.82549 8.00839 9.79564C8.00024 9.76579 7.99813 9.73461 8.00218 9.70394C8.00624 9.67326 8.01637 9.64371 8.032 9.617L8.272 9.208C8.30467 9.15373 8.35729 9.1144 8.41858 9.09843C8.47988 9.08245 8.54499 9.09109 8.6 9.1225L9.2665 9.5005C9.61315 9.11663 10.0743 8.85468 10.5815 8.7535V8.235C10.5815 8.105 10.689 8 10.822 8H11.302C11.4345 8 11.542 8.105 11.542 8.236V8.754C12.0492 8.85518 12.5104 9.11713 12.857 9.501L13.524 9.123C13.579 9.09159 13.6441 9.08295 13.7054 9.09893C13.7667 9.1149 13.8193 9.15423 13.852 9.2085L14.092 9.6175C14.1077 9.64423 14.1179 9.67383 14.122 9.70455C14.1261 9.73528 14.124 9.76652 14.1159 9.79643C14.1077 9.82634 14.0937 9.85431 14.0745 9.87871C14.0554 9.90311 14.0316 9.92344 14.0045 9.9385L13.335 10.3185C13.4135 10.5525 13.462 10.801 13.462 11.062C13.462 11.3225 13.4145 11.571 13.335 11.8055L14.0045 12.1855C14.0316 12.2006 14.0554 12.2209 14.0745 12.2453C14.0937 12.2697 14.1077 12.2977 14.1159 12.3276C14.124 12.3575 14.1261 12.3887 14.122 12.4194C14.1179 12.4502 14.1077 12.4798 14.092 12.5065ZM11.062 9.7495C10.7139 9.7495 10.3801 9.88778 10.1339 10.1339C9.88778 10.3801 9.7495 10.7139 9.7495 11.062C9.7495 11.4101 9.88778 11.7439 10.1339 11.9901C10.3801 12.2362 10.7139 12.3745 11.062 12.3745C11.4101 12.3745 11.7439 12.2362 11.9901 11.9901C12.2362 11.7439 12.3745 11.4101 12.3745 11.062C12.3745 10.7139 12.2362 10.3801 11.9901 10.1339C11.7439 9.88778 11.4101 9.7495 11.062 9.7495ZM11.062 11.4995C10.946 11.4995 10.8347 11.4534 10.7526 11.3714C10.6706 11.2893 10.6245 11.178 10.6245 11.062C10.6245 10.946 10.6706 10.8347 10.7526 10.7526C10.8347 10.6706 10.946 10.6245 11.062 10.6245C11.178 10.6245 11.2893 10.6706 11.3714 10.7526C11.4534 10.8347 11.4995 10.946 11.4995 11.062C11.4995 11.178 11.4534 11.2893 11.3714 11.3714C11.2893 11.4534 11.178 11.4995 11.062 11.4995Z"
5
+ />
6
+ </svg>
7
+ </template>
package/src/index.ts CHANGED
@@ -32,6 +32,9 @@ export { DepTargetType } from '@tmagic/core';
32
32
  export * from './type';
33
33
  export * from './hooks';
34
34
  export * from './utils';
35
+ // `isGlobalFlat` 同时在 `@tmagic/design` 和 `./utils/config` 中存在,
36
+ // 通过显式重导一次消除 `export *` 带来的歧义,并固定使用 editor 自己那份。
37
+ export { isGlobalFlat } from './utils/config';
35
38
  export { default as TMagicEditor } from './Editor.vue';
36
39
  export { default as TMagicCodeEditor } from './layouts/CodeEditor.vue';
37
40
  export { default as editorService } from './services/editor';
@@ -1,5 +1,9 @@
1
1
  <template>
2
- <div class="m-editor" ref="content" style="min-width: 900px">
2
+ <div
3
+ ref="content"
4
+ :class="['m-editor', theme ? `m-editor--${theme}` : '', theme ? `m-theme--${theme}` : '']"
5
+ style="min-width: 900px"
6
+ >
3
7
  <slot name="header"></slot>
4
8
 
5
9
  <slot name="nav"></slot>
@@ -97,6 +101,8 @@ const props = defineProps<{
97
101
  pageFilterFunction?: (_page: MPage | MPageFragment, _keyword: string) => boolean;
98
102
  /** 是否隐藏左侧面板 */
99
103
  hideSidebar?: boolean;
104
+ /** 主题名称,会在根节点追加 `m-editor--<theme>` 修饰类 */
105
+ theme?: string;
100
106
  }>();
101
107
 
102
108
  const codeOptions = inject('codeOptions', {});
@@ -111,14 +111,31 @@ const styleFormConfig = [
111
111
  },
112
112
  ];
113
113
 
114
+ // 用单调递增序号标记每次 init 调用,只让"最新一次"的 await 结果落到 ref 上。
115
+ // 避免节点快速切换时多个 init 并发 + 解析顺序错乱导致 stale config 覆盖最新选中节点,
116
+ // 以及组件已卸载 / FormPanel 正在 remount 的中间态下写 ref 触发 Vue setRef 把 __vnode
117
+ // 设到 null 上的报错(TypeError: Cannot set properties of null (setting '__vnode'))。
118
+ let initSeq = 0;
119
+ let mounted = true;
120
+
114
121
  const init = async () => {
122
+ initSeq += 1;
123
+ const seq = initSeq;
124
+
115
125
  if (!node.value) {
126
+ if (seq !== initSeq || !mounted) return;
116
127
  curFormConfig.value = [];
117
128
  return;
118
129
  }
119
130
 
120
131
  const type = node.value.type || (node.value.items ? 'container' : 'text');
121
- curFormConfig.value = await propsService.getPropsConfig(type, { node: node.value });
132
+ const config = await propsService.getPropsConfig(type, { node: node.value });
133
+
134
+ // 期间被新一次 init 取代 / 组件已卸载,丢弃本次结果
135
+ if (seq !== initSeq || !mounted) return;
136
+ if (!node.value) return;
137
+
138
+ curFormConfig.value = config;
122
139
  values.value = node.value;
123
140
  };
124
141
 
@@ -126,6 +143,7 @@ watchEffect(init);
126
143
  propsService.on('props-configs-change', init);
127
144
 
128
145
  onBeforeUnmount(() => {
146
+ mounted = false;
129
147
  propsService.off('props-configs-change', init);
130
148
  });
131
149
 
@@ -146,17 +164,25 @@ const submit = async (
146
164
  };
147
165
 
148
166
  if (v.style) {
149
- Object.entries(v.style).forEach(([key, value]) => {
150
- if (value !== '' && newValue.style) {
151
- newValue.style[key] = value;
152
- }
153
- });
154
-
155
- eventData?.changeRecords?.forEach((record) => {
156
- if (record.propPath?.startsWith('style') && record.value === '') {
157
- setValueByKeyPath(record.propPath, record.value, newValue);
158
- }
159
- });
167
+ // 空字符串样式值表示「清除该样式」,需保留才能在 doUpdate 的 mergeWith 中覆盖旧值。
168
+ if (eventData) {
169
+ // 表单编辑:先过滤掉空字符串(避免表单默认空值污染 DSL),
170
+ // 再按 changeRecords 恢复被主动清空的字段。
171
+ Object.entries(v.style).forEach(([key, value]) => {
172
+ if (value !== '' && newValue.style) {
173
+ newValue.style[key] = value;
174
+ }
175
+ });
176
+
177
+ eventData.changeRecords?.forEach((record) => {
178
+ if (record.propPath?.startsWith('style') && record.value === '') {
179
+ setValueByKeyPath(record.propPath, record.value, newValue);
180
+ }
181
+ });
182
+ } else {
183
+ // 源码编辑器保存(无 eventData):style 原样保留,其中的空字符串视为用户主动清除该样式。
184
+ newValue.style = { ...v.style };
185
+ }
160
186
  }
161
187
 
162
188
  // 区分操作途径:表单字段编辑(MForm @change)会带上 eventData(含 changeRecords);
@@ -155,7 +155,7 @@
155
155
 
156
156
  <script setup lang="ts">
157
157
  import { computed, nextTick, ref, watch } from 'vue';
158
- import { Close, Coin, EditPen, Goods, List } from '@element-plus/icons-vue';
158
+ import { Box, Close, Coin, EditPen, Finished } from '@element-plus/icons-vue';
159
159
 
160
160
  import FloatingBox from '@editor/components/FloatingBox.vue';
161
161
  import MIcon from '@editor/components/Icon.vue';
@@ -253,7 +253,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
253
253
  [SideItemKey.COMPONENT_LIST]: {
254
254
  $key: SideItemKey.COMPONENT_LIST,
255
255
  type: 'component',
256
- icon: Goods,
256
+ icon: Box,
257
257
  text: '组件',
258
258
  component: ComponentListPanel,
259
259
  slots: {},
@@ -261,7 +261,7 @@ const getItemConfig = (data: SideItem): SideComponent => {
261
261
  layer: {
262
262
  $key: SideItemKey.LAYER,
263
263
  type: 'component',
264
- icon: List,
264
+ icon: Finished,
265
265
  text: '已选组件',
266
266
  props: {
267
267
  layerContentMenu: props.layerContentMenu,
package/src/plugin.ts CHANGED
@@ -16,7 +16,7 @@
16
16
  * limitations under the License.
17
17
  */
18
18
 
19
- import type { App } from 'vue';
19
+ import { type App } from 'vue';
20
20
 
21
21
  import type { DesignPluginOptions } from '@tmagic/design';
22
22
  import designPlugin from '@tmagic/design';
@@ -56,6 +56,8 @@ const defaultInstallOpt: EditorInstallOptions = {
56
56
  customCreateMonacoEditor: (monaco, codeEditorEl, options) => monaco.editor.create(codeEditorEl, options),
57
57
  customCreateMonacoDiffEditor: (monaco, codeEditorEl, options) =>
58
58
  monaco.editor.createDiffEditor(codeEditorEl, options),
59
+
60
+ flat: false,
59
61
  };
60
62
 
61
63
  export default {
@@ -69,7 +71,6 @@ export default {
69
71
 
70
72
  app.config.globalProperties.$TMAGIC_EDITOR = option;
71
73
  setEditorConfig(option);
72
-
73
74
  app.component(`${Editor.name || 'MEditor'}`, Editor);
74
75
  app.component('magic-code-editor', CodeEditor);
75
76
  app.component('m-fields-ui-select', uiSelect);
@@ -550,8 +550,16 @@ class Editor extends BaseService {
550
550
  this.emit('change', {
551
551
  type: 'add',
552
552
  data: newNodes.map((node) => ({ node, page: this.getPageOfNode(node.id) })),
553
+ historySource,
554
+ doNotPushHistory,
553
555
  });
554
556
 
557
+ // 页面 / 页面片新增不入历史栈(见上方 isPage / isPageFragment 分支),这里合并补发一次结构变更通知
558
+ const addedPages = newNodes.filter((node) => isPage(node) || isPageFragment(node)) as (MPage | MPageFragment)[];
559
+ if (addedPages.length) {
560
+ historyService.notifyPageStructureChange({ add: addedPages, remove: [] });
561
+ }
562
+
555
563
  return Array.isArray(addNode) ? newNodes : newNodes[0];
556
564
  }
557
565
 
@@ -692,7 +700,13 @@ class Editor extends BaseService {
692
700
  }
693
701
 
694
702
  this.emit('remove', nodes);
695
- this.emit('change', { type: 'remove', data: changeItems });
703
+ this.emit('change', { type: 'remove', data: changeItems, historySource, doNotPushHistory });
704
+
705
+ // 页面 / 页面片删除不入历史栈(见上方 isPage / isPageFragment 分支),这里合并补发一次结构变更通知
706
+ const removedPages = nodes.filter((node) => isPage(node) || isPageFragment(node)) as (MPage | MPageFragment)[];
707
+ if (removedPages.length) {
708
+ historyService.notifyPageStructureChange({ add: [], remove: removedPages });
709
+ }
696
710
  }
697
711
 
698
712
  public async doUpdate(
@@ -846,6 +860,8 @@ class Editor extends BaseService {
846
860
  this.emit('change', {
847
861
  type: 'update',
848
862
  data: updateData.map((node) => ({ node, page: this.getPageOfNode(node.newNode.id) })),
863
+ historySource,
864
+ doNotPushHistory,
849
865
  });
850
866
  return Array.isArray(config) ? updateData.map((item) => item.newNode) : updateData[0].newNode;
851
867
  }
@@ -1092,6 +1108,8 @@ class Editor extends BaseService {
1092
1108
  type: 'move-layer',
1093
1109
  data: [{ node, page: this.getPageOfNode(node.id) }],
1094
1110
  offset,
1111
+ historySource,
1112
+ doNotPushHistory,
1095
1113
  });
1096
1114
  }
1097
1115
 
@@ -1277,6 +1295,8 @@ class Editor extends BaseService {
1277
1295
  data: configs.map((node) => ({ node, page: this.getPageOfNode(node.id) })),
1278
1296
  targetIndex,
1279
1297
  targetParent,
1298
+ historySource,
1299
+ doNotPushHistory,
1280
1300
  });
1281
1301
  }
1282
1302
 
@@ -1768,6 +1788,10 @@ class Editor extends BaseService {
1768
1788
  const nextMap = new Map(nextPages.map((p) => [`${p.id}`, p]));
1769
1789
  const indexInItems = (root: MApp, id: Id) => (root.items ?? []).findIndex((item) => `${item.id}` === `${id}`);
1770
1790
 
1791
+ // 收集本次整体替换中增删的页面,循环结束后合并为一次结构变更通知(避免逐页派发多个事件)
1792
+ const addedPages: (MPage | MPageFragment)[] = [];
1793
+ const removedPages: (MPage | MPageFragment)[] = [];
1794
+
1771
1795
  nextPages.forEach((nextPage) => {
1772
1796
  const prevPage = prevMap.get(`${nextPage.id}`);
1773
1797
  if (!prevPage) {
@@ -1777,6 +1801,7 @@ class Editor extends BaseService {
1777
1801
  { newSchema: cloneDeep(toRaw(nextPage)), parentId: nextRoot.id, index: indexInItems(nextRoot, nextPage.id) },
1778
1802
  source,
1779
1803
  );
1804
+ addedPages.push(nextPage);
1780
1805
  } else if (!isEqual(toRaw(prevPage), toRaw(nextPage))) {
1781
1806
  this.pushPageDiffStep(
1782
1807
  'update',
@@ -1795,8 +1820,13 @@ class Editor extends BaseService {
1795
1820
  { oldSchema: cloneDeep(toRaw(prevPage)), parentId: preRoot.id, index: indexInItems(preRoot, prevPage.id) },
1796
1821
  source,
1797
1822
  );
1823
+ removedPages.push(prevPage);
1798
1824
  }
1799
1825
  });
1826
+
1827
+ if (addedPages.length || removedPages.length) {
1828
+ historyService.notifyPageStructureChange({ add: addedPages, remove: removedPages });
1829
+ }
1800
1830
  }
1801
1831
 
1802
1832
  /**
@@ -1878,10 +1908,12 @@ class Editor extends BaseService {
1878
1908
  /**
1879
1909
  * 应用历史操作(撤销 / 重做)
1880
1910
  *
1881
- * 所有 DSL 修改都走 `editor.add / remove / update`,并通过 `doNotPushHistory` 阻止再次入栈、
1882
- * `doNotSelect / doNotSwitchPage` 让选区由方法末尾的统一逻辑兜底。
1911
+ * 删除 / 更新类修改走 `editor.remove / update`,并通过 `doNotPushHistory` 阻止再次入栈、
1912
+ * `doNotSelect / doNotSwitchPage` 让选区由方法末尾的统一逻辑兜底;
1913
+ * 「重新插回节点」(撤销 remove / 重做 add)需按 step 记录的 parentId / index 精确还原,
1914
+ * 不走 this.add,由手工 splice + stage.add 完成后补发等价的 add / change 事件(见 emitHistoryInsertEvents)。
1883
1915
  *
1884
- * 注意:这些公开方法会发出 add / remove / update 事件,业务侧若需要区分"用户操作"与"撤销重做触发",
1916
+ * 注意:这些路径都会发出 add / remove / update / change 事件,业务侧若需要区分"用户操作"与"撤销重做触发",
1885
1917
  * 请监听 `history-change` 事件配合判断。
1886
1918
  *
1887
1919
  * @param step 操作记录
@@ -1894,7 +1926,14 @@ class Editor extends BaseService {
1894
1926
  const stage = this.get('stage');
1895
1927
  if (!root) return;
1896
1928
 
1897
- const commonOpts = { doNotSelect: true, doNotSwitchPage: true, doNotPushHistory: true } as const;
1929
+ // 撤销/重做内部复用 add/remove/update,透传被应用 step 上记录的 source,
1930
+ // 使其触发的 change 事件同样携带「历史来源」
1931
+ const commonOpts = {
1932
+ doNotSelect: true,
1933
+ doNotSwitchPage: true,
1934
+ doNotPushHistory: true,
1935
+ historySource: step.source,
1936
+ } as const;
1898
1937
 
1899
1938
  switch (step.opType) {
1900
1939
  case 'add': {
@@ -1910,17 +1949,22 @@ class Editor extends BaseService {
1910
1949
  }
1911
1950
  } else {
1912
1951
  // 重做 add:按记录的 parentId / index 把节点重新插回父容器。
1913
- // 按目标 index 升序逐个插入,先小后大避免索引漂移
1952
+ // 按目标 index 升序逐个插入,先小后大避免索引漂移。
1953
+ // 不走 this.add:doAdd 依赖当前选中节点(无选区时直接抛错),且只能插到选中节点之后 / 末尾,
1954
+ // 无法按 step 记录的 parentId / index 精确还原,还会重算 style 破坏已记录的终态位置。
1914
1955
  const sorted = [...items].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
1956
+ const addedNodes: MNode[] = [];
1915
1957
  for (const { newSchema, parentId, index } of sorted) {
1916
1958
  if (!newSchema || parentId === undefined) continue;
1917
1959
  const parent = this.getNodeById(parentId, false) as MContainer | null;
1918
1960
  if (parent?.items) {
1961
+ const addedNode = cloneDeep(newSchema);
1919
1962
  if (typeof index === 'number' && index >= 0 && index < parent.items.length) {
1920
- parent.items.splice(index, 0, cloneDeep(newSchema));
1963
+ parent.items.splice(index, 0, addedNode);
1921
1964
  } else {
1922
- parent.items.push(cloneDeep(newSchema));
1965
+ parent.items.push(addedNode);
1923
1966
  }
1967
+ addedNodes.push(addedNode);
1924
1968
  await stage?.add({
1925
1969
  config: cloneDeep(newSchema),
1926
1970
  parent: cloneDeep(parent),
@@ -1929,19 +1973,23 @@ class Editor extends BaseService {
1929
1973
  });
1930
1974
  }
1931
1975
  }
1976
+ this.emitHistoryInsertEvents(addedNodes, step.source);
1932
1977
  }
1933
1978
  break;
1934
1979
  }
1935
1980
  case 'remove': {
1936
1981
  const items = step.diff ?? [];
1937
1982
  if (reverse) {
1938
- // 撤销 remove:按原 index 升序逐个插回(先小后大避免索引漂移)
1983
+ // 撤销 remove:按原 index 升序逐个插回(先小后大避免索引漂移);不走 this.add 的原因同上
1939
1984
  const sorted = [...items].sort((a, b) => (a.index ?? 0) - (b.index ?? 0));
1985
+ const addedNodes: MNode[] = [];
1940
1986
  for (const { oldSchema, parentId, index } of sorted) {
1941
1987
  if (!oldSchema || parentId === undefined) continue;
1942
1988
  const parent = this.getNodeById(parentId, false) as MContainer | null;
1943
1989
  if (parent?.items) {
1944
- parent.items.splice(index ?? parent.items.length, 0, cloneDeep(oldSchema));
1990
+ const addedNode = cloneDeep(oldSchema);
1991
+ parent.items.splice(index ?? parent.items.length, 0, addedNode);
1992
+ addedNodes.push(addedNode);
1945
1993
  await stage?.add({
1946
1994
  config: cloneDeep(oldSchema),
1947
1995
  parent: cloneDeep(parent),
@@ -1950,6 +1998,7 @@ class Editor extends BaseService {
1950
1998
  });
1951
1999
  }
1952
2000
  }
2001
+ this.emitHistoryInsertEvents(addedNodes, step.source);
1953
2002
  } else {
1954
2003
  // 重做 remove:再删一次
1955
2004
  for (const { oldSchema } of items) {
@@ -1989,7 +2038,7 @@ class Editor extends BaseService {
1989
2038
  return cloneDeep(reverse ? oldNode : newNode);
1990
2039
  });
1991
2040
  if (configs.length) {
1992
- await this.update(configs, { doNotPushHistory: true });
2041
+ await this.update(configs, { doNotPushHistory: true, historySource: step.source });
1993
2042
  }
1994
2043
  break;
1995
2044
  }
@@ -2028,6 +2077,22 @@ class Editor extends BaseService {
2028
2077
  private selectedConfigExceptionHandler(config: MNode | Id): EditorNodeInfo {
2029
2078
  return resolveSelectedNode(config, (id) => this.getNodeInfo(id), this.state.root?.id);
2030
2079
  }
2080
+
2081
+ /**
2082
+ * 撤销 remove / 重做 add 通过手工 splice 按 step 记录的 parentId / index 精确还原节点(不走 this.add,原因见调用处注释),
2083
+ * 这里补齐与 this.add 等价的 add / change 事件,保证撤销/重做路径的事件行为与正向操作一致。
2084
+ */
2085
+ private emitHistoryInsertEvents(nodes: MNode[], source?: HistoryOpSource) {
2086
+ if (!nodes.length) return;
2087
+ this.emit('add', nodes);
2088
+ this.emit('change', {
2089
+ type: 'add',
2090
+ data: nodes.map((node) => ({ node, page: this.getPageOfNode(node.id) })),
2091
+ historySource: source,
2092
+ // 撤销/重做本身不再写入历史记录
2093
+ doNotPushHistory: true,
2094
+ });
2095
+ }
2031
2096
  }
2032
2097
 
2033
2098
  export type EditorService = Editor;
@@ -19,7 +19,7 @@
19
19
  import { reactive } from 'vue';
20
20
  import type { Writable } from 'type-fest';
21
21
 
22
- import type { Id } from '@tmagic/core';
22
+ import type { Id, MPage, MPageFragment } from '@tmagic/core';
23
23
  import { guid } from '@tmagic/utils';
24
24
 
25
25
  import type {
@@ -353,6 +353,27 @@ class History extends BaseService {
353
353
  this.emit('mark-saved', { kind: stepType, id });
354
354
  }
355
355
 
356
+ /**
357
+ * 派发「页面 / 页面片结构变更」事件(`page-structure-change`)。
358
+ *
359
+ * 常规 `editorService.add` / `remove` 页面节点不会写入 `page` 历史栈(见 editor.add / remove 中
360
+ * 对 isPage / isPageFragment 的分支),因此不会产生任何 historyService 事件。该方法用于在这些
361
+ * 场景(以及 setRoot 整体替换 DSL 增删页面)下,向外统一通知页面结构的增删变化,供业务方感知。
362
+ *
363
+ * 一次操作涉及多个页面时,调用方应把本次增删的页面**合并为一个 change 一次性传入**,
364
+ * 使一次操作只派发一个事件;`add` / `remove` 分别为本次新增与删除的页面列表(其一可为空数组)。
365
+ *
366
+ * @param change 本次结构变更:{ add: 新增的页面列表, remove: 删除的页面列表 }
367
+ */
368
+ public notifyPageStructureChange(change: {
369
+ add: (MPage | MPageFragment)[];
370
+ remove: (MPage | MPageFragment)[];
371
+ }): void {
372
+ // 增删均为空时不派发,避免无意义通知
373
+ if (!change.add.length && !change.remove.length) return;
374
+ this.emit('page-structure-change', change);
375
+ }
376
+
356
377
  /**
357
378
  * 把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)序列化后写入本地 IndexedDB。
358
379
  *