封装一个动态表单吧

2,199 阅读6分钟

经常做后台的小伙伴儿可能经常要遭遇各种表单结构,input,select,时间、日期插件等等,各种属性配置到吐,今天就带大家来封装一个可复用表单。表单参考了网上的一些例子,再结合自己的需求,封装了一个自己的业务表单组件。 先上图:

image.png 首先给大家看一下结构布局: 我们现实放了一个form表单。然后利用el-form-item来循环遍历,给每一个el-form-item设置动态label,动态rules(校验规则)以及相对应的prop。 然后我们给每一种类型的表单元素添加动态disabled的属性,控制可编辑状态。 给属性绑定动态表单值,并且添加placeholder提示语; 对于select,checkbox等多值域绑定的表单元素,配置项中提供options属性。 然后我们的图片上传用的是我们之前封装的图片上传组件,传递一个绑定值和一个可编辑状态即可。 最后我们给每个表单元素添加了一个tooltip,用于提示用户每一个表单元素要填写的内容或者提示语。对于每一个label标签,我们也做了最基本的处理,大于四个字我们就给变成4字加省略号,通过tooltip来查看标签名称。

<template>
    <div>
        <el-form :label-position="labelPosition" :inline="inline" :label-width="labelWidth" :model="myForm" ref="customForm">
          <el-form-item v-for="(item,index) in formData" :label="returnLable(item.label)+':'" :rules="item.rules" :key="index" :prop="item.prop">
            <el-tooltip v-if="item.type == 'input'" :effect="effect" :content="'请填写'+item.label" placement="top">
                <!-- prefix-icon 首部图标--><!-- suffix-icon 尾部图标-->
                <el-input v-model="myForm[item.prop]" clearable :suffix-icon="item.sufIcon" :show-password ="item.password" :prefix-icon="item.preIcon" :placeholder="'选择'+item.label" :disabled="item.disabled">
                </el-input>
            </el-tooltip>
            <el-tooltip v-if="item.type == 'select'" :effect="effect" :content="'请选择'+item.label" placement="top">
                <el-select v-model="myForm[item.prop]" filterable clearable :multiple="item.multiple" filterable :placeholder="'选择'+item.label" :disabled="item.disabled">
                    <el-option v-for="o in item.options" :key="o.value" :label="o.label" :value="o.value">
                    </el-option>
                </el-select>
            </el-tooltip>
            <el-tooltip v-if="item.type == 'radio'" :effect="effect" :content="'请选择'+item.label" placement="top">
                <el-radio-group v-model="myForm[item.prop]" :disabled="item.disabled">
                    <el-radio v-for="o in item.options" :label="o.value" :placeholder="'选择'+item.label">{{o.label}}</el-radio>
                </el-radio-group>
            </el-tooltip>
            <el-tooltip v-if="item.type == 'checkbox'" :effect="effect" :content="'请选择'+item.label" placement="top">
                <el-checkbox-group v-model="myForm[item.prop]" :disabled="item.disabled">
                    <el-checkbox v-for="o in item.options" :label="o.value" :placeholder="'选择'+item.label">{{o.label}}</el-checkbox>
                </el-checkbox-group>
            </el-tooltip>
            <el-tooltip v-if="item.type == 'date'" :effect="effect" :content="'请选择'+item.label" placement="top">
                <el-date-picker
                    v-model="myForm[item.prop]" type="date" 
                    value-format="yyyy-MM-dd" 
                    :picker-options="pickerOptions" 
                    :placeholder="'选择'+item.label" 
                    :disabled="item.disabled">
                </el-date-picker>
            </el-tooltip>
            <el-tooltip v-if="item.type == 'datetime'" :effect="effect" :content="'请选择'+item.label" placement="top">
                <el-date-picker
                    v-model="myForm[item.prop]"
                    type="datetime"
                    value-format="yyyy-MM-dd HH:mm:ss"
                    :picker-options="pickerOptions"
                    :placeholder="'选择'+item.label"
                    default-time="12:00:00"
                    :disabled="item.disabled">
                </el-date-picker>
            </el-tooltip>
            <el-tooltip v-if="item.type == 'image'" :effect="effect" :content="'请选择'+item.label" placement="top">
                <upload-image v-model="myForm[item.prop]" :disabled="item.disabled"></upload-image>
            </el-tooltip>
          </el-form-item>
        </el-form>
        <el-form v-if="isHandle" style="padding-left: 20px;">
            <el-form-item>
              <slot name="handle"></slot>
            </el-form-item>
        </el-form>
    </div>
</template>

下面是组件内的方法及属性定义

<script>
import uploadImage from '@/mycomponents/UploadImage/index.vue'
export default {
    components:{uploadImage},
    props:{
        //ref命名
        formName:{
            type:String,
            default:'right'
        },
        //操作栏
        isHandle:{
            type:Boolean,
            default:true
        },
        //表单标签位置
        labelPosition:{
            type:String,
            default:'right'
        },
        //tooltip风格是dark还是light
        effect:{
            type:String,
            default:'light'
        },
        //表单元素是并列同行还是独占一行
        inline:{
            type:Boolean,
            default:true
        },
        //表单标签宽度
        labelWidth:{
            type:String,
            default:'100px'
        },
        //绑定的表单
        myForm:{
            type:Object,
            default:()=>{}
        },
        //表单配置数据
        formData:{
            type:Array,
            default:()=>[]
        }
    },
    data(){
        return {
            pickerOptions : {
              shortcuts: [{
                text: '今天',
                onClick(picker) {
                  picker.$emit('pick', new Date())
                }
              }, {
                text: '昨天',
                onClick(picker) {
                  const date = new Date()
                  date.setTime(date.getTime() - 3600 * 1000 * 24)
                  picker.$emit('pick', date)
                }
              }, {
                text: '一周前',
                onClick(picker) {
                  const date = new Date()
                  date.setTime(date.getTime() - 3600 * 1000 * 24 * 7)
                  picker.$emit('pick', date)
                }
              }]
            }
        }
    },
    methods:{
        //过滤超长标签
        returnLable(label){
            if(label.length>4){
                return label.substring(0,4)+'...'
            }
            return label
        },
        // 表单验证
        async submitForm(prop) {
            try {
                return await this.$refs["customForm"].validate();
            } catch (error) {
                return error;
            }
        },
    }
}
</script>

我们在页面里使用时,只需要调用一下校验方法,看是否通过校验即可,这里的表单校验方法返回的是一个promise,所以 这里用async 和 await搭配使用来renturn校验flag。 再来看我们的组件应用页面;(这里的具体属性可根据element文档自行查看。)

<template>
    <div class="pages-container">
        <dynamic-form :myForm="myForm" :formData="formData" ref="dynamic">
            <div slot="handle">
                <el-button type="primary" @click="onSubmit()">{{submitText}}</el-button>
                <el-button type="warning">取消</el-button>
            </div>
        </dynamic-form>
        <div>提交状态:{{text}}</div>
        <!-- 表单使用说明 -->
        <p>基本使用定义一个myForm(表单绑定字段的对象集合),再定义一个配置项formData(表单需要配置的验证方法、元素类型、元素标签名称等)</p>
        <p>label:'用户名',//表单元素的标签名称</p>
        <p>prop:'name',//表单元素需要绑定的字段</p>
        <p>type:'textInput',//表单元素类型,类型在下方会有说明具体是什么元素</p>
        <p>preIcon:'',//input输入框等元素首部图标,写空就是不显示</p>
        <p>sufIcon:'',//input输入框等元素尾部图标,写空就是不显示</p>
        <p>password:'',//input输入框是否为密码输入</p>
        <p>disabled:'',//表单元素可编辑状态</p>
        <p>rules:Check(true, null, 'blur', '用户名不能为空'),//校验规则,借鉴的是网上一个大神的封装方法。Check中填写的四个参数分别是:是否必填、正则方法名称、触发动作、校验提示语
        </p>
        <p>
            <span>配置项属性type值释义:</span>
            <span>input : 普通输入框</span>
            <span>select : 下拉框</span>
            <span>radio : 单选</span>
            <span>checkbox : 复选</span>
            <span>date : 日期</span>
            <span>dateTime : 日期时间</span>
            <span>image : 图片</span>
        </p>
    </div>
</template>
<script>
import DynamicForm from '@/mycomponents/Form/dynamic.vue'
import { Check } from '@/utils/rule.js'
export default {
components:{DynamicForm},
data(){
  const options = [{
      value: '1',
      label: '黄金糕'
    }, {
      value: '2',
      label: '双皮奶'
    }, {
      value: '3',
      label: '蚵仔煎'
    }, {
      value: '4',
      label: '龙须面'
    }, {
      value: '5',
      label: '北京烤鸭'
  }]
return {
    text:'',
    submitText:'编辑',
    //表单值绑定
    myForm:{
        name:'',
        vscode:'',
        prodType:'',
        prodp:'2',
        commissions:['1','5'],
        commdate:'',
        commdatetime:'',
        images:['https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80'],
    },
    //基本的表单配置项示例、有需要可自行添加其他属性
    formData:[
        {
            label:'用户名',//标签名称
            prop:'name',//字段值
            type:'input',//表单元素类型
            preIcon:'el-icon-search',//首部标签,input元素独有
            rules:Check(true, null, 'blur', '用户名不能为空'),//校验规则
            disabled:true,
        },
        {
            label:'管理员密码',
            prop:'vscode',
            type:'input',
            sufIcon:'',//尾部标签,input元素独有
            password:true,//input是否是密码类型
            rules:Check(true, null, 'blur', '密码不能为空'),
            disabled:true,
        },
        {
            label:'产品类型',
            prop:'prodType',
            type:'select',
            rules:Check(true, null, 'change', '请选择产品类型'),
            options:options,
            multiple:false,//select是否可以多选,多选绑定值要对应变为数组
            disabled:true,
        },
        {
            label:'产品分类',
            prop:'prodp',
            type:'radio',
            rules:Check(true, null, 'change', '请选择产品分类'),
            options:options,//属性选项值
            disabled:true,
        },
        {
            label:'订单选择',
            prop:'commissions',
            type:'checkbox',
            rules:Check(true, null, 'change', '请订单选择'),
            options:options,//属性选项值
            disabled:true,
        },
        {
            label:'订单日期',
            prop:'commdate',
            type:'date',
            rules:Check(true, null, 'change', '请选择订单日期'),
            disabled:true,
        },
        {
            label:'订单日期时间',
            prop:'commdatetime',
            type:'datetime',
            rules:Check(true, null, 'change', '请选择订单日期时间'),
            disabled:true,
        },
        {
            label:'订单图标',
            prop:'images',
            type:'image',
            rules:Check(true, null, 'change', '请选择订单图标'),
            disabled:true,
        }]
    }
},
mounted() {

},
methods:{

    //点击提交,校验表单,进行业务操作
    async onSubmit(){
        if(this.submitText == '编辑'){
            this.formData.forEach(item=>{
                item.disabled = false;
            })
            this.submitText = '提交'
        }else{
            this.formData.forEach(item=>{
                item.disabled = true;
                this.submitText = '编辑'
            })
            let flag = await this.$refs.dynamic.submitForm();
            if(flag){
                this.text = "success submit"
            }else{
                this.text = "fail submit"
            }
        }

      },
   }
 }
</script>

我的rules校验方法是在网上看到的一个大神的校验方法,了解详细,拿来即用,给大家看一下。

//Check方法
//校验规则列表(可扩展)
const rules = {
  URL(url) {
    const regex = /^(https?|ftp):\/\/([a-zA-Z0-9.-]+(:[a-zA-Z0-9.&%$-]+)*@)*((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}|([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(:[0-9]+)*(\/($|[a-zA-Z0-9.,?"\\+&%$#=~_-]+))*$/
    return valid(url, regex, "URL格式不正确")
  },

  LowerCase(str) {
    const regex = /^[a-z]+$/
    return valid(str, regex, "只能输入小写字母")
  },

  UpperCase(str) {
    const regex = /^[A-Z]+$/
    return valid(str, regex, "只能输入大写字母")
  },

  Alphabets(str) {
    const regex = /^[A-Za-z]+$/
    return valid(str, regex, "只能输入字母")
  },

  Email(email) {
    const regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
    return valid(email, regex, "邮箱地址格式不正确")
  },

  Mobile(mobile) {
    const regex = /^1\d{10}$/
    return valid(mobile, regex, "手机号格式不正确")
  },

  Phone(phone) {
    const regex = /^(0\d{2,3})?-?\d{7,8}$/
    return valid(phone, regex, "电话号码格式不正确")
  },

  Postcode(postcode) {
    const regex = /^[0-9][0-9]{5}$/
    return valid(postcode, regex, "邮编格式不正确")
  },

  Number(num) {
    const regex = /^\d+$/
    return valid(num, regex, "只能输入纯数字")
  },

  Fax(fax) {
    const regex = /^(\d{3,4}-)?\d{7,8}$/
    return valid(fax, regex, "传真格式不正确")
  },

  Int(num) {
    const regex = /^((0)|([1-9]\d*))$/
    return valid(num, regex, "只能输入非负整数")
  },

  IntPlus(num){
    const regex = /^[1-9]\d*$/
    return valid(num, regex, "只能输入正整数")
  },

  Float1(num){
    const regex = /^-?\d+(\.\d)?$/
    return valid(num, regex, "只能输入数字,最多一位小数")
  },

  Float2(num){
    const regex = /^-?\d+(\.\d{1,2})?$/
    return valid(num, regex, "只能输入数字,最多两位小数")
  },

  Float3(num){
    const regex = /^-?\d+(\.\d{1,3})?$/
    return valid(num, regex, "只能输入数字,最多三位小数")
  },
  
  FloatPlus3(num){
    const regex = /^\d+(\.\d{1,3})?$/
    return valid(num, regex, "只能输入数字,最多三位小数")
  },

  Encode(code){
    const regex = /^(_|-|[a-zA-Z0-9])+$/
    return valid(code, regex, "编码只能使用字母、数字、下划线、中划线")
  },

  Encode2(code){
    const regex = /^[a-zA-Z0-9]+$/
    return valid(code, regex, "编码只能使用字母、数字")
  },

  Encode3(code){
    const regex = /^(_|[a-zA-Z0-9])+$/
    return valid(code, regex, "编码只能使用字母、数字、下划线")
  },

  IdCard(code){
    const regex = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/
    return valid(code, regex, "请输入正确的身份证号码")
  },
  
  USCC(code){
    const regex = /^[0-9A-Z]{18}/
    return valid(code, regex, "请输入正确的社会信用号")
  },
  
  CarNum(code){
    const regex = /^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[DF])|([DF]([A-HJ-NP-Z0-9])[0-9]{4})))|([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$/i
    return valid(code, regex, "请输入正确的车牌号")
  },
  
  CNandEN(code){
    const regex = /^[a-zA-Z\u4e00-\u9fa5]+$/
    return valid(code, regex, "只能使用中文、英文")
  },
  
  MobileOrPhone(val){
  	const result = /^1\d{10}$/.test(val) || /^(0\d{2,3})?-?\d{7,8}$/.test(val)
  	return valid(result, null, "手机或电话号格式不正确")
  }
}

//val:String 要校验的值
//regex:RegExp 校验正则,不是正则时val作为result的值
//msg:String 校验不通过的错误信息
function valid(val, regex, msg){
  return {result: regex instanceof RegExp? regex.test(val) : !!val, errMsg: msg}
}

//required:Boolean 是否必填项,选填,默认"true"
//type:String/Function 校验类型,选填,
//     String时必须是上面rules中存在的函数名,
//     Function时只接收一个参数(输入值),返回格式: {result:Boolean, errMsg:String}
//trigger:String 触发动作,选填,默认"blur"
//nullMsg:String 未输入的提示语,选填,required=true时有效
export function Check(required=true, type, trigger="blur", nullMsg="该字段为必填项"){
  const rule = { required: !!required, trigger}

  let check = null
  if(typeof type === "function"){
    check = type
  }else{
    check = type ? rules[type+""] : null
  }

  if(check){//存在规则时添加规则
    rule.validator = (r, v, c) => {
      const {result, errMsg} = check(v)
      if(required){
        //必填项: null,undefined,"","  " 都算无输入内容
        return (v==null || (v+"").trim()==="") ? c(new Error(nullMsg)) : result ? c() : c(new Error(errMsg))
      }
      //选填项: null,undefined,"" 都算无输入内容,"  "会被校验
      return (v==null || (v+"")==="" || result) ? c() : c(new Error(errMsg))
    }
  }else{
    rule.message = nullMsg
  }

  return [rule]
}

校验方法很完美。

image.png 写到这里,我们的一个简单的form表单封装就完成了。有需要的小伙伴儿可以添加更多的元素进去,例如开关,例如radio-button等等,这里的封装元素也只添加了最基本的元素,想要的更多可以自行添加,示例都在上面。大家觉得对你有帮助的,可以点个赞!!!谢谢!!