移动端 h5-table react版本支持虚拟列表

介绍

适用于 react + ts 的 h5 移动端项目 table 组件

github 链接 :https://github.com/duKD/react-h5-table

有帮助的话 给个小星星

有两种表格组件

常规的:

支持 左侧固定 滑动 每行点击回调 支持 指定列排序 支持滚动加载更多

效果和之前写的vue3 版本类似
vue3 h5 表格

大数据量时 使用虚拟列表:

也支持 左侧固定 滑动 每行点击回调 支持 指定列排序 不支持滚动加载

开始

javascript 复制代码
npm i @lqcoder/react-h5-table

入口 引入table样式文件

javascript 复制代码
import "@lqcoder/react-h5-table/scripts/style.css";

常规版使用

相关 props 配置 说明

javascript 复制代码
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; // 显示栏的高度 
};

代码示例:

javascript 复制代码
// 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;
javascript 复制代码
// 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 说明

javascript 复制代码
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; // 显示栏的高度
};

代码示例:

javascript 复制代码
// 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;
javascript 复制代码
// 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;
}
相关推荐
EatFan8 小时前
前后端分离项目中 Token 到底应该怎么设计?Access Token + Refresh Token 实战
java·前端·后端
dsyyyyy11018 小时前
Typescript装饰器
前端·javascript·typescript
一拳不是超人9 小时前
一个没测暗色模式的 Bug,吃掉了我一半 谷歌扩展用户
前端·javascript·程序员
liangshanbo121510 小时前
React useState 函数式更新面试题整理
前端·react.js·前端框架
涛涛ing10 小时前
为什么你的页面在 Safari 上总出问题?Interop 2027 正在解决这个 20 年老毛病
前端
不一样的少年_10 小时前
WebP 压缩到底在干嘛?小白也能看懂的原理拆解
前端·后端·图片资源
问心无愧051310 小时前
ctf show web 177
前端·笔记
不一样的少年_10 小时前
PNG/JPG 如何变成 WebP?真相不是改后缀!
前端·后端·图片资源
杉氧10 小时前
RN 性能调优指南:重渲染(Re-renders)控制与长列表(FlatList)优化
android·前端·react native
一_个前端10 小时前
[JS] 一站式搞定 PDF、图片、Dom弹窗、表格的浏览器打印功能
前端