前兩天封裝了一個基於vue和Element的table表格組件,閱讀的人仍是不少的,看來你們都是很認同組件化、高複用這種開發模式的,畢竟開發效率高,代碼優雅,逼格高嘛。雖然這兩天個人心情很糟糕,就像「懂王」懟記者:「你是一個糟糕的記者;CNN,Fake news」同樣的心情,但我仍是忍着難受的心情來工做和分享,畢竟工做是飯碗,分享也能化解我糟糕透頂的心情。html
今天,就來分享一下基於vue和Element所封裝的form表單組件,其中所用到的技術,在上一篇文章封裝Vue Element的table表格組件中已介紹的差很少了,今天再來多講一個vue的動態組件component。關於動態組件component的介紹,vue官方卻是很吝嗇,就只是給了一個例子來告訴咱們如何使用而已。咱們能夠把它理解成一個佔位符,其具體展現什麼,是由is
attribute來實現的,好比官網給的例子: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就能夠了。