前言
最近在使用uniapp
进行开发的时候,需要使用table
列表,且table
样式大致与el-table
相同,但是目前uview
或者colorUI
等组件库并没有这种table
组件,所以这里参考el-table
的源码,使用uniapp
写了一个el-table
组件,这里记录下table
封装中遇到的问题和过程。
准备工作
开始封装之前需要参考el-table
的功能,列举下功能清单,为了方便这里就不做el-table-column
组件,通过传入columns
参数进行渲染table
。
- 基础列表 :需传入
columns
列配置和data
数据配置参数; - 斑马纹列表 :传入
stripe
进行配置; - 边框列表 :传入
border
进行配置; - 列class配置 :传入
row-class-name
进行配置; - 固定列 :
columns
中传入fixed
进行配置; - 高度和最大高度 :传入
height
和max-height
进行配置; - 自定义列
- 表尾合计
- 列索引 :传入
indexShow
开启索引
做好功能清单之后,接下来就是开始进行基础布局,由于小程序中没有table
属性,所以这里都用view
标签代替,通过css
去设置table
属性,表头为了方便这里默认固定。其中几个比较核心的点是:
- 表头宽度计算
- 固定表头和列样式
- 合计功能
核心功能
参考el-table组件源码,我们去实现这些核心逻辑功能。
表头宽度计算
在el-table
中,如果表头项未设置宽度,它宽度会根据总的宽度平分,若某一项设置了宽度,剩下列宽的会根据剩下的宽度平分,这里可以看下el-table
源码。
js
updateColumnsWidth() {
// 部分代码
const fit = this.fit;
const bodyWidth = this.table.$el.clientWidth;
let bodyMinWidth = 0;
const flattenColumns = this.getFlattenColumns();
let flexColumns = flattenColumns.filter((column) => typeof column.width !== 'number');
flattenColumns.forEach((column) => {
if (typeof column.width === 'number' && column.realWidth) column.realWidth = null;
});
if (flexColumns.length > 0 && fit) {
flattenColumns.forEach((column) => {
bodyMinWidth += column.width || column.minWidth || 80;
});
const scrollYWidth = this.scrollY ? this.gutterWidth : 0;
if (bodyMinWidth <= bodyWidth - scrollYWidth) { // DON'T HAVE SCROLL BAR
this.scrollX = false;
const totalFlexWidth = bodyWidth - scrollYWidth - bodyMinWidth;
if (flexColumns.length === 1) {
flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth;
} else {
const allColumnsWidth = flexColumns.reduce((prev, column) => prev + (column.minWidth || 80), 0);
const flexWidthPerPixel = totalFlexWidth / allColumnsWidth;
let noneFirstWidth = 0;
flexColumns.forEach((column, index) => {
if (index === 0) return;
const flexWidth = Math.floor((column.minWidth || 80) * flexWidthPerPixel);
noneFirstWidth += flexWidth;
column.realWidth = (column.minWidth || 80) + flexWidth;
});
flexColumns[0].realWidth = (flexColumns[0].minWidth || 80) + totalFlexWidth - noneFirstWidth;
}
} else { // HAVE HORIZONTAL SCROLL BAR
this.scrollX = true;
flexColumns.forEach(function(column) {
column.realWidth = column.minWidth;
});
}
}
但是我们这里没必要弄这么复杂,所以我把这里简化了下,主要拆分成几步即可。
第一步:获取需要分配宽度的列,计算列表最小宽度
js
const flexColumn = this.columns.filter(item => !item.width);
// 这里我们设置,如果不给列设置宽度,默认最小宽度为80
const minWidth=80;
// 计算整个列表最小宽度
let bodyMinWidth = this.columns.reduce((t, c) => {
c.width = c.width || minWidth;
return t + parseFloat(c.width);
}, 0);
//如果索引列存在
if(this.indexShow){
bodyMinWidth+=60
}
第二步:计算列表实际宽度
这个宽度一定 >= 列表最小宽度。
js
mounted(){
const query = uni.createSelectorQuery().in(this).select('.base-table');
query.boundingClientRect(data => {
this.tableWidth = data.width;
}.exec();
}
第三步:分配宽度
可分配宽度=列表实际宽度- 列表最小宽度;
- 如果列项=1,则直接将剩余宽度直接分配给他即可;
- 如果列项>1,则计算剩余宽度平均值,将这个平均值平均分配给每项。
js
if (flexColumn.length > 0 && bodyMinWidth < clienWidth) {
const flexWidth = clienWidth - bodyMinWidth;
if (flexColumn.length === 1) {
flexColumn[0].width = minWidth + flexWidth;
} else {
const scaleWidth = flexWidth / flexColumn.length;
flexColumn.forEach(item => {
item.width = minWidth + Math.floor(scaleWidth);
});
}
}
固定表头和列
我们可以使用position
中的sticky
粘性定位去实现该功能,这个属性比较像realtive
和fixed
的结合,解决了fixed
只能根据根目录定位的痛点,简单理解就是默认是相对定位,滚动后超过设置的to
p,或left
等值后,变成相对于父盒子的固定定位。假设table
组件结构如下
js
<view class="base-table">
<view class="base-table-inner">
<view class="base-table-header">
</view>
<view class="base-table-body">
</view>
<view class="base-table-footer">
</view>
</view>
</view>
固定头部和底部,可直接如下设置,这样即可实现
scss
.basel-table{
overflow:auto;
.base-table-inner {
display: flex;
height: 100%;
flex-direction: column;
.base-table-header,
.base-table-footer {
width: 100%;
flex-shrink: 0;
position: sticky;
z-index: 3;
}
.base-table-header{
top:0;
}
.base-table-footer{
bottom:0;
}
.base-table-body {
position: relative;
flex: 1;
}
}
}
至于列的固定,可以通过columns
中配置的fixed
去配置:
js
getCellCalss(row) {
const classList = [];
if (row.fixed) {
classList.push('fixed');
if (row.fixed === 'left') {
classList.push('fixed-left');
} else {
classList.push('fixed-right');
}
}
return classList
}
scss
&.fixed {
position: sticky !important;
z-index: 2;
border-right: 0;
&.fixed-left {
left: 0;
}
&.fixed-right {
right: 0;
}
}
合计功能
参考el-table
,设置showFooter
和footerMethod
两个参数,设置是否显示合计和合计函数。
js
watch: {
data: {
handler() {
this.init();
},
immediate: true,
deep: true
}
}
js
init(){
this.sumList = [];
if (this.showFooter && this.data.length > 0) {
const { columns, data, footerText } = this;
if (typeof this.footerMethod === 'function') {
this.sumList = this.footerMethod({ columns, data });
} else {
columns.forEach((column, index) => {
if (!this.indexShow && index === 0) {
this.sumList[index] = footerText;
return;
}
const values = data.map(item => Number(item[column.fieldName]));
const precisions = [];
let notNumber = true;
values.forEach(value => {
if (!Number.isNaN(+value)) {
notNumber = false;
const decimal = `${value}`.split('.')[1];
precisions.push(decimal ? decimal.length : 0);
}
});
const precision = Math.max.apply(null, precisions);
if (!notNumber) {
this.sumList[index] = values.reduce((prev, curr) => {
const value = Number(curr);
if (!Number.isNaN(+value)) {
return Number.parseFloat((prev + curr).toFixed(Math.min(precision, 20)));
} else {
return prev;
}
}, 0);
} else {
this.sumList[index] = '';
}
});
}
}
}
最后
到这里uniapp
版的el-table
已基本实现了核心功能,至于具体的一些斑马纹这些,只不过就是添加传入一个参数添加样式而已,这里就不做过多描述,目前这个插件已经放在uniapp
插件市场里面,有兴趣可以去看看。 点击这里去查看:basic-table table列表 - DCloud 插件市场