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,
        },
      },
},

结尾

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

相关推荐
加载中3616 小时前
pnpm时代包版本不一致问题还是否存在
前端·面试·npm
老马啊老马6 小时前
30 分钟搞定!Docker+Jenkins+Nginx + 腾讯云实现前端 CI/CD
前端
VillenK6 小时前
用插件的方式注入Vue组件
前端·vue.js
掘金安东尼6 小时前
Node.js 如何在 2025 年挤压 I/O 性能
前端·javascript·github
跟橙姐学代码6 小时前
Python异常处理:告别程序崩溃,让代码更优雅!
前端·python·ipython
niuhuahua6 小时前
大屏拖拽功能,配合ai组件使用,配合各个组件都可使用
前端
得物技术7 小时前
前端日志回捞系统的性能优化实践|得物技术
前端·javascript·性能优化
陶甜也7 小时前
threeJS 实现开花的效果
前端·vue·blender·threejs
用户7678797737327 小时前
后端转全栈之Next.js 路由系统App Router
前端·next.js