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

185 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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: -128rpx`、`min-height: 1440rpx` 等视觉设计)
3. 仅通过外层 `scroll-view``scroll-y` 动态绑定 + `HotCategoryBlock` 内部增加独立 scroll 容器达成
4. 切换 tab 时重置外层 scrollTop避免残留滚动位置
## 设计
### 1. 模板改动 (`frontend/pages/square/square.vue`)
唯一改动:把 `<scroll-view>` 上的 `scroll-y` 从静态改为动态绑定。
```vue
<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 有边界:
```scss
.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>` 中。
**模板改动**
```vue
<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>
```
**样式改动**
```scss
.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
```javascript
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` 不生效,可改为:
> ```javascript
> 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?.()` 可选链,
`creationGridRef``null` 时安全无副作用,**无需修改**
- `onShow``activeContentTab.value = 'xinghe'` 重置行为保留
- `Header` / `BottomNav` / `RankingModal` / `GuideOverlay` 都不受影响
## 错误处理
| 场景 | 处理 |
| --- | --- |
| 切到 星河/星榜 时 scrollTop 残留 | watch 重置 |
| `HotCategoryBlock` 内部 scroll-view 在加载更多时 | 当前 `HotCategoryBlock` 内部没有 `loadMore`(数据固定 11 条),不需要处理;如未来需要,可参考 `CreationGrid``scrolltolower` 模式 |
| `top: -128rpx` 在 scroll-y=false 时的视觉 | 保留现状不调整StarGalaxy 顶部 mask 透明渐变已设计为"上面有 banner 透出" |
## 测试要点
手动验证(无单元测试覆盖此 UI 行为):
1. **星河**:进入 星河 tab → 尝试上下/左右滑动 → 页面无任何滚动StarGalaxy 完整可见部分铺满
2. **星榜**:进入 星榜 tab → 尝试滑动 banner / 顶部 → 顶部不滚;滑动 grid 卡片区域 → grid 区域独立滚
3. **广场**:进入 广场 tab → 整体页面可滚(与改造前完全一致)
4. **切换**:从 广场 滚动到中段 → 切到 星河 → 切回 广场 → 页面从顶部开始
5. **横幅/分类 fixed**`CreationGrid` 的"分类标签切 fixed"在 广场 tab 仍正常
6. **双击点赞/单击跳转**:三个 tab 都正常
## 涉及文件
| 文件 | 改动 |
| --- | --- |
| `frontend/pages/square/square.vue` | scroll-view 加 `:scroll-y` 动态绑定;加 watch 重置 scrollTop`.hot-category-wrapper` 加 `height: 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