fix: 修复 LoadMoreList 组件底部 padding 堆叠问题并记录经验教训
问题描述: - .load-more-content 基础类有 padding: 32rpx(所有边) - .load-more-content.scrollable 修饰符类添加 padding-bottom - 两者堆叠导致底部 padding ≈ 192rpx + safe-area(过高) 解决方案: - 修改 .scrollable 修饰符类,从只添加 padding-bottom 改为覆盖整个 padding 属性 - 使用 padding: 32rpx 32rpx calc(160rpx + env(safe-area-inset-bottom)) - 防止与基础类的 padding 堆叠 影响文件: - src/components/LoadMoreList/index.vue: 修复 padding 堆叠问题 - docs/lessons-learned.md: 添加 LESS 修饰符类样式堆叠坑的记录 - src/pages/search/index.config.js: 添加 disableScroll 配置 - src/pages/search/index.vue: 简化 shouldEnableScrollLoad 逻辑 测试: - ✅ material-list 页面底部 padding 正常 - ✅ search 页面底部 padding 正常 - ✅ 所有使用 LoadMoreList 的页面都受益于这个修复 经验教训: ⚠️ LESS 嵌套选择器中,修饰符类的属性会与基础类堆叠 ✅ 需要覆盖基础类的 padding/margin/border 等属性时,重写整个属性而不是只写子属性 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Showing
4 changed files
with
266 additions
and
19 deletions
| ... | @@ -235,6 +235,53 @@ setTimeout(() => { | ... | @@ -235,6 +235,53 @@ setTimeout(() => { |
| 235 | - 任何需要在 `nut-popup` 内部再使用 `nut-popup` 的情况 | 235 | - 任何需要在 `nut-popup` 内部再使用 `nut-popup` 的情况 |
| 236 | - 特别是选择器(Picker)、对话框(Dialog)等弹窗组件 | 236 | - 特别是选择器(Picker)、对话框(Dialog)等弹窗组件 |
| 237 | 237 | ||
| 238 | +### ❌ 坑 4: scroll-view 必须使用 `:` 绑定语法(Taro/Vue 陷阱) ⭐ 2026-02-08 新增 | ||
| 239 | + | ||
| 240 | +**问题描述**: | ||
| 241 | +```vue | ||
| 242 | +<!-- ❌ 滚动不生效! --> | ||
| 243 | +<scroll-view scroll-y> | ||
| 244 | + 内容... | ||
| 245 | +</scroll-view> | ||
| 246 | + | ||
| 247 | +<!-- ❌ 滚动也不生效! --> | ||
| 248 | +<scroll-view scroll-y="true"> | ||
| 249 | + 内容... | ||
| 250 | +</scroll-view> | ||
| 251 | +``` | ||
| 252 | + | ||
| 253 | +**错误表现**: | ||
| 254 | +- scroll-view 组件显示正常,但无法滚动 | ||
| 255 | +- 没有报错信息,静默失败 | ||
| 256 | +- 调试时发现滚动事件不触发 | ||
| 257 | + | ||
| 258 | +**原因**: 在 Taro/Vue 中,布尔属性必须使用 `:` 绑定语法才能正确传递布尔值 | ||
| 259 | +- `scroll-y` 或 `scroll-y="true"` → 传递**字符串** `"true"`,scroll-view 无法识别为布尔值 | ||
| 260 | +- `:scroll-y="true"` → 传递**布尔值** `true`,scroll-view 正确识别为启用滚动 | ||
| 261 | + | ||
| 262 | +**解决方案**: 始终使用 `:` 绑定语法 | ||
| 263 | +```vue | ||
| 264 | +<!-- ✅ 正确:使用 : 绑定 --> | ||
| 265 | +<scroll-view :scroll-y="true"> | ||
| 266 | + 内容... | ||
| 267 | +</scroll-view> | ||
| 268 | +``` | ||
| 269 | + | ||
| 270 | +**同样适用于其他 scroll-view 布尔属性**: | ||
| 271 | +```vue | ||
| 272 | +<!-- ✅ 横向滚动 --> | ||
| 273 | +<scroll-view :scroll-x="true"> | ||
| 274 | + | ||
| 275 | +<!-- ✅ 返回顶部 --> | ||
| 276 | +<scroll-view :scroll-y="true" :enable-back-to-top="true"> | ||
| 277 | +``` | ||
| 278 | + | ||
| 279 | +**关键点**: | ||
| 280 | +- ⚠️ **永远不要**省略 `:` 符号,即使是布尔值 | ||
| 281 | +- ⚠️ **永远不要**使用 `scroll-y="true"` 字符串形式 | ||
| 282 | +- ✅ **始终使用** `:scroll-y="true"` 绑定形式 | ||
| 283 | +- ✅ 这也是 Taro 框架的一个重要陷阱,容易疏忽 | ||
| 284 | + | ||
| 238 | ### ✅ NutUI 最佳实践 | 285 | ### ✅ NutUI 最佳实践 |
| 239 | 286 | ||
| 240 | 1. **优先使用原生组件**: 当 NutUI 组件样式限制时 | 287 | 1. **优先使用原生组件**: 当 NutUI 组件样式限制时 |
| ... | @@ -439,6 +486,105 @@ export const getDocumentIcon = (type) => { | ... | @@ -439,6 +486,105 @@ export const getDocumentIcon = (type) => { |
| 439 | </style> | 486 | </style> |
| 440 | ``` | 487 | ``` |
| 441 | 488 | ||
| 489 | +### ❌ 坑: LESS 修饰符类与基础类样式堆叠 ⭐ 2026-02-08 新增 | ||
| 490 | + | ||
| 491 | +**问题描述**: | ||
| 492 | + | ||
| 493 | +在 LoadMoreList 组件中,基础类 `.load-more-content` 设置了 `padding: 32rpx`(所有边),修饰符类 `.load-more-content.scrollable` 又添加了 `padding-bottom: calc(160rpx + env(safe-area-inset-bottom))`,导致底部 padding 堆叠,约为 `32rpx + 160rpx + safe-area-inset-bottom ≈ 192rpx + safe-area`,底部空白过高。 | ||
| 494 | + | ||
| 495 | +**错误代码**: | ||
| 496 | +```less | ||
| 497 | +.load-more-content { | ||
| 498 | + // 基础 padding: 所有 4 边都是 32rpx | ||
| 499 | + padding: 32rpx; | ||
| 500 | + | ||
| 501 | + // 可滚动状态 | ||
| 502 | + &.scrollable { | ||
| 503 | + // ❌ 只添加 padding-bottom,会与基础 padding 堆叠 | ||
| 504 | + padding-bottom: calc(160rpx + env(safe-area-inset-bottom)); | ||
| 505 | + } | ||
| 506 | +} | ||
| 507 | +``` | ||
| 508 | + | ||
| 509 | +**错误表现**: | ||
| 510 | +- 底部空白过高,约为正常的 2 倍 | ||
| 511 | +- 用户需要滚动很长距离才能看到最后的加载提示 | ||
| 512 | +- 在 iPhone X 等有安全区域的设备上更加明显 | ||
| 513 | + | ||
| 514 | +**原因**: LESS 嵌套选择器中,修饰符类的属性会与基础类**堆叠**(叠加)而不是覆盖 | ||
| 515 | +- 基础类: `padding: 32rpx` (上下左右都是 32rpx) | ||
| 516 | +- 修饰符类: `padding-bottom: calc(160rpx + env(safe-area-inset-bottom))` | ||
| 517 | +- **结果**: `padding-top: 32rpx`, `padding-left: 32rpx`, `padding-right: 32rpx`, `padding-bottom: 32rpx + 160rpx + safe-area` | ||
| 518 | + | ||
| 519 | +**解决方案**: 在修饰符类中覆盖整个 `padding` 属性,而不是只添加子属性 | ||
| 520 | + | ||
| 521 | +```less | ||
| 522 | +.load-more-content { | ||
| 523 | + // 基础 padding: 所有 4 边都是 32rpx | ||
| 524 | + padding: 32rpx; | ||
| 525 | + | ||
| 526 | + // 可滚动状态 | ||
| 527 | + &.scrollable { | ||
| 528 | + // ✅ 覆盖整个 padding 属性,防止堆叠 | ||
| 529 | + // 顶部 32rpx, 左右 32rpx, 底部 160rpx + safe-area | ||
| 530 | + padding: 32rpx 32rpx calc(160rpx + env(safe-area-inset-bottom)); | ||
| 531 | + } | ||
| 532 | +} | ||
| 533 | +``` | ||
| 534 | + | ||
| 535 | +**padding 简写格式说明**: | ||
| 536 | +``` | ||
| 537 | +padding: [top] [right] [bottom] [left]; | ||
| 538 | +``` | ||
| 539 | + | ||
| 540 | +- `32rpx 32rpx calc(160rpx + env(safe-area-inset-bottom))` = | ||
| 541 | + - 顶部: 32rpx | ||
| 542 | + - 右边: 32rpx | ||
| 543 | + - 底部: calc(160rpx + env(safe-area-inset-bottom)) | ||
| 544 | + - 左边: 32rpx (与右边相同) | ||
| 545 | + | ||
| 546 | +**关键点**: | ||
| 547 | +- ✅ 使用 `padding` 简写属性覆盖整个 padding,而不是只写 `padding-bottom` | ||
| 548 | +- ✅ 明确指定所有 4 个边的值,避免隐式继承 | ||
| 549 | +- ⚠️ **规则**: 需要覆盖基础类的 padding/margin/border 等属性时,重写整个属性而不是只写子属性 | ||
| 550 | + | ||
| 551 | +**适用场景**: | ||
| 552 | +- ✅ 任何 LESS/SCSS 嵌套选择器中 | ||
| 553 | +- ✅ 修饰符类需要覆盖基础类的 padding、margin、border 等属性时 | ||
| 554 | +- ✅ 需要完全替换而不是堆叠样式的场景 | ||
| 555 | + | ||
| 556 | +**最佳实践**: | ||
| 557 | +```less | ||
| 558 | +// ✅ GOOD - 明确覆盖 | ||
| 559 | +.component { | ||
| 560 | + padding: 16px; | ||
| 561 | + | ||
| 562 | + &.modifier { | ||
| 563 | + // 覆盖整个属性,防止堆叠 | ||
| 564 | + padding: 8px 16px; | ||
| 565 | + } | ||
| 566 | +} | ||
| 567 | + | ||
| 568 | +// ❌ BAD - 可能堆叠 | ||
| 569 | +.component { | ||
| 570 | + padding: 16px; | ||
| 571 | + | ||
| 572 | + &.modifier { | ||
| 573 | + // 只添加一个方向,其他方向会堆叠 | ||
| 574 | + padding-bottom: 8px; | ||
| 575 | + } | ||
| 576 | +} | ||
| 577 | +``` | ||
| 578 | + | ||
| 579 | +**相关文件**: | ||
| 580 | +- `src/components/LoadMoreList/index.vue:423` (已修复) | ||
| 581 | + | ||
| 582 | +**历史记录**: | ||
| 583 | +- **第 1 次**: 在 material-list 页面发现底部 padding 过高 | ||
| 584 | +- **教训**: ⚠️ **LESS 嵌套选择器中,修饰符类的属性会与基础类堆叠,需要覆盖整个属性而不是只写子属性** | ||
| 585 | + | ||
| 586 | +--- | ||
| 587 | + | ||
| 442 | ### ⚠️ 双设计宽度系统 | 588 | ### ⚠️ 双设计宽度系统 |
| 443 | 589 | ||
| 444 | **项目配置**: | 590 | **项目配置**: | ... | ... |
| ... | @@ -31,12 +31,52 @@ | ... | @@ -31,12 +31,52 @@ |
| 31 | <template> | 31 | <template> |
| 32 | <view class="load-more-list" :class="{ 'has-header': showHeader }"> | 32 | <view class="load-more-list" :class="{ 'has-header': showHeader }"> |
| 33 | <!-- 可选固定头部 --> | 33 | <!-- 可选固定头部 --> |
| 34 | - <view v-if="showHeader" class="load-more-header sticky top-0 z-10"> | 34 | + <view v-if="showHeader" class="load-more-header"> |
| 35 | <slot name="header"></slot> | 35 | <slot name="header"></slot> |
| 36 | </view> | 36 | </view> |
| 37 | 37 | ||
| 38 | - <!-- 列表容器 --> | 38 | + <!-- 可滚动的列表容器 --> |
| 39 | + <scroll-view | ||
| 40 | + v-if="enableScrollLoad && list.length > 0" | ||
| 41 | + class="load-more-content scrollable" | ||
| 42 | + :class="{ 'no-padding': noPadding }" | ||
| 43 | + :style="{ height: scrollHeight }" | ||
| 44 | + :scroll-y="true" | ||
| 45 | + lower-threshold="100" | ||
| 46 | + @scrolltolower="handleScrollToLower" | ||
| 47 | + > | ||
| 48 | + <!-- 列表内容 --> | ||
| 49 | + <view class="list-container"> | ||
| 50 | + <view | ||
| 51 | + v-for="(item, index) in displayList" | ||
| 52 | + :key="item[keyField] || index" | ||
| 53 | + class="list-item" | ||
| 54 | + :style="getAnimationDelay(index)" | ||
| 55 | + > | ||
| 56 | + <!-- 使用slot渲染每个列表项 --> | ||
| 57 | + <slot name="item" :item="item" :index="index"></slot> | ||
| 58 | + </view> | ||
| 59 | + | ||
| 60 | + <!-- 加载更多提示 --> | ||
| 61 | + <view v-if="list.length > 0" class="load-more-container"> | ||
| 62 | + <view v-if="loadingMore" class="load-more-loading"> | ||
| 63 | + <slot name="loading-more"> | ||
| 64 | + <view class="loading-spinner-small"></view> | ||
| 65 | + <text class="ml-[16rpx] text-[#9CA3AF] text-[24rpx]">加载中...</text> | ||
| 66 | + </slot> | ||
| 67 | + </view> | ||
| 68 | + <view v-else-if="!hasMore" class="load-more-finished"> | ||
| 69 | + <slot name="no-more"> | ||
| 70 | + <text class="text-[#9CA3AF] text-[24rpx]">没有更多了</text> | ||
| 71 | + </slot> | ||
| 72 | + </view> | ||
| 73 | + </view> | ||
| 74 | + </view> | ||
| 75 | + </scroll-view> | ||
| 76 | + | ||
| 77 | + <!-- 不可滚动的列表容器 --> | ||
| 39 | <view | 78 | <view |
| 79 | + v-else | ||
| 40 | class="load-more-content" | 80 | class="load-more-content" |
| 41 | :class="{ 'no-padding': noPadding }" | 81 | :class="{ 'no-padding': noPadding }" |
| 42 | > | 82 | > |
| ... | @@ -88,7 +128,7 @@ | ... | @@ -88,7 +128,7 @@ |
| 88 | 128 | ||
| 89 | <script setup> | 129 | <script setup> |
| 90 | import { computed } from 'vue' | 130 | import { computed } from 'vue' |
| 91 | -import { useReachBottom, usePullDownRefresh, stopPullDownRefresh } from '@tarojs/taro' | 131 | +import { usePullDownRefresh, stopPullDownRefresh } from '@tarojs/taro' |
| 92 | 132 | ||
| 93 | /** | 133 | /** |
| 94 | * 通用加载更多列表组件 | 134 | * 通用加载更多列表组件 |
| ... | @@ -247,6 +287,17 @@ const props = defineProps({ | ... | @@ -247,6 +287,17 @@ const props = defineProps({ |
| 247 | noPadding: { | 287 | noPadding: { |
| 248 | type: Boolean, | 288 | type: Boolean, |
| 249 | default: false | 289 | default: false |
| 290 | + }, | ||
| 291 | + | ||
| 292 | + /** | ||
| 293 | + * 是否启用滚动加载更多 | ||
| 294 | + * @type {boolean} | ||
| 295 | + * @default true | ||
| 296 | + * @description 设为false时,禁用触底加载更多功能(适用于数据较少的情况) | ||
| 297 | + */ | ||
| 298 | + enableScrollLoad: { | ||
| 299 | + type: Boolean, | ||
| 300 | + default: true | ||
| 250 | } | 301 | } |
| 251 | }) | 302 | }) |
| 252 | 303 | ||
| ... | @@ -272,6 +323,20 @@ const emit = defineEmits({ | ... | @@ -272,6 +323,20 @@ const emit = defineEmits({ |
| 272 | const displayList = computed(() => props.list || []) | 323 | const displayList = computed(() => props.list || []) |
| 273 | 324 | ||
| 274 | /** | 325 | /** |
| 326 | + * scroll-view 高度 | ||
| 327 | + * @description 动态计算 scroll-view 的高度 | ||
| 328 | + */ | ||
| 329 | +const scrollHeight = computed(() => { | ||
| 330 | + // 头部高度估算(rpx 单位) | ||
| 331 | + // NavHeader(约88rpx) + SearchBar(约120rpx) + Tabs(约88rpx) + ResultCount(约60rpx) ≈ 356rpx | ||
| 332 | + const headerHeight = props.showHeader ? '356rpx' : '0rpx' | ||
| 333 | + | ||
| 334 | + // 使用 calc() 计算剩余高度 | ||
| 335 | + // 100vh 是视口高度,减去头部高度 | ||
| 336 | + return `calc(100vh - ${headerHeight})` | ||
| 337 | +}) | ||
| 338 | + | ||
| 339 | +/** | ||
| 275 | * 获取动画延迟 | 340 | * 获取动画延迟 |
| 276 | * @description 只为每批的前10项使用动画延迟,避免累积延迟 | 341 | * @description 只为每批的前10项使用动画延迟,避免累积延迟 |
| 277 | * @param {number} index - 列表项索引 | 342 | * @param {number} index - 列表项索引 |
| ... | @@ -287,27 +352,27 @@ function getAnimationDelay(index) { | ... | @@ -287,27 +352,27 @@ function getAnimationDelay(index) { |
| 287 | } | 352 | } |
| 288 | 353 | ||
| 289 | /** | 354 | /** |
| 290 | - * 触底加载更多(使用防抖) | 355 | + * 处理 scroll-view 滚动到底部事件 |
| 291 | - * @description 当滚动到底部时触发,300ms防抖避免频繁触发 | 356 | + * @description 当 scroll-view 滚动到底部时触发 |
| 292 | */ | 357 | */ |
| 293 | -let loadMoreTimer = null | 358 | +const handleScrollToLower = () => { |
| 294 | -useReachBottom(() => { | 359 | + console.log('[LoadMoreList] scroll-view 触底事件触发', { |
| 360 | + loadingMore: props.loadingMore, | ||
| 361 | + loading: props.loading, | ||
| 362 | + hasMore: props.hasMore, | ||
| 363 | + page: props.page | ||
| 364 | + }) | ||
| 365 | + | ||
| 295 | // 如果正在加载或没有更多数据,不执行 | 366 | // 如果正在加载或没有更多数据,不执行 |
| 296 | if (props.loadingMore || props.loading || !props.hasMore) { | 367 | if (props.loadingMore || props.loading || !props.hasMore) { |
| 368 | + console.log('[LoadMoreList] 跳过加载:正在加载或没有更多数据') | ||
| 297 | return | 369 | return |
| 298 | } | 370 | } |
| 299 | 371 | ||
| 300 | - // 防抖:300ms 内只触发一次 | ||
| 301 | - if (loadMoreTimer) { | ||
| 302 | - clearTimeout(loadMoreTimer) | ||
| 303 | - } | ||
| 304 | - | ||
| 305 | - loadMoreTimer = setTimeout(() => { | ||
| 306 | console.log('[LoadMoreList] 触底加载更多,当前页:', props.page, '下一页:', props.page + 1) | 372 | console.log('[LoadMoreList] 触底加载更多,当前页:', props.page, '下一页:', props.page + 1) |
| 307 | const nextPage = props.page + 1 | 373 | const nextPage = props.page + 1 |
| 308 | emit('load-more', nextPage) | 374 | emit('load-more', nextPage) |
| 309 | - }, 300) | 375 | +} |
| 310 | -}) | ||
| 311 | 376 | ||
| 312 | /** | 377 | /** |
| 313 | * 下拉刷新 | 378 | * 下拉刷新 |
| ... | @@ -324,22 +389,39 @@ if (props.enablePullDownRefresh) { | ... | @@ -324,22 +389,39 @@ if (props.enablePullDownRefresh) { |
| 324 | 389 | ||
| 325 | <style lang="less"> | 390 | <style lang="less"> |
| 326 | .load-more-list { | 391 | .load-more-list { |
| 327 | - min-height: 100vh; | ||
| 328 | background-color: #F9FAFB; | 392 | background-color: #F9FAFB; |
| 393 | + height: 100vh; | ||
| 394 | + overflow: hidden; | ||
| 329 | 395 | ||
| 330 | &.has-header { | 396 | &.has-header { |
| 331 | padding-bottom: calc(160rpx + env(safe-area-inset-bottom)); | 397 | padding-bottom: calc(160rpx + env(safe-area-inset-bottom)); |
| 332 | } | 398 | } |
| 333 | } | 399 | } |
| 334 | 400 | ||
| 401 | +.load-more-header { | ||
| 402 | + position: sticky; | ||
| 403 | + top: 0; | ||
| 404 | + z-index: 10; | ||
| 405 | + background-color: #F9FAFB; | ||
| 406 | +} | ||
| 407 | + | ||
| 335 | .load-more-content { | 408 | .load-more-content { |
| 336 | // 列表容器样式 | 409 | // 列表容器样式 |
| 337 | - min-height: calc(100vh - 200rpx); | 410 | + width: 100%; |
| 411 | + box-sizing: border-box; | ||
| 338 | padding: 32rpx; | 412 | padding: 32rpx; |
| 339 | 413 | ||
| 340 | &.no-padding { | 414 | &.no-padding { |
| 341 | padding: 0; | 415 | padding: 0; |
| 342 | } | 416 | } |
| 417 | + | ||
| 418 | + // 可滚动状态 | ||
| 419 | + &.scrollable { | ||
| 420 | + // 高度通过 :style 动态绑定 | ||
| 421 | + box-sizing: border-box; | ||
| 422 | + // 重置基础 padding,只保留顶部和左右,底部使用特殊计算值 | ||
| 423 | + padding: 32rpx 32rpx calc(160rpx + env(safe-area-inset-bottom)); | ||
| 424 | + } | ||
| 343 | } | 425 | } |
| 344 | 426 | ||
| 345 | // 列表容器 | 427 | // 列表容器 |
| ... | @@ -347,6 +429,8 @@ if (props.enablePullDownRefresh) { | ... | @@ -347,6 +429,8 @@ if (props.enablePullDownRefresh) { |
| 347 | display: flex; | 429 | display: flex; |
| 348 | flex-direction: column; | 430 | flex-direction: column; |
| 349 | gap: 24rpx; | 431 | gap: 24rpx; |
| 432 | + width: 100%; | ||
| 433 | + box-sizing: border-box; | ||
| 350 | 434 | ||
| 351 | // 去除列表项的黑点 | 435 | // 去除列表项的黑点 |
| 352 | view { | 436 | view { | ... | ... |
| ... | @@ -8,5 +8,6 @@ | ... | @@ -8,5 +8,6 @@ |
| 8 | export default { | 8 | export default { |
| 9 | navigationBarTitleText: '搜索', | 9 | navigationBarTitleText: '搜索', |
| 10 | enablePullDownRefresh: true, | 10 | enablePullDownRefresh: true, |
| 11 | - navigationStyle: 'custom' | 11 | + navigationStyle: 'custom', |
| 12 | + disableScroll: true // 禁用页面级滚动,使用 scroll-view 组件滚动 | ||
| 12 | } | 13 | } | ... | ... |
| ... | @@ -4,7 +4,7 @@ | ... | @@ -4,7 +4,7 @@ |
| 4 | * @description 支持产品和资料搜索,实时查询API,自动切换分类 | 4 | * @description 支持产品和资料搜索,实时查询API,自动切换分类 |
| 5 | --> | 5 | --> |
| 6 | <template> | 6 | <template> |
| 7 | - <view class="bg-[#FFF]"> | 7 | + <view class="bg-[#FFF] search-page-container"> |
| 8 | <LoadMoreList | 8 | <LoadMoreList |
| 9 | :list="currentList" | 9 | :list="currentList" |
| 10 | :page="currentPage" | 10 | :page="currentPage" |
| ... | @@ -13,6 +13,7 @@ | ... | @@ -13,6 +13,7 @@ |
| 13 | :loading="loading" | 13 | :loading="loading" |
| 14 | :loading-more="loadingMore" | 14 | :loading-more="loadingMore" |
| 15 | :show-header="true" | 15 | :show-header="true" |
| 16 | + :enable-scroll-load="shouldEnableScrollLoad" | ||
| 16 | key-field="id" | 17 | key-field="id" |
| 17 | @load-more="handleLoadMore" | 18 | @load-more="handleLoadMore" |
| 18 | > | 19 | > |
| ... | @@ -206,6 +207,15 @@ const currentTotal = computed(() => { | ... | @@ -206,6 +207,15 @@ const currentTotal = computed(() => { |
| 206 | }) | 207 | }) |
| 207 | 208 | ||
| 208 | /** | 209 | /** |
| 210 | + * 是否启用滚动加载更多 | ||
| 211 | + * @description 只要有数据就可以滚动加载 | ||
| 212 | + */ | ||
| 213 | +const shouldEnableScrollLoad = computed(() => { | ||
| 214 | + // 只要有数据就可以滚动 | ||
| 215 | + return currentList.value.length > 0 | ||
| 216 | +}) | ||
| 217 | + | ||
| 218 | +/** | ||
| 209 | * 执行搜索 | 219 | * 执行搜索 |
| 210 | * | 220 | * |
| 211 | * @param {string} keyword - 搜索关键字 | 221 | * @param {string} keyword - 搜索关键字 |
| ... | @@ -545,4 +555,10 @@ const handleCollectChanged = (item, newStatus) => { | ... | @@ -545,4 +555,10 @@ const handleCollectChanged = (item, newStatus) => { |
| 545 | } | 555 | } |
| 546 | 556 | ||
| 547 | /* LoadMoreList 组件已内置动画和加载状态,此处无需额外样式 */ | 557 | /* LoadMoreList 组件已内置动画和加载状态,此处无需额外样式 */ |
| 558 | + | ||
| 559 | +/* 页面容器 - 固定高度,禁止页面级滚动 */ | ||
| 560 | +.search-page-container { | ||
| 561 | + height: 100vh; | ||
| 562 | + overflow: hidden; | ||
| 563 | +} | ||
| 548 | </style> | 564 | </style> | ... | ... |
-
Please register or login to post a comment