mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-11-29 22:12:11 +08:00
refactor(form): 保持单向数据流,表单内部的组件不去修改表单的值,统一通过chang事件通知表单修改
This commit is contained in:
parent
e36da82d29
commit
d59428d2d6
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)) {
|
||||
// field内容下包含field-link时,model===value, 这里避免循环引用
|
||||
props.model[name.value] = value;
|
||||
}
|
||||
|
||||
if (changeRecords.length === 0) {
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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)"
|
||||
>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user