前言
午后写的这篇文章,微风加阳光,很是惬意🏖。
然后,话不多说,我们直接进入正题,今天主要会讲解一下
可伸缩列、固定列、多级表头
和几个表格的常见问题✍️,干货满满哦😯。
温馨提示:本篇文章是继上次 table 组件了解一下? 这篇文章之后写的,所以建议先去阅读一下前面那篇文章👀。当然也可以直接往下看,因为这里主要说下思路,没放多少代码😁。
可伸缩列
可伸缩列,顾名思义就是我们可以通过拖动表头的
border
来实现列宽的大小,看看下面这张图你就懂了👇:
第一步:
我们在表头的每个
th
里面都多增加一个
div
,也就是上图中红色的部分,然后绝对定位于
th
的右边即可。
第二步:
在表头
header
和表体
body
同级的地方增加一个
div
来表示上图中的虚线,默认是隐藏的,就像下面这张图:
第三步:
对红色部分的鼠标事件进行监听:当鼠标按下的时候,就显示虚线,并实时改变虚线的
left
值,当鼠标抬起的时候就隐藏虚线,并计算出拖拽后的列宽,之后修改的
columns
里面对应列的
width
即可,这样表头和表体的列宽都会同步改变。
当然,还要记得在鼠标抬起时解绑
mousemove
和
mouseup
事件,这是个好习惯。
以上就是可伸缩列的实现方式。
固定列
接下来我们来看看固定列是怎么实现的。首先还是 api 的设计,这个应该容易想到,我们在
columns
里面把需要固定的列添加上
fixed
属性即可,它的值有两种选择(
left
或
right
),就像下面这样:
columns: [
{
title: '姓名',
key: 'name',
width: 100,
fixed: 'left'
}
...
]
复制代码
为了简化问题,这里我们只考虑左列固定,因为右列固定也是一样的。
这里先一句话说明下原理😁:就是多渲染一份表格并绝对定位在原来的表格之上,下面这张图应该能帮助你理解👇:
第一步:处理表格数据
因为要把需要固定的列放到左边,所以我们一开始最需要处理的就是表格数据,把所有有
fixed="left"
属性的列排在前面。
第二步:渲染两个表格
再来就是正常渲染两个表格(A 和 B),事实上表格 A 和表格 B 是一模一样的,只不过表格 B多设置了一些属性,比如绝对定位在左上角、定宽(宽度就是有
fixed="left"
属性的列的宽度之和)、溢出隐藏啊等。具体代码结构如下图所示:
fixed
部分我们可以设置
visibility: hidden
,因为它不需要展示,而且 Element 也是这样写的;同样地,对于表格 B 的非
fixed
部分也可以设置
visibility: hidden
。
这时候我突然产生了一个疑问🤔,就是 为什么要设置成
visibility: hidden
而不设置成
display: none
呢?
display: none
难道不是可以渲染更少的 dom 吗?设置成
visibility
的意义在哪里?
这是个不错的问题,建议大家思考一下下。。。再往下看😁。
带着疑问我顺便看了下 iView 和 Ant Design 的 dom 结构,发现 iView 也是用
visibility: hidden
来处理的,而 Ant Design 则是直接不渲染了,这就很奇怪啦!于是乎我把 iView 和 Element 的
visibility: hidden
换成
display: none
试了一下,发现好像也 ok,表格展示也是对的,没什么问题,所以是为什么呢?其实最主要的原因是把
visibility: hidden
换成
display: none
会引发
行对不齐
的问题。
什么意思呢?就是说如果我们设置
display: none
的话,表格 A 里面的行高是不固定的,但这时候表格 B 是没有展示表格 A 中表体的内容,所以表格 B 不能同步表格 A 里面的高;而如果我们设置成
visibility: hidden
的话,表格 A 和表格 B 其实是都包含所有数据的,只是视觉上不可见而已,这样它们的行高就能够保持完全一致,虽然会导致多余的 dom 元素。
那 Ant Design 为什么可以呢?其实 Ant Design 也是有这个问题的,虽然它没有渲染多余的 dom,但是它会事先计算出表格 A 的行高,然后在去同步设置固定列的行高,此外在表格大小、列宽变化的时候也要去同步。它们是两种不同的方案,大家自己好好体会一下🙌。
当然,这里我们采用的是 Element 和 iView 的方案。
第三步:同步滚动
上面的实现方式会有什么问题呢🤔?
最明显的问题就是表格 A 和表格 B 是割裂的,
所以滚动其中一个表格的时候,另外一个表格是不会跟着响应的。事实上每个表格里面的表头和表体也是割裂的,所以现在我们要做就是同步滚动:当我们横向滚动表格 A 的表体时,我们需要同步滚动表格 A 的表头部分;当我们纵向滚动表格 A 的表体时,我们需要同步滚动表格 B 的表体部分。
这里以表格 A 里面的表体(
A__body
)滚动为例子,简要说下具体做法:
A__body
横向滚动时:获取
A__body
的
scrollLeft
值,然后把值同步到表格 A 的表头;
A__body
纵向滚动时:获取
A__body
的
scrollTop
值,然后把值同步到表格 B 的表体。
当然滚动是一个高频动作,所以我们可以进行防抖处理;还可以尝试用
transform
来替代
scrollTop
和
scrollLeft
进行滚动。
第四步:同步hover
同第三步一样,当我们
hover
每一行的时候也需要像滚动一样进行同步,也就是
hover
样式的同步。
同样地我们以
hover
表格 A 的表体为例🌰,监听行的
mouseenter
和
mouseleave
事件,当鼠标移入到某一行,这时候你是能获取该行信息的,然后同步到固定列的对应行即可;同理
hover
到固定列的时候也要同步到表格 A,这里就不再赘述。
多级表头
表头
首先还是 api 的设计,我们希望在
columns
里面加个
children
就能实现,就像下面这样(顺便看下多级表头长什么样):
columns: [
{
title: '日期',
key: 'date',
width: 200
},
{
title: '配送信息',
children: [
{
title: '姓名',
key: 'name',
width: 200
},
{
title: '地址',
key: 'addr'
}
]
}
]
复制代码
v-for
循环,让我们截个 iView 的代码看看大体结构:
columns
进行格式化,使其变成
下面这个样子(二维数组):
level
、
rowspan
和
colspan
,后面两个是用来实现合并单元格的,比如
columns
的第一项日期,它的
colspan
应该是它
children
的总数,如果没有
children
就是1;它的
rowspan
应该等于最大层数-当前层数+1,如果有
children
则为1。
这里可能会有点绕,所以需要停下来理一理思路🤔,但其实本质就是数据结构的转换,所以这里没有特别强调说明如何转换,大家自己动手试一试吧😊。
表体
表体渲染就简单了,只不过我们同样要对
columns
进行一下处理,我们先看看整理之后的列大概长什么样:
columns
就是遍历一下旧的
columns
,有
children
就继续遍历获取子列,没
children
就直接取出该列,类似于获取到所有叶子节点的列,这个新的列会代替原有的
columns
来渲染,由于这个数据结构转换简单点,所以我把代码贴了上来: