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

其他文章

相关推荐
正小安1 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch3 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光3 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   3 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   3 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web3 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery
安冬的码畜日常3 小时前
【CSS in Depth 2 精译_044】第七章 响应式设计概述
前端·css·css3·html5·响应式设计·响应式
二十雨辰4 小时前
[uni-app]小兔鲜-07订单+支付
uni-app
莹雨潇潇4 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
Jiaberrr4 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui