封装Vue Element的table表格组件的示例详解


Posted in Javascript onAugust 19, 2020

在封装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组件,还可以是一个返回DOM节点字符串或Vue组件的函数;第二个参数是一个对象,这个参数是可选的,定义了渲染组件所需的参数;第三个参数是子级虚拟节点,可以是一个由createElement函数创建的组件,也可以是一个普通的字符串如:'hello world',还可以是一个数组,当然也可以是一个返回DOM节点字符串或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的表格组件是如何通过函数式组件来实现封装的吧。

效果图:

封装Vue Element的table表格组件的示例详解

1、所封装的table组件

<template>
 <el-table :data="config.tableData" style="width: 100%" v-on="cfg.on" v-bind="cfg.attrs">
 <el-table-column v-if="cfg.hasCheckbox" type="selection" width="55" label="xx" />
 <el-table-column v-for="n in cfg.headers" :prop="n.prop" :label="n.name" :key="n.prop" v-bind="{...columnAttrs, ...n.attrs}">
 <template slot-scope="{row}">
 <Cell :config="n" :data="row" />
 </template>
 </el-table-column>
 </el-table>
</template>

<script>
import Cell from './cell'

export default {
 components: {
 Cell,
 },
 props: {
 config: Object,
 },
 data(){
 return {
 columnAttrs: {
 align: 'left',
 resizable: false,
 },
 cfg: {
 on: this.getTableEvents(),
 attrs: {
  border: true,
  stripe: true,
 },
 ...this.config,
 },
 checked: [],
 }
 },
 methods: {
 getTableEvents(){
 let {hasCheckbox = false} = this.config, events = {}, _this = this;
 if(hasCheckbox){
 //绑定事件
 Object.assign(events, {
  'selection-change'(v){
  _this.checked = v;
  },
 });
 }

 return events;
 },
 getChecked(){
 return this.checked;
 },
 },
}
</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、不同于封装React AntD的table表格组件时将表格的每一列的渲染都集中在了一个table.js中,本次封装将每一列的渲染单独分开成多个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 Loop  from './Loop';
import Popover from './Popover';

export {
 Default,
 Date,
 Currency,
 Enum,
 Action,
 Link,
 Loop,
 Popover,
}

2)日期列Date.vue

<template functional>
 <span>{{props.value | date(props.format)}}</span>
</template>

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 => list.reduce((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: '监控中',
 },
 ]),
}

export default {
 functional: true,
 render(h, {props: {value, Enum, empty}, parent}){
 let enums = Object.assign({}, STATUS, parent.$store.getters.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 functional>
 <router-link :to="{ path: props.url, query: {id: props.data.id} }">{{props.value}}</router-link>
</template>

8)可循环展示数组数据的列Loop.vue

<template functional>
 <div v-html="props.Loop(props.value)" />
</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>
 </el-popover>
</template>
<style scoped>
.popover-txt{
 overflow:hidden;
 text-overflow:ellipsis;
 white-space:nowrap;
 display: block;
 cursor: pointer;
}
</style>

从以上代码中可以看出,我既使用了基于render函数类型的函数式组件也使用了基于模板的函数式组件,主要是为了在封装时的方便,毕竟使用render这个最接近编译器的函数还是有点麻烦的,不如基于模板的函数式组件来的方便。

4、使用封装后的表格table组件:

<template>
 <div style="margin: 20px;">
 <el-button type="primary" v-if="excelExport" @click="download">获取勾选的表格数据</el-button>
 <Table :config="config" ref="table" />
 </div>
</template>

<script>
import Table from '@/components/table'

export default {
 components: {
 Table,
 },
 data() {
 return {
 config: {
 headers: [
  {prop: 'contractCode', name: '业务编号', attrs: {width: 200, align: 'center'}},
  {prop: 'payeeAcctName', name: '收款账户名', type: 'Link', url: 'otherElTable', attrs: {width: 260, align: 'right'}},
  {prop: 'tradeAmt', name: '付款金额', type: 'Currency'},
  {prop: 'status', name: '操作状态', type: 'Enum', Enum: {name: 'order'}},
  {prop: 'statistic', name: '预警统计', type: 'Loop', Loop: (val) => this.loop(val)},
  {prop: 'reason', name: '原因', type: 'Popover'},
  {prop: 'payTime', name: '付款时间', type: "Date", format: 'yyyy-MM-dd hh:mm:ss'}, //不设置format的话,日期格式默认为yyyy/MM/dd
  {prop: 'monitorStatus', name: '当前监控状态', type: 'Enum', Enum: {name: 'monitor'}},
 ].concat(this.getActions()),
 tableData: [
  {id: 1, contractCode: '', payeeAcctName: '中国银行上海分行', tradeAmt: '503869', status: '00', payTime: 1593585652530, 
  statistic:[
  {level: 3, total: 5},
  {level: 2, total: 7},
  {level: 1, total: 20},
  {level: 0, total: 0}
  ]
  },
  {id: 2, contractCode: 'GLP-YG-B3-1111', payeeAcctName: '中国邮政上海分行', tradeAmt: '78956.85', status: 'CREATED', payTime: 1593416718317, 
  reason: 'Popover的属性与Tooltip很类似,它们都是基于Vue-popper开发的,因此对于重复属性,请参考Tooltip的文档,在此文档中不做详尽解释。',
  },
  {id: 3, contractCode: 'HT1592985730310', payeeAcctName: '招商银行上海支行', tradeAmt: '963587123', status: 'PASS', payTime: 1593420950772, monitorStatus: '01'},
  {id: 4, contractCode: 'pi239', payeeAcctName: '广州物流有限公司', tradeAmt: '875123966', status: 'REJECT', payTime: 1593496609363},
  {id: 5, contractCode: '0701001', payeeAcctName: '建设银行上海分账', tradeAmt: '125879125', status: 'REFUSE', payTime: 1593585489177},
 ],
 hasCheckbox: true,
 },
 status: "01",
 permission: ["handle", "pass", "refuse", "reApply", 'export']
 }
 },
 computed: {
 handle() {
 return this.permission.some(n => n == "handle");
 },
 pass() {
 return this.permission.some(n => n == "pass");
 },
 reject() {
 return this.permission.some(n => n == "reject");
 },
 refuse() {
 return this.permission.some(n => n == "refuse");
 },
 excelExport(){
 return this.permission.some(n => n == "handle") && this.permission.some(n => n == "export");
 },
 },
 methods: {
 getActions(){
 return {prop: 'action', name: '操作', type: "Action", value: [
 {label: "查看", click: data => {console.log(data)}},
 {label: "办理", click: data => {}, filter: ({status}) => status == 'CREATED' && this.handle},
 {label: "通过", click: data => {}, filter: ({status}) => status == 'PASS' && this.pass},
 {label: "驳回", click: data => {}, filter: ({status}) => status == 'REJECT' && this.reject},
 {label: "拒绝", click: data => {}, filter: ({status}) => status == 'CREATED' && this.refuse},
 ]}
 },
 loop(val){
 let str = '';
 val.forEach(t => {
 str += '<span style="margin-right:5px;">' + t.total + '</span>';
 })
 return str;
 },
 download(){
 console.log(this.$refs.table.getChecked())
 },
 },
};
</script>
<style>
.action span{margin-right:10px;color:#359C67;cursor: pointer;}
</style>

关于金额千分位和时间戳格式化的实现,这里就不再贴代码了,可自行实现。

总结

到此这篇关于封装Vue Element的table表格组件的文章就介绍到这了,更多相关封装Vue Element table表格组件内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
将string解析为json的几种方式小结
Nov 11 Javascript
setTimeout和setInterval的区别你真的了解吗?
Mar 31 Javascript
jQuery实现气球弹出框式的侧边导航菜单效果
Sep 22 Javascript
基于JavaScript获取鼠标位置的各种方法
Dec 16 Javascript
js正则表达式replace替换变量方法
May 21 Javascript
Angular ng-repeat遍历渲染完页面后执行其他操作详细介绍
Dec 13 Javascript
基于VUE.JS的移动端框架Mint UI的使用
Oct 11 Javascript
微信小程序实现下载进度条的方法
Dec 08 Javascript
JavaScript引用类型Date常见用法实例分析
Aug 08 Javascript
nest.js 使用express需要提供多个静态目录的操作方法
Oct 24 Javascript
JS实现轮播图效果
Jan 11 Javascript
JavaScript代码压缩工具UglifyJS和Google Closure Compiler的基本用法
Apr 13 Javascript
JS实现页面鼠标点击出现图片特效
Aug 19 #Javascript
Vue实现点击导航栏当前标签后变色功能
Aug 19 #Javascript
JS实现悬浮球只在一侧滑动并且是横屏状态下
Aug 19 #Javascript
Javascript前端下载后台传来的文件流代码实例
Aug 18 #Javascript
Vue-cli assets SubDirectory及PublicPath区别详解
Aug 18 #Javascript
JavaScript语句错误throw、try及catch实例解析
Aug 18 #Javascript
JavaScript中交换值的10种方法总结
Aug 18 #Javascript
You might like
Dedecms V3.1 生成HTML速度的优化办法
2007/03/18 PHP
PHP中substr()与explode()函数用法分析
2014/11/24 PHP
PHP实现的常规正则验证helper公共类完整实例
2017/04/27 PHP
Laravel框架实现的记录SQL日志功能示例
2018/06/19 PHP
jQuery对象和DOM对象使用说明
2010/06/25 Javascript
html a标签-超链接中confirm方法使用介绍
2013/01/04 Javascript
将nodejs打包工具整合到鼠标右键的方法
2013/05/11 NodeJs
JavaScript中数据结构与算法(三):链表
2015/06/19 Javascript
通过点击jqgrid表格弹出需要的表格数据
2015/12/02 Javascript
JavaScript中最容易混淆的作用域、提升、闭包知识详解(推荐)
2016/09/05 Javascript
Radio 单选JS动态添加的选项onchange事件无效的解决方法
2016/12/12 Javascript
input输入框内容实时监测(附代码)
2017/08/15 Javascript
详解基于Vue,Nginx的前后端不分离部署教程
2018/12/04 Javascript
详解bootstrap-fileinput文件上传控件的亲身实践
2019/03/21 Javascript
vue鼠标悬停事件实例详解
2019/04/01 Javascript
vue封装swiper代码实例解析
2019/10/08 Javascript
vue中echarts图表大小适应窗口大小且不需要刷新案例
2020/07/19 Javascript
[03:14]DOTA2斧王 英雄基础教程
2013/11/26 DOTA
[00:44]TI7不朽珍藏III——军团指挥官不朽展示
2017/07/15 DOTA
Python探索之ModelForm代码详解
2017/10/26 Python
python抓取文件夹的所有文件
2018/02/27 Python
Python中.join()和os.path.join()两个函数的用法详解
2018/06/11 Python
对python文件读写的缓冲行为详解
2019/02/13 Python
python opencv图片编码为h264文件的实例
2019/12/12 Python
python脚本第一行如何写
2020/08/30 Python
Python jieba结巴分词原理及用法解析
2020/11/05 Python
CSS 3.0 结合video视频实现的创意开幕效果
2020/06/01 HTML / CSS
HTML5实现应用程序缓存(Application Cache)
2020/06/16 HTML / CSS
波兰多品牌运动商店:StreetStyle24.pl
2020/09/22 全球购物
互联网创业计划书写作技巧攻略
2014/03/23 职场文书
请假条的格式
2014/04/11 职场文书
2014年电信员工工作总结
2014/12/19 职场文书
音乐教师求职信范文
2015/03/20 职场文书
学雷锋献爱心活动总结
2015/05/11 职场文书
golang内置函数len的小技巧
2021/07/25 Golang
对讲机的最大通讯距离是多少
2022/02/18 无线电