1.1 前言
在查看报表时,通常都会使用表格,因为表格查看和对比数据是最直接方便的。
1.2 实现效果
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: 增加列宽可拖拽的实现方法