基于vue3 element plus form 表单的二次封装

记录一下开发中对form表单的二次封装及使用


前言

        在我们的日常工作中经常会使用到form表来满足提交需求,那么将form表单封装成组件将会大大提高我们的工作效率,避免更多重复代码的书写,只需一些简单的配置就轻松实现。


一、form表单的封装

<template>
  <div class='form'>
    <el-form
      :label-position="labelPosition"
      :inline="inline"
      :rules="rules"
      ref="formRef"
      :label-width="labelWidth"
      :model="fieldForm"
      :class="customClass"
    >
      <el-form-item
        v-for="(item, index) in fieldList"
        :key="index"
        :prop="item.field"
        :validate-on-rule-change="false"
        :style="{
          flexBasis: item.percent ? item.percent : item.percent ?     
         `${item.percent}%!important` : '',
          width: item.width && isNaN(Number(item.width)) ? item.width +'!important'|| '' 
          : item.width + 'px!important' || '',
        }"
        :class="[item.className || '', `is-${item.formType}`]"
        :rules="getRules(item)"
      >
        <template
          #label
          v-if="item.name"
        >
          <div style="display: inline">
            <a :style="{ color: item.labelColor || '' }">{{ item.name || '' }}</a>
          </div>
        </template>
        <template v-if="item.formType && item.mold && !item.hidden">
          <component
            :is="com['M' + item.mold]"
            v-model="fieldForm[item.field]"
            :item="item"
            :index="index"
            :disabled="disabled"
            :size="size"
            :field-form="fieldForm"
            @change="commonChange"
          />
        </template>
      </el-form-item>
    </el-form>
  </div>
</template>
<script lang="ts" setup>
import { toRefs } from 'vue';
//引入组件
import MInput from './components/MInput.vue';
import MDate from './components/MDate.vue';
import MSelect from './components/MSelect.vue';

//这是自定义表单验证规则
import GenerateRules from './GenerateRules.ts';

//接收父组件传递的参数
const props = defineProps({
  customClass: {
    type: String,
    default: 'is-three-columns',
  },
  rules: {
    type: Object,
    default: () => {
      return {};
    },
  },
  fieldForm: {
    type: Object,
    default: () => {
      return {};
    },
  },
  labelWidth: {
    type: Number,
    default: 90
  },
  fieldList: {
    type: Array,
    default: () => [],
  },
  // 行内表单
  inline: {
    type: Boolean,
    default: false,
  },
  // 对齐方式 'right' | 'left' | 'top'
  labelPosition: {
    type: String,
    default: 'right',
  },
  size: {
    type: String,
    default: 'default',
  },
  disabled: {
    type: Boolean,
    default: false,
  },
});
const { customClass, rules, fieldForm, labelWidth, fieldList } = toRefs(props);

// 定义组件
const com: Record<string, any> = {
  MInput: MInput,
  MDate: MDate,
  MSelect: MSelect,
};

//事件传递
const $emit = defineEmits(['change']);

/**
 * 常规组件change事件
 * @param item 配置数据
 * @param index 下标
 * @param value 选中/输入的值
 * @param data 选中的整条数据
 */
const commonChange = (item, index, value, data) => {
  $emit('change', item, index, value, data);
};

//自定义验证规则
const { getRules } = GenerateRules();

</script>

二、components 

1.MDate.vue

代码如下(示例):

<template>
  <el-date-picker
    v-if="item.formType == 'date'"
    v-model="fieldForm[item.field]"
    :disabled="item.disabled || disabled"
    clearable
    style="width: 100%"
    type="date"
    :size="size"
    :editable="true"
    value-format="YYYY-MM-DD"
    :placeholder="item.placeholder || '选择日期'"
    @click.stop
    @change="commonChange(item, index, $event)"
  />
  <el-date-picker
    v-else-if="item.formType == 'month'"
    v-model="fieldForm[item.field]"
    :disabled="item.disabled || disabled"
    clearable
    style="width: 100%"
    type="month"
    :size="size"
    value-format="YYYY-MM"
    :placeholder="item.placeholder || '选择月份'"
    @change="commonChange(item, index, $event)"
  />
  <el-date-picker
    v-else-if="item.formType == 'year'"
    v-model="fieldForm[item.field]"
    :disabled="item.disabled || disabled"
    clearable
    style="width: 100%"
    type="year"
    :size="size"
    value-format="YYYY"
    :placeholder="item.placeholder || '选择年份'"
    @change="commonChange(item, index, $event)"
  />
  <el-date-picker
    v-else-if="item.formType == 'dateRange'"
    v-model="fieldForm[item.field]"
    :disabled="item.disabled || disabled"
    :type="item.dateType || 'daterange'"
    :value-format="item.dateValueFormat || 'YYYY-MM-DD'"
    :picker-options="item.pickerOptions || ''"
    clearable
    :size="size"
    style="width: 100%"
    :start-placeholder="item.startPlaceholder || '开始日期'"
    :end-placeholder="item.endPlaceholder || '结束日期'"
    @change="commonChange(item, index, $event)"
  />
  <el-date-picker
    v-else-if="item.formType == 'datetime'"
    v-model="fieldForm[item.field]"
    :disabled="item.disabled || disabled"
    clearable
    style="width: 100%"
    :size="size"
    type="datetime"
    value-format="YYYY-MM-DD HH:mm:ss"
    :placeholder="item.placeholder || '选择日期'"
    @change="commonChange(item, index, $event)"
  />
</template>
<script lang="ts" setup>
import { toRefs, PropType } from 'vue';
import Hooks from '../Hooks.ts';
// 定义defineProps
const props = defineProps({
  modelValue: {
    type: [Number, String],
    default: '',
  },
  item: {
    type: Object,
    default: () => {},
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  size: {
    type: String as PropType <''| 'large' | 'small'>,
    default: 'small',
  },
  index: {
    type: Number,
    default: null,
  },
  fieldForm: {
    type: Object,
    default: () => {},
  },
});
const { fieldForm } = toRefs(props);

// 定义$emit
const $emit = defineEmits(['change']);

const { commonChange } = Hooks($emit);
</script>

2.MInput.vue

代码如下(示例):

<template>
  <el-input
    v-if="item.formType == 'text' && !item.hidden"
    v-model="fieldForm[item.field]"
    :size="size || ''"
    :disabled="item.disabled || disabled"
    :maxlength="item.maxlength"
    :placeholder="item.placeholder"
    :type="item.formType"
    :show-word-limit="item.showLimit"
    :title="fieldForm[item.field]"
    clearable
    :readonly="item.readonly"
    :name="item.field"
    @input="commonChange(item, index, $event)"
  />
  <el-input-number
    v-else-if="['floatnumber', 'number'].includes(item.formType)"
    v-model="fieldForm[item.field]"
    :placeholder="item.placeholder"
    :disabled="item.disabled || disabled"
    :controls="item.controls || false"
    :controls-position="item.position || 'right'"
    clearable
    :size="size"
    @change="commonChange(item, index, $event)"
  />
  <el-input
    v-else-if="item.formType === 'textarea'"
    v-model="fieldForm[item.field]"
    :disabled="item.disabled || disabled"
    :rows="item.rows || 2"
    :autosize="!item.autosize && { minRows: item.rows || 2 }"
    :maxlength="item.maxlength || 800"
    :placeholder="item.placeholder"
    :type="item.formType"
    clearable
    :size="size"
    :resize="item.resize || 'none'"
    @input="commonChange(item, index, $event)"
  />
</template>
<script lang="ts" setup>
import { toRefs} from 'vue';
import Hooks from '../Hooks.ts';

// 定义props 
const props = defineProps({
  modelValue: {
    default: '',
  },
  item: {
    type: Object,
    default: () => {},
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  size: {
    type: String,
    default: 'small',
  },
  index: {
    type: Number,
    default: null,
  },
  fieldForm: {
    type: Object,
    default: () => {},
  },
});
const { fieldForm } = toRefs(props);

// 定义$emit
const $emit = defineEmits(['change']);

const { commonChange } = Hooks($emit);
</script>

3.MInput.vue

代码如下(示例):

<template>
  <el-select
    v-if="item.formType == 'select'"
    v-model="fieldForm[item.field]"
    :disabled="item.disabled || disabled"
    :clearable="item.clearable || true"
    :placeholder="item.placeholder"
    :multiple="item.multiple"
    :collapse-tags="item.collapse"
    :collapse-tags-tooltip="item.tooltip"
    :size="size"
    :value-key="item.props?.valueKey || 'id'"
    filterable
    style="width: 100%"
    @change="commonChange(item, index, $event)"
  >
    <el-option
      v-for="(items, i) in item.basicGroup
        ? getGroupValue(item)
        : item.setting"
      :key="i"
      :label="!isEmptyValue(items[item.props?.value || 'value']) ?                 
      items[item.props?.label || 'label'] || items.name : items"
      :value="!isEmptyValue(items[item.props?.value || 'value']) ? 
      items[item.props?.value || 'value'] : items"
    />
  </el-select>
  <el-radio-group
    v-else-if="item.formType == 'radio-group'"
    :size="size"
    :disabled="item.disabled || disabled"
    v-model="fieldForm[item.field]"
    @change="commonChange(item, index, $event)"
  >
    <el-radio
      :label="items.value"
      v-for="(items, i) in item.setting"
      :key="i"
    >{{ items.label }}</el-radio>
  </el-radio-group>
  <el-switch
    v-else-if="item.formType == 'switch'"
    v-model="fieldForm[item.field]"
    inline-prompt
    :size="size"
    :active-color="item.active&&item.active.color || '#67c23a'"
    :inactive-color="item.inactive&&item.inactive.color || '#f56c6c'"
    :active-text="item.active&&item.active.text || '是'"
    :inactive-text="item.inactive&&item.inactive.text || '否'"
  />
  <el-checkbox-group
    v-else-if="item.formType == 'checkbox-group'"
    :size="size"
    :disabled="item.disabled || disabled"
    v-model="fieldForm[item.field]"
    @change="commonChange(item, index, $event)">
    <el-checkbox
      :label="items.label"
      v-for="(items, i) in item.setting"
      :key="i"
    />
  </el-checkbox-group>
</template>
<script lang="ts" setup>
import Hooks from '../Hooks.ts';
import { toRefs} from 'vue';

// 定义props 
const props = defineProps({
  modelValue: {
    default: '',
  },
  item: {
    type: Object,
    default: () => { },
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  size: {
    type: String,
    default: 'default',
  },
  index: {
    type: Number,
    default: null,
  },
  fieldForm: {
    type: Object,
    default: () => { },
  },
});
const { fieldForm, size } = toRefs(props);

// 定义$emit
const $emit = defineEmits(['change']);

const { commonChange, isEmptyValue } = Hooks($emit);
</script>

4.Hooks.ts

代码如下(示例):

export default function ($emit: any) {
    
 /**
 * 常规组件change事件
 */
  const commonChange = (item: any, index: any, value: any, data: any) => {
    $emit('change', item, index, value, data);
  };


  /**
  * 判断是空值
  */
  const isEmptyValue = (value: any) => {
    return value === null || value == undefined;
  };

  return {
    commonChange,
    isEmptyValue,
  };
}

5.Hooks.ts

代码如下(示例):

/**
 * Created by yxk at 2020/6/1
 * 单个自定义字段生成校验规则
 */

//类型判断 这些就不贴出来啦 
import { isEmpty, isObject, isArray } from '@/utils/types.ts';
export default function () {
  /**
   * 唯一性校验
   * @param data
   * @returns {Promise<unknown>}
   * @constructor
  */
  const UniquePromise = (data: any) => {
    return new Promise((resolve: any) => {
      resolve(data);
    });
  };
  /**
 * 生成单个字段的验证规则
 * @param item 字段信息
 * @returns {[]}
 */
  const getRules = (item: any) => {
    const tempList = [];
    // 验证必填
    if (item.isNull === 1) {
      if (item.formType === 'detail_table') {
        tempList.push({
          validator: ({ item }: any, value: any, callback: any) => {
            if (getDetailTableIsEmpty(item.fieldExtendList, value)) {
              callback(new Error(item.name + '不能为空'));
            } else {
              callback();
            }
          },
          item: item,
          trigger: ['blur', 'change']
        });
      } else if (item.formType === 'checkbox' || item.formType === 'dep-select') {
        tempList.push({
          validator: ({ item }: any, value: any, callback: any) => {
            if (!isArray(value) || value.length === 0) {
              callback(new Error(item.name + '不能为空'));
            } else {
              const emptyObj = value.find((valueItem: any) => isEmpty(valueItem));
              emptyObj === '' ? callback(new Error(item.name + '不能为空')) : callback();
            }
          },
          item: item,
          trigger: ['blur', 'change']
        });
      } else {
        tempList.push({
          required: true,
          message: item.name + '不能为空',
          trigger: ['blur', 'change']
        });
      }
    }

    // 验证唯一
    if (item.isUnique === 1 && UniquePromise) {
      const validateUnique = (rule: any, value: any, callback: any) => {
        if (isEmpty(value)) {
          callback();
        } else {
          // 验证唯一
          UniquePromise({
            field: item,
            rule: rule,
            value: value
          }).then(() => {
            callback();
          }).catch(msg => {
            callback(new Error(msg || '验证出错'));
          });
        }
      };
      tempList.push({
        validator: validateUnique,
        item: item,
        trigger:
          item.formType === 'checkbox' || item.formType === 'select' ?
            ['change', 'blur'] :
            ['blur']
      });
    }

    return tempList;
  };

  /**
  * 获取数值规则
  */
  const getNumberRule = (rule: any, value: any, callback: any) => {
    const field = rule.item;

    const arr = String(value).split('.');

    const len = String(value)
      .replace('.', '')
      .replace('-', '')
      .length;
    const maxlength = field.formType === 'percent' ? 10 : 15;

    const min = isEmpty(field.minNumRestrict) ? -Infinity : Number(field.minNumRestrict 
               || -Infinity);
    const max = isEmpty(field.maxNumRestrict) ? Infinity : Number(field.maxNumRestrict || 
    Infinity);

    if (len > maxlength) {
      callback(new Error(`最多支持${maxlength}位数字(包含小数位)`));
    } else if (isEmpty(field.precisions) && String(value).includes('.')) {
      // null 不支持小数  0 不限制小数位
      callback(new Error(`不支持小数`));
    } else if (arr.length > 1 && arr[1].length > Number(field.precisions)) {
      callback(new Error(`小数位不能大于${field.precisions}`));
    } else if (value < min) {
      callback(new Error(`不能小于${min}`));
    } else if (value > max) {
      callback(new Error(`不能大于${max}`));
    } else {
      callback();
    }
  };

  /**
  * 判断明细表格是否是空
  * @param {*} fieldList
  * @param {*} valueObj
  */
  const getDetailTableIsEmpty = (fieldList: any, valueObjs: any) => {
    for (let index = 0; index < valueObjs.length; index++) {
      const valueObj = valueObjs[index];
      if (judgeFormValueIsEmpty(fieldList, valueObj)) {
        return true;
      }
    }
    return false;
  };
  /**
  * 判断对象值是否是空
  */
  const judgeFormValueIsEmpty = (fieldList: any, valueObj: any) => {
    for (let index = 0; index < fieldList.length; index++) {
      const field = fieldList[index];
      const value = valueObj[field.fieldName];
      if (field.formType === 'location') {
        if (isObject(value) && (!isEmpty(value.lat) || !isEmpty(value.lng) ||                     
        !isEmpty(value.address))) {
          return false;
        }
      } else if (!isEmpty(value)) {
        return false;
      }
    }
    return true;
  };
  return {
    UniquePromise,
    getRules
  };
}

6.form表单的使用

<div class="form_container">
      <Form
        :field-list="fieldList"
        :field-form="fieldForm" />
   </div>
const formData = reactive({
  fieldForm: {},
  fieldList: [
    {
      field: 'name',
      name: '活动名称',
      mold: 'Input',
      formType: 'text',
      // 检验唯一性
      isNull:1
    },
    {
      field: 'region',
      name: '活动区域',
      mold: 'Select',
      formType: 'select',
      setting: [
        {
          label: '区域一',
          value: 0
        },
        {
          label: '区域二',
          value: 0
        }
      ]
    },
    {
      field: 'date',
      name: '活动区域',
      mold: 'Date',
      formType: 'datetime',
    },
    {
      field: 'deliver',
      name: '即时配送',
      mold: 'Select',
      formType: 'switch',
    },
    {
      field: 'type',
      name: '活动性质',
      mold: 'Select',
      formType: 'checkbox-group',
      setting: [
        {
          label: '美食/餐厅线上活动',
        },
        {
          label: '地推活动',
        },
        {
          label: '线下主题活动',
        },
        {
          label: '单纯品牌曝光',
        },
      ]
    },
    {
      field: 'resource',
      name: '资源',
      mold: 'Select',
      formType: 'radio-group',
      setting: [
        {
          label: '线上品牌商赞助',
          value:0
        },
        {
          label: '线下场地免费',
          value:1
        },
      ]
    },
    {
      field: 'desc',
      name: '活动形式',
      mold: 'Input',
      formType: 'textarea',
      rows:3
    },
  ]
});

 出来的效果就是这样子滴

 

总结

         以上就是form表单的全部代码啦 如果对您有用就浅浅的点个赞吧

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值