Skip to content

Conversation

@RylanBot
Copy link
Collaborator

@RylanBot RylanBot commented Dec 24, 2025

🤔 这个 PR 的性质是?

  • 日常 bug 修复
  • 新特性提交
  • 文档改进
  • 演示代码改进
  • 组件样式/交互改进
  • CI/CD 改进
  • 重构
  • 代码风格优化
  • 测试用例
  • 分支合并
  • 其他

🔗 相关 Issue

💡 需求背景和解决方案

测试代码存档
import React from 'react';
import type { TableProps } from 'tdesign-react';
import { DialogPlugin, Link, Table } from 'tdesign-react';
import { v4 as uuidv4 } from 'uuid';

interface ITableMaxHeightParams {
  topPosStart?: number;
  reservationHeight?: number;
  heightLowerLimit?: number;
  heightUpperLimit?: number;
  viewHeight?: number;
}

const getTableMaxHeight = (params: ITableMaxHeightParams = {}): number => {
  const {
    topPosStart = 0,
    reservationHeight = 0,
    heightLowerLimit = 500,
    heightUpperLimit = 1200,
    viewHeight = window.innerHeight,
  } = params;

  let tableHeight = viewHeight - topPosStart - reservationHeight;
  if (tableHeight < heightLowerLimit) {
    tableHeight = heightLowerLimit;
  }
  if (tableHeight > heightUpperLimit) {
    tableHeight = heightUpperLimit;
  }

  return tableHeight;
};

// 工具函数:随机加权选择
function randomWeightedChoice(items: string[], weights: number[]) {
  if (items.length !== weights.length) {
    throw new Error('Items and weights must have the same length.');
  }

  const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
  const random = Math.random() * totalWeight;

  let weightSum = 0;
  for (let i = 0; i < items.length; i++) {
    weightSum += weights[i];
    if (random <= weightSum) {
      return items[i];
    }
  }

  return items[items.length - 1];
}

// 生成随机数据
const getRandomData = (rowCount = 500) => {
  const upperLimit = 1000;
  const result: TableProps['data'] = new Array(rowCount).fill(null).map((_, i) => {
    const type_name = randomWeightedChoice(['电脑', '平板', '手机'], [0.75, 0.2, 0.05]);
    let typeLabel = '';
    if (type_name === '电脑') typeLabel = 'Computer';
    else if (type_name === '平板') typeLabel = 'Tablet';
    else if (type_name === '手机') typeLabel = 'Phone';

    return {
      id: uuidv4(),
      model_name: `Model_Name_${typeLabel}_${i.toString().padStart(4, '0')}`,
      type_name,
      quantity_1: Math.floor(Math.random() * upperLimit),
      quantity_2: Math.floor(Math.random() * upperLimit),
      quantity_3: Math.floor(Math.random() * upperLimit),
      quantity_4: Math.floor(Math.random() * upperLimit),
      quantity_5: Math.floor(Math.random() * upperLimit),
      quantity_6: Math.floor(Math.random() * upperLimit),
    };
  });

  return result;
};

// 数字格式化函数
function numeral(value: number | string | null | undefined) {
  const num = Number(value) || 0;

  return {
    format(pattern: string): string {
      const hasComma = pattern.includes(',');
      const decimalMatch = pattern.match(/\.(0+)/);
      const decimals = decimalMatch ? decimalMatch[1].length : 0;

      return num.toLocaleString('en-US', {
        useGrouping: hasComma,
        minimumFractionDigits: decimals,
        maximumFractionDigits: decimals,
      });
    },
  };
}

// 表格组件接口
interface TableDemoProps {
  tableMaxHeight: number;
  enableVirtualScroll?: boolean;
  onClickCallback?: () => void;
}

// 表格组件
const TableDemo: React.FC<TableDemoProps> = ({ tableMaxHeight, enableVirtualScroll = false, onClickCallback }) => {
  const ranDatas = React.useMemo(() => getRandomData(), []);

  const onClickDrillDown = (row: any) => {
    const { props } = row.row_shared;
    props.onClickCallback?.();
  };

  const columns: TableProps['columns'] = [
    { colKey: 'serial-number', title: '序号', width: 60, align: 'center' },
    {
      colKey: 'model_name',
      title: '名称',
      width: 160,
      align: 'left',
      sortType: 'asc',
      sorter: (a: any, b: any) => a.model_name.localeCompare(b.model_name),
      foot: (col: any) => (
        <div style={{ textAlign: 'left' }}>
          <b style={{ fontWeight: 'bold' }}>{`全部(${col.row.total_rows})`}</b>
        </div>
      ),
    },
    {
      colKey: 'type_name',
      title: '类型',
      width: 100,
      align: 'center',
      sortType: 'asc',
      sorter: (a: any, b: any) => a.type_name.localeCompare(b.type_name),
      filter: {
        type: 'multiple',
        resetValue: [],
        list: [
          { label: '全部', checkAll: true },
          { label: '电脑', value: '电脑' },
          { label: '平板', value: '平板' },
          { label: '手机', value: '手机' },
        ],
      },
    },
    {
      colKey: 'quantity_1',
      title: 'AAA数量1',
      width: 100,
      align: 'right',
      cell: ({ row }: any) => (
        <Link theme="primary" onClick={() => onClickDrillDown(row)}>
          {numeral(row.quantity_1).format('0,0')}
        </Link>
      ),
      sortType: 'desc',
      sorter: (a: any, b: any) => a.quantity_1 - b.quantity_1,
      foot: (col: any) => (
        <div style={{ textAlign: 'right' }}>
          <b style={{ fontWeight: 'bold' }}>
            <Link theme="primary" onClick={() => onClickDrillDown(col.row)}>
              {numeral(col.row.quantity_1).format('0,0')}
            </Link>
          </b>
        </div>
      ),
    },
    {
      colKey: 'quantity_2',
      title: 'AAA数量2',
      width: 100,
      align: 'right',
      cell: ({ row }: any) => numeral(row.quantity_2).format('0,0'),
      sortType: 'desc',
      sorter: (a: any, b: any) => a.quantity_2 - b.quantity_2,
      foot: (col: any) => (
        <div style={{ textAlign: 'right' }}>
          <b style={{ fontWeight: 'bold' }}>{numeral(col.row.quantity_2).format('0,0')}</b>
        </div>
      ),
    },
    {
      colKey: 'quantity_3',
      title: 'AAA数量3',
      width: 100,
      align: 'right',
      cell: ({ row }: any) => numeral(row.quantity_3).format('0,0'),
      sortType: 'desc',
      sorter: (a: any, b: any) => a.quantity_3 - b.quantity_3,
      foot: (col: any) => (
        <div style={{ textAlign: 'right' }}>
          <b style={{ fontWeight: 'bold' }}>{numeral(col.row.quantity_3).format('0,0')}</b>
        </div>
      ),
    },
    {
      colKey: 'quantity_4',
      title: '表格标题-数量4',
      width: 100,
      align: 'right',
      cell: ({ row }: any) => numeral(row.quantity_4).format('0,0'),
      sortType: 'desc',
      sorter: (a: any, b: any) => a.quantity_4 - b.quantity_4,
      foot: (col: any) => (
        <div style={{ textAlign: 'right' }}>
          <b style={{ fontWeight: 'bold' }}>{numeral(col.row.quantity_4).format('0,0')}</b>
        </div>
      ),
    },
    {
      colKey: 'quantity_5',
      title: 'AAA数量5',
      width: 100,
      align: 'right',
      cell: ({ row }: any) => numeral(row.quantity_5).format('0,0'),
      sortType: 'desc',
      sorter: (a: any, b: any) => a.quantity_5 - b.quantity_5,
      foot: (col: any) => (
        <div style={{ textAlign: 'right' }}>
          <b style={{ fontWeight: 'bold' }}>{numeral(col.row.quantity_5).format('0,0')}</b>
        </div>
      ),
    },
    {
      colKey: 'quantity_6',
      title: 'AA数量6',
      width: 100,
      align: 'right',
      cell: ({ row }: any) => numeral(row.quantity_6).format('0,0'),
      sortType: 'desc',
      sorter: (a: any, b: any) => a.quantity_6 - b.quantity_6,
      foot: (col: any) => (
        <div style={{ textAlign: 'right' }}>
          <b style={{ fontWeight: 'bold' }}>{numeral(col.row.quantity_6).format('0,0')}</b>
        </div>
      ),
    },
  ];

  // 计算汇总数据
  const getFootCumulativeData = (rowDatas: any[]) => {
    const result: { [key: string]: any } = {};
    const cumProps = ['quantity_1', 'quantity_2', 'quantity_3', 'quantity_4', 'quantity_5', 'quantity_6'];

    cumProps.forEach((prop) => {
      result[prop] = rowDatas.reduce((acc, row) => acc + row[prop], 0);
    });

    return result;
  };

  // 计算表格数据
  const computedProps = React.useMemo(() => {
    const tableRows = ranDatas.map((row: any) => ({ ...row, row_shared: { props: { onClickCallback } } }));
    const cumRow = getFootCumulativeData(tableRows);
    const footRows = [
      {
        ...cumRow,
        total_rows: tableRows.length,
        row_shared: { props: { onClickCallback } },
      },
    ];
    return { tableRows, footRows };
  }, [ranDatas, onClickCallback]);

  return (
    <Table
      rowKey="id"
      data={computedProps.tableRows}
      footData={computedProps.footRows}
      columns={columns}
      // resizable={true}
      // tableLayout="fixed"
      maxHeight={tableMaxHeight}
      hideSortTips={true}
      footerAffixedBottom={false}
      scroll={enableVirtualScroll ? { type: 'virtual' } : undefined}
    />
  );
};

// 主组件
const enableVirtualScroll = true;

const showDialog = () => {
  const defaultDlgRestHeightExceptBody = 170;
  const tableMaxHeight = getTableMaxHeight({ reservationHeight: 100 + defaultDlgRestHeightExceptBody });
  const dialogBody = (
    <TableDemo onClickCallback={showDialog} tableMaxHeight={tableMaxHeight} enableVirtualScroll={enableVirtualScroll} />
  );
  const dlgInstance = DialogPlugin({
    header: '递进查询',
    body: dialogBody,
    onClose: () => {
      dlgInstance.destroy();
    },
    confirmBtn: null,
    cancelBtn: '关闭',
    width: '80%',
    top: '32px',
    draggable: true,
  });
};

export default function App() {
  const tableMaxHeight = getTableMaxHeight({ reservationHeight: 35 });

  return (
    <div
    // style={{ width: 1000 }}
    >
      <style>{`body{margin:8px}`}</style>
      <TableDemo
        onClickCallback={showDialog}
        tableMaxHeight={tableMaxHeight}
        enableVirtualScroll={enableVirtualScroll}
      />
    </div>
  );
}

📝 更新日志

  • fix(Table): 修复放置在 Dialog 内部的组件,开启虚拟滚动时,初始化数据错误问题

  • fix(Table): 修复放置在 Dialog 内部的组件,窗口尺寸变化时,表头对齐不稳定的问题

  • fix(Table): 开启虚拟滚动或 headerAffixedTop 时,原始表头透明度降为 0,避免干扰视线

  • 本条 PR 不需要纳入 Changelog

☑️ 请求合并前的自查清单

⚠️ 请自检并全部勾选全部选项⚠️

  • 文档已补充或无须补充
  • 代码演示已提供或无须提供
  • TypeScript 定义已补充或无须补充
  • Changelog 已提供或无须提供

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 24, 2025

  • tdesign-react-demo

    npm i https://pkg.pr.new/Tencent/tdesign-react@4034
    
    npm i https://pkg.pr.new/Tencent/tdesign-react/@tdesign-react/chat@4034
    

commit: 0441528

@github-actions
Copy link
Contributor

github-actions bot commented Dec 24, 2025

完成

@RylanBot RylanBot added the WIP work in porgess label Dec 24, 2025
@RylanBot RylanBot force-pushed the rylan/fix/table/virtual branch from a7ebb49 to 51346f1 Compare December 24, 2025 12:06
@RylanBot RylanBot force-pushed the rylan/fix/table/virtual branch 2 times, most recently from 1c61c52 to 89ab5b7 Compare December 24, 2025 13:14
@RylanBot RylanBot added WIP work in porgess and removed WIP work in porgess labels Dec 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

WIP work in porgess

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants