-
Notifications
You must be signed in to change notification settings - Fork 282
feat: 新增虚拟滚动表格 #3346
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat_v3.x
Are you sure you want to change the base?
feat: 新增虚拟滚动表格 #3346
Conversation
Walkthrough本次变更新增虚拟表格组件与相关钩子、类型与样式;扩展 Table 文档与示例(含虚拟滚动与动态行高);更新 Table 排序逻辑与排序图标接口;新增多处 H5 Demo;引入样式变量用于表头背景;补充 Joycode 代理定义与一键环境安装提示。 Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant U as User
participant TV as TableVirtual
participant VS as useVirtualScroll
participant TR as TableRow (memo)
participant DOM as Scroll Container
U->>TV: 渲染 TableVirtual(props: data, columns, virtual, height, rowHeight, dynamicHeight...)
TV->>VS: 初始化 useVirtualScroll(total, viewportHeight, itemHeight, overscan, dynamicHeight)
VS-->>TV: { visibleRange, offsetY, totalHeight, onScroll, getRowRef, scrollTo }
TV->>DOM: 渲染容器与占位高度(totalHeight),绑定 onScroll
loop 可视区渲染
TV->>TR: 渲染 [start..end] 行,传入 sticky 样式/渲染器/getRowRef
TR-->>TV: 行节点(动态行高时回填高度)
TV->>VS: updateItemHeight(index, measuredHeight) (动态行高)
end
DOM-->>VS: scroll 事件
VS-->>TV: 更新 visibleRange/offsetY
TV->>TR: 按新范围重渲染可视行
sequenceDiagram
autonumber
participant U as User
participant TV as TableVirtual
participant S as Sorter
participant VS as useVirtualScroll
U->>TV: 点击列头排序
TV->>S: 计算 nextSortState(null→asc→desc→null)
alt asc/desc
S->>TV: 生成 sortedData
TV-->>U: onSort(column, sortedData, sortState)
TV->>VS: 触发容器 scroll 刷新可视范围
else 清除排序
TV-->>U: onSort(column) // 无排序数据
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 分钟 Possibly related PRs
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 19
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/packages/table/doc.md (2)
141-152
: 同步 onSort 与 sorterIcon 文档到最新 API当前文档的
onSort
签名与实现不符(实现里第三个参数为sortState
,且可省略),同时漏了列级sorterIcon
(函数)能力。请更新如下。-| onSort | 点击排序按钮触发 | `item: TableColumnProps, data: Array<any>` | `-` | -| sorterIcon | 排序 icon | `ReactNode` | `<ArrowDown />` | +| onSort | 点击排序按钮触发 | `(column: TableColumnProps, sortedData?: Array<any>, sortState?: SortStateType)` | `-` | +| sorterIcon | 全局排序图标(列未自定义时生效) | `ReactNode` | `<ArrowDown />` |
155-164
: 补充列级 sorterIcon 能力,并澄清 sorter 默认算法
TableColumnProps
需补充sorterIcon?: (currentSortState: SortStateType) => ReactNode
;同时说明sorter: 'default'
的行为与true
一致(基于列key
值排序)。| key | 列的唯一标识 | `string` | `-` | | title | 表头标题 | `string` | `-` | | align | 列的对齐方式 | `left` \| `center` \| `right` | `left` | +| sorterIcon | 自定义该列的排序图标 | `(currentSortState: SortStateType) => ReactNode` | `-` | -| sorter | 排序,可选值有 true,function, default, 其中 default表示点击之后可能会依赖接口, function可以返回具体的排序函数, default表示采用默认的排序算法 | `boolean` \| `Function` \| `string` | `-` | +| sorter | 排序,可选值 `true` / `'default'` / 自定义函数;`'default'` 与 `true` 一致,按该列 `key` 的值排序 | `boolean` \| `Function` \| `string` | `-` | | render | 自定义渲染列数据,优先级高 | `Function(record)` | `-` | | width | 列宽度 | `number` | `auto` | | fixed | 固定位置 | `left` \| `right` | `-` |
🧹 Nitpick comments (27)
.joycode/modes/rules-4k79ih5s/agentDefinition.md (5)
6-7
: 措辞/语法存在歧义,建议压缩指令并避免双重否定当前句式冗长且有语法瑕疵,建议更清晰、可执行:
- - Always write correct, best practice, DRY principle (Dont Repeat Yourself), bug free, fully functional and working code also it should be aligned to listed rules down below at Code Implementation Guidelines . - - Focus on easy and readability code, over being performant. + - 代码必须可运行、符合最佳实践并遵循 DRY;严禁遗留明显缺陷。 + - 在不牺牲可维护性的前提下优先可读性;性能优化放在有数据支撑时进行。
11-13
: 标点与表达不统一,影响可读性补齐标点并消除歧义:
- - Include all required imports, and ensure proper naming of key components. - - Be concise Minimize any other prose. + - Include all required imports, and ensure proper naming of key components. + - Be concise; minimize any other prose.
3-5
: 流程要求(先长篇伪代码再“Confirm”)可能拖慢常规开发节奏建议在 PR 场景弱化“先确认再写代码”的强约束,改为:对复杂/破坏性变更需要先给出设计要点或伪代码,日常小改可直接提交并在 MR 描述中列出变更点。
1-1
: Markdown 结构与重复开头用词可将首句改为标题并减少重复主语,提升可读性与搜索友好度:
- You are a Senior Front-End Developer and an Expert in ReactJS, NextJS, JavaScript, TypeScript, HTML, CSS and modern UI/UX frameworks (e.g., TailwindCSS, Shadcn, Radix). You are thoughtful, give nuanced answers, and are brilliant at reasoning. You carefully provide accurate, factual, thoughtful answers, and are a genius at reasoning. + # Role: Senior Front-End Developer (React/NutUI) + You provide accurate, thoughtful, and well-reasoned answers across React, TypeScript, and modern UI frameworks (TailwindCSS, Shadcn, Radix) when applicable.
16-27
: 收窄“Coding Environment” 列表,突出 NutUI React 核心栈验证结果:仓库主体为 NutUI‑React(大量 src/packages、sites 文档与 demo);Next.js 仅出现在模板 packages/nutui-templates/next-demo;Tailwind 未见源码/配置引用(仅在 node_modules 文档中)。
- 建议将 "Coding Environment" 优先列为:React、TypeScript、NutUI(主题/样式体系);将 Next.js、Tailwind 标注为“仅在示例/模板/示例站使用”或移至示例说明。
- 建议追加句子: “除非在文档/示例中,默认不引入与库样式体系冲突的全局样式方案。”
位置:.joycode/modes/rules-4k79ih5s/agentDefinition.md
.joycode/prompt.json (3)
1-1
: 建议补充跨平台与版本管理的默认策略,避免“curl | bash”式不安全安装在安装指导中优先推荐:
- Node:nvm/Volta +
.nvmrc
/packageManager
(启用 corepack,优先 pnpm/yarn 约定);安装依赖用npm ci
/pnpm install --frozen-lockfile
。- 多语言并存时,明确优先级与最小必需集合,避免全量安装拖慢流程。
可在“工作流程/安装指导”下追加安全与可重复性的约束说明。
1-1
: 将 .joycode 目录排除出 npm 发包产物若该目录仅服务于内部辅助工具,建议通过以下任一方式避免随包发布:
- 在 package.json 增加
"files"
白名单不含.joycode/
;- 或在
.npmignore
中加入.joycode/
。请确认当前发包配置。需要我起草变更片段可告知。
1-1
: 文案微调(中文)以减少歧义示例:
- 检测和安装Node.js(如果用户没要求推荐LTS版本) + 检测并安装 Node.js(若未指定版本,默认推荐安装 LTS 版本)如需,我可批量润色整段中文提示,保持术语与项目文档一致。
src/styles/variables.scss (1)
2233-2233
: 新增表头背景变量合理,但建议统一回退值写法为与本文件其他 token 风格保持一致,回退值可用 $white 而非硬编码 #fff。
可按下列方式微调:
-$table-th-bg-color: var(--nutui-table-th-bg-color, #fff) !default; +$table-th-bg-color: var(--nutui-table-th-bg-color, $white) !default;src/styles/variables-jmapp.scss (1)
2345-2345
: 与基础主题保持一致的回退值写法同 variables.scss,建议将回退值改为 $white,便于主题一致性与后续全局替换。
-$table-th-bg-color: var(--nutui-table-th-bg-color, #fff) !default; +$table-th-bg-color: var(--nutui-table-th-bg-color, $white) !default;src/packages/table/demos/h5/demo15.tsx (3)
43-50
: 命名小拼写:columnsStickHeaderLeft → columnsStickyHeaderLeft更正命名有助于可读性与检索。
- const [columnsStickHeaderLeft] = useState<Array<TableColumnProps>>([ + const [columnsStickyHeaderLeft] = useState<Array<TableColumnProps>>([并同步更新引用处:
- columns={columnsStickHeaderLeft} + columns={columnsStickyHeaderLeft}
5-41
: 示例数据存在“生日/年龄”不一致,易误导为避免读者困惑,建议让年龄与生日大致对应,或仅保留其一。
5-41
: 常量数据/列配置无需 useState,使用 useMemo 或组件外常量更轻量减少不必要的状态,避免心智负担。
-import React, { useState } from 'react' +import React from 'react' - const [data] = useState([ + const data = React.useMemo( + () => [ { /* ... */ }, { /* ... */ }, { /* ... */ }, { /* ... */ }, { /* ... */ }, - ]) + ], + [] + ) - const [columnsStickyHeaderLeft] = useState<Array<TableColumnProps>>([ + const columnsStickyHeaderLeft = React.useMemo<Array<TableColumnProps>>( + () => [ { /* ... */ }, { /* ... */ }, { /* ... */ }, { /* ... */ }, { /* ... */ }, - ]) + ], + [] + )Also applies to: 43-67
src/packages/table/demo.tsx (1)
59-61
: 英文文案大小写统一其余标题均为 Title Case,建议统一:
- noVirtual: 'no virtual scroll', - virtual: 'virtual scroll', + noVirtual: 'No Virtual Scroll', + virtual: 'Virtual Scroll',src/types/spec/table/base.ts (1)
26-30
: onSort 新签名向后兼容,但可进一步增强类型安全第二个参数建议泛型化,避免 any;同时为第三个参数补充 JSDoc,明确语义。
示意(需在接口层引入泛型,超出本次 diff 变更范围):
export interface BaseTable<T = any> extends BaseProps { // ... onSort?: (column: TableColumnProps, sortedData?: Array<T>, sortState?: SortStateType) => void }若暂不引入泛型,也建议在文档中明确 sortedData/SortStateType 的取值与时序。
src/packages/table/demos/h5/demo-no-virtual.tsx (1)
1-1
: 优化:用 useMemo/常量替代 useState 保存静态 columns/data,移除 any这两处状态从未更新,使用 useMemo 或直接常量可避免不必要的状态、重新渲染与类型漂移;同时将 render 的
_record
从 any 改为 DataItem。-import React, { useState } from 'react' +import React, { useMemo } from 'react' @@ - const [columns] = useState<TableColumnProps[]>([ + const columns = useMemo<TableColumnProps[]>(() => [ @@ - render: (_record: any, index: number) => { + render: (_record: DataItem, index: number) => { return index + 1 }, @@ - ]) + ], []) @@ - const [data] = useState(generateData(10)) + const data = useMemo(() => generateData(10), [])Also applies to: 38-86, 88-91
src/packages/table/index.virtual.ts (1)
1-16
: 建议:类型仅导入 + 同步命名导出,减少运行时体积并提升 API 对称性将类型用
import type
引入,避免被打包;同时补充命名导出TableVirtual
,便于用户既可默认导入也可命名导入。-import { - TableVirtual, - VirtualTableRef, - VirtualTableProps, - useVirtualScroll, - useThrottle, - useDebounce, -} from './virtual' -import { TableColumnProps } from '@/types/spec/table/base' +import { TableVirtual, useVirtualScroll, useThrottle, useDebounce } from './virtual' +import type { VirtualTableRef, VirtualTableProps } from './virtual' +import type { TableColumnProps } from '@/types/spec/table/base' @@ -export { useVirtualScroll, useThrottle, useDebounce } +export { TableVirtual, useVirtualScroll, useThrottle, useDebounce } export type { TableColumnProps, VirtualTableRef, VirtualTableProps } export default TableVirtualWrappersrc/packages/table/doc.en-US.md (2)
177-184
: 文案微调:dynamicHeight 描述更准确更清晰表达为“运行时测量实际行高”,避免歧义。
-| dynamicHeight | Whether to enable dynamic height (if true, will try to get the actual height of each row) | `boolean` | `false` | +| dynamicHeight | Whether to enable dynamic row height (measures actual row heights at runtime) | `boolean` | `false` |
117-126
: 标题与说明优化:更贴合读者预期将 “Disable Virtual Scrolling” 调整为更中性的 “Without Virtual Scrolling”,并点明即
virtual=false
。-### Disable Virtual Scrolling - -Virtual scrolling table without enabling virtual scrolling, used as a regular table. +### Without Virtual Scrolling + +Use TableVirtual as a regular table (virtual=false).src/packages/table/table.scss (1)
52-56
: 选择器可能过宽:.nut-table-main-virtual 下对 “> div” 的样式有外溢风险
.nut-table-main-body > div
可能同时命中虚拟容器与单元格自定义内容的直系包装 div。建议在 TSX 为虚拟容器添加特征类或 data-attr,并将选择器收窄(避免样式污染)。可选方案(需同时在虚拟列表容器上加上对应类/属性):
-&-virtual { - .nut-table-main-body > div { +&-virtual { + .nut-table-main-body > [data-virtual-container='true'] { background-color: $color-background-overlay; } }src/packages/table/demos/h5/demo-virtual.tsx (1)
97-107
: 示例中存在未使用代码(updateData、handleScrollToRow)
updateData
与handleScrollToRow
没有被调用。建议删除以简化示例,或增加交互按钮触发它们。如选择删除,最小变更如下:
- // 更新数据的方法 - const updateData = (count: number) => { - setData(generateData(count)) - } - - // 滚动到指定行的方法 - const handleScrollToRow = (index: number) => { - if (tableRef.current) { - tableRef.current.scrollToIndex(index) - } - } + // 演示用:如需重新生成/滚动示例,请在页面添加按钮后再恢复这些方法src/packages/table/doc.md (1)
145-151
: 用词修正:summary 含义不清"是否显示简介" 容易误解,应表达为“总结栏内容”。建议改为:“总结栏内容”。类型
ReactNode
不变。-| summary | 是否显示简介 | `ReactNode` | `-` | +| summary | 总结栏内容 | `ReactNode` | `-` |src/packages/table/virtual/table-virtual.tsx (2)
346-355
: 虚拟切片边界处理可更稳健(避免 length=0 时负索引)当
innerValue.length === 0
时,Math.min(visibleRange[0], innerValue.length - 1)
为-1
。虽然当前slice(-1, 0)
返回空数组,但语义不直观。建议将起止索引都 clamp 到[0, length]
。- const dataToRender = virtual - ? innerValue.slice( - Math.min(visibleRange[0], innerValue.length - 1), - Math.min(visibleRange[1] + 1, innerValue.length) - ) - : innerValue + const start = Math.max(0, Math.min(visibleRange[0], innerValue.length)) + const end = Math.max(start, Math.min(visibleRange[1] + 1, innerValue.length)) + const dataToRender = virtual ? innerValue.slice(start, end) : innerValue
404-426
: 非虚拟模式不应强制设置固定高度在非虚拟模式下强制
height
可能导致内容被裁剪且不滚动。建议仅在virtual
为true
时设置maxHeight/overflow
,非虚拟时让内容自然撑开或由外部容器控制高度。- ...(virtual - ? { - height: height || 300, - maxHeight: height || 300, - overflow: 'auto', - position: 'relative', - } - : { - height: height || 300, - }), + ...(virtual + ? { + height: height || 300, + maxHeight: height || 300, + overflow: 'auto', + position: 'relative', + } + : { + // 非虚拟场景不强制高度,交由外部容器决定 + }),src/packages/table/virtual/table-row.tsx (2)
100-107
: 顶层容器不应在子组件内部设置 keykey 应由父级列表渲染处控制;在子组件内部设置 key 既无效又易引发困惑。
return ( <div className={bodyClassPrefix} - key={`row-${rowIndex}`} ref={dynamicHeight && getRowRef ? getRowRef(rowIndex) : undefined} >
19-19
: 样式类型应使用 React.CSSProperties更明确的类型能提升可读性与编辑器提示质量。
- getStickyStyle: (key: string) => Record<string, any> | undefined + getStickyStyle: (key: string) => React.CSSProperties | undefinedsrc/packages/table/virtual/virtual-scroll.ts (1)
241-252
: 动态高度仅在获取 ref 时测量一次,行高变化无法感知建议在 getRowRef 中对 element 使用 ResizeObserver 监听高度变化,或在行渲染后批量测量,保证缓存与真实高度一致。
如需,我可补充基于 ResizeObserver 的实现示例。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (20)
.joycode/modes/rules-4k79ih5s/agentDefinition.md
(1 hunks).joycode/prompt.json
(1 hunks)src/packages/table/demo.tsx
(4 hunks)src/packages/table/demos/h5/demo-no-virtual.tsx
(1 hunks)src/packages/table/demos/h5/demo-virtual.tsx
(1 hunks)src/packages/table/demos/h5/demo15.tsx
(1 hunks)src/packages/table/demos/h5/demo9.tsx
(2 hunks)src/packages/table/doc.en-US.md
(2 hunks)src/packages/table/doc.md
(2 hunks)src/packages/table/index.virtual.ts
(1 hunks)src/packages/table/table.scss
(3 hunks)src/packages/table/table.tsx
(5 hunks)src/packages/table/virtual/hooks.ts
(1 hunks)src/packages/table/virtual/index.ts
(1 hunks)src/packages/table/virtual/table-row.tsx
(1 hunks)src/packages/table/virtual/table-virtual.tsx
(1 hunks)src/packages/table/virtual/virtual-scroll.ts
(1 hunks)src/styles/variables-jmapp.scss
(1 hunks)src/styles/variables.scss
(1 hunks)src/types/spec/table/base.ts
(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
src/packages/table/virtual/virtual-scroll.ts (1)
src/packages/table/virtual/hooks.ts (1)
useThrottle
(10-64)
src/types/spec/table/base.ts (1)
src/types/base/atoms.ts (1)
PositionX
(42-42)
src/packages/table/demos/h5/demo-virtual.tsx (1)
src/packages/table/index.virtual.ts (2)
VirtualTableRef
(15-15)TableColumnProps
(15-15)
src/packages/table/virtual/table-row.tsx (1)
src/packages/table/index.virtual.ts (1)
TableColumnProps
(15-15)
src/packages/table/virtual/hooks.ts (2)
src/packages/table/index.virtual.ts (2)
useThrottle
(14-14)useDebounce
(14-14)src/packages/table/virtual/index.ts (2)
useThrottle
(8-8)useDebounce
(8-8)
src/packages/table/demos/h5/demo9.tsx (1)
src/types/spec/table/base.ts (2)
TableColumnProps
(5-14)SortStateType
(16-16)
src/packages/table/virtual/table-virtual.tsx (5)
src/utils/typings.ts (1)
ComponentDefaults
(10-13)src/types/spec/table/base.ts (1)
SortStateType
(16-16)src/hooks/use-props-value.ts (1)
usePropsValue
(11-35)src/packages/table/utils.ts (1)
useTableSticky
(4-84)src/packages/table/virtual/virtual-scroll.ts (1)
useVirtualScroll
(41-264)
src/packages/table/index.virtual.ts (2)
src/packages/table/virtual/index.ts (1)
TableVirtual
(1-1)src/packages/table/virtual/table-virtual.tsx (1)
TableVirtual
(489-491)
src/packages/table/table.tsx (1)
src/types/spec/table/base.ts (1)
SortStateType
(16-16)
🪛 LanguageTool
.joycode/modes/rules-4k79ih5s/agentDefinition.md
[style] ~1-~1: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...nswers, and are brilliant at reasoning. You carefully provide accurate, factual, th...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
[grammar] ~4-~4: There might be a mistake here.
Context: ...pseudocode, written out in great detail. - Confirm, then write code! - Always write...
(QB_NEW_EN)
[grammar] ~6-~6: There might be a mistake here.
Context: ... working code also it should be aligned to listed rules down below at Code Impleme...
(QB_NEW_EN)
[grammar] ~7-~7: There might be a mistake here.
Context: ...readability code, over being performant. - Fully implement all requested functional...
(QB_NEW_EN)
[grammar] ~9-~9: There might be a mistake here.
Context: ... todo’s, placeholders or missing pieces. - Ensure code is complete! Verify thorough...
(QB_NEW_EN)
[grammar] ~12-~12: There might be a mistake here.
Context: .... - Be concise Minimize any other prose. - If you think there might not be a correc...
(QB_NEW_EN)
[grammar] ~20-~20: There might be a mistake here.
Context: ...e following coding languages: - ReactJS - NextJS - JavaScript - TypeScript - Tailw...
(QB_NEW_EN)
[grammar] ~21-~21: There might be a mistake here.
Context: ...ng coding languages: - ReactJS - NextJS - JavaScript - TypeScript - TailwindCSS - ...
(QB_NEW_EN)
[grammar] ~22-~22: There might be a mistake here.
Context: ...guages: - ReactJS - NextJS - JavaScript - TypeScript - TailwindCSS - HTML - CSS #...
(QB_NEW_EN)
[grammar] ~23-~23: There might be a mistake here.
Context: ...actJS - NextJS - JavaScript - TypeScript - TailwindCSS - HTML - CSS ### Code Imple...
(QB_NEW_EN)
[grammar] ~24-~24: There might be a mistake here.
Context: ... - JavaScript - TypeScript - TailwindCSS - HTML - CSS ### Code Implementation Guid...
(QB_NEW_EN)
[grammar] ~25-~25: There might be a mistake here.
Context: ...Script - TypeScript - TailwindCSS - HTML - CSS ### Code Implementation Guidelines ...
(QB_NEW_EN)
[grammar] ~36-~36: There might be a mistake here.
Context: ... and on:keydown, and similar attributes. - Use consts instead of functions, for exa...
(QB_NEW_EN)
src/packages/table/doc.en-US.md
[grammar] ~177-~177: There might be a mistake here.
Context: ...y | Description | Type | Default Value | | --- | --- | --- | --- | | virtual | Wh...
(QB_NEW_EN)
[grammar] ~178-~178: There might be a mistake here.
Context: ...efault Value | | --- | --- | --- | --- | | virtual | Whether to enable virtual sc...
(QB_NEW_EN)
[grammar] ~179-~179: There might be a mistake here.
Context: ...irtual scrolling | boolean
| false
| | height | Table viewport height | `numb...
(QB_NEW_EN)
[grammar] ~180-~180: There might be a mistake here.
Context: ...ble viewport height | number
| 300
| | rowHeight | Height of each row | `numb...
(QB_NEW_EN)
[grammar] ~181-~181: There might be a mistake here.
Context: ...| Height of each row | number
| 40
| | overscan | Number of rows to preload |...
(QB_NEW_EN)
[grammar] ~182-~182: There might be a mistake here.
Context: ...er of rows to preload | number
| 5
| | dynamicHeight | Whether to enable dyna...
(QB_NEW_EN)
[grammar] ~187-~187: There might be a mistake here.
Context: ...leRef | Property | Description | Type | | --- | --- | --- | | scrollToIndex | Me...
(QB_NEW_EN)
[grammar] ~188-~188: There might be a mistake here.
Context: ...Description | Type | | --- | --- | --- | | scrollToIndex | Method to scroll to a ...
(QB_NEW_EN)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (10)
src/packages/table/demos/h5/demo15.tsx (1)
69-75
: 请确认“同时固定表头和左列”的效果确实开启当前仅设置了容器高度与左列 fixed。若表头吸顶不依赖显式 prop,此实现OK;若需要显式开关(例如 stickyHeader/scroll.y 等),请补充以确保示例名副其实。
src/packages/table/demo.tsx (1)
17-19
: 新增示例接入位置合理引入 Demo15/Virtual/NoVirtual 并在页面中分区展示,结构清晰。
Also applies to: 92-99
src/types/spec/table/base.ts (1)
16-16
: 新增 SortStateType 定义清晰三态类型简单明了,便于拓展。
src/packages/table/demos/h5/demo-no-virtual.tsx (1)
44-46
: TableColumnProps.render 已定义第二个参数 index,TableVirtual 在 TableRow 中传入 rowIndex 正确,无需修改类型或实现Likely an incorrect or invalid review comment.
src/packages/table/virtual/index.ts (1)
1-8
: 导出聚合清晰,命名与类型拆分合理当前导出结构简洁且易于 Tree-shaking,保持即可。
src/packages/table/table.scss (2)
73-81
: 表头样式调整合理为 th 增加
box-sizing
、独立background-color
与较高z-index
,与 sticky/fixed 列层级配合良好。
94-96
: 为 td 增加 box-sizing: border-box 有助于宽度一致性与指定
width
的列交互时更可预期,保留此变更。src/packages/table/demos/h5/demo9.tsx (1)
48-61
: 排序图标与回调签名更新合理
sorterIcon
基于SortStateType
渲染图标,契合新 API。onSort
回调签名与实现一致(含sortedData
、sortState
)。Also applies to: 64-70
src/packages/table/table.tsx (1)
135-171
: 默认排序图标渲染逻辑清晰,可保留列级
sorterIcon
优先,其次全局,再到默认图标,优先级符合预期。Also applies to: 184-186
src/packages/table/virtual/hooks.ts (1)
10-15
: 更新 useThrottle/useDebounce 签名为 void 返回类型
将泛型 T 约束为返回 void,且函数签名改为 (...args: Parameters) => void,以保证类型签名与实际实现一致,避免下游误用。已确认 virtual-scroll.ts 中唯一的调用(throttledScrollHandler)并未使用其返回值,变更安全。
- Use early returns whenever possible to make the code more readable. | ||
- Always use Tailwind classes for styling HTML elements; avoid using CSS or tags. | ||
- Use “class:” instead of the tertiary operator in class tags whenever possible. | ||
- Use descriptive variable and function/const names. Also, event functions should be named with a “handle” prefix, like “handleClick” for onClick and “handleKeyDown” for onKeyDown. | ||
- Implement accessibility features on elements. For example, a tag should have a tabindex=“0”, aria-label, on:click, and on:keydown, and similar attributes. | ||
- Use consts instead of functions, for example, “const toggle = () =>”. Also, define a type if possible. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
与本项目技术栈不符:Svelte 指令与 Tailwind-only 约束会误导 React/NutUI 贡献者
- “class:”“on:click”“on:keydown”是 Svelte 语法,React 中应使用 className、onClick、onKeyDown,且 a11y 属性在 JSX 中为 tabIndex。
- “始终使用 Tailwind,避免使用 CSS”与 NutUI React 现有样式体系(Less/主题变量、组件内样式约束)相冲突,易造成风格割裂、样式覆盖难以维护。
建议将规则改为 React/NutUI 友好版,并放宽 Tailwind-only 约束(可选用 clsx/twMerge 统一 className)。示例修订:
- - Always use Tailwind classes for styling HTML elements; avoid using CSS or tags.
- - Use “class:” instead of the tertiary operator in class tags whenever possible.
- - Implement accessibility features on elements. For example, a tag should have a tabindex=“0”, aria-label, on:click, and on:keydown, and similar attributes.
+ - 优先遵循本项目的样式体系(NutUI 变量/Less、组件约定)。在不冲突时可配合 Tailwind 使用,并通过 clsx/twMerge 组织 className。
+ - 在 React 中使用 className 与条件类工具(如 clsx/cn 或 twMerge),避免深层三元表达式。
+ - 实现无障碍:在 JSX 中使用 tabIndex、aria-* 属性,并使用 onClick/onKeyDown/onKeyUp 等事件处理器;必要时添加 role 与键盘交互逻辑。
如需,我可以提交一版与 NutUI 风格系统对齐的完整规则重写。
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
- Use early returns whenever possible to make the code more readable. | |
- Always use Tailwind classes for styling HTML elements; avoid using CSS or tags. | |
- Use “class:” instead of the tertiary operator in class tags whenever possible. | |
- Use descriptive variable and function/const names. Also, event functions should be named with a “handle” prefix, like “handleClick” for onClick and “handleKeyDown” for onKeyDown. | |
- Implement accessibility features on elements. For example, a tag should have a tabindex=“0”, aria-label, on:click, and on:keydown, and similar attributes. | |
- Use consts instead of functions, for example, “const toggle = () =>”. Also, define a type if possible. | |
- Use early returns whenever possible to make the code more readable. | |
- 优先遵循本项目的样式体系(NutUI 变量/Less、组件约定)。在不冲突时可配合 Tailwind 使用,并通过 clsx/twMerge 组织 className。 | |
- 在 React 中使用 className 与条件类工具(如 clsx/cn 或 twMerge),避免深层三元表达式。 | |
- 实现无障碍:在 JSX 中使用 tabIndex、aria-* 属性,并使用 onClick/onKeyDown/onKeyUp 等事件处理器;必要时添加 role 与键盘交互逻辑。 | |
- Use descriptive variable and function/const names. Also, event functions should be named with a “handle” prefix, like “handleClick” for onClick and “handleKeyDown” for onKeyDown. | |
- Use consts instead of functions, for example, “const toggle = () =>”. Also, define a type if possible. |
🧰 Tools
🪛 LanguageTool
[grammar] ~36-~36: There might be a mistake here.
Context: ... and on:keydown, and similar attributes. - Use consts instead of functions, for exa...
(QB_NEW_EN)
🤖 Prompt for AI Agents
.joycode/modes/rules-4k79ih5s/agentDefinition.md around lines 32 to 37: the
rules currently reference Svelte directives and enforce Tailwind-only styling
which conflicts with this repo’s React/NutUI stack; update the rule text to use
React/JSX equivalents (className instead of class:, onClick/onKeyDown instead of
on:click/on:keydown, tabIndex and aria-* casing for JSX), relax the
“Tailwind-only” constraint to allow NutUI’s Less/theme variables and component
styles (recommend using clsx or twMerge to compose className when Tailwind is
used), keep guidance on early returns, descriptive names and handler naming
(e.g., handleClick), prefer const arrow functions and adding types where
applicable, and note accessibility attributes should be applied in JSX form;
optionally offer to attach a full rewritten rule set aligned to NutUI styling
conventions.
@@ -0,0 +1 @@ | |||
[{"label":"一键安装环境","name":"Install","description":"专注于解决工作空间环境问题","prompt":"你是一位专门从事解决工作空间环境问题的全栈工程师和DevOps专家,你的主要任务是帮助用户诊断、修复和配置当前工作空间`/Users/jiangqi147/github/nutui-react`的开发环境。\n\n## 核心职责\n\n### 1. 环境检测与诊断\n- 自动扫描工作空间中的项目文件(package.json, requirements.txt, pom.xml, Gemfile, go.mod等)\n- 识别项目所需的运行环境和依赖\n- 检测当前系统已安装的环境版本\n- 分析环境配置冲突和兼容性问题\n\n### 2. 主流环境支持\n**Node.js生态系统:**\n- 检测和安装Node.js(如果用户没要求推荐LTS版本)\n- 配置npm/yarn/pnpm包管理器\n- 处理node_modules依赖问题\n- 解决版本冲突和权限问题\n\n**Python生态系统:**\n- 安装Python(2.x/3.x版本管理)\n- 配置pip包管理器和虚拟环境(venv/conda)\n- 处理requirements.txt依赖安装\n- 解决Python路径和模块导入问题\n\n**Java生态系统:**\n- 安装和配置JDK/JRE(版本选择和JAVA_HOME设置)\n- 配置Maven/Gradle构建工具\n- 处理依赖下载和仓库配置\n- 解决类路径和编译问题\n\n**其他主流环境:**\n- Go语言环境配置\n- Ruby和Rails环境\n- PHP和Composer\n- .NET Core环境\n- Docker容器化环境\n\n### 3. 项目启动与运行\n- 分析项目启动脚本和配置文件\n- 提供标准化的启动命令\n- 配置开发服务器和热重载\n- 设置环境变量和配置文件\n- 处理端口冲突和服务依赖\n\n### 4. 问题解决策略\n- 提供跨平台解决方案(Windows/macOS/Linux)\n- 给出详细的安装步骤和命令\n- 提供多种安装方式选择(官方安装器/包管理器/容器化)\n- 预防常见错误和最佳实践建议\n- 提供环境验证和测试方法\n\n### 5. 交互方式\n- 首先询问用户的操作系统和项目类型\n- 逐步引导用户完成环境配置\n- 提供可复制的命令和脚本\n- 在每个步骤后确认执行结果\n- 遇到问题时提供多种备选方案\n\n## 工作流程\n1. **环境扫描**:分析工作空间文件结构,识别项目类型\n2. **需求评估**:确定所需的运行环境和版本要求\n3. **现状检查**:检测当前已安装的环境和工具\n4. **差距分析**:对比需求与现状,列出缺失项\n5. **安装指导**:提供详细的安装和配置步骤\n6. **验证测试**:确保环境配置正确可用\n7. **项目启动**:协助用户成功启动项目\n\n请始终保持耐心和专业,用通俗易懂的语言解释技术概念,并在每个关键步骤提供清晰的指导。现在请开始分析当前工作空间的环境需求。\n"}] No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
硬编码本地绝对路径不可移植,且可能泄露开发者用户名
/Users/jiangqi147/github/nutui-react
会在他人机器上失效,也会把个人用户名暴露到仓库历史。建议改为占位符或由运行环境注入工作区根路径。
- "…配置当前工作空间`/Users/jiangqi147/github/nutui-react`的开发环境。"
+ "…配置当前工作空间`${WORKSPACE_ROOT}`的开发环境。"
同时在执行时由宿主侧将 ${WORKSPACE_ROOT}
替换为实际路径,或在工具侧自动探测 repo 根目录。
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
[{"label":"一键安装环境","name":"Install","description":"专注于解决工作空间环境问题","prompt":"你是一位专门从事解决工作空间环境问题的全栈工程师和DevOps专家,你的主要任务是帮助用户诊断、修复和配置当前工作空间`/Users/jiangqi147/github/nutui-react`的开发环境。\n\n## 核心职责\n\n### 1. 环境检测与诊断\n- 自动扫描工作空间中的项目文件(package.json, requirements.txt, pom.xml, Gemfile, go.mod等)\n- 识别项目所需的运行环境和依赖\n- 检测当前系统已安装的环境版本\n- 分析环境配置冲突和兼容性问题\n\n### 2. 主流环境支持\n**Node.js生态系统:**\n- 检测和安装Node.js(如果用户没要求推荐LTS版本)\n- 配置npm/yarn/pnpm包管理器\n- 处理node_modules依赖问题\n- 解决版本冲突和权限问题\n\n**Python生态系统:**\n- 安装Python(2.x/3.x版本管理)\n- 配置pip包管理器和虚拟环境(venv/conda)\n- 处理requirements.txt依赖安装\n- 解决Python路径和模块导入问题\n\n**Java生态系统:**\n- 安装和配置JDK/JRE(版本选择和JAVA_HOME设置)\n- 配置Maven/Gradle构建工具\n- 处理依赖下载和仓库配置\n- 解决类路径和编译问题\n\n**其他主流环境:**\n- Go语言环境配置\n- Ruby和Rails环境\n- PHP和Composer\n- .NET Core环境\n- Docker容器化环境\n\n### 3. 项目启动与运行\n- 分析项目启动脚本和配置文件\n- 提供标准化的启动命令\n- 配置开发服务器和热重载\n- 设置环境变量和配置文件\n- 处理端口冲突和服务依赖\n\n### 4. 问题解决策略\n- 提供跨平台解决方案(Windows/macOS/Linux)\n- 给出详细的安装步骤和命令\n- 提供多种安装方式选择(官方安装器/包管理器/容器化)\n- 预防常见错误和最佳实践建议\n- 提供环境验证和测试方法\n\n### 5. 交互方式\n- 首先询问用户的操作系统和项目类型\n- 逐步引导用户完成环境配置\n- 提供可复制的命令和脚本\n- 在每个步骤后确认执行结果\n- 遇到问题时提供多种备选方案\n\n## 工作流程\n1. **环境扫描**:分析工作空间文件结构,识别项目类型\n2. **需求评估**:确定所需的运行环境和版本要求\n3. **现状检查**:检测当前已安装的环境和工具\n4. **差距分析**:对比需求与现状,列出缺失项\n5. **安装指导**:提供详细的安装和配置步骤\n6. **验证测试**:确保环境配置正确可用\n7. **项目启动**:协助用户成功启动项目\n\n请始终保持耐心和专业,用通俗易懂的语言解释技术概念,并在每个关键步骤提供清晰的指导。现在请开始分析当前工作空间的环境需求。\n"}] | |
[{"label":"一键安装环境","name":"Install","description":"专注于解决工作空间环境问题","prompt":"你是一位专门从事解决工作空间环境问题的全栈工程师和DevOps专家,你的主要任务是帮助用户诊断、修复和配置当前工作空间`${WORKSPACE_ROOT}`的开发环境。\n\n## 核心职责\n\n### 1. 环境检测与诊断\n- 自动扫描工作空间中的项目文件(package.json, requirements.txt, pom.xml, Gemfile, go.mod等)\n- 识别项目所需的运行环境和依赖\n- 检测当前系统已安装的环境版本\n- 分析环境配置冲突和兼容性问题\n\n### 2. 主流环境支持\n**Node.js生态系统:**\n- 检测和安装Node.js(如果用户没要求推荐LTS版本)\n- 配置npm/yarn/pnpm包管理器\n- 处理node_modules依赖问题\n- 解决版本冲突和权限问题\n\n**Python生态系统:**\n- 安装Python(2.x/3.x版本管理)\n- 配置pip包管理器和虚拟环境(venv/conda)\n- 处理requirements.txt依赖安装\n- 解决Python路径和模块导入问题\n\n**Java生态系统:**\n- 安装和配置JDK/JRE(版本选择和JAVA_HOME设置)\n- 配置Maven/Gradle构建工具\n- 处理依赖下载和仓库配置\n- 解决类路径和编译问题\n\n**其他主流环境:**\n- Go语言环境配置\n- Ruby和Rails环境\n- PHP和Composer\n- .NET Core环境\n- Docker容器化环境\n\n### 3. 项目启动与运行\n- 分析项目启动脚本和配置文件\n- 提供标准化的启动命令\n- 配置开发服务器和热重载\n- 设置环境变量和配置文件\n- 处理端口冲突和服务依赖\n\n### 4. 问题解决策略\n- 提供跨平台解决方案(Windows/macOS/Linux)\n- 给出详细的安装步骤和命令\n- 提供多种安装方式选择(官方安装器/包管理器/容器化)\n- 预防常见错误和最佳实践建议\n- 提供环境验证和测试方法\n\n### 5. 交互方式\n- 首先询问用户的操作系统和项目类型\n- 逐步引导用户完成环境配置\n- 提供可复制的命令和脚本\n- 在每个步骤后确认执行结果\n- 遇到问题时提供多种备选方案\n\n## 工作流程\n1. **环境扫描**:分析工作空间文件结构,识别项目类型\n2. **需求评估**:确定所需的运行环境和版本要求\n3. **现状检查**:检测当前已安装的环境和工具\n4. **差距分析**:对比需求与现状,列出缺失项\n5. **安装指导**:提供详细的安装和配置步骤\n6. **验证测试**:确保环境配置正确可用\n7. **项目启动**:协助用户成功启动项目\n\n请始终保持耐心和专业,用通俗易懂的语言解释技术概念,并在每个关键步骤提供清晰的指导。现在请开始分析当前工作空间的环境需求。\n"}] |
🤖 Prompt for AI Agents
.joycode/prompt.json lines 1-1: The prompt hardcodes a local absolute path
(/Users/jiangqi147/github/nutui-react), which is non-portable and exposes a
username; replace it with a placeholder or inject the workspace root at runtime.
Update the prompt text to use a variable like ${WORKSPACE_ROOT} or
<WORKSPACE_ROOT> (and document that the host will replace it), and add fallback
logic in tooling to auto-detect the repository root if the variable is not
provided so the prompt remains portable and safe for other contributors.
const DemoVirtual = () => { | ||
// 创建表格引用 | ||
const tableRef = useRef<VirtualTableRef>(null) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ref 未绑定导致示例中 scrollToIndex 无法使用
已创建 tableRef
,但未通过 ref
传给 <TableVirtual />
;示例内的 handleScrollToRow
永远拿不到实例。请至少将 ref 绑定到组件。
应用以下 diff 修复:
- const tableRef = useRef<VirtualTableRef>(null)
+ const tableRef = useRef<VirtualTableRef>(null)
@@
- <TableVirtual
+ <TableVirtual
+ ref={tableRef}
columns={columns}
data={data}
virtual
height={400}
rowHeight={40} // 默认行高,实际会根据内容动态调整
overscan={10}
bordered
dynamicHeight // 启用动态高度
/>
Also applies to: 109-119
🤖 Prompt for AI Agents
In src/packages/table/demos/h5/demo-virtual.tsx around lines 15-18 (and also
apply same fix at lines 109-119): a useRef for VirtualTableRef is created but
never passed to the <TableVirtual /> component, so handlers like
handleScrollToRow cannot access the instance; bind the ref by passing
ref={tableRef} (or the correct forwardedRef prop name) into the <TableVirtual />
JSX where the table is rendered so the ref is attached and instance methods
(e.g., scrollToIndex) become available to the handlers.
```tsx | ||
import { TableVirtual } from '@nutui/nutui-react' | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
验证:包根是否已导出 TableVirtual(与文档导入路径一致)
文档使用 import { TableVirtual } from '@nutui/nutui-react'
,请确认根入口已 re-export。
🏁 Script executed:
#!/usr/bin/env bash
# 查找任何导出 TableVirtual 的根级入口
rg -nP -C2 '\bexport\s+{[^}]*\bTableVirtual\b' src || true
# 查找是否有从 table/index.virtual 再导出到包根
rg -nP -C2 'export\s+\*\s+from.+table\/index\.virtual' src || true
Length of output: 407
确保在包根入口文件导出 TableVirtual
文档中示例使用了
import { TableVirtual } from '@nutui/nutui-react'
但在包根入口(如 src/index.ts 或主导出文件)中未找到对 TableVirtual 的 re-export。请在入口加入:
export { TableVirtual } from './packages/table/virtual';
🤖 Prompt for AI Agents
In src/packages/table/doc.en-US.md around lines 169 to 171, the docs show
importing TableVirtual from '@nutui/nutui-react' but the package root does not
re-export TableVirtual; update the package root export file (e.g., src/index.ts
or your library's main export file) to add a re-export for the virtual table
component so consumers can import it from the library root, specifically
re-export the component from its source path (./packages/table/virtual) and
ensure the export is included in the library build/exports map.
## TableVirtual | ||
|
||
### 引入 | ||
|
||
```tsx | ||
import { TableVirtual } from '@nutui/nutui-react' | ||
``` | ||
|
||
### Props | ||
|
||
继承 Table 组件的所有属性,并新增以下属性: | ||
|
||
| 属性 | 说明 | 类型 | 默认值 | | ||
| --- | --- | --- | --- | | ||
| virtual | 是否启用虚拟滚动 | `boolean` | `false` | | ||
| height | 表格可视区域高度 | `number` | `300` | | ||
| rowHeight | 每行高度 | `number` | `40` | | ||
| overscan | 预加载的行数 | `number` | `5` | | ||
| dynamicHeight | 是否启用动态高度(如果为true,将尝试获取每行的实际高度) | `boolean` | `false` | | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
确认 VirtualTable Props 未暴露 props 版的 scrollToIndex
实现中 ref 暴露 scrollToIndex
,props 形态会引起误用(见组件实现)。文档当前未列出 props 版,保持如此是正确的;请确保后续不添加该 props,以免与 ref 冲突。
建议运行脚本核对导出与文档一致:
🏁 Script executed:
#!/bin/bash
# 校验是否存在 props 版 scrollToIndex
rg -nP "scrollToIndex\s*\?:\s*\(" src/packages/table -C2
# 校验对外导出的类型/组件
rg -nP "export\s+\{[^}]*TableVirtual|VirtualTableRef|TableColumnProps|SortStateType" -C2
Length of output: 39529
移除 VirtualTableProps 中的 scrollToIndex
该方法仅应通过 ref 暴露,props 接口不应包含 scrollToIndex
,请在 src/packages/table/virtual/table-virtual.tsx
的 VirtualTableProps
中删除该属性声明,并确保文档和导出类型保持一致。
🤖 Prompt for AI Agents
In src/packages/table/doc.md around lines 165 to 184 and in
src/packages/table/virtual/table-virtual.tsx (VirtualTableProps), remove the
scrollToIndex property from the VirtualTableProps type/interface so it is no
longer declared on props, implement scrollToIndex only via the component ref
API, update any exported types to reflect the removal, and update the doc.md to
not list scrollToIndex in the props table (ensuring typed exports and docs
remain consistent).
const handleSorterClick = (item: TableColumnProps) => { | ||
if (!item.sorter) return | ||
|
||
// 获取当前排序状态,如果不存在则默认为 null(不排序) | ||
const currentSortState = sortedMapping.current[item.key] || null | ||
|
||
// 根据当前状态确定下一个状态:null -> asc -> desc -> null | ||
let nextSortState: 'asc' | 'desc' | null | ||
if (currentSortState === null) { | ||
nextSortState = 'asc' // 默认不排序 -> 升序 | ||
} else if (currentSortState === 'asc') { | ||
nextSortState = 'desc' // 升序 -> 降序 | ||
} else { | ||
nextSortState = null // 降序 -> 不排序 | ||
} | ||
|
||
// 更新排序状态 | ||
sortedMapping.current[item.key] = nextSortState | ||
|
||
// 根据排序状态执行相应的排序操作 | ||
if (nextSortState === null) { | ||
// 不排序,恢复原始数据 | ||
setValue(data) | ||
onSort && onSort(item) | ||
} else { | ||
const copied = [...innerValue] | ||
if (typeof item.sorter === 'function') { | ||
// 使用自定义排序函数 | ||
if (nextSortState === 'asc') { | ||
copied.sort(item.sorter as (a: any, b: any) => number) | ||
} else { | ||
// 降序:交换排序函数的参数顺序 | ||
copied.sort( | ||
(a, b) => -(item.sorter as (a: any, b: any) => number)(a, b) | ||
) | ||
} | ||
} else if (item.sorter === 'default') { | ||
// 默认排序 | ||
if (nextSortState === 'asc') { | ||
copied.sort() | ||
} else { | ||
copied.sort().reverse() | ||
} | ||
} else if (item.sorter === true) { | ||
// 简单排序,根据列的 key 值进行排序 | ||
const key = item.key | ||
if (nextSortState === 'asc') { | ||
copied.sort((a, b) => (a[key] > b[key] ? 1 : -1)) | ||
} else { | ||
copied.sort((a, b) => (a[key] > b[key] ? -1 : 1)) | ||
} | ||
} | ||
setValue(copied, true) | ||
onSort && onSort(item, copied, nextSortState) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
排序:与 Table 保持一致的修复(单列高亮、'default' 行为与相等值返回 0)
这里与非虚拟表相同的问题,需要一并修复。
- // 更新排序状态
- sortedMapping.current[item.key] = nextSortState
+ // 更新排序状态:只保留当前列
+ sortedMapping.current = {}
+ sortedMapping.current[item.key] = nextSortState
@@
- } else if (item.sorter === 'default') {
- // 默认排序
- if (nextSortState === 'asc') {
- copied.sort()
- } else {
- copied.sort().reverse()
- }
+ } else if (item.sorter === 'default') {
+ // 默认排序:等同于 true,按列值排序
+ const key = item.key
+ if (nextSortState === 'asc') {
+ copied.sort((a, b) => (a[key] === b[key] ? 0 : a[key] > b[key] ? 1 : -1))
+ } else {
+ copied.sort((a, b) => (a[key] === b[key] ? 0 : a[key] > b[key] ? -1 : 1))
+ }
@@
- copied.sort((a, b) => (a[key] > b[key] ? 1 : -1))
+ copied.sort((a, b) => (a[key] === b[key] ? 0 : a[key] > b[key] ? 1 : -1))
@@
- copied.sort((a, b) => (a[key] > b[key] ? -1 : 1))
+ copied.sort((a, b) => (a[key] === b[key] ? 0 : a[key] > b[key] ? -1 : 1))
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const handleSorterClick = (item: TableColumnProps) => { | |
if (!item.sorter) return | |
// 获取当前排序状态,如果不存在则默认为 null(不排序) | |
const currentSortState = sortedMapping.current[item.key] || null | |
// 根据当前状态确定下一个状态:null -> asc -> desc -> null | |
let nextSortState: 'asc' | 'desc' | null | |
if (currentSortState === null) { | |
nextSortState = 'asc' // 默认不排序 -> 升序 | |
} else if (currentSortState === 'asc') { | |
nextSortState = 'desc' // 升序 -> 降序 | |
} else { | |
nextSortState = null // 降序 -> 不排序 | |
} | |
// 更新排序状态 | |
sortedMapping.current[item.key] = nextSortState | |
// 根据排序状态执行相应的排序操作 | |
if (nextSortState === null) { | |
// 不排序,恢复原始数据 | |
setValue(data) | |
onSort && onSort(item) | |
} else { | |
const copied = [...innerValue] | |
if (typeof item.sorter === 'function') { | |
// 使用自定义排序函数 | |
if (nextSortState === 'asc') { | |
copied.sort(item.sorter as (a: any, b: any) => number) | |
} else { | |
// 降序:交换排序函数的参数顺序 | |
copied.sort( | |
(a, b) => -(item.sorter as (a: any, b: any) => number)(a, b) | |
) | |
} | |
} else if (item.sorter === 'default') { | |
// 默认排序 | |
if (nextSortState === 'asc') { | |
copied.sort() | |
} else { | |
copied.sort().reverse() | |
} | |
} else if (item.sorter === true) { | |
// 简单排序,根据列的 key 值进行排序 | |
const key = item.key | |
if (nextSortState === 'asc') { | |
copied.sort((a, b) => (a[key] > b[key] ? 1 : -1)) | |
} else { | |
copied.sort((a, b) => (a[key] > b[key] ? -1 : 1)) | |
} | |
} | |
setValue(copied, true) | |
onSort && onSort(item, copied, nextSortState) | |
} | |
} | |
const handleSorterClick = (item: TableColumnProps) => { | |
if (!item.sorter) return | |
// 获取当前排序状态,如果不存在则默认为 null(不排序) | |
const currentSortState = sortedMapping.current[item.key] || null | |
// 根据当前状态确定下一个状态:null -> asc -> desc -> null | |
let nextSortState: 'asc' | 'desc' | null | |
if (currentSortState === null) { | |
nextSortState = 'asc' // 默认不排序 -> 升序 | |
} else if (currentSortState === 'asc') { | |
nextSortState = 'desc' // 升序 -> 降序 | |
} else { | |
nextSortState = null // 降序 -> 不排序 | |
} | |
// 更新排序状态:只保留当前列 | |
sortedMapping.current = {} | |
sortedMapping.current[item.key] = nextSortState | |
// 根据排序状态执行相应的排序操作 | |
if (nextSortState === null) { | |
// 不排序,恢复原始数据 | |
setValue(data) | |
onSort && onSort(item) | |
} else { | |
const copied = [...innerValue] | |
if (typeof item.sorter === 'function') { | |
// 使用自定义排序函数 | |
if (nextSortState === 'asc') { | |
copied.sort(item.sorter as (a: any, b: any) => number) | |
} else { | |
// 降序:交换排序函数的参数顺序 | |
copied.sort( | |
(a, b) => -(item.sorter as (a: any, b: any) => number)(a, b) | |
) | |
} | |
} else if (item.sorter === 'default') { | |
// 默认排序:等同于 true,按列值排序 | |
const key = item.key | |
if (nextSortState === 'asc') { | |
copied.sort((a, b) => (a[key] === b[key] ? 0 : a[key] > b[key] ? 1 : -1)) | |
} else { | |
copied.sort((a, b) => (a[key] === b[key] ? 0 : a[key] > b[key] ? -1 : 1)) | |
} | |
} else if (item.sorter === true) { | |
// 简单排序,根据列的 key 值进行排序 | |
const key = item.key | |
if (nextSortState === 'asc') { | |
copied.sort((a, b) => (a[key] === b[key] ? 0 : a[key] > b[key] ? 1 : -1)) | |
} else { | |
copied.sort((a, b) => (a[key] === b[key] ? 0 : a[key] > b[key] ? -1 : 1)) | |
} | |
} | |
setValue(copied, true) | |
onSort && onSort(item, copied, nextSortState) | |
} | |
} |
🤖 Prompt for AI Agents
In src/packages/table/virtual/table-virtual.tsx around lines 203 to 258:
currently sorting toggles per-column but does not clear other columns' sort
state (so single-column highlight isn't enforced), uses negation of custom
sorter for desc (which can break comparators and 0 results), and uses
copied.sort()/reverse for 'default' which doesn't return 0 for equal values. Fix
by: when setting nextSortState for item.key, clear all other keys in
sortedMapping.current so only this column is highlighted; for custom sorter,
call the sorter with reversed arguments for desc (i.e. sorter(b,a)) instead of
negating the return value; for 'default' and key-based true sorter, use an
explicit comparator that returns 1/0/-1 (e.g. a[key] > b[key] ? 1 : a[key] <
b[key] ? -1 : 0) and apply that for asc and reverse the comparator for desc;
keep restoring original data when nextSortState is null and call setValue/onSort
as before.
// 动态高度的计算 | ||
let currentHeight = 0 | ||
let startIndex = 0 | ||
let endIndex = 0 | ||
|
||
// 找到起始索引 | ||
for (let i = 0; i < total; i++) { | ||
const rowHeight = heightCache[i] || itemHeight | ||
if (currentHeight + rowHeight > currentScrollTop) { | ||
startIndex = Math.max(0, i - overscan) | ||
break | ||
} | ||
currentHeight += rowHeight | ||
} | ||
|
||
// 找到结束索引 | ||
currentHeight = 0 | ||
for (let i = 0; i < total; i++) { | ||
const rowHeight = heightCache[i] || itemHeight | ||
currentHeight += rowHeight | ||
if (currentHeight > currentScrollTop + viewportHeight) { | ||
endIndex = Math.min(total - 1, i + overscan) | ||
break | ||
} | ||
|
||
// 如果到达最后一行,设置结束索引为最后一行 | ||
if (i === total - 1) { | ||
endIndex = total - 1 | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
动态高度可见区计算为 O(n)×2,长列表下性能风险较大
二次从 0 扫描 total。建议基于前缀和数组做二分查找,或从 startIndex 开始单向累加计算 endIndex,均可降为 O(log n) 或 O(k)。
可先做轻量修正:从 startIndex 开始计算 endIndex,避免第二次从 0 扫描。
- // 找到结束索引
- currentHeight = 0
- for (let i = 0; i < total; i++) {
- const rowHeight = heightCache[i] || itemHeight
- currentHeight += rowHeight
- if (currentHeight > currentScrollTop + viewportHeight) {
- endIndex = Math.min(total - 1, i + overscan)
- break
- }
- if (i === total - 1) {
- endIndex = total - 1
- }
- }
+ // 从 startIndex 开始计算结束索引,避免 O(n) 级全表扫描
+ currentHeight = 0
+ for (let i = startIndex; i < total; i++) {
+ const rowHeight = heightCache[i] || itemHeight
+ currentHeight += rowHeight
+ if (currentHeight > viewportHeight) {
+ endIndex = Math.min(total - 1, i + overscan)
+ break
+ }
+ if (i === total - 1) {
+ endIndex = total - 1
+ }
+ }
如需进一步优化,我可提供前缀和 + 二分版本。
🤖 Prompt for AI Agents
In src/packages/table/virtual/virtual-scroll.ts around lines 117 to 147, the
code scans from 0 twice to compute startIndex and endIndex causing O(n)×2 work;
change the second scan to begin at startIndex and initialize currentHeight to
the cumulative height at startIndex so endIndex is found by a single forward
accumulation from startIndex until currentHeight exceeds currentScrollTop +
viewportHeight (adding overscan and clamping to bounds). Concretely: after
finding startIndex, set currentHeight to the sum of heights of rows
0..startIndex-1 (or maintain the already computed currentHeight from the first
loop), then loop i from startIndex to total-1 accumulating row heights and break
when the viewport bottom is reached, setting endIndex = Math.min(total - 1, i +
overscan). This avoids the second full scan and reduces work to O(k); for
further optimization replace with prefix-sum + binary search later.
const onScroll = (event: React.UIEvent) => { | ||
// 获取当前滚动位置 | ||
const scrollContainer = event.target as HTMLDivElement | ||
const currentScrollTop = scrollContainer.scrollTop | ||
|
||
// 使用节流函数处理滚动更新 | ||
throttledScrollHandler(currentScrollTop) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onScroll 使用 event.target 取滚动容器不可靠,应使用 currentTarget
target 可能指向子元素,导致读取错误的 scrollTop。
- const onScroll = (event: React.UIEvent) => {
+ const onScroll = (event: React.UIEvent<HTMLDivElement>) => {
// 获取当前滚动位置
- const scrollContainer = event.target as HTMLDivElement
+ const scrollContainer = event.currentTarget as HTMLDivElement
const currentScrollTop = scrollContainer.scrollTop
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const onScroll = (event: React.UIEvent) => { | |
// 获取当前滚动位置 | |
const scrollContainer = event.target as HTMLDivElement | |
const currentScrollTop = scrollContainer.scrollTop | |
// 使用节流函数处理滚动更新 | |
throttledScrollHandler(currentScrollTop) | |
} | |
const onScroll = (event: React.UIEvent<HTMLDivElement>) => { | |
// 获取当前滚动位置 | |
const scrollContainer = event.currentTarget as HTMLDivElement | |
const currentScrollTop = scrollContainer.scrollTop | |
// 使用节流函数处理滚动更新 | |
throttledScrollHandler(currentScrollTop) | |
} |
🤖 Prompt for AI Agents
In src/packages/table/virtual/virtual-scroll.ts around lines 196 to 203,
onScroll currently reads scroll position from event.target which can be a child
element; change it to use event.currentTarget (typed as HTMLDivElement or set
onScroll parameter to React.UIEvent<HTMLDivElement>) and read
currentTarget.scrollTop, then pass that value to throttledScrollHandler so the
scroll position is always taken from the actual scroll container.
const scrollTo = (index: number) => { | ||
if (containerRef.current) { | ||
const targetIndex = Math.min(Math.max(0, index), totalRef.current - 1) // 确保不会滚动到超出范围的位置 | ||
|
||
let targetScrollTop = 0 | ||
if (!dynamicHeight) { | ||
targetScrollTop = targetIndex * itemHeight | ||
} else { | ||
// 计算目标位置的滚动偏移 | ||
for (let i = 0; i < targetIndex; i++) { | ||
targetScrollTop += heightCache[i] || itemHeight | ||
} | ||
} | ||
|
||
// 设置DOM元素的滚动位置 | ||
containerRef.current.scrollTop = targetScrollTop | ||
|
||
// 更新ref值 | ||
scrollTopRef.current = targetScrollTop | ||
|
||
// 触发状态更新以重新渲染 | ||
setScrollTopState(targetScrollTop) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
scrollTo 在 total=0 时会计算出 -1 索引,可能写入负的 scrollTop
需要边界保护。
const scrollTo = (index: number) => {
if (containerRef.current) {
- const targetIndex = Math.min(Math.max(0, index), totalRef.current - 1) // 确保不会滚动到超出范围的位置
+ if (totalRef.current <= 0) {
+ containerRef.current.scrollTop = 0
+ scrollTopRef.current = 0
+ setScrollTopState(0)
+ return
+ }
+ const targetIndex = Math.min(Math.max(0, index), totalRef.current - 1)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const scrollTo = (index: number) => { | |
if (containerRef.current) { | |
const targetIndex = Math.min(Math.max(0, index), totalRef.current - 1) // 确保不会滚动到超出范围的位置 | |
let targetScrollTop = 0 | |
if (!dynamicHeight) { | |
targetScrollTop = targetIndex * itemHeight | |
} else { | |
// 计算目标位置的滚动偏移 | |
for (let i = 0; i < targetIndex; i++) { | |
targetScrollTop += heightCache[i] || itemHeight | |
} | |
} | |
// 设置DOM元素的滚动位置 | |
containerRef.current.scrollTop = targetScrollTop | |
// 更新ref值 | |
scrollTopRef.current = targetScrollTop | |
// 触发状态更新以重新渲染 | |
setScrollTopState(targetScrollTop) | |
} | |
} | |
const scrollTo = (index: number) => { | |
if (containerRef.current) { | |
// Guard against empty list (total = 0) to avoid negative scrollTop | |
if (totalRef.current <= 0) { | |
containerRef.current.scrollTop = 0 | |
scrollTopRef.current = 0 | |
setScrollTopState(0) | |
return | |
} | |
const targetIndex = Math.min( | |
Math.max(0, index), | |
totalRef.current - 1 | |
) // 确保不会滚动到超出范围的位置 | |
let targetScrollTop = 0 | |
if (!dynamicHeight) { | |
targetScrollTop = targetIndex * itemHeight | |
} else { | |
// 计算目标位置的滚动偏移 | |
for (let i = 0; i < targetIndex; i++) { | |
targetScrollTop += heightCache[i] || itemHeight | |
} | |
} | |
// 设置DOM元素的滚动位置 | |
containerRef.current.scrollTop = targetScrollTop | |
// 更新ref值 | |
scrollTopRef.current = targetScrollTop | |
// 触发状态更新以重新渲染 | |
setScrollTopState(targetScrollTop) | |
} | |
} |
🤖 Prompt for AI Agents
In src/packages/table/virtual/virtual-scroll.ts around lines 206 to 229,
scrollTo can compute targetIndex as Math.min(Math.max(0, index),
totalRef.current - 1) which becomes -1 when totalRef.current is 0 and leads to a
negative scrollTop; fix by early-returning when totalRef.current <= 0 (or set
targetIndex = 0 if you prefer no-op), ensuring you never use totalRef.current -
1 when total is zero, and keep the rest of the logic unchanged (this prevents
the for-loop and scrollTop calculations from running with an invalid negative
targetIndex).
sorterIcon?: (currentSortState: SortStateType) => ReactNode | ||
sorter?: ((a: any, b: any) => number) | boolean | string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
列级 sorterIcon 为函数,但表格级 sorterIcon 仍为 ReactNode,API 不一致
建议统一两处 API 形态,表格级也支持函数形式作为默认渲染器,避免歧义与重复命名造成的困惑。
针对列级命名,也可考虑改为 renderSorterIcon 以与节点型 props 区分(可选)。
- sorterIcon?: (currentSortState: SortStateType) => ReactNode
+ sorterIcon?: (currentSortState: SortStateType) => ReactNode
+ // 可选:rename 为 renderSorterIcon 以避免与表格级 sorterIcon 混淆
同时,建议调整 BaseTable.sorterIcon 的类型(该行不在本次变更范围,示意如下):
- sorterIcon?: ReactNode
+ sorterIcon?: ReactNode | ((currentSortState: SortStateType) => ReactNode)
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
sorterIcon?: (currentSortState: SortStateType) => ReactNode | |
sorter?: ((a: any, b: any) => number) | boolean | string | |
sorterIcon?: (currentSortState: SortStateType) => ReactNode | |
// 可选:rename 为 renderSorterIcon 以避免与表格级 sorterIcon 混淆 | |
sorter?: ((a: any, b: any) => number) | boolean | string |
🤖 Prompt for AI Agents
In src/types/spec/table/base.ts around lines 9 to 10, the column-level
sorterIcon is typed as a function (currentSortState: SortStateType) => ReactNode
while the table-level sorterIcon remains a ReactNode, causing an inconsistent
API; update the table-level sorterIcon type to also accept a function signature
(currentSortState: SortStateType) => ReactNode as the default renderer so both
levels share the same shape, and optionally consider renaming the column prop to
renderSorterIcon to distinguish render-function props from node props (ensure
any references/types elsewhere are updated accordingly).
🤔 这个变动的性质是?
🔗 相关 Issue
💡 需求背景和解决方案
☑️ 请求合并前的自查清单
Summary by CodeRabbit