适用vue 3.4 版本以上
在日常的前端开发中,表单开发必不可少,尤其是在遇到一些大型的复杂的表单,这往往会变成一个痛点。于是就想着开发一个可以list来配置的表单组件。
先上组件代码
typescript
<!-- 该组件 VUE 版本在 3.4 以上可使用-->
<template>
<Form
ref="formRef"
:model="model"
v-bind='form'>
<Row :gutter="[8, 8]">
<template v-for="config in newConfigs" :key="config.key">
<Col v-bind="config.wrapperCol" v-if="config.when(model)">
<Form.Item v-bind="config.forImteAttrs"
:label-col="config.labelCol">
<component
:is="config.type"
v-bind="config.componentAttrs"
:config="config"
:raw="config.raw"
:options="config.componentAttrs.options || options?.[config.key]"
v-model:[config.model]="model[config.key]"
v-on='{
change: (e: any) => {
change(e, config.key);
config.componentAttrs.onChange?.({
update: change,
value: model
})
}
}'>
{{ config.componentAttrs.buttonText || null }}
</component>
</Form.Item>
</Col>
</template>
<slot />
</Row>
</Form>
</template>
<script setup lang="ts">
import {
Col,
Form,
FormInstance,
Row,
} from 'ant-design-vue';
import {
computed, Ref,
toRefs,
useAttrs,
} from 'vue';
import { FormItemConfig } from './ConfigFormType';
const formRef = defineModel<Ref<null | undefined> | FormInstance>('formRef');
const model = defineModel<any>();
interface IConfigFormProps {
layout?: 'inline' | 'vertical' | 'horizontal';
FormGrid?: {};
labelCol?: any;
wrapperCol?: any;
form?: any;
configs: FormItemConfig[];
options?: { [index: string]: any[] }
readonly?: boolean;
}
const props = defineProps<IConfigFormProps>();
const {
configs, FormGrid, labelCol, options, wrapperCol, readonly,
} = toRefs(props);
const generateUUID = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
// eslint-disable-next-line no-bitwise
const r = Math.random() * 16 | 0;
// eslint-disable-next-line no-bitwise, no-mixed-operators
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
const gloablAttrs = useAttrs();
const newConfigs = computed(() => configs.value.map((config: any) => {
const {
grid, key, type, attrs, itemModel, when, children, ...rest
} = config;
if (key) {
rest.name = key;
}
if (!config.wrapperCol) {
rest.wrapperCol = wrapperCol.value;
}
if (!config.labelCol) {
rest.labelCol = labelCol.value;
}
const forImteAttrs: Partial<any> = rest;
const componentAttrs = attrs || {};
return {
layout: { ...FormGrid, ...grid },
name: [key || rest.name],
key: key || rest.name || generateUUID(),
when: when || (() => true),
type,
children,
model: itemModel!,
forImteAttrs,
wrapperCol: rest.wrapperCol,
labelCol: rest.labelCol,
componentAttrs,
readonly: gloablAttrs.readonly || readonly.value,
disabled: gloablAttrs.disabled,
raw: config,
};
}));
interface IConfigFormEmits {
(e: 'finish', value: any): void;
(e: 'change', value: any): void;
}
const emit = defineEmits<IConfigFormEmits>();
const change = (e: any, key: string) => emit('change', { value: e, key });
</script>
具体使用
typescript
<ConfigForm
:configs="configComputed"
v-model:formRef="formRef"
v-model="formState"
@finish="fetchInfo"
layout="inline" />
configs
用来生成表单的具体配置,类型如下
typescript
// FormItemConfig.ts
import { ColProps } from 'ant-design-vue';
import { Slot } from 'vue';
export type FormItemConfig = {
title?: string;
type: any;
layout?: 'horizontal' | 'vertical' | 'inline';
key?: string;
name?: string;
label?: string;
rules?: any[] | any;
colon?: boolean;
options?: any[];
hasFeedback?: boolean;
help?: string | Slot;
labelAlign?: 'left' | 'right';
labelCol?: ColProps;
required?: boolean;
tooltip?: string | Slot;
validateFirst?: boolean;
validateStatus?: 'success' | 'warning' | 'error' | 'validating';
validateTrigger?: string | string[];
wrapperCol?: ColProps;
extra?: string | Slot | any;
model?: any;
// eslint-disable-next-line no-unused-vars
when?: (values: Partial<any>, currentValue?: Partial<any>) => boolean;
itemModel?: string;
attrs?: Partial<any>;
children?: FormItemConfig[];
};
configs配置
1、attrs 属性为绑定到具体表单组件的属性,例如Select,Input之类的
2、其余大部分属性绑定到<Form.Item>组件上,故可以使用label,rules之类的属性,
3、itemModel一般情况下为value,type为Switch组件时,需根据实际情况变化
4、type属性是直接注入< components >组件的is属性上,所以一些自定义的组件也可以使用,注入到type组件中
typescript
import {
AutoComplete, Button, Spin,
} from 'ant-design-vue';
import {ref } from 'vue';
const autoCompleteLoading = ref<boolean>(false)
const configs = [
{
title: 'XXX',
key: 'name',
type: autoCompleteLoading.value ? Spin : AutoComplete,
label: 'item 1',
rules: { required: true, message: '请输入XXX', trigger: 'blur' },
itemModel: 'value',
attrs: {
options: [{ label: 'item1', value: 'value1' }],
allowClear: true,
placeholder: 'XXXXXXXX',
style: 'width: 280px',
filterOption: (inputValue: string, option: any) => option.value
.toLowerCase()
.indexOf(inputValue.toLowerCase()) >= 0,
},
},
{
title: 'XXXXXXX',
type: Button,
attrs: {
type: 'primary',
htmlType: 'submit',
buttonText: '查询',
},
},
];
也可以配合计算属性和响应式函数动态响应表单
typescript
// config.ts
import { computed, Ref } from 'vue';
import {
AutoComplete, Button, Spin,
} from 'ant-design-vue';
export function useConfigComputed(
autoCompleteLoading:Ref<boolean>,
dataNameList: Ref<{ label: string, value: any }[]>
) {
return computed(() => [
{
title: 'XXX',
key: 'name',
type: autoCompleteLoading.value ? Spin : AutoComplete,
label: 'item 1',
rules: { required: true, message: '请输入XXX', trigger: 'blur' },
itemModel: 'value',
attrs: {
options: [{ label: 'item1', value: 'value1' }],
allowClear: true,
placeholder: 'XXXXXXXX',
style: 'width: 280px',
filterOption: (inputValue: string, option: any) => option.value
.toLowerCase()
.indexOf(inputValue.toLowerCase()) >= 0,
},
},
{
title: 'XXXXXXX',
type: Button,
attrs: {
type: 'primary',
htmlType: 'submit',
buttonText: '查询',
},
},
]);
}
// index.vue <script setup lang='ts'>
const autoCompleteLoading = ref<boolean>(false);
const dataNameList = ref<{ label: string, value: any }[]>([]);
const configs = useConfigComputed(
autoCompleteLoading,
dataNameList,
)