Skip to content

Conversation

irisSong
Copy link
Collaborator

@irisSong irisSong commented Apr 29, 2025

🤔 这个变动的性质是?

  • 新特性提交
  • 日常 bug 修复
  • 站点、文档改进
  • 演示代码改进
  • 组件样式/交互改进
  • TypeScript 定义更新
  • 包体积优化
  • 性能优化
  • 功能增强
  • 国际化改进
  • 重构
  • 代码风格优化
  • 测试用例
  • 分支合并
  • 其他改动(是关于什么的改动?)

🔗 相关 Issue

💡 需求背景和解决方案

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

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

  • 文档已补充或无须补充
  • 代码演示已提供或无须提供
  • TypeScript 定义已补充或无须补充
  • fork仓库代码是否为最新避免文件冲突
  • Files changed 没有 package.json lock 等无关文件

Summary by CodeRabbit

  • 新功能

    • Elevator 组件新增支持水平(horizontal)和垂直(vertical)两种模式,可通过 mode 属性切换,默认水平模式。
    • 垂直模式下支持吸顶标题,布局和滚动行为有明显区别。
    • Elevator 组件支持受控与非受控的当前项状态管理,增强灵活性。
  • 样式

    • Elevator 组件整体样式大幅优化,采用 flex 布局,提升响应性和视觉一致性。
    • 调整字体、间距、颜色等变量,优化分组条与列表项的展示效果,新增模式修饰类。
    • 优化分组条的高亮和选中效果,提升可用性。
    • 新增多个样式变量,增强定制能力。
  • 文档

    • Elevator 组件文档更新:spaceHeight 默认值由 23 改为 18,移除 titleHeight 属性,完善中英文及 Taro 版文档说明。
    • 新增 mode 属性说明,明确默认值及可选项。
  • 演示与示例

    • 所有演示数据结构调整,分组和省份数据更丰富,部分示例切换为垂直模式,演示内容更贴合实际场景。
    • 优化演示组件的包裹结构和图标展示顺序,提升视觉体验。
    • 移除多余的包裹元素,简化结构。
  • 测试

    • 扩展 Elevator 组件测试覆盖,新增多种模式、交互行为及边界情况测试,提升稳定性和可靠性。

Copy link

coderabbitai bot commented Apr 29, 2025

## Walkthrough

本次变更为 Elevator 组件及其相关演示、样式和文档带来了多项更新。Elevator 组件新增了 `mode` 属性(支持 `horizontal``vertical`),并调整了 sticky 头部的渲染逻辑和子项结构,支持不同布局模式。样式(SCSS 和变量)针对新布局做了大量调整,优化了字体、间距、颜色等。文档移除了 `titleHeight` 属性,调整了 `spaceHeight` 默认值。多个演示 demo 的数据结构和渲染方式也做了相应扩展和优化。

## Changes

| 文件/路径分组 | 变更摘要 |
|---|---|
| `src/packages/elevator/elevator.tsx`, `src/packages/elevator/elevator.taro.tsx` | Elevator 组件新增 `mode` 属性(支持 horizontal/vertical),默认值为 horizontal,调整 sticky 头部渲染与子项结构,优化滚动与高度计算逻辑。 |
| `src/packages/elevator/elevator.scss`, `src/styles/variables.scss`, `src/styles/variables-jmapp.scss`, `src/styles/variables-jrkf.scss` | Elevator 相关样式大幅调整,采用 flex 布局,优化字体、间距、颜色,新增/移除部分变量,支持横向/纵向模式。 |
| `src/packages/elevator/demo.tsx` | 移除每个 Demo 组件外层的白色背景 div,Demo 直接渲染。 |
| `src/packages/elevator/demos/h5/demo1.tsx`, `src/packages/elevator/demos/h5/demo3.tsx`, `src/packages/elevator/demos/h5/demo5.tsx`, `src/packages/elevator/demos/taro/demo1.tsx`, `src/packages/elevator/demos/taro/demo3.tsx`, `src/packages/elevator/demos/taro/demo5.tsx` | Demo 数据结构扩展,分组及省份条目数量增加,ID 与排序调整,部分 Demo 渲染顺序和样式优化。 |
| `src/packages/elevator/demos/h5/demo4.tsx`, `src/packages/elevator/demos/taro/demo4.tsx` | Elevator 组件新增 `mode="vertical"` 属性,调整为纵向模式。 |
| `src/packages/elevator/doc.md`, `src/packages/elevator/doc.en-US.md`, `src/packages/elevator/doc.taro.md`, `src/packages/elevator/doc.zh-TW.md` | 文档移除 `titleHeight` 属性,`spaceHeight` 默认值由 23 改为 18,新增 `mode` 属性说明,调整多个 CSS 变量默认值。 |
| `src/types/spec/elevator/base.ts` | 新增 `ElevatorMode` 类型及 `mode` 属性定义,支持 horizontal 和 vertical 两种模式。 |
| `src/sites/sites-react/doc/docs/react/migrate-from-v2.md`, `src/sites/sites-react/doc/docs/taro/migrate-from-v2.md` | 更新迁移文档,新增 `mode` 属性说明,移除 `titleHeight` 属性说明。 |
| `src/packages/elevator/index.ts`, `src/packages/elevator/index.taro.ts` | 类型导出由本地类型改为从外部 `@/types` 引入对应的 `WebElevatorProps``TaroElevatorProps`,并重命名为 `ElevatorProps`|
| `src/packages/elevator/__tests__/elevator.spec.tsx` | 大幅扩展测试覆盖,新增多种模式、交互、边界情况测试,移除未使用的回调函数。 |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant 用户
    participant Elevator组件
    participant 样式系统

    用户->>Elevator组件: 传入 mode (horizontal/vertical)
    Elevator组件->>Elevator组件: 根据 mode 切换布局与 sticky 头部渲染
    Elevator组件->>样式系统: 应用对应的 CSS 类和变量
    样式系统-->>Elevator组件: 返回对应布局样式
    Elevator组件-->>用户: 渲染横向或纵向电梯列表

Possibly related PRs

  • fix(elevator): harmony适配 #2836: 修改了 Elevator 组件的样式,增加了固定列表项的嵌套类 .nut-elevator-list-fixed-title,与本次样式重构相关但侧重点不同。

Suggested reviewers

  • xiaoyatong
  • oasis-cloud

Poem

🐇
电梯组件添新装,
横竖切换样式忙。
省份分组更丰富,
滑动体验更流畅。
文档跟进做减法,
小兔点赞乐开花!


<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

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.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=jdf2e/nutui-react&utm_content=3210):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

@github-actions github-actions bot added 3.x Target branch 3.x action:review This PR needs more reviews (less than 2 approvals) labels Apr 29, 2025
Copy link

codecov bot commented Apr 29, 2025

Codecov Report

Attention: Patch coverage is 75.00000% with 9 lines in your changes missing coverage. Please review.

Project coverage is 86.87%. Comparing base (9499ec7) to head (ee4d188).
Report is 20 commits behind head on feat_v3.x.

Files with missing lines Patch % Lines
src/packages/elevator/elevator.tsx 75.00% 9 Missing ⚠️

❌ Your patch status has failed because the patch coverage (75.00%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@              Coverage Diff              @@
##           feat_v3.x    #3210      +/-   ##
=============================================
+ Coverage      86.70%   86.87%   +0.16%     
=============================================
  Files            291      291              
  Lines          19062    19071       +9     
  Branches        2891     2899       +8     
=============================================
+ Hits           16528    16567      +39     
+ Misses          2529     2499      -30     
  Partials           5        5              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🔭 Outside diff range comments (2)
src/packages/elevator/doc.md (1)

57-67: 🛠️ Refactor suggestion

缺少新增的 mode 属性文档

文档中移除了 titleHeight 属性,并更新了 spaceHeight 默认值,但未添加新增的 mode 属性文档。根据代码实现和演示示例,应添加 mode 属性说明。

建议在属性表中添加以下内容:

| sticky | 索引是否吸顶 | `boolean` | `false` |
| showKeys | 展示右侧导航 | `boolean` | `true` |
+| mode | 布局模式 | `string` | `'horizontal'` |
| spaceHeight | 右侧锚点的上下间距 | `number` | `18` |
src/packages/elevator/doc.taro.md (1)

57-67: 🛠️ Refactor suggestion

缺少新增的 mode 属性文档

文档中移除了 titleHeight 属性,并更新了 spaceHeight 默认值,但未添加新增的 mode 属性文档。根据代码实现和演示示例,应添加 mode 属性说明。

建议在属性表中添加以下内容:

| sticky | 索引是否吸顶 | `boolean` | `false` |
| showKeys | 展示右侧导航 | `boolean` | `true` |
+| mode | 布局模式 | `string` | `'horizontal'` |
| spaceHeight | 右侧锚点的上下间距 | `number` | `18` |
🧹 Nitpick comments (9)
src/packages/elevator/demos/h5/demo3.tsx (2)

28-42: 数据结构已更新,但存在重复条目

'G' 分组数据已更新,将原来的 "广西" 改为 "甘肃",并添加了新条目。但请注意,在修改后的数据中出现了两个相同名称 "广东" 的条目(id 32 和 33)。虽然这在技术上是允许的,但可能会让查看示例的用户感到困惑。

建议考虑使用不同的名称,以更好地展示组件的功能。

        {
          name: '甘肃',
          id: 31,
        },
        {
          name: '广东',
          id: 32,
        },
        {
-          name: '广东',
+          name: '广西',
          id: 33,
        },
        {
          name: '贵州',
          id: 34,
        },

49-67: 'H' 分组中存在重复条目

在 'H' 分组中,同样存在重复的条目名称("湖南"和"湖北"各出现两次)。虽然使用相同名称但不同 id 的方式可以测试组件的某些特性,但这可能不是一个理想的示例展示方式。

建议考虑使用更具差异性的数据来展示组件功能。

        {
          name: '湖南',
          id: 41,
        },
        {
          name: '湖北',
          id: 42,
        },
        {
-          name: '湖北',
+          name: '河北',
          id: 43,
        },
        {
-          name: '湖南',
+          name: '河南',
          id: 44,
        },
        {
          name: '海南',
          id: 45,
        },
src/packages/elevator/demos/h5/demo5.tsx (3)

29-44: 重复数据可能导致混淆

list 中出现了两条「广东」记录(id 分别为 32、33),名称相同但 id 不同。
如果组件逻辑依赖 id 唯一性,UI 层面显示则依赖名称可区分度,重复项可能给使用者带来困惑,甚至导致点击回调拿到“错误”的省份。

如无业务需求,建议去重或在名称中增加区分信息。
如果确需展示重复名称,请在文档或示例注释中说明原因,避免后续维护者误以为是疏漏。


49-70: 同名省份大量重复,需确认意图

在 “H” 分组里,「湖南」与「湖北」均出现两次;
新增的「海南」与前两者同属 H 组但排序置于最后,排序规则不一致。
请确认:

  1. 是否确实需要同名多条记录。
  2. 是否有意按 id 而非拼音/名称排序。
  3. 是否考虑保持地域顺序一致性(例如 “海南” 理应位于 “湖北” 之前)。

如仅为演示效果,可保留,但建议在 demo 顶部添加注释阐明,减少误解。


111-113: 无障碍与语义化建议

图标现在位于文字之后并带有 margin-left: 4px,视觉和交互都 OK。
<Fabulous> 组件若纯装饰作用,应添加 aria-hidden="true"role="presentation",避免屏幕阅读器重复朗读。
此外,行内样式可改为 className 统一维护,提高可维护性。

src/packages/elevator/elevator.tsx (1)

202-208: className 拼接可能产生 'undefined'

根节点 className 写法为

`${classPrefix} ${classPrefix}-${mode} ${className}`

当外部未传入 className 时会得到字符串 "nut-elevator nut-elevator-horizontal undefined",影响样式调试。

建议使用 classNames 工具或条件拼接:

- className={`${classPrefix} ${classPrefix}-${mode} ${className}`}
+ className={classNames(classPrefix, `${classPrefix}-${mode}`, className)}
src/packages/elevator/elevator.taro.tsx (1)

224-225: 根节点 className 多余空格

字符串模板拼接两端多出空格,最终渲染结果形如 "nut-elevator custom nut-elevator-horizontal "
虽不影响功能,但会增加样式排查成本,可改为:

-className={`${classPrefix} ${className} ${classPrefix}-${mode} `}
+className={classNames(classPrefix, `${classPrefix}-${mode}`, className)}
src/packages/elevator/elevator.scss (2)

95-107: 优化点位导航项样式
&-bars-inner-item 使用 inline-flex 和一系列硬编码尺寸(16px),建议考虑将尺寸提取为 SCSS 变量,以便未来主题定制和统一管理。


129-137: 纵向模式专属样式
.nut-elevator-vertical 下设置 flex-direction:column、分割线和统一内边距,建议后续将公共 padding 合并到基础类以减少重复。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc1f644 and 56a2ed5.

📒 Files selected for processing (17)
  • src/packages/elevator/demo.tsx (1 hunks)
  • src/packages/elevator/demos/h5/demo1.tsx (2 hunks)
  • src/packages/elevator/demos/h5/demo3.tsx (2 hunks)
  • src/packages/elevator/demos/h5/demo4.tsx (1 hunks)
  • src/packages/elevator/demos/h5/demo5.tsx (3 hunks)
  • src/packages/elevator/demos/taro/demo1.tsx (2 hunks)
  • src/packages/elevator/demos/taro/demo3.tsx (2 hunks)
  • src/packages/elevator/demos/taro/demo4.tsx (1 hunks)
  • src/packages/elevator/demos/taro/demo5.tsx (4 hunks)
  • src/packages/elevator/doc.en-US.md (1 hunks)
  • src/packages/elevator/doc.md (1 hunks)
  • src/packages/elevator/doc.taro.md (1 hunks)
  • src/packages/elevator/doc.zh-TW.md (1 hunks)
  • src/packages/elevator/elevator.scss (3 hunks)
  • src/packages/elevator/elevator.taro.tsx (9 hunks)
  • src/packages/elevator/elevator.tsx (5 hunks)
  • src/styles/variables.scss (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/packages/elevator/elevator.tsx (1)
src/utils/typings.ts (1)
  • ComponentDefaults (10-13)
src/packages/elevator/elevator.taro.tsx (2)
src/packages/elevator/elevator.tsx (3)
  • elevatorContext (13-13)
  • ElevatorProps (15-25)
  • Elevator (41-297)
src/utils/typings.ts (2)
  • BasicComponent (3-8)
  • ComponentDefaults (10-13)
🪛 Biome (1.9.4)
src/packages/elevator/elevator.taro.tsx

[error] 108-108: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (40)
src/packages/elevator/demos/taro/demo4.tsx (1)

128-128: 添加了垂直模式支持,符合新功能设计

添加的 mode="vertical" 属性使组件以垂直布局模式展示,这与 Elevator 组件的新功能设计一致。同时移除了之前的 className="test-elevator4" 属性,简化了代码结构。

src/packages/elevator/doc.md (1)

64-64: 文档中 spaceHeight 属性默认值已更新

spaceHeight 默认值从 23 更新为 18,与代码实现保持一致。

src/packages/elevator/demo.tsx (1)

37-47: 简化了演示组件结构

移除了每个演示组件外层的白色背景容器,直接渲染 Demo 组件,使整体结构更加简洁。这种变更有助于更好地展示新增的垂直模式和水平模式布局差异。

src/packages/elevator/doc.taro.md (1)

64-64: 文档中 spaceHeight 属性默认值已更新

spaceHeight 默认值从 23 更新为 18,与代码实现保持一致。

src/packages/elevator/demos/h5/demo4.tsx (1)

128-128: 正确使用了新增的 mode 属性

这里正确地使用了新增的 mode="vertical" 属性,使示例展示了电梯组件的垂直模式。这与组件的新特性一致,有助于用户理解不同布局模式的使用方法。

src/packages/elevator/demos/h5/demo3.tsx (1)

70-78: 增加了新的分组,丰富了示例数据

添加了 'L' 分组及其条目,这有助于丰富示例并更全面地测试组件功能,是一个良好的改进。

src/packages/elevator/demos/taro/demo3.tsx (4)

33-42: 数据结构中更新了 'G' 组的省份 ID 和内容

'G' 组中的省份结构已更新,新增了两个广东条目(ID 32 和 33)和一个贵州条目(ID 34)。这些更改与新版本的电梯组件布局模式相匹配。


50-67: 'H' 组数据结构更新,扩展了省份列表

'H' 组中的湖南和湖北的 ID 已更新(41 和 42),并增加了额外的湖北、湖南和新的海南条目。这种数据扩展支持展示更丰富的内容,更好地测试组件在不同数据量下的表现。


71-76: 新增 'L' 组数据

添加了新的 'L' 组,包含辽宁省。这进一步丰富了演示数据,有助于测试组件处理更多分组的能力。


88-94: 移除了测试类名属性

注意到组件调用中移除了 className="test-elevator3" 属性。这与其他 Taro 演示文件中类似的更改一致,使代码更加简洁。

src/packages/elevator/demos/h5/demo1.tsx (4)

28-42: 'G' 组数据结构完全重组

'G' 组中的省份数据已完全重组,"广西"被"甘肃"(ID 31)替换,并添加了多个"广东"条目(ID 32、33)和"贵州"(ID 34)。这种更新与 Taro 演示中的变更保持一致,确保了跨平台的数据一致性。


50-67: 'H' 组数据扩展和 ID 更新

'H' 组中的省份数据进行了扩展,湖南和湖北的 ID 已更新(41 和 42),并新增了多个条目。这种变更增强了演示的数据丰富度。


71-78: 新增 'L' 组数据

添加了新的 'L' 组数据,包含辽宁省。这种新增的数据组更好地测试组件对多种分组的处理能力。


80-97: 新增并扩展了 'S' 组数据

新增了 'S' 组,包含山东、山西、上海和陕西多个省市。这一变更与 Taro 演示保持一致,提供了更全面的测试数据。

src/packages/elevator/demos/taro/demo1.tsx (2)

28-42: 'G' 组数据结构全面更新

'G' 组的数据结构已全面更新,使用"甘肃"(ID 31)替换了原来的"广西",并添加了多个"广东"条目和"贵州"。这种数据更新与 H5 版本的 demo1 保持一致,确保了跨平台体验的一致性。


50-67: 'H' 组数据扩展和重组

'H' 组的数据已经扩展和重组,包括更新的 ID 和新增的条目。这些变更提供了更丰富的演示数据。

src/packages/elevator/demos/taro/demo5.tsx (4)

4-4: 新增 Text 组件导入

增加了 Text 组件的导入,用于改进内容渲染结构。这与组件新的布局模式要求一致。


30-44: 'G' 组数据更新和扩展

'G' 组数据已更新,用"甘肃"替换了"广西",并添加了新条目。这与其他演示文件中的变更保持一致。


52-69: 'H' 组数据扩展

'H' 组数据已扩展,包括更新的 ID 和新增的条目。这种变更与其他演示文件一致。


113-114: 优化了子元素渲染顺序和样式

更改了 Elevator.Context.Consumer 中子元素的渲染顺序,现在先渲染文本内容,然后是图标,并调整了图标的边距和高度。这种调整更符合常见的文本+图标布局习惯,提高了可读性。

-              <Fabulous style={{ marginLeft: '15px' }} size={12} />
-              <span style={{ marginLeft: '15px' }}>{value?.name}</span>
+              <Text>{value?.name}</Text>
+              <Fabulous style={{ marginLeft: '4px' }} height={12} />
src/packages/elevator/elevator.tsx (1)

226-252: 子列表增加包裹节点后样式需同步

新增 .nut-elevator-list-item-sublist 容器后,原有 SCSS 若未同步更新,可能导致:

  • Flex / margin 失效,子项换行异常
  • 旧选择器 .nut-elevator-list-item > * 覆盖不到新结构

请确认 elevator.scss 已加入对应样式。

src/packages/elevator/elevator.taro.tsx (1)

123-147: await calculateHeight() 实际未生效

受上条问题影响,这里的 await 不会等待高度数组准备完成,仍存在取不到高度的竞态条件。
修复 calculateHeight 返回值后,此处逻辑即可正常工作;请务必同时将 scrollTo 的错误边界判断与 listHeight 写入逻辑自测一遍。

src/packages/elevator/elevator.scss (10)

3-4: 新增主容器 Flex 布局
此处将 .nut-elevator 设置为 display:flexwidth:100%,符合水平/垂直模式的通用布局要求。


9-10: 优化列表容器 Flex 布局
&-list 采用 display:flex; width:100% 以撑满父容器,适配横纵两种模式。


14-17: 配置列表内部子容器 Flex Column
&-list-inner 使用 flex-direction:columnmin-height:100% 以保证滚动区域完整性和 Sticky 头部体验。


23-27: 添加子列表 Flex 布局
&-list-item 及其子 &-sublist 使用 display:flexflex-direction:column,结构清晰,可考虑在后续 demo 中验证多层嵌套效果。


30-31: 对码元居中对齐
&-code 添加 align-items:center 保证左侧字母/数字垂直居中,体验更好。


41-42: 统一列表名称字体和颜色
&-namefont-sizecolor 已对齐到变量设置,无需修改。


55-63: 固定标题增强可读性
&-list-fixed 设置 display:flex; align-items:center; width:100%; background-color,提升 Sticky 标题在两种模式下的表现。


76-84: 当前码元高亮样式
&-code-currentdisplay:flex; align-items:center; justify-content:center 保证高亮圈内文本水平垂直居中,并设置 color


111-116: 平台条件样式无误
&-bars-inner-item-active 根据 weapp jd 和非 JD 平台分别使用渐变或纯色,确保跨平台一致性。


121-126: 横向模式专属样式
.nut-elevator-horizontal 下调整 .nut-elevator-list-item-code 宽度并居中,布局逻辑清晰。

src/styles/variables.scss (8)

2229-2229: 设置列表字体大小
$elevator-list-font-size 默认使用 $font-size-base,与主题变量一致。


2233-2234: 更新列表项内边距
$elevator-list-item-padding 调整为 0 36px 0 20px,满足横纵模式下的视觉间距。


2237-2238: 调整名称行高
$elevator-list-item-name-height 更新为 34px,与子项对齐后视觉更平衡。


2254-2255: 同步码元高度
$elevator-list-item-code-height 修改为 34px,保证与名称行高一致。


2257-2259: 新增码元宽度变量
$elevator-list-item-code-width 设置为 34px,与高度匹配正方形外观。


2304-2306: 调整导航字体尺寸
$elevator-bars-font-size 使用 $font-size-xxs,与视觉稿匹配。


2307-2309: 更新导航默认颜色
$elevator-bars-color 设置为 $color-text-help,确保默认状态颜色弱化。


2311-2313: 更新导航激活颜色
$elevator-bars-active-color 改为 $white,结合高亮背景更具对比度。

Comment on lines +51 to 97
},
{
name: '湖北',
id: 42,
},
{
name: '湖北',
id: 6,
id: 43,
},
{
name: '湖南',
id: 44,
},
{
name: '海南',
id: 45,
},
],
},
{
title: 'L',
list: [
{
name: '辽宁',
id: 51,
},
],
},
{
title: 'S',
list: [
{
name: '山东',
id: 51,
},
{
name: '山西',
id: 52,
},
{
name: '上海',
id: 53,
},
{
name: '河南',
id: 7,
name: '陕西',
id: 54,
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

需注意 ID 重复问题

在数据结构中发现了重复的 ID:山东(ID 51)和辽宁(ID 51)使用了相同的 ID 值。这可能在某些操作中导致意外行为,例如在点击回调中通过 ID 识别具体项目时。

建议修复重复的 ID 问题:

-          id: 51,
+          id: 61,

🏁 Script executed:

#!/bin/bash
# 检查所有 demo 文件中是否存在重复的 ID
grep -r "id: 51" src/packages/elevator/demos/

Length of output: 527


需修复:多处演示文件中存在 ID 重复

根据检查结果,id: 51 在以下文件中出现多次,会导致通过 ID 定位项目时产生歧义,请统一调整为唯一值(可参考 GB/T 2260 省级行政区划代码或按业务需求自定义):

  • src/packages/elevator/demos/h5/demo1.tsx (2 处)
  • src/packages/elevator/demos/h5/demo3.tsx
  • src/packages/elevator/demos/h5/demo5.tsx
  • src/packages/elevator/demos/taro/demo1.tsx (2 处)
  • src/packages/elevator/demos/taro/demo3.tsx
  • src/packages/elevator/demos/taro/demo5.tsx

示例修复(以 h5/demo1.tsx 为例):

--- a/src/packages/elevator/demos/h5/demo1.tsx
@@ -55,7 +55,7 @@
     {
       title: 'L',
       list: [
-        { name: '辽宁', id: 51 },
+        { name: '辽宁', id: 21 }, // 官方省份编码:辽宁 = 21
       ],
     },
     {
@@ -62,7 +62,7 @@
     {
       title: 'S',
       list: [
-        { name: '山东', id: 51 },
+        { name: '山东', id: 37 }, // 官方省份编码:山东 = 37
         { name: '山西', id: 52 },
         { name: '上海', id: 53 },
         { name: '陕西', id: 54 },
📝 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.

Suggested change
},
{
name: '湖北',
id: 42,
},
{
name: '湖北',
id: 6,
id: 43,
},
{
name: '湖南',
id: 44,
},
{
name: '海南',
id: 45,
},
],
},
{
title: 'L',
list: [
{
name: '辽宁',
id: 51,
},
],
},
{
title: 'S',
list: [
{
name: '山东',
id: 51,
},
{
name: '山西',
id: 52,
},
{
name: '上海',
id: 53,
},
{
name: '河南',
id: 7,
name: '陕西',
id: 54,
},
{
title: 'L',
list: [
{
name: '辽宁',
id: 21, // 官方省份编码:辽宁 = 21
},
],
},
{
title: 'S',
list: [
{
name: '山东',
id: 37, // 官方省份编码:山东 = 37
},
{
name: '山西',
id: 52,
},
{
name: '上海',
id: 53,
},
{
name: '陕西',
id: 54,
},
],
},

Comment on lines +71 to 97
title: 'L',
list: [
{
name: '辽宁',
id: 51,
},
],
},
{
title: 'S',
list: [
{
name: '山东',
id: 51,
},
{
name: '山西',
id: 52,
},
{
name: '上海',
id: 53,
},
{
name: '河南',
id: 7,
name: '陕西',
id: 54,
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

新增 'L' 和 'S' 组数据

新增了 'L' 和 'S' 组数据,包含多个省市。这一变更与 H5 演示保持一致,但存在与 H5 demo1 相同的 ID 重复问题。

山东(ID 51)和辽宁(ID 51)使用了相同的 ID,这可能导致在回调函数中难以区分这两个不同的条目。建议修复:

-          id: 51,
+          id: 61,
📝 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.

Suggested change
title: 'L',
list: [
{
name: '辽宁',
id: 51,
},
],
},
{
title: 'S',
list: [
{
name: '山东',
id: 51,
},
{
name: '山西',
id: 52,
},
{
name: '上海',
id: 53,
},
{
name: '河南',
id: 7,
name: '陕西',
id: 54,
},
title: 'L',
list: [
{
name: '辽宁',
id: 51,
},
],
},
{
title: 'S',
list: [
{
name: '山东',
id: 61,
},
{
name: '山西',
id: 52,
},
{
name: '上海',
id: 53,
},
{
name: '陕西',
id: 54,
},
],
},

Comment on lines +73 to 90
title: 'S',
list: [
{
name: '山东',
id: 51,
},
{
name: '山西',
id: 52,
},
{
name: '上海',
id: 53,
},
{
name: '河南',
id: 7,
name: '陕西',
id: 54,
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

新增 'S' 组数据

新增了 'S' 组,包含多个省市。这与其他演示文件中的变更保持一致,但同样存在 ID 重复问题。

山东使用了与辽宁相同的 ID(51),建议修复:

-          id: 51,
+          id: 61,
📝 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.

Suggested change
title: 'S',
list: [
{
name: '山东',
id: 51,
},
{
name: '山西',
id: 52,
},
{
name: '上海',
id: 53,
},
{
name: '河南',
id: 7,
name: '陕西',
id: 54,
},
title: 'S',
list: [
{
name: '山东',
- id: 51,
+ id: 61,
},
{
name: '山西',
id: 52,
},
{
name: '上海',
id: 53,
},
{
name: '陕西',
id: 54,
},
],

Comment on lines 202 to +208
return (
<div className={`${classPrefix} ${className}`} style={style} {...rest}>
{sticky && scrollY > 0 ? (
<div
className={`${classPrefix} ${classPrefix}-${mode} ${className}`}
style={style}
{...rest}
>
{mode === 'vertical' && sticky && scrollY > 0 ? (
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

未移除 scroll 事件监听,存在内存泄漏风险

useEffect 中向 listview.current 绑定了 scroll 事件,但在组件卸载或 listview.current 变更时没有执行 removeEventListener
这会导致:

  1. 组件多次挂载/卸载后事件叠加,性能下降。
  2. 某些场景下产生无法释放的闭包引用。

建议在 useEffect 返回清理函数,同时依赖数组使用 [](ref 对象本身不会变化):

-  useEffect(() => {
-    if (listview.current) {
-      setListGroup()
-      listview.current.addEventListener('scroll', listViewScroll)
-    }
-  }, [listview])
+  useEffect(() => {
+    if (listview.current) {
+      setListGroup()
+      const current = listview.current
+      current.addEventListener('scroll', listViewScroll)
+      return () => {
+        current.removeEventListener('scroll', listViewScroll)
+      }
+    }
+  }, [])

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 183 to +207
const listViewScroll = (e: any) => {
const { listHeight } = state.current
if (!listHeight.length) {
calculateHeight()
}
const target = e.target as Element
const { scrollTop } = target
state.current.scrollY = Math.floor(scrollTop)
Taro.getEnv() === 'WEB' && setScrollTop(scrollTop)
if (sticky && scrollTop !== scrollY) {
setScrollY(Math.floor(scrollTop) > 0 ? 1 : 0)
}
if (scrolling.current) return
for (let i = 0; i < listHeight.length - 1; i++) {
const height1 = listHeight[i]
const height2 = listHeight[i + 1]
if (state.current.scrollY >= height1 && state.current.scrollY < height2) {
return setCodeIndex(i)
raf(() => {
const { listHeight } = state.current
if (!listHeight.length) {
calculateHeight()
}
}
const target = e.target as Element
const { scrollTop } = target
state.current.scrollY = Math.floor(scrollTop)
Taro.getEnv() === 'WEB' && setScrollTop(scrollTop)
if (mode === 'vertical' && sticky) {
setScrollY(Math.floor(scrollTop) > 0 ? 1 : 0)
}
if (scrolling.current) return

const index = listHeight.findIndex(
(height, i) =>
state.current.scrollY >= height &&
state.current.scrollY < (listHeight[i + 1] || Infinity)
)

if (index !== -1 && index !== codeIndex) {
setCodeIndex(index)
}
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

滚动节流逻辑仍可能读取过期数据

raf 回调里若 state.current.listHeight 为空时触发 calculateHeight(),同样由于异步执行,在后续 findIndex 判断前未必完成写入,可能返回 -1
可在 calculateHeight 完成后再执行后续逻辑,或在获取高度失败时提前 return,以保证索引计算准确。

Comment on lines +101 to 121
state.current.listHeight = [0]

state.current.listHeight.push(height)
for (let i = 0; i < state.current.listGroup.length; i++) {
const query = createSelectorQuery()
query
.selectAll(`.${classPrefix}-${uuid} .nut-elevator-item-${i}`)
.boundingClientRect()
// eslint-disable-next-line no-loop-func
query.exec((res: any) => {
if (res[0][0]) height += res[0][0].height
state.current.listHeight.push(height)
const query = createSelectorQuery()
query
.selectAll(`#${classPrefix}-${uuid} .${classPrefix}-list-item`)
.boundingClientRect()
.exec((rect = []) => {
if (rect[0] && rect[0].length) {
// rect[0] = rect[0].reverse()
state.current.listHeight = rect[0].reduce(
(acc: any[], item: { height: any }, index: number) => {
// 当前项的高度等于前面所有项的高度之和
const height = acc[index] + item.height
acc.push(height)
return acc
},
[0]
)
}
})
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

calculateHeight 为异步回调,但未返回 Promise

函数内部 createSelectorQuery().exec() 异步回调完成后才写入 state.current.listHeight
然而函数本身没有 return Promise,导致调用方无法感知完成时机。

立即在下方 await calculateHeight() 等待并不会真正阻塞执行,scrollTo 里仍然可能读取到空数组,出现定位错误。

建议改造成返回 Promise<void>,示例 diff 如下:

- const calculateHeight = () => {
-   state.current.listHeight = [0]
-   const query = createSelectorQuery()
-   ...
-   .exec((rect = []) => {
-     ...
-   })
- }
+ const calculateHeight = (): Promise<void> =>
+   new Promise((resolve) => {
+     state.current.listHeight = [0]
+     createSelectorQuery()
+       .selectAll(`#${classPrefix}-${uuid} .${classPrefix}-list-item`)
+       .boundingClientRect()
+       .exec((rect = []) => {
+         if (rect[0]?.length) {
+           state.current.listHeight = rect[0].reduce(
+             (acc: number[], item: { height: number }, idx: number) => {
+               acc.push(acc[idx] + item.height)
+               return acc
+             },
+             [0]
+           )
+         }
+         resolve()
+       })
+   })
📝 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.

Suggested change
state.current.listHeight = [0]
state.current.listHeight.push(height)
for (let i = 0; i < state.current.listGroup.length; i++) {
const query = createSelectorQuery()
query
.selectAll(`.${classPrefix}-${uuid} .nut-elevator-item-${i}`)
.boundingClientRect()
// eslint-disable-next-line no-loop-func
query.exec((res: any) => {
if (res[0][0]) height += res[0][0].height
state.current.listHeight.push(height)
const query = createSelectorQuery()
query
.selectAll(`#${classPrefix}-${uuid} .${classPrefix}-list-item`)
.boundingClientRect()
.exec((rect = []) => {
if (rect[0] && rect[0].length) {
// rect[0] = rect[0].reverse()
state.current.listHeight = rect[0].reduce(
(acc: any[], item: { height: any }, index: number) => {
// 当前项的高度等于前面所有项的高度之和
const height = acc[index] + item.height
acc.push(height)
return acc
},
[0]
)
}
})
}
}
const calculateHeight = (): Promise<void> =>
new Promise((resolve) => {
state.current.listHeight = [0]
createSelectorQuery()
.selectAll(`#${classPrefix}-${uuid} .${classPrefix}-list-item`)
.boundingClientRect()
.exec((rect = []) => {
if (rect[0]?.length) {
state.current.listHeight = rect[0].reduce(
(acc: number[], item: { height: number }, idx: number) => {
acc.push(acc[idx] + item.height)
return acc
},
[0]
)
}
resolve()
})
})
🧰 Tools
🪛 Biome (1.9.4)

[error] 108-108: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/packages/elevator/doc.md (3)

79-79: 建议补充内边距调整原因说明
--nutui-elevator-list-item-padding 从旧的值更新为 0 36px 0 20px,建议在文档中简要说明此调整是为适配横向/竖向模式下的布局需求,以便使用者快速理解。


80-80: 补充列表项高度修改背景信息
--nutui-elevator-list-item-name-height 默认值变更为 34px,建议在文档中标注此修改适配的场景(如文字大小或行高更新),以提高可维护性。


92-92: 确认导航栏定位变量的更新逻辑
--nutui-elevator-bars-right 默认值更新为 16px,建议检查在横、竖两种模式下,此定位值是否符合预期,并在文档中区分不同模式的定位差异。

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 56a2ed5 and 40830e7.

📒 Files selected for processing (4)
  • src/packages/elevator/doc.en-US.md (2 hunks)
  • src/packages/elevator/doc.md (2 hunks)
  • src/packages/elevator/doc.taro.md (2 hunks)
  • src/packages/elevator/doc.zh-TW.md (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/packages/elevator/doc.zh-TW.md
  • src/packages/elevator/doc.en-US.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/packages/elevator/doc.taro.md
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: test
🔇 Additional comments (5)
src/packages/elevator/doc.md (5)

64-64: 请确认 spaceHeight 默认值与组件实现保持一致并补充单位说明
文档中将 spaceHeight 默认值调整为 18,建议检查 elevator.tsx 或相关类型声明里是否也同步更新为 18,并在文档中明确该值单位(如 px 或 数值对应的默认单位)。


77-77: 校验列表项字体大小变量的实际默认值
文档将 --nutui-elevator-list-font-size 的默认值改为 $font-size-base,请确认在 variables.scss 中该 CSS 变量的默认值是否已修改,并与文档保持一致。


86-86: 校验提示背景色变量的实际效果
文档将 --nutui-elevator-list-item-code-current-bg-color 默认值设置为 $color-text-disabled,请在组件样式中预览该配色在深色/浅色主题下的可读性,确保无色彩对比度问题。


95-95: 验证激活态文字颜色与主题一致性
--nutui-elevator-bars-active-color 的默认值改为 $white,请确认在深色背景或主题色背景下,该激活文字的可见性与设计规范是否一致。


98-98: 检查导航文字大小更新的合理性
文档将 --nutui-elevator-bars-font-size 调整为 $font-size-xxs,建议与设计稿或视觉规范对齐后再确认该字号是否适合各终端设备的可读性。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
src/packages/elevator/elevator.taro.tsx (1)

109-132: 🛠️ Refactor suggestion

适当处理异步高度计算

将scrollTo方法改为async并添加await calculateHeight()是一个好的改进,但由于calculateHeight没有返回Promise,这个await并不会真正阻塞执行。

另外,增加了基于mode的条件判断if (mode === 'vertical' && sticky)来控制滚动行为,这很好地支持了不同模式的差异化处理。

除了修复calculateHeight返回Promise外,建议在此方法中增加错误处理:

  const scrollTo = async (index: number) => {
    if (!index && index !== 0) {
      return
    }

    if (!state.current.listHeight.length) {
      await calculateHeight()
+     // 检查计算后的高度列表是否有效
+     if (!state.current.listHeight.length) {
+       console.warn('Failed to calculate list heights')
+       return
+     }
    }
    // 后续代码...
  }
♻️ Duplicate comments (2)
src/packages/elevator/elevator.taro.tsx (2)

86-107: ⚠️ Potential issue

优化高度计算方法

calculateHeight方法改进了列表高度的计算,通过一次性查询所有列表项并使用reduce计算累积高度,替代了之前逐项查询的方式,提高了性能。

但仍存在一个问题:该方法没有返回Promise,导致后续的await calculateHeight()不会真正起到等待作用。

建议将函数改造为返回Promise:

- const calculateHeight = () => {
+ const calculateHeight = (): Promise<void> => new Promise((resolve) => {
    state.current.listHeight = [0]
    const query = createSelectorQuery()
    query
      .selectAll(`#${classPrefix}-${uuid} .${classPrefix}-list-item`)
      .boundingClientRect()
      .exec((rect = []) => {
        if (rect[0] && rect[0].length) {
          state.current.listHeight = rect[0].reduce(
            (acc: any[], item: { height: any }, index: number) => {
              const height = acc[index] + item.height
              acc.push(height)
              return acc
            },
            [0]
          )
        }
+       resolve()
      })
+ })
🧰 Tools
🪛 Biome (1.9.4)

[error] 94-94: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


169-193: ⚠️ Potential issue

优化滚动处理逻辑

使用raf工具进行滚动节流是很好的性能优化,但当listHeight为空时触发calculateHeight()后,由于异步执行,在后续的findIndex判断前未必完成写入,可能返回-1

建议在获取高度失败时提前返回,以保证索引计算准确:

  const listViewScroll = (e: any) => {
    raf(() => {
      const { listHeight } = state.current
      if (!listHeight.length) {
        calculateHeight()
+       return // 等待下一帧再处理
      }
      
      // 后续代码...
      
      const index = listHeight.findIndex(
        (height, i) =>
          state.current.scrollY >= height &&
          state.current.scrollY < (listHeight[i + 1] || Infinity)
      )

      if (index !== -1 && index !== codeIndex) {
        setCodeIndex(index)
      }
    })
  }
🧹 Nitpick comments (1)
src/packages/elevator/elevator.taro.tsx (1)

94-94: 优化对象安全访问

使用可选链操作符可以简化代码并提高安全性。

- if (rect[0] && rect[0].length) {
+ if (rect[0]?.length) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 94-94: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40830e7 and 34a31ac.

📒 Files selected for processing (9)
  • src/packages/elevator/doc.en-US.md (2 hunks)
  • src/packages/elevator/doc.md (2 hunks)
  • src/packages/elevator/doc.taro.md (2 hunks)
  • src/packages/elevator/doc.zh-TW.md (2 hunks)
  • src/packages/elevator/elevator.taro.tsx (10 hunks)
  • src/packages/elevator/elevator.tsx (6 hunks)
  • src/sites/sites-react/doc/docs/react/migrate-from-v2.md (1 hunks)
  • src/sites/sites-react/doc/docs/taro/migrate-from-v2.md (1 hunks)
  • src/types/spec/elevator/base.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • src/sites/sites-react/doc/docs/taro/migrate-from-v2.md
  • src/sites/sites-react/doc/docs/react/migrate-from-v2.md
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/packages/elevator/doc.md
  • src/packages/elevator/doc.en-US.md
  • src/packages/elevator/elevator.tsx
  • src/packages/elevator/doc.taro.md
  • src/packages/elevator/doc.zh-TW.md
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/packages/elevator/elevator.taro.tsx (3)
src/packages/elevator/elevator.tsx (2)
  • elevatorContext (14-14)
  • Elevator (27-283)
src/types/spec/elevator/base.ts (1)
  • ElevatorItem (4-9)
src/utils/typings.ts (1)
  • ComponentDefaults (10-13)
🪛 Biome (1.9.4)
src/packages/elevator/elevator.taro.tsx

[error] 94-94: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (10)
src/types/spec/elevator/base.ts (2)

18-18: 添加ElevatorMode类型支持水平和垂直模式

这个新增的类型定义很好地支持了Elevator组件的布局模式扩展,明确限制了可选值为'horizontal'和'vertical'。


21-21: BaseElevator接口正确添加了mode属性

将mode属性添加到BaseElevator接口中,使其成为必填项,确保了类型安全和一致性。

src/packages/elevator/elevator.taro.tsx (8)

12-16: 导入必要的工具和类型

导入的工具和类型与组件需求一致,尤其是添加的raf工具可以优化滚动性能,ElevatorItemTaroElevatorProps类型确保了类型安全。


18-18: 上下文类型明确为ElevatorItem

将createContext的泛型类型从空对象更改为ElevatorItem,提高了类型安全性和代码可读性。


20-29: 更新默认属性配置

默认属性添加了mode: 'horizontal',并适当调整了spaceHeight的默认值,与整体设计保持一致。


65-67: 状态类型更新为ElevatorItem

将currentData的类型从通用对象更新为具体的ElevatorItem类型,提高了类型安全性。


208-214: 根元素类名中添加模式区分

将模式(horizontal/vertical)添加到根元素的类名中,有助于应用特定于模式的样式,使组件更灵活。


216-227: 优化ScrollView配置

添加enhanced属性并显式禁用滚动动画(scrollWithAnimation={false})可以提高滚动性能,特别是在复杂列表中。


237-262: 列表项子列表结构优化

子列表的结构优化得当,使用ElevatorItem类型提高了类型安全性,保持了代码一致性。


306-312: 基于模式条件渲染固定标题

仅在垂直模式下且启用sticky时显示固定标题,符合垂直模式下的用户体验预期,避免在水平模式下不必要的元素渲染。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34a31ac and 622896c.

📒 Files selected for processing (2)
  • src/packages/elevator/index.taro.ts (1 hunks)
  • src/packages/elevator/index.ts (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: test
  • GitHub Check: lint
  • GitHub Check: test

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/packages/elevator/elevator.tsx (1)

189-194: ⚠️ Potential issue

未移除 scroll 事件监听,存在内存泄漏风险

useEffect 中向 listview.current 绑定了 scroll 事件,但在组件卸载或 listview.current 变更时没有执行 removeEventListener
这会导致:

  1. 组件多次挂载/卸载后事件叠加,性能下降。
  2. 某些场景下产生无法释放的闭包引用。

建议在 useEffect 返回清理函数,同时依赖数组使用 [](ref 对象本身不会变化):

-  useEffect(() => {
-    if (listview.current) {
-      setListGroup()
-      listview.current.addEventListener('scroll', listViewScroll)
-    }
-  }, [listview])
+  useEffect(() => {
+    if (listview.current) {
+      setListGroup()
+      const current = listview.current
+      current.addEventListener('scroll', listViewScroll)
+      return () => {
+        current.removeEventListener('scroll', listViewScroll)
+      }
+    }
+  }, [])
🧹 Nitpick comments (2)
src/packages/elevator/elevator.tsx (2)

11-14: 类型定义更新改进了代码的可维护性

代码将类型定义从本地移动到外部的 @/types 文件,并使用了 ElevatorItemWebElevatorProps 类型。这种做法改进了代码组织结构,使类型在整个项目中可以被重用。

建议:

  • 考虑在文件中添加对这些类型定义的简短注释,以提高代码的可读性。
  • 确保这些导入的类型已经在 @/types 中完整定义,并且与组件需求保持一致。

Also applies to: 26-26, 28-28, 31-31, 61-63


124-127: 拖拽事件处理优化但缺少测试覆盖

在 onDragStart 中添加了对无效索引的早期返回处理,这是一个很好的防御性编程实践。但根据静态分析,这段代码也缺少测试覆盖。

建议:

  • 添加相关测试案例,确保这种边缘情况能被正确处理
  • 考虑记录一条调试日志,以便在开发模式下观察这种情况的发生
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 124-127: src/packages/elevator/elevator.tsx#L124-L127
Added lines #L124 - L127 were not covered by tests

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10824b4 and ba04046.

📒 Files selected for processing (1)
  • src/packages/elevator/elevator.tsx (8 hunks)
🧰 Additional context used
🪛 GitHub Check: codecov/patch
src/packages/elevator/elevator.tsx

[warning] 80-83: src/packages/elevator/elevator.tsx#L80-L83
Added lines #L80 - L83 were not covered by tests


[warning] 124-127: src/packages/elevator/elevator.tsx#L124-L127
Added lines #L124 - L127 were not covered by tests

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (2)
src/packages/elevator/elevator.tsx (2)

18-18: 新增的 mode 属性增强了组件的灵活性

新增的 mode 属性(默认值为 'horizontal')允许组件在水平和垂直两种布局模式下工作,这大大增强了组件的适用性。根据 mode 的值,组件的 class 名和固定标题的渲染逻辑也相应地进行了调整。

建议:

  • 确保为这两种模式添加适当的样式规则
  • 在组件文档中明确说明这两种模式的用途和区别

Also applies to: 198-198, 202-208


220-221: 改进了子列表的结构和样式

将子列表项从 React Fragment 更改为带有类名 ${classPrefix}-list-item-sublist 的 div,这有助于应用特定样式并提高结构的清晰度。

建议:

  • 确保相应的 CSS 样式已经定义,以便正确显示子列表
  • 考虑为这个子列表容器添加适当的语义角色(aria role)以提高可访问性

Also applies to: 245-245

Copy link
Collaborator

@xiaoyatong xiaoyatong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1、demo中 list 类型不一致
2、建议增加受控逻辑,如 传入 北京,可以定位到 北京,标记为 选中 状态;
3、选中字重 600
4、提升单测覆盖率

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list 提示 类型不匹配、

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已修改

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
src/packages/elevator/elevator.tsx (1)

189-194: ⚠️ Potential issue

事件监听存在内存泄漏风险

useEffect中向listview.current绑定了scroll事件,但在组件卸载时没有移除事件监听,这可能导致内存泄漏。

建议修改为:

-useEffect(() => {
-  if (listview.current) {
-    setListGroup()
-    listview.current.addEventListener('scroll', listViewScroll)
-  }
-}, [listview])
+useEffect(() => {
+  if (listview.current) {
+    setListGroup()
+    const current = listview.current
+    current.addEventListener('scroll', listViewScroll)
+    return () => {
+      current.removeEventListener('scroll', listViewScroll)
+    }
+  }
+}, []) // 依赖为空数组,因为ref对象本身不会变化
♻️ Duplicate comments (1)
src/packages/elevator/elevator.taro.tsx (1)

169-193: ⚠️ Potential issue

滚动处理逻辑优化但存在潜在问题

使用raf进行滚动事件节流是一个好的优化,但在回调中可能存在竞态条件:当listHeight为空时调用calculateHeight(),但由于该函数是异步执行的,可能在后续的findIndex判断之前尚未完成,可能导致返回-1的索引。

建议修改为:

raf(() => {
  const { listHeight } = state.current
  if (!listHeight.length) {
    calculateHeight()
+   return // 如果需要计算高度,则先返回,等待下一帧
  }
  const target = e.target as Element
  const { scrollTop } = target
  state.current.scrollY = Math.floor(scrollTop)
  // 其他代码...
})

或者将calculateHeight函数改造为返回Promise,以便能够等待计算完成:

-const calculateHeight = () => {
+const calculateHeight = (): Promise<void> => 
+  new Promise((resolve) => {
  state.current.listHeight = [0]
  
  const query = createSelectorQuery()
  query
    .selectAll(`#${classPrefix}-${uuid} .${classPrefix}-list-item`)
    .boundingClientRect()
    .exec((rect = []) => {
      if (rect[0]?.length) {
        state.current.listHeight = rect[0].reduce(
          (acc: any[], item: { height: any }, index: number) => {
            const height = acc[index] + item.height
            acc.push(height)
            return acc
          },
          [0]
        )
      }
+     resolve()
    })
+  })
🧹 Nitpick comments (1)
src/packages/elevator/elevator.taro.tsx (1)

86-106: 优化高度计算逻辑但存在潜在问题

使用单个选择器查询获取所有列表项,提高了性能。但在第94行的条件判断中,建议使用可选链操作符以防止潜在的空引用异常。

-if (rect[0] && rect[0].length) {
+if (rect[0]?.length) {
🧰 Tools
🪛 Biome (1.9.4)

[error] 94-94: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f625b0 and 0be23c8.

⛔ Files ignored due to path filters (1)
  • src/packages/address/__test__/__snapshots__/address.spec.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (4)
  • src/packages/elevator/__tests__/elevator.spec.tsx (1 hunks)
  • src/packages/elevator/elevator.taro.tsx (11 hunks)
  • src/packages/elevator/elevator.tsx (10 hunks)
  • src/types/spec/elevator/base.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/types/spec/elevator/base.ts
🧰 Additional context used
🪛 Biome (1.9.4)
src/packages/elevator/elevator.taro.tsx

[error] 94-94: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🪛 GitHub Check: codecov/patch
src/packages/elevator/elevator.tsx

[warning] 80-83: src/packages/elevator/elevator.tsx#L80-L83
Added lines #L80 - L83 were not covered by tests


[warning] 124-127: src/packages/elevator/elevator.tsx#L124-L127
Added lines #L124 - L127 were not covered by tests


[warning] 260-260: src/packages/elevator/elevator.tsx#L260
Added line #L260 was not covered by tests

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: build
🔇 Additional comments (35)
src/packages/elevator/__tests__/elevator.spec.tsx (10)

133-137: 新增的模式属性测试覆盖了重要功能

测试验证了新增的mode="vertical"属性能正确渲染垂直模式类名,这对确保布局渲染正确很重要。


139-143: showKeys属性测试覆盖了UI条件渲染

测试验证了当showKeysfalse时不渲染索引栏,符合预期行为。


145-162: 自定义内容渲染测试实现完善

测试验证了通过children属性实现自定义内容渲染的功能,测试用例结构清晰,断言合理。


164-171: 非数字高度值处理测试有效

测试验证了组件对非数字高度值(如"50vh")的正确处理,确保了组件的灵活性。


173-177: 空列表渲染测试覆盖了边缘情况

测试验证了当列表为空数组时的渲染行为,确保组件在无数据时不会崩溃。


179-209: 自定义楼层键测试实现完善

测试验证了通过floorKey属性支持自定义标识字段的功能,测试数据结构合理,断言准确。


211-241: 非字符串值渲染测试确保了类型兼容性

测试验证了组件对数字类型索引值的正确处理,确保了更广泛的数据兼容性。


243-257: 索引点击传值测试保证了事件回调准确性

测试验证了点击索引栏项目时能正确传递索引值到回调函数,确保事件处理机制正常。


259-281: 滚动高亮测试确保了交互一致性

测试验证了滚动时索引高亮状态的正确性,使用actwaitFor正确处理了异步操作。


283-309: 垂直模式下固定头部测试覆盖了关键功能

测试验证了垂直模式下启用sticky属性时固定头部的显示逻辑,模拟了滚动事件并正确断言了结果。

src/packages/elevator/elevator.taro.tsx (14)

12-16: 优化引用的类型和工具函数

引入了raf工具函数用于请求动画帧处理,并从@/types导入了更规范的类型定义,提高了代码质量和类型安全性。


18-18: 更新上下文类型定义

将上下文类型从ElevatorData更新为ElevatorItem,与整体类型系统保持一致。


20-29: 新增mode属性并优化默认配置

新增了mode属性支持水平和垂直布局,默认值为'horizontal',同时调整了spaceHeight默认值为18,使组件更加灵活。


31-33: 更新组件Props类型定义

将组件Props类型从Partial<ElevatorProps>更新为Partial<TaroElevatorProps>,增强了类型系统的一致性。


65-67: 统一状态类型定义

currentData状态类型更新为ElevatorItem,与其他部分保持一致。


109-132: 滚动函数异步化并增加模式条件

scrollTo函数改为异步函数并在需要时等待高度计算完成,同时添加了模式条件判断,只在垂直模式下更新滚动Y值,提高了代码的健壮性。


159-163: 更新事件处理函数参数类型

handleClickItem函数的参数类型更新为ElevatorItem,与新的类型系统保持一致。

🧰 Tools
🪛 Biome (1.9.4)

[error] 160-162: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


202-206: 使用nextTick优化初始化时机

使用nextTick确保在组件渲染完成后再计算高度,提高了初始化的可靠性。


209-212: 更新根容器类名以支持不同模式

在根容器类名中添加了模式标识${classPrefix}-${mode},便于针对不同模式应用样式。


219-221: 优化滚动视图配置

添加了enhanced属性并关闭了滚动动画,提高了滚动性能和用户体验。


237-262: 改进子列表结构

将原来的React片段替换为具有类名的div容器,提供了更好的DOM结构和样式控制能力。


277-278: 确保索引键值类型一致性

使用String()确保索引键值渲染为字符串,防止可能的类型错误。


298-299: 确保楼层键值类型一致性

使用String()确保楼层键值渲染为字符串,防止可能的类型错误。


306-312: 优化固定标题的条件渲染

只在垂直模式且启用sticky属性时才渲染固定标题,符合新的设计需求。

src/packages/elevator/elevator.tsx (11)

11-12: 优化引用的类型

@/types导入了更规范的类型定义,提高了代码质量和类型安全性。


14-14: 更新上下文类型定义

将上下文类型从ElevatorData更新为ElevatorItem,与整体类型系统保持一致。


16-25: 新增mode属性并优化默认配置

新增了mode属性支持水平和垂直布局,默认值为'horizontal',同时调整了spaceHeight默认值为18,使组件更加灵活。


27-29: 更新组件Props类型定义

将组件Props类型从Partial<ElevatorProps>更新为Partial<WebElevatorProps>,增强了类型系统的一致性。


61-63: 统一状态类型定义

currentData状态类型更新为ElevatorItem,与其他部分保持一致。


145-149: 更新事件处理函数参数类型

handleClickItem函数的参数类型更新为ElevatorItem,与新的类型系统保持一致。


197-201: 更新根容器类名以支持不同模式

在根容器类名中添加了模式标识${classPrefix}-${mode},便于针对不同模式应用样式。


202-208: 优化固定标题的条件渲染

只在垂直模式且启用sticky属性时才渲染固定标题,符合新的设计需求。


220-245: 改进子列表结构

将原来的React片段替换为具有类名的div容器,提供了更好的DOM结构和样式控制能力。


281-282: 确保楼层键值类型一致性

使用String()确保楼层键值渲染为字符串,防止可能的类型错误。


260-261:

✅ Verification successful

确保索引键值类型一致性

使用String()确保索引键值渲染为字符串,防止可能的类型错误,但此行代码缺少测试覆盖。

建议添加针对非字符串值的测试用例:


🏁 Script executed:

#!/bin/bash
# 检查是否有测试非字符串值渲染的测试用例
grep -r "非字符串" --include="*.spec.tsx" --include="*.test.tsx" src/packages/elevator/

Length of output: 196


无需新增非字符串值渲染测试

src/packages/elevator/__tests__/elevator.spec.tsx 已包含 “测试非字符串值的正确渲染” 用例,覆盖了使用 String() 强制转换的场景,无需额外添加测试。

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 260-260: src/packages/elevator/elevator.tsx#L260
Added line #L260 was not covered by tests

Comment on lines +124 to +127
if (index < 0) {
setScrollStart(false)
return
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

增加拖拽起始点的边界检查但缺少测试覆盖

增加了对索引小于0的情况的处理,防止无效操作,但这部分代码也缺少测试覆盖。

建议添加单元测试覆盖此边界情况,并考虑添加更明确的日志或注释说明此边界条件的处理逻辑。

🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 124-127: src/packages/elevator/elevator.tsx#L124-L127
Added lines #L124 - L127 were not covered by tests

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/packages/elevator/__tests__/elevator.spec.tsx (1)

378-394: 列表高度计算测试可以更精确

当前测试只验证了列表项的数量,但没有验证高度计算的正确性。建议添加更精确的断言来确保高度计算正确。

  // 验证列表项高度计算
  expect(listItems.length).toBe(list.length)
+ // 确保每个列表项都有高度
+ listItems.forEach((item) => {
+   expect(item.clientHeight).toBeGreaterThan(0)
+ })
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc18eaf and 9e7b48a.

📒 Files selected for processing (2)
  • src/packages/elevator/__tests__/elevator.spec.tsx (1 hunks)
  • src/packages/elevator/elevator.tsx (10 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/packages/elevator/elevator.tsx
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/packages/elevator/__tests__/elevator.spec.tsx (1)
src/packages/elevator/elevator.tsx (1)
  • Elevator (27-287)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: build
  • GitHub Check: test
🔇 Additional comments (13)
src/packages/elevator/__tests__/elevator.spec.tsx (13)

133-137: 完善了对垂直模式的测试

这个测试很好地验证了 Elevator 组件在垂直模式下的渲染行为,确保添加了正确的 CSS 类名。这与组件添加的新 mode 属性一致。


139-143: 验证了 showKeys 属性功能

这个测试正确验证了当 showKeys 设置为 false 时,索引条不会被渲染,这是一个重要的行为验证。


145-162: 自定义内容渲染测试设计合理

这个测试很好地验证了使用 children 属性自定义内容的渲染功能。测试中创建了一个自定义组件并验证其内容被正确显示。


164-171: 非数字高度值处理测试有效

这个测试正确验证了组件能够处理非数字高度值(如 CSS 单位 "vh"),确保组件在各种使用场景下都能正常工作。


173-177: 空列表处理测试合理

这个测试验证了组件在列表为空时的渲染行为,确保组件能够优雅地处理边缘情况。


179-209: 自定义楼层键测试设计全面

这个测试很好地验证了组件支持自定义 floorKey 的功能,通过使用非标准的键名进行测试,确保组件的灵活性。


211-241: 非字符串值渲染测试有效

这个测试验证了组件能够正确处理非字符串值(如数字)作为楼层标识,确保了组件在各种数据类型下的兼容性。


243-257: 索引点击处理测试完善

这个测试很好地验证了点击索引条时回调函数接收到正确的键值,确保了事件处理的正确性。


311-319: getData 边界情况测试逻辑合理

这个测试很好地验证了当元素不存在 data-index 属性时的行为,确保组件能够处理边缘情况。


321-352: scrollTo 函数边界情况测试全面

这个测试很好地覆盖了 scrollTo 函数的各种边界情况,包括滚动到索引 0、负索引和超出列表长度的索引,确保组件的健壮性。


354-376: 拖拽操作状态更新测试全面

这个测试很好地验证了组件在拖拽操作期间的状态更新,包括拖拽开始和结束时的状态变化。


414-426: 列表组设置测试设计合理

这个测试很好地验证了组件正确设置列表组,确保所有列表项都有正确的类名。


428-465: 组合点击场景测试设计全面

这个测试很好地验证了组件在点击索引和列表项时的行为,确保回调函数接收到正确的参数并且高亮状态正确更新。

Comment on lines 259 to 281
// 测试列表滚动时高亮显示的正确性
test('should highlight the correct index when scrolling', async () => {
const { container } = render(<Elevator list={list} height={200} />)

// 模拟滚动
const listView = container.querySelector('.nut-elevator-list-inner')

await act(() => {
// 手动触发点击索引,应该会导致滚动和高亮
const indexItem = container.querySelectorAll(
'.nut-elevator-bars-inner-item'
)[2]
fireEvent.click(indexItem)
})

// 检查是否正确高亮了第三个索引
waitFor(() => {
const activeIndex = container.querySelector(
'.nut-elevator-bars-inner-item-active'
)
expect(activeIndex?.textContent).toBe('G')
})
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

滚动高亮测试有待完善

测试验证了点击索引后的高亮逻辑,但 waitFor 没有返回 Promise 并等待断言完成,这可能导致测试不稳定。

-  waitFor(() => {
+  await waitFor(() => {
     const activeIndex = container.querySelector(
       '.nut-elevator-bars-inner-item-active'
     )
     expect(activeIndex?.textContent).toBe('G')
-  })
+  })
📝 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.

Suggested change
// 测试列表滚动时高亮显示的正确性
test('should highlight the correct index when scrolling', async () => {
const { container } = render(<Elevator list={list} height={200} />)
// 模拟滚动
const listView = container.querySelector('.nut-elevator-list-inner')
await act(() => {
// 手动触发点击索引,应该会导致滚动和高亮
const indexItem = container.querySelectorAll(
'.nut-elevator-bars-inner-item'
)[2]
fireEvent.click(indexItem)
})
// 检查是否正确高亮了第三个索引
waitFor(() => {
const activeIndex = container.querySelector(
'.nut-elevator-bars-inner-item-active'
)
expect(activeIndex?.textContent).toBe('G')
})
})
// 测试列表滚动时高亮显示的正确性
test('should highlight the correct index when scrolling', async () => {
const { container } = render(<Elevator list={list} height={200} />)
// 模拟滚动
const listView = container.querySelector('.nut-elevator-list-inner')
await act(() => {
// 手动触发点击索引,应该会导致滚动和高亮
const indexItem = container.querySelectorAll(
'.nut-elevator-bars-inner-item'
)[2]
fireEvent.click(indexItem)
})
// 检查是否正确高亮了第三个索引
await waitFor(() => {
const activeIndex = container.querySelector(
'.nut-elevator-bars-inner-item-active'
)
expect(activeIndex?.textContent).toBe('G')
})
})

Comment on lines 396 to 412
// 测试 listViewScroll 函数
test('should handle list view scroll correctly', async () => {
const { container } = render(<Elevator list={list} height={200} />)
const listView = container.querySelector('.nut-elevator-list-inner')

// 模拟滚动事件
await act(() => {
if (listView) {
Object.defineProperty(listView, 'scrollTop', { value: 100 })
fireEvent.scroll(listView)
}
})

// 验证滚动位置更新
const fixedTitle = container.querySelector('.nut-elevator-list-fixed-title')
expect(fixedTitle).toBeTruthy()
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

列表视图滚动处理测试需要修正断言

在垂直模式下测试固定标题的存在,但没有明确指定 mode="vertical",这可能导致测试不可靠。

  const { container } = render(
-   <Elevator list={list} height={200} />
+   <Elevator list={list} height={200} mode="vertical" sticky />
  )
📝 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.

Suggested change
// 测试 listViewScroll 函数
test('should handle list view scroll correctly', async () => {
const { container } = render(<Elevator list={list} height={200} />)
const listView = container.querySelector('.nut-elevator-list-inner')
// 模拟滚动事件
await act(() => {
if (listView) {
Object.defineProperty(listView, 'scrollTop', { value: 100 })
fireEvent.scroll(listView)
}
})
// 验证滚动位置更新
const fixedTitle = container.querySelector('.nut-elevator-list-fixed-title')
expect(fixedTitle).toBeTruthy()
})
// 测试 listViewScroll 函数
test('should handle list view scroll correctly', async () => {
const { container } = render(
<Elevator list={list} height={200} mode="vertical" sticky />
)
const listView = container.querySelector('.nut-elevator-list-inner')
// 模拟滚动事件
await act(() => {
if (listView) {
Object.defineProperty(listView, 'scrollTop', { value: 100 })
fireEvent.scroll(listView)
}
})
// 验证滚动位置更新
const fixedTitle = container.querySelector('.nut-elevator-list-fixed-title')
expect(fixedTitle).toBeTruthy()
})

Comment on lines +283 to +309
// 测试当存在垂直模式和sticky时,固定头部是否正确显示
test('should show fixed title in vertical mode with sticky', async () => {
const { container } = render(
<Elevator list={list} mode="vertical" sticky height={200} />
)

// 首先触发点击以模拟滚动
await act(() => {
const indexItem = container.querySelectorAll(
'.nut-elevator-bars-inner-item'
)[1]
fireEvent.click(indexItem)

// 模拟滚动事件
const listView = container.querySelector('.nut-elevator-list-inner')
if (listView) {
Object.defineProperty(listView, 'scrollTop', { value: 50 })
fireEvent.scroll(listView)
}
})

// 等待滚动效果完成后检查固定标题
waitFor(() => {
const fixedTitle = container.querySelector('.nut-elevator-list-fixed-title')
expect(fixedTitle).not.toBeNull()
})
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

垂直模式下固定标题测试需要修复

与前一个类似的问题,waitFor 需要等待断言完成,否则测试可能不稳定。

-  waitFor(() => {
+  await waitFor(() => {
     const fixedTitle = container.querySelector('.nut-elevator-list-fixed-title')
     expect(fixedTitle).not.toBeNull()
-  })
+  })
📝 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.

Suggested change
// 测试当存在垂直模式和sticky时,固定头部是否正确显示
test('should show fixed title in vertical mode with sticky', async () => {
const { container } = render(
<Elevator list={list} mode="vertical" sticky height={200} />
)
// 首先触发点击以模拟滚动
await act(() => {
const indexItem = container.querySelectorAll(
'.nut-elevator-bars-inner-item'
)[1]
fireEvent.click(indexItem)
// 模拟滚动事件
const listView = container.querySelector('.nut-elevator-list-inner')
if (listView) {
Object.defineProperty(listView, 'scrollTop', { value: 50 })
fireEvent.scroll(listView)
}
})
// 等待滚动效果完成后检查固定标题
waitFor(() => {
const fixedTitle = container.querySelector('.nut-elevator-list-fixed-title')
expect(fixedTitle).not.toBeNull()
})
})
// 测试当存在垂直模式和sticky时,固定头部是否正确显示
test('should show fixed title in vertical mode with sticky', async () => {
const { container } = render(
<Elevator list={list} mode="vertical" sticky height={200} />
)
// 首先触发点击以模拟滚动
await act(() => {
const indexItem = container.querySelectorAll(
'.nut-elevator-bars-inner-item'
)[1]
fireEvent.click(indexItem)
// 模拟滚动事件
const listView = container.querySelector('.nut-elevator-list-inner')
if (listView) {
Object.defineProperty(listView, 'scrollTop', { value: 50 })
fireEvent.scroll(listView)
}
})
// 等待滚动效果完成后检查固定标题
await waitFor(() => {
const fixedTitle = container.querySelector('.nut-elevator-list-fixed-title')
expect(fixedTitle).not.toBeNull()
})
})

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
src/packages/elevator/elevator.taro.tsx (1)

84-90: 🛠️ Refactor suggestion

缺少非法 index 处理,可能误将未定义数据当作 0

getData 在未找到 dataset.index 时返回字符串 '0',导致用户点击空白处也会跳转到第一个分组。建议与 web 端保持一致,返回 '-1' 并在 touchStart 时检测并中断。

-if (!el.dataset.index) {
-  return '0'
-}
-return el.dataset.index as string
+return el.dataset.index ?? '-1'
🧰 Tools
🪛 Biome (1.9.4)

[error] 84-84: void is confusing inside a union type.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

♻️ Duplicate comments (2)
src/packages/elevator/elevator.tsx (1)

189-195: ⚠️ Potential issue

必须在卸载时移除 scroll 事件监听,避免内存泄漏

useEffect 中仅添加了 scroll 监听,却没有在返回值里执行 removeEventListener。组件被多次挂载/卸载时,事件会叠加,造成性能下降及潜在内存泄漏。该问题在上一次评审中已指出,仍未修复。

-  useEffect(() => {
-    if (listview.current) {
-      setListGroup()
-      listview.current.addEventListener('scroll', listViewScroll)
-    }
-  }, [listview])
+  useEffect(() => {
+    if (listview.current) {
+      setListGroup()
+      const current = listview.current
+      current.addEventListener('scroll', listViewScroll)
+      return () => current.removeEventListener('scroll', listViewScroll)
+    }
+    // listview ref 对象本身不会变化,依赖可置空
+  }, [])
src/packages/elevator/elevator.taro.tsx (1)

91-112: ⚠️ Potential issue

calculateHeight 为异步回调,却未返回 Promise —— await 无效

scrollTo 中使用 await calculateHeight(),但 calculateHeight 没有返回值,await 会立即继续执行,state.current.listHeight 仍为空,最终定位可能出错。上次评审已提示,仍未修复。

-const calculateHeight = () => {
-  state.current.listHeight = [0]
-  const query = createSelectorQuery()
-  ...
-}
+const calculateHeight = (): Promise<void> =>
+  new Promise((resolve) => {
+    state.current.listHeight = [0]
+    createSelectorQuery()
+      .selectAll(`#${classPrefix}-${uuid} .${classPrefix}-list-item`)
+      .boundingClientRect()
+      .exec((rect = []) => {
+        if (rect[0]?.length) {
+          state.current.listHeight = rect[0].reduce(
+            (acc: number[], item: { height: number }, idx: number) => {
+              acc.push(acc[idx] + item.height)
+              return acc
+            },
+            [0]
+          )
+        }
+        resolve()
+      })
+  })
🧰 Tools
🪛 Biome (1.9.4)

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

🧹 Nitpick comments (2)
src/packages/elevator/elevator.tsx (2)

167-184: 滚动回调频繁 setState,建议节流处理

listViewScroll 每次 scrollsetScrollY/setCurrentIndex,在长列表上可能触发大量重渲染。可使用 requestAnimationFramelodash/throttle 对回调做节流,或在状态重复时提前 return


196-201: 类名拼接存在空字符串,使用 classNames 更安全

className 为空值时,${classPrefix} ${classPrefix}-${mode} ${className} 会产生尾部空格。改用 classNames 可避免此问题并提升可读性。

-<div className={`${classPrefix} ${classPrefix}-${mode} ${className}`} ...>
+<div
+  className={classNames(classPrefix, `${classPrefix}-${mode}`, className)}
+  ...>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e248e3d and ad3b5f4.

📒 Files selected for processing (3)
  • src/packages/elevator/elevator.taro.tsx (10 hunks)
  • src/packages/elevator/elevator.tsx (10 hunks)
  • src/types/spec/elevator/base.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/types/spec/elevator/base.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/packages/elevator/elevator.tsx (4)
src/packages/elevator/elevator.taro.tsx (2)
  • elevatorContext (19-19)
  • Elevator (34-318)
src/types/spec/elevator/base.ts (2)
  • ElevatorItem (4-8)
  • ElevatorList (11-14)
src/packages/elevator/index.ts (1)
  • WebElevatorProps (3-3)
src/hooks/use-props-value.ts (1)
  • usePropsValue (11-35)
🪛 Biome (1.9.4)
src/packages/elevator/elevator.taro.tsx

[error] 99-99: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 165-169: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)


[error] 171-173: Change to an optional chain.

Unsafe fix: Change to an optional chain.

(lint/complexity/useOptionalChain)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (1)
src/packages/elevator/elevator.taro.tsx (1)

114-135: scrollTo 中的 await calculateHeight() 不起作用

受上一问题影响,即使 await,随后立即读取 listHeight[cacheIndex] 也可能还是空数组,应在确保高度计算完成后再执行滚动逻辑,可将读取逻辑移入 calculateHeightthen 中或在其 resolve 后调用。

Comment on lines +78 to 85
const getData = (el: HTMLElement, name: string): string => {
const prefix = 'data-'
return el.getAttribute(prefix + name) as string
// 检查点击的元素是否直接包含 data-index 属性
if (el.hasAttribute('data-index')) {
return el.getAttribute(prefix + name) as string
}
return '-1'
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

getData 仅检查当前元素,子节点点击将失效

很多场景中点击事件会冒泡到子节点(例如 <span> 或图标),这些子节点不含 data-index,函数会直接返回 -1,导致无法拖动。建议向上递归查找最近带有 data-index 的父元素,或改用事件代理读取 currentTarget.dataset.index

Comment on lines +188 to +197
const index = listHeight.findIndex(
(height, i) =>
state.current.scrollY >= height &&
state.current.scrollY < (listHeight[i + 1] || Infinity)
)

if (index !== -1 && index !== codeIndex) {
setCodeIndex(index)
}
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

findIndex 逻辑仍依赖异步数据,需考虑空数组分支

listHeight 为空时立即 calculateHeight() 但未等待结果即可执行 findIndex,依旧可能返回 -1。应在高度未就绪时直接 return,或同样改造成 Promise 链确保数据可用。

@irisSong irisSong changed the title fix(elevator): v15 适配 fix(elevator): v15 适配 & 选中值支持受控 May 14, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/packages/elevator/__tests__/elevator.spec.tsx (2)

260-282: ⚠️ Potential issue

滚动高亮测试需要修复

测试验证了点击索引后的高亮逻辑,但waitFor没有返回Promise并等待断言完成,这可能导致测试不稳定。

-  waitFor(() => {
+  await waitFor(() => {
     const activeIndex = container.querySelector(
       '.nut-elevator-bars-inner-item-active'
     )
     expect(activeIndex?.textContent).toBe('G')
-  })
+  })

284-310: ⚠️ Potential issue

垂直模式下固定标题测试需要修复

与前一个类似的问题,waitFor需要等待断言完成,否则测试可能不稳定。

-  waitFor(() => {
+  await waitFor(() => {
     const fixedTitle = container.querySelector('.nut-elevator-list-fixed-title')
     expect(fixedTitle).not.toBeNull()
-  })
+  })
🧹 Nitpick comments (2)
src/packages/elevator/__tests__/elevator.spec.tsx (2)

312-317: getData函数边界情况测试可优化

当前测试只验证了元素没有data-index属性,但没有实际验证getData函数的返回值。建议添加对函数返回值的断言。

test('should return -1 when data-index attribute is not present', () => {
-  const { container } = render(<Elevator list={list} />)
-  const element = container.querySelector('.nut-elevator')
-  expect(element).not.toHaveAttribute('data-index')
+  const { container } = render(<Elevator list={list} />)
+  const element = container.querySelector('.nut-elevator')!
+  
+  // 模拟访问内部方法来测试getData函数
+  // 注意:这需要将getData暴露出来或使用其他方法访问组件实例
+  // 这里只是示例,实际实现可能需要调整
+  expect(element).not.toHaveAttribute('data-index')
+  // TODO: 验证getData(element)返回-1
})

319-324: 简化测试用例可以扩展

这些测试过于简化,只是检查组件能否正常渲染,没有验证实际功能。建议扩展这些测试以提供更有意义的功能验证。

例如,对于scrollTo函数的边界情况测试,可以:

  1. 模拟列表项的DOM结构
  2. 调用scrollTo函数并传入边界值(如-1或超出范围的索引)
  3. 验证是否正确处理了这些边界情况

对于其他简化的测试也可采用类似方法进行扩展。

Also applies to: 326-331, 340-347, 359-379, 381-386

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3cae35c and 34ad8bc.

📒 Files selected for processing (1)
  • src/packages/elevator/__tests__/elevator.spec.tsx (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/packages/elevator/__tests__/elevator.spec.tsx (1)
src/packages/elevator/elevator.tsx (1)
  • Elevator (30-290)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: build
🔇 Additional comments (11)
src/packages/elevator/__tests__/elevator.spec.tsx (11)

5-5: 导入了事件模拟工具

新增了触摸事件模拟工具的导入,用于测试拖拽相关功能,这是一个很好的改进。


134-138: 垂直模式测试实现正确

正确地测试了垂直模式渲染,验证了对应的CSS类存在。


140-144: showKeys属性测试有效

正确验证了当showKeys为false时不渲染索引栏。


146-163: 自定义内容渲染测试实现良好

通过CustomItem组件测试了children属性的渲染逻辑,代码结构清晰。


165-172: 非数字高度值处理测试良好

正确验证了组件能够处理"50vh"等非数字高度值。


174-178: 空列表渲染测试有效

正确验证了当列表为空时不渲染任何列表项。


180-210: 自定义楼层键测试实现正确

通过customTitle字段正确测试了floorKey属性的功能。


212-242: 非字符串值渲染测试有效

正确验证了组件能够处理数字等非字符串值的渲染。


244-258: 索引点击事件参数传递测试正确

验证了点击索引栏时能正确传递索引值到回调函数。


388-402: 拖拽开始测试实现良好

正确使用了await waitFor和事件模拟工具测试拖拽开始行为。


404-417: 拖拽结束测试实现良好

正确测试了拖拽结束后的状态变化,确保了代码指示器被正确移除。

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/packages/elevator/__tests__/elevator.spec.tsx (2)

84-89: 应将 waitFor 方法与 await 一起使用以确保测试稳定性

测试中使用了 waitFor 方法但没有等待其完成,这可能导致测试不稳定。这与先前的审查意见相同,仍然需要修复。

-  waitFor(() => {
+  await waitFor(() => {
     expect(
       container.querySelector('.nut-elevator-list-item-name-highcolor')
         ?.innerHTML
     ).toBe('安徽')
   })

239-265: 应将 waitFor 方法与 await 一起使用以确保测试稳定性

与前一个类似的问题,waitFor 需要等待断言完成,否则测试可能不稳定。

-  waitFor(() => {
+  await waitFor(() => {
     const fixedTitle = container.querySelector('.nut-elevator-list-fixed-title')
     expect(fixedTitle).not.toBeNull()
-  })
+  })
🧹 Nitpick comments (2)
src/packages/elevator/__tests__/elevator.spec.tsx (2)

125-142: 建议增强自定义内容渲染测试

测试验证了自定义内容能够正确渲染,但没有实际测试 Context 的使用。考虑添加使用 Elevator.Context 的测试用例以更全面地验证功能。

 test('should render custom content with children', () => {
   // 使用 Context 消费者组件
   const CustomItem = () => {
     // 在测试环境中,我们不能直接使用 useContext,因此直接渲染一个固定元素
-    return <div className="custom-item">自定义内容</div>
+    return (
+      <Elevator.Context.Consumer>
+        {(value) => (
+          <div className="custom-item" data-id={value?.id}>
+            {value?.name || '自定义内容'}
+          </div>
+        )}
+      </Elevator.Context.Consumer>
+    )
   }

   const { container } = render(
     <Elevator list={list}>
       <CustomItem />
     </Elevator>
   )

   const customItems = container.querySelectorAll('.custom-item')
   expect(customItems.length).toBeGreaterThan(0)
-  expect(customItems[0].textContent).toBe('自定义内容')
+  expect(customItems[0]).toHaveAttribute('data-id')
+  expect(customItems[0].textContent).toBeTruthy()
 })

267-272: 测试覆盖不足,建议增强对 getData 函数的测试

当前测试只验证了元素没有 data-index 属性,但没有验证 getData 函数返回的实际值。建议增强测试以验证函数的完整行为。

 test('should return -1 when data-index attribute is not present', () => {
   const { container } = render(<Elevator list={list} />)
   const element = container.querySelector('.nut-elevator')
   expect(element).not.toHaveAttribute('data-index')
+  
+  // 模拟一个有 data-index 的元素
+  const mockElement = document.createElement('div')
+  mockElement.setAttribute('data-index', '2')
+  
+  // 访问内部 getData 函数进行测试
+  // 注意:这种做法在实际测试中可能需要额外的方法来访问组件内部函数
+  // 这里仅作为示例
+  // const result = component.instance().getData(mockElement, 'index')
+  // expect(result).toBe('2')
 })
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 34ad8bc and 9e13646.

📒 Files selected for processing (1)
  • src/packages/elevator/__tests__/elevator.spec.tsx (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/packages/elevator/__tests__/elevator.spec.tsx (1)
src/packages/elevator/elevator.tsx (1)
  • Elevator (30-290)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: test
🔇 Additional comments (6)
src/packages/elevator/__tests__/elevator.spec.tsx (6)

113-117: LGTM! 垂直模式的渲染测试正确有效

测试正确验证了当设置 mode="vertical" 时,组件会添加对应的 CSS 类。


119-123: LGTM! showKeys 属性验证逻辑合理

测试正确地验证了当 showKeys 设置为 false 时,索引栏不会被渲染。


144-151: LGTM! 非数字高度值的处理逻辑正确

测试正确地验证了组件可以处理非数字高度值(如 "50vh")。


153-157: LGTM! 空列表渲染测试有效

测试正确验证了当提供空列表时组件的渲染行为。


159-189: LGTM! 自定义楼层键测试全面

测试全面验证了组件可以使用自定义属性名作为楼层键。


191-221: LGTM! 非字符串值的渲染测试完善

测试正确验证了组件可以处理数字类型的索引值,并将其正确地转换为字符串显示。

Comment on lines +275 to +279
test('should handle edge cases in scrollTo function', () => {
render(<Elevator list={list} height={200} />)
// 这个测试只是确认组件能正常渲染,不会因为边界条件而崩溃
expect(true).toBeTruthy()
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

多个简化测试需要增强断言逻辑

多个测试(包括 scrollTo、拖拽操作、listViewScroll 和 resetScrollState 函数测试)过于简化,只验证了组件能够渲染而没有测试实际功能。建议增强这些测试以验证相应功能的正确实现。

考虑为这些简化测试添加更具体的断言,比如:

  1. 对于 scrollTo 测试,可以在调用函数后验证滚动位置变化
  2. 对于拖拽操作,可以验证状态变化和 UI 更新
  3. 对于 listViewScroll,可以模拟实际滚动并验证索引更新
  4. 对于 resetScrollState,可以验证状态重置后的 UI 变化

例如,对于 resetScrollState 测试:

 test('should reset scroll state correctly', () => {
-  render(<Elevator list={list} height={200} />)
-  // 简化测试,只确认组件正常渲染
-  expect(true).toBeTruthy()
+  const { container } = render(<Elevator list={list} height={200} />)
+  
+  // 首先模拟拖拽开始,触发 scrollStart 状态
+  const barsInner = container.querySelector('.nut-elevator-bars-inner')
+  act(() => {
+    trigger(barsInner, 'touchstart', 0, 0)
+    trigger(barsInner, 'touchmove', 0, 20)
+  })
+  
+  // 验证拖拽开始后,滚动指示器显示
+  expect(container.querySelector('.nut-elevator-code-current')).toBeTruthy()
+  
+  // 模拟拖拽结束,触发状态重置
+  act(() => {
+    trigger(barsInner, 'touchend', 0, 0)
+  })
+  
+  // 验证状态重置后,滚动指示器消失
+  expect(container.querySelector('.nut-elevator-code-current')).toBeFalsy()
 })

Also applies to: 281-286, 295-302, 336-341

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (6)
src/packages/elevator/__tests__/elevator.spec.tsx (6)

84-89: 需要添加 await 以确保测试稳定性

此处等待断言完成应使用 await waitFor 而不是 waitFor,以确保测试稳定性。

-  waitFor(() => {
+  await waitFor(() => {
     expect(
       container.querySelector('.nut-elevator-list-item-name-highcolor')
         ?.innerHTML
     ).toBe('安徽')
   })

261-264: 测试中 waitFor 需要 await 支持

垂直模式下固定标题测试需要添加 await 以确保测试稳定性。

-  waitFor(() => {
+  await waitFor(() => {
     const fixedTitle = container.querySelector('.nut-elevator-list-fixed-title')
     expect(fixedTitle).not.toBeNull()
   })

275-279: 简化测试需要增强断言逻辑

此测试仅确认组件能正常渲染,没有测试 scrollTo 函数的边界情况。建议添加实际的边界条件测试和断言。

建议增加对 scrollTo 函数的实际测试,比如:

 test('should handle edge cases in scrollTo function', () => {
-  render(<Elevator list={list} height={200} />)
-  // 这个测试只是确认组件能正常渲染,不会因为边界条件而崩溃
-  expect(true).toBeTruthy()
+  const { container } = render(<Elevator list={list} height={200} />)
+  
+  // 获取组件实例
+  const component = container.querySelector('.nut-elevator')
+  
+  // 检查无效索引的情况
+  const indexItems = container.querySelectorAll('.nut-elevator-bars-inner-item')
+  const invalidIndex = indexItems.length + 1
+  
+  // 触发点击事件到有效和无效索引
+  fireEvent.click(indexItems[0])
+  
+  // 验证组件状态更新但不会崩溃
+  expect(container.querySelector('.nut-elevator-bars-inner-item-active')).toBeTruthy()
 })

281-286: 拖拽操作状态变化测试过于简化

测试没有实际验证拖拽操作过程中的状态变化,仅验证了组件能够渲染。建议增加实际的拖拽操作测试和相应的状态断言。

 test('should update states correctly during drag operations', () => {
-  render(<Elevator list={list} height={200} />)
-  // 简化测试,只确认组件正常渲染
-  expect(true).toBeTruthy()
+  const { container } = render(<Elevator list={list} height={200} />)
+  
+  const barsInner = container.querySelector('.nut-elevator-bars-inner')
+  
+  // 模拟拖拽操作
+  act(() => {
+    trigger(barsInner, 'touchstart', 0, 0)
+    trigger(barsInner, 'touchmove', 0, 20)
+  })
+  
+  // 验证拖拽开始后的状态变化
+  expect(container.querySelector('.nut-elevator-code-current')).toBeTruthy()
+  
+  // 模拟拖拽结束
+  act(() => {
+    trigger(barsInner, 'touchend', 0, 20)
+  })
+  
+  // 验证拖拽结束后的状态重置
+  expect(container.querySelector('.nut-elevator-code-current')).toBeFalsy()
 })

295-302: 列表视图滚动处理测试需要加强

listViewScroll 函数测试过于简化,没有实际验证滚动事件的处理逻辑。建议增加实际的滚动事件测试和相应的断言。

 test('should handle list view scroll correctly', () => {
   const { container } = render(
     <Elevator list={list} height={200} mode="vertical" sticky />
   )
-  // 简化测试,只确认组件正常渲染
-  expect(container.querySelector('.nut-elevator')).toBeTruthy()
+  
+  // 获取列表视图元素
+  const listView = container.querySelector('.nut-elevator-list-inner')
+  
+  // 模拟滚动事件
+  act(() => {
+    if (listView) {
+      Object.defineProperty(listView, 'scrollTop', { value: 50 })
+      fireEvent.scroll(listView)
+    }
+  })
+  
+  // 验证滚动后固定标题显示
+  const fixedTitle = container.querySelector('.nut-elevator-list-fixed-title')
+  expect(fixedTitle).not.toBeNull()
 })

336-341: 滚动状态重置测试需要加强

resetScrollState 函数测试过于简化,没有实际验证状态重置的逻辑。建议增加实际的状态重置测试和相应的断言。

 test('should reset scroll state correctly', () => {
-  render(<Elevator list={list} height={200} />)
-  // 简化测试,只确认组件正常渲染
-  expect(true).toBeTruthy()
+  const { container } = render(<Elevator list={list} height={200} />)
+  
+  // 获取导航栏元素
+  const barsInner = container.querySelector('.nut-elevator-bars-inner')
+  
+  // 模拟拖拽开始,触发滚动状态
+  act(() => {
+    trigger(barsInner, 'touchstart', 0, 0)
+    trigger(barsInner, 'touchmove', 0, 20)
+  })
+  
+  // 验证拖拽开始后滚动指示器显示
+  expect(container.querySelector('.nut-elevator-code-current')).toBeTruthy()
+  
+  // 模拟拖拽结束,触发状态重置
+  act(() => {
+    trigger(barsInner, 'touchend', 0, 20)
+  })
+  
+  // 验证状态重置后滚动指示器消失
+  expect(container.querySelector('.nut-elevator-code-current')).toBeFalsy()
 })
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e13646 and afaced6.

📒 Files selected for processing (1)
  • src/packages/elevator/__tests__/elevator.spec.tsx (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: build
🔇 Additional comments (8)
src/packages/elevator/__tests__/elevator.spec.tsx (8)

113-117: 新增的垂直模式测试良好

为新添加的 mode="vertical" 属性编写了测试,验证了垂直模式下正确应用了 CSS 类。这有助于确保该功能在未来的更改中保持稳定。


119-123: showKeys 属性测试实现合理

针对 showKeys={false} 情况的测试确保了当此属性设置为 false 时不会渲染索引栏,这是一个有用的功能测试。


125-142: 自定义内容渲染测试实现完善

通过自定义组件 CustomItem 测试了 children 属性,验证了组件能够正确渲染自定义内容,这对于组件的灵活性至关重要。


153-157: 空列表处理测试有助于稳定性

测试了空列表情况下的组件渲染,这对于防止边界情况下的崩溃非常重要。


159-189: 自定义属性键测试增强了组件灵活性

通过 floorKey 属性测试了自定义标题属性,这增强了组件的灵活性,允许用户使用自定义数据结构。


191-221: 非字符串值渲染测试完善了组件健壮性

通过测试数字索引值的正确渲染,增强了组件处理不同数据类型的能力,提高了组件的健壮性。


223-237: 索引点击值测试确保了事件处理正确性

点击索引项后验证了正确的索引值被传递给回调函数,这对于确保组件的交互行为符合预期非常重要。


354-367: 拖拽结束测试实现合理

使用 triggerDrag 模拟完整的拖拽过程,并验证了拖拽结束后的状态变化,测试逻辑完整且有效。

Comment on lines +314 to +334
// 测试 handleClickItem 和 handleClickIndex 的组合场景 - 使用简化的测试
test('should handle combined click scenarios', () => {
const onItemClick = vi.fn()
const onIndexClick = vi.fn()

const { container, getByText } = render(
<Elevator
list={list}
height={200}
sticky
onIndexClick={(key: string) => testClick(key)}
onItemClick={onItemClick}
onIndexClick={onIndexClick}
/>
)
const listItem = container.querySelectorAll(
'.nut-elevator-bars-inner-item'
)[2]
fireEvent.click(listItem) // 模拟点击
setTimeout(() => {
expect(container.querySelectorAll('.nut-elevator-list-fixed').length).toBe(
1
)
}, 300)

// 简化测试,只确认组件正常渲染和事件处理函数可以被调用
const indexItem = container.querySelector('.nut-elevator-bars-inner-item')
expect(indexItem).toBeTruthy()

const listItem = container.querySelector('.nut-elevator-list-item-name')
expect(listItem).toBeTruthy()
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

组合点击场景测试需要添加实际点击操作和断言

测试声明了要测试组合点击场景,但实际上只检查了元素的存在,没有触发任何点击事件或验证处理函数的调用情况。

 test('should handle combined click scenarios', () => {
   const onItemClick = vi.fn()
   const onIndexClick = vi.fn()

   const { container, getByText } = render(
     <Elevator
       list={list}
       height={200}
       onItemClick={onItemClick}
       onIndexClick={onIndexClick}
     />
   )

-  // 简化测试,只确认组件正常渲染和事件处理函数可以被调用
   const indexItem = container.querySelector('.nut-elevator-bars-inner-item')
   expect(indexItem).toBeTruthy()

   const listItem = container.querySelector('.nut-elevator-list-item-name')
   expect(listItem).toBeTruthy()
+
+  // 测试点击索引
+  fireEvent.click(indexItem)
+  expect(onIndexClick).toHaveBeenCalled()
+
+  // 测试点击列表项
+  fireEvent.click(listItem)
+  expect(onItemClick).toHaveBeenCalled()
+
+  // 验证点击索引后列表项的高亮状态
+  fireEvent.click(indexItem)
+  expect(container.querySelector('.nut-elevator-bars-inner-item-active')).toBeTruthy()
 })
📝 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.

Suggested change
// 测试 handleClickItem 和 handleClickIndex 的组合场景 - 使用简化的测试
test('should handle combined click scenarios', () => {
const onItemClick = vi.fn()
const onIndexClick = vi.fn()
const { container, getByText } = render(
<Elevator
list={list}
height={200}
sticky
onIndexClick={(key: string) => testClick(key)}
onItemClick={onItemClick}
onIndexClick={onIndexClick}
/>
)
const listItem = container.querySelectorAll(
'.nut-elevator-bars-inner-item'
)[2]
fireEvent.click(listItem) // 模拟点击
setTimeout(() => {
expect(container.querySelectorAll('.nut-elevator-list-fixed').length).toBe(
1
)
}, 300)
// 简化测试,只确认组件正常渲染和事件处理函数可以被调用
const indexItem = container.querySelector('.nut-elevator-bars-inner-item')
expect(indexItem).toBeTruthy()
const listItem = container.querySelector('.nut-elevator-list-item-name')
expect(listItem).toBeTruthy()
})
// 测试 handleClickItem 和 handleClickIndex 的组合场景 - 使用简化的测试
test('should handle combined click scenarios', () => {
const onItemClick = vi.fn()
const onIndexClick = vi.fn()
const { container, getByText } = render(
<Elevator
list={list}
height={200}
onItemClick={onItemClick}
onIndexClick={onIndexClick}
/>
)
const indexItem = container.querySelector('.nut-elevator-bars-inner-item')
expect(indexItem).toBeTruthy()
const listItem = container.querySelector('.nut-elevator-list-item-name')
expect(listItem).toBeTruthy()
// 测试点击索引
fireEvent.click(indexItem)
expect(onIndexClick).toHaveBeenCalled()
// 测试点击列表项
fireEvent.click(listItem)
expect(onItemClick).toHaveBeenCalled()
// 验证点击索引后列表项的高亮状态
fireEvent.click(indexItem)
expect(container.querySelector('.nut-elevator-bars-inner-item-active')).toBeTruthy()
})

Comment on lines 343 to 352
test('should handle drag start correctly', async () => {
const { container } = render(<Elevator list={list} height={200} />)
const barsInner = container.querySelector('.nut-elevator-bars-inner')

// 模拟拖拽开始
await act(async () => {
trigger(barsInner, 'dragstart', 0, 0)
trigger(barsInner, 'dragmove', 0, 20)
})
})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

拖拽开始测试需要统一事件名称和添加断言

测试中使用了 dragstartdragmove 事件,而不是标准的 touchstarttouchmove 事件,与第 359-361 行的 triggerDrag 方法不一致。同时没有验证拖拽开始后的状态变化。

 test('should handle drag start correctly', async () => {
   const { container } = render(<Elevator list={list} height={200} />)
   const barsInner = container.querySelector('.nut-elevator-bars-inner')

   // 模拟拖拽开始
   await act(async () => {
-    trigger(barsInner, 'dragstart', 0, 0)
-    trigger(barsInner, 'dragmove', 0, 20)
+    trigger(barsInner, 'touchstart', 0, 0)
+    trigger(barsInner, 'touchmove', 0, 20)
   })
+
+  // 验证拖拽开始后滚动指示器显示
+  await waitFor(() => {
+    expect(container.querySelector('.nut-elevator-code-current')).toBeTruthy()
+  })
 })
📝 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.

Suggested change
test('should handle drag start correctly', async () => {
const { container } = render(<Elevator list={list} height={200} />)
const barsInner = container.querySelector('.nut-elevator-bars-inner')
// 模拟拖拽开始
await act(async () => {
trigger(barsInner, 'dragstart', 0, 0)
trigger(barsInner, 'dragmove', 0, 20)
})
})
test('should handle drag start correctly', async () => {
const { container } = render(<Elevator list={list} height={200} />)
const barsInner = container.querySelector('.nut-elevator-bars-inner')
// 模拟拖拽开始
await act(async () => {
trigger(barsInner, 'touchstart', 0, 0)
trigger(barsInner, 'touchmove', 0, 20)
})
// 验证拖拽开始后滚动指示器显示
await waitFor(() => {
expect(container.querySelector('.nut-elevator-code-current')).toBeTruthy()
})
})

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (6)
src/packages/elevator/__tests__/elevator.spec.tsx (6)

238-264: 修复垂直模式下固定标题测试中的异步问题

测试需要等待异步操作完成,当前实现可能导致测试不稳定。

waitFor 前添加 await 确保异步断言完成:

-  waitFor(() => {
+  await waitFor(() => {
     const fixedTitle = container.querySelector('.nut-elevator-list-fixed-title')
     expect(fixedTitle).not.toBeNull()
-  })
+  })

273-278: scrollTo 函数边界情况测试需增强

当前测试过于简化,只验证了组件能够渲染而没有测试实际功能。

建议增强测试以验证 scrollTo 函数的实际功能,例如:

 test('should handle edge cases in scrollTo function', () => {
-  render(<Elevator list={list} height={200} />)
-  // 这个测试只是确认组件能正常渲染,不会因为边界条件而崩溃
-  expect(true).toBeTruthy()
+  const { container } = render(<Elevator list={list} height={200} />)
+  
+  // 获取组件实例
+  const elevatorInstance = container.querySelector('.nut-elevator')
+  
+  // 模拟点击某个索引来触发 scrollTo 函数
+  const indexItem = container.querySelectorAll('.nut-elevator-bars-inner-item')[2]
+  fireEvent.click(indexItem)
+  
+  // 验证滚动发生了变化
+  const listView = container.querySelector('.nut-elevator-list-inner')
+  expect(listView.scrollTop).not.toBe(0)
+  
+  // 测试边界情况:点击不存在的索引
+  // ...
 })

280-286: 拖拽状态变化测试需增强

当前测试过于简化,只验证了组件能够渲染而没有测试实际的拖拽状态变化。

建议增强测试以验证拖拽操作过程中的状态变化,例如验证拖拽开始后滚动指示器显示、拖拽结束后滚动状态重置等。


294-302: listViewScroll 函数测试需增强

当前测试过于简化,只验证了组件能够渲染而没有测试实际的滚动功能。

建议增强测试以验证列表滚动时的索引更新逻辑,例如模拟滚动事件并验证当前索引值的变化。


313-333: 点击处理函数组合测试需增强

当前测试仅验证元素存在,没有执行实际的点击操作或验证函数调用。

建议增强测试以验证实际的点击场景,例如:

 test('should handle combined click scenarios', () => {
   const onItemClick = vi.fn()
   const onIndexClick = vi.fn()

   const { container, getByText } = render(
     <Elevator
       list={list}
       height={200}
       onItemClick={onItemClick}
       onIndexClick={onIndexClick}
     />
   )

-  // 简化测试,只确认组件正常渲染和事件处理函数可以被调用
   const indexItem = container.querySelector('.nut-elevator-bars-inner-item')
   expect(indexItem).toBeTruthy()

   const listItem = container.querySelector('.nut-elevator-list-item-name')
   expect(listItem).toBeTruthy()
+
+  // 测试点击索引
+  fireEvent.click(indexItem)
+  expect(onIndexClick).toHaveBeenCalled()
+
+  // 测试点击列表项
+  fireEvent.click(listItem)
+  expect(onItemClick).toHaveBeenCalled()
+
+  // 验证点击索引后列表项的高亮状态
+  fireEvent.click(indexItem)
+  expect(container.querySelector('.nut-elevator-bars-inner-item-active')).toBeTruthy()
 })

335-341: resetScrollState 函数测试需增强

当前测试过于简化,只验证了组件能够渲染而没有测试实际的状态重置功能。

建议增强测试以验证状态重置后的 UI 变化,例如模拟拖拽然后触发状态重置,验证滚动指示器的消失。

🧹 Nitpick comments (3)
src/packages/elevator/__tests__/elevator.spec.tsx (3)

287-292: calculateHeight 函数测试有待完善

当前测试只验证了列表项目数量,没有验证高度计算的正确性。

建议修改测试以验证高度计算的正确性,例如模拟不同高度的列表项,然后验证计算结果。


303-311: setListGroup 函数测试需增强

当前测试仅验证了列表项目的类名和数量,没有验证列表组的设置逻辑。

建议增强测试以验证 setListGroup 函数的实际功能,例如检查 DOM 元素的引用是否被正确获取。


342-366: 拖拽开始事件测试实现

测试通过 CustomEvent 模拟了拖拽开始事件,但缺少对拖拽开始后状态的验证。

建议添加状态验证,例如检查拖拽开始后滚动指示器是否显示:

   await act(async () => {
     // 触发 onDragStart
     const event = new CustomEvent('dragstart', { detail: dragStartEvent })
     barItem?.dispatchEvent(event)
   })
+
+  // 验证拖拽开始后的状态
+  await waitFor(() => {
+    const currentCode = container.querySelector('.nut-elevator-code-current')
+    expect(currentCode).toBeTruthy()
+  })
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between afaced6 and ee4d188.

📒 Files selected for processing (1)
  • src/packages/elevator/__tests__/elevator.spec.tsx (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/packages/elevator/__tests__/elevator.spec.tsx (1)
src/packages/elevator/elevator.tsx (1)
  • Elevator (30-290)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: test
  • GitHub Check: build
🔇 Additional comments (11)
src/packages/elevator/__tests__/elevator.spec.tsx (11)

112-116: 测试组件垂直模式渲染正确

这个测试正确地验证了当设置 mode="vertical" 属性时,组件会添加 .nut-elevator-vertical 类。


118-122: 验证 showKeys 属性控制索引栏显示

测试正确地验证了当 showKeys={false} 时,不会渲染电梯索引栏。


124-141: 自定义内容渲染测试完善

测试通过传入自定义组件作为 children 属性并验证其渲染结果,充分测试了组件的自定义内容渲染能力。


143-150: 非数字高度值处理测试

测试验证了组件能够正确处理 "50vh" 这样的非数字高度值,属于良好的边界情况测试。


152-156: 空列表渲染测试

测试验证了组件在列表为空时的渲染行为,是必要的边界情况测试。


158-188: 自定义 floorKey 属性测试

测试验证了当使用自定义的 floorKey 属性时,组件能够正确读取并显示对应的标题值。


190-220: 非字符串值渲染测试

测试验证了组件能够正确处理和显示数字等非字符串类型的索引值,这是重要的边界情况测试。


222-236: 索引点击事件参数传递测试

测试验证了点击索引栏项目时传递的参数正确性,逻辑清晰且完整。


266-271: 数据属性缺失的边界情况测试

测试验证了当元素没有 data-index 属性时的行为,这是必要的边界情况测试。


368-397: 拖拽结束事件测试完善

测试正确模拟了拖拽结束事件并验证了状态更新,使用 await waitFor 保证了异步测试的可靠性。


399-455: 完整拖拽流程测试实现

测试模拟了完整的拖拽过程(开始、移动、结束)并验证了最终状态,测试设计全面且包含了必要的状态验证。

@irisSong irisSong requested a review from xiaoyatong May 14, 2025 08:48
@xiaoyatong xiaoyatong merged commit bab9aa7 into jdf2e:feat_v3.x Jun 7, 2025
7 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.x Target branch 3.x action:review This PR needs more reviews (less than 2 approvals) size/XXL
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants