在使用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,
};
}
- 最终的演示效果