fix(plan): 应用 plan reviewer 5 条 issue + 2 条 advisory
修复: 1. Task 14: 删除 scrolltolower 误绑定(每次滚到底会触发全量刷新) 2. Task 14: 补 onShow 生命周期(spec §4.1 要求从其他页返回时强制刷) 3. Task 3: refresh() 改为 cache-aware 默认行为;force 需显式传参 4. Task 7: 删除未被引用的 chart-theme.json(死文件) 5. 删除原 Task 13(SectionSkeleton+EmptyState 死组件) 6. Task 13: pageBg 改用 SCSS class 引用,移除 inline 重复字符串 7. 新增 Task 14 全链路手动验证(spec §8.2 核对清单) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
702fc1d383
commit
72c91c1787
@ -15,7 +15,7 @@
|
||||
|
||||
## 文件总览
|
||||
|
||||
**Create(15 个)**:
|
||||
**Create(13 个)**:
|
||||
- `frontend/pages/dashboard/dashboard.vue`
|
||||
- `frontend/pages/dashboard/components/DashboardHeader.vue`
|
||||
- `frontend/pages/dashboard/components/CrystalOverview.vue`
|
||||
@ -27,8 +27,6 @@
|
||||
- `frontend/pages/dashboard/components/LevelDistribution.vue`
|
||||
- `frontend/pages/dashboard/components/UpcomingUpgrades.vue`
|
||||
- `frontend/pages/dashboard/components/RecentUpgrades.vue`
|
||||
- `frontend/pages/dashboard/components/SectionSkeleton.vue`
|
||||
- `frontend/pages/dashboard/components/EmptyState.vue`
|
||||
- `frontend/composables/useDashboardData.js`
|
||||
- `frontend/utils/mock/dashboard.js`
|
||||
|
||||
@ -443,21 +441,24 @@ export function useDashboardData({ starId = null } = {}) {
|
||||
}
|
||||
|
||||
// —— 局部刷新 ——
|
||||
async function refresh(section) {
|
||||
if (!section) {
|
||||
return loadAll(true)
|
||||
// - refresh(section) : 单 section 强制重拉(无缓存)
|
||||
// - refresh() : 全量刷新,cache-aware(30 分钟内复用)
|
||||
// - refresh(null, true) : 全量强制刷新(绕缓存)
|
||||
async function refresh(section, force = false) {
|
||||
if (section) {
|
||||
const fetcherMap = {
|
||||
today: dashboardApi.getTodayOverview,
|
||||
curve: dashboardApi.get7DayIncomeCurve,
|
||||
exhibition: dashboardApi.getExhibitionSummary,
|
||||
likeIncome: dashboardApi.getLikeIncomeByLevel,
|
||||
topAssets: dashboardApi.getTopAssets,
|
||||
levels: dashboardApi.getLevelDistribution,
|
||||
upgrades: dashboardApi.getUpgradeProgress,
|
||||
}
|
||||
if (!fetcherMap[section]) return
|
||||
return loadSection(section, fetcherMap[section])
|
||||
}
|
||||
const fetcherMap = {
|
||||
today: dashboardApi.getTodayOverview,
|
||||
curve: dashboardApi.get7DayIncomeCurve,
|
||||
exhibition: dashboardApi.getExhibitionSummary,
|
||||
likeIncome: dashboardApi.getLikeIncomeByLevel,
|
||||
topAssets: dashboardApi.getTopAssets,
|
||||
levels: dashboardApi.getLevelDistribution,
|
||||
upgrades: dashboardApi.getUpgradeProgress,
|
||||
}
|
||||
if (!fetcherMap[section]) return
|
||||
await loadSection(section, fetcherMap[section])
|
||||
return loadAll(force)
|
||||
}
|
||||
|
||||
// —— effectScope 资源释放 ——
|
||||
@ -1118,51 +1119,8 @@ git commit -m "feat(dashboard): CrystalOverview 双卡(水晶余额+今日收
|
||||
|
||||
**Files:**
|
||||
- Create: `frontend/pages/dashboard/components/IncomeCurve.vue`
|
||||
- Create: `frontend/pages/dashboard/chart-theme.json`(uCharts 主题配置)
|
||||
|
||||
- [ ] **Step 1: 创建 uCharts 主题配置 `frontend/pages/dashboard/chart-theme.json`**
|
||||
|
||||
> **注意**:此文件路径在 Vue 项目中通过 `?url` 或 `import` 引入。H5 用 `import`,小程序/App 走条件编译。
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "barline",
|
||||
"color": ["#FFCC14", "#1BAFEE"],
|
||||
"padding": [16, 16, 8, 16],
|
||||
"dataLabel": false,
|
||||
"legend": { "show": false },
|
||||
"xAxis": {
|
||||
"disableGrid": true,
|
||||
"axisLine": false,
|
||||
"fontColor": "#FFFFFF",
|
||||
"fontSize": 9
|
||||
},
|
||||
"yAxis": {
|
||||
"data": [{ "min": 0 }],
|
||||
"disableGrid": true,
|
||||
"axisLine": false,
|
||||
"fontColor": "#FFFFFF",
|
||||
"fontSize": 9
|
||||
},
|
||||
"extra": {
|
||||
"bar": {
|
||||
"type": "group",
|
||||
"width": 18,
|
||||
"activeBgColor": "#000000",
|
||||
"activeBgOpacity": 0.1,
|
||||
"linear": true,
|
||||
"color": ["#FFDF77", "#B984FF", "#FF8183"]
|
||||
},
|
||||
"line": {
|
||||
"type": "curve",
|
||||
"width": 2,
|
||||
"activeType": "hollow"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 完整实现 IncomeCurve.vue**
|
||||
- [ ] **Step 1: 完整实现 IncomeCurve.vue**
|
||||
|
||||
```vue
|
||||
<template>
|
||||
@ -1389,7 +1347,7 @@ cd frontend && npm install qiun-data-charts
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/pages/dashboard/components/IncomeCurve.vue frontend/pages/dashboard/dashboard.vue frontend/pages/dashboard/chart-theme.json frontend/package.json frontend/package-lock.json
|
||||
git add frontend/pages/dashboard/components/IncomeCurve.vue frontend/pages/dashboard/dashboard.vue frontend/package.json frontend/package-lock.json
|
||||
git commit -m "feat(dashboard): IncomeCurve 七日柱状+折线(uCharts H5)"
|
||||
```
|
||||
|
||||
@ -2673,170 +2631,90 @@ git commit -m "feat(dashboard): UpcomingUpgrades + RecentUpgrades 双列布局"
|
||||
|
||||
---
|
||||
|
||||
## Task 13: SectionSkeleton + EmptyState 通用组件
|
||||
|
||||
**Files:**
|
||||
- Create: `frontend/pages/dashboard/components/SectionSkeleton.vue`
|
||||
- Create: `frontend/pages/dashboard/components/EmptyState.vue`
|
||||
|
||||
- [ ] **Step 1: 完整实现 SectionSkeleton.vue**
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="section-skeleton" :class="`skeleton-${variant}`">
|
||||
<view v-if="variant === 'card'" class="sk-card">
|
||||
<view class="sk-label"></view>
|
||||
<view class="sk-value"></view>
|
||||
</view>
|
||||
<view v-else-if="variant === 'list'" class="sk-list">
|
||||
<view v-for="i in 3" :key="i" class="sk-row"></view>
|
||||
</view>
|
||||
<view v-else-if="variant === 'chart'" class="sk-chart"></view>
|
||||
<view v-else-if="variant === 'matrix'" class="sk-matrix">
|
||||
<view class="sk-row"></view>
|
||||
<view class="sk-row"></view>
|
||||
<view class="sk-row"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SectionSkeleton',
|
||||
props: {
|
||||
variant: { type: String, default: 'card' }, // card | list | chart | matrix
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.section-skeleton {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-radius: 17rpx;
|
||||
padding: 24rpx;
|
||||
margin: 16rpx 0;
|
||||
}
|
||||
|
||||
.sk-card,
|
||||
.sk-list,
|
||||
.sk-chart,
|
||||
.sk-matrix {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.sk-card {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sk-label {
|
||||
width: 40%;
|
||||
height: 24rpx;
|
||||
border-radius: 6rpx;
|
||||
background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
.sk-value {
|
||||
width: 60%;
|
||||
height: 48rpx;
|
||||
border-radius: 8rpx;
|
||||
background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
.sk-row {
|
||||
width: 100%;
|
||||
height: 64rpx;
|
||||
border-radius: 12rpx;
|
||||
background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
.sk-chart {
|
||||
width: 100%;
|
||||
height: 240rpx;
|
||||
border-radius: 14rpx;
|
||||
background: linear-gradient(90deg, rgba(255, 255, 255, 0.1) 25%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% 0; }
|
||||
100% { background-position: -200% 0; }
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 完整实现 EmptyState.vue**
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<view class="empty-state">
|
||||
<text class="empty-icon">{{ icon }}</text>
|
||||
<text class="empty-text">{{ text }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'EmptyState',
|
||||
props: {
|
||||
text: { type: String, default: '暂无数据' },
|
||||
icon: { type: String, default: '📭' },
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60rpx 32rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 80rpx;
|
||||
margin-bottom: 16rpx;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
font-size: 26rpx;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/pages/dashboard/components/SectionSkeleton.vue frontend/pages/dashboard/components/EmptyState.vue
|
||||
git commit -m "feat(dashboard): SectionSkeleton + EmptyState 通用组件"
|
||||
```
|
||||
|
||||
> **注**:本任务暂不替换各 section 内的内联骨架(已能用),后续若需要统一可在各组件中替换 `<view class="skeleton-...">` 为 `<SectionSkeleton variant="..." />`。
|
||||
|
||||
---
|
||||
|
||||
## Task 14: onShow 刷新 + Tab 缓存 + 下拉刷新
|
||||
## Task 13: onShow 刷新 + Tab 缓存 + 下拉刷新
|
||||
|
||||
**Files:**
|
||||
- Modify: `frontend/pages/dashboard/dashboard.vue`
|
||||
- Modify: `frontend/pages.json`(启用下拉刷新)
|
||||
|
||||
- [ ] **Step 1: 加入 onShow 静默刷新 + Tab 缓存**
|
||||
- [ ] **Step 1: 在 `uni.scss` 中追加 page-bg CSS 类**
|
||||
|
||||
完整替换 `<script setup>` 部分(在 `export default` 内):
|
||||
在 `uni.scss` 末尾 dashboard token 段后追加:
|
||||
```scss
|
||||
/* dashboard 页面背景(CSS 类,组件用 class 引用,避免 inline 重复) */
|
||||
.dashboard-page-bg {
|
||||
background: linear-gradient(153deg, #FF9597 0%, #80DFFF 33%, #B8B8B8 74%, #D9D9D9 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
- [ ] **Step 2: 重写 `frontend/pages/dashboard/dashboard.vue`(含 onShow + scroll-view + class 引用)**
|
||||
|
||||
完整文件内容:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="dashboard-page-bg dashboard-scroll"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="loading.overall"
|
||||
@refresherrefresh="handlePullDownRefresh"
|
||||
>
|
||||
<view class="dashboard-container">
|
||||
<DashboardHeader
|
||||
:active-tab="activeTab"
|
||||
@update:active-tab="handleTabChange"
|
||||
/>
|
||||
|
||||
<!-- Tab 1: 水晶相关 -->
|
||||
<view v-if="activeTab === 'crystal'" class="dashboard-content">
|
||||
<CrystalOverview
|
||||
:data="data.today"
|
||||
:loading="loading.today"
|
||||
:error="error.today"
|
||||
@retry="refresh('today')"
|
||||
/>
|
||||
<IncomeCurve
|
||||
:points="data.curve?.points || []"
|
||||
:loading="loading.curve"
|
||||
:error="error.curve"
|
||||
@retry="refresh('curve')"
|
||||
/>
|
||||
<ExhibitionCenter
|
||||
:data="data.exhibition"
|
||||
:loading="loading.exhibition"
|
||||
:error="error.exhibition"
|
||||
@retry="refresh('exhibition')"
|
||||
/>
|
||||
<LikeIncomeBoard
|
||||
:stats="data.likeIncome ? { total_like_count: data.likeIncome.total_like_count, total_income: data.likeIncome.total_income } : null"
|
||||
:levels="data.likeIncome?.levels || []"
|
||||
:loading="loading.likeIncome"
|
||||
:error="error.likeIncome"
|
||||
@retry="refresh('likeIncome')"
|
||||
/>
|
||||
<CollectionMatrix
|
||||
:top-five="data.topAssets"
|
||||
:levels="data.levels"
|
||||
:upgrades="data.upgrades"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- Tab 2: 赛季总览(占位) -->
|
||||
<view v-else class="dashboard-content">
|
||||
<view class="season-placeholder">
|
||||
<text class="placeholder-icon">🏆</text>
|
||||
<text class="placeholder-title">赛季总览 · 即将上线</text>
|
||||
<text class="placeholder-sub">历史赛季数据正在筹备中</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import DashboardHeader from './components/DashboardHeader.vue'
|
||||
import CrystalOverview from './components/CrystalOverview.vue'
|
||||
import IncomeCurve from './components/IncomeCurve.vue'
|
||||
@ -2855,73 +2733,126 @@ export default {
|
||||
starId: starId.value,
|
||||
})
|
||||
|
||||
// Tab 切换:30 分钟内复用缓存;切回水晶 Tab 时静默刷新
|
||||
// Tab 切换:30 分钟内复用缓存;切回水晶 Tab 时 cache-aware 刷新
|
||||
function handleTabChange(tab) {
|
||||
activeTab.value = tab
|
||||
if (tab === 'crystal') {
|
||||
// 静默后台刷新(不显示 loading 覆盖,因为数据已存在)
|
||||
// refresh() 默认走 30 分钟缓存(不闪骨架屏);需要强刷用 refresh(null, true)
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
async function handlePullDownRefresh() {
|
||||
await refresh()
|
||||
await refresh(null, true) // force=true 绕缓存
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 首屏已由 composable 自动 loadAll()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
dispose()
|
||||
})
|
||||
|
||||
return {
|
||||
activeTab,
|
||||
loading,
|
||||
error,
|
||||
data,
|
||||
isReady,
|
||||
lastFetched,
|
||||
handleTabChange,
|
||||
handlePullDownRefresh,
|
||||
pageBg: 'linear-gradient(153deg, #FF9597 0%, #80DFFF 33%, #B8B8B8 74%, #D9D9D9 100%)',
|
||||
// onShow:用户从其他页面返回时强制刷新一次(spec §4.1)
|
||||
onShow lifecycle hook(uni-app 页面级,需在 export default 上声明,setup 内通过 getCurrentInstance 获取):
|
||||
```js
|
||||
// 在 export default 中追加:
|
||||
onShow() {
|
||||
this.$options._setupRef?.refresh?.(null, true)
|
||||
}
|
||||
```
|
||||
|
||||
// 实际写法(替换上面注释块):在 setup 末尾 return 前暴露 refresh 引用
|
||||
const setupRef = { refresh }
|
||||
onShow lifecycle:见下方 onShow 实现
|
||||
},
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
**关于 onShow 的关键说明**:uni-app 的 `onShow` 是 Options API 生命周期钩子,**必须在 `export default` 顶层声明**(不在 `setup` 内)。完整 onShow 实现:
|
||||
|
||||
在 `<script>` 块内的 `export default` 顶层(与 `components`、`setup` 同级)追加:
|
||||
```javascript
|
||||
export default {
|
||||
components: { ... },
|
||||
setup() { ... return { refresh, ... } },
|
||||
onShow() {
|
||||
// 页面 onShow 时强制刷新(绕 30 分钟缓存,spec §4.1)
|
||||
this.refresh(null, true)
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
并在 `<template>` 顶部加 pull-down-refresh 包装(uni-app 页面配置):
|
||||
注意:`onShow` 内的 `this.refresh` 来自 `setup()` 的 `return` 暴露,vue 会自动合并 setup return 到实例。
|
||||
|
||||
把 `<view class="dashboard-container" :style="{ background: pageBg }">` 改为:
|
||||
```vue
|
||||
<scroll-view
|
||||
scroll-y
|
||||
class="dashboard-scroll"
|
||||
:style="{ background: pageBg }"
|
||||
@scrolltolower="handlePullDownRefresh"
|
||||
:refresher-enabled="true"
|
||||
:refresher-triggered="loading.overall"
|
||||
@refresherrefresh="handlePullDownRefresh"
|
||||
>
|
||||
<view class="dashboard-container">
|
||||
...(原内容)
|
||||
</view>
|
||||
</scroll-view>
|
||||
> **重要**:上面模板中 `onShow` 注释块仅作说明,**实际写入文件时按下方"onShow 完整写法"**(替换 `setup` 末段):
|
||||
|
||||
`setup()` 末段(return 之前)追加:
|
||||
```javascript
|
||||
// 将 refresh 暴露给 onShow 生命周期
|
||||
const _refresh = refresh
|
||||
// 在 return 中暴露
|
||||
return {
|
||||
activeTab, loading, error, data, isReady, lastFetched,
|
||||
handleTabChange, handlePullDownRefresh,
|
||||
refresh: _refresh, // 给 onShow 用
|
||||
}
|
||||
```
|
||||
|
||||
在 `<style>` 中加:
|
||||
> 最终 onShow 写法(重写整个 export default):
|
||||
```javascript
|
||||
export default {
|
||||
components: { DashboardHeader, CrystalOverview, IncomeCurve, ExhibitionCenter, LikeIncomeBoard, CollectionMatrix },
|
||||
setup() {
|
||||
const activeTab = ref('crystal')
|
||||
const starId = ref(uni.getStorageSync('star_id') || null)
|
||||
const { loading, error, data, refresh, isReady, lastFetched, dispose } = useDashboardData({ starId: starId.value })
|
||||
function handleTabChange(tab) {
|
||||
activeTab.value = tab
|
||||
if (tab === 'crystal') refresh()
|
||||
}
|
||||
async function handlePullDownRefresh() {
|
||||
await refresh(null, true)
|
||||
uni.stopPullDownRefresh()
|
||||
}
|
||||
onUnmounted(() => dispose())
|
||||
return { activeTab, loading, error, data, isReady, lastFetched, handleTabChange, handlePullDownRefresh, refresh }
|
||||
},
|
||||
onShow() {
|
||||
// 从其他页面返回时强制刷新(spec §4.1)
|
||||
if (this.refresh) this.refresh(null, true)
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 在 `<style>` 末尾追加 scroll-view 样式**
|
||||
|
||||
在 dashboard.vue 的 `<style lang="scss" scoped>` 块末尾追加:
|
||||
```scss
|
||||
.dashboard-scroll {
|
||||
height: 100vh;
|
||||
}
|
||||
.dashboard-container {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.dashboard-content {
|
||||
padding: 24rpx 32rpx 80rpx;
|
||||
}
|
||||
.season-placeholder {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 22rpx;
|
||||
padding: 120rpx 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.placeholder-icon { font-size: 96rpx; margin-bottom: 24rpx; }
|
||||
.placeholder-title { color: #ffffff; font-size: 36rpx; font-weight: 700; margin-bottom: 16rpx; }
|
||||
.placeholder-sub { color: rgba(255, 255, 255, 0.7); font-size: 26rpx; }
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 启用页面下拉刷新**
|
||||
> **删掉** `dashboard.vue` 现有的 `.dashboard-container`、`.dashboard-content`、`.placeholder-*`、`.season-placeholder` 旧样式(被 `<style>` 末尾的覆盖即可,但为避免重复,请删旧版块)。
|
||||
|
||||
修改 `frontend/pages.json` 中 dashboard 条目:
|
||||
- [ ] **Step 4: 在 `pages.json` 中启用下拉刷新**
|
||||
|
||||
把 dashboard 条目改为:
|
||||
```json
|
||||
,{
|
||||
"path": "pages/dashboard/dashboard",
|
||||
@ -2935,14 +2866,16 @@ export default {
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 手动验证**
|
||||
- [ ] **Step 5: 手动验证**
|
||||
|
||||
刷新 H5 dev URL:
|
||||
- 拖动下拉 → 触发刷新(loading 短暂亮起)
|
||||
- 切换 Tab 1 → Tab 2 → Tab 1:第一次切回 Tab 1 时触发静默 refresh
|
||||
- 离开页面后再次进入:composable dispose 已调用
|
||||
- 拖动下拉 → 触发强制刷新(loading.overall 短暂为 true)
|
||||
- 切换 Tab 1 → Tab 2 → Tab 1:30 分钟内 cache-aware 静默刷新(不闪骨架)
|
||||
- 离开页面到其他页面 → 返回:onShow 触发 force refresh
|
||||
- 关闭网络(DevTools Network → Offline)→ 各 section 显示错误态
|
||||
- 恢复网络 → 点击错误卡片 → 该 section 重试成功
|
||||
|
||||
- [ ] **Step 4: 验证 Figma 视觉一致性**
|
||||
- [ ] **Step 6: 验证 Figma 视觉一致性**
|
||||
|
||||
用 `superpowers:verification-before-completion` 流程对照 `docs/figma-analysis-data-dashboard.md` 第三节设计 token 逐项核对:
|
||||
- [ ] 颜色 token 与 §3.1 一致
|
||||
@ -2950,15 +2883,48 @@ export default {
|
||||
- [ ] 圆角与 §3.3 一致
|
||||
- [ ] 5 个等级色与 §2.7.2 一致
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add frontend/pages/dashboard/dashboard.vue frontend/pages.json
|
||||
git commit -m "feat(dashboard): onShow 静默刷新 + Tab 缓存 + 下拉刷新"
|
||||
git add frontend/pages/dashboard/dashboard.vue frontend/uni.scss frontend/pages.json
|
||||
git commit -m "feat(dashboard): onShow 强制刷新 + Tab 缓存 + 下拉刷新 + SCSS class 引用"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 14: 全链路手动验证
|
||||
|
||||
**Files:** 无(验证任务)
|
||||
|
||||
- [ ] **Step 1: 执行 `superpowers:verification-before-completion` 流程**
|
||||
|
||||
逐项对照 spec §8.2 验收清单:
|
||||
|
||||
| 项 | 预期 | 实际 | 通过 |
|
||||
|----|------|------|------|
|
||||
| 视觉对齐 Figma 稿 | 6 模块结构、token 一致 | | ☐ |
|
||||
| Tab 切换不重发请求 | 30 分钟内切回 Tab 1,无网络请求 | | ☐ |
|
||||
| 下拉刷新拉新数据 | 拖动触发,loading.overall 亮起 | | ☐ |
|
||||
| 飞行模式各 section 独立重试 | DevTools Offline → 各 section 显示错误态 → 恢复后点击重试成功 | | ☐ |
|
||||
| 后端 401 跳登录 | 临时改 mock 抛 401 → 跳登录页 | | ☐ |
|
||||
| 移动端 320px 小屏无溢出 | Chrome DevTools 切到 320×568,无横向滚动条 | | ☐ |
|
||||
| uCharts 渲染正常 | 七日柱状+折线显示,peak 高亮 | | ☐ |
|
||||
| 5 个等级环渐变正确 | UR/SSR/SR/R/N 渐变色与 spec 一致 | | ☐ |
|
||||
| Tab 2 占位 | "赛季总览 · 即将上线",无网络请求 | | ☐ |
|
||||
| onShow 强制刷新 | 离开页面到任意页 → 返回 → 看到新数据 | | ☐ |
|
||||
|
||||
- [ ] **Step 2: 通过所有项 → 标记 done**
|
||||
|
||||
如全部通过,本任务可视为完成;如有失败项,**回到对应 Task 修复后重跑本表**。
|
||||
|
||||
如有任何项需要修复但没有对应 Task(例如"uCharts 主题色微调"),追加新 Task 而非挤入本任务。
|
||||
|
||||
- [ ] **Step 3: 不需要 commit(验证任务,无代码变更)**
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## 风险与决策记录
|
||||
|
||||
| 风险 | 应对 |
|
||||
|
||||
Loading…
Reference in New Issue
Block a user