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

news2024/10/6 6:51:17

传送带:
vue3 + antd 封装动态表单组件(一)


前置条件:

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>

效果图

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1412229.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

echarts + gauge + 半圆效果

请注意以下配置需要echarts 5.0.0以上版本&#xff0c;4是不行的 option {series: [{center: [50%, 80%],type: gauge,startAngle: 180,endAngle: 0,min: 0,max: 150,axisTick: {show: false},splitLine: {show: false},detail: {color: #3096fe,offsetCenter: [0, -10],form…

Docker部署Stable-Diffusion-webui

前排提示&#xff1a;如果不想折腾&#xff0c;可直接跳到最后获取封装好的容器&#xff0c;一键运行 :D 前言 乘上AI生成的快车&#xff0c;一同看看沿途的风景。 启一个miniconda容器 docker run -itd -v 宿主机内SD项目路径:/tmp --gpus all --ipc host -p 7860:7860 con…

echarts柱状图添加白色柱状图背景+滚动+柱状顶部添加横线

echarts柱状图添加白色柱状图背景滚动 <template><div class"stream-water-wrapper"><div id"biologybgchart" class"bck-chart"></div><div id"biologychart" class"biology-chart"></…

【JS基础】定时器的使用、事件监听

文章目录 前言一、定时器1.1定时器是什么1.2 setInterval函数1.3 关闭定时器clearInterval 二、事件监听2.1 事件监听是什么2.2 事件监听的使用基本语法点击事件鼠标事件焦点事件键盘事件 2.3 事件对象event 总结 前言 JavaScript 中的定时器和事件监听是 Web 开发中至关重要的…

如何在今日头条广告中轻松唤起微信?这个方法你一定不能错过

要在今日头条的广告中调起微信&#xff0c;实现加好友的功能&#xff0c;可以参考以下步骤&#xff1a; 首先&#xff0c;通过搜索引擎找到“数灵通”外链工具的官网&#xff0c;并进入其后台。在后台填写相关参数&#xff0c;生成一条能够跳转到微信的链接。这个链接的作用是…

云手机哪一款好用?

随着海外市场的不断发展&#xff0c;云手机市场也呈现蓬勃的态势&#xff0c;众多云设备软件纷纷涌现。企业在选择云手机软件时&#xff0c;如何找到性能卓越的软件成为一项关键任务。在众多选择中&#xff0c;OgPhone云手机凭借其卓越的性能和独特功能脱颖而出。以下是OgPhone…

机器学习分类模型评价指标总结(准确率、精确率、召回率、Fmax、TPR、FPR、ROC曲线、PR曲线,AUC,AUPR)

为了看懂论文&#xff0c;不得不先学一些预备知识&#xff08;&#xff08;55555 主要概念 解释见图 TP、FP、TN、FN 准确率、精确率&#xff08;查准率&#xff09;、召回率&#xff08;查全率&#xff09; 真阳性率TPR、伪阳性率FPR F1-score2TP/(2*TPFPFN) 最大响应分…

【python】爬取豆瓣影评保存到Excel文件中【附源码】

欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 【往期相关文章】 爬取豆瓣电影排行榜Top250存储到Excel文件中 爬取豆瓣电影排行榜TOP250存储到CSV文件中 爬取知乎热榜Top50保存到Excel文件中 爬取百度热搜排行榜Top50可视化 爬取斗鱼直播照片保存到本地目录 爬…

司铭宇老师:汽车销售培训:汽车销售员培训:汽车销售技巧培训:汽车销售技巧和话术

汽车销售培训&#xff1a;汽车销售员培训&#xff1a;汽车销售技巧培训&#xff1a;汽车销售技巧和话术 汽车销售是一项充满挑战性的工作&#xff0c;它需要销售人员具备良好的沟通技巧、谈判技巧以及产品讲解能力。在这篇文章中&#xff0c;我们将详细探讨汽车销售中的技巧和话…

无状态应用管理Deployment

无状态应用管理Deployment 1、Deployment介绍 Deployment一般用于部署公司的无状态服务。 格式&#xff1a; apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment labels: app: nginx spec: replicas: 3 selector: matchLabels: app: nginx …

openlayers+vue实现缓冲区

文章目录 前言一、准备二、初始化地图1、创建一个地图容器2、引入必须的类库3、地图初始化4、给地图增加底图 三、创建缓冲区1、引入需要的工具类库2、绘制方法 四、完整代码总结 前言 缓冲区是地理空间目标的一种影响范围或服务范围,是对选中的一组或一类地图要素(点、线或面…

2024年最新MacBook苹果电脑安装JDK8、JDK11教程,配置环境变量 + 快速切换JDK版本

本帖发布日期&#xff1a;2024年01月26日&#xff0c;全网最新教程整理。 1、概述 本文主要为在MacBook苹果电脑系统下安装JDK及环境变量配置。 教程并非原创&#xff0c;摘抄自互联网&#xff0c;本人作为更新整理亲测。&#xff08;也算给自己记录一贴&#xff09; 本帖分…

python之异常的捕获、模块、包

目录 1.了解异常 2.异常的捕获 3.异常的传递性 4.模块的概念和导入 5.自定义模块并导入 6.自定义python包 7.安装第三方包 1.了解异常 2.异常的捕获 直接报错了&#xff0c;说明我们捕获的就是名字的异常而没有捕获除0的异常。 这样就可以打印出异常 捕获全部的异常可以使…

HTML-表单

表单 概念&#xff1a;一个包含交互的区域&#xff0c;用于收集用户提供的数据。 1.基本结构 示例代码&#xff1a; <form action"https://www.baidu.com/s" target"_blank" method"get"><input type"text" name"wd&q…

使用代码取大量2*2像素图片各通道均值,存于Exel文件中。

任务是取下图RGB各个通道的均值及标签&#xff08;R, G&#xff0c;B&#xff0c;Label&#xff09;,其中标签由图片存放的文件夹标识。由于2*2像素图片较多&#xff0c;所以将结果放置于Exel表格中&#xff0c;之后使用SVM对他们进行分类。

20.云原生之GitLab集成Runner

云原生专栏大纲 文章目录 GitLab RunnerGitLab Runner 介绍GitLab Runner分类GitLab Runner工作流程 Gitlab集成Gitlab RunnerGitLab Runner 版本选择Runner在CitLab中位置专用Runner在gitlab中位置群组Runner在gitlab中位置共享Runner在gitlab中位置 GitLab部署Gitlab Runner…

Pandas ------ 向 Excel 文件中写入含有 multi-index 和 Multi-column 表头的数据

Pandas ------ 向 Excel 文件中写入含有 multi-index 和 Multi-column 表头的数据 引言正文 引言 之前在 《pandas向已经拥有数据的Excel文件中添加新数据》 一文中我们介绍了如何通过 pandas 向 Excel 文件中写入数据。那么对于含有多表头的数据&#xff0c;我们该如何将它们…

Linux管道学习(无名管道)

目录 1、概述 2、管道的创建 3、管道读写行为 3.1、管道读 3.2、管道写 4、管道用于兄弟进程之间的通讯 在linux中管道有两种&#xff0c;一是无名管道&#xff08;匿名管道&#xff09;&#xff0c;第二种是有名管道&#xff1b;无名管道主要用于有血缘关系的父子进程间…

八、Kotlin 反射

1. 什么是反射 反射是允许在运行时期访问 程序结构 的一类特性&#xff08;程序结构包括&#xff1a;类、接口、方法、属性等&#xff09;。 2. 反射的依赖库 Kotlin 中不仅自己实现了一套 Kotlin 反射的 API&#xff0c;还可以使用 Java 反射的 API。 Kotlin 反射的 API 是…

day33WEB 攻防-通用漏洞文件上传中间件解析漏洞编辑器安全

目录 一&#xff0c;中间件文件解析漏洞-IIS&Apache&Nginx -IIS 6 7 文件名 目录名 -Apache 换行解析 配置不当 1、换行解析-CVE-2017-15715 2、配置不当-.htaccess 配置不当 -Nginx 文件名逻辑 解析漏洞 1、文件名逻辑-CVE-2013-4547 2、解析漏洞-nginx.conf …