@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.
- package/dist/es/Form.vue_vue_type_script_setup_true_lang.js +51 -16
- package/dist/es/FormDialog.vue_vue_type_script_setup_true_lang.js +12 -5
- package/dist/es/FormDrawer.vue_vue_type_script_setup_true_lang.js +38 -12
- package/dist/es/containers/Container.vue_vue_type_script_setup_true_lang.js +23 -17
- package/dist/es/containers/GroupList.vue_vue_type_script_setup_true_lang.js +6 -5
- package/dist/es/containers/GroupListItem.vue_vue_type_script_setup_true_lang.js +41 -31
- package/dist/es/containers/Panel.vue_vue_type_script_setup_true_lang.js +16 -10
- package/dist/es/containers/table/ActionsColumn.vue_vue_type_script_setup_true_lang.js +13 -6
- package/dist/es/containers/table/Table.vue_vue_type_script_setup_true_lang.js +4 -2
- package/dist/es/containers/table/useTableColumns.js +5 -1
- package/dist/es/containers/table-group-list/TableGroupList.vue_vue_type_script_setup_true_lang.js +2 -1
- package/dist/es/fields/Cascader.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/Checkbox.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/CheckboxGroup.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/ColorPicker.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/Date.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/DateTime.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/Daterange.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/Display.vue_vue_type_script_setup_true_lang.js +5 -2
- package/dist/es/fields/DynamicField.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/Link.vue_vue_type_script_setup_true_lang.js +29 -7
- package/dist/es/fields/Number.vue_vue_type_script_setup_true_lang.js +7 -2
- package/dist/es/fields/NumberRange.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/RadioGroup.vue_vue_type_script_setup_true_lang.js +19 -9
- package/dist/es/fields/Select.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/Switch.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/Text.vue_vue_type_script_setup_true_lang.js +112 -77
- package/dist/es/fields/Textarea.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/Time.vue_vue_type_script_setup_true_lang.js +4 -1
- package/dist/es/fields/Timerange.vue_vue_type_script_setup_true_lang.js +6 -3
- package/dist/es/index.js +3 -3
- package/dist/es/plugin.js +3 -1
- package/dist/es/style.css +122 -8
- package/dist/es/submitForm.js +2 -8
- package/dist/es/utils/config.js +4 -1
- package/dist/es/utils/form.js +33 -3
- package/dist/es/utils/typeMatch.js +1 -1
- package/dist/style.css +122 -8
- package/dist/themes/magic-admin.css +499 -0
- package/dist/tmagic-form.umd.cjs +541 -296
- package/package.json +4 -4
- package/src/Form.vue +71 -18
- package/src/FormDialog.vue +14 -3
- package/src/FormDrawer.vue +40 -12
- package/src/containers/Container.vue +16 -5
- package/src/containers/GroupList.vue +1 -1
- package/src/containers/GroupListItem.vue +28 -22
- package/src/containers/Panel.vue +10 -3
- package/src/containers/table/ActionsColumn.vue +10 -4
- package/src/containers/table/Table.vue +1 -0
- package/src/containers/table/useTableColumns.ts +7 -3
- package/src/containers/table-group-list/TableGroupList.vue +7 -1
- package/src/fields/Link.vue +16 -3
- package/src/fields/Number.vue +2 -1
- package/src/fields/RadioGroup.vue +21 -6
- package/src/fields/Text.vue +43 -19
- package/src/plugin.ts +3 -0
- package/src/submitForm.ts +5 -11
- package/src/theme/container.scss +3 -0
- package/src/theme/fieldset.scss +51 -0
- package/src/theme/form.scss +6 -0
- package/src/theme/group-list.scss +9 -7
- package/src/theme/index.scss +1 -0
- package/src/theme/link.scss +17 -0
- package/src/theme/panel.scss +1 -1
- package/src/theme/radio-group.scss +23 -0
- package/src/theme/tabs.scss +2 -2
- package/src/theme/text.scss +38 -0
- package/src/theme/themes/magic-admin/index.scss +95 -0
- package/src/utils/config.ts +7 -1
- package/src/utils/form.ts +37 -0
- package/types/index.d.ts +64 -3
package/src/fields/Link.vue
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<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
|
|
package/src/fields/Number.vue
CHANGED
|
@@ -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
|
|
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
|
|
13
|
-
|
|
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 '
|
|
65
|
+
return '14';
|
|
51
66
|
}
|
|
52
67
|
if (props.size === 'large') {
|
|
53
68
|
return '16';
|
|
54
69
|
}
|
|
55
|
-
return '
|
|
70
|
+
return '16';
|
|
56
71
|
});
|
|
57
72
|
</script>
|
package/src/fields/Text.vue
CHANGED
|
@@ -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: #
|
|
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
|
-
|
|
109
|
+
const { append } = props.config;
|
|
110
|
+
|
|
111
|
+
// 字符串形态:纯文本 append。
|
|
112
|
+
if (typeof append === 'string') {
|
|
84
113
|
return {
|
|
85
114
|
type: 'text',
|
|
86
|
-
text:
|
|
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
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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 {
|
package/src/theme/container.scss
CHANGED
package/src/theme/fieldset.scss
CHANGED
|
@@ -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);
|
package/src/theme/form.scss
CHANGED
|
@@ -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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
}
|
package/src/theme/index.scss
CHANGED
package/src/theme/link.scss
CHANGED
|
@@ -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
|
+
}
|
package/src/theme/panel.scss
CHANGED
|
@@ -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
|
+
}
|
package/src/theme/tabs.scss
CHANGED
package/src/theme/text.scss
CHANGED
|
@@ -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
|
+
}
|
package/src/utils/config.ts
CHANGED
|
@@ -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;
|