Bootstrap

微信小程序表格组件(锁头、锁列、样式、字体颜色等)

微信小程序表格组件(锁头、锁列、样式、字体颜色等)

因为微信不支持table组件,所以通过scroll-view组件来实现一个表格组件。

功能实现:

  1. 提供了三种表格样式
  2. 可以锁头
  3. 可以锁第一列
  4. 自定义表头宽度、高度、背景色、文字大小、字体颜色、是否加粗
  5. 自定义表格宽度、高度
  6. 表格内容的数字是否需要与0判断进行颜色区别(本人做金融,所以需要红绿色)

增加功能(2024年3月19日)

  1. 可以修改表格最大高度(从父组件中动态传值,防止表格高度过小下滑时表头无法固定)
  2. 表格数字可以转换单位
  3. 表格数字可以转成百分百形式

增加功能(2024年3月21日)

  1. 新增表格模式:可以横向或者纵向,用table_sl控制。具体前请见案例
  2. 新增表头点击事件(head_click)和表格行(row_click)点击事件

结果展示

注:该demo案例是table_sl为fir模式下
在这里插入图片描述在这里插入图片描述
注:该demo案例是table_sl为sec模式下
在这里插入图片描述在这里插入图片描述

组件代码:

wxml页面:
<!-- 小程序表格
参数说明:
totalHeight:表格最大高度(默认为250px)
table_sl:表格模式(提供两种:'fir'表示横向, 'sec'表示纵向)
line_sl:表格线条样式(提供三种:'all', 'fir', 'sec')
headList: 表头数据
table:表格数据
lock_header:是否锁表头(默认为true,锁头)
lock_first:是否锁第一列(默认为true,锁第一列)
table_width:表格宽度(默认为180rpx)
header_height:表头高度(默认为60rpx)
header_bg:表头背景色(默认为#fff,白色)
header_text_size:表头文字大小(默认为32rpx)
header_bold:表头文字是否加粗(默认为true,加粗)
header_text_color:表头文字颜色(默认为#111,黑色)
cell_height:表格内容高度(默认为100rpx)
is_unit:该列(table_sl='sec'时为行)表格内容(针对数字型|字符串型数字)是否需要转换成"亿,万"单位式
is_per:该列(table_sl='sec'时为行)表格内容(针对数字型|字符串型数字)是否需要转成百分比形式(默认保留两位小数)
注:如果表格内容中的数字不需要做不同标记,则使用字符串类型数字即可(颜色有:默认大于0为red,等于0为gray,小于0为green)
head_click:表头点击事件
row_click:表格行点击事件
-->
<view class="table_box" style="border-width: {{line_sl=='all'?'':(line_sl=='fir'?'0 0 1rpx 0':'0')}};">
  <scroll-view class="table_scroll" scroll-y scroll-x="{{lock_first}}" style="max-height:{{totalHeight}}px;" bindscrolltolower="handleScrollToLower">
    <view class="table_header" style="{{lock_header?'position: sticky': ''}};{{header_bold?'font-weight: bold': ''}}">
      <view class="table_header_item" style="border-width: {{line_sl=='all'?'':'0'}};{{header_style}}" wx:for="{{headList}}" wx:key="index">
        <text wx:if="{{table_sl=='fir'}}" style="width: {{table_width[index]?table_width[index]:'180'}}rpx" data-index="{{index}}" data-value="{{item}}" bind:tap="header_click">{{item}}</text>
        <block wx:else>
          <text wx:if="{{index==0}}" style="width: {{table_width[index]?table_width[index]:'180'}}rpx">{{item}}</text>
          <view wx:else class="alignment" style="width: {{table_width[index]?table_width[index]:'180'}}rpx" data-index="{{index}}" data-value="{{item}}" bind:tap="header_click">
            <view>
              <view>{{item[1]}}</view>
              <view style="font-size: 18rpx; color: gray; font-weight: normal;">{{item[0]}}</view>
            </view>
            <text class="arrow" style="margin-left: 10rpx;" />
          </view>
        </block>
      </view>
    </view>
    <block wx:if="{{table_sl=='fir'}}">
      <view class="table_content_line" wx:for="{{table}}" wx:key="rowIndex" wx:for-index="rowIndex" wx:for-item="rowItem">
        <block wx:for="{{rowItem}}" wx:key="index">
          <view wx:if="{{index==0}}" class="table_content_line_item" style="border-width: {{line_sl=='fir'?'0 0 1rpx 0':(line_sl=='sec'?'0':'')}};height: {{cell_height}}rpx">
            <view style="width: {{table_width[index]?table_width[index]:'180'}}rpx;" data-index="{{rowIndex}}" data-value="{{rowItem}}" bind:tap="row_click">
              <view>{{item[1]}}</view>
              <view>{{item[0]}}</view>
            </view>
          </view>
          <view wx:else class="table_content_line_item" style="border-width: {{line_sl=='fir'?'0 0 1rpx 0':(line_sl=='sec'?'0':'')}};height: {{cell_height}}rpx" bind:tap="row_click" data-index="{{rowIndex}}" data-value="{{rowItem}}">
            <text style="width: {{table_width[index]?table_width[index]:'180'}}rpx; color: {{uts.judgeNumber(item)?(item>0?'red':(item==0?'gray':'green')):''}}">{{is_unit[index]?uts.change_unit(item):(is_per[index]?uts.change_per(item):item)}}</text>
          </view>
        </block>
      </view>
    </block>
    <view wx:else>
      <view class="table_content_line" wx:for="{{table}}" wx:key="rowIndex" wx:for-index="rowIndex" wx:for-item="rowItem">
        <block wx:for="{{rowItem}}" wx:key="index">
          <view class="table_content_line_item" style="border-width: {{line_sl=='fir'?'0 0 1rpx 0':(line_sl=='sec'?'0':'')}};height: {{cell_height}}rpx" data-index="{{rowIndex}}" data-value="{{rowItem}}" bind:tap="row_click">
            <text style="width: {{table_width[rowIndex]?table_width[rowIndex]:'180'}}rpx; color: {{uts.judgeNumber(item)?(item>0?'red':(item==0?'gray':'green')):''}}">{{is_unit[rowIndex]?uts.change_unit(item):(is_per[rowIndex]?uts.change_per(item):item)}}</text>
          </view>
        </block>
      </view>
    </view>
  </scroll-view>
</view>

<!-- wxs脚本 -->
<wxs module="uts">
  /**
   * 给数字增加单位
   * @param {String|Number} item 需要转换的数字或者字符类型数字
  */
  var change_unit = function (item) {
    var num = parseFloat(item)
    if (num) {  // 先判断是否可以转化成数字
      if (num >= 100000000 || -num >= 100000000) {  // 因为在wxs脚本中不能使用abs函数,所以利用这个方法处理负数形式
        return (num / 100000000).toFixed(2) + ' 亿';
      } else if (num >= 10000 || -num >= 10000) {
        return (num / 10000).toFixed(2) + ' 万';
      }
      return num
    }
    return item;
  }
  /**
 * 给数字转成百分比形式
 * @param {String|Number} item 需要转换的数字或者字符类型数字
*/
  var change_per = function (item) {
    var num = parseFloat(item)
    if (num) {
      return (num * 100).toFixed(2) + '%'
    }
    return item
  }
  // 判断所给元素是否为数字类型
  var judgeNumber = function (item) {
    if (typeof (item) == "number") {
      return true
    }
    return false
  }
  module.exports = {
    change_unit: change_unit,
    change_per: change_per,
    judgeNumber: judgeNumber
  }
</wxs>
wxss页面:
.table_box {
  width: 100%;
  height: auto;
  box-sizing: border-box;
  position: relative;
  top: 10rpx;
  z-index: 999;
  border: 1px solid #E4E4E4;
}

.table_scroll {
  overflow: hidden;
  background: #FFF;
}

.table_header {
  top: 0;
  z-index: 999;
  display: grid;
  /* display: grid; 网格布局 */
  /* grid-auto-flow 属性控制自动放置的项目如何插入网格中 */
  /* column	通过填充每一列来放置项目 */
  grid-auto-flow: column;
  font-size: 26rpx;
  color: #333333;
  background: #F4F6FF;
}

.table_header_item {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  box-sizing: border-box;
  position: relative;
  z-index: 888;
  border: 1rpx solid #E4E4E4;
  border-left: 0;
  border-top: 0;
}

.table_header_item:nth-child(1) {
  position: sticky;
  left: 0;
  z-index: 999;
}

.table_content {
  background-color: #fff;
  /* 这是兼容 iPhone x */
  padding-bottom: 10rpx;
  margin-bottom: constant(safe-area-inset-bottom);
  margin-bottom: env(safe-area-inset-bottom);
}

.table_content_line {
  display: grid;
  grid-auto-flow: column;
  position: relative;
}

.table_content_line_item {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  box-sizing: border-box;
  background-color: #fff;
  border: 1rpx solid #E4E4E4;
  border-left: 0;
  border-top: 0;
  font-size: 26rpx;
}

.table_content_line_item:nth-child(1) {
  position: sticky;
  left: 0;
  /* top: 0; */
}

.alignment {
  display: flex;
  align-items: center;
  justify-content: center
}

/* 右箭头css样式 */
.arrow {
  width: 0;
  height: 0;
  border-left: 10rpx solid #ccc;
  /* 左侧透明 */
  border-top: 10rpx solid transparent;
  /* 顶部有颜色 */
  border-bottom: 10rpx solid transparent;
}
js页面:
// components/table/table.js
Component({

  /**
   * 组件的属性列表
   */
  properties: {
    totalHeight: {
      type: Number,
      value: 250
    },
    headList: Array,
    table: Array,
    table_sl: {
      type: String,
      value: 'fir'
    },
    line_sl: {
      type: String,
      value: 'sec'
    },
    lock_header: {
      type: Boolean,
      value: true
    },
    lock_first: {
      type: Boolean,
      value: true
    },
    table_width: {
      type: Array,
      value: []
    },
    header_height: {
      type: Number,
      value: 60
    },
    header_bg: {
      type: String,
      value: '#fff'
    },
    header_text_size: {
      type: Number,
      value: 32
    },
    header_bold: {
      type: Boolean,
      value: true
    },
    header_text_color: {
      type: String,
      value: '#111'
    },
    cell_height: {
      type: Number,
      value: 100
    },
    is_unit: {
      type: Array,
      value: []
    },
    is_per: {
      type: Array,
      value: []
    }
  },

  /**
   * 组件的初始数据
   */
  data: {
    header_style: ""
  },

  /**
   * 组件的方法列表
   */
  methods: {

    /**
     * 表头元素点击事件
     * @param {Obejct} e 所选表头数据
     */
    header_click(e) {
      // console.log(e)
      let data = e.currentTarget.dataset
      // 将所点击表头的索引和值传给父组件
      this.triggerEvent('head_click', {
        'index': data.index,
        'value': data.value
      })
    },

    /**
     * 行点击事件
     * @param {Object} e 每行数据
     */
    row_click(e) {
      // console.log(e)
      let data = e.currentTarget.dataset
      // 将所点击行的索引和值传给父组件
      this.triggerEvent('row_click', {
        'index': data.index,
        'value': data.value
      })
    },

    /**
     * 下拉触底事件函数
     * @param {Object} e scroll-view下拉触底变量
     */
    handleScrollToLower(e) {
      if (e.detail.direction == 'bottom') {
        console.log('下拉到底加载事件')
        // 将数据传给父组件
        this.triggerEvent('tableToLower', {
          'data': '111'
        })
      }
    },

    /**
     * 设置表头样式
     */
    process_header() {
      let [header_height, header_bg, header_text_size, header_text_color] = [this.properties.header_height, this.properties.header_bg, this.properties.header_text_size, this.properties.header_text_color]
      this.setData({
        header_style: "background:" + header_bg + ";font-size:" + header_text_size + "rpx;color:" + header_text_color + ";height:" + header_height + "rpx"
      })
    },
  },
  lifetimes: {
    ready() {
      // // 获取屏幕高度用来分配表格的显示
      // wx.getSystemInfo({
      //   success: (res) => {
      //     this.setData({
      //       totalHeight: res.windowHeight - 180
      //     })
      //   }
      // })
      this.process_header()
    }
  }
})

demo案例:

index.wxml中调用table组件
<view style="padding: 10rpx;">
  <table2 headList="{{headList}}" table="{{list}}" table_sl="{{table_sl}}" totalHeight="280" header_text_size="20" is_unit="{{is_unit}}" bind:head_click="head_click" bind:row_click="row_click"/>
</view>
index.js
// pages/test3/index.js
Page({
  /**
   * 页面的初始数据
   */
  data: {
    totalHeight: 0,
    /* 该headList、 list、is_unit、is_per是属于table_sl='fir'模式下的数据格式*/

    // headList:['股票名称', '时间日期', '姓名', '百分比', '星期', '结果'],
    // list: [
    //   [['123456', '测试数据'], '2022-04-01', '李四', '0.001', '星期二', -1000000000],
    //   [['123456', '测试数据'], '2022-04-01', '张三', '0.001', '星期二', 100000],
    //   [['123456', '测试数据'], '2022-04-01', '李四', '0.001', '星期二', 100],
    //   [['123456', '测试数据'], '2022-04-01', '张三', '0.001', '星期二', -100],
    //   [['123456', '测试数据'], '2022-04-01', '李四', '0.001', '星期二', 100],
    //   [['123456', '测试数据'], '2022-04-01', '张三', '0.001', '星期二', 100],
    //   [['123456', '测试数据'], '2022-04-01', '李四', '0.001', '星期二', 100],
    //   [['123456', '测试数据'], '2022-04-01', '张三', '0.001', '星期二', -100],
    //   [['123456', '测试数据'], '2022-04-01', '李四', '0.001', '星期二', 0],
    //   [['123456', '测试数据'], '2022-04-01', '张三', '0.001', '星期二', 100],
    //   [['123456', '测试数据'], '2022-04-01', '李四', '0.001', '星期二', 100],
    //   [['123456', '测试数据'], '2022-04-01', '张三', '0.001', '星期二', 100],
    //   [['123456', '测试数据'], '2022-04-01', '李四', '0.001', '星期二', -100],
    //   [['123456', '测试数据'], '2022-04-01', '张三', '0.001', '星期二', 100],
    //   [['123456', '测试数据'], '2022-04-01', '李四', '0.001', '星期二', 100],
    // ],
    // is_unit: ['', '', '', '', '', true],  // 如果只需要该后面的,前面的需要空值占位
    // is_per: ['','','',true],  // 如果只需要该后面的,前面的需要空值占位

    /* 该headList、 list、is_unit、is_per是属于table_sl='sec'模式下的数据格式*/
    table_sl: 'sec',
    headList: [
      '指标名称',
      ['123456', '测试数据'],
      ['234567', '测试数据'],
      ['345678', '测试数据'],
      ['456789', '测试数据'],
      ['567890', '测试数据']
    ],
    list: [
      ['时间日期', '2022-04-01', '2022-04-01', '2022-04-01', '2022-04-01', '2022-04-01'],
      ['姓名', '李四', '张三', '李四', '张三', '李四'],
      ['百分比', '0.001', '0.001', '0.001', '0.001', '0.001'],
      ['星期', '星期二', '星期二', '星期二', '星期二', '星期二'],
      ['结果', -1000000000, 100000, 100, -100, 100]
    ],
    is_unit: ['', '', '', '', true],
  },

  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    // wx.getSystemInfo({
    //   success:(res)=> {
    //     this.setData({
    //       // totalHeight: (res.windowHeight * 2) - 240
    //       totalHeight: res.windowHeight - 120
    //     })
    //   }
    // })
  },
  // 触底事件
  handleScrollToLower(e) {
    // if(e.detail.direction == 'bottom') {
    //   console.log('scroll-view触底事件在这里处理加载下一页数据')
    // }
    console.log(e)
  },


  /**
   * 接收表格表头点击事件
   * @param {Object} e 
   */
  head_click(e) {
    console.log(e)
  },

  /**
   * 表格的行点击事件
   * @param {Object} e 
   */
  row_click(e) {
    console.log(e)
  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {

  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})
index.json引入table组件
{"usingComponents": {"table": "../../components/table/table"}
}

最后感谢大佬@m0_61073617提供的思路

;