refactor(form): 保持单向数据流,表单内部的组件不去修改表单的值,统一通过chang事件通知表单修改

This commit is contained in:
roymondchen 2025-10-16 21:03:57 +08:00
parent e36da82d29
commit d59428d2d6
22 changed files with 360 additions and 185 deletions

View File

@ -15,13 +15,14 @@ export interface ChangeRecord {
export interface OnChangeHandlerData {
model: FormValue;
values?: FormValue;
values?: Readonly<FormValue>;
parent?: FormValue;
formValue?: FormValue;
config: any;
config: Readonly<any>;
prop: string;
changeRecords: ChangeRecord[];
setModel: (prop: string, value: any) => void;
setFromValue: (prop: string, value: any) => void;
}
export type FormValue = Record<string | number, any>;
@ -385,6 +386,7 @@ export interface NumberConfig extends FormItem {
*/
export interface NumberRangeConfig extends FormItem {
type?: 'number-range';
clearable?: boolean;
}
/**

View File

@ -32,6 +32,7 @@ import { provide, reactive, ref, shallowRef, toRaw, watch, watchEffect } from 'v
import { cloneDeep, isEqual } from 'lodash-es';
import { TMagicForm, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
import { setValueByKeyPath } from '@tmagic/utils';
import Container from './containers/Container.vue';
import { getConfig } from './utils/config';
@ -173,7 +174,18 @@ watch(
const changeHandler = (v: FormValue, eventData: ContainerChangeEventData) => {
if (eventData.changeRecords?.length) {
changeRecords.value.push(...eventData.changeRecords);
for (const record of eventData.changeRecords) {
if (record.propPath) {
const index = changeRecords.value.findIndex((item) => item.propPath === record.propPath);
if (index > -1) {
changeRecords.value[index] = record;
} else {
changeRecords.value.push(record);
}
setValueByKeyPath(record.propPath, record.value, values.value);
}
}
}
emit('change', values.value, eventData);
};
@ -201,6 +213,7 @@ defineExpose({
submitForm: async (native?: boolean): Promise<any> => {
try {
await tMagicForm.value?.validate();
changeRecords.value = [];
return native ? values.value : cloneDeep(toRaw(values.value));
} catch (invalidFields: any) {
emit('error', invalidFields);

View File

@ -1,32 +1,18 @@
<template>
<div
v-if="config"
:data-tmagic-id="config.id"
:data-tmagic-form-item-prop="itemProp"
:style="config.tip ? 'display: flex;align-items: baseline;' : ''"
:class="`m-form-container m-container-${type || ''} ${config.className || ''}`"
:class="`m-form-container m-container-${type || ''} ${config.className || ''}${config.tip ? ' has-tip' : ''}`"
>
<m-fields-hidden
v-if="type === 'hidden'"
:model="model"
:config="config"
:name="config.name"
:disabled="disabled"
:prop="itemProp"
></m-fields-hidden>
<m-fields-hidden v-if="type === 'hidden'" v-bind="fieldsProps" :model="model"></m-fields-hidden>
<component
v-else-if="items && !text && type && display"
:key="key(config)"
:size="size"
v-bind="fieldsProps"
:is="tagName"
:model="model"
:last-values="lastValues"
:is-compare="isCompare"
:config="config"
:disabled="disabled"
:name="name"
:prop="itemProp"
:step-active="stepActive"
:expand-more="expand"
:label-width="itemLabelWidth"
@ -35,45 +21,28 @@
></component>
<template v-else-if="type && display && !showDiff">
<TMagicFormItem
:style="config.tip ? 'flex: 1' : ''"
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text }"
:prop="itemProp"
:label-width="itemLabelWidth"
:label-position="config.labelPosition"
:rules="rule"
>
<TMagicFormItem v-bind="formItemProps" :class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text }">
<template #label><span v-html="type === 'checkbox' ? '' : text" :title="config.labelTitle"></span></template>
<TMagicTooltip v-if="tooltip">
<component
:key="key(config)"
:size="size"
v-bind="fieldsProps"
:is="tagName"
:model="model"
:last-values="lastValues"
:config="config"
:name="name"
:disabled="disabled"
:prop="itemProp"
@change="onChangeHandler"
@addDiffCount="onAddDiffCount"
></component>
<template #content>
<template #content v-if="tooltip">
<div v-html="tooltip"></div>
</template>
</TMagicTooltip>
<component
v-else
:key="key(config)"
:size="size"
v-bind="fieldsProps"
:is="tagName"
:model="model"
:last-values="lastValues"
:config="config"
:name="name"
:disabled="disabled"
:prop="itemProp"
@change="onChangeHandler"
@addDiffCount="onAddDiffCount"
></component>
@ -93,44 +62,18 @@
<template v-else-if="type && display && showDiff">
<!-- 上次内容 -->
<TMagicFormItem
:style="config.tip ? 'flex: 1' : ''"
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text }"
:prop="itemProp"
:label-width="itemLabelWidth"
:label-position="config.labelPosition"
:rules="rule"
style="background: #f7dadd"
v-bind="formItemProps"
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text, 'show-diff': true }"
>
<template #label><span v-html="type === 'checkbox' ? '' : text" :title="config.labelTitle"></span></template>
<TMagicTooltip v-if="tooltip">
<component
:key="key(config)"
:size="size"
:is="tagName"
:model="lastValues"
:config="config"
:name="name"
:disabled="disabled"
:prop="itemProp"
@change="onChangeHandler"
></component>
<template #content>
<component v-bind="fieldsProps" :is="tagName" :model="lastValues" @change="onChangeHandler"></component>
<template #content v-if="tooltip">
<div v-html="tooltip"></div>
</template>
</TMagicTooltip>
<component
v-else
:key="key(config)"
:size="size"
:is="tagName"
:model="lastValues"
:config="config"
:name="name"
:disabled="disabled"
:prop="itemProp"
@change="onChangeHandler"
></component>
<component v-else v-bind="fieldsProps" :is="tagName" :model="lastValues" @change="onChangeHandler"></component>
<div v-if="extra" v-html="extra" class="m-form-tip"></div>
</TMagicFormItem>
@ -141,46 +84,22 @@
<div v-html="config.tip"></div>
</template>
</TMagicTooltip>
<!-- 当前内容 -->
<TMagicFormItem
v-bind="formItemProps"
:style="config.tip ? 'flex: 1' : ''"
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text }"
:prop="itemProp"
:label-width="itemLabelWidth"
:label-position="config.labelPosition"
:rules="rule"
style="background: #def7da"
:class="{ 'tmagic-form-hidden': `${itemLabelWidth}` === '0' || !text, 'show-diff': true }"
>
<template #label><span v-html="type === 'checkbox' ? '' : text" :title="config.labelTitle"></span></template>
<TMagicTooltip v-if="tooltip">
<component
:key="key(config)"
:size="size"
:is="tagName"
:model="model"
:config="config"
:name="name"
:disabled="disabled"
:prop="itemProp"
@change="onChangeHandler"
></component>
<component v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
<template #content>
<div v-html="tooltip"></div>
</template>
</TMagicTooltip>
<component
v-else
:key="key(config)"
:size="size"
:is="tagName"
:model="model"
:config="config"
:name="name"
:disabled="disabled"
:prop="itemProp"
@change="onChangeHandler"
></component>
<component v-else v-bind="fieldsProps" :is="tagName" :model="model" @change="onChangeHandler"></component>
<div v-if="extra" v-html="extra" class="m-form-tip"></div>
</TMagicFormItem>
@ -223,12 +142,11 @@
</template>
<script setup lang="ts">
import { computed, inject, ref, toRaw, watch, watchEffect } from 'vue';
import { computed, inject, readonly, ref, toRaw, watch, watchEffect } from 'vue';
import { WarningFilled } from '@element-plus/icons-vue';
import { isEqual } from 'lodash-es';
import { TMagicButton, TMagicFormItem, TMagicIcon, TMagicTooltip } from '@tmagic/design';
import { setValueByKeyPath } from '@tmagic/utils';
import type { ChildConfig, ContainerChangeEventData, ContainerCommonConfig, FormState, FormValue } from '../schema';
import { display as displayFunction, filterFunction, getRules } from '../utils/form';
@ -334,6 +252,22 @@ const display = computed((): boolean => {
return value;
});
const fieldsProps = computed(() => ({
size: props.size,
config: props.config,
name: name.value,
disabled: disabled.value,
prop: itemProp.value,
key: props.config[mForm?.keyProps],
}));
const formItemProps = computed(() => ({
prop: itemProp.value,
labelWidth: itemLabelWidth.value,
labelPosition: props.config.labelPosition,
rules: rule.value,
}));
const itemLabelWidth = computed(() => props.config.labelWidth ?? props.labelWidth);
watchEffect(() => {
@ -406,8 +340,29 @@ const isValidName = () => {
return true;
};
const createModelProxy = (
target: any,
setModelFn: (_key: string, _value: any) => void,
pathPrefix: string = '',
): any => {
return new Proxy(target, {
get: (obj, key: string) => {
const value = obj[key];
if (value && typeof value === 'object') {
const newPath = pathPrefix ? `${pathPrefix}.${key}` : key;
return createModelProxy(value, setModelFn, newPath);
}
return value;
},
set: (obj, key: string, value) => {
setModelFn(pathPrefix ? `${pathPrefix}.${key}` : key, value);
return true;
},
});
};
const onChangeHandler = async function (v: any, eventData: ContainerChangeEventData = {}) {
const { filter, onChange, trim, dynamicKey } = props.config as any;
const { filter, onChange, trim } = props.config as any;
let value: FormValue | number | string | any[] = toRaw(v);
const changeRecords = eventData.changeRecords || [];
const newChangeRecords = [...changeRecords];
@ -416,20 +371,28 @@ const onChangeHandler = async function (v: any, eventData: ContainerChangeEventD
value = filterHandler(filter, v);
if (typeof onChange === 'function') {
const setModel = (key: string, value: any) => {
if (props.config.name) {
newChangeRecords.push({ propPath: itemProp.value.replace(`${props.config.name}`, key), value });
} else {
newChangeRecords.push({ propPath: itemProp.value, value });
}
};
const setFormValue = (key: string, value: any) => {
newChangeRecords.push({ propPath: key, value });
};
value =
(await onChange(mForm, value, {
model: props.model,
values: mForm?.initValues,
formValue: mForm?.values,
model: createModelProxy(props.model, setModel),
values: mForm ? readonly(mForm.initValues) : null,
formValue: createModelProxy(mForm?.values || {}, setFormValue),
prop: itemProp.value,
config: props.config,
changeRecords: newChangeRecords,
setModel: (key: string, value: any) => {
setValueByKeyPath(key, value, props.model);
if (props.config.name) {
newChangeRecords.push({ propPath: itemProp.value.replace(`${props.config.name}`, key), value });
}
},
setModel,
setFormValue,
})) ?? value;
}
value = trimHandler(trim, value) ?? value;
@ -440,19 +403,10 @@ const onChangeHandler = async function (v: any, eventData: ContainerChangeEventD
let valueProp = itemProp.value;
if (hasModifyKey(eventData)) {
if (dynamicKey) {
props.model[eventData.modifyKey!] = value;
} else if (isValidName()) {
props.model[name.value][eventData.modifyKey!] = value;
}
valueProp = valueProp ? `${valueProp}.${eventData.modifyKey}` : eventData.modifyKey!;
// modifyKey
delete eventData.modifyKey;
} else if (isValidName() && props.model !== value && (v !== value || props.model[name.value] !== value)) {
// fieldfield-linkmodel===value,
props.model[name.value] = value;
}
if (changeRecords.length === 0) {

View File

@ -2,7 +2,7 @@
<fieldset v-if="name ? model[name] : model" class="m-fieldset" :style="show ? 'padding: 15px' : 'border: 0'">
<component v-if="name && config.checkbox" :is="!show ? 'div' : 'legend'">
<TMagicCheckbox
v-model="model[name].value"
:model-value="model[name].value"
:prop="`${prop}${prop ? '.' : ''}${config.name}.value`"
:true-value="1"
:false-value="0"

View File

@ -1,6 +1,6 @@
<template>
<TMagicCascader
v-model="value"
:model-value="value"
ref="tMagicCascader"
style="width: 100%"
clearable
@ -15,6 +15,7 @@
emitPath: config.emitPath ?? true,
checkStrictly: checkStrictly ?? false,
}"
@update:model-value="updateModelValueHandler"
@change="changeHandler"
></TMagicCascader>
</template>
@ -51,22 +52,31 @@ const remoteData = ref<any>(null);
const checkStrictly = computed(() => filterFunction(mForm, props.config.checkStrictly, props));
const valueSeparator = computed(() => filterFunction<string>(mForm, props.config.valueSeparator, props));
const value = computed({
get() {
if (typeof props.model[props.name] === 'string' && valueSeparator.value) {
return props.model[props.name].split(valueSeparator.value);
}
return props.model[props.name];
},
set(value) {
let result = value;
if (valueSeparator.value) {
result = value.join(valueSeparator.value);
}
props.model[props.name] = result;
},
const value = computed(() => {
if (typeof props.model[props.name] === 'string' && valueSeparator.value) {
return props.model[props.name].split(valueSeparator.value);
}
return props.model[props.name];
});
const updateModelValueHandler = (value: string[] | number[] | any) => {
let result = value;
if (valueSeparator.value) {
result = value.join(valueSeparator.value);
}
if (typeof result === 'undefined') {
if (Array.isArray(props.model[props.name])) {
emit('change', []);
} else if (typeof props.model[props.name] === 'string') {
emit('change', '');
} else if (typeof props.model[props.name] === 'object') {
emit('change', null);
}
}
emit('change', result);
};
const setRemoteOptions = async function () {
const { config } = props;
const { option } = config;
@ -126,6 +136,5 @@ const changeHandler = () => {
if (!tMagicCascader.value) return;
tMagicCascader.value.setQuery('');
tMagicCascader.value.setPreviousQuery(null);
emit('change', props.model[props.name]);
};
</script>

View File

@ -1,11 +1,11 @@
<template>
<TMagicCheckbox
v-model="model[name]"
:model-value="model[name]"
:size="size"
:trueValue="activeValue"
:falseValue="inactiveValue"
:disabled="disabled"
@change="changeHandler"
@update:model-value="changeHandler"
>{{ config.text }}</TMagicCheckbox
>
</template>

View File

@ -1,5 +1,5 @@
<template>
<TMagicCheckboxGroup v-model="model[name]" :size="size" :disabled="disabled" @change="changeHandler">
<TMagicCheckboxGroup :model-value="model[name]" :size="size" :disabled="disabled" @update:model-value="changeHandler">
<TMagicCheckbox v-for="option in options" :value="option.value" :key="option.value" :disabled="option.disabled"
>{{ option.text }}
</TMagicCheckbox>

View File

@ -1,10 +1,10 @@
<template>
<TMagicColorPicker
v-model="model[name]"
:model-value="model[name]"
:size="size"
:disabled="disabled"
:showAlpha="true"
@change="changeHandler"
@update:model-value="changeHandler"
></TMagicColorPicker>
</template>

View File

@ -1,13 +1,13 @@
<template>
<TMagicDatePicker
v-model="model[name]"
:model-value="model[name]"
type="date"
:size="size"
:placeholder="config.placeholder"
:disabled="disabled"
:format="config.format || 'YYYY/MM/DD'"
:value-format="config.valueFormat || 'YYYY/MM/DD'"
@change="changeHandler"
@update:model-value="changeHandler"
></TMagicDatePicker>
</template>

View File

@ -1,6 +1,6 @@
<template>
<TMagicDatePicker
v-model="model[name]"
:model-value="model[name]"
popper-class="magic-datetime-picker-popper"
type="datetime"
:size="size"
@ -9,7 +9,7 @@
:format="config.format || 'YYYY/MM/DD HH:mm:ss'"
:value-format="config.valueFormat || 'YYYY/MM/DD HH:mm:ss'"
:default-time="config.defaultTime"
@change="changeHandler"
@update:model-value="changeHandler"
></TMagicDatePicker>
</template>

View File

@ -1,6 +1,6 @@
<template>
<TMagicDatePicker
v-model="value"
:model-value="value"
type="datetimerange"
range-separator="-"
start-placeholder="开始日期"
@ -12,7 +12,7 @@
:value-format="config.valueFormat || 'YYYY/MM/DD HH:mm:ss'"
:date-format="config.dateFormat || 'YYYY/MM/DD'"
:time-format="config.timeFormat || 'HH:mm:ss'"
@change="changeHandler"
@update:model-value="changeHandler"
></TMagicDatePicker>
</template>

View File

@ -1,7 +1,7 @@
<template>
<TMagicInputNumber
v-if="model"
v-model="model[name]"
:model-value="model[name]"
clearable
controls-position="right"
:size="size"
@ -10,7 +10,7 @@
:step="config.step"
:placeholder="config.placeholder"
:disabled="disabled"
@change="changeHandler"
@update:model-value="changeHandler"
@input="inputHandler"
></TMagicInputNumber>
</template>

View File

@ -1,19 +1,19 @@
<template>
<div class="m-fields-number-range">
<TMagicInput
v-model="model[name][0]"
clearable
:model-value="model[name][0]"
:clearable="config.clearable ?? true"
:size="size"
:disabled="disabled"
@change="minChangeHandler"
@update:model-value="minChangeHandler"
></TMagicInput>
<span class="split-tag">-</span>
<TMagicInput
v-model="model[name][1]"
clearable
:model-value="model[name][1]"
:clearable="config.clearable ?? true"
:size="size"
:disabled="disabled"
@change="maxChangeHandler"
@update:model-value="maxChangeHandler"
></TMagicInput>
</div>
</template>

View File

@ -1,22 +1,18 @@
<template>
<TMagicRadioGroup v-if="model" v-model="model[name]" :size="size" :disabled="disabled">
<TMagicRadioGroup v-if="model" :model-value="model[name]" :size="size" :disabled="disabled">
<component
:is="itemComponent"
v-for="option in config.options"
:is="itemComponent"
:value="option.value"
:key="`${option.value}`"
@click.prevent="clickHandler(option.value)"
>
<TMagicTooltip v-if="option.tooltip" placement="top-start" :content="option.tooltip">
<TMagicTooltip :disabled="!Boolean(option.tooltip)" placement="top-start" :content="option.tooltip">
<div>
<TMagicIcon v-if="option.icon" :size="iconSize"><component :is="option.icon"></component></TMagicIcon>
<span>{{ option.text }}</span>
</div>
</TMagicTooltip>
<div v-else>
<TMagicIcon v-if="option.icon" :size="iconSize"><component :is="option.icon"></component></TMagicIcon>
<span>{{ option.text }}</span>
</div>
</component>
</TMagicRadioGroup>
</template>
@ -39,13 +35,9 @@ const itemComponent = computed(() => (props.config.childType === 'button' ? TMag
const emit = defineEmits(['change']);
const changeHandler = (value: number) => {
emit('change', value);
};
const clickHandler = (item: any) => {
props.model[props.name] = props.model[props.name] === item ? '' : item;
changeHandler(props.model[props.name]);
const clickHandler = (item: string | number | boolean) => {
//
emit('change', props.model[props.name] === item ? '' : item);
};
useAddField(props.prop);

View File

@ -1,7 +1,7 @@
<template>
<TMagicSelect
v-if="model"
v-model="model[name]"
:model-value="model[name]"
v-loading="loading"
class="m-select"
ref="tMagicSelect"
@ -16,7 +16,7 @@
:allow-create="config.allowCreate"
:disabled="disabled"
:remote-method="config.remote && remoteMethod"
@change="changeHandler"
@update:model-value="changeHandler"
@visible-change="visibleHandler"
>
<template v-if="config.group">

View File

@ -1,11 +1,11 @@
<template>
<TMagicSwitch
v-model="model[name]"
:model-value="model[name]"
:size="size"
:activeValue="activeValue"
:inactiveValue="inactiveValue"
:disabled="disabled"
@change="changeHandler"
@update:model-value="changeHandler"
></TMagicSwitch>
</template>

View File

@ -1,13 +1,13 @@
<template>
<div class="m-fields-text">
<TMagicInput
v-model="model[name]"
:model-value="model[name]"
ref="input"
clearable
:size="size"
:placeholder="config.placeholder"
:disabled="disabled"
@change="changeHandler"
@update:model-value="changeHandler"
@input="inputHandler"
@keyup="keyUpHandler($event)"
>

View File

@ -1,12 +1,12 @@
<template>
<TMagicInput
v-model="model[name]"
:model-value="model[name]"
type="textarea"
:size="size"
clearable
:placeholder="config.placeholder"
:disabled="disabled"
@change="changeHandler"
@update:model-value="changeHandler"
@input="inputHandler"
>
</TMagicInput>

View File

@ -1,12 +1,12 @@
<template>
<TMagicTimePicker
v-model="model[name]"
:model-value="model[name]"
:value-format="config.valueFormat || 'HH:mm:ss'"
:format="config.format || 'HH:mm:ss'"
:size="size"
:placeholder="config.placeholder"
:disabled="disabled"
@change="changeHandler"
@update:model-value="changeHandler"
></TMagicTimePicker>
</template>

View File

@ -1,6 +1,6 @@
<template>
<TMagicTimePicker
v-model="value"
:model-value="value"
is-range
range-separator="-"
start-placeholder="开始时间"
@ -9,7 +9,7 @@
:unlink-panels="true"
:disabled="disabled"
:default-time="config.defaultTime"
@change="changeHandler"
@update:model-value="changeHandler"
></TMagicTimePicker>
</template>

View File

@ -3,3 +3,20 @@
display: inline-flex;
}
}
.m-form-container {
&.has-tip {
display: flex;
align-items: baseline;
.tmagic-design-form-item {
flex: 1;
}
}
.tmagic-design-form-item {
&.show-diff {
background: #f7dadd;
}
}
}

View File

@ -17,7 +17,7 @@
*/
import { describe, expect, test } from 'vitest';
import { nextTick } from 'vue';
import MagicForm, { MForm } from '@form/index';
import MagicForm, { createForm, MForm } from '@form/index';
import { mount } from '@vue/test-utils';
import ElementPlus from 'element-plus';
@ -45,4 +45,192 @@ describe('表单', () => {
expect(wrapper.text()).toBe('text');
});
test('changeRecords', async () => {
const initValues = {};
const config = [
{
text: 'text',
name: 'text',
},
{
name: 'object',
items: [
{
text: 'text',
name: 'objectText',
},
],
},
{
items: [
{
text: 'text',
name: 'text1',
},
],
},
{
items: [
{
name: 'object',
items: [
{
text: 'text',
name: 'text1',
},
],
},
],
},
];
const wrapper = mount(MForm, {
global: {
plugins: [ElementPlus as any, MagicForm as any],
},
props: {
initValues,
config,
},
});
await nextTick();
const inputs = wrapper.findAll('input');
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i];
input.setValue(i);
}
expect(wrapper.vm.changeRecords).toEqual([
{ propPath: 'text', value: '0' },
{ propPath: 'object.objectText', value: '1' },
{ propPath: 'text1', value: '2' },
{ propPath: 'object.text1', value: '3' },
]);
expect(wrapper.vm.values).toEqual({
text: '0',
object: {
objectText: '1',
text1: '3',
},
text1: '2',
});
});
test('onChange setFormValue', async () => {
const initValues = {};
const config = createForm([
{
text: 'text',
name: 'text',
onChange: (vm, value: string, { formValue, setFormValue }: any) => {
setFormValue('object.objectText', value);
formValue!.object.objectText2 = value;
},
},
{
name: 'object',
items: [
{
text: 'text',
name: 'objectText',
},
{
text: 'text',
name: 'objectText2',
},
],
},
]);
const wrapper = mount(MForm, {
global: {
plugins: [ElementPlus as any, MagicForm as any],
},
props: {
initValues,
config,
},
});
await nextTick();
const input = wrapper.find('input');
input.setValue('a');
await nextTick();
expect(wrapper.vm.changeRecords).toEqual([
{ propPath: 'object.objectText', value: 'a' },
{ propPath: 'object.objectText2', value: 'a' },
{ propPath: 'text', value: 'a' },
]);
expect(wrapper.vm.values).toEqual({
text: 'a',
object: {
objectText: 'a',
objectText2: 'a',
},
});
});
test('onChange setModel', async () => {
const initValues = {};
const config = createForm([
{
name: 'object',
items: [
{
text: 'text',
name: 'objectText',
onChange: (vm: any, value: string, { model, setModel }: any) => {
model.objectText2 = value;
setModel('objectText3', value);
},
},
{
text: 'text',
name: 'objectText2',
},
{
text: 'text',
name: 'objectText3',
},
],
},
]);
const wrapper = mount(MForm, {
global: {
plugins: [ElementPlus as any, MagicForm as any],
},
props: {
initValues,
config,
},
});
await nextTick();
const input = wrapper.find('input');
input.setValue('a');
await nextTick();
expect(wrapper.vm.changeRecords).toEqual([
{ propPath: 'object.objectText2', value: 'a' },
{ propPath: 'object.objectText3', value: 'a' },
{ propPath: 'object.objectText', value: 'a' },
]);
expect(wrapper.vm.values).toEqual({
object: {
objectText: 'a',
objectText2: 'a',
objectText3: 'a',
},
});
});
});