@tmagic/editor 1.8.0-beta.25 → 1.8.0-beta.26

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 (111) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +10 -16
  2. package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +4 -4
  3. package/dist/es/components/CompareForm.vue_vue_type_script_setup_true_lang.js +5 -8
  4. package/dist/es/components/ViewForm.vue_vue_type_script_setup_true_lang.js +3 -5
  5. package/dist/es/fields/Code.vue_vue_type_script_setup_true_lang.js +3 -11
  6. package/dist/es/fields/CodeSelect.vue_vue_type_script_setup_true_lang.js +29 -124
  7. package/dist/es/fields/CodeSelectCol.vue_vue_type_script_setup_true_lang.js +5 -6
  8. package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +1 -1
  9. package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +4 -5
  10. package/dist/es/fields/DataSourceFields.vue_vue_type_script_setup_true_lang.js +6 -10
  11. package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +5 -7
  12. package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +1 -4
  13. package/dist/es/fields/DataSourceMocks.vue_vue_type_script_setup_true_lang.js +4 -6
  14. package/dist/es/fields/DataSourceSelect.vue_vue_type_script_setup_true_lang.js +1 -1
  15. package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +9 -142
  16. package/dist/es/fields/EventSelect.vue_vue_type_script_setup_true_lang.js +51 -326
  17. package/dist/es/fields/KeyValue.vue_vue_type_script_setup_true_lang.js +2 -4
  18. package/dist/es/fields/StyleSetter/components/Border.vue_vue_type_script_setup_true_lang.js +3 -34
  19. package/dist/es/fields/StyleSetter/configs.js +663 -0
  20. package/dist/es/fields/StyleSetter/pro/Background.vue_vue_type_script_setup_true_lang.js +5 -108
  21. package/dist/es/fields/StyleSetter/pro/Border.vue_vue_type_script_setup_true_lang.js +3 -9
  22. package/dist/es/fields/StyleSetter/pro/Font.vue_vue_type_script_setup_true_lang.js +5 -102
  23. package/dist/es/fields/StyleSetter/pro/Layout.vue_vue_type_script_setup_true_lang.js +5 -265
  24. package/dist/es/fields/StyleSetter/pro/Position.vue_vue_type_script_setup_true_lang.js +5 -85
  25. package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +3 -22
  26. package/dist/es/fields/configs/codeSelect.js +98 -0
  27. package/dist/es/fields/configs/displayConds.js +158 -0
  28. package/dist/es/fields/configs/eventSelect.js +225 -0
  29. package/dist/es/fields/configs/stickyAddButton.js +20 -0
  30. package/dist/es/fields/headless-validation.js +139 -0
  31. package/dist/es/headless.js +2 -0
  32. package/dist/es/hooks/use-compare-form.js +6 -23
  33. package/dist/es/hooks/use-form-context.js +19 -0
  34. package/dist/es/index.js +2 -1
  35. package/dist/es/initService.js +17 -1
  36. package/dist/es/layouts/history-list/HistoryDiffDialog.vue_vue_type_script_setup_true_lang.js +1 -7
  37. package/dist/es/layouts/history-list/HistoryListPanel.vue_vue_type_script_setup_true_lang.js +1 -11
  38. package/dist/es/layouts/history-list/useHistoryRevert.js +3 -11
  39. package/dist/es/layouts/props-panel/FormPanel.vue_vue_type_script_setup_true_lang.js +10 -23
  40. package/dist/es/layouts/props-panel/PropsPanel.vue_vue_type_script_setup_true_lang.js +3 -13
  41. package/dist/es/layouts/sidebar/code-block/CodeBlockListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  42. package/dist/es/layouts/sidebar/code-block/useContentMenu.js +1 -1
  43. package/dist/es/layouts/sidebar/data-source/DataSourceListPanel.vue_vue_type_script_setup_true_lang.js +1 -1
  44. package/dist/es/layouts/sidebar/data-source/useContentMenu.js +1 -1
  45. package/dist/es/plugin.js +33 -32
  46. package/dist/es/style.css +116 -60
  47. package/dist/es/utils/data-source/index.js +2 -1
  48. package/dist/es/utils/props.js +1 -4
  49. package/dist/es/utils/type-match-rules.js +2 -2
  50. package/dist/style.css +116 -60
  51. package/dist/themes/magic-admin.css +128 -67
  52. package/dist/tmagic-editor-headless.umd.cjs +10867 -0
  53. package/dist/tmagic-editor.umd.cjs +2123 -2015
  54. package/package.json +12 -7
  55. package/src/Editor.vue +12 -16
  56. package/src/components/CodeParams.vue +10 -4
  57. package/src/components/CompareForm.vue +4 -5
  58. package/src/components/ViewForm.vue +2 -3
  59. package/src/editorProps.ts +1 -2
  60. package/src/fields/Code.vue +2 -10
  61. package/src/fields/CodeSelect.vue +20 -116
  62. package/src/fields/CodeSelectCol.vue +3 -5
  63. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +1 -1
  64. package/src/fields/DataSourceFieldSelect/Index.vue +3 -5
  65. package/src/fields/DataSourceFields.vue +0 -7
  66. package/src/fields/DataSourceMethodSelect.vue +2 -5
  67. package/src/fields/DataSourceMethods.vue +1 -6
  68. package/src/fields/DataSourceMocks.vue +1 -10
  69. package/src/fields/DataSourceSelect.vue +1 -1
  70. package/src/fields/DisplayConds.vue +17 -157
  71. package/src/fields/EventSelect.vue +28 -375
  72. package/src/fields/KeyValue.vue +3 -4
  73. package/src/fields/StyleSetter/components/Border.vue +4 -38
  74. package/src/fields/StyleSetter/configs.ts +690 -0
  75. package/src/fields/StyleSetter/pro/Background.vue +4 -104
  76. package/src/fields/StyleSetter/pro/Border.vue +3 -12
  77. package/src/fields/StyleSetter/pro/Font.vue +4 -123
  78. package/src/fields/StyleSetter/pro/Layout.vue +4 -219
  79. package/src/fields/StyleSetter/pro/Position.vue +6 -110
  80. package/src/fields/StyleSetter/pro/Transform.vue +4 -30
  81. package/src/fields/configs/codeSelect.ts +127 -0
  82. package/src/fields/configs/displayConds.ts +184 -0
  83. package/src/fields/configs/eventSelect.ts +309 -0
  84. package/src/fields/configs/stickyAddButton.ts +31 -0
  85. package/src/fields/headless-validation.ts +177 -0
  86. package/src/headless.ts +33 -0
  87. package/src/hooks/use-compare-form.ts +7 -25
  88. package/src/hooks/use-form-context.ts +40 -0
  89. package/src/index.ts +2 -0
  90. package/src/initService.ts +23 -1
  91. package/src/layouts/history-list/HistoryDiffDialog.vue +0 -10
  92. package/src/layouts/history-list/HistoryListPanel.vue +1 -13
  93. package/src/layouts/history-list/useHistoryRevert.ts +7 -13
  94. package/src/layouts/props-panel/FormPanel.vue +17 -28
  95. package/src/layouts/props-panel/PropsPanel.vue +1 -4
  96. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +1 -1
  97. package/src/layouts/sidebar/code-block/useContentMenu.ts +1 -1
  98. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +1 -1
  99. package/src/layouts/sidebar/data-source/useContentMenu.ts +1 -1
  100. package/src/plugin.ts +32 -33
  101. package/src/theme/code-block.scss +0 -37
  102. package/src/theme/display-conds.scss +4 -0
  103. package/src/theme/event.scss +22 -45
  104. package/src/theme/props-panel.scss +17 -3
  105. package/src/theme/themes/magic-admin/index.scss +12 -1
  106. package/src/type.ts +10 -29
  107. package/src/utils/data-source/index.ts +1 -0
  108. package/src/utils/props.ts +1 -4
  109. package/src/utils/type-match-rules.ts +8 -3
  110. package/types/headless.d.ts +18 -0
  111. package/types/index.d.ts +154 -179
@@ -0,0 +1,690 @@
1
+ import { markRaw } from 'vue';
2
+
3
+ import { appendValidateSuggestion } from '@tmagic/design/headless';
4
+ import { defineFormItem, type FormConfig } from '@tmagic/form/headless';
5
+ import type { StyleSchema } from '@tmagic/schema';
6
+
7
+ import type { validateDataSourceFieldSelect } from '@editor/utils/type-match-rules';
8
+
9
+ import BackgroundPosition from './components/BackgroundPosition.vue';
10
+ import {
11
+ AlignItemsCenter,
12
+ AlignItemsFlexEnd,
13
+ AlignItemsFlexStart,
14
+ AlignItemsSpaceAround,
15
+ AlignItemsSpaceBetween,
16
+ } from './icons/align-items';
17
+ import { BackgroundNoRepeat, BackgroundRepeat, BackgroundRepeatX, BackgroundRepeatY } from './icons/background-repeat';
18
+ import { DisplayBlock, DisplayFlex, DisplayInline, DisplayInlineBlock, DisplayNone } from './icons/display';
19
+ import {
20
+ FlexDirectionColumn,
21
+ FlexDirectionColumnReverse,
22
+ FlexDirectionRow,
23
+ FlexDirectionRowReverse,
24
+ } from './icons/flex-direction';
25
+ import {
26
+ JustifyContentCenter,
27
+ JustifyContentFlexEnd,
28
+ JustifyContentFlexStart,
29
+ JustifyContentSpaceAround,
30
+ JustifyContentSpaceBetween,
31
+ } from './icons/justify-content';
32
+ import { AlignCenter, AlignLeft, AlignRight } from './icons/text-align';
33
+
34
+ /**
35
+ * 「布局」面板配置。
36
+ *
37
+ * `theme` 需要由调用方从 `useTheme()` 解析后传入:magic-admin 主题下换行只能用普通
38
+ * radio,其余主题用 button。
39
+ */
40
+ export const createLayoutConfig = (theme: string): FormConfig => [
41
+ defineFormItem({
42
+ name: 'display',
43
+ text: '模式',
44
+ type: 'radioGroup',
45
+ childType: 'button',
46
+ labelWidth: '90px',
47
+ iconSize: '24px',
48
+ options: [
49
+ {
50
+ value: 'inline',
51
+ icon: markRaw(DisplayInline),
52
+ tooltip: '内联布局 inline',
53
+ },
54
+ {
55
+ value: 'flex',
56
+ icon: markRaw(DisplayFlex),
57
+ tooltip: '弹性布局 flex',
58
+ },
59
+ {
60
+ value: 'block',
61
+ icon: markRaw(DisplayBlock),
62
+ tooltip: '块级布局 block',
63
+ },
64
+ {
65
+ value: 'inline-block',
66
+ icon: markRaw(DisplayInlineBlock),
67
+ tooltip: '内联块布局 inline-block',
68
+ },
69
+ {
70
+ value: 'none',
71
+ icon: markRaw(DisplayNone),
72
+ tooltip: '隐藏 none',
73
+ },
74
+ ],
75
+ }),
76
+ defineFormItem({
77
+ name: 'flexDirection',
78
+ text: '主轴方向',
79
+ type: 'radioGroup',
80
+ childType: 'button',
81
+ labelWidth: '90px',
82
+ iconSize: '24px',
83
+ options: [
84
+ { value: 'row', icon: markRaw(FlexDirectionRow), tooltip: '水平方向 起点在左侧 row' },
85
+ {
86
+ value: 'row-reverse',
87
+ icon: markRaw(FlexDirectionRowReverse),
88
+ tooltip: '水平方向 起点在右侧 row-reverse',
89
+ },
90
+ {
91
+ value: 'column',
92
+ icon: markRaw(FlexDirectionColumn),
93
+ tooltip: '垂直方向 起点在上沿 column',
94
+ },
95
+ {
96
+ value: 'column-reverse',
97
+ icon: markRaw(FlexDirectionColumnReverse),
98
+ tooltip: '垂直方向 起点在下沿 column-reverse',
99
+ },
100
+ ],
101
+ display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
102
+ }),
103
+ defineFormItem({
104
+ name: 'justifyContent',
105
+ text: '主轴对齐',
106
+ type: 'radioGroup',
107
+ childType: 'button',
108
+ labelWidth: '90px',
109
+ iconSize: '24px',
110
+ options: [
111
+ { value: 'flex-start', icon: markRaw(JustifyContentFlexStart), tooltip: '左对齐 flex-start' },
112
+ { value: 'flex-end', icon: markRaw(JustifyContentFlexEnd), tooltip: '右对齐 flex-end' },
113
+ { value: 'center', icon: markRaw(JustifyContentCenter), tooltip: '居中 center' },
114
+ {
115
+ value: 'space-between',
116
+ icon: markRaw(JustifyContentSpaceBetween),
117
+ tooltip: '两端对齐 space-between',
118
+ },
119
+ {
120
+ value: 'space-around',
121
+ icon: markRaw(JustifyContentSpaceAround),
122
+ tooltip: '横向平分 space-around',
123
+ },
124
+ ],
125
+ display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
126
+ }),
127
+ defineFormItem({
128
+ name: 'alignItems',
129
+ text: '辅轴对齐',
130
+ type: 'radioGroup',
131
+ childType: 'button',
132
+ labelWidth: '90px',
133
+ iconSize: '24px',
134
+ options: [
135
+ { value: 'flex-start', icon: markRaw(AlignItemsFlexStart), tooltip: '左对齐 flex-start' },
136
+ { value: 'flex-end', icon: markRaw(AlignItemsFlexEnd), tooltip: '右对齐 flex-end' },
137
+ { value: 'center', icon: markRaw(AlignItemsCenter), tooltip: '居中 center' },
138
+ {
139
+ value: 'space-between',
140
+ icon: markRaw(AlignItemsSpaceBetween),
141
+ tooltip: '两端对齐 space-between',
142
+ },
143
+ {
144
+ value: 'space-around',
145
+ icon: markRaw(AlignItemsSpaceAround),
146
+ tooltip: '横向平分 space-around',
147
+ },
148
+ ],
149
+ display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
150
+ }),
151
+ defineFormItem({
152
+ name: 'flexWrap',
153
+ text: '换行',
154
+ type: 'radioGroup',
155
+ childType: theme !== 'magic-admin' ? 'button' : 'default',
156
+ labelWidth: '90px',
157
+ iconSize: '24px',
158
+ options: [
159
+ { value: 'nowrap', text: '不换行', tooltip: '不换行 nowrap' },
160
+ { value: 'wrap', text: '正换行', tooltip: '第一行在上方 wrap' },
161
+ { value: 'wrap-reverse', text: '逆换行', tooltip: '第一行在下方 wrap-reverse' },
162
+ ],
163
+ display: (_mForm, { model }: { model: Record<any, any> }) => model.display === 'flex',
164
+ }),
165
+ defineFormItem({
166
+ type: 'row',
167
+ items: [
168
+ {
169
+ name: 'width',
170
+ text: '宽度(px)',
171
+ labelWidth: '90px',
172
+ type: 'data-source-field-select',
173
+ fieldConfig: {
174
+ type: 'text',
175
+ },
176
+ },
177
+ ],
178
+ }),
179
+ defineFormItem({
180
+ type: 'row',
181
+ items: [
182
+ {
183
+ name: 'height',
184
+ text: '高度(px)',
185
+ labelWidth: '90px',
186
+ type: 'data-source-field-select',
187
+ fieldConfig: {
188
+ type: 'text',
189
+ },
190
+ },
191
+ ],
192
+ }),
193
+ defineFormItem({
194
+ type: 'row',
195
+ items: [
196
+ {
197
+ type: 'data-source-field-select',
198
+ text: 'overflow',
199
+ name: 'overflow',
200
+ labelWidth: '90px',
201
+ checkStrictly: false,
202
+ dataSourceFieldType: ['string'],
203
+ fieldConfig: {
204
+ type: 'select',
205
+ clearable: true,
206
+ allowCreate: true,
207
+ options: [
208
+ { text: 'visible', value: 'visible' },
209
+ { text: 'hidden', value: 'hidden' },
210
+ { text: 'clip', value: 'clip' },
211
+ { text: 'scroll', value: 'scroll' },
212
+ { text: 'auto', value: 'auto' },
213
+ { text: 'overlay', value: 'overlay' },
214
+ { text: 'initial', value: 'initial' },
215
+ ],
216
+ },
217
+ },
218
+ ],
219
+ }),
220
+ defineFormItem({
221
+ type: 'row',
222
+ items: [
223
+ {
224
+ type: 'data-source-field-select',
225
+ text: '透明度(%)',
226
+ name: 'opacity',
227
+ labelWidth: '90px',
228
+ dataSourceFieldType: ['string', 'number'],
229
+ fieldConfig: {
230
+ type: 'text',
231
+ },
232
+ },
233
+ ],
234
+ }),
235
+ ];
236
+
237
+ const positionText: Record<string, string> = {
238
+ static: '不定位',
239
+ relative: '相对定位',
240
+ absolute: '绝对定位',
241
+ fixed: '固定定位',
242
+ sticky: '粘性定位',
243
+ };
244
+
245
+ /**
246
+ * 「位置」面板配置。
247
+ *
248
+ * `values` 是当前 style 的 model:left/top/right/bottom 的 display 在求值时读取
249
+ * `values.position`,所以必须传入响应式对象本身,不能先解构。
250
+ */
251
+ export const createPositionConfig = (values: Partial<StyleSchema>): FormConfig => [
252
+ defineFormItem({
253
+ name: 'position',
254
+ text: '定位',
255
+ labelWidth: '68px',
256
+ type: 'data-source-field-select',
257
+ fieldConfig: {
258
+ type: 'select',
259
+ options: Object.keys(positionText).map((item) => ({
260
+ value: item,
261
+ text: `${item}(${positionText[item]})`,
262
+ })),
263
+ },
264
+ }),
265
+ defineFormItem({
266
+ type: 'row',
267
+ labelWidth: '68px',
268
+ display: () => values.position !== 'static',
269
+ items: [
270
+ {
271
+ name: 'left',
272
+ type: 'data-source-field-select',
273
+ text: 'left',
274
+ fieldConfig: {
275
+ type: 'text',
276
+ },
277
+ rules: [
278
+ {
279
+ typeMatch: true,
280
+ message: appendValidateSuggestion('left 应为字符串', '请参考以下示例值:"10"'),
281
+ },
282
+ ],
283
+ },
284
+ {
285
+ name: 'top',
286
+ type: 'data-source-field-select',
287
+ text: 'top',
288
+ fieldConfig: {
289
+ type: 'text',
290
+ },
291
+ rules: [
292
+ {
293
+ typeMatch: true,
294
+ message: appendValidateSuggestion('top 应为字符串', '请参考以下示例值:"10"'),
295
+ },
296
+ ],
297
+ },
298
+ ],
299
+ }),
300
+ defineFormItem({
301
+ type: 'row',
302
+ labelWidth: '68px',
303
+ display: () => values.position !== 'static',
304
+ items: [
305
+ {
306
+ name: 'right',
307
+ type: 'data-source-field-select',
308
+ text: 'right',
309
+ fieldConfig: {
310
+ type: 'text',
311
+ },
312
+ rules: [
313
+ {
314
+ typeMatch: true,
315
+ message: appendValidateSuggestion('right 应为字符串', '请参考以下示例值:"10"'),
316
+ },
317
+ ],
318
+ },
319
+ {
320
+ name: 'bottom',
321
+ type: 'data-source-field-select',
322
+ text: 'bottom',
323
+ fieldConfig: {
324
+ type: 'text',
325
+ },
326
+ rules: [
327
+ {
328
+ typeMatch: true,
329
+ message: appendValidateSuggestion('bottom 应为字符串', '请参考以下示例值:"10"'),
330
+ },
331
+ ],
332
+ },
333
+ ],
334
+ }),
335
+ defineFormItem({
336
+ labelWidth: '68px',
337
+ name: 'zIndex',
338
+ text: 'zIndex',
339
+ type: 'data-source-field-select',
340
+ fieldConfig: {
341
+ type: 'text',
342
+ },
343
+ rules: [
344
+ {
345
+ typeMatch: true,
346
+ message: appendValidateSuggestion('zIndex 应为数字', '请参考以下示例值:10'),
347
+ },
348
+ ],
349
+ }),
350
+ ];
351
+
352
+ /**
353
+ * 「背景」面板配置。
354
+ *
355
+ * 校验文案由 `@tmagic/design` 的 `appendValidateSuggestion` 生成,必须延迟到调用时
356
+ * 才执行,不能在模块顶层求值:本模块会被大量组件间接引入,而 design 层在部分场景下
357
+ * 才可用。
358
+ */
359
+ export const createBackgroundConfig = (): FormConfig => [
360
+ defineFormItem({
361
+ name: 'backgroundColor',
362
+ text: '背景色',
363
+ labelWidth: '68px',
364
+ type: 'data-source-field-select',
365
+ fieldConfig: {
366
+ type: 'colorPicker',
367
+ },
368
+ rules: [
369
+ {
370
+ typeMatch: true,
371
+ message: appendValidateSuggestion('背景色应为字符串', '请参考以下示例值:"#000000"'),
372
+ },
373
+ ],
374
+ }),
375
+ defineFormItem({
376
+ name: 'backgroundImage',
377
+ text: '背景图',
378
+ labelWidth: '68px',
379
+ type: 'data-source-field-select',
380
+ fieldConfig: {
381
+ type: 'img-upload',
382
+ } as any,
383
+ }),
384
+ defineFormItem({
385
+ name: 'backgroundSize',
386
+ text: '背景尺寸',
387
+ type: 'radioGroup',
388
+ childType: 'button',
389
+ labelWidth: '68px',
390
+ options: [
391
+ { value: 'auto', text: '默认', tooltip: '默认 auto' },
392
+ { value: 'contain', text: '等比填充', tooltip: '等比填充 contain' },
393
+ { value: 'cover', text: '等比覆盖', tooltip: '等比覆盖 cover' },
394
+ ],
395
+ rules: [
396
+ {
397
+ typeMatch: false,
398
+ },
399
+ {
400
+ validator: ({ value, callback }) => {
401
+ if (value === '' || value === null || value === undefined) {
402
+ return callback();
403
+ }
404
+
405
+ const keywords = ['auto', 'cover', 'contain', 'inherit', 'initial', 'revert', 'unset'];
406
+ // 单值:关键字 或 长度/百分比
407
+ const lengthPercent = /^-?\d+(\.\d+)?(px|em|rem|ex|ch|vw|vh|vmin|vmax|cm|mm|in|pt|pc|%)$/;
408
+ const singleValue = (v: string) => keywords.includes(v) || lengthPercent.test(v);
409
+
410
+ const str = String(value).trim();
411
+ const parts = str.split(/\s+/);
412
+
413
+ // cover / contain 不能与其他值组合
414
+ if (parts.length > 1 && (parts.includes('cover') || parts.includes('contain'))) {
415
+ return callback('cover/contain 不能与其他值组合');
416
+ }
417
+
418
+ // 多值最多两个
419
+ if (parts.length > 2) {
420
+ return callback('backgroundSize 最多支持两个值');
421
+ }
422
+
423
+ // 关键字 auto 在多值场景中允许与其他长度/百分比组合
424
+ if (parts.every((part) => singleValue(part))) {
425
+ return callback();
426
+ }
427
+
428
+ return callback('backgroundSize 值不合法');
429
+ },
430
+ },
431
+ ],
432
+ }),
433
+ defineFormItem({
434
+ name: 'backgroundRepeat',
435
+ text: '重复显示',
436
+ type: 'radioGroup',
437
+ childType: 'button',
438
+ labelWidth: '68px',
439
+ options: [
440
+ { value: 'no-repeat', icon: markRaw(BackgroundNoRepeat), tooltip: '不重复 no-repeat' },
441
+ { value: 'repeat-x', icon: markRaw(BackgroundRepeatX), tooltip: '水平方向重复 repeat-x' },
442
+ { value: 'repeat-y', icon: markRaw(BackgroundRepeatY), tooltip: '垂直方向重复 repeat-y' },
443
+ {
444
+ value: 'repeat',
445
+ icon: markRaw(BackgroundRepeat),
446
+ tooltip: '垂直和水平方向重复 repeat',
447
+ },
448
+ ],
449
+ }),
450
+ defineFormItem({
451
+ name: 'backgroundPosition',
452
+ text: '背景定位',
453
+ type: 'component',
454
+ component: BackgroundPosition,
455
+ labelWidth: '68px',
456
+ }),
457
+ ];
458
+
459
+ /**
460
+ * 「文字」面板配置。
461
+ *
462
+ * 字重校验依赖编辑器服务(数据源/代码块),由调用方把 `type-match-rules` 的
463
+ * `validateDataSourceFieldSelect` 传进来:本模块只描述配置,不直接引用服务层,
464
+ * 否则会把整条服务依赖链拖进所有引用方。
465
+ */
466
+ export const createFontConfig = (validateDataSourceField: typeof validateDataSourceFieldSelect): FormConfig => [
467
+ defineFormItem({
468
+ type: 'row',
469
+ items: [
470
+ {
471
+ labelWidth: '68px',
472
+ name: 'fontSize',
473
+ text: '字号',
474
+ type: 'data-source-field-select',
475
+ fieldConfig: {
476
+ type: 'text',
477
+ },
478
+ rules: [
479
+ {
480
+ typeMatch: true,
481
+ message: appendValidateSuggestion('字号应为字符串或数字', '请参考以下示例值:24 或 "24"'),
482
+ },
483
+ ],
484
+ },
485
+ {
486
+ labelWidth: '68px',
487
+ name: 'lineHeight',
488
+ text: '行高',
489
+ type: 'data-source-field-select',
490
+ fieldConfig: {
491
+ type: 'text',
492
+ },
493
+ },
494
+ ],
495
+ }),
496
+ defineFormItem({
497
+ name: 'fontWeight',
498
+ text: '字重',
499
+ labelWidth: '68px',
500
+ type: 'data-source-field-select',
501
+ dataSourceFieldType: ['string', 'number'],
502
+ fieldConfig: {
503
+ type: 'select',
504
+ allowCreate: true,
505
+ options: ['normal', 'bold']
506
+ .concat(
507
+ Array(7)
508
+ .fill(1)
509
+ .map((x, i) => `${i + 1}00`),
510
+ )
511
+ .map((item) => ({
512
+ value: item,
513
+ text: item,
514
+ })),
515
+ },
516
+ rules: [
517
+ {
518
+ typeMatch: false,
519
+ },
520
+ {
521
+ validator: ({ value, callback }, { config, model, prop }, mForm) => {
522
+ if (value === '' || value === null || value === undefined) {
523
+ return callback();
524
+ }
525
+
526
+ const result = validateDataSourceField(
527
+ value,
528
+ {
529
+ fieldType: 'data-source-field-select',
530
+ mForm,
531
+ props: { config, model, prop },
532
+ },
533
+ {
534
+ // 字重允许 string(含可创建项)与 number(如 700)
535
+ validatePlainValue: (plainValue) => {
536
+ if (typeof plainValue === 'string' || (typeof plainValue === 'number' && !Number.isNaN(plainValue))) {
537
+ return undefined;
538
+ }
539
+ return '字重应为字符串或数字';
540
+ },
541
+ },
542
+ );
543
+
544
+ if (result && typeof (result as Promise<string | undefined>).then === 'function') {
545
+ (result as Promise<string | undefined>).then(
546
+ (error) => callback(error),
547
+ (error) => callback(error),
548
+ );
549
+ return;
550
+ }
551
+
552
+ return callback(result);
553
+ },
554
+ },
555
+ ],
556
+ }),
557
+ defineFormItem({
558
+ labelWidth: '68px',
559
+ name: 'color',
560
+ text: '颜色',
561
+ type: 'data-source-field-select',
562
+ fieldConfig: {
563
+ type: 'colorPicker',
564
+ },
565
+ rules: [
566
+ {
567
+ typeMatch: true,
568
+ message: appendValidateSuggestion('颜色应为字符串', '请参考以下示例值:"#000000"'),
569
+ },
570
+ ],
571
+ }),
572
+ defineFormItem({
573
+ name: 'textAlign',
574
+ text: '对齐',
575
+ type: 'radioGroup',
576
+ childType: 'button',
577
+ labelWidth: '68px',
578
+ options: [
579
+ { value: 'left', icon: markRaw(AlignLeft), tooltip: '左对齐 row', text: '左对齐' },
580
+ { value: 'center', icon: markRaw(AlignCenter), tooltip: '居中对齐 center', text: '居中对齐' },
581
+ { value: 'right', icon: markRaw(AlignRight), tooltip: '右对齐 right', text: '右对齐' },
582
+ ],
583
+ }),
584
+ ];
585
+
586
+ /** 「边框与圆角」面板中圆角部分的配置,边框四向配置见 `createBorderDirectionConfig`。 */
587
+ export const borderRadiusConfig = defineFormItem({
588
+ labelWidth: '68px',
589
+ name: 'borderRadius',
590
+ text: '圆角',
591
+ type: 'data-source-field-select',
592
+ fieldConfig: {
593
+ type: 'text',
594
+ },
595
+ });
596
+
597
+ /** 「变形」面板配置。 */
598
+ export const transformConfig = defineFormItem({
599
+ name: 'transform',
600
+ items: [
601
+ {
602
+ name: 'rotate',
603
+ text: '旋转角度',
604
+ labelWidth: '68px',
605
+ type: 'data-source-field-select',
606
+ checkStrictly: false,
607
+ dataSourceFieldType: ['string', 'number'],
608
+ fieldConfig: {
609
+ type: 'text',
610
+ },
611
+ },
612
+ {
613
+ name: 'scale',
614
+ text: '缩放',
615
+ labelWidth: '68px',
616
+ type: 'data-source-field-select',
617
+ checkStrictly: false,
618
+ dataSourceFieldType: ['string', 'number'],
619
+ fieldConfig: {
620
+ type: 'text',
621
+ },
622
+ },
623
+ ],
624
+ });
625
+
626
+ /**
627
+ * 把 6 个样式面板里会向父表单注册 FormItem 的配置按 `Index.vue` 的面板顺序拼成一份。
628
+ *
629
+ * 给无渲染校验的嵌套配置用:组件侧仍按面板各自渲染,这份合成配置只描述「父表单上
630
+ * 实际会出现哪些字段」。以下两处与组件一致、不纳入:
631
+ * - Layout 里的 Box(margin/padding)是裸 input,没有 MContainer;
632
+ * - Border 子组件的四向边框 MContainer 没传 `:prop`(路径从父表单脱落,本轮不修)。
633
+ *
634
+ * `theme` 只影响 flexWrap 的 UI `childType`,不影响校验字段。
635
+ */
636
+ export const createStyleSetterConfig = (
637
+ values: Partial<StyleSchema>,
638
+ theme: string,
639
+ validateDataSourceField: typeof validateDataSourceFieldSelect,
640
+ ): FormConfig => [
641
+ ...createLayoutConfig(theme),
642
+ ...createPositionConfig(values),
643
+ ...createBackgroundConfig(),
644
+ ...createFontConfig(validateDataSourceField),
645
+ borderRadiusConfig,
646
+ transformConfig,
647
+ ];
648
+
649
+ /**
650
+ * 边框配置,按当前选中的方向拼字段名。
651
+ *
652
+ * `direction` 取 ''(四向) / 'Top' / 'Right' / 'Bottom' / 'Left',空串对应
653
+ * `borderWidth` 这类不带方向的字段。
654
+ */
655
+ export const createBorderDirectionConfig = (direction: string) =>
656
+ defineFormItem({
657
+ items: [
658
+ {
659
+ name: `border${direction}Width`,
660
+ text: '边框宽度',
661
+ labelWidth: '68px',
662
+ type: 'data-source-field-select',
663
+ fieldConfig: {
664
+ type: 'text',
665
+ },
666
+ },
667
+ {
668
+ name: `border${direction}Color`,
669
+ text: '边框颜色',
670
+ labelWidth: '68px',
671
+ type: 'data-source-field-select',
672
+ fieldConfig: {
673
+ type: 'colorPicker',
674
+ },
675
+ },
676
+ {
677
+ name: `border${direction}Style`,
678
+ text: '边框样式',
679
+ labelWidth: '68px',
680
+ type: 'data-source-field-select',
681
+ fieldConfig: {
682
+ type: 'select',
683
+ options: ['solid', 'dashed', 'dotted'].map((item) => ({
684
+ value: item,
685
+ text: item,
686
+ })),
687
+ },
688
+ },
689
+ ],
690
+ });