vue3 + antd 封装动态表单组件(一)

前置条件:

vue版本 v3.3.11
ant-design-vue版本 v4.1.1

创建动态组件配置文件config.js

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,
}

创建dynamic-form.vue组件

html 复制代码
<template>
  <div>
    <a-form ref="formRef" :model="formModel">
      <a-form-item
        :name="item.field"
        :label="item.label"
        v-for="item in formSchema"
        :key="item.field"
        v-bind="item.formItemProps"
      >
        <component
          :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 } from "vue";
import { componentsMap } from "./config.js";

const props = defineProps({
  // 表单项配置
  schema: {
    type: Array,
    default: () => [],
  },
  // 表单model配置,一般用于默认值、回显数据
  model: {
    type: Object,
    default: () => ({}),
  },
});

const formRef = ref(null);

const formSchema = ref([]);
const formModel = ref({});

// 表单初始化
const initForm = () => {
  formSchema.value = props.schema.map((x) => {
    return {
      ...x,
    };
  });

  // model初始数据
  formModel.value = props.schema.reduce((pre, cur) => {
    if (!pre[cur.field]) {
      // 表单初始数据(默认值)
      pre[cur.field] = cur.value;
      return pre;
    }
  }, {});
};

onMounted(() => {
  initForm();
  // 构建表单项后才回显model值,model会覆盖schema配置的value值
  watch(
    () => props.model,
    (newVal) => {
      formModel.value = { ...formModel.value, ...newVal };
    },
    {
      immediate: true,
      deep: true,
    }
  );
});

// 表单验证
const validateFields = () => {
  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组件

html 复制代码
<template>
  <div style="padding: 200px">
    <DynamicForm ref="formRef" :schema="schema" :model="model" />
    <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";
const formRef = ref(null);

const schema = ref([
  {
    label: "姓名",
    field: "name",
    component: "Text",
    componentProps: {
      allowClear: true,
      showCount: true,
      maxlength: 20,
      style: {
        width: "500px",
      },
    },
    formItemProps: {
      rules: [
        {
          required: true,
          message: "请输入姓名",
          trigger: "blur",
        },
      ],
    },
  },
  {
    label: "性别",
    field: "sex",
    component: "Radio",
    componentProps: {
      options: [
        { value: 1, label: "男" },
        { value: 2, label: "女" },
        { value: 3, label: "保密" },
      ],
    },
    formItemProps: {
      rules: [
        {
          required: true,
          message: "请选择性别",
          trigger: "blur",
        },
      ],
    },
    value: 1,
  },
  {
    label: "生日",
    field: "birthday",
    component: "DatePicker",
    formItemProps: {
      rules: [
        {
          required: true,
          message: "生日日期不能为空",
          trigger: "blur",
        },
      ],
    },
  },
  {
    label: "兴趣",
    field: "hobby",
    component: "Checkbox",
    componentProps: {
      options: [
        { value: 1, label: "足球" },
        { value: 2, label: "篮球" },
        { value: 3, label: "排球" },
      ],
    },
  },
  {
    label: "国家",
    field: "country",
    component: "Select",
    componentProps: {
      allowClear: true,
      options: [
        { value: 1, label: "中国" },
        { value: 2, label: "美国" },
        { value: 3, label: "俄罗斯" },
      ],
    },
  },
  {
    label: "简介",
    field: "desc",
    component: "Textarea",
    componentProps: {
      allowClear: true,
      autoSize: {
        minRows: 4,
        maxRows: 4,
      },
      maxlength: 200,
      showCount: true,
    },
  },
]);
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>

效果图

相关推荐
AI_零食24 分钟前
番茄钟鸿蒙PC Electron框架完成:状态机、定时器管理与专注力工具设计
前端·javascript·华为·electron·开源·鸿蒙·鸿蒙系统
提子拌饭13325 分钟前
逛三园游戏——基于鸿蒙PC Electron框架实现
前端·javascript·游戏·华为·electron·鸿蒙
llz_11228 分钟前
web-第三次课后作业
前端·后端·web
遗憾随她而去.42 分钟前
Web地图全体系深度梳理:引擎、数据源、图层、投影核心知识
前端
爱因斯坦乐1 小时前
Vue项目整合
前端·javascript·vue.js
FlyWIHTSKY1 小时前
TS、TSX、JS、JSX 文件扩展名详解
开发语言·javascript·ecmascript
无风听海1 小时前
IndexedDB 深度指南 浏览器中的事务型对象数据库
前端·数据库
ct9782 小时前
组件间的通信
前端·javascript·vue.js
左手吻左脸。3 小时前
Vue 全栈面试题大全(2026 最新版最详细)
前端·javascript·vue.js
Aphasia3113 小时前
手写KeepAlive组件
前端·react.js·面试