微信小程序?qū)崿F(xiàn)固定表頭、列表格組件
需求:
微信小程序?qū)崿F(xiàn)固定表頭固定列表格組件(移動(dòng)端做點(diǎn)小修改通用)
功能點(diǎn)
- 排序表格
- 表頭可固定
- 首列固定(可以優(yōu)化成可以配置指定列左側(cè)右側(cè)固定)
- 翻頁(上拉加載)監(jiān)聽
效果圖


實(shí)現(xiàn)思路
開始想用三個(gè)ScrollView去實(shí)現(xiàn)滾動(dòng)聯(lián)動(dòng),固定表頭、列的話,表格內(nèi)容滾動(dòng)表頭、列也應(yīng)該對(duì)應(yīng)滾動(dòng),寫了demo后發(fā)現(xiàn)監(jiān)聽一個(gè)ScrollView的位置信息去設(shè)置另外兩個(gè)ScrollView的位置真機(jī)會(huì)很卡,體驗(yàn)極差
使用position:sticky; 讓表頭相對(duì)表格頂部sticky,每行的第一個(gè)元素相對(duì)當(dāng)前行左側(cè)sticky。
遇到的問題:
- 表格左滑的時(shí)候,滑動(dòng)一個(gè)屏幕后固定列跟著滑出屏幕了。解決方法:動(dòng)態(tài)設(shè)置表格的寬度,原理:滑出去的原因是整行滑出屏幕了,而sticky是相對(duì)整行左側(cè)定位的。
- 表格高度設(shè)置為100%后useReachBottom上拉監(jiān)聽失效 將表格高度設(shè)高的話固定表頭就失效了。解決方法:表格用ScrollView套一層使用onScrollToLower監(jiān)聽加載
具體代碼(react\taro3.0)
index.tsx
/**
* 可滑動(dòng)、固定表頭、固定列表格組件
* @example <Table data={data} dataAttribute={dataAttribute} sortTypeChange={sortTypeChange} handleRow={toDetails}/>
*/
import React, { useState, useMemo, useEffect } from 'react'
import classNames from 'classnames'
// components
import { View, Text, ScrollView } from '@tarojs/components'
// utils
import { noop } from '@/utils/util'
// styles
import styles from './index.module.less'
interface DataAttributeItem {
title: string
key: string | number
sortKey?: string | number
}
interface Props {
data: Array<any>
dataAttribute: Array<DataAttributeItem>
sortTypeChange?: (sort_item_id: any, sort_desc: boolean) => void
handleRow?: (data: any) => void
handleScrollToLower?: (e: any) => void
}
export default function Table(props: Props) {
const { data, dataAttribute, sortTypeChange = noop, handleRow = noop, handleScrollToLower = noop } = props
const [isSortDesc, setIsSortDesc] = useState<boolean>(true)
const [sortIndex, setSortIndex] = useState<number>(1)
const tableWidth = useMemo(() => {
return `${(dataAttribute.length * 148 + 48)}rpx`
}, [dataAttribute])
const tableHeight = useMemo(() => {
return `${((data.length + 1) * 96)}rpx`
}, [data])
const handleSortItem = (attrItem, attrIndex) => {
if (attrIndex === 0) {
return
}
const beforeIndex = sortIndex
const sortKey = attrItem.sortKey
dataAttribute.map((item, index)=>{
if (item.sortKey === sortKey) {
if (beforeIndex === index) {
setIsSortDesc(!isSortDesc)
} else {
setSortIndex(index)
setIsSortDesc(true)
}
}
})
}
useEffect(()=>{
const sort_desc = isSortDesc
const sort_item_id = dataAttribute[sortIndex].sortKey
sortTypeChange(sort_item_id,sort_desc)
},[sortIndex, isSortDesc])
return (
<ScrollView className={styles['table']} scrollY scrollX onScrollToLower={handleScrollToLower}>
<View className={styles['sticky-box']} style={{height: tableHeight}}>
<View className={styles['grey-box']} style={{width: tableWidth, position: 'sticky'}}/>
<View className={styles['table__head']} style={{width: tableWidth, position: 'sticky'}}>
{dataAttribute.map((attrItem, attrIndex) => (
<View className={styles['table__head__td']} key={attrIndex} onClick={()=>handleSortItem(attrItem, attrIndex)}>
<Text
className={classNames({
[styles['table__head__td__text']]: true,
[styles['table__head__td__text-active']]: sortIndex === attrIndex,
})}
key={attrIndex}
>{attrItem.title}</Text>
{attrIndex !== 0 && <View
className={classNames({
[styles['table__head__td__sorter-indicate']]: true,
[styles['table__head__td__sorter-indicate--asc-active']]: sortIndex === attrIndex && !isSortDesc,
[styles['table__head__td__sorter-indicate--desc-active']]: sortIndex === attrIndex && isSortDesc
})}
/>}
</View>
))}
</View>
{data.map((dataItem, dataIndex) => (
<View className={styles['table__row']} key={dataIndex} style={{width: tableWidth}} onClick={() => handleRow(dataItem)}>
{dataAttribute.map((attrItem, attrIndex) => {
return (
<Text className={styles['table__row__td']} key={attrIndex}>{dataItem[attrItem.key] || '-'}</Text>
)
})}
</View>
))}
</View>
</ScrollView>
)
}
index.module.less
@import '~@/assets/style/mixins/ellipsis.less';
page{
font-size: 26rpx;
line-height: 60rpx;
color: #222;
height: 100%;
width: 100%;
}
.grey-box{
height: 10rpx;
top: 0;
background: #f8f8f8;
z-index: 100;
}
.table{
position: relative;
overflow: scroll;
width: 100%;
height: 100%;
overflow: scroll;
&__head{
position: relative;
height: 96rpx;
white-space: nowrap;
// position: sticky;
top: 10rpx;
z-index: 100;
height: 88rpx;
font-size: 24rpx;
line-height: 88rpx;
color: #aaabbd;
background-color: #f8f8f8;
border-bottom: 2rpx solid #ecf1f8;
background-color: #fff;
white-space: nowrap;
display: flex;
&__td{
.ellipsis();
width: 148rpx;
// padding-right: 40rpx;
display: flex;
justify-content: flex-start;
align-items: center;
background-color: #fff;
position: relative;
box-sizing: border-box;
&:nth-child(1) {
padding-left: 24rpx;
width: 154rpx;
margin-right: 40rpx;
position: sticky;
z-index: 10;
left: 0;
}
&__text{
display: inline;
&-active{
color: #6d70ff;
}
}
&__sorter-indicate{
width: 24rpx;
height: 24rpx;
display: inline-block;
background-repeat: no-repeat;
background-size: 100% 100%;
background-image: url('https://icon1.png');
&--asc-active {
background-image: url('https://icon2.png');
}
&--desc-active {
background-image: url('https://icon3.png');
}
}
}
}
&__row{
position: relative;
height: 96rpx;
white-space: nowrap;
display: flex;
justify-content: flex-start;
align-items: center;
border-bottom: 2rpx solid #ecf1f8;
&__td{
// .ellipsis();
overflow: scroll;
white-space: nowrap;
width: 148rpx;
// padding-right: 40rpx;
display: inline-block;
background-color: #fff;
position: relative;
box-sizing: border-box;
font-size: 26rpx;
line-height: 96rpx;
&:nth-child(1) {
margin-right: 40rpx;
padding-left: 24rpx;
width: 154rpx;
position: sticky;
z-index: 10;
left: 0;
}
}
}
}
具體代碼(小程序原生)
<ScrollView class="table" scroll-x scroll-y bindscrolltolower="handleScrollToLower">
<View class="sticky-box" style="height:{{tableHeight}}rpx;">
<View class="table__head" style="width:{{tableWidth}}rpx;">
<View class="table__head__td" wx:for="{{dataAttribute}}" wx:key="attrIndex" wx:for-index="attrIndex" wx:for-item="attrItem">
<Text
class="table__head__td__text"
>{{attrItem.title}}</Text>
</View>
</View>
<View class="table__row" wx:for="{{data}}" wx:key="dataIndex" wx:for-index="dataIndex" wx:for-item="dataItem" style="width:{{tableWidth}}rpx;">
<Text class="table__row__td" wx:for="{{dataAttribute}}" wx:key="dataIndex" wx:for-index="attrIndex" wx:for-item="attrItem">{{dataItem[attrItem.key] || '-'}}</Text>
</View>
</View>
</ScrollView>
const app = getApp()
Page({
data: {
data: [
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
{
a: 123,
b: 456,
c: 489,
d: 789,
e: 458,
f: 789
},
],
dataAttribute: [
{
title: '第一列',
key: 'a'
},
{
title: '第2列',
key: 'b'
},
{
title: '第3列',
key: 'c'
},
{
title: '第4列',
key: 'd'
},
{
title: '第5列',
key: 'e'
},
{
title: '第6列',
key: 'f'
}
],
tableHeight: (20 + 1) * 96,
tableWidth: 200 * 6 + 60
}
})
page{
font-size: 26rpx;
line-height: 60rpx;
color: #222;
height: 100%;
width: 100%;
}
.table{
display: block;
position: relative;
overflow: scroll;
width: 100%;
height: 100%;
}
.sticky-box{
}
.table__head{
height: 96rpx;
white-space: nowrap;
position: sticky;
top: 0rpx;
z-index: 100;
height: 88rpx;
font-size: 24rpx;
line-height: 88rpx;
color: #aaabbd;
background-color: #f8f8f8;
border-bottom: 2rpx solid #ecf1f8;
background-color: #fff;
white-space: nowrap;
display: flex;
}
.table__head__td{
width: 200rpx;
display: flex;
justify-content: flex-start;
align-items: center;
background-color: #fff;
box-sizing: border-box;
position: relative;
overflow: hidden;
white-space: nowrap;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
}
.table__head__td:nth-child(1) {
padding-left: 24rpx;
width: 260rpx;
margin-right: 40rpx;
position: sticky;
z-index: 101;
left: 0rpx;
}
.table__head__td__text{
display: inline;
}
.table__row{
position: relative;
height: 96rpx;
white-space: nowrap;
display: flex;
justify-content: flex-start;
align-items: center;
border-bottom: 2rpx solid #ecf1f8;
}
.table__row__td{
overflow: scroll;
white-space: nowrap;
width: 200rpx;
display: inline-block;
background-color: #fff;
box-sizing: border-box;
font-size: 26rpx;
line-height: 96rpx;
position: relative;
overflow: hidden;
white-space: nowrap;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
}
.table__row__td:nth-child(1) {
margin-right: 40rpx;
padding-left: 24rpx;
width: 260rpx;
position: sticky;
z-index: 10;
left: 0;
}
總結(jié)
到此這篇關(guān)于微信小程序?qū)崿F(xiàn)固定表頭、列表格組件的文章就介紹到這了,更多相關(guān)微信小程序固定表頭內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript的new date等日期函數(shù)在safari中遇到的坑
safari中對(duì)于JavaScript的new Date函數(shù)的支持有一個(gè)比較奇怪的問題,帶著這個(gè)奇怪的問題我們通過本文一起學(xué)習(xí)吧2016-10-10
JavaScript中實(shí)現(xiàn)異步編程模式的4種方法
這篇文章主要介紹了JavaScript中實(shí)現(xiàn)異步編程模式的4種方法,本文講解了回調(diào)函數(shù)、事件監(jiān)聽、發(fā)布/訂閱、Promises對(duì)象4種方法,需要的朋友可以參考下2014-09-09
基于HTML+JS實(shí)現(xiàn)網(wǎng)頁版蘋果計(jì)算器
這篇文章主要為大家詳細(xì)介紹了如何利用HTML+CSS+JS實(shí)現(xiàn)網(wǎng)頁版的蘋果計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
微信小程序?qū)崿F(xiàn)手勢(shì)滑動(dòng)卡片效果
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)手勢(shì)滑動(dòng)卡片效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
JS實(shí)現(xiàn)商城秒殺倒計(jì)時(shí)功能(動(dòng)態(tài)設(shè)置秒殺時(shí)間)
這篇文章主要介紹了JS實(shí)現(xiàn)商城秒殺倒計(jì)時(shí)功能(動(dòng)態(tài)設(shè)置秒殺時(shí)間),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12
JS函數(shù)節(jié)流和防抖之間的區(qū)分和實(shí)現(xiàn)詳解
本文主要介紹的是關(guān)于JS中比較常用的函數(shù):節(jié)流函數(shù)和防抖函數(shù),從概念、使用場(chǎng)景到代碼簡(jiǎn)單實(shí)現(xiàn)做了一個(gè)詳細(xì)的區(qū)分。感興趣的小伙伴們可以參考一下2019-01-01

