topfans/docs/superpowers/specs/2026-06-11-square-tab-scroll-behavior-design.md

6.4 KiB
Raw Blame History

Square Tab 滚动行为差异化设计

日期: 2026-06-11 状态: 已通过 spec 评审 作者: Claude (brainstorming session)

背景

square.vue 页面通过 activeContentTab 切换 星河 / 星榜 / 广场 三个内容。 当前所有 tab 共用同一个外层 <scroll-view class="content-wrapper" scroll-y> 导致三个 tab 的滚动行为完全一致:都是页面整体滚动。

需求:三个 tab 的滚动行为需要差异化:

Tab 期望行为
星河 (xinghe) 完全禁用滚动(外层 + 内部都不滚)
星榜 (xingbang) 页面不滚动,只有 HotCategoryBlock 内部 grid 区域可滚
广场 (guangchang) 保持当前行为(外层 scroll-view 整体滚动)

目标

  1. 不重构页面骨架
  2. 不修改 StarGalaxy 任何样式(保留 top: -128rpxmin-height: 1440rpx 等视觉设计)
  3. 仅通过外层 scroll-viewscroll-y 动态绑定 + HotCategoryBlock 内部增加独立 scroll 容器达成
  4. 切换 tab 时重置外层 scrollTop避免残留滚动位置

设计

1. 模板改动 (frontend/pages/square/square.vue)

唯一改动:把 <scroll-view> 上的 scroll-y 从静态改为动态绑定。

<scroll-view
  class="content-wrapper"
  :scroll-y="activeContentTab === 'guangchang'"
  :show-scrollbar="false"
  :bounce="false"
  @scroll="onScroll"
  @scrolltolower="handleScrollToLower"
>
  <!-- banner + ContentTabs + 三个 tab 内容结构完全不变 -->
  <view class="banner-section">...</view>
  <ContentTabs ... />
  <view v-if="activeContentTab === 'xinghe'" class="star-galaxy-wrapper">
    <StarGalaxy @cardClick="handleCardClick" />
  </view>
  <view v-else-if="activeContentTab === 'xingbang'" class="hot-category-wrapper">
    <HotCategoryBlock @cardClick="handleCardClick" />
  </view>
  <CreationGrid
    v-if="activeContentTab === 'guangchang'"
    @cardClick="handleCardClick"
    ref="creationGridRef"
  />
</scroll-view>

样式改动:给 .hot-category-wrapper 固定高度,让 HotCategoryBlock 的内部 scroll 有边界:

.hot-category-wrapper {
  position: relative;
  /* 100vh - header(208rpx) - banner(360rpx) - tabs(88rpx) = 可用 body 高度 */
  height: calc(100vh - 208rpx - 360rpx - 88rpx);
}

2. HotCategoryBlock 内部改造 (frontend/pages/square/components/HotCategoryBlock.vue)

把 grid 区域loading 骨架 / items-grid包到一个新的内部 <scroll-view> 中。

模板改动

<template>
  <view class="hot-category-block">
    <!-- Tab 固定不滚-->
    <view class="ranking-tabs">
      <!-- ... existing code ... -->
    </view>

    <!-- 网格区域内部 scroll -->
    <scroll-view
      class="content-scroll"
      scroll-y
      :show-scrollbar="false"
      :bounce="false"
    >
      <view v-if="loading" class="grid-skeleton">...</view>
      <view v-else class="items-grid">...</view>
    </scroll-view>
  </view>
</template>

样式改动

.hot-category-block {
  height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.content-scroll {
  flex: 1;
  min-height: 0; /* flex 子项需要 min-height: 0 才能正确伸缩 */
  overflow: hidden;
}

3. 切换 tab 时重置 scrollTop

square.vue<script setup> 中加 watch

import { ref, watch, onMounted, onUnmounted } from "vue";

// ... 现有代码 ...

watch(activeContentTab, () => {
  // 切 tab 时把外层 scroll-view 滚回顶部
  // 星河/星榜 时 scroll-y=false但 scrollTop 残留可能导致显示异常
  uni.pageScrollTo({ scrollTop: 0, duration: 0 });
});

备注:uni.pageScrollTo 是页面级 API对 scroll-view 的 scrollTop 重置, 实际实现可改用 ref 绑定 scroll-top 属性并显式置 0。视具体平台支持调整。 如果运行时发现 uni.pageScrollTo 不生效,可改为:

const scrollTop = ref(0);
watch(activeContentTab, () => { scrollTop.value = 0; });
// <scroll-view :scroll-top="scrollTop" ...>

数据流 & 副作用

Tab 外层 scroll-y onScroll 触发? CreationGrid 分类标签 fixed
星河 false 不相关CreationGrid 不渲染)
星榜 false 不相关
广场 true 正常
  • onScroll / handleScrollToLower 内部已用 creationGridRef.value?.updateScroll?.() 可选链, 在 creationGridRefnull 时安全无副作用,无需修改
  • onShowactiveContentTab.value = 'xinghe' 重置行为保留
  • Header / BottomNav / RankingModal / GuideOverlay 都不受影响

错误处理

场景 处理
切到 星河/星榜 时 scrollTop 残留 watch 重置
HotCategoryBlock 内部 scroll-view 在加载更多时 当前 HotCategoryBlock 内部没有 loadMore(数据固定 11 条),不需要处理;如未来需要,可参考 CreationGridscrolltolower 模式
top: -128rpx 在 scroll-y=false 时的视觉 保留现状不调整StarGalaxy 顶部 mask 透明渐变已设计为"上面有 banner 透出"

测试要点

手动验证(无单元测试覆盖此 UI 行为):

  1. 星河:进入 星河 tab → 尝试上下/左右滑动 → 页面无任何滚动StarGalaxy 完整可见部分铺满
  2. 星榜:进入 星榜 tab → 尝试滑动 banner / 顶部 → 顶部不滚;滑动 grid 卡片区域 → grid 区域独立滚
  3. 广场:进入 广场 tab → 整体页面可滚(与改造前完全一致)
  4. 切换:从 广场 滚动到中段 → 切到 星河 → 切回 广场 → 页面从顶部开始
  5. 横幅/分类 fixedCreationGrid 的"分类标签切 fixed"在 广场 tab 仍正常
  6. 双击点赞/单击跳转:三个 tab 都正常

涉及文件

文件 改动
frontend/pages/square/square.vue scroll-view 加 :scroll-y 动态绑定;加 watch 重置 scrollTop.hot-category-wrapperheight: calc(...)
frontend/pages/square/components/HotCategoryBlock.vue grid 区域包到内部 scroll-view.content-scroll 样式;改 .hot-category-block 为 flex 布局

不涉及

  • StarGalaxy 任何文件(设计要求保留原状)
  • CreationGrid 任何文件(行为不变)
  • BannerCarousel / ContentTabs / Header / BottomNav / RankingModal / GuideOverlay 任何文件
  • 后端 API、store、composables