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

前提条件
  1. vue2 版本@2.6.14
  2. Ant Design of Vue 版本@1.7.8
封装步骤
  1. src目录创建components文件夹,并创建dynamic-form.vue文件
  2. dynamic-form.vue文件封装动态表单控件,根据传入的参数类型显示指定的表单控件,如文本框、下拉框、多选框等等
  <div>
    <a-form :form="form" ref="form" :label-col="labelCol" :wrapper-col="wrapperCol">
      <div v-for="field in fieldItemOptions" :key="field.key">
        <a-form-item :label="field.label" :required="field.required">
          
          <template v-if="field.type === 'text'">template>
          
		  
          <template v-else-if="field.type === 'textarea'">template>

          
          <template v-else-if="field.type === 'select'">template>

          
          <template v-else-if="field.type === 'checkbox'">template>

          
          <template v-else-if="field.type === 'radio'">template>

		  
        a-form-item>
      div>
    a-form>
  div>
  1. 完善表单控件内容,完成一个简单的动态表单组件
<template>
  <div>
    <a-form :form="form" ref="form" :label-col="labelCol" :wrapper-col="wrapperCol">
      <div v-for="field in fieldItemOptions" :key="field.key">
        <a-form-item :label="field.label" :required="field.required">
          
          <template v-if="field.type === 'text'">
            <a-input
              v-decorator="[
              field.key,
              {
                rules: [{ required: field.required, message: `${field.label}不能为空` }],
                initialValue: field.value 
              },
            ]"
              :placeholder="`请输入${field.label}`"
            >a-input>
          template>

          
          <template v-else-if="field.type === 'textarea'">
            <a-textarea
              v-decorator="[
              field.key,
              {
                rules: [{ required: field.required, message: `${field.label}不能为空` }],
                initialValue: field.value 
              },
            ]"
              :placeholder="`请输入${field.label}`"
              :auto-size="{ minRows: field.minRows || 5, maxRows: field.maxRows || 8 }"
              :maxLength="field.max || 256"
            />
          template>

          
          <template v-else-if="field.type === 'select'">
            <a-select
              :placeholder="`请选择${field.label}`"
              v-decorator="[
              field.key,
              {
                rules: [{ required: field.required, message: `请选择${field.label}` }],
                initialValue: field.value 
              },
            ]"
            >
              <a-select-option
                v-for="option in field.options"
                :key="option.value"
                :value="option.value"
              >{{option.label}}a-select-option>
            a-select>
          template>

          
          <template v-else-if="field.type === 'checkbox'">
            <a-checkbox-group
              v-decorator="[
              field.key,
              {
                rules: [{ required: field.required, message: `请选择${field.label}` }],
                initialValue: field.value
              },
            ]"
            >
              <a-checkbox
                v-for="option in field.options"
                :key="option.value"
                :value="option.value"
                :style="{width: field.width}"
              >{{option.label}}a-checkbox>
            a-checkbox-group>
          template>

          
          <template v-else-if="field.type === 'radio'">
            <a-radio-group
              v-decorator="[
              field.key,
              {
                rules: [{ required: field.required, message: `请选择${field.label}` }],
                initialValue: field.value 
              },
            ]"
            >
              <a-radio
                v-for="option in field.options"
                :key="option.value"
                :value="option.value"
              >{{option.label}}a-radio>
            a-radio-group>
          template>
        a-form-item>
      div>
    a-form>
    <a-button type="primary" @click="handleSubmit()">提交a-button>
  div>
template>
<script>
import { deepClone } from "@/common/utils";
export default {
  props: {
    // 表单域配置
    fieldOptions: {
      type: Array,
      default: () => []
    },

    // 编辑时表单回显的默认数据
    model: {
      type: Object,
      default: () => ({})
    },

    // 标签宽度
    labelCol: {
      type: Object,
      default: () => {
        return {
          xs: { span: 24 },
          sm: { span: 6 }
        };
      }
    },

    // 控件宽度
    wrapperCol: {
      type: Object,
      default: () => {
        return {
          xs: { span: 24 },
          sm: { span: 16 }
        };
      }
    }
  },

  watch: {
    fieldOptions: {
      handler(fieldOptions) {
        this.fieldItemOptions = deepClone(fieldOptions);
        this.fieldItemOptions.forEach(c => {
          for (const key in this.model) {
            if (c.key === key) {
              c.value = this.model[key];
            }
          }
        });
      },
      deep: true,
      immediate: true
    }
  },
  data() {
    return {
      fieldItemOptions: [],
      // 表单
      form: this.$form.createForm(this)
    };
  },
  methods: {
    // 提交表单
    handleSubmit() {
      this.form.validateFields((err, formData) => {
        if (err) {
          return;
        }
        // 提交表单逻辑
      });
    }
  },
  created() {}
};
script>
  1. 使用动态表单组件,此时model为空对象,一般用于新增操作
<template>
  <div>
    <dynamic-form
      ref="dynamicForm"
      :fieldOptions="fieldOptions"
      :model="model"
      :labelCol="labelCol"
      :wrapperCol="wrapperCol"
    >dynamic-form>
  div>
template>

<script>
import DynamicForm from "./dynamic-form.vue";
export default {
  components: {
    DynamicForm
  },
  data() {
    return {
      fieldOptions: [
        {
          label: "姓名",
          key: "name",
          value: "",
          type: "text",
          required: true
        },
        {
          label: "性别",
          key: "sex",
          value: 1,
          type: "radio",
          required: true,
          options: [
            {
              value: 1,
              label: "男"
            },
            {
              value: 2,
              label: "女"
            }
          ]
        },
        {
          label: "兴趣爱好",
          key: "hobby",
          value: [],
          type: "checkbox",
          required: true,
          options: [
            {
              value: 1,
              label: "足球"
            },
            {
              value: 2,
              label: "篮球"
            },
            {
              value: 3,
              label: "排球"
            }
          ]
        },
        {
          label: "国家",
          key: "country",
          value: undefined,
          type: "select",
          required: true,
          options: [
            {
              value: 1,
              label: '中国'
            },
             {
              value: 2,
              label: '美国'
            },
             {
              value: 3,
              label: '俄罗斯'
            }
          ]
        },
        {
          label: "个人简介",
          key: "desc",
          value: "",
          type: "textarea",
          required: false
        }
      ],
      // 表单默认内容
      model: {
        // name: '动态表单',
        // sex: 2,
        // hobby: [1, 2], 
        // country: 1,
        // desc: '这是一个简单的例子'
      },
      labelCol: {
        xs: { span: 24 },
        sm: { span: 6 }
      },
      wrapperCol: {
        xs: { span: 24 },
        sm: { span: 16 }
      }
    };
  }
};
script>

vue2 + antd 封装动态表单组件(一)_第1张图片
5. 若表单有默认内容,需要回显,通常用于编辑操作,只需要给model对应的键添加值即可

vue2 + antd 封装动态表单组件(一)_第2张图片
附:深克隆函数deepClone

const getRegExp = (re) => {
  var flags = "";
  if (re.global) flags += "g";
  if (re.ignoreCase) flags += "i";
  if (re.multiline) flags += "m";
  return flags;
};
const isType = (obj, type) => {
  if (typeof obj !== "object") return false;
  const typeString = Object.prototype.toString.call(obj);
  let flag;
  switch (type) {
    case "Array":
      flag = typeString === "[object Array]";
      break;
    case "Date":
      flag = typeString === "[object Date]";
      break;
    case "RegExp":
      flag = typeString === "[object RegExp]";
      break;
    default:
      flag = false;
  }
  return flag;
};
export const deepClone = (parent) => {
  // 维护两个储存循环引用的数组
  const parents = [];
  const children = [];

  const _clone = (parent) => {
    if (parent === null) return null;
    if (typeof parent !== "object") return parent;

    let child, proto;

    if (isType(parent, "Array")) {
      // 对数组做特殊处理
      child = [];
    } else if (isType(parent, "RegExp")) {
      // 对正则对象做特殊处理
      child = new RegExp(parent.source, getRegExp(parent));
      if (parent.lastIndex) child.lastIndex = parent.lastIndex;
    } else if (isType(parent, "Date")) {
      // 对Date对象做特殊处理
      child = new Date(parent.getTime());
    } else {
      // 处理对象原型
      proto = Object.getPrototypeOf(parent);
      // 利用Object.create切断原型链
      child = Object.create(proto);
    }

    // 处理循环引用
    const index = parents.indexOf(parent);

    if (index != -1) {
      // 如果父数组存在本对象,说明之前已经被引用过,直接返回此对象
      return children[index];
    }
    parents.push(parent);
    children.push(child);

    for (let i in parent) {
      // 递归
      child[i] = _clone(parent[i]);
    }

    return child;
  };
  return _clone(parent);
};

拓展

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

你可能感兴趣的:(vue,antd,vue)