赞
踩
前置条件:
vue版本 v3.3.11
ant-design-vue版本 v4.1.1
vue3 + antd 封装动态表单组件(一)是基础版本,但是并不好用, 因为需要配置很多表单项的schem组件属性componentProps,如果很多地方用到这些表单项,就需要大量的重复工作去配置这些相同的组件属性。
因此,本篇文章新增了默认组件属性和表单项配置功能,大大简化了动态表单schema配置,以及新增了各表单项空值配置,请根据实际的业务场景进行配置;
动态组件配置文件config.js
import { Input, Textarea, InputNumber, Select, RadioGroup, CheckboxGroup, DatePicker } from 'ant-design-vue'; // 表单域组件类型 export const componentsMap = { Text: Input, Textarea, Number: InputNumber, Select, Radio: RadioGroup, Checkbox: CheckboxGroup, DatePicker, } // 配置各组件属性默认值,相关配置项请查看ant-design官网各组件api属性配置 export const defaultComponentProps = { Text: { allowClear: true, bordered: true, disabled: false, showCount: true, maxlength: 20, }, Textarea: { allowClear: true, autoSize: { minRows: 4, maxRows: 4 }, showCount: true, maxlength: 200, style: { width: '100%' } }, Select: { allowClear: true, bordered: true, disabled: false, showArrow: true, optionFilterProp: 'label', optionLabelProp: 'label', showSearch: true, }, DatePicker: { allowClear: true, bordered: true, disabled: false, format: 'YYYY-MM-DD', picker: 'date', style: { width: '100%' } }, }
dynamic-form.vue组件
<template> <div> <a-form ref="formRef" :model="formModel" v-bind="$attrs"> <a-form-item :name="item.field" :label="item.label" v-for="item in formSchema" :key="item.field" v-bind="item.formItemProps" > <span v-if="item.loading" ><LoadingOutlined style="margin-right: 4px" />数据加载中...</span > <component v-else :is="componentsMap[item.component]" v-bind="item.componentProps" v-model:value="formModel[item.field]" /> </a-form-item> </a-form> </div> </template> <script setup> import { ref, watch, onMounted, computed } from "vue"; import { componentsMap, defaultComponentProps } from "./config.js"; import { LoadingOutlined } from "@ant-design/icons-vue"; import dayjs from "dayjs"; const props = defineProps({ // 表单项配置 schema: { type: Array, default: () => [], }, // 表单model配置,一般用于默认值、回显数据 model: { type: Object, default: () => ({}), }, // 组件属性配置 componentProps: { type: Object, default: () => ({}), }, }); const formRef = ref(null); const formSchema = ref([]); const formModel = ref({}); // 组件placeholder const getPlaceholder = (x) => { let placeholder = ""; switch (x.component) { case "Text": case "Textarea": placeholder = `请输入${x.label}`; break; case "RangePicker": placeholder = ["开始时间", "结束时间"]; break; default: placeholder = `请选择${x.label}`; break; } return placeholder; }; // 组件属性componentProps, 注意优先级:组件自己配置的componentProps > props.componentProps > config.js中的componentProps const getComponentProps = (x) => { if (!x?.componentProps) x.componentProps = {}; // 使得外层可以直接配置options if (x.hasOwnProperty("options") && x.options) { x.componentProps.options = []; const isFunction = typeof x.options === "function"; const isArray = Array.isArray(x.options); if (isFunction || isArray) { // 函数时先赋值空数组 x.componentProps.options = isFunction ? [] : x.options; } } return { placeholder: x?.componentProps?.placeholder ?? getPlaceholder(x), ...(defaultComponentProps[x.component] || {}), // config.js带过来的基础componentProps默认配置 ...(props.componentProps[x.component] || {}), // props传进来的组件componentProps配置 ...x.componentProps, // 组件自身的componentProps }; }; // 表单属性formItemProps const getFormItemProps = (x) => { let result = { ...(x.formItemProps || {}) }; // 使得外层可以直接配置required必填项 if (x.hasOwnProperty("required") && x.required) { result.rules = [ ...(x?.formItemProps?.rules || []), { required: true, message: getPlaceholder(x), trigger: "blur", }, ]; } return result; }; // 各组件为空时的默认值 const getDefaultEmptyValue = (x) => { let defaultEmptyValue = ""; switch (x.component) { case "Text": case "Textarea": defaultEmptyValue = ""; break; case "Select": defaultEmptyValue = ["tag", "multiple"].includes(x?.componentProps?.mode) ? [] : undefined; case "Cascader": defaultEmptyValue = x?.value?.length ? x.value : []; default: defaultEmptyValue = undefined; break; } return defaultEmptyValue; }; // 格式化各组件值 const getValue = (x) => { let formatValue = x.value; if (!!x.value) { switch (x.component) { case "DatePicker": formatValue = dayjs(x.value, "YYYY-MM-DD"); break; default: formatValue = x.value; break; } } return formatValue; }; const getSchemaConfig = (x) => { return { ...x, componentProps: getComponentProps(x), formItemProps: getFormItemProps(x), value: x.value ?? getDefaultEmptyValue(x), }; }; const setFormModel = () => { formModel.value = formSchema.value.reduce((pre, cur) => { if (!pre[cur.field]) { // 表单初始数据(默认值) pre[cur.field] = getValue(cur); return pre; } }, {}); }; // 表单初始化 const initForm = () => { formSchema.value = props.schema.map((x) => getSchemaConfig(x)); // model初始数据 setFormModel(); // options-获取异步数据 formSchema.value.forEach(async (x) => { if (x.options && typeof x.options === "function") { x.loading = true; x.componentProps.options = await x.options(formModel.value); x.loading = false; } }); }; onMounted(() => { initForm(); watch( () => props.model, (newVal) => { // model重新赋值给formSchema,注意:model会覆盖schema配置的value值 formSchema.value.forEach((x) => { for (const key in newVal) { if (x.field === key) { x.value = newVal[key]; } } }); setFormModel(); }, { immediate: true, deep: true, } ); }); const hasLoadingSchema = computed(() => formSchema.value.some((x) => x.loading) ); // 表单验证 const validateFields = () => { if (hasLoadingSchema.value) { console.log("正在加载表单项数据..."); return; } return new Promise((resolve, reject) => { formRef.value .validateFields() .then((formData) => { resolve(formData); }) .catch((err) => reject(err)); }); }; // 表单重置 const resetFields = (isInit = true) => { // 是否清空默认值 if (isInit) { formModel.value = {}; } formRef.value.resetFields(); }; // 暴露方法 defineExpose({ validateFields, resetFields, }); </script>
使用dynamic-form.vue组件,注意比较vue3 + antd 封装动态表单组件(一)中的表单项的schema配置
<template> <div style="padding: 200px"> <DynamicForm ref="formRef" :schema="schema" :model="model" :labelCol="{ span: 4 }" :wrapperCol="{ span: 20 }"/> <div style="display: flex; justify-content: center"> <a-button @click="handleReset(true)">重置(全部清空)</a-button> <a-button style="margin-left: 50px" @click="handleReset(false)" >重置</a-button > <a-button type="primary" style="margin-left: 50px" @click="handleSubmit" >提交</a-button > </div> </div> </template> <script setup> import DynamicForm from "@/components/form/dynamic-form.vue"; import { ref } from "vue"; import dayjs from "dayjs"; import { getRemoteData } from "@/common/utils"; const formRef = ref(null); const schema = ref([ { label: "姓名", field: "name", component: "Text", required: true, }, { label: "性别", field: "sex", component: "Radio", options: [ { value: 1, label: "男" }, { value: 2, label: "女" }, { value: 3, label: "保密" }, ], value: 1, required: true, }, { label: "生日", field: "birthday", component: "DatePicker", required: true, }, { label: "兴趣", field: "hobby", component: "Checkbox", options: async () => { // 后台返回的数据list const list = [ { value: 1, label: "足球" }, { value: 2, label: "篮球" }, { value: 3, label: "排球" }, ]; return await getRemoteData(list); }, }, { label: "国家", field: "country", component: "Select", options: [ { value: 1, label: "中国" }, { value: 2, label: "美国" }, { value: 3, label: "俄罗斯" }, ], }, { label: "简介", field: "desc", component: "Textarea", }, ]); const model = ref({ name: "百里守约" }); // 提交 const handleSubmit = async () => { const formData = await formRef.value.validateFields(); if (formData.birthday) { formData.birthday = dayjs(formData.birthday).format("YYYY-MM-DD"); } console.log("提交信息:", formData); }; // 重置 const handleReset = (isInit) => { formRef.value.resetFields(isInit); }; </script>
效果图

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。