[微信小程序] 表格开发

199 阅读4分钟

1.1 前言

在查看报表时,通常都会使用表格,因为表格查看和对比数据是最直接方便的。

1.2 实现效果

image.png

1.3 关键点

下拉加载:scroll-view + bindscrolltolower

表头固定:position: sticky + top: 0

列固定:position: sticky + left: 0(第一列是left: 0,第二列是left: 第一列宽度)

动态计算列宽:遍历单元格获取最大宽度,然后设置给列,除非超过了列设定的最大值,那么会出现省略号,点击则显示完整数据。

  • 好处是:如果列的内容都很短,那列也会很短,不占额外空间
  • 坏处是:每次下拉加载动态计算时,表格会闪烁一下

列宽可拖拽:在列头右边增加一个拖拽块,再配合bindtouchstart、bindtouchmove、bindtouchend事件

1.4 完整代码

关键部分已经打上注释,便于理解:

<view class="container">
  <view class="loading" wx:if="{{loading}}">
    <t-loading theme="circular" size="40rpx" class="wrapper" />
  </view>
  <scroll-view wx:if="{{reportData.length > 0 && !loading}}" class="table-body" scroll-y scroll-x bindscrolltolower="getReportDataNextPage">
    <view class="table-wrapper">
      <view class="table-header" style="width: calc({{c1Width}} + {{c2Width}} + {{c3Width}} + {{c4Width}});">
        <view class="th c1 th1" style="width: {{c1Width}};" bindtouchstart="onColumnResizeStart" bindtouchmove="onColumnResizeMove" bindtouchend="onColumnResizeEnd">
          <view>医院</view>
          <view class="resize-handle"></view>
        </view>
        <view class="th c2" style="width: {{c2Width}};text-align: right;">列2</view>
        <view class="th c3" style="width: {{c3Width}};text-align: right;">列3</view>
        <view class="th c4" style="width: {{c4Width}};text-align: right;">列4</view>
      </view>
      <view class="row" wx:for="{{reportData}}" wx:key="id" style="width: calc({{c1Width}} + {{c2Width}} + {{c3Width}} + {{c4Width}});">
        <view class="td c1" style="width: {{c1Width}};" bind:tap="showComplete" data-message="医院:{{item.name}}">{{item.name || ''}}</view>
        <view class="td c2" style="width: {{c2Width}};text-align: right;" bind:tap="showComplete" data-message="列2:{{item.orderTotal}}">{{item.orderTotal}}</view>
        <view class="td c3" style="width: {{c3Width}};text-align: right;" bind:tap="showComplete" data-message="列3:{{ format.sep3plus(item.amountTotal) }}">{{ format.sep3plus(item.amountTotal) }}</view>
        <view class="td c4" style="width: {{c4Width}};text-align: right;" bind:tap="showComplete" data-message="列4:{{ format.sep3plus(item.unitPrice) }}">{{ format.sep3plus(item.unitPrice) }}</view>
      </view>
    </view>
  </scroll-view>
  <view wx:if="{{reportData.length == 0 && !loading}}" class="no-data">
    <image src="../../images/nodata.png" style="width: 400.00rpx;height: 400.00rpx;margin-left: 4rpx;" />
    <text class="no-data-text">暂无数据</text>
  </view>
</view>

<!-- ======================================================================= -->

.table-wrapper {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.table-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  color: white;
  position: sticky; // 表头冻结
  top: 0;
  z-index: 1; // 提高一层
  min-width: 100%; // 当动态计算的所有列宽加起来没到一屏宽时,保证表头显示一屏
  background: #8B9AAD;
}

.table-header .th {
  text-align: center;
  padding: 8px;
  font-size: 26.00rpx;
  background: #8B9AAD;
}

.table-header .th:nth-child(1) {
  position: sticky; // 首列冻结
  left: 0;
}

.table-header .th1 {
  display: flex;
  align-items: center;
  justify-content: center;
}

.table-header .resize-handle {
  position: absolute;
  right: 0;
  width: 1px;
  height: calc(100% - 15px);
  background-color: #ddd;
  cursor: col-resize;

}

// 扩大点击热区
.table-header .resize-handle::before {
  content: "";
  position: absolute;
  top: -20px;
  right: -20px;
  bottom: -20px;
  left: -20px;
}

.table-body {
  flex: 1;
  min-height: 0;
  overflow: auto;
}

.table-body .row {
  display: flex;
  align-items: center;
  border-bottom: 0.5px solid rgba(9, 30, 66, 0.06);
}

.table-body .td {
  color: #091E42;
  padding: 8px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  word-break: break-all;
  font-size: 24.00rpx;
  background-color: white;
}

.table-body .td:nth-child(1) {
  position: sticky; // 首列冻结
  left: 0;
  border-right: 1px solid rgba(9, 30, 66, 0.06);
}

<!-- ======================================================================= -->

data: {
  currentPage: 1,
  pageSize: 50,
  total: 0,
  reportData: [],
  loading: true,
  stopGet: false,
  c1Width: 'auto',
  c2Width: 'auto',
  c3Width: 'auto',
  c4Width: 'auto',
  resizeData: {
    isResizing: false,
    startX: 0,
    startWidth: 0
  },
}

/**
* 获取单元格最大宽度
*/
getCellMaxWidth() {
 console.log('getCellMaxWidth');
 
 if (this.data.c1Width !== 'auto') return // 手动调整后不再自动计算,因为第一列可以按表头拖拽

 // 先重置成默认宽度,触发一次渲染
 this.setData({
   c1Width: 'auto',
   c2Width: 'auto',
   c3Width: 'auto',
   c4Width: 'auto',
   c5Width: 'auto',
 }, () => { // 在重置完成后执行查询
   const query = wx.createSelectorQuery().in(this);
   const columns = [
     { className: 'c1', minWidth: 180, adjust: 0 },
     { className: 'c2', minWidth: 100, adjust: 0 },
     { className: 'c3', minWidth: 150, adjust: 20 },
     { className: 'c4', minWidth: 150, adjust: 20 },
     { className: 'c5', minWidth: 150, adjust: 10 },
   ];

   // 添加所有列的选择器查询
   columns.forEach(col => {
     query.selectAll(`.${col.className}`).boundingClientRect();
   });

   query.exec(res => {
     const newData = {};
     columns.forEach((col, index) => {
       const rects = res[index];
       if (!rects || rects.length === 0) return;

       // 计算最大宽度
       const maxContentWidth = Math.max(...rects.map(rect => rect.width));
       const adjustedWidth = maxContentWidth + col.adjust;
       const finalWidth = Math.min(col.minWidth, adjustedWidth);

       // 获取当前宽度(需处理'auto'或未定义的情况)
       const currentWidth = parseInt(this.data[`${col.className}Width`]) || 0;
       if (finalWidth !== currentWidth) {
         newData[`${col.className}Width`] = `${finalWidth}px`;
       }
     });

     // 仅当有变化时更新
     if (Object.keys(newData).length > 0) {
       this.setData(newData);
     }
   });
 });
},

/** 获取报表数据 */
getReportData() {
 return new Promise((resolve) => {
   const {
     currentPage,
     pageSize,
     stopGet,
   } = this.data

   if (stopGet) {
     wx.showToast({
       icon: "none",
       title: '没有更多数据了',
     });
     return
   }

   if (!this.data.reportData?.length) { // 防止每次下拉加载都重新加载表格
     this.setData({ loading: true })
   }

   const params = {
     current: currentPage,
     size: pageSize,
   }

   console.log('params: ', params);

   http.postData('statistics/pageOrderGroup', params, (res) => {
     const data = res.returnValue || {};
     if (currentPage <= data.pages) {
       const newData = data.records.map((item) => ({
         ...item,
         amountTotal: item.amountTotal.toFixed(2),
       }))
       this.setData({ reportData: this.data.reportData.concat(newData), total: data.total })
       this.setData({ loading: false })
       this.getCellMaxWidth()
     } else {
       wx.showToast({
         icon: "none",
         title: '没有更多数据了',
       });
       this.setData({ loading: false, stopGet: true })
     }
   })
 })
},

/**
* 获取下一页报表数据
*/
getReportDataNextPage(e) {
 const directionBottom = e.detail.direction == 'bottom'
 if (directionBottom) { // 只在垂直滚动时获取下一页数据
   this.setData({
     currentPage: this.data.currentPage + 1
   })
   this.getReportData()
 }
},

/**
* 从第一页重新请求报表数据
*/
getReportDataNew() {
 this.setData({
   currentPage: 1,
   reportData: [],
   stopGet: false,
 })
 this.getReportData()
},

/**
* 显示完整数据
*/
showComplete(e) {
 const message = e.currentTarget.dataset.message
 wx.showToast({
   icon: "none",
   title: message,
   duration: 5000,
 });
},

onColumnResizeStart(e) {
  const touch = e.touches[0]
  this.setData({
    resizeData: {
      isResizing: true,
      startX: touch.clientX,
      startWidth: parseInt(this.data.c1Width) || 120 // 获取当前列宽
    }
  })
},

onColumnResizeMove(e) {
  if (!this.data.resizeData.isResizing) return
  const touch = e.touches[0]
  const deltaX = touch.clientX - this.data.resizeData.startX
  const newWidth = Math.max(110, Math.min(260, this.data.resizeData.startWidth + deltaX)) // 限制最小110px,最大260px
  this.setData({
    c1Width: `${newWidth}px`
  })
},

onColumnResizeEnd() {
  this.setData({
    resizeData: {
      isResizing: false,
      startX: 0,
      startWidth: 0
    }
  })
},

1.5 最后

如果帮到你了可以点个赞,有更好的思路想法欢迎分享。

2025/02/17:发布文章
2025/02/18:优化getCellMaxWidth方法实现
2025/02/27: 增加列宽可拖拽的实现方法