| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | ## Introduction | 3 | ## Introduction |
| 4 | 4 | ||
| 5 | -PullToRefresh is a pull-to-refresh and load-more component available in the OpenHarmony environment. It supports setting various properties for built-in animations, supports custom animations, and supports using LazyForEach data as a data source. | 5 | +PullToRefresh is a pull-to-refresh and pull-up-to-load-more component for OpenHarmony ArkUI. It supports built-in animation configuration, custom animations, and LazyForEach data sources. |
| 6 | 6 | ||
| 7 | ## Demo | 7 | ## Demo |
| 8 | 8 | ||
| @@ -37,7 +37,7 @@ Verified in the following versions: | |||
| 37 | ## Usage Example | 37 | ## Usage Example |
| 38 | 38 | ||
| 39 | ```typescript | 39 | ```typescript |
| 40 | -// How to use the V1 decorator | 40 | +// Usage with @Component |
| 41 | import { PullToRefresh } from '@ohos/pulltorefresh' | 41 | import { PullToRefresh } from '@ohos/pulltorefresh' |
| 42 | 42 | ||
| 43 | // Create a scroller instance. | 43 | // Create a scroller instance. |
| @@ -77,7 +77,7 @@ PullToRefresh({ | |||
| 77 | customRefresh: null, | 77 | customRefresh: null, |
| 78 | }) | 78 | }) |
| 79 | 79 | ||
| 80 | -// How to use the V2 decorator | 80 | +// Usage with @ComponentV2 |
| 81 | import { PullToRefreshV2 } from '@ohos/pulltorefresh' | 81 | import { PullToRefreshV2 } from '@ohos/pulltorefresh' |
| 82 | 82 | ||
| 83 | // Create a scroller instance. | 83 | // Create a scroller instance. |
| @@ -120,15 +120,39 @@ PullToRefreshV2({ | |||
| 120 | 120 | ||
| 121 | For more examples, please see [example code](https://gitcode.com/openharmony-sig/ohos_pull_to_refresh/tree/master/entry/src/main/ets/pages). | 121 | For more examples, please see [example code](https://gitcode.com/openharmony-sig/ohos_pull_to_refresh/tree/master/entry/src/main/ets/pages). |
| 122 | 122 | ||
| 123 | +### bindSheet Pull-Down Dismiss | ||
| 124 | + | ||
| 125 | +When `PullToRefresh` is used inside a half-modal `bindSheet` and the sheet needs to be dismissed by pulling down, disable pull-down refresh with the existing configuration: `new PullToRefreshConfigurator().setHasRefresh(false)`. After pull-down refresh is disabled, the component handles only pull-up loading gestures and does not intercept downward pan gestures. | ||
| 126 | + | ||
| 127 | +ArkUI also requires the scrollable container in the sheet to configure `nestedScroll`. `PullToRefresh` / `PullToRefreshV2` provide the boolean property `enableNestedScroll`. When set to `true`, the internal main `Scroll` applies the default nestedScroll configuration (`scrollForward` and `scrollBackward` are both `NestedScrollMode.SELF_FIRST`). | ||
| 128 | + | ||
| 129 | +```typescript | ||
| 130 | +private refreshConfigurator = new PullToRefreshConfigurator() | ||
| 131 | + .setHasRefresh(false); | ||
| 132 | + | ||
| 133 | +PullToRefresh({ | ||
| 134 | + data: $data, | ||
| 135 | + scroller: this.scroller, | ||
| 136 | + enableNestedScroll: true, | ||
| 137 | + refreshConfigurator: this.refreshConfigurator, | ||
| 138 | + customList: () => { this.getContentView(); }, | ||
| 139 | + // ... | ||
| 140 | +}) | ||
| 141 | +``` | ||
| 142 | + | ||
| 143 | +See `pages/bindSheetPullRefresh.ets` in the entry module for a full sample. | ||
| 144 | + | ||
| 145 | +**Note:** Pull-down refresh and pull-down sheet dismissal both use downward gestures. In sheet-dismiss scenarios, disable pull-down refresh (`setHasRefresh(false)`). Pull-up loading uses an upward gesture and is not affected; it remains functional via `Scroll.onReachEnd` when `enableNestedScroll` is enabled. `setHasRefresh(false)` does not enable nested scrolling by itself. To coordinate nested scrolling between `bindSheet` and the internal Scroll, set `enableNestedScroll: true`. When enabled, pull-down gestures based on `scroller` are not handled; `customList` is wrapped by the internal `Scroll`, and do not bind the same `scroller` to another `List`, `Scroll`, or other scrollable container inside `customList`. V2 sample: `pages/bindSheetPullRefreshV2.ets`. | ||
| 146 | + | ||
| 123 | ## Usage Instructions | 147 | ## Usage Instructions |
| 124 | 148 | ||
| 125 | -### PullToRefresh Component (V1 Decorator) | 149 | +### PullToRefresh Component (@Component) |
| 126 | 150 | ||
| 127 | -The PullToRefresh component is used to wrap List, Scroll, Tabs, Grid, or WaterFlow components to provide pull-down refresh and pull-up load more functionality. | 151 | +The PullToRefresh component wraps List, Scroll, Tabs, Grid, or WaterFlow components to provide pull-down refresh and pull-up load-more functionality. |
| 128 | 152 | ||
| 129 | -### PullToRefreshV2 Component (V2 Decorator) | 153 | +### PullToRefreshV2 Component (@ComponentV2) |
| 130 | 154 | ||
| 131 | -The PullToRefreshV2 component has similar functionality to PullToRefresh but is adapted to the new API specification. | 155 | +PullToRefreshV2 provides the same refresh and load-more capabilities as PullToRefresh and targets @ComponentV2 state management. |
| 132 | 156 | ||
| 133 | ### LazyForEach Data Source Support | 157 | ### LazyForEach Data Source Support |
| 134 | 158 | ||
| @@ -176,9 +200,10 @@ For specific usage, please refer to the OpenHarmony official documentation: [Laz | |||
| 176 | 200 | ||
| 177 | | Property | Type | Description | Default | Required | OpenHarmony Platform Support | | 201 | | Property | Type | Description | Default | Required | OpenHarmony Platform Support | |
| 178 | |----------|------|-------------|---------|----------|------------------------------| | 202 | |----------|------|-------------|---------|----------|------------------------------| |
| 179 | -| data | Object[] \| undefined | Data bound to the list or grid component | undefined | Partial (required in PullToRefresh, not required in PullToRefreshV2) | Yes | | 203 | +| data | Object[] \| undefined | Data bound to the list or grid component | undefined | Yes for PullToRefresh; No for PullToRefreshV2 | Yes | |
| 180 | | scroller | Scroller | Scroller object bound to the list or grid component | undefined | Yes | Yes | | 204 | | scroller | Scroller | Scroller object bound to the list or grid component | undefined | Yes | Yes | |
| 181 | | customList | () => void | Custom main layout, containing list or grid component | undefined | Yes | Yes | | 205 | | customList | () => void | Custom main layout, containing list or grid component | undefined | Yes | Yes | |
| 206 | +| enableNestedScroll | boolean | Whether to enable the built-in nested scroll mode; when enabled, the main area uses `Scroll` and applies the default nestedScroll configuration (`scrollForward` and `scrollBackward` are both `NestedScrollMode.SELF_FIRST`). Pull-down refresh is disabled; pull-up loading is supported via `Scroll.onReachEnd` | false | No | Yes | | ||
| 182 | | refreshConfigurator | PullToRefreshConfigurator | Component property configuration | PullToRefreshConfigurator | No | Yes | | 207 | | refreshConfigurator | PullToRefreshConfigurator | Component property configuration | PullToRefreshConfigurator | No | Yes | |
| 183 | | mWidth | Length | Container width | undefined (adaptive) | No | Yes | | 208 | | mWidth | Length | Container width | undefined (adaptive) | No | Yes | |
| 184 | | mHeight | Length | Container height | undefined (adaptive) | No | Yes | | 209 | | mHeight | Length | Container height | undefined (adaptive) | No | Yes | |
| @@ -2,7 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | ## 简介 | 3 | ## 简介 |
| 4 | 4 | ||
| 5 | -PullToRefresh 是一款 OpenHarmony 环境下可用的下拉刷新、上拉加载组件。支持设置内置动画的各种属性,支持设置自定义动画,支持 LazyForEach 的数据作为数据源。 | 5 | +PullToRefresh 是基于 OpenHarmony ArkUI 的下拉刷新、上拉加载组件。支持配置内置动画属性、自定义动画,以及 LazyForEach 数据源。 |
| 6 | 6 | ||
| 7 | ## 效果展示 | 7 | ## 效果展示 |
| 8 | 8 | ||
| @@ -37,7 +37,7 @@ OpenHarmony ohpm 环境配置等更多内容,请参考[如何安装 OpenHarmon | |||
| 37 | ## 使用示例 | 37 | ## 使用示例 |
| 38 | 38 | ||
| 39 | ```typescript | 39 | ```typescript |
| 40 | -// V1装饰器下的使用方式 | 40 | +// @Component 组件的使用方式 |
| 41 | import { PullToRefresh } from '@ohos/pulltorefresh' | 41 | import { PullToRefresh } from '@ohos/pulltorefresh' |
| 42 | 42 | ||
| 43 | // 需绑定列表或宫格组件 | 43 | // 需绑定列表或宫格组件 |
| @@ -77,7 +77,7 @@ customLoad: null, | |||
| 77 | customRefresh: null, | 77 | customRefresh: null, |
| 78 | }) | 78 | }) |
| 79 | 79 | ||
| 80 | -// V2装饰器下的使用方式 | 80 | +// @ComponentV2 组件的使用方式 |
| 81 | import { PullToRefreshV2 } from '@ohos/pulltorefresh' | 81 | import { PullToRefreshV2 } from '@ohos/pulltorefresh' |
| 82 | 82 | ||
| 83 | // 需绑定列表或宫格组件 | 83 | // 需绑定列表或宫格组件 |
| @@ -120,15 +120,39 @@ PullToRefreshV2({ | |||
| 120 | 120 | ||
| 121 | 更多示例请查看 [示例代码](https://gitcode.com/openharmony-sig/ohos_pull_to_refresh/tree/master/entry/src/main/ets/pages)。 | 121 | 更多示例请查看 [示例代码](https://gitcode.com/openharmony-sig/ohos_pull_to_refresh/tree/master/entry/src/main/ets/pages)。 |
| 122 | 122 | ||
| 123 | +### 半模态 bindSheet 下拉关闭 | ||
| 124 | + | ||
| 125 | +在半模态 `bindSheet` 内使用 `PullToRefresh` 时,若需要下拉关闭 Sheet,可通过已有配置关闭下拉刷新:`new PullToRefreshConfigurator().setHasRefresh(false)`。关闭后,组件仅处理上拉加载手势,不会拦截向下拖动手势。 | ||
| 126 | + | ||
| 127 | +同时,ArkUI 要求半模态中的滚动容器配置 `nestedScroll`。`PullToRefresh` / `PullToRefreshV2` 提供布尔属性 `enableNestedScroll`;设置为 `true` 后,组件内部主体 `Scroll` 会应用默认 nestedScroll 配置(`scrollForward` 与 `scrollBackward` 均为 `NestedScrollMode.SELF_FIRST`)。 | ||
| 128 | + | ||
| 129 | +```typescript | ||
| 130 | +private refreshConfigurator = new PullToRefreshConfigurator() | ||
| 131 | + .setHasRefresh(false); | ||
| 132 | + | ||
| 133 | +PullToRefresh({ | ||
| 134 | + data: $data, | ||
| 135 | + scroller: this.scroller, | ||
| 136 | + enableNestedScroll: true, | ||
| 137 | + refreshConfigurator: this.refreshConfigurator, | ||
| 138 | + customList: () => { this.getContentView(); }, | ||
| 139 | + // ... | ||
| 140 | +}) | ||
| 141 | +``` | ||
| 142 | + | ||
| 143 | +完整示例见 entry 中的 `pages/bindSheetPullRefresh.ets`。 | ||
| 144 | + | ||
| 145 | +**说明**:下拉刷新与下拉关闭 Sheet 均为向下手势,半模态关闭场景建议关闭下拉刷新(`setHasRefresh(false)`)。上拉加载为向上手势,不受影响,开启 `enableNestedScroll` 后仍可通过 `Scroll.onReachEnd` 正常触发。`setHasRefresh(false)` 不会自动启用嵌套滚动;若需要 `bindSheet` 与内部 Scroll 协同,需设置 `enableNestedScroll: true`。开启后组件不再基于 `scroller` 处理下拉手势,`customList` 由内部 `Scroll` 承载,且不要在 `customList` 内将同一 `scroller` 绑定到 `List`、`Scroll` 等滚动容器。V2 示例见 `pages/bindSheetPullRefreshV2.ets`。 | ||
| 146 | + | ||
| 123 | ## 使用说明 | 147 | ## 使用说明 |
| 124 | 148 | ||
| 125 | -### PullToRefresh 组件(V1 装饰器) | 149 | +### PullToRefresh 组件(@Component) |
| 126 | 150 | ||
| 127 | -PullToRefresh 组件用于包装 List、Scroll、Tabs、Grid 或 WaterFlow 组件,提供下拉刷新和上拉加载更多功能。 | 151 | +PullToRefresh 用于包装 List、Scroll、Tabs、Grid 或 WaterFlow 组件,提供下拉刷新和上拉加载更多功能。 |
| 128 | 152 | ||
| 129 | -### PullToRefreshV2 组件(V2 装饰器) | 153 | +### PullToRefreshV2 组件(@ComponentV2) |
| 130 | 154 | ||
| 131 | -PullToRefreshV2 组件功能与 PullToRefresh 类似,但适配了新的 API 规范。 | 155 | +PullToRefreshV2 提供与 PullToRefresh 相同的刷新与加载能力,面向 @ComponentV2 状态管理能力。 |
| 132 | 156 | ||
| 133 | ### LazyForEach 数据源支持 | 157 | ### LazyForEach 数据源支持 |
| 134 | 158 | ||
| @@ -176,14 +200,15 @@ interface DataChangeListener { | |||
| 176 | 200 | ||
| 177 | | 属性 | 类型 | 描述 | 默认值 | 必填 | OpenHarmony 平台支持 | | 201 | | 属性 | 类型 | 描述 | 默认值 | 必填 | OpenHarmony 平台支持 | |
| 178 | |-----|------|------|--------|------|-------------------| | 202 | |-----|------|------|--------|------|-------------------| |
| 179 | -| data | Object[] \| undefined | 列表或宫格组件所绑定的数据 | undefined | 部分(在PullToRefresh中是必填项,在PullToRefreshV2中不是必填项) | 是 | | 203 | +| data | Object[] \| undefined | 列表或宫格组件所绑定的数据 | undefined | PullToRefresh:是;PullToRefreshV2:否 | 是 | |
| 180 | | scroller | Scroller | 列表或宫格组件所绑定的 Scroller 对象 | undefined | 是 | 是 | | 204 | | scroller | Scroller | 列表或宫格组件所绑定的 Scroller 对象 | undefined | 是 | 是 | |
| 181 | | customList | () => void | 自定义主体布局,内部有列表或宫格组件 | undefined | 是 | 是 | | 205 | | customList | () => void | 自定义主体布局,内部有列表或宫格组件 | undefined | 是 | 是 | |
| 206 | +| enableNestedScroll | boolean | 是否启用组件内置嵌套滚动;开启后主体使用 `Scroll` 承载,并应用默认 nestedScroll 配置(`scrollForward` 与 `scrollBackward` 均为 `NestedScrollMode.SELF_FIRST`)。下拉刷新不生效,上拉加载通过 `Scroll.onReachEnd` 触发 | false | 否 | 是 | | ||
| 182 | | refreshConfigurator | PullToRefreshConfigurator | 组件属性配置 | PullToRefreshConfigurator | 否 | 是 | | 207 | | refreshConfigurator | PullToRefreshConfigurator | 组件属性配置 | PullToRefreshConfigurator | 否 | 是 | |
| 183 | | mWidth | Length | 容器宽度 | undefined(自适应) | 否 | 是 | | 208 | | mWidth | Length | 容器宽度 | undefined(自适应) | 否 | 是 | |
| 184 | | mHeight | Length | 容器高度 | undefined(自适应) | 否 | 是 | | 209 | | mHeight | Length | 容器高度 | undefined(自适应) | 否 | 是 | |
| 185 | -| onRefresh | () => Promise\<string\> | 下拉刷新回调 | 1秒后后结束下拉刷新动画并提示"刷新失败" | 否 | 是 | | 210 | +| onRefresh | () => Promise\<string\> | 下拉刷新回调 | 1 秒后结束下拉刷新动画并提示"刷新失败" | 否 | 是 | |
| 186 | -| onLoadMore | () => Promise\<string\> | 上拉加载更多回调 | 1秒后后结束上拉加载动画 | 否 | 是 | | 211 | +| onLoadMore | () => Promise\<string\> | 上拉加载更多回调 | 1 秒后结束上拉加载动画 | 否 | 是 | |
| 187 | | customRefresh | () => void | 自定义下拉刷新动画布局 | undefined | 否 | 是 | | 212 | | customRefresh | () => void | 自定义下拉刷新动画布局 | undefined | 否 | 是 | |
| 188 | | onAnimPullDown | (value?: number, width?: number, height?: number) => void \| undefined | 下拉中回调 | undefined | 否 | 是 | | 213 | | onAnimPullDown | (value?: number, width?: number, height?: number) => void \| undefined | 下拉中回调 | undefined | 否 | 是 | |
| 189 | | onAnimRefreshing | (value?: number, width?: number, height?: number) => void \| undefined | 刷新中回调 | undefined | 否 | 是 | | 214 | | onAnimRefreshing | (value?: number, width?: number, height?: number) => void \| undefined | 刷新中回调 | undefined | 否 | 是 | |
| @@ -0,0 +1,123 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (C) 2026 Huawei Device Co., Ltd. | ||
| 3 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | + * you may not use this file except in compliance with the License. | ||
| 5 | + * You may obtain a copy of the License at | ||
| 6 | + * | ||
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + * | ||
| 9 | + * Unless required by applicable law or agreed to in writing, software | ||
| 10 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | + * See the License for the specific language governing permissions and | ||
| 13 | + * limitations under the License. | ||
| 14 | + */ | ||
| 15 | +import { PullToRefresh, PullToRefreshConfigurator } from '@ohos/pulltorefresh' | ||
| 16 | + | ||
| 17 | +@Entry | ||
| 18 | +@Component | ||
| 19 | +struct BindSheetPullRefresh { | ||
| 20 | + @State sheetVisible: boolean = false; | ||
| 21 | + @State useBindSheetMode: boolean = false; | ||
| 22 | + @State data: Array<string> = ['item 1', 'item 2', 'item 3', 'item 4', 'item 5']; | ||
| 23 | + // 嵌套滚动模式:仅交给 PullToRefresh 内部 Scroll,customList 内不可再绑定 scroller | ||
| 24 | + private nestedScrollScroller: Scroller = new Scroller(); | ||
| 25 | + // 未配置模式:PullToRefresh 与 customList 内 List 共用同一 scroller | ||
| 26 | + private listModeScroller: Scroller = new Scroller(); | ||
| 27 | + private defaultConfigurator: PullToRefreshConfigurator = new PullToRefreshConfigurator(); | ||
| 28 | + private bindSheetConfigurator: PullToRefreshConfigurator = new PullToRefreshConfigurator() | ||
| 29 | + .setHasRefresh(false); | ||
| 30 | + | ||
| 31 | + build() { | ||
| 32 | + Column({ space: 16 }) { | ||
| 33 | + Button('未配置模式(原始 List)') | ||
| 34 | + .onClick(() => { | ||
| 35 | + this.useBindSheetMode = false; | ||
| 36 | + this.sheetVisible = true; | ||
| 37 | + }) | ||
| 38 | + Button('开启嵌套滚动模式') | ||
| 39 | + .onClick(() => { | ||
| 40 | + this.useBindSheetMode = true; | ||
| 41 | + this.sheetVisible = true; | ||
【AI-Review】【一般】【业务功能】【功能实现错误】示例页面bindSheetConfigurator设置setHasLoadMore(false)导致无法验证onReachEnd加载功能 ● 问题:在示例页面
● 影响:一般。示例页面未能完整演示嵌套滚动模式的核心功能(上拉加载 via onReachEnd),可能导致该路径的 Bug 未被覆盖,也会误导开发者认为嵌套滚动模式下不支持上拉加载。 ● 建议:修改示例中嵌套滚动模式的 configurator,保留
或者,如果确实需要展示"仅滚动、无加载"的场景,建议增加第三个按钮来演示"嵌套滚动+上拉加载"模式。 ![]() ![]() | |||
| 42 | + }) | ||
| 43 | + } | ||
| 44 | + .width('100%') | ||
| 45 | + .height('100%') | ||
| 46 | + .justifyContent(FlexAlign.Center) | ||
| 47 | + .bindSheet(this.sheetVisible, this.sheetContent(), { | ||
| 48 | + height: SheetSize.MEDIUM, | ||
| 49 | + dragBar: true, | ||
| 50 | + onDisappear: () => { | ||
| 51 | + this.sheetVisible = false; | ||
| 52 | + } | ||
| 53 | + }) | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + @Builder | ||
| 57 | + private sheetContent() { | ||
| 58 | + Column() { | ||
| 59 | + PullToRefresh({ | ||
| 60 | + data: $data, | ||
| 61 | + scroller: this.useBindSheetMode ? this.nestedScrollScroller : this.listModeScroller, | ||
| 62 | + enableNestedScroll: this.useBindSheetMode, | ||
| 63 | + refreshConfigurator: this.useBindSheetMode ? this.bindSheetConfigurator : this.defaultConfigurator, | ||
| 64 | + customList: () => { | ||
| 65 | + if (this.useBindSheetMode) { | ||
| 66 | + this.sheetColumn(); | ||
| 67 | + } else { | ||
| 68 | + this.sheetList(); | ||
| 69 | + } | ||
| 70 | + }, | ||
【AI-Review】【一般】【基础代码问题】【代码逻辑错误】bindSheetPullRefresh 示例中 List 和 PullToRefresh 共享 scroller 导致冲突 ● 问题: ● 影响: 一般。作为示例代码,可能误导使用者。在实际使用中,当 enableNestedScroll 为 true 时,不应在 customList 内部的 List 上设置 scroller,因为 PullToRefresh 已经在外部 Scroll 上使用了该 scroller。 ● 建议: 在 nestedScroll 模式下,内部的 List 不应设置 scroller,因为滚动由外部的 Scroll 管理。建议在示例中区分两种模式的 List 创建方式:
或在文档中明确说明:当 ![]() ![]() | |||
| 71 | + onLoadMore: () => { | ||
| 72 | + return new Promise<string>((resolve) => { | ||
| 73 | + setTimeout(() => { | ||
| 74 | + this.data = [...this.data, `more ${this.data.length}`]; | ||
| 75 | + resolve(''); | ||
| 76 | + }, 800); | ||
【AI-Review】【一般】【基础代码问题】【代码逻辑错误】示例代码 sheetList() 在非嵌套模式下 scroller 双重绑定,与 README 警告矛盾 ● 问题:示例 实际上,在非嵌套模式下,由于组件内部没有创建 Scroll 容器,scroller 只绑定到 List 一处,这是原有设计——用户传入 scroller,组件用它来检测偏移和执行 scrollEdge/scrollTo,List 也用这个 scroller 做滚动控制,两者操作的是同一个滚动容器,所以不冲突。但问题在于,当切换到嵌套滚动模式时, ● 影响:一般。示例代码作为开发者参考存在设计缺陷,在模式切换场景下可能引发 scroller 冲突。同时,README 的警告措辞需要更精确——不是"不要双重绑定",而是"在嵌套滚动模式下不要双重绑定"。 ● 建议:1) 修改示例代码,为嵌套滚动模式使用独立的 scroller:
![]() ![]() | |||
| 77 | + }); | ||
| 78 | + }, | ||
| 79 | + customLoad: null, | ||
| 80 | + customRefresh: null, | ||
| 81 | + }) | ||
| 82 | + } | ||
| 83 | + .width('100%') | ||
【AI-Review】【一般】【业务功能】【功能实现错误】示例页面在嵌套滚动模式下仍传入 onLoadMore 回调 ● 问题: ● 影响:一般。示例作为开发者参考,传入无效回调会误导使用者对 enableNestedScroll 行为的理解,可能导致开发者在实际项目中错误配置。 ● 建议:在嵌套滚动模式下不传入
或至少在示例中添加注释说明该回调在嵌套滚动模式下不会触发。 ![]() ![]() | |||
| 84 | + .height('100%') | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + @Builder | ||
| 88 | + private sheetList() { | ||
| 89 | + List({ space: 12, scroller: this.listModeScroller }) { | ||
| 90 | + ForEach(this.data, (item: string) => { | ||
| 91 | + ListItem() { | ||
| 92 | + Text(item) | ||
| 93 | + .width('100%') | ||
【AI-Review】【严重】【基础代码问题】【稳定性问题】示例页面中 data.push() 直接修改数组不触发 UI 刷新 ● 问题:在 ● 影响:严重。上拉加载更多后,新条目不会在列表中显示,用户看到的列表不会增长,功能完全失效。 ● 建议:使用数组重新赋值触发状态更新:
![]() ![]() | |||
| 94 | + .height(100) | ||
| 95 | + .fontSize(18) | ||
| 96 | + .textAlign(TextAlign.Center) | ||
| 97 | + .backgroundColor('#95efd2') | ||
| 98 | + } | ||
| 99 | + }) | ||
| 100 | + } | ||
| 101 | + .width('100%') | ||
| 102 | + .height('100%') | ||
| 103 | + .backgroundColor('#eeeeee') | ||
| 104 | + .divider({ strokeWidth: 1, color: 0x222222 }) | ||
| 105 | + .edgeEffect(EdgeEffect.None) | ||
| 106 | + } | ||
【AI-Review】【一般】【业务功能】【示例设计问题】
● 问题: ● 影响: 一般。示例页面无法正确演示嵌套滚动模式的功能,可能导致开发者误用该功能。同时,同一个 scroller 对象在 sheetList() 和 sheetColumn() 两种布局中混用,在切换模式时可能导致 scroller 状态不一致。 ● 建议: 修改 sheetColumn() 使用可滚动的组件,例如使用 List 或 Grid:
或者考虑为两种模式使用不同的 scroller 对象,避免状态冲突:
这样可以更好地展示 nestedScroll 模式下 Scroll 组件与内部可滚动组件的正确协作方式。 ![]() ![]() 【AI-Review】【一般】【基础代码问题】【代码逻辑错误】示例 sheetColumn 使用不可滚动 Column 导致上拉加载误触发 ● 问题: ● 影响:一般。示例页面中上拉加载功能在嵌套滚动模式下会被误触发,无法正确演示该功能的使用方式,可能误导开发者。 ● 建议:
这样外层 ![]() ![]() | |||
| 107 | + | ||
| 108 | + @Builder | ||
| 109 | + private sheetColumn() { | ||
| 110 | + Column({ space: 12 }) { | ||
【AI-Review】【一般】【基础代码问题】【代码逻辑错误】示例中 sheetColumn() 缺少 .height('100%'),嵌套滚动模式下 Scroll 包裹不可滚动内容导致上拉加载失效 ● 问题: ● 影响:一般。示例代码作为开发者参考,存在功能缺陷可能误导使用者认为上拉加载在嵌套滚动模式下可以正常工作。同时 ● 建议:为
![]() ![]() 【AI-Review】【一般】【基础代码问题】【性能和效率问题】sheetColumn()使用Column而非List,大数据量下无懒加载 ● 问题: ● 影响:一般。在数据量较小(如demo中的5项)时影响不大,但作为公开示例代码,可能误导开发者认为 Column 是推荐的实现方式。在实际业务中数据量可能较大,导致性能问题。 ● 建议:将
或者,如果 ![]() ![]() | |||
| 111 | + ForEach(this.data, (item: string) => { | ||
| 112 | + Text(item) | ||
| 113 | + .width('100%') | ||
| 114 | + .height(100) | ||
| 115 | + .fontSize(18) | ||
| 116 | + .textAlign(TextAlign.Center) | ||
| 117 | + .backgroundColor('#95efd2') | ||
| 118 | + }) | ||
| 119 | + } | ||
| 120 | + .width('100%') | ||
【AI-Review】【建议】【基础代码问题】【验证问题】bindSheetPullRefresh 示例中 sheetColumn 缺少高度约束 ● 问题: 在 sheetColumn() Builder 中(第 110-123 行),Column 仅设置了 .width('100%') 而未设置 .height('100%')。当 enableNestedScroll=true 时,此 Column 作为 PullToRefresh 内部 Scroll 的子内容。如果数据项不足以填满 Sheet 可视区域,Column 的高度将仅等于内容高度,导致 .backgroundColor('#eeeeee') 不会填充整个可视区域,出现底部空白。相比之下,sheetList() 的 List 设置了 .height('100%'),两者表现不一致。 ● 影响: 建议。在数据项较少时,Sheet 内嵌套滚动模式的背景色不会填满可视区域,视觉效果不一致。 ● 建议: 为 sheetColumn() 的 Column 添加 .height('100%'),使其与 sheetList() 的视觉效果保持一致。同时 PullToRefreshV2 版本的 demo(bindSheetPullRefreshV2.ets)也存在同样的问题。 ![]() ![]() | |||
| 121 | + .backgroundColor('#eeeeee') | ||
| 122 | + } | ||
| 123 | +} | ||
| @@ -0,0 +1,123 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (C) 2026 Huawei Device Co., Ltd. | ||
| 3 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | + * you may not use this file except in compliance with the License. | ||
| 5 | + * You may obtain a copy of the License at | ||
| 6 | + * | ||
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + * | ||
| 9 | + * Unless required by applicable law or agreed to in writing, software | ||
| 10 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | + * See the License for the specific language governing permissions and | ||
| 13 | + * limitations under the License. | ||
| 14 | + */ | ||
| 15 | +import { PullToRefreshV2, PullToRefreshConfigurator } from '@ohos/pulltorefresh' | ||
| 16 | + | ||
| 17 | +@Entry | ||
| 18 | +@ComponentV2 | ||
| 19 | +struct BindSheetPullRefreshV2 { | ||
| 20 | + @Local sheetVisible: boolean = false; | ||
| 21 | + @Local useBindSheetMode: boolean = false; | ||
| 22 | + @Local data: Array<string> = ['item 1', 'item 2', 'item 3', 'item 4', 'item 5']; | ||
| 23 | + // 嵌套滚动模式:仅交给 PullToRefreshV2 内部 Scroll,customList 内不可再绑定 scroller | ||
| 24 | + private nestedScrollScroller: Scroller = new Scroller(); | ||
| 25 | + // 未配置模式:PullToRefreshV2 与 customList 内 List 共用同一 scroller | ||
| 26 | + private listModeScroller: Scroller = new Scroller(); | ||
| 27 | + private defaultConfigurator: PullToRefreshConfigurator = new PullToRefreshConfigurator(); | ||
| 28 | + private bindSheetConfigurator: PullToRefreshConfigurator = new PullToRefreshConfigurator() | ||
| 29 | + .setHasRefresh(false); | ||
| 30 | + | ||
| 31 | + build() { | ||
| 32 | + Column({ space: 16 }) { | ||
| 33 | + Button('未配置模式(原始 List)') | ||
| 34 | + .onClick(() => { | ||
| 35 | + this.useBindSheetMode = false; | ||
| 36 | + this.sheetVisible = true; | ||
| 37 | + }) | ||
| 38 | + Button('开启嵌套滚动模式') | ||
| 39 | + .onClick(() => { | ||
| 40 | + this.useBindSheetMode = true; | ||
| 41 | + this.sheetVisible = true; | ||
| 42 | + }) | ||
| 43 | + } | ||
| 44 | + .width('100%') | ||
| 45 | + .height('100%') | ||
| 46 | + .justifyContent(FlexAlign.Center) | ||
| 47 | + .bindSheet(this.sheetVisible, this.sheetContent(), { | ||
| 48 | + height: SheetSize.MEDIUM, | ||
| 49 | + dragBar: true, | ||
| 50 | + onDisappear: () => { | ||
| 51 | + this.sheetVisible = false; | ||
| 52 | + } | ||
| 53 | + }) | ||
| 54 | + } | ||
| 55 | + | ||
| 56 | + @Builder | ||
| 57 | + private sheetContent() { | ||
| 58 | + Column() { | ||
| 59 | + PullToRefreshV2({ | ||
| 60 | + data: this.data, | ||
| 61 | + scroller: this.useBindSheetMode ? this.nestedScrollScroller : this.listModeScroller, | ||
| 62 | + enableNestedScroll: this.useBindSheetMode, | ||
| 63 | + refreshConfigurator: this.useBindSheetMode ? this.bindSheetConfigurator : this.defaultConfigurator, | ||
| 64 | + customList: () => { | ||
| 65 | + if (this.useBindSheetMode) { | ||
| 66 | + this.sheetColumn(); | ||
| 67 | + } else { | ||
| 68 | + this.sheetList(); | ||
| 69 | + } | ||
| 70 | + }, | ||
| 71 | + onLoadMore: () => { | ||
| 72 | + return new Promise<string>((resolve) => { | ||
| 73 | + setTimeout(() => { | ||
| 74 | + this.data = [...this.data, `more ${this.data.length}`]; | ||
| 75 | + resolve(''); | ||
| 76 | + }, 800); | ||
| 77 | + }); | ||
| 78 | + }, | ||
| 79 | + customLoad: null, | ||
| 80 | + customRefresh: null, | ||
| 81 | + }) | ||
| 82 | + } | ||
| 83 | + .width('100%') | ||
| 84 | + .height('100%') | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + @Builder | ||
| 88 | + private sheetList() { | ||
| 89 | + List({ space: 12, scroller: this.listModeScroller }) { | ||
| 90 | + ForEach(this.data, (item: string) => { | ||
| 91 | + ListItem() { | ||
| 92 | + Text(item) | ||
| 93 | + .width('100%') | ||
| 94 | + .height(100) | ||
| 95 | + .fontSize(18) | ||
| 96 | + .textAlign(TextAlign.Center) | ||
| 97 | + .backgroundColor('#95efd2') | ||
| 98 | + } | ||
| 99 | + }) | ||
| 100 | + } | ||
| 101 | + .width('100%') | ||
| 102 | + .height('100%') | ||
| 103 | + .backgroundColor('#eeeeee') | ||
| 104 | + .divider({ strokeWidth: 1, color: 0x222222 }) | ||
| 105 | + .edgeEffect(EdgeEffect.None) | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + @Builder | ||
| 109 | + private sheetColumn() { | ||
| 110 | + Column({ space: 12 }) { | ||
| 111 | + ForEach(this.data, (item: string) => { | ||
| 112 | + Text(item) | ||
| 113 | + .width('100%') | ||
| 114 | + .height(100) | ||
| 115 | + .fontSize(18) | ||
| 116 | + .textAlign(TextAlign.Center) | ||
| 117 | + .backgroundColor('#95efd2') | ||
| 118 | + }) | ||
| 119 | + } | ||
| 120 | + .width('100%') | ||
| 121 | + .backgroundColor('#eeeeee') | ||
| 122 | + } | ||
| 123 | +} | ||
| @@ -49,6 +49,11 @@ struct Index { | |||
| 49 | .onClick(() => { | 49 | .onClick(() => { |
| 50 | router.pushUrl({ url: 'pages/lazyForEachGuide' }) | 50 | router.pushUrl({ url: 'pages/lazyForEachGuide' }) |
| 51 | }) | 51 | }) |
| 52 | + Button($r("app.string.bindSheetPullRefresh")) | ||
| 53 | + .margin({ top: 10 }) | ||
| 54 | + .onClick(() => { | ||
| 55 | + router.pushUrl({ url: 'pages/bindSheetPullRefresh' }) | ||
| 56 | + }) | ||
| 52 | } | 57 | } |
| 53 | .width('100%') | 58 | .width('100%') |
| 54 | } | 59 | } |
| @@ -49,6 +49,11 @@ struct Index { | |||
| 49 | .onClick(() => { | 49 | .onClick(() => { |
| 50 | router.pushUrl({ url: 'pages/lazyForEachGuideV2' }) | 50 | router.pushUrl({ url: 'pages/lazyForEachGuideV2' }) |
| 51 | }) | 51 | }) |
| 52 | + Button($r("app.string.bindSheetPullRefresh")) | ||
| 53 | + .margin({ top: 10 }) | ||
| 54 | + .onClick(() => { | ||
| 55 | + router.pushUrl({ url: 'pages/bindSheetPullRefreshV2' }) | ||
| 56 | + }) | ||
| 52 | } | 57 | } |
| 53 | .width('100%') | 58 | .width('100%') |
| 54 | } | 59 | } |
| @@ -83,6 +83,10 @@ | |||
| 83 | { | 83 | { |
| 84 | "name": "Loading", | 84 | "name": "Loading", |
| 85 | "value": "Loading..." | 85 | "value": "Loading..." |
| 86 | + }, | ||
| 87 | + { | ||
| 88 | + "name": "bindSheetPullRefresh", | ||
| 89 | + "value": "bindSheet + PullToRefresh" | ||
| 86 | } | 90 | } |
| 87 | ] | 91 | ] |
| 88 | } | 92 | } |
| @@ -16,6 +16,8 @@ | |||
| 16 | "pages/fullScreenV2", | 16 | "pages/fullScreenV2", |
| 17 | "pages/interface.testV2", | 17 | "pages/interface.testV2", |
| 18 | "pages/demoV1", | 18 | "pages/demoV1", |
| 19 | - "pages/demoV2" | 19 | + "pages/demoV2", |
| 20 | + "pages/bindSheetPullRefresh", | ||
| 21 | + "pages/bindSheetPullRefreshV2" | ||
| 20 | ] | 22 | ] |
| 21 | } | 23 | } |
| @@ -71,6 +71,10 @@ | |||
| 71 | { | 71 | { |
| 72 | "name": "Loading", | 72 | "name": "Loading", |
| 73 | "value": "加载中..." | 73 | "value": "加载中..." |
| 74 | + }, | ||
| 75 | + { | ||
| 76 | + "name": "bindSheetPullRefresh", | ||
| 77 | + "value": "半模态+bindSheet" | ||
| 74 | } | 78 | } |
| 75 | ] | 79 | ] |
| 76 | } | 80 | } |
| @@ -29,6 +29,11 @@ export struct PullToRefresh { | |||
| 29 | @Link data: Object[]; | 29 | @Link data: Object[]; |
| 30 | scroller: Scroller = new Scroller(); | 30 | scroller: Scroller = new Scroller(); |
| 31 | @BuilderParam customList?: () => void; | 31 | @BuilderParam customList?: () => void; |
| 32 | + /** | ||
| 33 | + * 启用后组件主体使用内置 `Scroll` 承载内容并应用默认 `.nestedScroll(...)` 配置。 | ||
| 34 | + * customList 内部不应再使用同一个 scroller 绑定 List/Scroll 等滚动容器。 | ||
| 35 | + */ | ||
| 36 | + enableNestedScroll?: boolean = false; | ||
【AI-Review】【建议】【基础代码问题】【验证问题】PullToRefresh V1 中 enableNestedScroll 无响应式装饰器,动态变更不会触发 UI 更新 ● 问题: 在 对比 ● 影响: 建议。在 bindSheetPullRefresh 示例中, ● 建议: 将 PullToRefresh V1 中的
这样父组件动态改变时,组件能正确响应并切换渲染模式。 ![]() ![]() | |||
| 32 | refreshConfigurator?: PullToRefreshConfigurator; | 37 | refreshConfigurator?: PullToRefreshConfigurator; |
【AI-Review】【严重】【基础代码问题】【代码逻辑错误】PullToRefresh V1 中 enableNestedScroll 无状态装饰器,无法响应父组件更新 ● 问题:在 PullToRefresh(V1 @Component)中,enableNestedScroll 声明为普通可选属性 enableNestedScroll?: boolean = false,没有 @Prop 或 @Link 装饰器。而 V2 组件 PullToRefreshV2 中相同属性使用了 @Param enableNestedScroll?: boolean = false。V1 中,如果父组件动态改变 enableNestedScroll 的值(例如通过状态变量绑定),PullToRefresh 组件内部不会感知到变化,build() 中的 if (this.enableNestedScroll === true) 分支不会重新渲染,导致组件行为与预期不一致。 ● 影响:严重。V1 组件中 enableNestedScroll 实际上变成了只读初始值,后续任何动态变更都不会生效。这与 V2 组件的行为不一致,违反了 V1/V2 提供一致能力的设计目标。开发者在 V1 中动态切换 enableNestedScroll 时会得到静默失败的结果。 ● 建议:将 V1 组件中的 enableNestedScroll 声明为 @Prop enableNestedScroll?: boolean = false,确保能响应父组件的状态变更。与 V2 的 @Param 保持对等的能力。 ![]() ![]() | |||
| 33 | mWidth?: Length = '100%'; | 38 | mWidth?: Length = '100%'; |
| 34 | mHeight?: Length = '100%'; | 39 | mHeight?: Length = '100%'; |
| @@ -81,6 +86,8 @@ export struct PullToRefresh { | |||
| 81 | private refreshPoint2x?: number = 0; | 86 | private refreshPoint2x?: number = 0; |
| 82 | private refreshPoint2y?: number = 0; | 87 | private refreshPoint2y?: number = 0; |
| 83 | private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down }) | 88 | private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down }) |
| 89 | + private isLoadingMore: boolean = false; | ||
| 90 | + private isDestroyed: boolean = false; | ||
| 84 | uiContext: UIContext | undefined = undefined; | 91 | uiContext: UIContext | undefined = undefined; |
| 85 | 92 | ||
| 86 | aboutToAppear() { | 93 | aboutToAppear() { |
【AI-Review】【一般】【稳定性问题】【资源管理问题】timer 未在组件销毁时清理 ● 问题:组件中使用 ● 影响:一般。可能导致内存泄漏,特别是在频繁切换页面或快速关闭 Sheet 的场景下。 ● 建议:添加
同时,在 ![]() ![]() | |||
| @@ -90,6 +97,14 @@ export struct PullToRefresh { | |||
| 90 | } | 97 | } |
| 91 | } | 98 | } |
【AI-Review】【一般】【基础代码问题】【验证问题】enableNestedScroll 与 hasRefresh/hasLoadMore 不兼容时缺乏运行时校验 ● 问题: 当 enableNestedScroll=true 且 hasRefresh=true 或 hasLoadMore=true 时,组件不会给出任何提示,但 PanGesture 被 early-return 完全禁用,导致刷新/加载更多功能静默失效。用户可能花费大量时间调试才能发现此配置组合不可用。 ● 影响: 一般。配置不兼容时功能静默失效,降低库的可用性和调试体验。虽然 README 提到需配合 setHasRefresh(false) 使用,但作为公开 API 的三方库,运行时校验是必要的防御性编程。 ● 建议: 在 aboutToAppear 中添加配置校验,当检测到不兼容配置时输出警告日志。例如在 PullToRefresh.ets 的 aboutToAppear 方法中,当 enableNestedScroll 为 true 时检查 refreshConfigurator 的 hasRefresh 和 hasLoadMore,若为 true 则通过 LogUtil 输出警告信息,提示用户调用 setHasRefresh(false) 或 setHasLoadMore(false)。PullToRefreshV2.ets 中也应添加同样的校验逻辑。 ![]() ![]() 【AI-Review】【严重】【基础代码问题】【稳定性问题】组件缺少 aboutToDisappear 清理 timer ● 问题: PullToRefresh 组件在 ● 影响: 严重。在 bindSheet 场景下关闭 Sheet 后,定时器仍在运行,每 10ms 执行一次回调,持续修改已销毁组件的状态,可能导致运行时异常或不可预期的行为。对于频繁打开/关闭 Sheet 的用户,还可能累积多个未清除的定时器。 ● 建议: 添加
同时,在 ![]() ![]() | |||
| 92 | 99 | ||
| 100 | + aboutToDisappear() { | ||
【AI-Review】【严重】【基础代码问题】【资源使用问题】aboutToDisappear 未追踪和清理 setTimeout,组件销毁后可能执行回调 ● 问题: 新增的
这些 ● 影响: 严重。组件销毁后 setTimeout 回调仍执行,可能引发运行时异常。在 bindSheet 快速开关场景下尤其容易触发。 ● 建议: 将所有 setTimeout 的返回值保存到实例变量中,并在
PullToRefreshV2.ets(第 688 行同样有未追踪的 setTimeout)也需要同步修复。 ![]() ![]() | |||
| 101 | + this.isDestroyed = true; | ||
| 102 | + if (this.timer !== undefined) { | ||
| 103 | + clearInterval(this.timer); | ||
| 104 | + this.timer = undefined; | ||
| 105 | + } | ||
| 106 | + } | ||
| 107 | + | ||
| 93 | private initCanvas(): void { | 108 | private initCanvas(): void { |
| 94 | if (this.refreshRingOx == 0) { | 109 | if (this.refreshRingOx == 0) { |
| 95 | 110 | ||
| @@ -121,13 +136,73 @@ export struct PullToRefresh { | |||
| 121 | .backgroundColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshBackgroundColor() : 0) | 136 | .backgroundColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshBackgroundColor() : 0) |
| 122 | 137 | ||
| 123 | // 主体列表部分 | 138 | // 主体列表部分 |
【AI-Review】【一般】【基础代码问题】【稳定性问题】嵌套滚动模式下 Scroll 缺少 edgeEffect 配置 ● 问题:在嵌套滚动模式中,内部 ● 影响:一般。在边缘场景下(enableNestedScroll=true + hasRefresh=true),下拉刷新手势检测可能不稳定,出现偶发性无法触发刷新的问题。同时,与非嵌套模式的 edgeEffect 配置不一致,行为不可预测。 ● 建议:为嵌套滚动模式的内部 Scroll 显式设置
![]() ![]() 【AI-Review】【一般】【软件设计】【冗余重复代码】PullToRefresh 与 PullToRefreshV2 的 nestedScroll 分支逻辑完全重复 ● 问题: ● 影响:一般。增加维护成本,未来修改 nestedScroll 相关逻辑时需要同步修改两处,遗漏概率高。 ● 建议:考虑将 nestedScroll 相关逻辑提取为共享函数或通过组合模式复用。短期可在代码中添加明确注释标记该段逻辑需要与另一组件同步维护,并确保本次 PR 中两组件的 nestedScroll 逻辑完全一致(当前 PullToRefresh.ets:179 有 ![]() ![]() | |||
| 124 | - Column() { | 139 | + if (this.enableNestedScroll === true) { |
| 125 | - if (this.customList !== undefined) { | 140 | + Scroll(this.scroller) { |
【AI-Review】【严重】【基础代码问题】【代码逻辑错误】嵌套滚动模式下 scroller 双重绑定导致 currentOffset() 返回值不可预测 ● 问题:当 ● 影响:严重。在 bindSheet + enableNestedScroll=true 的实际使用场景中,如果用户在 customList 内部使用了 List 并绑定了同一个 scroller(这是常见用法),手势检测逻辑会完全失效,可能导致下拉刷新误触发或无法触发、上拉加载失效。README 仅用一行注释提醒"不要绑定同一个 scroller",但这与组件原有的 scroller 传参设计直接冲突——现有用户升级后若启用 enableNestedScroll,代码会静默产生行为异常。 ● 建议:方案一(推荐):当启用嵌套滚动模式时,不应将 scroller 传给内部 Scroll,而是新建内部专用 scroller,外部传入的 scroller 仅用于手势检测时操作 customList 内部的滚动容器。方案二:在 README 和代码注释中更显著地警告此限制,并在组件初始化时(aboutToAppear)检测 enableNestedScroll=true 时打印警告日志,提醒开发者 customList 内部不应使用同一个 scroller。同时修改示例代码 ![]() ![]() 【AI-Review】【严重】【基础代码问题】【代码逻辑错误】enableNestedScroll 模式下 Scroll 缺少 edgeEffect(EdgeEffect.None),导致 bindSheet 无法通过滚动关闭 ● 问题: ● 影响:严重。这是 bindSheet + PullToRefresh 场景的核心交互缺陷——用户无法通过在内容顶部向下滚动来关闭 Sheet,只能通过 Sheet 顶部 dragBar 操作,严重影响用户体验。 ● 建议:在 enableNestedScroll 模式下的 Scroll 组件添加
同样适用于 PullToRefreshV2.ets 中的相同代码段。 ![]() ![]() 【AI-Review】【严重】【基础代码问题】【代码逻辑错误】enableNestedScroll模式下Scroll与customList内List的scroller冲突风险 ● 问题:当 ● 影响:严重。scroller 冲突会导致滚动位置跳跃、滚动卡顿、甚至功能失效。这个问题在 bindSheet 场景下更容易出现,因为用户需要区分两种模式的 scroller 用法。 ● 建议:方案一(推荐):在
方案二:在 ![]() ![]() | |||
| 126 | - this.customList() | 141 | + if (this.customList !== undefined) { |
【AI-Review】【一般】【基础代码问题】【代码逻辑错误】嵌套滚动模式下高度计算方式不一致,可能导致上下拉动画位移偏差 ● 问题:非嵌套滚动模式下,主体区域使用精确高度计算: ● 影响:一般。在启用 enableNestedScroll 后执行下拉刷新或上拉加载动画时,主体区域高度变化可能与非嵌套模式有细微差异,导致动画不够流畅或出现视觉抖动。 ● 建议:保持两种模式下高度计算方式一致,嵌套滚动模式也使用精确高度计算而非 layoutWeight:
![]() ![]() | |||
| 142 | + this.customList() | ||
| 143 | + } | ||
【AI-Review】【严重】【基础代码问题】【稳定性问题】onLoadMore回调在组件销毁后仍会执行 ● 问题: 在 ● 影响: 严重。在bindSheet场景下,用户在加载更多过程中关闭Sheet,回调会在已销毁的组件上执行,可能导致应用崩溃或状态异常。 ● 建议: 添加一个
同时建议对 ![]() ![]() 【AI-Review】【一般】【基础代码问题】【代码逻辑错误】onReachEnd在内容不足以滚动时可能立即触发加载更多 ● 问题: 当 ● 影响: 一般。在数据量较少的场景下(如demo中初始仅5条数据),如果Sheet高度足够容纳所有内容,用户一打开Sheet就会看到加载更多的动画,影响用户体验。 ● 建议: 在
或者更稳健的方式是使用 ![]() ![]() 【AI-Review】【一般】【软件设计】【冗余重复代码】onReachEnd中加载动画逻辑与onActionEnd大量重复 ● 问题: ● 影响: 一般。重复代码增加了维护成本,如果后续修改加载动画逻辑(如调整动画步长、添加新的动画回调),容易遗漏其中一处,导致两种触发路径的行为不一致。 ● 建议: 将加载更多动画逻辑提取为独立方法:
然后在 ![]() ![]() 【AI-Review】【严重】【基础代码问题】【代码逻辑错误】enableNestedScroll 为 true 时 Scroll 模式下 trYBottom 无法正常更新导致加载更多功能异常 ● 问题: 当 ● 影响: 严重。在 bindSheet + enableNestedScroll 场景下,如果 ● 建议: 在 nestedScroll 模式下,应该使用 Scroll 组件的
![]() ![]() | |||
| 127 | } | 144 | } |
| 145 | + .edgeEffect(EdgeEffect.None) | ||
| 146 | + .onReachEnd(() => { | ||
【AI-Review】【严重】【业务功能】【功能实现错误】enableNestedScroll 模式下 onReachEnd 自动触发加载更多,丧失了原有的"上拉阈值触发"用户体验 ● 问题:当
这意味着在嵌套滚动模式下,只要列表内容不够填满视口或用户快速滑动到底部,就会自动加载更多数据,用户无法预览当前内容而不触发加载。 ● 影响:严重。这是一个未在 API 文档或注释中说明的行为差异,可能导致:1)用户在 bindSheet 场景中不经意间触发大量加载;2)与原有"上拉加载更多"的交互预期不一致;3)无法通过 ● 建议:1)在 ![]() ![]() | |||
| 147 | + if (this.refreshConfigurator?.getHasLoadMore() && this.onLoadMore && !this.isLoadingMore) { | ||
| 148 | + this.isLoadingMore = true; | ||
【AI-Review】【一般】【业务功能】【功能实现错误】onReachEnd 直接跳到 IS_LOADING 状态,跳过上拉加载的两阶段过渡,footer UI 出现突兀 ● 问题: 在 ● 影响: 一般。enableNestedScroll 模式下加载更多的 footer 出现过于突兀,缺少过渡动画,用户体验不如非嵌套模式。 ● 建议: 在
PullToRefreshV2.ets 的 onReachEnd 同样适用。 ![]() ![]() | |||
| 149 | + this.state = IS_LOADING; | ||
| 150 | + let maxTranslate = this.refreshConfigurator.getMaxTranslate(); | ||
| 151 | + if (maxTranslate !== undefined) { | ||
| 152 | + let translateY = -maxTranslate * 0.75; | ||
| 153 | + this.uiContext?.animateTo({ duration: 0 }, () => { | ||
| 154 | + this.trYBottom = translateY; | ||
| 155 | + }); | ||
| 156 | + } | ||
| 157 | + clearInterval(this.timer); | ||
| 158 | + this.value = 0; | ||
| 159 | + this.timer = setInterval(() => { | ||
| 160 | + if (this.value !== undefined) { | ||
| 161 | + if (this.value >= 1) { | ||
| 162 | + this.value -= 1; | ||
| 163 | + } else { | ||
| 164 | + this.value += 0.01; | ||
| 165 | + } | ||
| 166 | + this.value = Math.round(this.value * 100) / 100; | ||
| 167 | + if (!this.customLoad) { | ||
| 168 | + this.drawLoadView(false, this.value); | ||
| 169 | + } | ||
| 170 | + } | ||
| 171 | + if (this.onAnimLoading) { | ||
| 172 | + if (this.trYBottom !== undefined) { | ||
| 173 | + this.onAnimLoading(this.value, this.mWidthNumber, -this.trYBottom); | ||
| 174 | + } | ||
| 175 | + } | ||
| 176 | + }, 10); | ||
| 177 | + this.onLoadMore().then(() => { | ||
| 178 | + if (this.isDestroyed) return; | ||
| 179 | + this.closeLoad(); | ||
| 180 | + this.isLoadingMore = false; | ||
| 181 | + }).catch((error: Error) => { | ||
| 182 | + if (this.isDestroyed) return; | ||
| 183 | + LogUtil.error(`onLoadMore error: ${error}`); | ||
| 184 | + this.closeLoad(); | ||
| 185 | + this.isLoadingMore = false; | ||
| 186 | + }); | ||
| 187 | + } | ||
| 188 | + }) | ||
| 189 | + .nestedScroll({ | ||
| 190 | + scrollForward: NestedScrollMode.SELF_FIRST, | ||
| 191 | + scrollBackward: NestedScrollMode.SELF_FIRST | ||
| 192 | + }) | ||
| 193 | + .width('100%') | ||
| 194 | + .height((this.mHeightNumber !== undefined ? this.mHeightNumber : 0) - | ||
| 195 | + (this.trYTop !== undefined ? this.trYTop : 0) + (this.trYBottom !== undefined ? this.trYBottom : 0)) | ||
| 196 | + } else { | ||
| 197 | + Column() { | ||
| 198 | + if (this.customList !== undefined) { | ||
| 199 | + this.customList() | ||
| 200 | + } | ||
| 201 | + } | ||
| 202 | + .width('100%') | ||
| 203 | + .height((this.mHeightNumber !== undefined ? this.mHeightNumber : 0) - | ||
| 204 | + (this.trYTop !== undefined ? this.trYTop : 0) + (this.trYBottom !== undefined ? this.trYBottom : 0)) | ||
| 128 | } | 205 | } |
| 129 | - .width('100%') | ||
| 130 | - .height((this.mHeightNumber !== undefined ? this.mHeightNumber : 0) - (this.trYTop !== undefined ? this.trYTop : 0) + (this.trYBottom !== undefined ? this.trYBottom : 0)) | ||
| 131 | 206 | ||
| 132 | // 上拉加载动画部分 | 207 | // 上拉加载动画部分 |
| 133 | Stack() { | 208 | Stack() { |
| @@ -146,16 +221,25 @@ export struct PullToRefresh { | |||
| 146 | .parallelGesture( | 221 | .parallelGesture( |
【AI-Review】【一般】【基础代码问题】【性能和效率问题】enableNestedScroll 模式下 parallelGesture 仍被注册,无意义地拦截手势事件 ● 问题:当 enableNestedScroll === true 时,所有 PanGesture 回调(onActionStart、onActionUpdate、onActionEnd)均立即 return,但 .parallelGesture(PanGesture(...)) 仍被附加到组件上。parallelGesture 会在手势识别阶段就参与事件分发,即便回调不做任何处理,手势仍被并行识别和消费。这会与内部 Scroll 的嵌套滚动以及 bindSheet 的下拉关闭手势产生不必要的竞争,可能导致手势响应延迟或滚动不跟手。 ● 影响:一般。在 bindSheet + enableNestedScroll 场景下,多余的 parallelGesture 注册会增加手势仲裁开销,可能导致下拉关闭 Sheet 时手势识别不够灵敏,或滚动时出现轻微卡顿。 ● 建议:当 enableNestedScroll 为 true 时,跳过 parallelGesture 的注册。可以通过条件判断在 build() 中使用两个不同的 Column 分支:enableNestedScroll 为 true 时不附加 parallelGesture,为 false 时正常注册。PullToRefreshV2.ets 同理。 ![]() ![]() 【AI-Review】【一般】【基础代码问题】【性能和效率问题】enableNestedScroll 为 true 时 parallelGesture 仍注册但永不生效,造成不必要的手势识别开销 ● 问题:当 此外, ● 影响:一般。每次滚动操作多出三次无效回调调用,以及不必要的 PanGesture 识别开销。在极端情况下,parallelGesture 与内部 Scroll 的手势可能产生微妙的竞争,虽然 parallelGesture 设计为并行工作,但在 ArkUI 的特定版本中不能完全排除手势冲突的可能。 ● 建议:当
或在 PullToRefreshV2.ets 存在相同问题。 ![]() ![]() | |||
| 147 | PanGesture(this.panOption) | 222 | PanGesture(this.panOption) |
| 148 | .onActionStart((event?: GestureEvent) => { | 223 | .onActionStart((event?: GestureEvent) => { |
| 224 | + if (this.enableNestedScroll === true) { | ||
| 225 | + return; | ||
| 226 | + } | ||
| 149 | if (event !== undefined) { | 227 | if (event !== undefined) { |
| 150 | this.touchYOld = event.offsetY; | 228 | this.touchYOld = event.offsetY; |
| 151 | } | 229 | } |
| 152 | }) | 230 | }) |
| 153 | .onActionUpdate((event?: GestureEvent) => { | 231 | .onActionUpdate((event?: GestureEvent) => { |
| 232 | + if (this.enableNestedScroll === true) { | ||
| 233 | + return; | ||
| 234 | + } | ||
| 154 | if (event !== undefined) { | 235 | if (event !== undefined) { |
| 155 | this.onActionUpdate(event); | 236 | this.onActionUpdate(event); |
| 156 | } | 237 | } |
| 157 | }) | 238 | }) |
| 158 | .onActionEnd(() => { | 239 | .onActionEnd(() => { |
| 240 | + if (this.enableNestedScroll === true) { | ||
| 241 | + return; | ||
| 242 | + } | ||
| 159 | this.onActionEnd(); | 243 | this.onActionEnd(); |
| 160 | }) | 244 | }) |
| 161 | ) | 245 | ) |
| @@ -29,6 +29,11 @@ export struct PullToRefreshV2 { | |||
| 29 | @Param data?: Object[] | undefined = undefined; | 29 | @Param data?: Object[] | undefined = undefined; |
| 30 | @Param scroller: Scroller = new Scroller(); | 30 | @Param scroller: Scroller = new Scroller(); |
| 31 | @BuilderParam customList?: () => void; | 31 | @BuilderParam customList?: () => void; |
| 32 | + /** | ||
| 33 | + * 启用后组件主体使用内置 `Scroll` 承载内容并应用默认 `.nestedScroll(...)` 配置。 | ||
| 34 | + * customList 内部不应再使用同一个 scroller 绑定 List/Scroll 等滚动容器。 | ||
| 35 | + */ | ||
| 36 | + @Param enableNestedScroll?: boolean = false; | ||
| 32 | @Param refreshConfigurator?: PullToRefreshConfigurator = new PullToRefreshConfigurator(); | 37 | @Param refreshConfigurator?: PullToRefreshConfigurator = new PullToRefreshConfigurator(); |
| 33 | @Param mWidth?: Length = '100%'; | 38 | @Param mWidth?: Length = '100%'; |
| 34 | @Param mHeight?: Length = '100%'; | 39 | @Param mHeight?: Length = '100%'; |
| @@ -81,6 +86,8 @@ export struct PullToRefreshV2 { | |||
| 81 | private refreshPoint2x?: number = 0; | 86 | private refreshPoint2x?: number = 0; |
| 82 | private refreshPoint2y?: number = 0; | 87 | private refreshPoint2y?: number = 0; |
| 83 | private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down }) | 88 | private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Up | PanDirection.Down }) |
| 89 | + private isLoadingMore: boolean = false; | ||
| 90 | + private isDestroyed: boolean = false; | ||
| 84 | uiContext: UIContext | undefined = undefined; | 91 | uiContext: UIContext | undefined = undefined; |
| 85 | 92 | ||
| 86 | aboutToAppear() { | 93 | aboutToAppear() { |
【AI-Review】【一般】【稳定性问题】【资源管理问题】PullToRefreshV2 缺少 aboutToDisappear 清理 timer ● 问题:PullToRefreshV2 组件同样使用了 ● 影响:一般。可能导致内存泄漏,特别是在使用 @ComponentV2 的场景下,组件生命周期管理更严格,应确保资源正确释放。 ● 建议:与 PullToRefresh V1 组件保持一致,添加
![]() ![]() | |||
| @@ -90,6 +97,14 @@ export struct PullToRefreshV2 { | |||
| 90 | // } | 97 | // } |
| 91 | } | 98 | } |
【AI-Review】【一般】【基础代码问题】【验证问题】enableNestedScroll 与 hasRefresh/hasLoadMore 不兼容时缺乏运行时校验 ● 问题: 当 enableNestedScroll=true 且 hasRefresh=true 或 hasLoadMore=true 时,组件不会给出任何提示,但 PanGesture 被 early-return 完全禁用,导致刷新/加载更多功能静默失效。此问题与 PullToRefresh.ets 相同。 ● 影响: 一般。配置不兼容时功能静默失效,降低库的可用性和调试体验。 ● 建议: 在 aboutToAppear 中添加与 PullToRefresh.ets 相同的配置校验逻辑,当 enableNestedScroll 为 true 时检查 refreshConfigurator 的 hasRefresh 和 hasLoadMore,若为 true 则通过 LogUtil 输出警告信息。 ![]() ![]() 【AI-Review】【严重】【基础代码问题】【稳定性问题】组件缺少 aboutToDisappear 清理 timer ● 问题: PullToRefreshV2 组件同样在 ● 影响: 严重。与 PullToRefresh V1 相同的问题,在 bindSheet 等动态创建/销毁组件的场景下尤为危险。 ● 建议: 添加
![]() ![]() | |||
| 92 | 99 | ||
| 100 | + aboutToDisappear() { | ||
【AI-Review】【严重】【基础代码问题】【资源使用问题】PullToRefreshV2 aboutToDisappear 同样未追踪 setTimeout ● 问题: 与 PullToRefresh.ets 相同的问题。PullToRefreshV2.ets 的 ● 影响: 严重。组件销毁后回调仍执行,可能引发异常。 ● 建议: 将 setTimeout 返回值保存到实例变量,在 ![]() ![]() | |||
| 101 | + this.isDestroyed = true; | ||
| 102 | + if (this.timer !== undefined) { | ||
| 103 | + clearInterval(this.timer); | ||
| 104 | + this.timer = undefined; | ||
| 105 | + } | ||
| 106 | + } | ||
| 107 | + | ||
| 93 | private initCanvas(): void { | 108 | private initCanvas(): void { |
| 94 | if (this.refreshRingOx == 0) { | 109 | if (this.refreshRingOx == 0) { |
| 95 | if (this.canvasRefresh !== undefined && this.refreshConfigurator !== undefined) { | 110 | if (this.canvasRefresh !== undefined && this.refreshConfigurator !== undefined) { |
| @@ -120,13 +135,72 @@ export struct PullToRefreshV2 { | |||
| 120 | .backgroundColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshBackgroundColor() : 0) | 135 | .backgroundColor(this.refreshConfigurator !== undefined ? this.refreshConfigurator.getRefreshBackgroundColor() : 0) |
| 121 | 136 | ||
| 122 | // 主体列表部分 | 137 | // 主体列表部分 |
| 123 | - Column() { | 138 | + if (this.enableNestedScroll === true) { |
| 124 | - if (this.customList !== undefined) { | 139 | + Scroll(this.scroller) { |
【AI-Review】【严重】【基础代码问题】【代码逻辑错误】enableNestedScroll 模式下 Scroll 缺少 edgeEffect(EdgeEffect.None),导致 bindSheet 无法通过滚动关闭 ● 问题: ● 影响:严重。这是 bindSheet + PullToRefresh 场景的核心交互缺陷——用户无法通过在内容顶部向下滚动来关闭 Sheet,只能通过 Sheet 顶部 dragBar 操作,严重影响用户体验。 ● 建议:在 enableNestedScroll 模式下的 Scroll 组件添加
同样适用于 PullToRefresh.ets 中的相同代码段。 ![]() ![]() | |||
| 125 | - this.customList() | 140 | + if (this.customList !== undefined) { |
| 141 | + this.customList() | ||
【AI-Review】【一般】【基础代码问题】【稳定性问题】PullToRefreshV2 嵌套滚动模式下 Scroll 同样缺少 edgeEffect 配置 ● 问题:与 PullToRefresh.ets 相同,PullToRefreshV2 的嵌套滚动模式中内部 ● 影响:一般。与 PullToRefresh.ets 同理,在 enableNestedScroll=true + hasRefresh=true 的边缘场景下,下拉刷新手势检测可能不稳定。 ● 建议:为嵌套滚动模式的内部 Scroll 显式设置
![]() ![]() | |||
| 142 | + } | ||
【AI-Review】【一般】【基础代码问题】【稳定性问题】PullToRefreshV2的onLoadMore回调同样存在组件销毁后执行问题 ● 问题: PullToRefreshV2组件的 ● 影响: 一般。与V1相同,在bindSheet场景下关闭Sheet时可能导致运行时异常。由于V2使用 ● 建议: 与V1相同,添加 ![]() ![]() | |||
| 126 | } | 143 | } |
| 144 | + .edgeEffect(EdgeEffect.None) | ||
| 145 | + .onReachEnd(() => { | ||
| 146 | + if (this.refreshConfigurator?.getHasLoadMore() && this.onLoadMore && !this.isLoadingMore) { | ||
| 147 | + this.isLoadingMore = true; | ||
| 148 | + this.state = IS_LOADING; | ||
| 149 | + let maxTranslate = this.refreshConfigurator.getMaxTranslate(); | ||
| 150 | + if (maxTranslate !== undefined) { | ||
| 151 | + let translateY = -maxTranslate * 0.75; | ||
| 152 | + this.uiContext?.animateTo({ duration: 0 }, () => { | ||
| 153 | + this.trYBottom = translateY; | ||
| 154 | + }); | ||
| 155 | + } | ||
| 156 | + clearInterval(this.timer); | ||
| 157 | + this.value = 0; | ||
| 158 | + this.timer = setInterval(() => { | ||
| 159 | + if (this.value !== undefined) { | ||
| 160 | + if (this.value >= 1) { | ||
| 161 | + this.value -= 1; | ||
| 162 | + } else { | ||
| 163 | + this.value += 0.01; | ||
| 164 | + } | ||
| 165 | + this.value = Math.round(this.value * 100) / 100; | ||
| 166 | + if (!this.customLoad) { | ||
| 167 | + this.drawLoadView(false, this.value); | ||
| 168 | + } | ||
| 169 | + } | ||
| 170 | + if (this.onAnimLoading) { | ||
| 171 | + if (this.trYBottom !== undefined) { | ||
| 172 | + this.onAnimLoading(this.value, this.mWidthNumber, -this.trYBottom); | ||
| 173 | + } | ||
| 174 | + } | ||
| 175 | + }, 10); | ||
| 176 | + this.onLoadMore().then(() => { | ||
| 177 | + if (this.isDestroyed) return; | ||
| 178 | + this.closeLoad(); | ||
| 179 | + this.isLoadingMore = false; | ||
| 180 | + }).catch((error: Error) => { | ||
| 181 | + if (this.isDestroyed) return; | ||
| 182 | + LogUtil.error(`onLoadMore error: ${error}`); | ||
| 183 | + this.closeLoad(); | ||
| 184 | + this.isLoadingMore = false; | ||
| 185 | + }); | ||
| 186 | + } | ||
| 187 | + }) | ||
| 188 | + .nestedScroll({ | ||
| 189 | + scrollForward: NestedScrollMode.SELF_FIRST, | ||
| 190 | + scrollBackward: NestedScrollMode.SELF_FIRST | ||
| 191 | + }) | ||
| 192 | + .width('100%') | ||
| 193 | + .height((this.mHeightNumber !== undefined ? this.mHeightNumber : 0) - | ||
| 194 | + (this.trYTop !== undefined ? this.trYTop : 0) + (this.trYBottom !== undefined ? this.trYBottom : 0)) | ||
| 195 | + } else { | ||
| 196 | + Column() { | ||
| 197 | + if (this.customList !== undefined) { | ||
| 198 | + this.customList() | ||
| 199 | + } | ||
| 200 | + } | ||
| 201 | + .width('100%') | ||
| 202 | + .height((this.mHeightNumber !== undefined ? this.mHeightNumber : 0) - (this.trYTop !== undefined ? this.trYTop : 0) + (this.trYBottom !== undefined ? this.trYBottom : 0)) | ||
| 127 | } | 203 | } |
| 128 | - .width('100%') | ||
| 129 | - .height((this.mHeightNumber !== undefined ? this.mHeightNumber : 0) - (this.trYTop !== undefined ? this.trYTop : 0) + (this.trYBottom !== undefined ? this.trYBottom : 0)) | ||
| 130 | 204 | ||
| 131 | // 上拉加载动画部分 | 205 | // 上拉加载动画部分 |
| 132 | Stack() { | 206 | Stack() { |
| @@ -145,16 +219,25 @@ export struct PullToRefreshV2 { | |||
| 145 | .parallelGesture( | 219 | .parallelGesture( |
| 146 | PanGesture(this.panOption) | 220 | PanGesture(this.panOption) |
| 147 | .onActionStart((event?: GestureEvent) => { | 221 | .onActionStart((event?: GestureEvent) => { |
| 222 | + if (this.enableNestedScroll === true) { | ||
| 223 | + return; | ||
| 224 | + } | ||
| 148 | if (event !== undefined) { | 225 | if (event !== undefined) { |
| 149 | this.touchYOld = event.offsetY; | 226 | this.touchYOld = event.offsetY; |
| 150 | } | 227 | } |
| 151 | }) | 228 | }) |
| 152 | .onActionUpdate((event?: GestureEvent) => { | 229 | .onActionUpdate((event?: GestureEvent) => { |
| 230 | + if (this.enableNestedScroll === true) { | ||
| 231 | + return; | ||
| 232 | + } | ||
| 153 | if (event !== undefined) { | 233 | if (event !== undefined) { |
| 154 | this.onActionUpdate(event); | 234 | this.onActionUpdate(event); |
| 155 | } | 235 | } |
| 156 | }) | 236 | }) |
| 157 | .onActionEnd(() => { | 237 | .onActionEnd(() => { |
| 238 | + if (this.enableNestedScroll === true) { | ||
| 239 | + return; | ||
| 240 | + } | ||
| 158 | this.onActionEnd(); | 241 | this.onActionEnd(); |
| 159 | }) | 242 | }) |
| 160 | ) | 243 | ) |
【AI-Review】【建议】【软件设计】【冗余重复代码】PullToRefresh 与 PullToRefreshV2 的 getPanDirection 逻辑完全重复 ● 问题: ● 影响:建议。增加了维护成本和引入不一致 bug 的风险。 ● 建议:考虑将 ![]() ![]() | |||


【AI-Review】【一般】【业务功能】【功能实现错误】示例页面未展示 enableNestedScroll + 上拉加载场景
● 问题:
bindSheetConfigurator设置了.setHasLoadMore(false),当useBindSheetMode为 true 时,onReachEnd中的this.refreshConfigurator?.getHasLoadMore()为 false,上拉加载不会触发。但 PR 描述中明确说明"开启后,上拉加载改为通过 Scroll.onReachEnd 自动触发(onLoadMore 仍可用)",示例未能展示该核心功能场景。● 影响:一般。开发者无法从示例中了解如何在
enableNestedScroll模式下使用上拉加载功能,可能导致误用。● 建议:增加一个
enableNestedScroll + setHasLoadMore(true)的示例场景,或者在现有示例中将bindSheetConfigurator的setHasLoadMore设为true,以完整展示嵌套滚动下的上拉加载能力。