二十二.uniapp仿封装el-table

前言

最近在使用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进行配置;
  • 高度和最大高度 :传入heightmax-height进行配置;
  • 自定义列
  • 表尾合计
  • 列索引 :传入indexShow开启索引

做好功能清单之后,接下来就是开始进行基础布局,由于小程序中没有table属性,所以这里都用view标签代替,通过css去设置table属性,表头为了方便这里默认固定。其中几个比较核心的点是:

  1. 表头宽度计算
  2. 固定表头和列样式
  3. 合计功能

核心功能

参考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粘性定位去实现该功能,这个属性比较像realtivefixed的结合,解决了fixed只能根据根目录定位的痛点,简单理解就是默认是相对定位,滚动后超过设置的top,或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,设置showFooterfooterMethod两个参数,设置是否显示合计和合计函数。

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 插件市场

其他文章

相关推荐
徐同保2 分钟前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun1 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp1 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.2 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl4 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫5 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友5 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理7 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻7 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js
2501_915909068 小时前
WebView 调试工具全解析,解决“看不见的移动端问题”
android·ios·小程序·https·uni-app·iphone·webview