前言
在b端开发过程中,我用的最多的组件是table组件, ui组件库使用的是elementui, 所以比较简单的表格显示单纯只用elementui的table就可以了,但是一旦在表格中需要涉及多种交互(排序, 存储, 筛选, 编辑)时,el-table的功能显然是不够的, 大部分需要自己在代码中实现, el-table的可拓展性不是很高, 在表格功能复杂和数据非常多的时候,迭代和开发的难度增加,表格渲染卡顿。
CmciTable组件的由来
cmci是我现在正在迭代的的一个管理系统类项目群体总称, 包含前端项目(评审,测试, ci管理, 需求,版本, 样品)等管理系统,样式统一,UI库也统一使用elementui
我接手开发和整改CMCI所有项目的时候为了统一管理和后期维护采用了vxetable作为的CMCI项目组内部项目table组件的demo模板 vxe-table的可拓展性更高, 可以和其他ui组件库配合。
vxetable代码抽离
vxe-table不仅包含了table组件还包含其他的UI组件,我们并不需要这些组件,因为我们使用elementUI了, 只是单纯的使用table组件即可,加上我们项目组的table样式基本和vxetable的完全不一样, 为了迭代和开发方便,我直接从源码中把table相关的代码下来,样式重新改了一版, 只要是看过他的文档的都知道Vxetable的配置很复杂,就是因为内部支持的功能非常多,我在看源码的过程中也发现很多官网文档没有说到的使用说明,导致在使用过程中总感觉在踩坑。可能还是因为使用场景不同的原因。
在使用过程中逐步修改源码,适配我们的项目需求,整个抽离和使用迭代过程持续了一整年,目前已经迭代到12个版本,但是我们觉得大部分需求还是可以给其他项目组使用的,因为这些需求都是b端的使用者提出来的,对于使用习惯上来说应该具有通用性。
更新记录
v0.1.1 : 完成vxe-table整体table组件移入
v0.1.2 : 完成筛选模块改装, 自定义筛选不显示筛选按钮并向业务提供筛选方式调用, 部分样式调整
v0.1.3 : 完成编辑模块自动聚焦&筛选标志位外放到业务层&表格默认选中项每次加载都会勾选&筛选添加字段动态控制是否筛选&排序业务层添加sortBy字段
V0.1.4: 添加编辑校验功能&样式适配elementui编辑框&动态控制是否启用编辑功能&筛选添加filterid显示当前筛选的字段
V0.1.5: 添加本地持久化功能,支持排序, 筛选, 隐藏/显示列功能按登录用户存储&树形结构默认展开所有树节点 &添加筛选取消按钮面板
V0.1.6: 添加cmci-grid方法解决列过多卡顿问题
V0.1.7: 添加默认隐藏列可以存储到下次刷新后显示的功能
V0.1.8: 新增gulp打包配置, 转义es6语法
V0.1.9: 解决筛选无法获取最新业务值, 新增筛选项本地存储
V0.2.0:添加行内编辑鼠标移入单元格显示对应编辑icon
V0.2.1: 本地存储多个列表异常修复
V0.2.1-beta: 修复单选框样式&行内编辑hover样式&激活单元格编辑方法失效修复
V0.2.2 : 全局参数设置
安装方式
npm i cmcitable (目前这个npm包只在我们内网npm库可以安装使用,至于叫啥table自己想叫啥都行,在源码里面改就行)
一. 使用方法
我为了方便以后创建自己的插件库所以采用pnpm+monorepo的开发模式来管理公用组件,也为了统一整个项目组的插件使用,所以这个表格插件也是放在这个项目下统一管理。 前期因为打包采用的vue-cli3的库模式打包,在vue-cli2的项目中会导致umd,js文件找不到table组件,当时不知道这是因为库模式打包出来的文件含有es6语法,低版本的脚手架如果没单独处理会导致项目报错。
所以在使用的过程中要么在window中找到cmcitable:
1.按需加载
import 'cmcitable' import {CmciTable,Tooltip} from window.CmciTable Vue.use(cmcitable)2.全部安装
import 'cmcitable' Vue.use(window.CmciTable)
要么就是在项目中先用babel进行es6处理,即在webpack.base.conf.js中的module中添加rules来处理
但是需要注意,babel转义会导致构建速度变慢
在V0.1.8以前的项目中node_moudles包内的es6语法无法解析需要配置webpack,同样css文件在低版本的webpack中也是需要配置css-loader,为了解决这个问题,V0.1.8版本开始使用gulp来做js代码转义以及压缩和css压缩等,所以直接使用以下方式导入即可
1. 按需加载
目前按需加载只支持tooltip,CmciTable,其他的vxetable的组件均不支持
import {CmciTable} from 'cmcitable' //引入样式 import 'cmcitable/lib/cmcitable.css' Vue.use(CmciTable)2. 全部安装
import CmciTable from 'cmcitable' 引入样式 import 'cmcitable/lib/cmcitable.css' Vue.use(CmciTable)3.引入样式
import 'cmcitable/lib/cmcitable.css'
二. 项目中使用
因为为了适配项目中常用的功能,所以源码进行了删减,不支持的功能:
-
导入, 到出
-
功能组件,: 弹框, 选择器, from表单等
-
右键菜单,键盘事件(移动高亮等), loading效果
-
分页, 弹框
三. 更改功能点
筛选
我当时是使用V2.x版本,官网的筛选例子我第一次使用的时候完全摸不着头脑,就因为文档说明的原因,在看了源码以后才能了解这是干嘛用的
- 因为我们是不需要底部的筛选按钮,在鼠标点击其他位置直接触发筛选,但是官网的插槽一定会有底部的筛选按钮
所以对于用户使用插槽自定义的筛选目前已经屏蔽了底部的筛选按钮, 并将筛选功能暴露到业务端筛选具体使用
- 我们的筛选要求点击后直接聚焦,下拉框显示下拉框, 文本聚焦,所以在筛选中我们只用了插槽,里面都是elementui的选择框和输入框
3. vxetable的筛选是基于表格的本地筛选,但是我们需要后端筛选,所以后端返回的表格数据刷新后vxetable的状态值都还原了所以在源码上我做了更改,为了更改的控制当前的筛选状态添加了字段own_checked,
-
own_checked用于控制当前筛选标签是否高亮,官网上的文档高亮采用的是内部变量checked,更改该状态的方式是changeOption,这个checked是判断是否处于筛选状态的重要标志,有个isFilter方法就是用这个状态值进行的判断,所以我们的own_checked字段基本取代了内部的checked字段的功能。
-
另外说下官网文档没有说明的一点,在筛选值发生改变时候会存在filter-change方法,这个方法的触发是内部的comfirmFilter()方法,而用户要触发这个方法是通过changeOption方法触发的,但是官网没说在多选的时候即使调用的changeOption也是无法触发filter-change方法的,必须点击底部的筛选按钮,所以我直接将confirmFilter方法暴露给用户,让用户决定什么时候触发
具体代码如下:
<cmci-column
title="标签"
width="80px"
field="label"
:filters="[
{
data: '',
disabled: showfilter('label'),
own_checked: !!filter.label,
},
]"
>
<template #filter="{ $panel }">
<el-input
v-model="filter.label"
clearable
@change="$panel.confirmFilter()"
placeholder="按回车确认筛选"
/></template>
</cmci-column>
computed:
showfilter(){
return (id)=>!this.keys.includes(id)
}
`
- 筛选框的关闭控制: vxe-table的筛选框只有点击筛选框不会关闭以外点击任何位置都会关闭筛选框对于样式调整起来很不友好, 所以在filters数组中的元素中新增了一个字段isclose,方便样式调试
注意:filters中如果存在多个元素, 任意一个元素中有isclose=false都会生效
以下新增的筛选配置项:
| 属性名 | 属性类型 | 含义 | 默认值 |
|---|---|---|---|
| disabled | boolean | 决定当前列是否支持筛选(动态决定是否筛选使用) | false |
| isclose | boolean | 当前弹框是否在调试时候关闭 | true |
| own_checked | boolean | 当前列筛选是否高亮 | false |
| header-class-name | string | 设置当前列的类名, 如果设置为syncfilter则控制当前筛选是否高亮 | '' |
| filterid | string | 当前传给后端的筛选字段/或者控制筛选该列的字段 | ' ' |
| showbutton | boolean | 底部是否显示重置/筛选按钮 | false |
| label | string | vxetable自带 | '' |
| value | sringt | 当前筛选的值 | '' |
设置筛选底部按钮
当然我也保留了使用底部按钮的情况
-
设置showbutton=true , 内部的disabled属性是通过own_checked控制的
-
cmci-column列设置重置方法filter-reset-method 处理筛选数据, 注意这个重置筛选方法返回true会触发filter-change关闭筛选框, (因为可能只是重置值, 不需要调用筛选接口,也不需要关闭筛选面板)
-
聚焦输入框修改为当前筛选项为空时候触发, 当筛选项有值则不会触发自动聚焦
-
注意项目重置中的筛选字段如果跟field不同,可以在options中获取filterid,filterid是设置的筛选的字段
例如:
<cmci-column field="test_owner" title="责任人" width="120px" sort-by="owner_name" :sortable="true" :edit-render="{}" :filter-reset-method='filterresetmethod' :filters="[{ own_checked:typeof(filter.test_owner)=='number', showbutton:true }]" > <template #filter="{$panel}"> <el-select v-model="filter.test_owner" filterable clearable @change="$panel.confirmFilter()"> <el-option v-for="item in test_owners" :key="item.id" :value="item.id" :label="item.name" ></el-option> </el-select> </template> </cmci-column> //重置方法 filterresetmethod({options,column}){ this.filter[column.field]='' //如果项目中不是用field字符串传给后端的接口的话,就需要传 filterid (options中获取filterid) return true //需要关闭筛选框并发送接口 },
编辑
在edit-render中配置:
| 属性名 | 属性类型 | 含义 | 默认 |
|---|---|---|---|
| enable | boolean | 动态控制当前是否编辑 | true |
| icon | string | 单元格移入编辑显示的icon类名 | 'vxe-icon--edit-outline' |
<cmci-column slot="gradeId" title="问题级别"
field="gradeId" min-width='80'
:edit-render="{enable:!isEdit,icon:'vxe-icon--caret-bottom'}">...</cmci-column>
-
编辑功能没有内置的组件,基本都是靠插槽定义需要注意数据还原功能, 必须是原来table的data中存在的字段才能做到重置,不存在的字段不会重置
-
注意closeEdit这个方法,在内部这个方法是关闭编辑框同时也是更新数据的方法, 目前配置是点击任何非编辑框位置都会触发这个方法, 如果不需要失去焦点就更新可以通过配置edit-config.autoClear属性来关闭这个功能
-
目前已经实现点击编辑自动聚焦的功能
-
clearEdit()方法一定会触发edit-closed事件,并且可以关闭编辑框
-
编辑单元格正常只在表头有个编辑图标用户可能不知道当前单元格可编辑,所以在编辑单元格时候添加hover显示icon,默认都会显示
如果需要其他的icon可自行配置
编辑校验
编辑校验的配置参考vxe-table官网, 注意编辑如果在输入或者选中选项期间需要触发校验必须调用vxetable的方法updateStatus才能触发校验, 因为校验只会在validate调用后校验, 不需要实时触发校验的行不用调用这个方法
validator的使用方法:返回一个Promise,注意: 返回的reject中可以添加返回的信息,如果不写返回信息,会优先获取整个校验的message, 没有message不会有提示
validator: ({ cellValue, row, column }) =>
new Promise((resolve, reject) => {
if ((row.checkResult == 2 || row.checkResult == 4 || !row.checkResult) && cellValue) {
return reject()
} else {
return resolve()
}
}),
message:'key不能为空',
trigger: 'change'
...
也可以在reject中直接写message
validator: ({ cellValue, row, column }) =>
new Promise((resolve, reject) => {
if ((row.checkResult == 2 || row.checkResult == 4 || !row.checkResult) && cellValue) {
return reject({message:'不能为空'})
} else {
return resolve()
}
}),
trigger: 'change',
..
错误信息也可以使用new Error()返回,但是正确时候一定要用resolve返回
{
validator: ({ cellValue }) =>
{
if (!cellValue) {
return new Error('哈哈')
} else {
return Promise.resolve()
}
},
trigger: 'change',
},
编辑校验在校验失败时按照要求会高亮当前行
排序
在项目中排序一般都是后端排序, 并且排序字段是field控制, 如果遇到排序字段和field不相同的可以在column中设置sort-by字段
<cmci-column
field="test_owner"
title="责任人"
width="120px"
sort-by="owner_name"
:sortable="true"
></cmci-column>
在sort-change方法中已经把sortBy暴露到业务层,可以直接取
sortChange({field,sortBy, order}) {
if (order) {
this.$set(
this.filter,
'sorting',
[{ [sortBy?sortBy:field]: order.startsWith('desc') }],
) } else {
this.$delete(
this.filter,
'sorting',
)
}
},
持久化功能
持久化功能跟vxetable官网的功能不太一样, 因为项目中可能出现不同用户登录的情况, 所以在原来的功能上做了改动,支持将字段传给后端进行接口存储
存储位置在sessionstorage中, 只针对当前窗口有效
表格整体配置
| 配置项 | 类型 | 含义 |
|---|---|---|
| id | string | 当前存储表格的名称 |
| userid | string | 当前登录用户唯一值 |
例如:
代表当前表格叫"cmcitable", 当前用户名:'wangxi', 存储项为:排序和筛选和隐藏列
<cmci-table
border
ref="xTable1"
id='cmcitable'
userid='wangxi'
:data="tableData"
@filter-change='filterchange'
:custom-config={storage:{visible:true,sort:true,filter:true}}>
</cmci-table>
存储项新增配置
custom-config属性与官网配置相同, custom-config.storage设置为true时代表都存储, 设置为对象时代表存储部分数据, 以下是新增属性
| 配置项 | 类型 | 含义 |
|---|---|---|
| filter | boolean | 是否存储筛选项 |
具体取值排序参考sort-config.defaultSort的默认排序,筛选直接赋值给项目中的筛选项绑定值, 显示/隐藏列源码中处理不需要单独设置
VXE_CUSTOM_STORE:{wangxi: hahah: {
filterData: {name: "1234"}
sortData: {name: {order: "desc", field: "name"}}
visibleData: {sex: false}
_userid: "hahah"
_v: "3.7.9"}}
注意
- 在默认有隐藏列的情况, 如果想存储默认隐藏列的显影需要在列的配置项中添加配置params:{savesesssion:true} 否则插件内部不会对默认隐藏列做存储
{ field: 'apply_prod_strong', title: '适用产品强相关',visible:false ,params:{savesesssion:true} },
2. 筛选项存储
在做筛选储存时候需要注意我们的筛选是插槽实现的,只能在业务代码内获取session存储的值, 表格内不做处理, 所以存储时候必选在cmci-culomn中的filters存储筛选的字段值filterid值和当前的筛选value值
` <cmci-column
slot="project_name"
min-width="120"
field="project_name"
title="项目"
:filters="[{ own_checked:
!!searchForm.project_id,filterid:'project_id',value:searchForm.project_id }]"
>
取值处理
let store=JSON.parse(sessionStorage.getItem('CMCI_CUSTOM_STORE'))
let user=this.$store.state.currentUser.username
if(store){
if(user in store){
if('taskTable' in store[user]){
this.searchForm='filterData' in store[user].taskTable ?
store[user].taskTable.filterData:{}
}
}
}
树形结构默认展开
官网的默认展开只有初始化时候展开, 当切换菜单无法展开, 所以修改源码内的tree-config.expandAll配置, 配置后数据更新后会自动触发展开子树
关于多选下次记住选择
由于我们项目多数都是多选,可能用到下次进入时候勾选上上次的值,正常情况调用setCheckboxRow去一个一个设置,但是也可以使用checkbox-config.checkRowKeys属性,因为这个属性是有初始化时候触发,所以我更改了源码让它每次都能触发,获取本次的已勾选选项
全局参数
全局参数的设置和官网是不一样,官网使用setup设置, 但是设置zindex没有起到作用, 因为代码内使用domzindex库来获取最新的zindex赋值,所以在源码上做了相应的改动
Vue.use(CMCItable,{
zIndex:2000,
size:'small',
.... //设置项参考官网
icon:{},
table:{}
})
总结
目前已经有四个系统接入了这个table组件,不同的前端开发人员也在慢慢熟悉这一套使用标准,方便整个项目组的系统ui和功能的统一,唯一的缺点就是只有我一个人维护这个组件库
其次这个组件库其他公用的组件我后期会慢慢把文档做整理,(主要是懒,写文档也挺没成就感的)方便大家在统一项目上提供便利 这个代码仓库分享出来,大家可以根据自己项目的需要再进行修改