@tmagic/editor 1.4.8 → 1.4.9

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 (143) hide show
  1. package/package.json +11 -11
  2. package/src/Editor.vue +211 -0
  3. package/src/components/CodeBlockEditor.vue +268 -0
  4. package/src/components/CodeParams.vue +60 -0
  5. package/src/components/ContentMenu.vue +193 -0
  6. package/src/components/FloatingBox.vue +178 -0
  7. package/src/components/Icon.vue +27 -0
  8. package/src/components/Resizer.vue +23 -0
  9. package/src/components/ScrollBar.vue +151 -0
  10. package/src/components/ScrollViewer.vue +128 -0
  11. package/src/components/SearchInput.vue +37 -0
  12. package/src/components/SplitView.vue +186 -0
  13. package/src/components/ToolButton.vue +138 -0
  14. package/src/components/Tree.vue +71 -0
  15. package/src/components/TreeNode.vue +150 -0
  16. package/src/editorProps.ts +117 -0
  17. package/src/fields/Code.vue +50 -0
  18. package/src/fields/CodeLink.vue +80 -0
  19. package/src/fields/CodeSelect.vue +132 -0
  20. package/src/fields/CodeSelectCol.vue +139 -0
  21. package/src/fields/DataSourceFieldSelect.vue +149 -0
  22. package/src/fields/DataSourceFields.vue +323 -0
  23. package/src/fields/DataSourceInput.vue +339 -0
  24. package/src/fields/DataSourceMethodSelect.vue +153 -0
  25. package/src/fields/DataSourceMethods.vue +104 -0
  26. package/src/fields/DataSourceMocks.vue +255 -0
  27. package/src/fields/DataSourceSelect.vue +98 -0
  28. package/src/fields/EventSelect.vue +375 -0
  29. package/src/fields/KeyValue.vue +137 -0
  30. package/src/fields/PageFragmentSelect.vue +63 -0
  31. package/src/fields/UISelect.vue +135 -0
  32. package/src/hooks/index.ts +24 -0
  33. package/src/hooks/use-code-block-edit.ts +83 -0
  34. package/src/hooks/use-data-source-edit.ts +46 -0
  35. package/src/hooks/use-data-source-method.ts +100 -0
  36. package/src/hooks/use-editor-content-height.ts +26 -0
  37. package/src/hooks/use-filter.ts +55 -0
  38. package/src/hooks/use-float-box.ts +76 -0
  39. package/src/hooks/use-getso.ts +35 -0
  40. package/src/hooks/use-next-float-box-position.ts +29 -0
  41. package/src/hooks/use-node-status.ts +48 -0
  42. package/src/hooks/use-stage.ts +129 -0
  43. package/src/hooks/use-window-rect.ts +20 -0
  44. package/src/icons/AppManageIcon.vue +15 -0
  45. package/src/icons/CenterIcon.vue +13 -0
  46. package/src/icons/CodeIcon.vue +28 -0
  47. package/src/icons/FolderMinusIcon.vue +22 -0
  48. package/src/icons/PinIcon.vue +25 -0
  49. package/src/icons/PinnedIcon.vue +25 -0
  50. package/src/index.ts +118 -0
  51. package/src/initService.ts +448 -0
  52. package/src/layouts/AddPageBox.vue +55 -0
  53. package/src/layouts/CodeEditor.vue +226 -0
  54. package/src/layouts/Framework.vue +163 -0
  55. package/src/layouts/NavMenu.vue +200 -0
  56. package/src/layouts/PropsPanel.vue +131 -0
  57. package/src/layouts/page-bar/AddButton.vue +48 -0
  58. package/src/layouts/page-bar/PageBar.vue +159 -0
  59. package/src/layouts/page-bar/PageBarScrollContainer.vue +156 -0
  60. package/src/layouts/page-bar/SwitchTypeButton.vue +45 -0
  61. package/src/layouts/sidebar/ComponentListPanel.vue +134 -0
  62. package/src/layouts/sidebar/Sidebar.vue +278 -0
  63. package/src/layouts/sidebar/code-block/CodeBlockList.vue +160 -0
  64. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +70 -0
  65. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +84 -0
  66. package/src/layouts/sidebar/data-source/DataSourceList.vue +172 -0
  67. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +106 -0
  68. package/src/layouts/sidebar/layer/LayerMenu.vue +128 -0
  69. package/src/layouts/sidebar/layer/LayerNodeTool.vue +32 -0
  70. package/src/layouts/sidebar/layer/LayerPanel.vue +119 -0
  71. package/src/layouts/sidebar/layer/use-click.ts +113 -0
  72. package/src/layouts/sidebar/layer/use-drag.ts +167 -0
  73. package/src/layouts/sidebar/layer/use-keybinding.ts +54 -0
  74. package/src/layouts/sidebar/layer/use-node-status.ts +114 -0
  75. package/src/layouts/workspace/Breadcrumb.vue +35 -0
  76. package/src/layouts/workspace/Workspace.vue +46 -0
  77. package/src/layouts/workspace/viewer/NodeListMenu.vue +115 -0
  78. package/src/layouts/workspace/viewer/Stage.vue +246 -0
  79. package/src/layouts/workspace/viewer/StageOverlay.vue +68 -0
  80. package/src/layouts/workspace/viewer/ViewerMenu.vue +145 -0
  81. package/src/services/BaseService.ts +230 -0
  82. package/src/services/codeBlock.ts +327 -0
  83. package/src/services/componentList.ts +58 -0
  84. package/src/services/dataSource.ts +216 -0
  85. package/src/services/dep.ts +152 -0
  86. package/src/services/editor.ts +1135 -0
  87. package/src/services/events.ts +93 -0
  88. package/src/services/history.ts +126 -0
  89. package/src/services/keybinding.ts +220 -0
  90. package/src/services/props.ts +286 -0
  91. package/src/services/stageOverlay.ts +212 -0
  92. package/src/services/storage.ts +155 -0
  93. package/src/services/ui.ts +156 -0
  94. package/src/shims-vue.d.ts +6 -0
  95. package/src/theme/breadcrumb.scss +6 -0
  96. package/src/theme/code-block.scss +6 -0
  97. package/src/theme/code-editor.scss +38 -0
  98. package/src/theme/common/var.scss +14 -0
  99. package/src/theme/component-list-panel.scss +95 -0
  100. package/src/theme/content-menu.scss +76 -0
  101. package/src/theme/data-source-field.scss +12 -0
  102. package/src/theme/data-source-fields.scss +13 -0
  103. package/src/theme/data-source-input.scss +18 -0
  104. package/src/theme/data-source-methods.scss +13 -0
  105. package/src/theme/data-source.scss +28 -0
  106. package/src/theme/event.scss +37 -0
  107. package/src/theme/floating-box.scss +32 -0
  108. package/src/theme/framework.scss +69 -0
  109. package/src/theme/icon.scss +12 -0
  110. package/src/theme/index.scss +10 -0
  111. package/src/theme/key-value.scss +20 -0
  112. package/src/theme/layer-panel.scss +26 -0
  113. package/src/theme/layout.scss +9 -0
  114. package/src/theme/nav-menu.scss +78 -0
  115. package/src/theme/page-bar.scss +89 -0
  116. package/src/theme/page-fragment-select.scss +14 -0
  117. package/src/theme/props-panel.scss +55 -0
  118. package/src/theme/resizer.scss +66 -0
  119. package/src/theme/ruler.scss +38 -0
  120. package/src/theme/search-input.scss +14 -0
  121. package/src/theme/sidebar.scss +79 -0
  122. package/src/theme/stage.scss +76 -0
  123. package/src/theme/theme.scss +27 -0
  124. package/src/theme/tree.scss +89 -0
  125. package/src/theme/workspace.scss +9 -0
  126. package/src/type.ts +749 -0
  127. package/src/utils/compose.ts +52 -0
  128. package/src/utils/config.ts +29 -0
  129. package/src/utils/content-menu.ts +95 -0
  130. package/src/utils/data-source/formConfigs/base.ts +30 -0
  131. package/src/utils/data-source/formConfigs/http.ts +60 -0
  132. package/src/utils/data-source/index.ts +243 -0
  133. package/src/utils/editor.ts +313 -0
  134. package/src/utils/idle-task.ts +72 -0
  135. package/src/utils/index.ts +24 -0
  136. package/src/utils/keybinding-config.ts +125 -0
  137. package/src/utils/logger.ts +47 -0
  138. package/src/utils/monaco-editor.ts +8 -0
  139. package/src/utils/operator.ts +106 -0
  140. package/src/utils/props.ts +504 -0
  141. package/src/utils/scroll-viewer.ts +165 -0
  142. package/src/utils/tree.ts +15 -0
  143. package/src/utils/undo-redo.ts +76 -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>