封装Vue Element的form表单组件

前两天封装了一个基于vue和Element的table表格组件,阅读的人仍是不少的,看来你们都是很认同组件化、高复用这种开发模式的,毕竟开发效率高,代码优雅,逼格高嘛。虽然这两天个人心情很糟糕,就像“懂王”怼记者:“你是一个糟糕的记者;CNN,Fake news”同样的心情,但我仍是忍着难受的心情来工做和分享,毕竟工做是饭碗,分享也能化解我糟糕透顶的心情。html

今天,就来分享一下基于vue和Element所封装的form表单组件,其中所用到的技术,在上一篇文章封装Vue Element的table表格组件中已介绍的差很少了,今天再来多讲一个vue的动态组件component。关于动态组件component的介绍,vue官方却是很吝啬,就只是给了一个例子来告诉咱们如何使用而已。咱们能够把它理解成一个占位符,其具体展现什么,是由isattribute来实现的,好比官网给的例子:vue

<component v-bind:is="currentTabComponent"></component>

在上述示例中,currentTabComponent能够包括:数组

  • 已注册组件的名字工具

  • 或一个组件的选项对象组件化

就酱,对它的介绍完了。fetch

若是你还想了解更多,能够去vue官网查看。ui

接下来就是封装的具体实现,照例先来张效果图:

一、封装的form表单组件Form.vue:this

<template>
  <el-form ref="form" :model="form" :rules="rules" size="small" :label-position="'top'">
    <el-row :gutter="20" v-for="(row, i) in columns" :key="i">
      <el-col :span="24 / rowSize" v-for="(x, idx) in row" :key="idx">
        <el-form-item :label="x.label" :prop="x.prop">
          <component v-model="form[x.prop]" v-bind="componentAttrs(x)" class="width-full" />
        </el-form-item>
      </el-col>
    </el-row>
    <div class="searchBtn">
      <el-button class="filter-item" @click="reset">重置</el-button>
      <el-button class="filter-item" type="primary" @click="submit">查询</el-button>
    </div>
  </el-form>
</template>

<script>
import { fromEntries, chunk } from '@/utils'
export default {
  props: {
    config: Object,
  },
  components: {
    selectBar: {
      functional: true,
      props: {value: String, list: Array, callback: Function},
      render(h, {props: {value = '', list = [], callback}, data: {attrs = {}}, listeners: {input}}){
        return h('el-select', {class: 'width-full', props: {value, ...attrs}, on: {change(v) {input(v); callback(v)}}}, list.map(o => h('el-option', {props: {...o, key: o.value}})))
      }
    },
    checkbox: {
      functional: true,
      props: {value: Boolean, label: String },
      render(h, {props: {value = '', label = ''}, data: {attrs = {}}, listeners: {input}}){
        return h('el-checkbox', {props: {value, ...attrs}, on: {change(v) {input(v)}}}, label)
      }
    },
    checkboxGroup: {
      functional: true,
      props: {value: Array, list: Array},
      render(h, {props: {value = [], list = []}, data: {attrs = {}}, listeners: {input}}){
        return h('el-checkbox-group', {props: {value, ...attrs}, on: {input(v) {input(v)}}}, list.map(o => h('el-checkbox', {props: {...o, label: o.value, key: o.value}}, [o.label])))
      }
    },
    radioGroup: {
      functional: true,
      props: {value: String, list: Array },
      render(h, {props: {value = '', list = []}, data: {attrs = {}}, listeners: {input}}){
        return h('el-radio-group', {props: {value, ...attrs}, on: {input(v) {input(v)}}}, list.map(o => h('el-radio', {props: {...o, key: o.label}}, [o.value])))
      }
    },
  },
  data(){
    const { columns, data, rowSize = 3 } = this.config;

    return {
      TYPE: {
        select: {
          is: 'selectBar',
          clearable: true,
        },
        text: {
          is: 'el-input',
          clearable: true,
        },
        switch: {
          is: 'el-switch',
        },
        checkbox: {
          is: 'checkbox',
          clearable: true,
        },
        checkboxGroup: {
          is: 'checkboxGroup',
          clearable: true,
        },
        radioGroup: {
          is: 'radioGroup',
          clearable: true,
        },
        daterange: {
          is: 'el-date-picker',
          type: 'daterange',
          valueFormat: 'yyyy-MM-dd',
          rangeSeparator: '至',
          startPlaceholder: '开始日期',
          endPlaceholder: '结束日期',
          editable: false,
        },
        date: {
          is: 'el-date-picker',
          type: "date",
          valueFormat: 'yyyy-MM-dd',
          editable: false,
        },
        auto: {
          is: 'el-autocomplete'
        }
      },
      form: columns.reduce((r, c) => Object.assign(r, {[c.prop]: data && data[c.prop] ? data[c.prop] : (c.is == 'checkboxGroup' ? [] : null)}), {}),
      rules: columns.reduce((r, c) => ({...r, [c.prop]: c.rules ? c.rules : []}), {}),
      columns: chunk(this.config.columns, rowSize),
      rowSize,
    }
  },
  methods: {
    componentAttrs(item) {
      const {is = 'text', label} = item, attrs = fromEntries(Object.entries(item).filter(n => !/^(prop|is|rules)/.test(n[0]))),
      placeholder = (/^(select|el-date-picker)/.test(is) ? '请选择' : '请输入') + label;
      
      return {...attrs, ...this.TYPE[is], placeholder}
    },
    reset() {
      this.$refs.form.resetFields();
    },
    submit() {
      this.$refs.form.validate(valid => valid && this.$emit('submit', this.form));
    },
  }
};
</script>

<style scoped>
.width-full{width: 100%;}
</style>

在封装的时候发现一个问题,就是有时候可能一行展现两列表单,有时候呢可能一行又要展现三列或四列表单,这样的话,也是须要在封装的时候去实现可配置的效果的,那么本次封装就顺便封装了一个相似lodash的_.chunk的工具来实现分段展现。spa

lodash对_.chunk的定义:将数组array拆分红多个size长度的区块,并将这些区块组成一个新数组。若是array没法被分割成所有等长的区块,那么最后剩余的元素将组成一个区块。prototype

其实lodash这个工具库就像它官网介绍的那样,确实很实用,但须要常常使用才能够掌握它所包含的工具,不然,也是百脸懵逼。不过它的不少工具从字面意思来看,也不难理解其表明的意思。

本身封装的分段chunk.js

export const chunk = (array, size) => {
  size = Math.max(size, 0);
  if (!array.length || size < 1) {
    return [];
  }
  const result = [];
  array.forEach((item, index) => {
     const rowSize = Math.floor(index / size);
     if(!(result[rowSize] instanceof Array)){
        result[rowSize] = [];
     }
     result[rowSize].push(item);
   })
   return result;
}

另外,在封装时有一个Object.fromEntries的方法不兼容ie,好比这里我原本写的是:

const attrs = Object.fromEntries(Object.entries(item).filter(n => !/^(prop|is|rules)/.test(n[0])))

但咱们公司又要求项目能够兼容ie(咱们公司的ie基本都是ie11),因此只能本身封装了一个fromEntries方法来代替Object.fromEntries。

export const fromEntries = arr => {
  if (Object.prototype.toString.call(arr) === '[object Map]') {
    console.log(1)
    let result = {};
    for (const key of arr.keys()) {
      result[key] = arr.get(key);
    }

    return result;
  }

  if(Array.isArray(arr)){
    let result = {}
    arr.map(([key,value]) => {
      result[key] =  value
    })

    return result
  }
  throw 'Uncaught TypeError: argument is not iterable';
}

二、使用已封装的表单组件:

<template>
  <Form :config="config" @submit="getList" ref="form" />
</template>

<script>
import Form from "./Form";

const statusLlist = [
  {label: '未提交', value: "0"},
  {label: '待审批', value: "1"},
  {label: '已经过', value: "2", disabled: true}
]

export default {
  components: {
    Form,
  },
  data() {
    const confimPass = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('请再次输入密码'));
      } else if (value !== this.$refs.form.form.password) {
        callback(new Error('两次输入密码不一致!'));
      } else {
        callback();
      }
    };

    return {
      config: {
        columns: [
          { prop: "name", label: "借款人名称", is: "auto", fetchSuggestions: this.querySearch },
          { prop: "certificateId", label: "统一信用代码", rules: [{required: true, message: '请输入统一信用代码'}] },
          { prop: 'daterange', label: "日期范围", is: 'daterange', },
          { prop: 'date', label: "日期", is: 'date', },
          { prop: 'status', label: "状态", is: 'select', list: statusLlist, callback: r => this.statusChange(r) },
          { prop: "password", label: "密码", type: 'password' },
          { prop: "confimPass", label: "确认密码", type: 'password', rules: [{validator: confimPass}] },
          { prop: 'remark', label: "备注", type: 'textarea' },
          { prop: "email", label: "邮箱", rules: [{ required: true, message: '请输入邮箱地址' }, { type: 'email', message: '请输入正确的邮箱地址' }] },
          { prop: 'remember', label: '记住密码', is: 'checkbox' },
          { prop: 'gender', label: '性别', is: 'radioGroup', list: [{label: 'male', value: "男"}, {label: 'famale', value: "女", disabled: true}] },
          { prop: 'love', label: '爱好', is: 'checkboxGroup', list: [{label: '篮球', value: "0"}, {label: '排球', value: "1"}, {label: '足球', value: "2", disabled: true}] },
          { prop: "delivery", label: "即时配送", is: 'switch' },
        ],
        data: {
          name: '小坏',
          certificateId: '222',
          status: '0',
          love: ['0']
        },
        rowSize: 3,   //一行能够展现几列表单,默认为3列
      },
    }
  },
  methods: {
    querySearch(q, cb){
      if (!q) {cb([]);return}
    },
    getList(res){
      console.log(res)
    },
    statusChange(r){
      console.log(r)
    },
  },
}
</script>

本次封装的form表单组件,基本考虑到了在平常开发中会常常使用到的表单组件,若是还有其余的需求,可自行添加。另外,本次封装也对表单的回显(返显)作了实现,好比咱们在编辑数据时,须要将被修改的数据显示在表单中,本次封装就充分考虑到了这一点,只要你在传给封装的form组件的参数中加一个data参数,并将须要回显的数据名称一一对应并赋值就能够了,好比:

data: {
  name: '小坏',
  certificateId: '222',
  status: '0',
  love: ['0']
}

若是你不须要回显数据,好比新增的时候,那就不传这个data就能够了。

相关文章
相关标签/搜索