前兩天封裝了一個基於vue和Element的table表格元件,閱讀的人還是很多的,看來大家都是很認同元件化、高複用這種開發模式的,畢竟開發效率高,程式碼優雅,逼格高嘛。雖然這兩天我的心情很糟糕,就像“懂王”懟記者:“你是一個糟糕的記者;CNN,Fake news”一樣的心情,但我還是忍著難受的心情來工作和分享,畢竟工作是飯碗,分享也能化解我糟糕透頂的心情。
今天,就來分享一下基於vue和Element所封裝的form表單元件,其中所用到的技術,在上一篇文章封裝Vue Element的table表格元件中已介紹的差不多了,今天再來多講一個vue的動態元件component。關於動態元件component的介紹,vue官方倒是很吝嗇,就只是給了一個例子來告訴我們如何使用而已。我們可以把它理解成一個佔位符,其具體展示什麼,是由is
attribute來實現的,比如官網給的例子:
<component v-bind:is="currentTabComponent"></component>
在上述示例中,currentTabComponent可以包括:
-
已註冊元件的名字
-
或一個元件的選項物件
就醬,對它的介紹完了。
如果你還想了解更多,可以去vue官網檢視。
接下來就是封裝的具體實現,照例先來張效果圖:
1、封裝的form表單元件Form.vue:
<template>
<el-form ref="form" :model="form" :rules="rules" size="small" :label-position="'top'">
<el-row :gutter="20">
<el-col :lg="8" :md="12" :sm="24" v-for="(x, i) in formItems" :key="i">
<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 } 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, desc: String },
render(h, {props: {value = '', desc = ''}, data: {attrs = {}}, listeners: {input}}){
return h('el-checkbox', {props: {value, ...attrs}, on: {change(v) {input(v)}}}, desc)
}
},
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(){
let { columns, data } = this.config;
return {
TYPE: {
select: {
is: 'selectBar',
clearable: true, //是否可清空,預設為false不可清空
},
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 : []}), {}),
}
},
computed: {
formItems() {
return this.config.columns;
},
},
methods: {
componentAttrs(item) {
let {is = 'text', label} = item, attrs = fromEntries(Object.entries(item).filter(n => !/^(label|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>
在封裝時有一個Object.fromEntries的方法不相容ie,而我們公司又要求專案可以相容ie(我們公司的ie基本都是ie11),所以只能自己封裝了一個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';
}
2、使用已封裝的表單元件:
<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: '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: 'love', label: '愛好', is: 'checkboxGroup', list: [{label: '籃球', value: "0"}, {label: '排球', value: "1"}, {label: '足球', value: "2", disabled: true}] },
],
data: {
name: '小壞',
certificateId: '222',
status: '0',
love: ['0']
}
},
}
},
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就可以了。