@tmagic/form 1.8.0-beta.13 → 1.8.0-beta.14

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 (72) hide show
  1. package/dist/es/Form.vue_vue_type_script_setup_true_lang.js +51 -16
  2. package/dist/es/FormDialog.vue_vue_type_script_setup_true_lang.js +12 -5
  3. package/dist/es/FormDrawer.vue_vue_type_script_setup_true_lang.js +38 -12
  4. package/dist/es/containers/Container.vue_vue_type_script_setup_true_lang.js +23 -17
  5. package/dist/es/containers/GroupList.vue_vue_type_script_setup_true_lang.js +6 -5
  6. package/dist/es/containers/GroupListItem.vue_vue_type_script_setup_true_lang.js +41 -31
  7. package/dist/es/containers/Panel.vue_vue_type_script_setup_true_lang.js +16 -10
  8. package/dist/es/containers/table/ActionsColumn.vue_vue_type_script_setup_true_lang.js +13 -6
  9. package/dist/es/containers/table/Table.vue_vue_type_script_setup_true_lang.js +4 -2
  10. package/dist/es/containers/table/useTableColumns.js +5 -1
  11. package/dist/es/containers/table-group-list/TableGroupList.vue_vue_type_script_setup_true_lang.js +2 -1
  12. package/dist/es/fields/Cascader.vue_vue_type_script_setup_true_lang.js +5 -2
  13. package/dist/es/fields/Checkbox.vue_vue_type_script_setup_true_lang.js +4 -1
  14. package/dist/es/fields/CheckboxGroup.vue_vue_type_script_setup_true_lang.js +5 -2
  15. package/dist/es/fields/ColorPicker.vue_vue_type_script_setup_true_lang.js +4 -1
  16. package/dist/es/fields/Date.vue_vue_type_script_setup_true_lang.js +5 -2
  17. package/dist/es/fields/DateTime.vue_vue_type_script_setup_true_lang.js +5 -2
  18. package/dist/es/fields/Daterange.vue_vue_type_script_setup_true_lang.js +5 -2
  19. package/dist/es/fields/Display.vue_vue_type_script_setup_true_lang.js +5 -2
  20. package/dist/es/fields/DynamicField.vue_vue_type_script_setup_true_lang.js +4 -1
  21. package/dist/es/fields/Link.vue_vue_type_script_setup_true_lang.js +29 -7
  22. package/dist/es/fields/Number.vue_vue_type_script_setup_true_lang.js +7 -2
  23. package/dist/es/fields/NumberRange.vue_vue_type_script_setup_true_lang.js +4 -1
  24. package/dist/es/fields/RadioGroup.vue_vue_type_script_setup_true_lang.js +19 -9
  25. package/dist/es/fields/Select.vue_vue_type_script_setup_true_lang.js +4 -1
  26. package/dist/es/fields/Switch.vue_vue_type_script_setup_true_lang.js +4 -1
  27. package/dist/es/fields/Text.vue_vue_type_script_setup_true_lang.js +112 -77
  28. package/dist/es/fields/Textarea.vue_vue_type_script_setup_true_lang.js +4 -1
  29. package/dist/es/fields/Time.vue_vue_type_script_setup_true_lang.js +4 -1
  30. package/dist/es/fields/Timerange.vue_vue_type_script_setup_true_lang.js +6 -3
  31. package/dist/es/index.js +3 -3
  32. package/dist/es/plugin.js +3 -1
  33. package/dist/es/style.css +122 -8
  34. package/dist/es/submitForm.js +2 -8
  35. package/dist/es/utils/config.js +4 -1
  36. package/dist/es/utils/form.js +33 -3
  37. package/dist/es/utils/typeMatch.js +1 -1
  38. package/dist/style.css +122 -8
  39. package/dist/themes/magic-admin.css +499 -0
  40. package/dist/tmagic-form.umd.cjs +541 -296
  41. package/package.json +4 -4
  42. package/src/Form.vue +71 -18
  43. package/src/FormDialog.vue +14 -3
  44. package/src/FormDrawer.vue +40 -12
  45. package/src/containers/Container.vue +16 -5
  46. package/src/containers/GroupList.vue +1 -1
  47. package/src/containers/GroupListItem.vue +28 -22
  48. package/src/containers/Panel.vue +10 -3
  49. package/src/containers/table/ActionsColumn.vue +10 -4
  50. package/src/containers/table/Table.vue +1 -0
  51. package/src/containers/table/useTableColumns.ts +7 -3
  52. package/src/containers/table-group-list/TableGroupList.vue +7 -1
  53. package/src/fields/Link.vue +16 -3
  54. package/src/fields/Number.vue +2 -1
  55. package/src/fields/RadioGroup.vue +21 -6
  56. package/src/fields/Text.vue +43 -19
  57. package/src/plugin.ts +3 -0
  58. package/src/submitForm.ts +5 -11
  59. package/src/theme/container.scss +3 -0
  60. package/src/theme/fieldset.scss +51 -0
  61. package/src/theme/form.scss +6 -0
  62. package/src/theme/group-list.scss +9 -7
  63. package/src/theme/index.scss +1 -0
  64. package/src/theme/link.scss +17 -0
  65. package/src/theme/panel.scss +1 -1
  66. package/src/theme/radio-group.scss +23 -0
  67. package/src/theme/tabs.scss +2 -2
  68. package/src/theme/text.scss +38 -0
  69. package/src/theme/themes/magic-admin/index.scss +95 -0
  70. package/src/utils/config.ts +7 -1
  71. package/src/utils/form.ts +37 -0
  72. package/types/index.d.ts +64 -3
@@ -1,5 +1,16 @@
1
1
  <template>
2
- <a v-if="config.href && !disabled" target="_blank" :href="href" :style="config.css || {}">{{ displayText }}</a>
2
+ <a
3
+ target="_blank"
4
+ :href="href"
5
+ v-if="isGlobalFlat && config.href && !disabled"
6
+ class="magic-admin-link"
7
+ :style="config.css || {}"
8
+ >
9
+ <TMagicButton link type="primary">
10
+ <TMagicIcon><Notebook /></TMagicIcon>{{ displayText }}
11
+ </TMagicButton>
12
+ </a>
13
+ <a v-else-if="config.href && !disabled" target="_blank" :href="href" :style="config.css || {}">{{ displayText }}</a>
3
14
  <span v-else-if="config.href && disabled" :style="config.disabledCss || {}">{{ displayText }}</span>
4
15
  <div v-else class="m-fields-link">
5
16
  <TMagicButton link type="primary" @click="editHandler">点击编辑</TMagicButton>
@@ -18,18 +29,20 @@
18
29
 
19
30
  <script lang="ts" setup>
20
31
  import { computed, inject, readonly, ref } from 'vue';
32
+ import { Notebook } from '@element-plus/icons-vue';
21
33
 
22
- import { TMagicButton } from '@tmagic/design';
34
+ import { TMagicButton, TMagicIcon } from '@tmagic/design';
23
35
 
24
36
  import FormDialog from '../FormDialog.vue';
25
37
  import type { FieldProps, FormState, LinkConfig } from '../schema';
38
+ import { isGlobalFlat } from '../utils/config';
26
39
  import { useAddField } from '../utils/useAddField';
27
40
 
28
41
  defineOptions({
29
42
  name: 'MFormLink',
30
43
  });
31
44
 
32
- const props = defineProps<FieldProps<LinkConfig>>();
45
+ const props = defineProps<FieldProps<LinkConfig> & { theme?: string }>();
33
46
 
34
47
  const emit = defineEmits(['change']);
35
48
 
@@ -3,7 +3,7 @@
3
3
  v-if="model"
4
4
  v-model="value"
5
5
  clearable
6
- controls-position="right"
6
+ :controls-position="isGlobalFlat ? '' : 'right'"
7
7
  :size="size"
8
8
  :max="config.max"
9
9
  :min="config.min"
@@ -21,6 +21,7 @@ import { inject, ref, watch } from 'vue';
21
21
  import { TMagicInputNumber } from '@tmagic/design';
22
22
 
23
23
  import type { FieldProps, FormState, NumberConfig } from '../schema';
24
+ import { isGlobalFlat } from '../utils/config';
24
25
  import { useAddField } from '../utils/useAddField';
25
26
 
26
27
  defineOptions({
@@ -1,5 +1,11 @@
1
1
  <template>
2
- <TMagicRadioGroup v-if="model" :model-value="model[name]" :size="size" :disabled="disabled">
2
+ <TMagicRadioGroup
3
+ v-if="model"
4
+ :model-value="model[name]"
5
+ :size="size"
6
+ :disabled="disabled"
7
+ class="icon-{{ iconSize }}"
8
+ >
3
9
  <component
4
10
  v-for="option in config.options"
5
11
  :is="itemComponent"
@@ -8,9 +14,14 @@
8
14
  @click="clickHandler(option.value)"
9
15
  >
10
16
  <TMagicTooltip :disabled="!Boolean(option.tooltip)" placement="top-start" :content="option.tooltip">
11
- <div>
12
- <TMagicIcon v-if="option.icon" :size="iconSize"><component :is="option.icon"></component></TMagicIcon>
13
- <span v-if="option.text">{{ option.text }}</span>
17
+ <div class="m-fields-radio-group__option">
18
+ <TMagicIcon
19
+ v-if="option.icon"
20
+ :size="iconSize"
21
+ :class="{ 'm-fields-radio-group__icon_with_text': !!option.text }"
22
+ ><component :is="option.icon"></component
23
+ ></TMagicIcon>
24
+ <span v-if="option.text" class="m-fields-radio-group__text">{{ option.text }}</span>
14
25
  </div>
15
26
  </TMagicTooltip>
16
27
  </component>
@@ -45,13 +56,17 @@ const clickHandler = (item: string | number | boolean) => {
45
56
 
46
57
  useAddField(props.prop);
47
58
 
59
+ // 这里换了设计稿里的图标,所以需要调整一下图标大小
48
60
  const iconSize = computed(() => {
61
+ if (props.config.iconSize) {
62
+ return props.config.iconSize;
63
+ }
49
64
  if (props.size === 'small') {
50
- return '12';
65
+ return '14';
51
66
  }
52
67
  if (props.size === 'large') {
53
68
  return '16';
54
69
  }
55
- return '14';
70
+ return '16';
56
71
  });
57
72
  </script>
@@ -1,6 +1,31 @@
1
1
  <template>
2
- <div class="m-fields-text">
2
+ <div :class="['m-fields-text', { 'm-fields-text--static': config.static }]">
3
+ <div v-if="config.disabled"></div>
4
+ <!-- 新增静态展示文本样式 -->
5
+ <template v-if="config.static">
6
+ <span v-if="config.prepend" class="m-fields-text__prepend">{{ config.prepend }}</span>
7
+ <span class="m-fields-text__value">{{ value }}</span>
8
+ <template v-if="appendConfig">
9
+ <TMagicButton
10
+ v-if="appendConfig.type === 'button'"
11
+ style="color: #0056ea"
12
+ :size="size"
13
+ @click.prevent="buttonClickHandler"
14
+ >
15
+ {{ appendConfig.text }}
16
+ </TMagicButton>
17
+ <img
18
+ v-else-if="appendConfig.type === 'icon'"
19
+ class="m-fields-text__icon"
20
+ :src="appendConfig.text"
21
+ @click="buttonClickHandler"
22
+ />
23
+ <span v-else>{{ appendConfig.text }}</span>
24
+ </template>
25
+ </template>
26
+
3
27
  <TMagicInput
28
+ v-else
4
29
  v-model="value"
5
30
  ref="input"
6
31
  :clearable="config.clearable ?? true"
@@ -17,17 +42,18 @@
17
42
  <template #append v-if="appendConfig">
18
43
  <TMagicButton
19
44
  v-if="appendConfig.type === 'button'"
20
- style="color: #409eff"
45
+ style="color: #0056ea"
21
46
  :size="size"
22
47
  @click.prevent="buttonClickHandler"
23
48
  >
24
49
  {{ appendConfig.text }}
25
50
  </TMagicButton>
51
+ <img v-else-if="appendConfig.type === 'icon'" :src="appendConfig.text" @click="buttonClickHandler" />
26
52
  <span v-else>{{ appendConfig.text }}</span>
27
53
  </template>
28
54
  </TMagicInput>
29
55
 
30
- <Teleport to="body">
56
+ <Teleport to="body" v-if="!config.static">
31
57
  <div v-if="popoverVisible" class="tmagic-form-text-popper m-form-item__content" ref="popoverEl">
32
58
  <div class="m-form-validate__warning">输入内容前后有空格,是否移除空格?</div>
33
59
  <div style="display: flex; justify-content: flex-end">
@@ -80,28 +106,26 @@ watch(
80
106
  );
81
107
 
82
108
  const appendConfig = computed(() => {
83
- if (typeof props.config.append === 'string') {
109
+ const { append } = props.config;
110
+
111
+ // 字符串形态:纯文本 append。
112
+ if (typeof append === 'string') {
84
113
  return {
85
114
  type: 'text',
86
- text: props.config.append,
115
+ text: append,
87
116
  handler: undefined,
88
117
  };
89
118
  }
90
- if (typeof props.config.append === 'object') {
91
- if (typeof props.config.append?.handler === 'function') {
92
- return {
93
- type: 'button',
94
- text: props.config.append.text,
95
- handler: props.config.append.handler,
96
- };
97
- }
98
- if (props.config.append) {
99
- if (props.config.append.value === 0) {
100
- return false;
101
- }
102
119
 
103
- return props.config.append;
104
- }
120
+ // 对象形态:按 schema 定义,`type` 可为 `'button' | 'icon'`,`handler` 可选。
121
+ //
122
+ // 旧逻辑里只要带 handler 就强制覆盖为 `type: 'button'`,导致 schema 中显式传入的
123
+ // `type: 'icon'` 在到达模板前被抹掉(参见 `@editor/utils/props.ts` 中 ID 字段
124
+ // 的复制按钮配置)。这里直接返回原对象,模板按 `type` 分支渲染 button / icon / text。
125
+ if (typeof append === 'object' && append) {
126
+ // `value === 0` 沿用旧约定:作为「隐藏 append」的开关。
127
+ if (append.value === 0) return false;
128
+ return append;
105
129
  }
106
130
 
107
131
  return false;
package/src/plugin.ts CHANGED
@@ -50,11 +50,13 @@ import { setConfig } from './utils/config';
50
50
  import { registerTypeMatchRules, type TypeMatchValidator } from './utils/typeMatch';
51
51
  import Form from './Form.vue';
52
52
  import FormDialog from './FormDialog.vue';
53
+ import FormDrawer from './FormDrawer.vue';
53
54
 
54
55
  import './theme/index.scss';
55
56
 
56
57
  // #region FormInstallOptions
57
58
  export interface FormInstallOptions {
59
+ flat?: boolean;
58
60
  /** 自定义字段 type 的 typeMatch 校验规则,可覆盖内置规则或扩展业务字段 */
59
61
  typeMatchRules?: Record<string, TypeMatchValidator>;
60
62
  [key: string]: any;
@@ -76,6 +78,7 @@ export default {
76
78
 
77
79
  app.component('m-form', Form);
78
80
  app.component('m-form-dialog', FormDialog);
81
+ app.component('m-form-drawer', FormDrawer);
79
82
  app.component('m-form-container', Container);
80
83
  app.component('m-form-fieldset', Fieldset);
81
84
  app.component('m-form-group-list', TableGroupList);
package/src/submitForm.ts CHANGED
@@ -18,6 +18,7 @@
18
18
 
19
19
  import { type AppContext, type Component, createApp, defineComponent, h, nextTick, type Ref, ref, watch } from 'vue';
20
20
 
21
+ import { applyExtendState } from './utils/form';
21
22
  import Form from './Form.vue';
22
23
  import type { ChangeRecord, FormConfig, FormState } from './schema';
23
24
 
@@ -192,17 +193,10 @@ const mountFormInstance = <T>(options: MountFormInstanceOptions<T>): Promise<T>
192
193
  console.error('[MForm] extendState failed:', e);
193
194
  return;
194
195
  }
195
- const apply = (state: Record<string, any> | null | undefined) => {
196
- if (!state) return;
197
- for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(state))) {
198
- if ('value' in descriptor) {
199
- (form.formState as any)[key] = (state as any)[key];
200
- } else {
201
- descriptor.configurable = true;
202
- Object.defineProperty(form.formState, key, descriptor);
203
- }
204
- }
205
- };
196
+ // 合并逻辑收口在 applyExtendState:props 派生的只读 getter 字段
197
+ // (keyProp 等)以普通字段形式返回时会被跳过并告警,避免 proxy set 抛错
198
+ const apply = (state: Record<string, any> | null | undefined) =>
199
+ applyExtendState(form.formState, state);
206
200
  if (result && typeof result.then === 'function') {
207
201
  result.then(apply, (e: any) => console.error('[MForm] extendState failed:', e));
208
202
  } else {
@@ -22,4 +22,7 @@
22
22
  background: #f7dadd;
23
23
  }
24
24
  }
25
+ .m-form-title-extra {
26
+ margin-left: 8px;
27
+ }
25
28
  }
@@ -1,3 +1,54 @@
1
+ .m-container-fieldset {
2
+ &.no-border-fieldset {
3
+ &.fieldset-in-row {
4
+ > fieldset.m-fieldset {
5
+ legend {
6
+ left: 0;
7
+ }
8
+ }
9
+ }
10
+ > fieldset.m-fieldset {
11
+ border: 0;
12
+ legend {
13
+ position: relative;
14
+ left: -5px;
15
+ font-size: 16px;
16
+ font-weight: 500;
17
+ line-height: 24px;
18
+ padding: 0;
19
+ }
20
+ }
21
+ }
22
+ &.fieldset-in-card-fit {
23
+ border-bottom: 1px solid #e6e6e6;
24
+ > fieldset.m-fieldset {
25
+ padding: 16px 0 16px 0 !important;
26
+ border: 0;
27
+ margin-top: 0;
28
+ margin-bottom: 0;
29
+ legend {
30
+ position: relative;
31
+ top: 10px;
32
+ background-color: transparent;
33
+ }
34
+ }
35
+ &.fieldset-in-card-fit-last {
36
+ padding-bottom: 0 !important;
37
+ border-bottom: 0;
38
+ }
39
+ &.fieldset-in-card-fit-first {
40
+ padding-top: 0 !important;
41
+ }
42
+ &:last-child {
43
+ padding-bottom: 0 !important;
44
+ border-bottom: 0;
45
+ }
46
+ &:first {
47
+ padding-top: 0 !important;
48
+ }
49
+ }
50
+ }
51
+
1
52
  fieldset.m-fieldset {
2
53
  position: relative;
3
54
  border: 1px solid rgb(229, 229, 229);
@@ -53,3 +53,9 @@
53
53
  margin-bottom: var(--td-comp-margin-xxl);
54
54
  }
55
55
  }
56
+
57
+ .m-form-container {
58
+ .m-form-container-expand {
59
+ text-align: center;
60
+ }
61
+ }
@@ -1,4 +1,8 @@
1
1
  .m-fields-group-list {
2
+ .m-fields-group-list-item.tmagic-design-card--flat:last-child {
3
+ border: 0;
4
+ }
5
+
2
6
  .el-button--text {
3
7
  padding: 0;
4
8
  margin-bottom: 7px;
@@ -19,14 +23,11 @@
19
23
  &:last-of-type {
20
24
  border-bottom: 0;
21
25
  }
22
- }
23
26
 
24
- .tmagic-design-card {
25
- .el-card__header {
26
- padding: 5px 20px;
27
- }
28
- .t-card__header {
29
- padding: 5px 0;
27
+ .m-fields-group-list-item-header {
28
+ display: flex;
29
+ align-items: center;
30
+ gap: 8px;
30
31
  }
31
32
  }
32
33
 
@@ -34,5 +35,6 @@
34
35
  display: flex;
35
36
  justify-content: space-between;
36
37
  margin-top: 10px;
38
+ margin-bottom: 16px;
37
39
  }
38
40
  }
@@ -13,3 +13,4 @@
13
13
  @use "./tabs.scss";
14
14
  @use "./number-range.scss";
15
15
  @use "./form-box.scss";
16
+ @use "./radio-group.scss";
@@ -1,3 +1,20 @@
1
1
  div.m-fields-link {
2
2
  width: fit-content;
3
3
  }
4
+ a.magic-admin-link {
5
+ width: calc(100%);
6
+ box-sizing: border-box;
7
+ border-radius: 4px;
8
+ border: 1px solid #e6e6e6;
9
+ background-color: #fafafa;
10
+ padding: 0 8px;
11
+ height: 28px;
12
+ display: inline-flex;
13
+ gap: 4px;
14
+ align-items: center;
15
+ text-decoration: none;
16
+
17
+ i {
18
+ margin-right: 4px;
19
+ }
20
+ }
@@ -4,7 +4,7 @@
4
4
  background: #f2f6fc;
5
5
  }
6
6
  a {
7
- color: #409eff;
7
+ color: var(--el-color-primary, #409eff);
8
8
  }
9
9
  }
10
10
 
@@ -0,0 +1,23 @@
1
+ // 单个 radio option 的内容容器:横向排列 icon + 文本,垂直居中对齐。
2
+ // 这样多行文本(`text: '内联布局\n inline'`)能跟左侧 icon 在视觉中线对齐,
3
+ // 不会出现 icon 顶到第一行的错位。
4
+ .m-fields-radio-group__option {
5
+ display: flex;
6
+ align-items: center;
7
+ }
8
+
9
+ .m-fields-radio-group__text {
10
+ // `inline-block` 关键:inline span 的 line-height 会被父级 inline formatting
11
+ // context(如 `.el-radio-button__inner` 的 strut)覆盖,外观高度量出来会比
12
+ // `line-height` 大;显式声明 `inline-block` 让 span 有独立的格式化上下文,
13
+ // 自身 line-height 才真正生效。
14
+ display: inline-block;
15
+ vertical-align: middle;
16
+ // `pre-line` 让 `\n` 渲染为换行,同时保留自动折行。
17
+ white-space: pre-line;
18
+ line-height: 20px;
19
+ }
20
+
21
+ .m-fields-radio-group__icon_with_text {
22
+ margin-right: 8px;
23
+ }
@@ -5,8 +5,8 @@
5
5
  margin-right: auto;
6
6
  min-width: 50px;
7
7
  outline: none;
8
- border-color: #409eff;
9
- color: #409eff;
8
+ border-color: var(--el-color-primary, #409eff);
9
+ color: var(--el-color-primary, #409eff);
10
10
  width: 10px;
11
11
  &::before {
12
12
  content: "添加";
@@ -2,6 +2,30 @@
2
2
  display: flex;
3
3
  align-items: center;
4
4
  width: 100%;
5
+
6
+ // 纯文本(非输入框)形态:保留 prepend / append 槽位的横向布局。
7
+ // 配合 `TextConfig.static = true` 使用,常见于 ID 之类「只读 + 操作 icon」场景。
8
+ &--static {
9
+ gap: 6px;
10
+ min-height: 32px;
11
+
12
+ .m-fields-text__prepend {
13
+ color: var(--el-text-color-secondary, #909399);
14
+ }
15
+
16
+ .m-fields-text__value {
17
+ color: var(--el-text-color-regular, #606266);
18
+ overflow: hidden;
19
+ text-overflow: ellipsis;
20
+ white-space: nowrap;
21
+ }
22
+
23
+ .m-fields-text__icon {
24
+ width: 16px;
25
+ height: 16px;
26
+ cursor: pointer;
27
+ }
28
+ }
5
29
  }
6
30
 
7
31
  .m-form-validate__warning {
@@ -29,6 +53,20 @@
29
53
  }
30
54
  }
31
55
 
56
+ .m-container-text {
57
+ &.m-fields-title-container {
58
+ .tmagic-design-form-item {
59
+ .el-form-item__label {
60
+ color: #0f1113;
61
+ font-weight: 500;
62
+ font-size: 16px;
63
+ }
64
+ line-height: 24px;
65
+ margin-bottom: 16px;
66
+ }
67
+ }
68
+ }
69
+
32
70
  .tmagic-form-text-popper[data-popper-placement^="top"]
33
71
  > .tmagic-form-text-popper-arrow {
34
72
  bottom: -4px;
@@ -0,0 +1,95 @@
1
+ // magic-admin 主题:所有规则均以 `.m-form.m-form--magic-admin` 为前缀,
2
+ // 仅在 `<MForm theme="magic-admin" />` 时生效,避免污染默认样式。
3
+ @use "../../index.scss";
4
+
5
+ .m-form.m-form--magic-admin {
6
+ // 精确匹配"只挂 `m-form-container` + `m-container-fieldset` 这两个 class"的元素:
7
+ // `.a.b` 只能保证"包含"这两个 class,多余的 class 也会命中;
8
+ // 用 `[class="..."]` 做全量字符串等值匹配即可排除第三个 class 的情况。
9
+ // 由于 Vue/框架不保证 class 拼接顺序,两种顺序都列出以覆盖到位。
10
+ [class="m-form-container m-container-fieldset"],
11
+ [class="m-container-fieldset m-form-container"] {
12
+ margin-top: 15px;
13
+ }
14
+
15
+ .m-container-tab:not(.magic-right-panel-tabs-top):not(
16
+ .magic-form-dynamic-tab
17
+ ) {
18
+ > .tmagic-design-tabs:not(.el-tabs--border-card) {
19
+ > .el-tabs__content {
20
+ background-color: #fff;
21
+ padding: 16px 16px 0 16px;
22
+ border-radius: 4px;
23
+ }
24
+ }
25
+ }
26
+
27
+ .m-container-tab.magic-form-dynamic-tab {
28
+ }
29
+
30
+ .m-form-tip {
31
+ color: rgba(0, 0, 0, 0.55);
32
+ }
33
+
34
+ .tmagic-design-form-item {
35
+ &.show-after-diff {
36
+ background: #d4edbc;
37
+ }
38
+
39
+ &.show-before-diff {
40
+ background: #fad2cf;
41
+ }
42
+ }
43
+ .m-fields-radio-group__text {
44
+ // `pre-line` 让 `\n` 渲染为换行,同时保留自动折行;线高与默认按钮组高度对齐。
45
+ white-space: pre-line;
46
+ line-height: 20px;
47
+ }
48
+ .el-form-item__content {
49
+ .tmagic-design-radio-group {
50
+ .tmagic-design-radio-button {
51
+ height: 32px;
52
+ .el-radio-button__inner {
53
+ height: 32px;
54
+ display: flex;
55
+ padding-top: 0;
56
+ padding-bottom: 0;
57
+ align-items: center;
58
+ }
59
+ }
60
+ }
61
+ }
62
+ .m-fields-data-source-field-select .tmagic-design-button {
63
+ margin-left: 8px;
64
+ span {
65
+ font-size: 16px;
66
+ }
67
+ }
68
+
69
+ .m-fields-group-list-item.tmagic-design-card--flat {
70
+ .el-card__body {
71
+ .m-fields-table-wrap {
72
+ .m-fields-table {
73
+ .el-table__header-wrapper {
74
+ thead {
75
+ height: 48px;
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+ .m-fields-group-list-item-header {
82
+ position: relative;
83
+ .delete-button {
84
+ position: absolute;
85
+ right: 0;
86
+ color: #0f1113;
87
+ }
88
+ }
89
+ }
90
+ .m-form-container {
91
+ .m-form-container-expand {
92
+ text-align: left;
93
+ }
94
+ }
95
+ }
@@ -17,11 +17,17 @@
17
17
  */
18
18
 
19
19
  import type { Component } from 'vue';
20
+ import { ref } from 'vue';
20
21
 
21
22
  let $MAGIC_FORM = {} as any;
23
+ // 用 ref 持有 flat 全局开关:const 引用本身不可变,符合 `import/no-mutable-exports`;
24
+ // 通过 `.value` 改值仍保持模块级响应式语义,与 editor/plugin.ts、design/index.ts 里
25
+ // 的同名变量写法对齐。
26
+ const isGlobalFlat = ref(false);
22
27
 
23
28
  const setConfig = (option: any): void => {
24
29
  $MAGIC_FORM = option;
30
+ isGlobalFlat.value = option.flat ?? false;
25
31
  };
26
32
 
27
33
  const getConfig = <T = unknown>(key: string): T => $MAGIC_FORM[key];
@@ -39,4 +45,4 @@ const getField = (tagName: string): Component | undefined => fieldRegistry.get(t
39
45
 
40
46
  const deleteField = (tagName: string): boolean => fieldRegistry.delete(tagName);
41
47
 
42
- export { deleteField, getConfig, getField, registerField, setConfig };
48
+ export { deleteField, getConfig, getField, isGlobalFlat, registerField, setConfig };
package/src/utils/form.ts CHANGED
@@ -441,6 +441,43 @@ export const sortChange = (data: any[], { prop, order }: SortProp) => {
441
441
  }
442
442
  };
443
443
 
444
+ /**
445
+ * 将 extendState 返回的扩展字段合并进 formState。
446
+ *
447
+ * - data 描述符(普通字段)通过 `formState[key] = value` 写入,走 reactive proxy 的 set,
448
+ * 触发依赖通知;
449
+ * - accessor 描述符(`{ get stage() { return ... } }`)按原样 defineProperty,调用方
450
+ * 可控制读时求值;强制 `configurable: true` 以便下一次合并可再 define。
451
+ *
452
+ * 注意:formState 上由 props 派生的字段(keyProp / popperClass / config / initValues /
453
+ * isCompare / lastValues / parentValues)是只读 getter(无 setter),extendState 若以
454
+ * 普通字段形式返回同名 key,直接赋值会让 proxy 的 set trap 失败并抛出
455
+ * `TypeError: 'set' on proxy: trap returned falsish`,这里统一跳过并告警;
456
+ * 如确需覆盖,可在 extendState 中以 get 访问器形式返回。
457
+ */
458
+ export const applyExtendState = (formState: FormState, state: Record<string, any> | null | undefined): void => {
459
+ if (!state) return;
460
+
461
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(state))) {
462
+ if (!('value' in descriptor)) {
463
+ descriptor.configurable = true;
464
+ Object.defineProperty(formState, key, descriptor);
465
+ continue;
466
+ }
467
+
468
+ const targetDescriptor = Object.getOwnPropertyDescriptor(formState, key);
469
+ if (targetDescriptor && !('value' in targetDescriptor) && typeof targetDescriptor.set !== 'function') {
470
+ console.warn(
471
+ `[MForm] extendState: "${key}" is a read-only field derived from props and cannot be assigned a plain value. ` +
472
+ 'Return it as a getter accessor if you really need to override it.',
473
+ );
474
+ continue;
475
+ }
476
+
477
+ (formState as any)[key] = (state as any)[key];
478
+ }
479
+ };
480
+
444
481
  export const createObjectProp = (prop: string, key: string, name?: string | number) => {
445
482
  if (prop === '') {
446
483
  return key;