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

发布时间:2024年01月19日

介绍

适用于 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;
}
文章来源:https://blog.csdn.net/weixin_45485922/article/details/135697258
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。