封装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 相关文章推荐
仿猪八戒网左下角的文字滚动效果
Oct 28 Javascript
基于jQuery的输入框在光标位置插入内容, 并选中
Oct 29 Javascript
使用Post提交时须将空格转换成加号的解释
Jan 14 Javascript
JS将秒换成时分秒实现代码
Sep 03 Javascript
js的回调函数详解
Jan 05 Javascript
jQuery中eq()方法用法实例
Jan 05 Javascript
javascript实现淘宝幻灯片广告展示效果
Apr 27 Javascript
JavaScript将DOM事件处理程序封装为event.js 出现的低级错误问题
Aug 03 Javascript
jQuery中checkbox反复调用attr('checked', true/false)只有第一次生效的解决方法
Nov 16 Javascript
BootStrap Select清除选中的状态恢复默认状态
Jun 20 Javascript
jQuery选取所有复选框被选中的值并用Ajax异步提交数据的实例
Aug 04 jQuery
javascript实现超好看的3D烟花特效
Jan 01 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数据缓存技术
2007/02/14 PHP
php数组函数序列之array_slice() - 在数组中根据条件取出一段值,并返回
2011/11/07 PHP
php tp验证表单与自动填充函数代码
2012/02/22 PHP
PHP网页游戏学习之Xnova(ogame)源码解读(十)
2014/06/24 PHP
PHP以mysqli方式连接类完整代码实例
2014/07/15 PHP
PHP PDO操作MySQL基础教程
2017/06/05 PHP
jQuery提交表单ajax查询实例代码
2012/10/07 Javascript
jquery仿QQ商城带左右按钮控制焦点图片切换滚动效果
2013/06/27 Javascript
JavaScript数据类型检测代码分享
2015/01/26 Javascript
jquery对象访问是什么及使用方法介绍
2016/05/03 Javascript
JS制作图形验证码实现代码
2020/10/19 Javascript
JQuery统计input和textarea文字输入数量(代码分享)
2016/12/29 Javascript
原生JS发送异步数据请求
2017/06/08 Javascript
Vue中的数据监听和数据交互案例解析
2017/07/12 Javascript
JavaScript面向对象精要(下部)
2017/09/12 Javascript
JavaScript查看代码运行效率console.time()与console.timeEnd()用法
2019/01/18 Javascript
微信小程序学习笔记之跳转页面、传递参数获得数据操作图文详解
2019/03/28 Javascript
[00:57]辉夜杯战队访谈宣传片—VG
2015/12/25 DOTA
Django中实现一个高性能计数器(Counter)实例
2014/07/09 Python
Python中的os.path路径模块中的操作方法总结
2016/07/07 Python
Django REST Framework序列化外键获取外键的值方法
2019/07/26 Python
python3文件复制、延迟文件复制任务的实现方法
2019/09/02 Python
Python查找不限层级Json数据中某个key或者value的路径方式
2020/02/27 Python
django跳转页面传参的实现
2020/09/17 Python
html5 offlline 缓存使用示例
2013/06/24 HTML / CSS
Html5跳转到APP指定页面的实现
2020/01/14 HTML / CSS
戴森台湾线上商城:Dyson Taiwan
2018/05/21 全球购物
毕业生精彩的自我评价分享
2013/10/06 职场文书
环境工程大学生自荐信
2013/10/21 职场文书
刘胡兰的英雄事迹材料
2014/02/11 职场文书
青年文明号服务承诺
2014/03/31 职场文书
小学校长汇报材料
2014/08/20 职场文书
女性健康知识讲座主持词
2015/07/04 职场文书
董事长致辞
2015/07/29 职场文书
小学生班干部竞选稿
2015/11/20 职场文书
浅谈@Value和@Bean的执行顺序问题
2021/06/16 Java/Android