Table自定义单元格渲染分享

前言

我们使用 table 的时候肯定遇到过单元格溢出隐藏只展示几行,鼠标hover显示全部等需求。本篇文章就是分享一下团队自定义单元格渲染的实现

TXListRow

TXListRow 用于多个字符串不想换行展示时使用,超过一行的长度后,会显示一个更多icon,hover时展示全部内容。

表现如下:

tsx 复制代码
// 用于多个字符串不想换行展示时
// 组件行为:超过一行后提供一个更多按钮,hover展示全部内容

import { cn } from "@/utils/tools";
import { ReactNode, useEffect, useRef, useState } from "react";
import { Content } from "./Content";
import { ShowMore } from "./ShowMore";

export interface ITXListRowProps {
  /** @param 类名 */
  className?: string;
  /** @param 字符串集合 */
  list?: ReactNode | ReactNode[];
  /** @param 分隔符默认为, */
  separator?: ReactNode;
}

export const TXListRow = function TXListRow_(props: ITXListRowProps) {
  const { className = "", list = [], separator = "," } = props;
  const boxRef = useRef<HTMLDivElement>(null);
  const contentRef = useRef<HTMLDivElement>(null);
  const [renderMore, setRenderMore] = useState(false);

  let haveValue = true;

  if (Array.isArray(list) && list.length === 0) {
    haveValue = false;
  }

  if (!Array.isArray(list) && [false, null, undefined].includes(list as null)) {
    haveValue = false;
  }

  if (typeof list === "string" && !list) {
    haveValue = false;
  }

  useEffect(() => {
    if (!boxRef.current || !contentRef.current || !haveValue) {
      return;
    }

    const checkOverflow = () => {
      if (boxRef.current && contentRef.current) {
        const boxWidth = boxRef.current.getBoundingClientRect().width;
        const contentWidth = contentRef.current.getBoundingClientRect().width;
        setRenderMore(contentWidth > boxWidth);
      }
    };

    checkOverflow();

    const resizeObserver = new ResizeObserver(checkOverflow);

    if (boxRef.current) {
      resizeObserver.observe(boxRef.current);
    }
    if (contentRef.current) {
      resizeObserver.observe(contentRef.current);
    }

    return () => {
      resizeObserver.disconnect();
    };
  }, [list, haveValue]);

  if (!haveValue) {
    return <div className={className}>-</div>;
  }

  let realList: ReactNode[] = [];

  if (Array.isArray(list)) {
    realList = list;
  } else {
    realList = [list];
  }

  return (
    <div className={cn("flex wes max-w-full", className)} ref={boxRef}>
      <div className="flex-1 wes">
        <div className="flex gap-1 w-max" ref={contentRef}>
          <Content list={realList} separator={separator} />
        </div>
      </div>
      {renderMore && <ShowMore list={realList} separator={separator} />}
    </div>
  );
};

其中有两点需要注意一下:

  • 检查 list 类型,并兼容成需要的类型
  • 通过 ResizeObserver 判断 Box 的长度 和 content 的长度是否有溢出,如果有溢出则显示更多icon,然后hover时显示全部

配合 table 使用时:

arduino 复制代码
 {
          title: "电话",
          dataIndex: "phoneNumber",
          key: "phoneNumber",
          dataType: "list",
          width: 100,
},

这里这个 dataType 是我们二次封装了一下 Table 的配置项,当 dataType 为 list 时,就会用 TXListRow 去渲染单元格

单独使用也可以

tsx 复制代码
<TXListRow className="w-[160px]" separator={false} list={list} />

TXTagStr

TXTagStr 用于切割字符串,并以 Tag 渲染

表现如下:

tsx 复制代码
// 用于展示标签内容联合字符串
// 组件行为:切割字符串,渲染为Tag

import { cn } from "@/utils/tools";
import { Tag, Tooltip } from "antd";

export interface ITXTagStrsProps {
  /** @param 类名 */
  className?: string;
  /** @param 联合字符串 */
  str?: string;
  /** @param 分隔符 默认为, */
  mark?: string;
}

export const TXTagStrs = function TXTagStrs_(props: ITXTagStrsProps) {
  const { className = "", str, mark = "," } = props;

  if (!str) {
    return <div className={className}>-</div>;
  }

  const list = str.split(mark);

  return (
    <div className={cn("flex flex-wrap gap-1 wes max-w-full", className)}>
      {list.map((t, index) => {
        return (
          <Tooltip title={t} key={index}>
            <Tag color="default" className="wes cursor-pointer">
              {t}
            </Tag>
          </Tooltip>
        );
      })}
    </div>
  );
};

配合 table 使用

tsx 复制代码
{
      title: "成交项目",
      dataIndex: "dataNames",
      key: "dataNames",
      width: 180,
      dataType: "tagStr",
},

直接使用

tsx 复制代码
<TXTagStrs str={text} />

TXTime

TXTime 用于格式化时间展示

表现如下:

tsx 复制代码
// 用于时间展示
// 组件行为安装format格式展示时间

import { cn } from "@/utils/tools";
import dayjs from "dayjs";

export interface ITXTimeProps {
  /** @param 类名 */
  className?: string;
  /** @param 时间 */
  time?: string | number;
  /** @param 分隔符 默认为, */
  format?: "YYYY-MM-DD" | "YYYY-MM-DD HH:mm:ss" | string;
}

export const TXTime = function TXTime_(props: ITXTimeProps) {
  const { className = "", time, format = "YYYY-MM-DD" } = props;

  if (!time) {
    return <div className={className}>-</div>;
  }

  return (
    <div className={cn("wes", className)}>{dayjs(time).format(format)}</div>
  );
};

思路就是使用 dayjs 配合 format 一下就行

配合 table 使用

tsx 复制代码
{
      title: "上次登录时间",
      dataIndex: "lastLoginTime",
      key: "lastLoginTime",
      width: 180,
      dataType: "datetime",
},

单独使用

tsx 复制代码
<TXTime time={text} format={"HH:mm:ss"} />

TXPercentCell

TXPercentCell 用于展示百分比

表现形式如下:

tsx 复制代码
// 用于展示百分比

import { BigNumber } from "@/utils/BigNumber";
import { cn } from "@/utils/tools";

export interface ITXPercentCellProps {
  /** @param 类名 */
  className?: string;
  /** @param 百分比 */
  percent?: string | number;
  /** @param 是否为小数 */
  isFloat?: boolean;
  showColor?: boolean;
  /** @param 空白占位符 */
  placeholder?: React.ReactNode;
}

export const TXPercentCell = function TXPercentCell_(
  props: ITXPercentCellProps
) {
  const {
    className = "",
    percent,
    isFloat = false,
    showColor = false,
    placeholder = "-",
  } = props;

  let value = parseFloat(percent as string);
  if (typeof value !== "number" || Number.isNaN(value)) {
    return <div className={cn("wes max-w-full", className)}>{placeholder}</div>;
  }
  if (isFloat) {
    value = new BigNumber(value).multipy(100).getNumber();
  }

  let colorClass = "";
  if (showColor) {
    colorClass = "text-[#EC8100]";
    if (value >= 90) {
      colorClass = "text-[#00A73F]";
    } else if (value >= 60) {
      colorClass = "text-[#0867E9]";
    }
  }

  return (
    <div className={cn("wes max-w-full", className, colorClass)}>{value}%</div>
  );
};

先通过 parseFloat 转成浮点数,或者直接通过 bigNumber 算一下百分比

配合 table 使用

tsx 复制代码
{
      title: "成交单量占比",
      width: 120,
      dataIndex: "rateOfRepeatedOrders",
      key: "rateOfRepeatedOrders",
      dataType: "percent",
      dataExtraProps: {
        percent: {
          isFloat: true,
        },
      },
},

结尾

还有一些其他的单元格自定义渲染组件,也比较简单。这里就不一一举例啦,把我封装的分享一下

相关推荐
子兮曰2 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰2 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万2 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝2 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋2 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁2 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95272 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大2 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师2 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学2 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端