东莞网站建设哪里找,海珠区建网站公司,手机就可以直接做设计的网站,有名的网站制作电话传送带#xff1a; vue3 antd 封装动态表单组件#xff08;一#xff09; vue3 antd 封装动态表单组件#xff08;二#xff09; 前置条件#xff1a;
vue版本 v3.3.11 ant-design-vue版本 v4.1.1
我们发现ant-design-vue Input组件和FormItem组件某些属性支持slot插…传送带 vue3 antd 封装动态表单组件一 vue3 antd 封装动态表单组件二 前置条件
vue版本 v3.3.11 ant-design-vue版本 v4.1.1
我们发现ant-design-vue Input组件和FormItem组件某些属性支持slot插槽如何使得我们封装的动态表单组件也支持该功能呢(slot透传)本篇文章主要是解决该问题。
动态组件配置文件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组件
templatediva-form refformRef :modelformModel v-bind$attrsa-form-item:nameitem.field:labelitem.labelv-foritem in formSchema:keyitem.fieldv-binditem.formItemProps!-- 表单form-item插槽, 注意优先级组件formItemProps.slots formItemPropsSlots--templatev-forslot in formItemPropsSlots#[slot.name]slotProps:keyslot.keytemplate v-ifslot.field item.fieldslot :nameslot.key v-bindslotProps/slot/template/templatetemplatev-for(slot, name) in item.formItemProps?.slots || {}#[name]slotProps:key${item.field}_${name}component :isslot v-bindslotProps/component/templatetemplate v-ifitem.slotslot :nameitem.slot v-bindformModel/slot/templatetemplate v-elsespan v-ifitem.loadingLoadingOutlined stylemargin-right: 4px /数据加载中.../spancomponentv-else:iscomponentsMap[item.component]v-binditem.componentPropsv-model:valueformModel[item.field]!-- 表单项组件插槽, 注意优先级组件componentProps.slots componentPropsSlots--templatev-forslot in componentPropsSlots#[slot.name]slotProps:keyslot.keytemplate v-ifslot.field item.fieldslot :nameslot.key v-bindslotProps/slot/template/templatetemplatev-for(slot, name) in item.componentProps?.slots || {}#[name]slotProps:key${item.field}_componentProps_${name}!-- 这里是关键 渲染slot --component :isslot v-bindslotProps/component/template/component/template/a-form-item/a-form/div
/templatescript setup
import { ref, watch, onMounted, computed, useSlots } 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 slots useSlots();// 表单formItem slots
const formItemPropsSlots ref([]);// 表单项组件slots
const componentPropsSlots ref([]);// 用于获取componentProps、formItemProps插槽
const createPropsSlots (type) {// 对象转数组, 这里表单项slots规则为 对应的filed -type- slot名称,可自行定义规则对应字段匹配上即可const slotsArr Object.entries(slots);return slotsArr.filter((x) x[0].indexOf(type) ! -1).map((x) {const slotParams x[0].split(-);return {key: x[0],value: x[1],name: slotParams[2],field: slotParams[0],};});
};
const createSlots () {formItemPropsSlots.value createPropsSlots(formItemProps);componentPropsSlots.value createPropsSlots(componentProps);
};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 {};// 使得外层可以直接配置optionsif (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;}}return formatValue;
};const getSchemaConfig (x) {return {...x,componentProps: getComponentProps(x),formItemProps: getFormItemProps(x),value: x.value ?? getDefaultEmptyValue(x),label:x.formItemProps?.slots?.label ||formItemPropsSlots.value.find((y) y.field x.field)?.field? undefined: x.label,};
};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(() {createSlots();initForm();watch(() props.model,(newVal) {// 重新赋值给formSchemaformSchema.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使用动态表单组件
templatediv stylepadding: 200pxDynamicFormrefformRef:schemaschema:modelmodel:labelCol{ span: 4 }:wrapperCol{ span: 20 }template #country-formItemProps-labelspan stylecolor: green国家/span/template!-- 表单项field为name的slotcomponentProps配置的slot优先级高于此处 --template #name-componentProps-addonAfterspan我是slot/span/templatetemplate #country-componentProps-suffixIconspan我也是slot/span/templatetemplate #someComponentXformModeldivBellFilled stylecolor: red /我是特殊的某某组件/divdiv表单信息:{{ formModel }}/div/template/DynamicFormdiv styledisplay: flex; justify-content: centera-button clickhandleReset(true)重置(全部清空)/a-buttona-button stylemargin-left: 50px clickhandleReset(false)重置/a-buttona-button typeprimary stylemargin-left: 50px clickhandleSubmit提交/a-button/div/div
/templatescript langjsx setup
import DynamicForm from /components/form/dynamic-form.vue;
import { ref, reactive } from vue;
import dayjs from dayjs;
import { getRemoteData } from /common/utils;
import { UserOutlined, BellFilled } from ant-design/icons-vue;
const formRef ref(null);const schema ref([{label: 姓名,field: name,component: Text,required: true,componentProps: {slots: {addonAfter: () UserOutlined /,},},},{label: 性别,field: sex,component: Radio,options: [{ value: 1, label: 男 },{ value: 2, label: 女 },{ value: 3, label: 保密 },],value: 1,required: true,formItemProps: {slots: {label: () div stylecolor: blue性别/div}}},{label: 生日,field: birthday,component: DatePicker,required: true,},{label: 兴趣,field: hobby,component: Checkbox,options: async () {// 后台返回的数据listconst 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,},{label: 插槽组件X,field: someComponentX,slot: someComponentX,},
]);
const model reactive({ name: 百里守约, someComponentB: ok });
// 提交
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效果图 注意这里使用了jsx需要安装相关插件(本人用的前端构建工具是vite) 安装插件
npm install vitejs/plugin-vue-jsx --save在vite.config.js配置该插件