封装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 TextArea动态显示剩余字符
Oct 22 Javascript
JavaScript 权威指南(第四版) 读书笔记
Aug 11 Javascript
Javascript 实现的数独解题算法网页实例
Oct 15 Javascript
js判断IE浏览器版本过低示例代码
Nov 22 Javascript
javascript伸缩菜单栏实现代码分享
Nov 12 Javascript
深入php面向对象、模式与实践
Feb 16 Javascript
jQuery通过写入cookie实现更换网页背景的方法
Apr 15 Javascript
JS常见创建类的方法小结【工厂方式,构造器方式,原型方式,联合方式等】
Apr 01 Javascript
JS Testing Properties 判断属性是否在对象里的方法
Oct 01 Javascript
Vue中对拿到的数据进行A-Z排序的实例
Sep 25 Javascript
详解Node.js读写中文内容文件操作
Oct 10 Javascript
Javascript实现打鼓效果
Jan 29 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
PHP输出日历表代码实例
2015/03/27 PHP
php判断电子邮件是否正确方法
2018/12/04 PHP
javascript实现的动态文字变换
2007/07/28 Javascript
JavaScript 获取事件对象的注意点
2009/07/29 Javascript
javascript getElementsByName()的用法说明
2009/07/31 Javascript
jquery入门——事件机制之事件中的冒泡现象示例解释
2020/09/12 Javascript
javascript中数组array及string的方法总结
2014/11/28 Javascript
jQuery EasyUi实战教程之布局篇
2016/01/26 Javascript
使用node+vue.js实现SPA应用
2016/01/28 Javascript
微信小程序 欢迎页面的制作(源码下载)
2017/01/09 Javascript
Javascript实现登录记住用户名和密码功能
2017/03/22 Javascript
基于JavaScript实现移动端无限加载分页
2017/03/27 Javascript
JS中定位 position 的使用实例代码
2017/08/06 Javascript
一文让你彻底搞清楚javascript中的require、import与export
2017/09/24 Javascript
jquery 验证用户名是否重复代码实例
2019/05/14 jQuery
JavaScript函数式编程(Functional Programming)高阶函数(Higher order functions)用法分析
2019/05/22 Javascript
基于jQuery的时间戳与日期间的转化
2019/06/21 jQuery
vue-cli随机生成port源码的方法
2019/09/02 Javascript
VueCli4项目配置反向代理proxy的方法步骤
2020/05/17 Javascript
微信小程序实现底部弹出模态框
2020/11/18 Javascript
python网页请求urllib2模块简单封装代码
2014/02/07 Python
Python自动生产表情包
2017/03/17 Python
在Python中使用AOP实现Redis缓存示例
2017/07/11 Python
Python用61行代码实现图片像素化的示例代码
2018/12/10 Python
Python 计算任意两向量之间的夹角方法
2019/07/05 Python
Django中使用CORS实现跨域请求过程解析
2019/08/05 Python
python 计算两个列表的相关系数的实现
2019/08/29 Python
最新pycharm安装教程
2020/11/18 Python
python爬虫工具例举说明
2020/11/30 Python
在vscode中启动conda虚拟环境的思路详解
2020/12/25 Python
AmazeUI在模态框中嵌入表单形成模态输入框
2020/08/20 HTML / CSS
手工制作的意大利礼服鞋:Ace Marks
2018/12/15 全球购物
中东最大的在线宠物店:Dubai Pet Food
2020/06/11 全球购物
民族团结先进集体事迹材料
2014/05/22 职场文书
2014年学校教学工作总结
2014/12/06 职场文书
工人先锋号申报材料
2014/12/29 职场文书