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

11 KiB

Square Tab 滚动行为差异化实现计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal:square.vue 页面中,让 星河 / 星榜 / 广场 三个 tab 拥有差异化的滚动行为:星河完全禁用滚动,星榜只允许 HotCategoryBlock 内部 grid 区域滚动,广场保持当前整体滚动。

Architecture: 复用现有外层 <scroll-view>,仅通过动态绑定 :scroll-y="activeContentTab === 'guangchang'" 控制外层是否可滚;给 HotCategoryBlock 内部加一个独立的 <scroll-view> 包裹 grid 区域;切 tab 时重置外层 scrollTop。

Tech Stack: Vue 3 (Composition API)、uni-app、SCSS。

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


涉及文件

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

明确不动:

  • frontend/pages/square/components/StarGalaxy/* (任何文件)
  • frontend/pages/square/components/CreationGrid.vue
  • frontend/pages/square/components/BannerCarousel.vue
  • frontend/pages/square/components/ContentTabs.vue

实施说明

此计划涉及纯 UI 滚动行为调整,无独立可单元测试的纯函数,故采用手动验证 + 浏览器/H5 调试作为验证手段,不用 vitest/jest 单测框架。Vue 文件改动后通过 npm run dev:h5(或项目实际启动命令)启动后,使用浏览器 DevTools 模拟手机视口验证。


Task 1: 改造 HotCategoryBlock 加内部 scroll

Files:

  • Modify: frontend/pages/square/components/HotCategoryBlock.vue:1-118(template 部分)
  • Modify: frontend/pages/square/components/HotCategoryBlock.vue(style 部分)

1.1 修改 template,把 grid 区域包到内部 scroll-view

当前结构(HotCategoryBlock.vue 大致如下,行号是参考):

<view class="hot-category-block">
  <!-- Tab  -->
  <view class="ranking-tabs">...</view>

  <!-- 骨架屏 -->
  <view v-if="loading" class="grid-skeleton">
    <view v-for="i in 11" ...>...</view>
  </view>

  <!-- 内容网格 -->
  <view v-else class="items-grid">
    <view v-for="(item, index) in items" ...>...</view>
  </view>
</view>

改造为:

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

  <!-- 内容区域: 内部 scroll -->
  <scroll-view
    class="content-scroll"
    scroll-y
    :show-scrollbar="false"
    :bounce="false"
  >
    <!-- 骨架屏 -->
    <view v-if="loading" class="grid-skeleton">
      <view v-for="i in 11" ...>...</view>
    </view>

    <!-- 内容网格 -->
    <view v-else class="items-grid">
      <view v-for="(item, index) in items" ...>...</view>
    </view>
  </scroll-view>
</view>

具体编辑步骤:

  1. Read the file to find the exact lines
  2. </view> (closing of .ranking-tabs div 之后,<view v-if="loading"> 之前) 插入 <scroll-view class="content-scroll" scroll-y :show-scrollbar="false" :bounce="false">
  3. <view v-else class="items-grid"> 闭合 </view> 之后,</view> (closing of .hot-category-block) 之前,插入 </scroll-view>

1.2 修改 style,加 flex 布局和 .content-scroll

<style scoped> 中找到 .hot-category-block 相关样式,改为:

.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;
}

1.3 手动验证

  • Step 1: 启动 H5 调试模式

    Run: 在 frontend/ 目录执行项目实际启动命令(通常是 npm run dev:h5npm run dev:mp-weixin) Expected: 开发服务器启动,无编译错误

  • Step 2: 浏览器 DevTools 切到手机视口

    在 Chrome DevTools 中切到 iPhone 12/13 视口(390x844),访问对应页面。

  • Step 3: 临时验证(此时还在外层 scroll 内,grid 区域还没独立滚)

    切换到 星榜 tab,确认页面正常渲染、grid 正常显示。 注: 此步 Task 1 完成后,grid 应该已经在外层 scroll-view 内,但 HotCategoryBlock 内部 scroll-view 可能因为高度为 0 不显示滚动条 —— 这是预期,等到 Task 2 给 .hot-category-wrapper 设了固定高度后才真正工作。

  • Step 4: Commit

    git add frontend/pages/square/components/HotCategoryBlock.vue
    git commit -m "feat(square): wrap HotCategoryBlock grid area in internal scroll-view"
    

Task 2: 改造 square.vue 动态绑定 scroll-y + 加固定高度 + 重置 scrollTop

Files:

  • Modify: frontend/pages/square/square.vue:46-53(template scroll-view 部分)
  • Modify: frontend/pages/square/square.vue:94-130(script setup 部分,加 watch)
  • Modify: frontend/pages/square/square.vue:289-377(style 部分,改 .hot-category-wrapper)

2.1 把外层 scroll-view 的 scroll-y 改为动态绑定

当前代码(square.vue 第 46-53 行):

<scroll-view
  class="content-wrapper"
  scroll-y
  :show-scrollbar="false"
  :bounce="false"
  @scroll="onScroll"
  @scrolltolower="handleScrollToLower"
>

改造为:

<scroll-view
  class="content-wrapper"
  :scroll-y="activeContentTab === 'guangchang'"
  :show-scrollbar="false"
  :bounce="false"
  @scroll="onScroll"
  @scrolltolower="handleScrollToLower"
>

唯一改动:scroll-y:scroll-y="activeContentTab === 'guangchang'"

2.2 给 .hot-category-wrapper 加固定高度

<style lang="scss" scoped> 中找到 .hot-category-wrapper(原代码大约在第 349-352 行):

.hot-category-wrapper {
  position: relative;
  padding-bottom: 80rpx;
}

改造为:

.hot-category-wrapper {
  position: relative;
  /* 100vh - header(208rpx) - banner(360rpx) - tabs(88rpx) = 可用 body 高度 */
  height: calc(100vh - 208rpx - 360rpx - 88rpx);
  /* 移除 padding-bottom,改用内部 grid gap */
}

重要:

  1. 100vh 在小程序里可能需要换成 100% (取决于 uni-app 平台);在 H5 中 100vh 工作正常。先按 H5 实现,小程序端如果有问题再调整。
  2. 计算的三个高度值(208/360/88)对应:Header / BannerCarousel(.banner-section 的 360rpx) / ContentTabs。如有偏差,以实际渲染为准微调。

2.3 加 watch 重置 scrollTop

<script setup> 中找到 import 行(大约第 95 行):

当前:

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

改为:

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

找到 const activeContentTab = ref("xinghe"); 这一行(大约第 117 行),在它后面新增:

// 切 tab 时重置外层 scroll-view 的 scrollTop,避免残留滚动位置
watch(activeContentTab, () => {
  // 星河/星榜 时 scroll-y=false,但 scrollTop 残留可能导致显示异常
  uni.pageScrollTo({ scrollTop: 0, duration: 0 });
});

如果 uni.pageScrollTo 不生效(它主要用于页面级滚动,对 scroll-view 不一定有效),可改用 ref 绑定方式:

const outerScrollTop = ref(0);
watch(activeContentTab, () => { outerScrollTop.value = 0; });

然后在 scroll-view 上加 :scroll-top="outerScrollTop"先尝试 uni.pageScrollTo 简单方案,不生效再切换。

2.4 手动验证

  • Step 1: 重启开发服务器,确保无编译错误

    Expected: 启动无报错

  • Step 2: 验证 星河 tab

    • 进入 星河 tab
    • 在 StarGalaxy 区域上下滑动
    • 预期: 页面完全不能滚动(包括 banner、tabs、StarGalaxy 任何部分)
    • 预期: StarGalaxy 顶部 top: -128rpx 仍然让星图略微与 banner 重叠(顶部 mask 透明渐变让 banner 透出),这是设计预期
  • Step 3: 验证 星榜 tab

    • 切换到 星榜 tab
    • 在顶部 banner 区域上下滑动
    • 预期: 顶部(banner + tabs)不滚动
    • 在 grid 卡片区域上下滑动
    • 预期: grid 区域独立滚动,顶部不动
  • Step 4: 验证 广场 tab

    • 切换到 广场 tab
    • 上下滑动
    • 预期: 整体页面可滚动(与改造前完全一致)
    • 滚到中段时,banner 应消失
    • 验证: CreationGrid 的"分类标签切 fixed"行为仍正常
  • Step 5: 验证切换 tab 重置 scrollTop

    • 在 广场 tab 滚到中段(banner 已消失)
    • 切到 星河 → 切回 广场
    • 预期: 页面回到顶部
  • Step 6: 验证双击点赞 / 单击跳转

    • 三个 tab 都尝试双击卡片
    • 预期: 双击点赞动画 + 点赞 API 正常触发(行为与改造前一致)
  • Step 7: 验证 BannerCarousel 的 autoplay / 点击

    • 在 星河 / 星榜 tab,banner 仍能正常左右滑(因为 scroll-y=false 不影响 swiper 的横向滑动)
    • 预期: 正常
  • Step 8: Commit

    git add frontend/pages/square/square.vue
    git commit -m "feat(square): differentiate scroll behavior for xinghe/xingbang/guangchang tabs"
    

Task 3: 跨平台兼容性验证(如果项目支持多端)

Files: 无文件改动,仅验证

  • Step 1: 验证 H5 端

    Run: npm run dev:h5 Expected: 三个 tab 滚动行为符合预期(已 Task 2 Step 2-4 验证)

  • Step 2: 验证微信小程序端(如果项目支持)

    Run: npm run dev:mp-weixin(或对应命令) Expected:

    • 星河 / 星榜 滚动行为同 H5
    • 广场 滚动行为同 H5
    • 如果 100vh 在小程序中不生效,把 .hot-category-wrapperheight: calc(100vh - 656rpx) 改为 height: calc(100% - 656rpx),或者使用 uni.getSystemInfoSync().windowHeight 动态计算
    • 如果 uni.pageScrollTo 不生效,按 Task 2.3 注释中的备选方案切到 ref 绑定
  • Step 3: 验证 App 端(如果项目支持)

    Run: npm run dev:app(或对应命令) Expected: 同 H5,如有差异按小程序方案调整


Task 4: 收尾

  • Step 1: 检查所有改动文件

    Run: git diff --stat HEAD~2 Expected: 仅 frontend/pages/square/square.vuefrontend/pages/square/components/HotCategoryBlock.vue 两个文件有改动

  • Step 2: 整体回归

    • 三个 tab 切换流畅,无残留
    • 滚动手感符合需求
    • 横幅 / 分类标签 / 底部导航都正常
  • Step 3: 提交最终 commit(如果还有遗留改动)

    git status
    git add -u
    git commit -m "chore: final cleanup for square tab scroll behavior"
    

验证清单 (人工测试报告模板)

Tab 滚动行为 顶部 banner 顶部 ContentTabs 备注
星河 完全禁用 ✓ 固定 ✓ 固定 ✓
星榜 仅 HotCategoryBlock 内部滚 ✓ 固定 ✓ 固定 ✓
广场 整体可滚 ✓ 随滚消失 随滚消失 与改造前一致