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/dashboard.vue`
|
||||||
- `frontend/pages/dashboard/components/DashboardHeader.vue`
|
- `frontend/pages/dashboard/components/DashboardHeader.vue`
|
||||||
- `frontend/pages/dashboard/components/CrystalOverview.vue`
|
- `frontend/pages/dashboard/components/CrystalOverview.vue`
|
||||||
@ -27,8 +27,6 @@
|
|||||||
- `frontend/pages/dashboard/components/LevelDistribution.vue`
|
- `frontend/pages/dashboard/components/LevelDistribution.vue`
|
||||||
- `frontend/pages/dashboard/components/UpcomingUpgrades.vue`
|
- `frontend/pages/dashboard/components/UpcomingUpgrades.vue`
|
||||||
- `frontend/pages/dashboard/components/RecentUpgrades.vue`
|
- `frontend/pages/dashboard/components/RecentUpgrades.vue`
|
||||||
- `frontend/pages/dashboard/components/SectionSkeleton.vue`
|
|
||||||
- `frontend/pages/dashboard/components/EmptyState.vue`
|
|
||||||
- `frontend/composables/useDashboardData.js`
|
- `frontend/composables/useDashboardData.js`
|
||||||
- `frontend/utils/mock/dashboard.js`
|
- `frontend/utils/mock/dashboard.js`
|
||||||
|
|
||||||
@ -443,10 +441,11 @@ export function useDashboardData({ starId = null } = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// —— 局部刷新 ——
|
// —— 局部刷新 ——
|
||||||
async function refresh(section) {
|
// - refresh(section) : 单 section 强制重拉(无缓存)
|
||||||
if (!section) {
|
// - refresh() : 全量刷新,cache-aware(30 分钟内复用)
|
||||||
return loadAll(true)
|
// - refresh(null, true) : 全量强制刷新(绕缓存)
|
||||||
}
|
async function refresh(section, force = false) {
|
||||||
|
if (section) {
|
||||||
const fetcherMap = {
|
const fetcherMap = {
|
||||||
today: dashboardApi.getTodayOverview,
|
today: dashboardApi.getTodayOverview,
|
||||||
curve: dashboardApi.get7DayIncomeCurve,
|
curve: dashboardApi.get7DayIncomeCurve,
|
||||||
@ -457,7 +456,9 @@ export function useDashboardData({ starId = null } = {}) {
|
|||||||
upgrades: dashboardApi.getUpgradeProgress,
|
upgrades: dashboardApi.getUpgradeProgress,
|
||||||
}
|
}
|
||||||
if (!fetcherMap[section]) return
|
if (!fetcherMap[section]) return
|
||||||
await loadSection(section, fetcherMap[section])
|
return loadSection(section, fetcherMap[section])
|
||||||
|
}
|
||||||
|
return loadAll(force)
|
||||||
}
|
}
|
||||||
|
|
||||||
// —— effectScope 资源释放 ——
|
// —— effectScope 资源释放 ——
|
||||||
@ -1118,51 +1119,8 @@ git commit -m "feat(dashboard): CrystalOverview 双卡(水晶余额+今日收
|
|||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `frontend/pages/dashboard/components/IncomeCurve.vue`
|
- Create: `frontend/pages/dashboard/components/IncomeCurve.vue`
|
||||||
- Create: `frontend/pages/dashboard/chart-theme.json`(uCharts 主题配置)
|
|
||||||
|
|
||||||
- [ ] **Step 1: 创建 uCharts 主题配置 `frontend/pages/dashboard/chart-theme.json`**
|
- [ ] **Step 1: 完整实现 IncomeCurve.vue**
|
||||||
|
|
||||||
> **注意**:此文件路径在 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**
|
|
||||||
|
|
||||||
```vue
|
```vue
|
||||||
<template>
|
<template>
|
||||||
@ -1389,7 +1347,7 @@ cd frontend && npm install qiun-data-charts
|
|||||||
- [ ] **Step 6: Commit**
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
```bash
|
```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)"
|
git commit -m "feat(dashboard): IncomeCurve 七日柱状+折线(uCharts H5)"
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -2673,170 +2631,90 @@ git commit -m "feat(dashboard): UpcomingUpgrades + RecentUpgrades 双列布局"
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Task 13: SectionSkeleton + EmptyState 通用组件
|
## Task 13: onShow 刷新 + Tab 缓存 + 下拉刷新
|
||||||
|
|
||||||
**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 缓存 + 下拉刷新
|
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `frontend/pages/dashboard/dashboard.vue`
|
- 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
|
- [ ] **Step 2: 重写 `frontend/pages/dashboard/dashboard.vue`(含 onShow + scroll-view + class 引用)**
|
||||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
||||||
|
完整文件内容:
|
||||||
|
|
||||||
|
```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 DashboardHeader from './components/DashboardHeader.vue'
|
||||||
import CrystalOverview from './components/CrystalOverview.vue'
|
import CrystalOverview from './components/CrystalOverview.vue'
|
||||||
import IncomeCurve from './components/IncomeCurve.vue'
|
import IncomeCurve from './components/IncomeCurve.vue'
|
||||||
@ -2855,73 +2733,126 @@ export default {
|
|||||||
starId: starId.value,
|
starId: starId.value,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Tab 切换:30 分钟内复用缓存;切回水晶 Tab 时静默刷新
|
// Tab 切换:30 分钟内复用缓存;切回水晶 Tab 时 cache-aware 刷新
|
||||||
function handleTabChange(tab) {
|
function handleTabChange(tab) {
|
||||||
activeTab.value = tab
|
activeTab.value = tab
|
||||||
if (tab === 'crystal') {
|
if (tab === 'crystal') {
|
||||||
// 静默后台刷新(不显示 loading 覆盖,因为数据已存在)
|
// refresh() 默认走 30 分钟缓存(不闪骨架屏);需要强刷用 refresh(null, true)
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 下拉刷新
|
// 下拉刷新
|
||||||
async function handlePullDownRefresh() {
|
async function handlePullDownRefresh() {
|
||||||
await refresh()
|
await refresh(null, true) // force=true 绕缓存
|
||||||
uni.stopPullDownRefresh()
|
uni.stopPullDownRefresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
// onShow:用户从其他页面返回时强制刷新一次(spec §4.1)
|
||||||
// 首屏已由 composable 自动 loadAll()
|
onShow lifecycle hook(uni-app 页面级,需在 export default 上声明,setup 内通过 getCurrentInstance 获取):
|
||||||
})
|
```js
|
||||||
|
// 在 export default 中追加:
|
||||||
onUnmounted(() => {
|
onShow() {
|
||||||
dispose()
|
this.$options._setupRef?.refresh?.(null, true)
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
activeTab,
|
|
||||||
loading,
|
|
||||||
error,
|
|
||||||
data,
|
|
||||||
isReady,
|
|
||||||
lastFetched,
|
|
||||||
handleTabChange,
|
|
||||||
handlePullDownRefresh,
|
|
||||||
pageBg: 'linear-gradient(153deg, #FF9597 0%, #80DFFF 33%, #B8B8B8 74%, #D9D9D9 100%)',
|
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
// 实际写法(替换上面注释块):在 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 }">` 改为:
|
> **重要**:上面模板中 `onShow` 注释块仅作说明,**实际写入文件时按下方"onShow 完整写法"**(替换 `setup` 末段):
|
||||||
```vue
|
|
||||||
<scroll-view
|
`setup()` 末段(return 之前)追加:
|
||||||
scroll-y
|
```javascript
|
||||||
class="dashboard-scroll"
|
// 将 refresh 暴露给 onShow 生命周期
|
||||||
:style="{ background: pageBg }"
|
const _refresh = refresh
|
||||||
@scrolltolower="handlePullDownRefresh"
|
// 在 return 中暴露
|
||||||
:refresher-enabled="true"
|
return {
|
||||||
:refresher-triggered="loading.overall"
|
activeTab, loading, error, data, isReady, lastFetched,
|
||||||
@refresherrefresh="handlePullDownRefresh"
|
handleTabChange, handlePullDownRefresh,
|
||||||
>
|
refresh: _refresh, // 给 onShow 用
|
||||||
<view class="dashboard-container">
|
}
|
||||||
...(原内容)
|
|
||||||
</view>
|
|
||||||
</scroll-view>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
在 `<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
|
```scss
|
||||||
.dashboard-scroll {
|
.dashboard-scroll {
|
||||||
height: 100vh;
|
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
|
```json
|
||||||
,{
|
,{
|
||||||
"path": "pages/dashboard/dashboard",
|
"path": "pages/dashboard/dashboard",
|
||||||
@ -2935,14 +2866,16 @@ export default {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- [ ] **Step 3: 手动验证**
|
- [ ] **Step 5: 手动验证**
|
||||||
|
|
||||||
刷新 H5 dev URL:
|
刷新 H5 dev URL:
|
||||||
- 拖动下拉 → 触发刷新(loading 短暂亮起)
|
- 拖动下拉 → 触发强制刷新(loading.overall 短暂为 true)
|
||||||
- 切换 Tab 1 → Tab 2 → Tab 1:第一次切回 Tab 1 时触发静默 refresh
|
- 切换 Tab 1 → Tab 2 → Tab 1:30 分钟内 cache-aware 静默刷新(不闪骨架)
|
||||||
- 离开页面后再次进入:composable dispose 已调用
|
- 离开页面到其他页面 → 返回: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 逐项核对:
|
用 `superpowers:verification-before-completion` 流程对照 `docs/figma-analysis-data-dashboard.md` 第三节设计 token 逐项核对:
|
||||||
- [ ] 颜色 token 与 §3.1 一致
|
- [ ] 颜色 token 与 §3.1 一致
|
||||||
@ -2950,15 +2883,48 @@ export default {
|
|||||||
- [ ] 圆角与 §3.3 一致
|
- [ ] 圆角与 §3.3 一致
|
||||||
- [ ] 5 个等级色与 §2.7.2 一致
|
- [ ] 5 个等级色与 §2.7.2 一致
|
||||||
|
|
||||||
- [ ] **Step 5: Commit**
|
- [ ] **Step 7: Commit**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add frontend/pages/dashboard/dashboard.vue frontend/pages.json
|
git add frontend/pages/dashboard/dashboard.vue frontend/uni.scss frontend/pages.json
|
||||||
git commit -m "feat(dashboard): onShow 静默刷新 + Tab 缓存 + 下拉刷新"
|
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