封装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 相关文章推荐
javascript 原型模式实现OOP的再研究
Apr 09 Javascript
编写兼容IE和FireFox的脚本
May 18 Javascript
jquery1.83 之前所有与异步列队相关的模块详细介绍
Nov 13 Javascript
jQuery easyui datagrid动态查询数据实例讲解
Feb 26 Javascript
jQuery中get()方法用法实例
Dec 27 Javascript
javascript实现数据双向绑定的三种方式小结
Mar 09 Javascript
Angular directive递归实现目录树结构代码实例
May 05 Javascript
AngularJS基于http请求实现下载php生成的excel文件功能示例
Jan 23 Javascript
详解webpack引用jquery(第三方模块)的三种办法
Aug 21 jQuery
vue中的v-if和v-show的区别详解
Sep 01 Javascript
vue flex 布局实现div均分自动换行的示例代码
Aug 05 Javascript
k8s node节点重新加入master集群的实现
Feb 22 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
克隆一个新项目的快捷方式
2013/04/10 PHP
php中AES加密解密的例子小结
2014/02/18 PHP
符合标准的js表单提交的代码
2007/09/13 Javascript
js日期时间补零的小例子
2013/03/05 Javascript
jquery 淡入淡出效果的简单实现
2014/02/07 Javascript
JS拖动鼠标画出方框实现鼠标选区的方法
2015/08/05 Javascript
javascript鼠标滑过显示二级菜单特效
2020/11/18 Javascript
jQuery实现的浮动层div浏览器居中显示效果
2017/02/03 Javascript
for循环 + setTimeout 结合一些示例(前端面试题)
2017/08/30 Javascript
详解Vue源码学习之callHook钩子函数
2018/07/25 Javascript
浅谈微信小程序flex布局基础
2018/09/10 Javascript
微信小程序新手教程之启动页的重要性
2019/03/03 Javascript
JS中的防抖与节流及作用详解
2019/04/01 Javascript
详解利用nodejs对本地json文件进行增删改查
2019/09/20 NodeJs
es6中new.target的作用和使用场景简单示例分析
2020/03/14 Javascript
vue.js实现简单购物车功能
2020/05/30 Javascript
Python进阶学习之特殊方法实例详析
2017/12/01 Python
Python3多线程操作简单示例
2018/05/22 Python
django admin 自定义替换change页面模板的方法
2019/08/23 Python
python爬虫中多线程的使用详解
2019/09/23 Python
python 二维矩阵转三维矩阵示例
2019/11/30 Python
Python TCPServer 多线程多客户端通信的实现
2019/12/31 Python
Pytorch中实现只导入部分模型参数的方式
2020/01/02 Python
3D动画《斗罗大陆》上线当日播放过亿
2021/03/16 国漫
个性大学生自我评价
2013/12/04 职场文书
写给女朋友的道歉信
2014/01/12 职场文书
大学学习生活感言
2014/01/18 职场文书
企业安全标语
2014/06/07 职场文书
我的梦想演讲稿1000字
2014/08/21 职场文书
2014年银行柜员工作总结
2014/11/12 职场文书
2014年小学教研工作总结
2014/12/06 职场文书
雾霾停课通知
2015/04/24 职场文书
工程竣工验收申请报告
2015/05/15 职场文书
二婚主持词
2015/06/30 职场文书
在K8s上部署Redis集群的方法步骤
2021/04/27 Redis
Python中glob库实现文件名的匹配
2021/06/18 Python