Vue3封装动态Form表单
在使用Vue3的时候,有的时候,经常需要写很多的表单,使用UI组件库的时候,经常需要一大堆的Form和Form-Item;写的多,拷贝的也多,看到网上大佬的优雅代码,醍醐灌顶,改造了如下代码
UI库: @arco-design/web-vue ^2.56.3
vue: ^3.5.12,
- 父组件
<template><div class="container"><!-- 第一种用法,单文件.vue的用法,所有的属性通过props传递过去 --><!-- <FormBuilder ref="formRef" v-model="formData" :fields="fields" :label-col-props="{ span: 2 }":wrapper-col-props="{ span: 20 }" :rules="rules"></FormBuilder> --><!-- 第二种用法,hooks的方式,通过渲染函数进行渲染,h --><FormBuilder2></FormBuilder2><a-button @click="handleSubmit">提交</a-button></div>
</template><script setup lang="ts">
import FormBuilder from '@/components/FormBuilder.vue';
import { Message } from '@arco-design/web-vue';
import useFormBuilder from '@/hooks/useFormBuilder';const formRef = useTemplateRef<InstanceType<typeof FormBuilder>>('formRef')const formData = reactive({name: '张',age: 18,gender: '1',
})const fields = computed(() => ([{componentType: 'input',formItemProps: {label: '姓名',field: 'name',required: true,},componentProps: {placeholder: '请输入姓名',}},{componentType: 'number',isHidden: formData.name === "张三", // ",formItemProps: {label: '年龄',field: 'age',},componentProps: {placeholder: '请输入年龄',}},{componentType: 'select',formItemProps: {label: '性别',field: 'gender',},componentProps: {placeholder: '请选择性别',options: [{ label: '男', value: '1' },{ label: '女', value: '2' }]}}
]))const rules = {name: [{ required: true, message: '请输入姓名' }],age: [{ required: true, message: '请输入年龄' }],gender: [{ required: true, message: '请选择性别' }],
}const handleSubmit = async () => {// const valid = await formRef.value?.validate()const valid = await getFormBuilderRef().value?.validate()if (valid) return Message.error('提交失败')console.log(formData);Message.success('提交成功')
}
const { FormBuilder: FormBuilder2, getRef: getFormBuilderRef } = useFormBuilder({modelValue: formData,fields: fields.value,rules,wrapperColProps: {span: 20},labelColProps: {span: 2}
})
</script><style lang="scss" scoped></style>
- FormBuilder.vue 子组件
<template><a-form ref="formRef" :model="formData" v-bind="$attrs"><a-row><a-col :span="getSpan(item)" v-for="item in filterFields" :key="item.formItemProps.field"><a-form-item v-bind="item.formItemProps"><slot :name="item.formItemProps.field"><component v-model="formData[item.formItemProps.field]" :is="getComponent(item)"v-bind="item.componentProps"></component></slot></a-form-item></a-col></a-row></a-form>
</template><script setup lang="ts" name="FormBuilder">
import { Input, InputNumber, Select } from '@arco-design/web-vue'
import type { ValidatedError } from '@arco-design/web-vue/es/form/interface'
const props = defineProps({fields: {type: Array as PropType<Record<string, any>[]>,default: () => []}
})const formRef = useTemplateRef('formRef')
const formData = defineModel<Record<string, any>>({ required: true })const componentMap = {input: Input,number: InputNumber,select: Select,
}const getComponent = (item: Record<string, any>) => {return componentMap[item.componentType as keyof typeof componentMap]
}// 1. isHidden 为函数每次更新表单时都会执行
// 2. 对fields使用 computed 缓存
// const filterFields = computed(() => {
// return props.fields.filter((item) => {
// return !(typeof item.isHidden === 'function' ? item.isHidden() : item.isHidden)
// })
// })const filterFields = computed(() => {return props.fields.filter((item) => !item.isHidden)
})const getSpan = (item: Record<string, any>) => {return item.span || 12
}type ValidateFieldFnReturn = Promise<undefined | Record<string, ValidatedError>>
defineExpose({validate: () => formRef.value?.validate() as ValidateFieldFnReturn,validateField: ((field: string | string[]) => formRef.value?.validateField(field) as ValidateFieldFnReturn)
})
</script><style lang="scss" scoped></style>
- useFormBuilder.ts hooks文件
import FormBuilder from "@/components/FormBuilder.vue";
import type { SetupContext } from "vue";
type FormBuilderProps = InstanceType<typeof FormBuilder>["$props"];export default function useFormBuilder(props: Record<string, any>) {const formRef = ref<InstanceType<typeof FormBuilder>>();const Component = (_: Record<string, any>, { slots }: SetupContext) => {return h(FormBuilder, { ...(props as FormBuilderProps), ref: formRef }, slots);};return {FormBuilder: Component,getRef: () => formRef,};
}
- 最终的演示效果