mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-09-13 23:48:43 +00:00
feat(form): 容器透传 labelPosition
容器未单独配置时回落到父级/表单级,子项自身配置优先。
This commit is contained in:
parent
93dc0d2187
commit
c34ed6aafc
@ -74,7 +74,7 @@ const props = defineProps<{
|
||||
disabledShowSrc?: boolean;
|
||||
labelWidth?: string;
|
||||
codeValueKey?: string;
|
||||
labelPosition?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
||||
}>();
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
:last-values="lastValuesProcessed"
|
||||
:is-compare="isCompare"
|
||||
:label-width="item.labelWidth || labelWidth"
|
||||
:label-position="item.labelPosition || labelPosition"
|
||||
:step-active="stepActive"
|
||||
:size="size"
|
||||
@change="changeHandler"
|
||||
@ -92,7 +93,7 @@ const props = withDefaults(
|
||||
stepActive?: string | number;
|
||||
size?: 'small' | 'default' | 'large';
|
||||
inline?: boolean;
|
||||
labelPosition?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
keyProp?: string;
|
||||
popperClass?: string;
|
||||
preventSubmitDefault?: boolean;
|
||||
|
||||
@ -66,7 +66,7 @@ const props = withDefaults(
|
||||
size?: 'small' | 'default' | 'large';
|
||||
confirmText?: string;
|
||||
inline?: boolean;
|
||||
labelPosition?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
preventSubmitDefault?: boolean;
|
||||
/** 透传给内部 `MForm`,控制表单校验失败时错误提示前缀是否使用字段的 text 文案 */
|
||||
useFieldTextInError?: boolean;
|
||||
|
||||
@ -94,7 +94,7 @@ const props = withDefaults(
|
||||
disabled?: boolean;
|
||||
title?: string;
|
||||
inline?: boolean;
|
||||
labelPosition?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
zIndex?: number;
|
||||
size?: 'small' | 'default' | 'large';
|
||||
confirmText?: string;
|
||||
|
||||
@ -88,7 +88,7 @@ const props = withDefaults(
|
||||
size?: 'small' | 'default' | 'large';
|
||||
confirmText?: string;
|
||||
inline?: boolean;
|
||||
labelPosition?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
preventSubmitDefault?: boolean;
|
||||
/** 透传给内部 `MForm`,控制表单校验失败时错误提示前缀是否使用字段的 text 文案 */
|
||||
useFieldTextInError?: boolean;
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
:config="config"
|
||||
:prop="prop"
|
||||
:label-width="config.labelWidth || labelWidth"
|
||||
:label-position="config.labelPosition || labelPosition"
|
||||
:expand-more="expandMore"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
@ -37,6 +38,7 @@ const props = defineProps<{
|
||||
isCompare?: boolean;
|
||||
config: FormItemConfig;
|
||||
labelWidth?: string | number;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
expandMore?: boolean;
|
||||
span?: number;
|
||||
size?: string;
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
:step-active="stepActive"
|
||||
:expand-more="expand"
|
||||
:label-width="itemLabelWidth"
|
||||
:label-position="itemLabelPosition"
|
||||
:style="config.fieldStyle"
|
||||
@change="onChangeHandler"
|
||||
@addDiffCount="onAddDiffCount"
|
||||
@ -35,7 +36,7 @@
|
||||
:label-title="config.labelTitle"
|
||||
:text="text"
|
||||
></FormLabel>
|
||||
<span class="m-form-tip m-form-title-extra" v-if="config.titleExtra && config.labelPosition === 'top'">
|
||||
<span class="m-form-tip m-form-title-extra" v-if="config.titleExtra && itemLabelPosition === 'top'">
|
||||
{{ config.titleExtra }}</span
|
||||
>
|
||||
</slot>
|
||||
@ -217,6 +218,7 @@
|
||||
:step-active="stepActive"
|
||||
:expand-more="expand"
|
||||
:label-width="itemLabelWidth"
|
||||
:label-position="itemLabelPosition"
|
||||
:prop="itemProp"
|
||||
@change="onChangeHandler"
|
||||
@addDiffCount="onAddDiffCount"
|
||||
@ -293,6 +295,7 @@ const props = withDefaults(
|
||||
prop?: string;
|
||||
disabled?: boolean;
|
||||
labelWidth?: string | number;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
expandMore?: boolean;
|
||||
stepActive?: string | number;
|
||||
size?: string;
|
||||
@ -445,7 +448,7 @@ const fieldsProps = computed(() => ({
|
||||
const formItemProps = computed(() => ({
|
||||
prop: itemProp.value,
|
||||
labelWidth: itemLabelWidth.value,
|
||||
labelPosition: props.config.labelPosition,
|
||||
labelPosition: itemLabelPosition.value,
|
||||
|
||||
rules: rule.value,
|
||||
extra: filterFunction(mForm, props.config.extra, props),
|
||||
@ -454,6 +457,8 @@ const formItemProps = computed(() => ({
|
||||
|
||||
const itemLabelWidth = computed(() => props.config.labelWidth ?? props.labelWidth);
|
||||
|
||||
const itemLabelPosition = computed(() => props.config.labelPosition ?? props.labelPosition);
|
||||
|
||||
watchEffect(() => {
|
||||
expand.value = props.expandMore;
|
||||
});
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
:prop="prop"
|
||||
:disabled="disabled"
|
||||
:labelWidth="lWidth"
|
||||
:labelPosition="lPosition"
|
||||
:size="size"
|
||||
@change="changeHandler"
|
||||
@add-diff-count="onAddDiffCount()"
|
||||
@ -48,6 +49,7 @@
|
||||
:config="item"
|
||||
:prop="prop"
|
||||
:labelWidth="lWidth"
|
||||
:labelPosition="lPosition"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
@change="changeHandler"
|
||||
@ -74,6 +76,7 @@ defineOptions({
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
labelWidth?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
prop: string;
|
||||
size?: string;
|
||||
model: Record<string, any>;
|
||||
@ -140,6 +143,8 @@ const lWidth = computed(() => {
|
||||
return props.config.labelWidth || props.labelWidth || (props.config.text ? undefined : '0');
|
||||
});
|
||||
|
||||
const lPosition = computed(() => props.config.labelPosition || props.labelPosition);
|
||||
|
||||
const valueChangeHandler = (value: number | boolean) => {
|
||||
emit('change', value, { modifyKey: checkboxName.value });
|
||||
};
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:label-width="config.labelWidth || labelWidth"
|
||||
:label-position="config.labelPosition || labelPosition"
|
||||
@change="changeHandler"
|
||||
@addDiffCount="onAddDiffCount()"
|
||||
/>
|
||||
@ -37,6 +38,7 @@ const props = defineProps<{
|
||||
config: FlexLayoutConfig;
|
||||
name?: string;
|
||||
labelWidth?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
prop?: string;
|
||||
size?: string;
|
||||
disabled?: boolean;
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
:prop="prop"
|
||||
:index="Number(index)"
|
||||
:label-width="labelWidth"
|
||||
:label-position="labelPosition"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:group-model="model[name]"
|
||||
@ -53,6 +54,7 @@ const props = defineProps<{
|
||||
config: GroupListConfig;
|
||||
name: string;
|
||||
labelWidth?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
prop?: string;
|
||||
size?: string;
|
||||
disabled?: boolean;
|
||||
|
||||
@ -99,6 +99,7 @@
|
||||
:lastValues="lastValues"
|
||||
:is-compare="isCompare"
|
||||
:labelWidth="labelWidth"
|
||||
:labelPosition="labelPosition"
|
||||
:prop="rowProp"
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
@ -131,6 +132,7 @@ const props = defineProps<{
|
||||
groupModel: any[];
|
||||
config: GroupListConfig;
|
||||
labelWidth?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
prop?: string;
|
||||
size?: string;
|
||||
index: number;
|
||||
|
||||
@ -39,6 +39,7 @@
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:label-width="config.labelWidth || labelWidth"
|
||||
:label-position="config.labelPosition || labelPosition"
|
||||
@change="changeHandler"
|
||||
@addDiffCount="onAddDiffCount()"
|
||||
></Container>
|
||||
@ -59,6 +60,7 @@
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:label-width="config.labelWidth || labelWidth"
|
||||
:label-position="config.labelPosition || labelPosition"
|
||||
@change="changeHandler"
|
||||
@addDiffCount="onAddDiffCount()"
|
||||
></Container>
|
||||
@ -89,6 +91,7 @@ const props = defineProps<{
|
||||
config: PanelConfig;
|
||||
name?: string;
|
||||
labelWidth?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
prop?: string;
|
||||
size?: string;
|
||||
disabled?: boolean;
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
:span="col.span || config.span || 24 / config.items.length"
|
||||
:config="col"
|
||||
:labelWidth="config.labelWidth || labelWidth"
|
||||
:labelPosition="config.labelPosition || labelPosition"
|
||||
:expandMore="expandMore"
|
||||
:model="name ? model[name] : model"
|
||||
:lastValues="name ? lastValues[name] : lastValues"
|
||||
@ -39,6 +40,7 @@ const props = defineProps<{
|
||||
config: RowConfig;
|
||||
name: string;
|
||||
labelWidth?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
prop?: string;
|
||||
size?: string;
|
||||
expandMore?: boolean;
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
:size="size"
|
||||
:disabled="disabled"
|
||||
:label-width="config.labelWidth || labelWidth"
|
||||
:label-position="config.labelPosition || labelPosition"
|
||||
@change="changeHandler"
|
||||
@addDiffCount="onAddDiffCount()"
|
||||
></Container>
|
||||
@ -54,6 +55,7 @@ const props = withDefaults(
|
||||
config: StepConfig;
|
||||
stepActive?: number;
|
||||
labelWidth?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
size?: string;
|
||||
disabled?: boolean;
|
||||
}>(),
|
||||
|
||||
@ -70,6 +70,7 @@
|
||||
"
|
||||
:size="size"
|
||||
:label-width="tab.labelWidth || labelWidth"
|
||||
:label-position="tab.labelPosition || labelPosition"
|
||||
:expand-more="expandMore"
|
||||
@change="changeHandler"
|
||||
@addDiffCount="onAddDiffCount(Number(tabIndex))"
|
||||
@ -106,6 +107,7 @@ const props = withDefaults(
|
||||
name: string;
|
||||
size?: string;
|
||||
labelWidth?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
prop?: string;
|
||||
expandMore?: boolean;
|
||||
disabled?: boolean;
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
:last-values="lastValues"
|
||||
:prop="prop"
|
||||
:label-width="labelWidth"
|
||||
:label-position="labelPosition"
|
||||
:show-index="showIndex"
|
||||
:sort-key="sortKey"
|
||||
:sort="sort"
|
||||
@ -80,6 +81,7 @@ const props = defineProps<{
|
||||
name: string;
|
||||
prop?: string;
|
||||
labelWidth?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
disabled?: boolean;
|
||||
size?: string;
|
||||
enableToggleMode?: true;
|
||||
|
||||
@ -43,7 +43,7 @@ export interface SubmitFormOptions {
|
||||
stepActive?: string | number;
|
||||
size?: 'small' | 'default' | 'large';
|
||||
inline?: boolean;
|
||||
labelPosition?: string;
|
||||
labelPosition?: 'top' | 'left' | 'right';
|
||||
keyProp?: string;
|
||||
popperClass?: string;
|
||||
preventSubmitDefault?: boolean;
|
||||
|
||||
@ -94,6 +94,7 @@ export const getGroupListRowConfig = (config: GroupListConfig, index: number, ke
|
||||
span: config.span || 24,
|
||||
items: config.items,
|
||||
labelWidth: config.labelWidth,
|
||||
labelPosition: config.labelPosition,
|
||||
[key]: `${(config as Record<string, any>)[key]}${String(index)}`,
|
||||
};
|
||||
};
|
||||
|
||||
@ -51,6 +51,26 @@ describe('Form.vue —— 默认 props', () => {
|
||||
const formEl = wrapper.find('.m-form').element as HTMLElement;
|
||||
expect(formEl.getAttribute('style') || '').toContain('height: 300px');
|
||||
});
|
||||
|
||||
test('labelPosition 与 labelWidth 一样,未配置时回落到表单级,配置了则用自身', async () => {
|
||||
const wrapper = mountForm({
|
||||
labelPosition: 'left',
|
||||
labelWidth: '120px',
|
||||
config: [
|
||||
{ name: 'a', type: 'text', text: 'a' },
|
||||
{ name: 'b', type: 'text', text: 'b', labelPosition: 'top' },
|
||||
],
|
||||
initValues: { a: '1', b: '2' },
|
||||
});
|
||||
await nextTick();
|
||||
|
||||
const containers = wrapper.findAllComponents({ name: 'MFormContainer' });
|
||||
const byName = Object.fromEntries(containers.map((c) => [c.props('config')?.name, c]));
|
||||
|
||||
expect(byName.a.props('labelWidth')).toBe('120px');
|
||||
expect(byName.a.props('labelPosition')).toBe('left');
|
||||
expect(byName.b.props('labelPosition')).toBe('top');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Form.vue —— formState getter 行为', () => {
|
||||
|
||||
@ -98,4 +98,50 @@ describe('GroupList container', () => {
|
||||
expect(wrapper.text()).not.toContain('下移');
|
||||
});
|
||||
});
|
||||
|
||||
describe('labelPosition 透传', () => {
|
||||
const getFormItemLabelPosition = (wrapper: ReturnType<typeof mountForm>, prop: string) => {
|
||||
const item = wrapper.findAllComponents({ name: 'TMFormItem' }).find((w) => w.props('prop') === prop);
|
||||
return item?.props('labelPosition');
|
||||
};
|
||||
|
||||
test('group-list 的 labelPosition 透传到子表单项', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'group-list',
|
||||
name: 'list',
|
||||
labelPosition: 'left',
|
||||
labelWidth: '80px',
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
{ list: [{ text: 'a' }] },
|
||||
);
|
||||
await nextTick();
|
||||
|
||||
expect(getFormItemLabelPosition(wrapper, 'list.0.text')).toBe('left');
|
||||
});
|
||||
|
||||
test('子项自身的 labelPosition 优先于 group-list', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'group-list',
|
||||
name: 'list',
|
||||
labelPosition: 'left',
|
||||
items: [
|
||||
{ name: 'text', type: 'text', text: 'text' },
|
||||
{ name: 'title', type: 'text', text: 'title', labelPosition: 'top' },
|
||||
],
|
||||
},
|
||||
],
|
||||
{ list: [{ text: 'a', title: 'b' }] },
|
||||
);
|
||||
await nextTick();
|
||||
|
||||
expect(getFormItemLabelPosition(wrapper, 'list.0.text')).toBe('left');
|
||||
expect(getFormItemLabelPosition(wrapper, 'list.0.title')).toBe('top');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -89,4 +89,92 @@ describe('Panel container', () => {
|
||||
await nextTick();
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('labelPosition 透传', () => {
|
||||
const getFormItemLabelPosition = (wrapper: ReturnType<typeof mountForm>, prop: string) => {
|
||||
const item = wrapper.findAllComponents({ name: 'TMFormItem' }).find((w) => w.props('prop') === prop);
|
||||
return item?.props('labelPosition');
|
||||
};
|
||||
|
||||
test('panel 的 labelPosition 透传到子表单项', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'panel',
|
||||
title: 'group',
|
||||
labelPosition: 'left',
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
{ text: 'hello' },
|
||||
);
|
||||
await nextTick();
|
||||
|
||||
expect(getFormItemLabelPosition(wrapper, 'text')).toBe('left');
|
||||
});
|
||||
|
||||
test('fieldset 的 labelPosition 透传到子表单项', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'fieldset',
|
||||
legend: 'fs',
|
||||
labelPosition: 'left',
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
{ text: 'fs' },
|
||||
);
|
||||
await nextTick();
|
||||
|
||||
expect(getFormItemLabelPosition(wrapper, 'text')).toBe('left');
|
||||
});
|
||||
|
||||
test('flex-layout 的 labelPosition 透传到子表单项', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'flex-layout',
|
||||
labelPosition: 'left',
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
{ text: 'fl' },
|
||||
);
|
||||
await nextTick();
|
||||
|
||||
expect(getFormItemLabelPosition(wrapper, 'text')).toBe('left');
|
||||
});
|
||||
|
||||
test('row 的 labelPosition 透传到子表单项', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'row',
|
||||
labelPosition: 'left',
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
{ text: 'r' },
|
||||
);
|
||||
await nextTick();
|
||||
|
||||
expect(getFormItemLabelPosition(wrapper, 'text')).toBe('left');
|
||||
});
|
||||
|
||||
test('无 type 的 items 容器把 labelPosition 透传到子项', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
labelPosition: 'left',
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
{ text: 'x' },
|
||||
);
|
||||
await nextTick();
|
||||
|
||||
expect(getFormItemLabelPosition(wrapper, 'text')).toBe('left');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -43,4 +43,28 @@ describe('Step container', () => {
|
||||
expect(wrapper.text()).toContain('Step 1');
|
||||
expect(wrapper.text()).toContain('Step 2');
|
||||
});
|
||||
|
||||
test('step 的 labelPosition 透传到子表单项', async () => {
|
||||
const wrapper = mountForm(
|
||||
[
|
||||
{
|
||||
type: 'step',
|
||||
labelPosition: 'left',
|
||||
items: [
|
||||
{
|
||||
title: 'Step 1',
|
||||
name: 's1',
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
{ s1: { text: 'a' } },
|
||||
{ stepActive: 1 },
|
||||
);
|
||||
await nextTick();
|
||||
|
||||
const item = wrapper.findAllComponents({ name: 'TMFormItem' }).find((w) => w.props('prop') === 's1.text');
|
||||
expect(item?.props('labelPosition')).toBe('left');
|
||||
});
|
||||
});
|
||||
|
||||
@ -63,4 +63,24 @@ describe('Tabs', () => {
|
||||
const value = await (wrapper.vm as any).submitForm();
|
||||
expect(value.text).toBe('text');
|
||||
});
|
||||
|
||||
test('tab 的 labelPosition 透传到子表单项', async () => {
|
||||
const wrapper = getWrapper([
|
||||
{
|
||||
type: 'tab',
|
||||
items: [
|
||||
{
|
||||
title: 'tab1',
|
||||
labelPosition: 'left',
|
||||
items: [{ name: 'text', text: 'text' }],
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
await nextTick();
|
||||
|
||||
const item = wrapper.findAllComponents({ name: 'TMFormItem' }).find((w) => w.props('prop') === 'text');
|
||||
expect(item?.props('labelPosition')).toBe('left');
|
||||
});
|
||||
});
|
||||
|
||||
27
packages/form/tests/unit/utils/tableGroupList.spec.ts
Normal file
27
packages/form/tests/unit/utils/tableGroupList.spec.ts
Normal file
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||
*
|
||||
* Copyright (C) 2025 Tencent.
|
||||
*/
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { getGroupListRowConfig } from '@form/utils/tableGroupList';
|
||||
|
||||
describe('getGroupListRowConfig', () => {
|
||||
test('把 group-list 的 labelWidth / labelPosition 复制到 row 配置', () => {
|
||||
const row = getGroupListRowConfig(
|
||||
{
|
||||
type: 'group-list',
|
||||
name: 'list',
|
||||
labelWidth: '80px',
|
||||
labelPosition: 'left',
|
||||
items: [{ name: 'text', type: 'text', text: 'text' }],
|
||||
} as any,
|
||||
0,
|
||||
);
|
||||
|
||||
expect(row.type).toBe('row');
|
||||
expect(row.labelWidth).toBe('80px');
|
||||
expect(row.labelPosition).toBe('left');
|
||||
expect(row.items).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user