@tmagic/editor 1.4.8 → 1.4.10

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 (166) hide show
  1. package/dist/style.css +26 -0
  2. package/dist/tmagic-editor.js +3213 -2820
  3. package/dist/tmagic-editor.umd.cjs +3237 -2839
  4. package/package.json +14 -12
  5. package/src/Editor.vue +212 -0
  6. package/src/components/CodeBlockEditor.vue +268 -0
  7. package/src/components/CodeParams.vue +60 -0
  8. package/src/components/ContentMenu.vue +193 -0
  9. package/src/components/FloatingBox.vue +178 -0
  10. package/src/components/Icon.vue +27 -0
  11. package/src/components/Resizer.vue +23 -0
  12. package/src/components/ScrollBar.vue +151 -0
  13. package/src/components/ScrollViewer.vue +128 -0
  14. package/src/components/SearchInput.vue +37 -0
  15. package/src/components/SplitView.vue +186 -0
  16. package/src/components/ToolButton.vue +138 -0
  17. package/src/components/Tree.vue +71 -0
  18. package/src/components/TreeNode.vue +150 -0
  19. package/src/editorProps.ts +120 -0
  20. package/src/fields/Code.vue +50 -0
  21. package/src/fields/CodeLink.vue +80 -0
  22. package/src/fields/CodeSelect.vue +132 -0
  23. package/src/fields/CodeSelectCol.vue +139 -0
  24. package/src/fields/CondOpSelect.vue +87 -0
  25. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +152 -0
  26. package/src/fields/DataSourceFieldSelect/Index.vue +168 -0
  27. package/src/fields/DataSourceFields.vue +329 -0
  28. package/src/fields/DataSourceInput.vue +339 -0
  29. package/src/fields/DataSourceMethodSelect.vue +153 -0
  30. package/src/fields/DataSourceMethods.vue +104 -0
  31. package/src/fields/DataSourceMocks.vue +255 -0
  32. package/src/fields/DataSourceSelect.vue +98 -0
  33. package/src/fields/DisplayConds.vue +145 -0
  34. package/src/fields/EventSelect.vue +375 -0
  35. package/src/fields/KeyValue.vue +137 -0
  36. package/src/fields/PageFragmentSelect.vue +63 -0
  37. package/src/fields/UISelect.vue +135 -0
  38. package/src/hooks/index.ts +24 -0
  39. package/src/hooks/use-code-block-edit.ts +83 -0
  40. package/src/hooks/use-data-source-edit.ts +46 -0
  41. package/src/hooks/use-data-source-method.ts +100 -0
  42. package/src/hooks/use-editor-content-height.ts +26 -0
  43. package/src/hooks/use-filter.ts +55 -0
  44. package/src/hooks/use-float-box.ts +76 -0
  45. package/src/hooks/use-getso.ts +35 -0
  46. package/src/hooks/use-next-float-box-position.ts +29 -0
  47. package/src/hooks/use-node-status.ts +48 -0
  48. package/src/hooks/use-stage.ts +129 -0
  49. package/src/hooks/use-window-rect.ts +20 -0
  50. package/src/icons/AppManageIcon.vue +15 -0
  51. package/src/icons/CenterIcon.vue +13 -0
  52. package/src/icons/CodeIcon.vue +28 -0
  53. package/src/icons/FolderMinusIcon.vue +22 -0
  54. package/src/icons/PinIcon.vue +25 -0
  55. package/src/icons/PinnedIcon.vue +25 -0
  56. package/src/index.ts +124 -0
  57. package/src/initService.ts +448 -0
  58. package/src/layouts/AddPageBox.vue +55 -0
  59. package/src/layouts/CodeEditor.vue +226 -0
  60. package/src/layouts/Framework.vue +165 -0
  61. package/src/layouts/NavMenu.vue +200 -0
  62. package/src/layouts/PropsPanel.vue +131 -0
  63. package/src/layouts/page-bar/AddButton.vue +48 -0
  64. package/src/layouts/page-bar/PageBar.vue +165 -0
  65. package/src/layouts/page-bar/PageBarScrollContainer.vue +192 -0
  66. package/src/layouts/page-bar/PageList.vue +55 -0
  67. package/src/layouts/page-bar/SwitchTypeButton.vue +45 -0
  68. package/src/layouts/sidebar/ComponentListPanel.vue +134 -0
  69. package/src/layouts/sidebar/Sidebar.vue +278 -0
  70. package/src/layouts/sidebar/code-block/CodeBlockList.vue +160 -0
  71. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +70 -0
  72. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +84 -0
  73. package/src/layouts/sidebar/data-source/DataSourceList.vue +172 -0
  74. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +106 -0
  75. package/src/layouts/sidebar/layer/LayerMenu.vue +128 -0
  76. package/src/layouts/sidebar/layer/LayerNodeTool.vue +32 -0
  77. package/src/layouts/sidebar/layer/LayerPanel.vue +119 -0
  78. package/src/layouts/sidebar/layer/use-click.ts +113 -0
  79. package/src/layouts/sidebar/layer/use-drag.ts +167 -0
  80. package/src/layouts/sidebar/layer/use-keybinding.ts +54 -0
  81. package/src/layouts/sidebar/layer/use-node-status.ts +114 -0
  82. package/src/layouts/workspace/Breadcrumb.vue +35 -0
  83. package/src/layouts/workspace/Workspace.vue +46 -0
  84. package/src/layouts/workspace/viewer/NodeListMenu.vue +115 -0
  85. package/src/layouts/workspace/viewer/Stage.vue +246 -0
  86. package/src/layouts/workspace/viewer/StageOverlay.vue +68 -0
  87. package/src/layouts/workspace/viewer/ViewerMenu.vue +145 -0
  88. package/src/services/BaseService.ts +230 -0
  89. package/src/services/codeBlock.ts +327 -0
  90. package/src/services/componentList.ts +58 -0
  91. package/src/services/dataSource.ts +216 -0
  92. package/src/services/dep.ts +152 -0
  93. package/src/services/editor.ts +1135 -0
  94. package/src/services/events.ts +93 -0
  95. package/src/services/history.ts +126 -0
  96. package/src/services/keybinding.ts +220 -0
  97. package/src/services/props.ts +286 -0
  98. package/src/services/stageOverlay.ts +212 -0
  99. package/src/services/storage.ts +155 -0
  100. package/src/services/ui.ts +157 -0
  101. package/src/shims-vue.d.ts +6 -0
  102. package/src/theme/breadcrumb.scss +6 -0
  103. package/src/theme/code-block.scss +6 -0
  104. package/src/theme/code-editor.scss +38 -0
  105. package/src/theme/common/var.scss +14 -0
  106. package/src/theme/component-list-panel.scss +95 -0
  107. package/src/theme/content-menu.scss +76 -0
  108. package/src/theme/data-source-field-select.scss +16 -0
  109. package/src/theme/data-source-field.scss +12 -0
  110. package/src/theme/data-source-fields.scss +13 -0
  111. package/src/theme/data-source-input.scss +18 -0
  112. package/src/theme/data-source-methods.scss +13 -0
  113. package/src/theme/data-source.scss +28 -0
  114. package/src/theme/event.scss +37 -0
  115. package/src/theme/floating-box.scss +32 -0
  116. package/src/theme/framework.scss +69 -0
  117. package/src/theme/icon.scss +12 -0
  118. package/src/theme/index.scss +10 -0
  119. package/src/theme/key-value.scss +20 -0
  120. package/src/theme/layer-panel.scss +26 -0
  121. package/src/theme/layout.scss +9 -0
  122. package/src/theme/nav-menu.scss +78 -0
  123. package/src/theme/page-bar.scss +102 -0
  124. package/src/theme/page-fragment-select.scss +14 -0
  125. package/src/theme/props-panel.scss +55 -0
  126. package/src/theme/resizer.scss +66 -0
  127. package/src/theme/ruler.scss +38 -0
  128. package/src/theme/search-input.scss +14 -0
  129. package/src/theme/sidebar.scss +79 -0
  130. package/src/theme/stage.scss +76 -0
  131. package/src/theme/theme.scss +28 -0
  132. package/src/theme/tree.scss +89 -0
  133. package/src/theme/workspace.scss +9 -0
  134. package/src/type.ts +780 -0
  135. package/src/utils/compose.ts +52 -0
  136. package/src/utils/config.ts +29 -0
  137. package/src/utils/content-menu.ts +95 -0
  138. package/src/utils/data-source/formConfigs/base.ts +30 -0
  139. package/src/utils/data-source/formConfigs/http.ts +60 -0
  140. package/src/utils/data-source/index.ts +249 -0
  141. package/src/utils/editor.ts +313 -0
  142. package/src/utils/idle-task.ts +72 -0
  143. package/src/utils/index.ts +24 -0
  144. package/src/utils/keybinding-config.ts +125 -0
  145. package/src/utils/logger.ts +47 -0
  146. package/src/utils/monaco-editor.ts +8 -0
  147. package/src/utils/operator.ts +106 -0
  148. package/src/utils/props.ts +407 -0
  149. package/src/utils/scroll-viewer.ts +165 -0
  150. package/src/utils/tree.ts +15 -0
  151. package/src/utils/undo-redo.ts +76 -0
  152. package/types/editorProps.d.ts +3 -1
  153. package/types/fields/CondOpSelect.vue.d.ts +17 -0
  154. package/types/fields/DataSourceFieldSelect/FieldSelect.vue.d.ts +36 -0
  155. package/types/fields/{DataSourceFieldSelect.vue.d.ts → DataSourceFieldSelect/Index.vue.d.ts} +1 -1
  156. package/types/fields/DisplayConds.vue.d.ts +32 -0
  157. package/types/index.d.ts +3 -1
  158. package/types/layouts/Framework.vue.d.ts +3 -1
  159. package/types/layouts/PropsPanel.vue.d.ts +21 -21
  160. package/types/layouts/page-bar/PageBar.vue.d.ts +6 -0
  161. package/types/layouts/page-bar/PageBarScrollContainer.vue.d.ts +3 -0
  162. package/types/layouts/page-bar/PageList.vue.d.ts +25 -0
  163. package/types/services/ui.d.ts +1 -0
  164. package/types/type.d.ts +30 -4
  165. package/types/utils/data-source/index.d.ts +1 -0
  166. package/types/utils/props.d.ts +12 -0
@@ -0,0 +1,375 @@
1
+ <template>
2
+ <div class="m-fields-event-select">
3
+ <m-form-table
4
+ v-if="isOldVersion"
5
+ ref="eventForm"
6
+ name="events"
7
+ :size="size"
8
+ :disabled="disabled"
9
+ :model="model"
10
+ :config="tableConfig"
11
+ @change="onChangeHandler"
12
+ ></m-form-table>
13
+
14
+ <div v-else class="fullWidth">
15
+ <TMagicButton class="create-button" type="primary" :size="size" :disabled="disabled" @click="addEvent()"
16
+ >添加事件</TMagicButton
17
+ >
18
+ <MPanel
19
+ v-for="(cardItem, index) in model[name]"
20
+ :key="index"
21
+ :disabled="disabled"
22
+ :size="size"
23
+ :config="actionsConfig"
24
+ :model="cardItem"
25
+ :label-width="config.labelWidth || '100px'"
26
+ @change="onChangeHandler"
27
+ >
28
+ <template #header>
29
+ <MFormContainer
30
+ class="fullWidth"
31
+ :config="eventNameConfig"
32
+ :model="cardItem"
33
+ :disabled="disabled"
34
+ :size="size"
35
+ @change="onChangeHandler"
36
+ ></MFormContainer>
37
+ <TMagicButton
38
+ style="color: #f56c6c"
39
+ link
40
+ :icon="Delete"
41
+ :disabled="disabled"
42
+ :size="size"
43
+ @click="removeEvent(index)"
44
+ ></TMagicButton>
45
+ </template>
46
+ </MPanel>
47
+ </div>
48
+ </div>
49
+ </template>
50
+
51
+ <script lang="ts" setup>
52
+ import { computed, inject } from 'vue';
53
+ import { Delete } from '@element-plus/icons-vue';
54
+ import { has } from 'lodash-es';
55
+
56
+ import type { EventOption } from '@tmagic/core';
57
+ import { TMagicButton } from '@tmagic/design';
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
+ import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX } from '@tmagic/utils';
62
+
63
+ import type { CodeSelectColConfig, DataSourceMethodSelectConfig, EventSelectConfig, Services } from '@editor/type';
64
+ import { getCascaderOptionsFromFields, traverseNode } from '@editor/utils';
65
+
66
+ defineOptions({
67
+ name: 'MFieldsEventSelect',
68
+ });
69
+
70
+ const props = defineProps<FieldProps<EventSelectConfig>>();
71
+
72
+ const emit = defineEmits(['change']);
73
+
74
+ const services = inject<Services>('services');
75
+
76
+ const editorService = services?.editorService;
77
+ const dataSourceService = services?.dataSourceService;
78
+ const eventsService = services?.eventsService;
79
+ const codeBlockService = services?.codeBlockService;
80
+
81
+ // 事件名称下拉框表单配置
82
+ const eventNameConfig = computed(() => {
83
+ const defaultEventNameConfig: ChildConfig = {
84
+ name: 'name',
85
+ text: '事件',
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
+ },
95
+ labelWidth: '40px',
96
+ checkStrictly: () => props.config.src !== 'component',
97
+ valueSeparator: '.',
98
+ options: (mForm: FormState, { formValue }: any) => {
99
+ let events: EventOption[] | CascaderOption[] = [];
100
+
101
+ if (!eventsService || !dataSourceService) return events;
102
+
103
+ if (props.config.src === 'component') {
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
+
132
+ return events.map((option) => ({
133
+ text: option.label,
134
+ value: option.value,
135
+ }));
136
+ }
137
+
138
+ if (props.config.src === 'datasource') {
139
+ // 从数据源类型中获取到相关事件
140
+ events = dataSourceService.getFormEvent(formValue.type);
141
+ // 从数据源类型和实例中分别获取数据以追加数据变化的事件
142
+ const dataSource = dataSourceService.getDataSourceById(formValue.id);
143
+ const fields = dataSource?.fields || [];
144
+ if (fields.length > 0) {
145
+ return [
146
+ ...events,
147
+ {
148
+ label: '数据变化',
149
+ value: DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX,
150
+ children: getCascaderOptionsFromFields(fields),
151
+ },
152
+ ];
153
+ }
154
+
155
+ return events;
156
+ }
157
+ },
158
+ };
159
+ return { ...defaultEventNameConfig, ...props.config.eventNameConfig };
160
+ });
161
+
162
+ // 联动类型
163
+ const actionTypeConfig = computed(() => {
164
+ const defaultActionTypeConfig = {
165
+ name: 'actionType',
166
+ text: '联动类型',
167
+ type: 'select',
168
+ defaultValue: ActionType.COMP,
169
+ options: () => [
170
+ {
171
+ text: '组件',
172
+ label: '组件',
173
+ value: ActionType.COMP,
174
+ },
175
+ {
176
+ text: '代码',
177
+ label: '代码',
178
+ disabled: !Object.keys(codeBlockService?.getCodeDsl() || {}).length,
179
+ value: ActionType.CODE,
180
+ },
181
+ {
182
+ text: '数据源',
183
+ label: '数据源',
184
+ disabled: !dataSourceService
185
+ ?.get('dataSources')
186
+ ?.filter((ds) => ds.methods?.length || dataSourceService?.getFormMethod(ds.type).length).length,
187
+ value: ActionType.DATA_SOURCE,
188
+ },
189
+ ],
190
+ };
191
+ return { ...defaultActionTypeConfig, ...props.config.actionTypeConfig };
192
+ });
193
+
194
+ // 联动组件配置
195
+ const targetCompConfig = computed(() => {
196
+ const defaultTargetCompConfig = {
197
+ name: 'to',
198
+ text: '联动组件',
199
+ type: 'ui-select',
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
+ },
205
+ };
206
+ return { ...defaultTargetCompConfig, ...props.config.targetCompConfig };
207
+ });
208
+
209
+ // 联动组件动作配置
210
+ const compActionConfig = computed(() => {
211
+ const defaultCompActionConfig: ChildConfig = {
212
+ name: 'method',
213
+ text: '动作',
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,
225
+ options: (mForm: FormState, { model }: any) => {
226
+ const node = editorService?.getNodeById(model.to);
227
+ if (!node?.type) return [];
228
+
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,
258
+ }));
259
+ },
260
+ };
261
+ return { ...defaultCompActionConfig, ...props.config.compActionConfig };
262
+ });
263
+
264
+ // 代码联动配置
265
+ const codeActionConfig = computed(() => {
266
+ const defaultCodeActionConfig: CodeSelectColConfig = {
267
+ type: 'code-select-col',
268
+ text: '代码块',
269
+ name: 'codeId',
270
+ notEditable: () => !codeBlockService?.getEditStatus(),
271
+ display: (mForm, { model }) => model.actionType === ActionType.CODE,
272
+ };
273
+ return { ...defaultCodeActionConfig, ...props.config.codeActionConfig };
274
+ });
275
+
276
+ // 数据源联动配置
277
+ const dataSourceActionConfig = computed(() => {
278
+ const defaultDataSourceActionConfig: DataSourceMethodSelectConfig = {
279
+ type: 'data-source-method-select',
280
+ text: '数据源方法',
281
+ name: 'dataSourceMethod',
282
+ notEditable: () => !services?.dataSourceService.get('editable'),
283
+ display: (mForm, { model }) => model.actionType === ActionType.DATA_SOURCE,
284
+ };
285
+ return { ...defaultDataSourceActionConfig, ...props.config.dataSourceActionConfig };
286
+ });
287
+
288
+ // 兼容旧的数据格式
289
+ const tableConfig = computed(() => ({
290
+ type: 'table',
291
+ name: 'events',
292
+ items: [
293
+ {
294
+ name: 'name',
295
+ label: '事件名',
296
+ type: eventNameConfig.value.type,
297
+ options: (mForm: FormState, { formValue }: any) =>
298
+ eventsService?.getEvent(formValue.type).map((option: any) => ({
299
+ text: option.label,
300
+ value: option.value,
301
+ })),
302
+ },
303
+ {
304
+ name: 'to',
305
+ label: '联动组件',
306
+ type: 'ui-select',
307
+ },
308
+ {
309
+ name: 'method',
310
+ label: '动作',
311
+ type: compActionConfig.value.type,
312
+ options: (mForm: FormState, { model }: any) => {
313
+ const node = editorService?.getNodeById(model.to);
314
+ if (!node?.type) return [];
315
+
316
+ return eventsService?.getMethod(node.type).map((option: any) => ({
317
+ text: option.label,
318
+ value: option.value,
319
+ }));
320
+ },
321
+ },
322
+ ],
323
+ }));
324
+
325
+ // 组件动作组表单配置
326
+ const actionsConfig = computed<PanelConfig>(() => ({
327
+ type: 'panel',
328
+ items: [
329
+ {
330
+ type: 'group-list',
331
+ name: 'actions',
332
+ expandAll: true,
333
+ enableToggleMode: false,
334
+ titlePrefix: '动作',
335
+ items: [
336
+ actionTypeConfig.value,
337
+ targetCompConfig.value,
338
+ compActionConfig.value,
339
+ codeActionConfig.value,
340
+ dataSourceActionConfig.value,
341
+ ],
342
+ },
343
+ ],
344
+ }));
345
+
346
+ // 是否为旧的数据格式
347
+ const isOldVersion = computed(() => {
348
+ if (props.model[props.name].length === 0) return false;
349
+ return !has(props.model[props.name][0], 'actions');
350
+ });
351
+
352
+ // 添加事件
353
+ const addEvent = () => {
354
+ const defaultEvent = {
355
+ name: '',
356
+ actions: [],
357
+ };
358
+ if (!props.model[props.name]) {
359
+ props.model[props.name] = [];
360
+ }
361
+ props.model[props.name].push(defaultEvent);
362
+ onChangeHandler();
363
+ };
364
+
365
+ // 删除事件
366
+ const removeEvent = (index: number) => {
367
+ if (!props.name) return;
368
+ props.model[props.name].splice(index, 1);
369
+ onChangeHandler();
370
+ };
371
+
372
+ const onChangeHandler = () => {
373
+ emit('change', props.model);
374
+ };
375
+ </script>
@@ -0,0 +1,137 @@
1
+ <template>
2
+ <div class="m-fields-key-value">
3
+ <div v-if="!showCode">
4
+ <div class="m-fields-key-value-item" v-for="(item, index) in records" :key="index">
5
+ <TMagicInput
6
+ placeholder="key"
7
+ v-model="records[index][0]"
8
+ :disabled="disabled"
9
+ :size="size"
10
+ @change="keyChangeHandler"
11
+ ></TMagicInput>
12
+ <span class="m-fileds-key-value-delimiter">:</span>
13
+ <TMagicInput
14
+ placeholder="value"
15
+ v-model="records[index][1]"
16
+ :disabled="disabled"
17
+ :size="size"
18
+ @change="valueChangeHandler"
19
+ ></TMagicInput>
20
+
21
+ <TMagicButton
22
+ class="m-fileds-key-value-delete"
23
+ type="danger"
24
+ :size="size"
25
+ :disabled="disabled"
26
+ circle
27
+ plain
28
+ :icon="Delete"
29
+ @click="deleteHandler(index)"
30
+ ></TMagicButton>
31
+ </div>
32
+
33
+ <TMagicButton type="primary" :size="size" :disabled="disabled" plain :icon="Plus" @click="addHandler"
34
+ >添加</TMagicButton
35
+ >
36
+ </div>
37
+
38
+ <MagicCodeEditor
39
+ v-if="config.advanced && showCode"
40
+ height="200px"
41
+ :init-values="model[name]"
42
+ language="json"
43
+ :options="{
44
+ readOnly: disabled,
45
+ }"
46
+ :parse="true"
47
+ @save="save"
48
+ ></MagicCodeEditor>
49
+
50
+ <TMagicButton
51
+ v-if="config.advanced"
52
+ size="default"
53
+ :disabled="disabled"
54
+ link
55
+ :icon="CodeIcon"
56
+ @click="showCode = !showCode"
57
+ ></TMagicButton>
58
+ </div>
59
+ </template>
60
+
61
+ <script setup lang="ts">
62
+ import { ref, watchEffect } from 'vue';
63
+ import { Delete, Plus } from '@element-plus/icons-vue';
64
+
65
+ import { TMagicButton, TMagicInput } from '@tmagic/design';
66
+ import type { FieldProps, FormItem } from '@tmagic/form';
67
+
68
+ import CodeIcon from '@editor/icons/CodeIcon.vue';
69
+ import MagicCodeEditor from '@editor/layouts/CodeEditor.vue';
70
+
71
+ defineOptions({
72
+ name: 'MFieldsKeyValue',
73
+ });
74
+
75
+ const props = withDefaults(
76
+ defineProps<
77
+ FieldProps<
78
+ {
79
+ type: 'key-value';
80
+ advanced?: boolean;
81
+ } & FormItem
82
+ >
83
+ >(),
84
+ {
85
+ disabled: false,
86
+ },
87
+ );
88
+
89
+ const emit = defineEmits<(e: 'change', value: Record<string, any>) => void>();
90
+
91
+ const records = ref<[string, string][]>([]);
92
+ const showCode = ref(false);
93
+
94
+ watchEffect(() => {
95
+ const initValues: [string, any][] = Object.entries(props.model[props.name] || {});
96
+
97
+ for (const [, value] of initValues) {
98
+ if (typeof value !== 'string') {
99
+ showCode.value = true;
100
+ break;
101
+ }
102
+ }
103
+
104
+ records.value = initValues;
105
+ });
106
+
107
+ const getValue = () => {
108
+ const record: Record<string, string> = {};
109
+ records.value.forEach(([key, value]) => {
110
+ if (key) {
111
+ record[key] = value;
112
+ }
113
+ });
114
+ return record;
115
+ };
116
+
117
+ const keyChangeHandler = () => {
118
+ emit('change', getValue());
119
+ };
120
+
121
+ const valueChangeHandler = () => {
122
+ emit('change', getValue());
123
+ };
124
+
125
+ const addHandler = () => {
126
+ records.value.push(['', '']);
127
+ };
128
+
129
+ const deleteHandler = (index: number) => {
130
+ records.value.splice(index, 1);
131
+ emit('change', getValue());
132
+ };
133
+
134
+ const save = (v: string | any) => {
135
+ emit('change', v);
136
+ };
137
+ </script>
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <div class="m-fields-page-fragment-select">
3
+ <div class="page-fragment-select-container">
4
+ <!-- 页面片下拉框 -->
5
+ <m-form-container
6
+ class="select"
7
+ :config="selectConfig"
8
+ :model="model"
9
+ :size="size"
10
+ @change="changeHandler"
11
+ ></m-form-container>
12
+ <!-- 编辑按钮 -->
13
+ <Icon v-if="model[name]" class="icon" :icon="Edit" @click="editPageFragment(model[name])"></Icon>
14
+ </div>
15
+ </div>
16
+ </template>
17
+
18
+ <script lang="ts" setup>
19
+ import { computed, inject } from 'vue';
20
+ import { Edit } from '@element-plus/icons-vue';
21
+
22
+ import { FieldProps } from '@tmagic/form';
23
+ import { Id, NodeType } from '@tmagic/schema';
24
+
25
+ import Icon from '@editor/components/Icon.vue';
26
+ import type { PageFragmentSelectConfig, Services } from '@editor/type';
27
+
28
+ defineOptions({
29
+ name: 'MFieldsPageFragmentSelect',
30
+ });
31
+
32
+ const services = inject<Services>('services');
33
+ const emit = defineEmits(['change']);
34
+
35
+ const props = withDefaults(defineProps<FieldProps<PageFragmentSelectConfig>>(), {
36
+ disabled: false,
37
+ });
38
+ const pageList = computed(() =>
39
+ services?.editorService.get('root')?.items.filter((item) => item.type === NodeType.PAGE_FRAGMENT),
40
+ );
41
+
42
+ const selectConfig = {
43
+ type: 'select',
44
+ name: props.name,
45
+ options: () => {
46
+ if (pageList.value) {
47
+ return pageList.value.map((item) => ({
48
+ text: `${item.devconfig?.tabName || item.title || item.name}(${item.id})`,
49
+ label: `${item.devconfig?.tabName || item.title || item.name}(${item.id})`,
50
+ value: item.id,
51
+ }));
52
+ }
53
+ return [];
54
+ },
55
+ };
56
+ const changeHandler = async () => {
57
+ emit('change', props.model[props.name]);
58
+ };
59
+
60
+ const editPageFragment = (id: Id) => {
61
+ services?.editorService.select(id);
62
+ };
63
+ </script>
@@ -0,0 +1,135 @@
1
+ <template>
2
+ <div class="m-fields-ui-select" v-if="uiSelectMode" @click="cancelHandler">
3
+ <TMagicButton type="danger" :icon="Delete" :disabled="disabled" :size="size" link style="padding: 0"
4
+ >取消</TMagicButton
5
+ >
6
+ </div>
7
+ <div class="m-fields-ui-select" v-else style="display: flex">
8
+ <template v-if="val">
9
+ <TMagicTooltip content="清除" placement="top">
10
+ <TMagicButton
11
+ style="padding: 0"
12
+ type="danger"
13
+ :icon="Close"
14
+ :disabled="disabled"
15
+ :size="size"
16
+ link
17
+ @click.stop="deleteHandler"
18
+ ></TMagicButton>
19
+ </TMagicTooltip>
20
+
21
+ <TMagicTooltip content="点击选中组件" placement="top">
22
+ <TMagicButton
23
+ link
24
+ style="padding: 0; margin: 0"
25
+ :disabled="disabled"
26
+ :size="size"
27
+ @click="selectNode(val)"
28
+ @mouseenter="highlight(val)"
29
+ @mouseleave="unhighlight"
30
+ >{{ `${toName}_${val}` }}</TMagicButton
31
+ >
32
+ </TMagicTooltip>
33
+ </template>
34
+
35
+ <TMagicTooltip v-else content="点击此处选择" placement="top">
36
+ <TMagicButton link style="padding: 0; margin: 0" :disabled="disabled" :size="size" @click="startSelect"
37
+ >点击此处选择</TMagicButton
38
+ >
39
+ </TMagicTooltip>
40
+ </div>
41
+ </template>
42
+
43
+ <script lang="ts" setup>
44
+ import { computed, inject, ref } from 'vue';
45
+ import { Close, Delete } from '@element-plus/icons-vue';
46
+ import { throttle } from 'lodash-es';
47
+
48
+ import { TMagicButton, TMagicTooltip } from '@tmagic/design';
49
+ import type { FieldProps, FormItem, FormState } from '@tmagic/form';
50
+ import type { Id } from '@tmagic/schema';
51
+
52
+ import { Services, UI_SELECT_MODE_EVENT_NAME } from '@editor/type';
53
+
54
+ defineOptions({
55
+ name: 'MFieldsUISelect',
56
+ });
57
+
58
+ const props = defineProps<FieldProps<{ type: 'ui-select' } & FormItem>>();
59
+
60
+ const emit = defineEmits(['change']);
61
+
62
+ const services = inject<Services>('services');
63
+ const mForm = inject<FormState>('mForm');
64
+ const val = computed(() => props.model[props.name]);
65
+ const uiSelectMode = ref(false);
66
+
67
+ const cancelHandler = () => {
68
+ if (!services?.uiService) return;
69
+ services.uiService.set('uiSelectMode', false);
70
+ uiSelectMode.value = false;
71
+ globalThis.document.removeEventListener(UI_SELECT_MODE_EVENT_NAME, clickHandler as EventListener);
72
+ };
73
+
74
+ const clickHandler = ({ detail }: Event & { detail: any }) => {
75
+ if (detail.id) {
76
+ props.model[props.name] = detail.id;
77
+ emit('change', detail.id);
78
+ mForm?.$emit('field-change', props.prop, detail.id);
79
+ }
80
+
81
+ if (cancelHandler) {
82
+ cancelHandler();
83
+ }
84
+ };
85
+
86
+ const toName = computed(() => {
87
+ const config = services?.editorService.getNodeById(val.value);
88
+ return config?.name || '';
89
+ });
90
+
91
+ const startSelect = () => {
92
+ if (!services?.uiService) return;
93
+ services.uiService.set('uiSelectMode', true);
94
+ uiSelectMode.value = true;
95
+ globalThis.document.addEventListener(UI_SELECT_MODE_EVENT_NAME, clickHandler as EventListener);
96
+ };
97
+
98
+ const deleteHandler = () => {
99
+ if (props.model) {
100
+ props.model[props.name] = '';
101
+ emit('change', '');
102
+ mForm?.$emit('field-change', props.prop, '');
103
+ }
104
+ };
105
+
106
+ const selectNode = async (id: Id) => {
107
+ await services?.editorService.select(id);
108
+ services?.editorService.get('stage')?.select(id);
109
+ services?.stageOverlayService.get('stage')?.select(id);
110
+ };
111
+
112
+ const highlight = throttle((id: Id) => {
113
+ services?.editorService.highlight(id);
114
+ services?.editorService.get('stage')?.highlight(id);
115
+ services?.stageOverlayService.get('stage')?.highlight(id);
116
+ }, 150);
117
+
118
+ const unhighlight = () => {
119
+ services?.editorService.set('highlightNode', null);
120
+ services?.editorService.get('stage')?.clearHighlight();
121
+ services?.stageOverlayService.get('stage')?.clearHighlight();
122
+ };
123
+ </script>
124
+
125
+ <style lang="scss">
126
+ .m-fields-ui-select {
127
+ cursor: pointer;
128
+ i {
129
+ margin-right: 3px;
130
+ }
131
+ span {
132
+ color: #2882e0;
133
+ }
134
+ }
135
+ </style>