如何封装VueElement的table表格组件
在封装Vue组件时,我依旧会交叉使⽤函数式组件的⽅式来实现。关于函数式组件,我们可以把它想像成组件⾥的⼀个函数,⼊参是渲染上下⽂(render context),返回值是渲染好的HTML(VNode)。它⽐较适⽤于外层组件仅仅是对内层组件的⼀次逻辑封装,⽽渲染的模板结构变化扩展不多的情况,且它⼀定是⽆状态、⽆实例的,⽆状态就意味着它没有created、mounted、updated等Vue的⽣命周期函数,⽆实例就意味着它没有响应式数据data和this上下⽂。
我们先来⼀个简单的Vue函数式组件的例⼦吧,然后照着这个例⼦来详细介绍⼀下。
export default {
functional: true,
props: {},
render(createElement, context) {
return createElement('span', 'hello world')
}
}
Vue提供了⼀个functional开关,设置为true后,就可以让组件变为⽆状态、⽆实例的函数式组件。因为只是函数,所以渲染的开销相对来说较⼩。
函数式组件中的Render函数,提供了两个参数createElement和context,我们先来了解下第⼀个参数createElement。
createElement说⽩了就是⽤来创建虚拟DOM节点VNode的。它接收三个参数,第⼀个参数可以是DOM节点字符串,也可以是⼀个Vue组件,还可以是⼀个返回字符串或Vue组件的函数;第⼆个参数是⼀个对象,这个参数是可选的,定义了渲染组件所需的参数;第三个参数是⼦级虚拟节点,可以是⼀个由createElement函数创建的组件,也可以是⼀个普通的字符串如:'hello world',还可以是⼀个数组,当然也可以是⼀个返回字符串或Vue组件的函数。
createElement有⼏点需要注意:
createElement第⼀个参数若是组件,则第三个参数可省略,即使写上去也⽆效;
render函数在on事件中可监听组件$emit发出的事件
在2.3.0之前的版本中,如果⼀个函数式组件想要接收prop,则props选项是必须的。在2.3.0或以上的版本中,你可以省略props选项,组件上所有的attribute都会被⾃动隐式解析为prop。
函数式组件中Render的第⼆个参数是context上下⽂,data、props、slots、children以及parent都可以通过context来访问。
在2.5.0及以上版本中,如果你使⽤了单⽂件组件,那么基于模板的函数式组件可以这样声明:<template functional></template>,但是如果Vue组件中的render函数存在,则Vue构造函数不会从template选项或通过el选项指定的挂载元素中提取出的HTML模板编译渲染函数,也就是说⼀个组件中templete和render函数不能共存,如果⼀个组件中有了templete,即使有了render函数,render函数也不会执⾏,因为template选项的优先级⽐render选项的优先级⾼。
到这⾥,Vue函数式组件介绍的就差不多了,我们就来看看Element的表格组件是如何通过函数式组件来实现封装的吧。
效果图:
1、所封装的table组件:
<template>
<div>
<el-table :data="cfg.data" v-on="" v-bind="attrs" v-loading="loading">
<el-table-column v-if="cfg.hasCheckbox" v-bind="selectionAttrs" type="selection" width="55" label="xx" />
<el-table-column v-for="n in cfg.headers" :prop="n.prop" :label="n.label" :key="n.prop" v-bind="{...columnAttrs, ...n.attrs}">
<template slot-scope="{row}">
<slot :name="n.prop" :row="row"><Cell :config="n" :data="row" /></slot>
</template>
</el-table-column>
</el-table>
<el-pagination
class="pagination"
v-if="showPage"
layout="total, sizes, prev, pager, next, jumper"
:page-sizes="[2, 3, 6, 11]"
:page-size="page.size"
:total="al"
:current-page="page.page"
@current-change="loadPage"
@size-change="sizeChange"
/>
</div>
</template>
},
props: {
config: Object,
},
data(){
return {
loading: true,
columnAttrs: {
align: 'left',
resizable: false,
},
cfg: {
on: TableEvents(),
attrs: {
border: true,
stripe: true,
},
data: [],
...fig,
},
page: {
size: fig.size || 10,
page: 1,
total: 0,
},
checked: [],
}
},
created(){
this.load();
},
computed: {
selectionAttrs(){
let {selectable, reserveSelection = false} = fig || {}, obj = {};
// checkBox是否可以被选中
if(selectable && typeof selectable == 'function'){
Object.assign(obj, {
selectable,
})
}
//reserve-selection仅对type=selection的列有效,类型为Boolean,为true则会在数据更新之后保留之前选中的数据(需指定 row-key)  if(reserveSelection){
Object.assign(obj, {
'reserve-selection': reserveSelection,
})
}
return obj;
},
attrs(){
let {config: {spanMethod, rowKey}, cfg: {attrs}} = this;
// 合并单元格 - spanMethod是⽗组件传过来的合并单元格的⽅法,请参照element合并单元格
if(spanMethod && typeof spanMethod == 'function'){
Object.assign(attrs, {
'span-method': spanMethod,
})
}
// 表格跨页选中,需要设置row-key和reserve-selection,reserve-selection只能且必须设置在type为selection的el-table-column上
if(rowKey && typeof rowKey == 'function'){
Object.assign(attrs, {
'row-key': rowKey,
})
}
return attrs;
},
showPage(){
let {size, total} = this.page;
return size < total;
},
},
methods: {
getTableEvents(){
let {hasCheckbox = false} = fig || {}, events = {}, _this = this;
if(hasCheckbox){
// 绑定事件
Object.assign(events, {
'selection-change'(v){
_this.checked = v;
/
/ 获取勾选的⾏
getChecked(){
return this.checked;
},
// 请求数据
load(p = {}){
let { size, page } = this.page, {loadData = () => solve({})} = fig;
this.loading = true;
// 这⾥loadData的参数在初始化时只有分页所需的page和size,⾄于接⼝需要的其他参数,是在⽗组件的loadData中传递
loadData({...p, page, size}).then(({data, total}) => {
this.cfg.data = data;
this.page.page = page;
al = total;
this.loading = false;
});
},
loadPage(index){
this.page.page = index
this.load();
},
sizeChange(size){
this.page.size = size
this.load();
},
// ⼀般在点击查询按钮或局部刷新表格列表时,可调⽤此⽅法,如果不传参数,则默认从第⼀页开始
reload(p = {}){
this.page.page = 1
this.load(p);
},
},
}
</script>
2、汇总表格每⼀列的cell.js:
import * as Components from './components';
let empty = '-'
export default {
props: {
config: Object,
data: Object,
},
functional: true,
render: (h, c) => {
let {props: {config = {}, data = {}}} = c, {prop, type = 'Default'} = config, value = data[prop] || config.value, isEmpty = value === '' || value === undefined;  return isEmpty ? h(Components.Default, {props: {value: empty}}) : h(Components[type], {props: {value, empty, data, ...config}});
}
}
3、本次封装将每⼀列的渲染单独分开成多个vue组件,最后再合并在⼀个components.js⽂件中⼀起进⾏匹配。
1)整合⽂件components.js:
import Date    from './Date';
import Default  from './Default';
import Currency  from './Currency';
import Enum    from './Enum';
import Action    from './Action';
import Link    from './Link';
import Format    from './Format';
import Popover  from './Popover';
export {
Default,tabletotal函数
Date,
Currency,
Enum,
Action,
Link,
Format,
Popover,
}
2)⽇期列Date.vue
3)默认列Default.vue
<template functional>
<span>{{props.value}}</span>
</template>
4)⾦额千分位列Currency.vue
<template functional>
<span>{{props.value | currency}}</span>
</template>
5)映射列Enum.js
let mapIdAndKey = list => duce((c, i) => ({...c, [i.key]: i}), {}); let STATUS = {
order: mapIdAndKey([
{
id: 'draft',
key: 'CREATED',
val: '未提交',
},
{
id: 'pending',
key: 'IN_APPROVAL',
val: '审批中',
},
{
id: 'reject',
key: 'REJECT',
val: '审批驳回',
},
{
id: 'refuse',
key: 'REFUSE',
val: '审批拒绝',
},
{
id: 'sign',
key: 'CONTRACT_IN_SIGN',
val: '合同签署中',
},
{
id: 'signDone',
key: 'CONTRACT_SIGNED',
val: '合同签署成功',
},
{
id: 'lendDone',
key: 'LENDED',
val: '放款成功',
},
{
id: 'lendReject',
key: 'LOAN_REJECT',
val: '放款驳回',
},
{
id: 'cancel',
key: 'CANCEL',
val: '取消成功',
},
{
id: 'inLend',
key: 'IN_LOAN',
val: '放款审批中',
},
]),
monitor: mapIdAndKey([
{
key: '00',
val: '未监控',
},
{
key: '01',
val: '监控中',
},
]),
let enums = Object.assign({}, STATUS, parent.$s.dictionary),
{name = '', getVal = (values, v) => values[v]} = Enum, _value = getVal(enums[name], value);
if( _value === undefined) return h('span', _value === undefined ? empty : _value);
let {id, val} = _value;
return h('span', {staticClass: id}, [h('span', val)]);
}
}
6)操作列Action.js
const getAcitons = (h, value, data) => {
let result = value.filter(n => {
let {filter = () => true} = n;
return filter.call(n, data);
});
return result.map(a => h('span', {class: 'btn', on: {click: () => a.click(data)}, key: a.prop}, a.label))
}
export default {
functional: true,
render: (h, {props: {value, data}}) => {
return h('div', {class: 'action'}, getAcitons(h, value, data))
},
}
7)带有可跳转链接的列Link.vue
<template>
<router-link :to="{ path, query: params }">{{value}}</router-link>
</template>
<script>
export default {
props: {
data: Object,
value: String,
query: {
type: Function,
default: () => {
return {
path: '',
payload: {}
}
}
},
},
computed: {
// 路由path
path(){
const { path } = this.query(this.data)
return path
},
params(){
const { payload } = this.query(this.data)
return payload
},
},
}
</script>
8)⾃定义想要展⽰的数据格式Format.vue
<template functional>
<div v-html="props.format(props.value, props.data)" />
</template>
9)当内容过多需要省略并在⿏标移⼊后弹出⼀个提⽰窗显⽰全部内容的列Popover.vue <template functional>
<el-popover
placement="top-start"
width="300"
trigger="hover"
popper-class="popover"
:content="props.value">
<span slot="reference" class="popover-txt">{{props.value}}</span>

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。