适用于 react + ts 的 h5 移动端项目 table 组件
github 链接 :https://github.com/duKD/react-h5-table
有帮助的话 给个小星星
有两种表格组件
常规的:
支持 左侧固定 滑动 每行点击回调 支持 指定列排序 支持滚动加载更多
效果和之前写的vue3 版本类似
vue3 h5 表格
大数据量时 使用虚拟列表:
也支持 左侧固定 滑动 每行点击回调 支持 指定列排序 不支持滚动加载
npm i @lqcoder/react-h5-table
入口 引入table样式文件
import "@lqcoder/react-h5-table/scripts/style.css";
相关 props 配置 说明
export type tablePropsType<T = any> = {
rowKey?: string; //表格行 key 的取值字段 默认取id字段
minTableHeight?: number; //表格最小高度
showRowNum?: number; // 表格显示几行
headerHeight?: number; // 头部默认高度
rowHeight?: number; //每行数据的默认高度
column: Array<columnItemType<T>>;
tableData: Array<T>;
clickOptions?: clickOptions<T>; // 是否需要处理点击事件
disable?: boolean; // 是否启用下拉加载
pullDownProps?: pullDownPropsType;
changePullDownProps?: (args: pullDownPropsType) => void; // 修改加载状态
handleHeadSortClick?: (propsKey: string, type: sortStatusType) => void;
onload?: () => void; // 数据加载
rootValue?: number; //
};
export type columnItemType<T = any> = {
title: string; // 列名
dataIndex: string; // table data key 值
width: number; // 列 宽度
sortable?: boolean; //是否 支持排序
align?: "left" | "center" | "right"; // 布局
render?: (item: T, index?: number) => any; //自定义单元格显示的内容
};
// 下拉加载相关配置
export type pullDownPropsType = {
error?: boolean; // 数据加载失败
loading?: boolean; // 数据处于加载状态
finish?: boolean; // 数据 是否完全加载
loadingText?: string; // 加载文案
errorText?: string; // 失败文案
finishedText?: string; // 完成文案
offset?: number; //触发加载的底部距离
};
// 点击相关配置
export type clickOptions<T> = {
clickRender: (item: T, index: number) => React.JSX.Element; // 点击列触发渲染
clickHeight: number; // 显示栏的高度
};
代码示例:
// App.tsx 文件
import { useRef, useState } from "react";
import {
H5Table,
clickOptions,
columnItemType,
sortStatusType,
} from "@lqcoder/react-h5-table";
import Styles from "./App.module.scss";
function App() {
type dataType = {
id: number;
type?: number;
select: string;
position: string;
use: string;
markValue: string;
cur: string;
cost: string;
newPrice: number;
float: string;
profit: string;
count: string;
};
const column: Array<columnItemType<dataType>> = [
{
title: "班费/总值",
width: 250,
dataIndex: "rateAndSum",
render(item, _index) {
return (
<section className="nameAndMarkValue">
<div className="name">
{item.select}
<span className="type">{item.type === 1 ? "深" : "沪"}</span>
</div>
<div className="markValue">
{item.markValue}=={item.id}
</div>
</section>
);
},
align: "left",
},
{
title: "持仓/可用",
dataIndex: "positionAndUse",
sortable: true,
width: 200,
align: "right",
render(item, _index) {
return (
<section className="positionAndUse">
<div className="position">{item.position}</div>
<div className="use">{item.use}</div>
</section>
);
},
},
{
title: "现价/成本",
dataIndex: "curAndCost",
sortable: true,
width: 200,
align: "right",
render(item) {
return (
<section className="curAndCost">
<div className="cur">{item.cur}</div>
<div className="cost">{item.cost}</div>
</section>
);
},
},
{
title: "浮动/盈亏",
dataIndex: "float",
width: 200,
align: "right",
render(item) {
return (
<section className="floatAndProfit">
<div className="float">{item.float}</div>
<div className="profit">{item.profit}</div>
</section>
);
},
},
{
title: "账户资产",
dataIndex: "count",
width: 200,
},
];
const temp = Array.from({ length: 20 }).map((item, index) => {
return {
id: index,
select: "三年二班",
type: 1,
position: `${27000 + index * 10}`,
use: "5,000",
markValue: "500,033.341",
cur: "30.004",
cost: "32.453",
newPrice: 20,
float: "+18,879.09",
profit: "-5.45%",
count: "120,121",
};
});
const dataRef = useRef(temp);
const [data, setData] = useState(temp);
const [pullDownProps, setPullDownProps] = useState({
offset: 10,
error: false, // 数据加载失败
loading: false, // 数据处于加载状态
finish: false, // 数据 是否完全加载
loadingText: "加载中...", // 加载文案
errorText: "出错了", // 失败文案
finishedText: "到底了", // 完成文案
});
const onload = () => {
setTimeout(() => {
const len = data.length;
setData(
data.concat(
Array.from({ length: 10 }).map((item, index) => {
return {
id: len + index,
select: "三年二班",
type: 1,
position: "28000",
use: "5,000",
markValue: "500,033.341",
cur: "30.004",
cost: "32.453",
newPrice: 20,
float: "+18,879.09",
profit: "-5.45%",
count: "120,121",
};
})
)
);
dataRef.current = dataRef.current.concat(
Array.from({ length: 10 }).map((item, index) => {
return {
id: len + index,
select: "三年二班",
type: 1,
position: "28000",
use: "5,000",
markValue: "500,033.341",
cur: "30.004",
cost: "32.453",
newPrice: 20,
float: "+18,879.09",
profit: "-5.45%",
count: "120,121",
};
})
);
setPullDownProps({
...pullDownProps,
loading: false,
});
}, 1000);
};
const changePullDownProps = (args: any) => {
setPullDownProps(args);
};
/**
* 处理排序按钮回调 处理逻辑交给开发
* @param propsKey 点击的列名
* @param type 0 默认 1 升 2 降
* @returns
*/
const handleHeadSortClick = (propsKey: string, type: sortStatusType) => {
if (type === 0) {
setData(dataRef.current);
return;
}
if (propsKey === "positionAndUse") {
if (type === 1) {
const temp = [...dataRef.current].sort(
(a, b) => Number(b.position) - Number(a.position)
);
setData(temp);
} else {
const temp = [...dataRef.current].sort(
(a, b) => Number(a.position) - Number(b.position)
);
setData(temp);
}
}
if (propsKey === "curAndCost") {
if (type === 1) {
const temp = [...dataRef.current].sort(
(a, b) => Number(b.cur) - Number(a.cur)
);
setData(temp);
} else {
const temp = [...dataRef.current].sort(
(a, b) => Number(a.cur) - Number(b.cur)
);
setData(temp);
}
}
};
const handelSell = () => {
console.log("handelSell----");
};
const clickOptions: clickOptions<dataType> = {
clickRender(item, index) {
return (
<section className={Styles["rowDownMark"]}>
<div className={Styles["rowDownMark-item"]} onClick={handelSell}>
买入
</div>
<div className={Styles["rowDownMark-item"]}>卖出</div>
<div className={Styles["rowDownMark-item"]}>行情</div>
</section>
);
},
clickHeight: 60,
};
return (
<>
<H5Table<dataType>
disable
column={column}
tableData={data}
onload={onload}
pullDownProps={pullDownProps}
changePullDownProps={changePullDownProps}
handleHeadSortClick={handleHeadSortClick}
clickOptions={clickOptions}
></H5Table>
</>
);
}
export default App;
// App.module.scss
.app {
color: red;
font-size: 20px;
.container {
color: aqua;
}
}
.rowDownMark {
width: 100%;
display: flex;
height: 60px;
background-color: #fcfcfc;
align-items: center;
}
.rowDownMark-item {
flex-grow: 1;
color: #309fea;
text-align: center;
}
相关props 说明
export type virtualTablePropsType<T = any> = {
rowKey?: string; //表格行 key 的取值字段 默认取id字段
minTableHeight?: number; //表格最小高度
showRowNum?: number; // 表格显示几行
headerHeight?: number; // 头部默认高度
rowHeight?: number; //每行数据的默认高度
column: Array<columnItemType<T>>;
tableData: Array<T>;
clickOptions?: clickOptions<T>; // 是否需要处理点击事件
handleHeadSortClick?: (propsKey: string, type: sortStatusType) => void;
rootValue?: number; //
};
// 0 默认 1 升 2 降
export type sortStatusType = 0 | 1 | 2;
export interface virtualTableInstance {
scrollIntoView: (index: number) => void;
}
export type columnItemType<T = any> = {
title: string; // 列名
dataIndex: string; // table data key 值
width: number; // 列 宽度
sortable?: boolean; //是否 支持排序
align?: "left" | "center" | "right"; // 布局
render?: (item: T, index?: number) => any; //自定义单元格显示的内容
};
// 点击相关配置
export type clickOptions<T> = {
clickRender: (item: T, index: number) => React.JSX.Element; // 点击列触发渲染
clickHeight: number; // 显示栏的高度
};
代码示例:
// App.tsx
import { useRef, useState } from "react";
import {
H5VirtualTable,
clickOptions,
columnItemType,
sortStatusType,
virtualTableInstance,
} from "@lqcoder/react-h5-table";
import Styles from "./App.module.scss";
function App() {
type dataType = {
id: number;
type?: number;
select: string;
position: string;
use: string;
markValue: string;
cur: string;
cost: string;
newPrice: number;
float: string;
profit: string;
count: string;
};
const column: Array<columnItemType<dataType>> = [
{
title: "班费/总值",
width: 250,
dataIndex: "rateAndSum",
render(item, _index) {
return (
<section className="nameAndMarkValue">
<div className="name">
{item.select}
<span className="type">{item.type === 1 ? "深" : "沪"}</span>
</div>
<div className="markValue">
{item.markValue}=={item.id}
</div>
</section>
);
},
align: "left",
},
{
title: "持仓/可用",
dataIndex: "positionAndUse",
sortable: true,
width: 200,
align: "right",
render(item, _index) {
return (
<section className="positionAndUse">
<div className="position">{item.position}</div>
<div className="use">{item.use}</div>
</section>
);
},
},
{
title: "现价/成本",
dataIndex: "curAndCost",
sortable: true,
width: 200,
align: "right",
render(item) {
return (
<section className="curAndCost">
<div className="cur">{item.cur}</div>
<div className="cost">{item.cost}</div>
</section>
);
},
},
{
title: "浮动/盈亏",
dataIndex: "float",
width: 200,
align: "right",
render(item) {
return (
<section className="floatAndProfit">
<div className="float">{item.float}</div>
<div className="profit">{item.profit}</div>
</section>
);
},
},
{
title: "账户资产",
dataIndex: "count",
width: 200,
},
];
const temp = Array.from({ length: 20000 }).map((item, index) => {
return {
id: index,
select: "三年二班",
type: 1,
position: `${27000 + index * 10}`,
use: "5,000",
markValue: "500,033.341",
cur: "30.004",
cost: "32.453",
newPrice: 20,
float: "+18,879.09",
profit: "-5.45%",
count: "120,121",
};
});
const dataRef = useRef(temp);
const tableRef = useRef<virtualTableInstance>();
const [num, setNum] = useState(1);
const [data, setData] = useState(temp);
/**
* 处理排序按钮回调 处理逻辑交给开发
* @param propsKey 点击的列名
* @param type 0 默认 1 升 2 降
* @returns
*/
const handleHeadSortClick = (propsKey: string, type: sortStatusType) => {
if (type === 0) {
setData(dataRef.current);
return;
}
if (propsKey === "positionAndUse") {
if (type === 1) {
const temp = [...dataRef.current].sort(
(a, b) => Number(b.position) - Number(a.position)
);
setData(temp);
} else {
const temp = [...dataRef.current].sort(
(a, b) => Number(a.position) - Number(b.position)
);
setData(temp);
}
}
if (propsKey === "curAndCost") {
if (type === 1) {
const temp = [...dataRef.current].sort(
(a, b) => Number(b.cur) - Number(a.cur)
);
setData(temp);
} else {
const temp = [...dataRef.current].sort(
(a, b) => Number(a.cur) - Number(b.cur)
);
setData(temp);
}
}
};
const handelSell = () => {
console.log("handelSell----");
};
const clickOptions: clickOptions<dataType> = {
clickRender(item, index) {
return (
<section className={Styles["rowDownMark"]}>
<div className={Styles["rowDownMark-item"]} onClick={handelSell}>
买入
</div>
<div className={Styles["rowDownMark-item"]}>卖出</div>
<div className={Styles["rowDownMark-item"]}>行情</div>
</section>
);
},
clickHeight: 60,
};
const scrollTo = () => {
tableRef.current?.scrollIntoView(num);
};
const getValue = (val: any) => {
setNum(Number(val.target.value) || 0);
};
return (
<>
<input type="text" onChange={getValue} />
<button onClick={scrollTo}>跳到</button>
<H5VirtualTable<dataType>
disable
column={column}
tableData={data}
handleHeadSortClick={handleHeadSortClick}
clickOptions={clickOptions}
ref={tableRef}
></H5VirtualTable>
</>
);
}
export default App;
// App.module.scss
.app {
color: red;
font-size: 20px;
.container {
color: aqua;
}
}
.rowDownMark {
width: 100%;
display: flex;
height: 60px;
background-color: #fcfcfc;
align-items: center;
}
.rowDownMark-item {
flex-grow: 1;
color: #309fea;
text-align: center;
}