tdesign-react icon indicating copy to clipboard operation
tdesign-react copied to clipboard

[Table] 虚拟滚动问题:fix右列样式没对齐问题 + 滚到下面行的线消失了 + 合并单元格有问题

Open tingtingcheng6 opened this issue 2 months ago • 3 comments

tdesign-react 版本

1.15.1

重现链接

No response

重现步骤

    <Table
      rowKey='id'
      data={list}
      columns={getTableColumns({
        abnormalType,
        days,
        canEdit,
        onRemarkEditedCell,
      })}
      rowspanAndColspan={rowspanAndColspan}
      className={styles.commonFullTable}
      loading={loading}
      empty={<Empty />}
      scroll={{ type: 'virtual' }}
      lazyLoad
    />

1、右侧fixed的问题 { title: '备注', colKey: 'mark', fixed: 'right', width: 200, } 上面: Image 滚到下面: Image 2、滚到下面行的线消失了 Image 3、合并列失效

Image

期望结果

三个问题正常展示

Image

实际结果

No response

框架版本

No response

浏览器版本

No response

系统版本

No response

Node版本

No response

补充说明

No response

tingtingcheng6 avatar Sep 26 '25 08:09 tingtingcheng6

👋 @tingtingcheng6,感谢给 TDesign 提出了 issue。 请根据 issue 模版确保背景信息的完善,我们将调查并尽快回复你。

github-actions[bot] avatar Sep 26 '25 08:09 github-actions[bot]

问题 1 已修复,预计下个版本发布 https://github.com/Tencent/tdesign-react/pull/3792 问题 2 和 3 无法复现 请提供可复现的在线代码 DEMO

Image

RylanBot avatar Sep 26 '25 16:09 RylanBot

全部 Bug 场景均已可复现...接下来会进行修复并在某个版本发布

完整 DEMO 存档
import React, { useMemo } from 'react';
import { CheckCircleFilledIcon, CloseCircleFilledIcon, ErrorCircleFilledIcon } from 'tdesign-icons-react';
import { Link, Space, Table, Tag } from 'tdesign-react';

import type { TableProps } from 'tdesign-react';

const statusNameListMap = {
  0: { label: '正常', theme: 'success', icon: <CheckCircleFilledIcon /> },
  1: { label: '异常', theme: 'danger', icon: <CloseCircleFilledIcon /> },
  2: { label: '警告', theme: 'warning', icon: <ErrorCircleFilledIcon /> },
};

// 生成大量数据
const generateData = (count: number) => {
  const data: any[] = [];
  const departments = ['技术部', '产品部', '运营部', '市场部'];

  // 计算每个部门的人数
  const peoplePerDept = Math.ceil(count / departments.length);

  let currentId = 1;

  departments.forEach((dept, deptIndex) => {
    // 为每个部门生成指定数量的人员
    const deptSize =
      deptIndex === departments.length - 1
        ? count - peoplePerDept * (departments.length - 1) // 最后一个部门处理余数
        : peoplePerDept;

    for (let i = 0; i < deptSize; i++) {
      const globalIndex = currentId - 1;
      data.push({
        id: currentId,
        name: `${dept.replace('部', '')}员工${i + 1}`,
        department: dept,
        email: `user${currentId}@${dept.toLowerCase().replace('部', '')}.com`,
        phone: `138${String(globalIndex).padStart(8, '0')}`,
        status: globalIndex % 3,
        score: Math.floor(Math.random() * 100),
        joinDate: `2023-${String((globalIndex % 12) + 1).padStart(2, '0')}-${String((globalIndex % 28) + 1).padStart(
          2,
          '0',
        )}`,
        address: `北京市朝阳区${dept}办公楼${i + 1}号`,
        project: ['项目A', '项目B', '项目C', '项目D'][deptIndex % 4],
        workYears: Math.floor(i / 3) + 1,
        salary: (deptIndex + 1) * 8000 + i * 1000 + Math.floor(Math.random() * 3000),
        mark: i % 3 === 0 ? `${dept}重要成员${i + 1}` : `${dept}普通成员${i + 1}`,
      });
      currentId++;
    }
  });

  return data;
};

export default function VirtualScrollIssuesDemo() {
  const data = useMemo(() => generateData(500), []);

  // 合并单元格逻辑 - 按部门合并
  const rowspanAndColspan = useMemo(() => {
    const spanMap = new Map();

    // 计算每个部门的行数
    const departmentCounts = new Map();
    data.forEach((item, index) => {
      const dept = item.department;
      if (!departmentCounts.has(dept)) {
        departmentCounts.set(dept, []);
      }
      departmentCounts.get(dept).push(index);
    });

    // 设置合并单元格
    departmentCounts.forEach((indexes) => {
      indexes.forEach((rowIndex, i) => {
        if (i === 0) {
          // 第一行显示合并的单元格
          spanMap.set(`${rowIndex}_department`, {
            rowspan: indexes.length,
            colspan: 1,
          });
        } else {
          // 其他行隐藏
          spanMap.set(`${rowIndex}_department`, {
            rowspan: 0,
            colspan: 0,
          });
        }
      });
    });

    return ({ rowIndex, col }) => {
      const key = `${rowIndex}_${col.colKey}`;
      return spanMap.get(key);
    };
  }, [data]);

  const columns: TableProps['columns'] = [
    {
      title: 'ID',
      colKey: 'id',
      width: 80,
      fixed: 'left',
    },
    {
      title: '部门',
      colKey: 'department',
      width: 120,
      // 这一列会被合并
    },
    {
      title: '姓名',
      colKey: 'name',
      width: 120,
      fixed: 'left',
    },
    {
      title: '邮箱',
      colKey: 'email',
      width: 200,
      ellipsis: true,
    },
    {
      title: '电话',
      colKey: 'phone',
      width: 140,
    },
    {
      title: '状态',
      colKey: 'status',
      width: 120,
      cell: ({ row }) => {
        const { status } = row;
        return (
          <Tag
            shape="round"
            theme={statusNameListMap[status].theme}
            variant="light-outline"
            icon={statusNameListMap[status].icon}
          >
            {statusNameListMap[status].label}
          </Tag>
        );
      },
    },
    {
      title: '评分',
      colKey: 'score',
      width: 100,
    },
    {
      title: '入职日期',
      colKey: 'joinDate',
      width: 120,
    },
    {
      title: '地址',
      colKey: 'address',
      width: 250,
      ellipsis: true,
    },
    {
      title: '项目',
      colKey: 'project',
      width: 120,
    },
    {
      title: '工作年限',
      colKey: 'workYears',
      width: 100,
    },
    {
      title: '薪资',
      colKey: 'salary',
      width: 120,
    },
    {
      title: '操作',
      colKey: 'operation',
      width: 150,
      fixed: 'right',
      cell: () => (
        <Space size="small">
          <Link theme="primary" size="small">
            编辑
          </Link>
          <Link theme="danger" size="small">
            删除
          </Link>
        </Space>
      ),
    },
    {
      title: '备注',
      colKey: 'mark',
      fixed: 'right',
      width: 200,
      ellipsis: true,
    },
  ];

  return (
    <div style={{ width: '1000px' }}>
      <Table
        maxHeight={600}
        rowKey="id"
        data={data}
        columns={columns}
        rowspanAndColspan={rowspanAndColspan}
        scroll={{ type: 'virtual' }}
        bordered
      />
    </div>
  );
}

RylanBot avatar Oct 17 '25 14:10 RylanBot