# 数据看板前端 Implementation Plan > **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:** 实现数据看板页面(6 大模块),在 mock 数据下完整跑通水晶收益、藏品表现、升级进度的可视化展示。 **Architecture:** 单页面 + 1 个聚合 composable(`useDashboardData`)+ 11 个展示子组件。composable 内部用 `effectScope` 包裹 7 个并发请求,按 section 维度独立暴露 loading/error/refresh。后端未就绪时通过 `USE_MOCK_API` 开关走 mock 数据,接真实接口只需翻常量。 **Tech Stack:** Vue 3 (Composition API) + uni-app + Vuex 4(已有,不新增模块)+ uCharts 插件(柱状+折线)+ SCSS(uni.scss 共享 token)+ CSS conic-gradient(5 个等级环) **Spec:** `docs/superpowers/specs/2026-06-02-data-dashboard-frontend-design.md` **配套视觉/API 文档:** `docs/figma-analysis-data-dashboard.md` --- ## 文件总览 **Create(13 个)**: - `frontend/pages/dashboard/dashboard.vue` - `frontend/pages/dashboard/components/DashboardHeader.vue` - `frontend/pages/dashboard/components/CrystalOverview.vue` - `frontend/pages/dashboard/components/IncomeCurve.vue` - `frontend/pages/dashboard/components/ExhibitionCenter.vue` - `frontend/pages/dashboard/components/LikeIncomeBoard.vue` - `frontend/pages/dashboard/components/CollectionMatrix.vue` - `frontend/pages/dashboard/components/TopFiveAssets.vue` - `frontend/pages/dashboard/components/LevelDistribution.vue` - `frontend/pages/dashboard/components/UpcomingUpgrades.vue` - `frontend/pages/dashboard/components/RecentUpgrades.vue` - `frontend/composables/useDashboardData.js` - `frontend/utils/mock/dashboard.js` **Modify(3 个)**: - `frontend/pages.json`(注册 dashboard 页面) - `frontend/utils/api.js`(追加 `dashboardApi` 命名空间 + mock 触发) - `frontend/uni.scss`(追加设计 token 变量) **无新增测试文件**:项目未引入测试框架,验证走 `superpowers:verification-before-completion` 流程的手动核对清单。 --- ## Task 1: 页面注册 + SCSS 设计 token **Files:** - Modify: `frontend/pages.json`(在 `pages` 数组末尾追加一项) - Modify: `frontend/uni.scss`(在文件末尾追加 dashboard 专用 token) - [ ] **Step 1: 在 `pages.json` 末尾注册 dashboard 页面** 在 `pages` 数组最后一个对象(`tasks/revenue`)之后,逗号之后追加: ```json ,{ "path": "pages/dashboard/dashboard", "style": { "navigationStyle": "custom", "app-plus": { "bounce": "none" } } } ``` - [ ] **Step 2: 在 `uni.scss` 末尾追加 dashboard token** ```scss /* ==================== Dashboard 设计 Token ==================== */ /* 颜色(与 spec §3.1 一致) */ $d-text-data: #FFFABD; $d-text-white: #FFFFFF; $d-tab-active: linear-gradient(231deg, #A8A6ED 0%, #88C8D8 64%, #F380EF 100%); $d-card-crystal: linear-gradient(135deg, #FFDF77 0%, #8E95E2 40%, #F48CFF 100%); $d-card-today: linear-gradient(137deg, #FFDF77 0%, #8E95E2 40%, #F48CFF 100%); $d-progress-cyan: linear-gradient(90deg, #5EDFFF 0%, #FFC8C8 100%); $d-progress-pink: linear-gradient(90deg, #FFF375 0%, #FF6B84 100%); $d-bar-blue-yellow: linear-gradient(90deg, #1BAFEE 0%, #FFCC14 100%); $d-bar-fill: linear-gradient(135deg, #FFDF77 0%, #B984FF 60%, #FF8183 100%); $d-page-bg: linear-gradient(153deg, #FF9597 0%, #80DFFF 33%, #B8B8B8 74%, #D9D9D9 100%); /* 5 个等级专属渐变(环图、徽章、TOP 徽章) */ $d-level-ur: linear-gradient(135deg, #FF8A65 0%, #FFD740 100%); $d-level-ssr: linear-gradient(135deg, #FF5E9C 0%, #FFB199 100%); $d-level-sr: linear-gradient(135deg, #B17BFF 0%, #FF8FE6 100%); $d-level-r: linear-gradient(135deg, #5EDFFF 0%, #6FA9FF 100%); $d-level-n: linear-gradient(135deg, #C5C5C5 0%, #8C8C8C 100%); /* 阴影 */ $d-shadow-card: 0px 4px 4px rgba(189, 50, 50, 0.25); $d-shadow-data: -1px 1px 4px rgba(206, 9, 9, 0.84); $d-shadow-mascot: 2px 2px 4px rgba(242, 21, 21, 0.47); $d-shadow-tab: 0px 4px 4px rgba(0, 0, 0, 0.25); $d-shadow-inner: 0px 4px 4px rgba(96, 13, 13, 0.25); /* 圆角 */ $d-radius-thumb: 3px; $d-radius-progress: 6px; $d-radius-card-sm: 14px; $d-radius-card-md: 17px; $d-radius-card-lg: 22px; /* 字号 */ $d-fs-num-xl: 35px; $d-fs-num-lg: 18px; $d-fs-num-md: 14px; $d-fs-num-sm: 9px; $d-fs-title-lg: 20px; $d-fs-title-md: 18px; $d-fs-title-sm: 15px; $d-fs-label: 12px; /* 间距 */ $d-space-card-x: 16px; $d-space-section: 24px; $d-space-cell: 12px; ``` - [ ] **Step 3: 手动验证 - 路由可达** 启动 H5 dev server(如果已在跑则跳过): ```bash cd frontend && npm run dev:h5 ``` 在 H5 dev URL 末尾加 `/pages/dashboard/dashboard` 访问: - 预期:进入空页面(白屏即可,组件尚未创建) - 不预期:路由 404、JSON 解析错误 - [ ] **Step 4: Commit** ```bash git add frontend/pages.json frontend/uni.scss git commit -m "feat(dashboard): 注册页面 + 追加 SCSS 设计 token" ``` --- ## Task 2: Mock 数据 + dashboardApi 命名空间 **Files:** - Create: `frontend/utils/mock/dashboard.js` - Modify: `frontend/utils/api.js`(追加 `dashboardApi` + 注入 mock 触发逻辑) - [ ] **Step 1: 创建 mock 数据文件 `frontend/utils/mock/dashboard.js`** ```javascript // 数据看板 mock 数据工厂 // 后端就绪后,将 utils/api.js 顶部 USE_MOCK_API 改为 false 即可 const randomDelay = (min = 200, max = 600) => new Promise((resolve) => setTimeout(resolve, Math.random() * (max - min) + min)) // 1. 今日概览 export async function mockTodayOverview({ star_id }) { await randomDelay() return { code: 200, data: { crystal_balance: 2713, today_income: 213, week_rank: 12, }, } } // 2. 七日收益曲线 export async function mock7DayIncomeCurve({ star_id }) { await randomDelay() const today = new Date() const points = [] const incomes = [180, 245, 198, 312, 276, 221, 189] // 最后一个是今天 for (let i = 6; i >= 0; i--) { const d = new Date(today) d.setDate(d.getDate() - i) const income = incomes[6 - i] points.push({ date: `${d.getFullYear()}.${String(d.getMonth() + 1).padStart(2, '0')}.${String(d.getDate()).padStart(2, '0')}`, income, is_today: i === 0, is_peak: income === 312, }) } return { code: 200, data: { points, total_income: incomes.reduce((a, b) => a + b, 0), avg_income: Math.round(incomes.reduce((a, b) => a + b, 0) / 7), }, } } // 3. 展出收益中心 export async function mockExhibitionSummary({ star_id }) { await randomDelay() return { code: 200, data: { exhibiting_count: 21, starbook_count: 33, total_duration: '712:13:56', total_earnings: 39721, top5: [ { asset_id: 1, asset_name: '璀璨星河', asset_thumb: '', duration_7d: '144:13:56', earnings_7d: 2173, avg_earnings: 15 }, { asset_id: 2, asset_name: '夏日微风', asset_thumb: '', duration_7d: '77:13:56', earnings_7d: 1332, avg_earnings: 15 }, { asset_id: 3, asset_name: '夜色霓虹', asset_thumb: '', duration_7d: '64:15:37', earnings_7d: 1201, avg_earnings: 12 }, { asset_id: 4, asset_name: '黎明序曲', asset_thumb: '', duration_7d: '51:22:12', earnings_7d: 783, avg_earnings: 12 }, { asset_id: 5, asset_name: '深海回响', asset_thumb: '', duration_7d: '51:22:12', earnings_7d: 783, avg_earnings: 12 }, ], }, } } // 4. 点赞收益按等级 export async function mockLikeIncomeByLevel({ star_id }) { await randomDelay() return { code: 200, data: { total_like_count: 231, total_income: 12719, levels: [ { level: 'UR', asset_count: 1, total_income: 723, thumb: '' }, { level: 'SSR', asset_count: 2, total_income: 381, thumb: '' }, { level: 'SR', asset_count: 5, total_income: 233, thumb: '' }, { level: 'SR', asset_count: 4, total_income: 169, thumb: '' }, { level: 'R', asset_count: 6, total_income: 57, thumb: '' }, ], }, } } // 5. 藏品 TOP5 export async function mockTopAssets({ star_id }) { await randomDelay() return { code: 200, data: { items: [ { asset_id: 1, asset_name: '璀璨星河', asset_thumb: '', total_earnings: 8420, rank: 1 }, { asset_id: 2, asset_name: '夏日微风', asset_thumb: '', total_earnings: 6230, rank: 2 }, { asset_id: 3, asset_name: '夜色霓虹', asset_thumb: '', total_earnings: 5180, rank: 3 }, { asset_id: 4, asset_name: '黎明序曲', asset_thumb: '', total_earnings: 4320, rank: 4 }, { asset_id: 5, asset_name: '深海回响', asset_thumb: '', total_earnings: 3980, rank: 5 }, ], }, } } // 6. 藏品等级分布 export async function mockLevelDistribution({ star_id }) { await randomDelay() const total = 33 return { code: 200, data: { items: [ { level: 'UR', count: 1, total }, { level: 'SSR', count: 2, total }, { level: 'SR', count: 5, total }, { level: 'R', count: 6, total }, { level: 'N', count: 0, total }, ], }, } } // 7. 升级进度 export async function mockUpgradeProgress({ star_id }) { await randomDelay() return { code: 200, data: { upcoming: [ { asset_id: 1, asset_name: '璀璨星河', asset_thumb: '', like_progress: 73, duration_progress: 92 }, { asset_id: 2, asset_name: '夏日微风', asset_thumb: '', like_progress: 75, duration_progress: 96 }, { asset_id: 3, asset_name: '夜色霓虹', asset_thumb: '', like_progress: 97, duration_progress: 71 }, ], recent: [ { asset_id: 4, asset_name: '黎明序曲', asset_thumb: '', new_level: 'SSR', upgrade_time: Date.now() - 3600000 }, { asset_id: 5, asset_name: '深海回响', asset_thumb: '', new_level: 'SR', upgrade_time: Date.now() - 86400000 }, { asset_id: 6, asset_name: '晨曦微光', asset_thumb: '', new_level: 'SR', upgrade_time: Date.now() - 172800000 }, ], }, } } // 路由表:endpoints 字符串 → mock 工厂 export const mockRouter = { '/api/v1/dashboard/today-overview': mockTodayOverview, '/api/v1/dashboard/income-curve': mock7DayIncomeCurve, '/api/v1/dashboard/exhibition-summary': mockExhibitionSummary, '/api/v1/dashboard/like-income-by-level': mockLikeIncomeByLevel, '/api/v1/dashboard/top-assets': mockTopAssets, '/api/v1/dashboard/level-distribution': mockLevelDistribution, '/api/v1/dashboard/upgrade-progress': mockUpgradeProgress, } ``` - [ ] **Step 2: 在 `frontend/utils/api.js` 末尾追加 `dashboardApi` 命名空间** ```javascript // ==================== 数据看板 ==================== import { mockRouter } from './mock/dashboard' const DASHBOARD_PREFIX = '/api/v1/dashboard' // mock 触发器:当 USE_MOCK_API 为 true 时短路返回 mock 数据 // 后端就绪后将 USE_MOCK_API 改为 false 即可 async function dashboardRequest(endpoint, params = {}) { if (USE_MOCK_API) { const factory = mockRouter[`${DASHBOARD_PREFIX}${endpoint}`] if (factory) return factory(params) } return request({ url: `${DASHBOARD_PREFIX}${endpoint}`, method: 'GET', data: params }) } export const dashboardApi = { getTodayOverview: (starId) => dashboardRequest('/today-overview', { star_id: starId }).then((r) => r.data), get7DayIncomeCurve: (starId) => dashboardRequest('/income-curve', { star_id: starId }).then((r) => r.data), getExhibitionSummary: (starId) => dashboardRequest('/exhibition-summary', { star_id: starId }).then((r) => r.data), getLikeIncomeByLevel: (starId) => dashboardRequest('/like-income-by-level', { star_id: starId }).then((r) => r.data), getTopAssets: (starId) => dashboardRequest('/top-assets', { star_id: starId }).then((r) => r.data), getLevelDistribution: (starId) => dashboardRequest('/level-distribution', { star_id: starId }).then((r) => r.data), getUpgradeProgress: (starId) => dashboardRequest('/upgrade-progress', { star_id: starId }).then((r) => r.data), } ``` - [ ] **Step 3: 打开 `USE_MOCK_API` 开关(关键前置步骤)** > **重要**:项目 `frontend/utils/api.js` 第 9 行 `USE_MOCK_API` 默认是 `false`,会直接请求真实后端。后端尚未实现,必须先翻成 `true` 才能用 mock 验证。 修改 `frontend/utils/api.js`: ```javascript // 第 9 行: // 原始: const USE_MOCK_API = false // 改为: const USE_MOCK_API = true ``` > **切回真实接口**:后端 `/api/v1/dashboard/*` 7 个接口联调通过后,把这里改回 `false` 即可。 - [ ] **Step 4: 手动验证 mock 触发** 在 H5 dev console 跑: ```js const { dashboardApi } = await import('/utils/api.js') const today = await dashboardApi.getTodayOverview(1) console.log(today) // 预期: { crystal_balance: 2713, today_income: 213, week_rank: 12 } ``` - [ ] **Step 5: Commit** ```bash git add frontend/utils/mock/dashboard.js frontend/utils/api.js git commit -m "feat(dashboard): 追加 mock 数据工厂 + dashboardApi 命名空间 + 打开 USE_MOCK_API" ``` --- ## Task 3: useDashboardData Composable **Files:** - Create: `frontend/composables/useDashboardData.js` - [ ] **Step 1: 完整实现 composable** ```javascript import { ref, computed, onScopeDisposal } from 'vue' import { dashboardApi } from '@/utils/api' /** * 数据看板聚合 composable * - 7 个接口并发(Promise.allSettled,单失败不阻塞) * - 按 section 维度暴露 loading/error/data * - 30 分钟内 refresh 不重发请求(lastFetched 缓存) * - effectScope + onScopeDisposal 自动清理(页面 onUnmounted 调用 dispose) * * @param {object} options * @param {number|null} options.starId 顶粉星城 ID * @returns {UseDashboardDataReturn} */ export function useDashboardData({ starId = null } = {}) { // —— 内部 state —— const loading = ref({ overall: false, today: false, curve: false, exhibition: false, likeIncome: false, topAssets: false, levels: false, upgrades: false, }) const error = ref({ today: null, curve: null, exhibition: null, likeIncome: null, topAssets: null, levels: null, upgrades: null, }) const data = ref({ today: null, curve: null, exhibition: null, likeIncome: null, topAssets: null, levels: null, upgrades: null, }) const lastFetched = ref(0) const STALE_MS = 30 * 60 * 1000 // 30 分钟 const isReady = computed(() => Object.values(data.value).every((v) => v !== null)) // —— 内部辅助:单 section 加载 —— async function loadSection(section, fetcher) { loading.value[section] = true error.value[section] = null try { const result = await fetcher(starId) data.value[section] = result } catch (e) { error.value[section] = e.message || '加载失败' data.value[section] = null } finally { loading.value[section] = false } } // —— 全量加载 —— async function loadAll(force = false) { if (!force && Date.now() - lastFetched.value < STALE_MS) return loading.value.overall = true try { // 并发执行,单个失败由 loadSection 内部捕获 await Promise.allSettled([ loadSection('today', dashboardApi.getTodayOverview), loadSection('curve', dashboardApi.get7DayIncomeCurve), loadSection('exhibition', dashboardApi.getExhibitionSummary), loadSection('likeIncome', dashboardApi.getLikeIncomeByLevel), loadSection('topAssets', dashboardApi.getTopAssets), loadSection('levels', dashboardApi.getLevelDistribution), loadSection('upgrades', dashboardApi.getUpgradeProgress), ]) lastFetched.value = Date.now() } finally { loading.value.overall = false } } // —— 局部刷新 —— // - 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]) } return loadAll(force) } // —— effectScope 资源释放 —— // 调用方在页面 onUnmounted 中调用 dispose() function dispose() { // ref 状态由 Vue 自动 GC;此处保留接口给未来清理(如取消 in-flight 请求) // 当前实现:无 in-flight 引用,无需额外清理 } // 自动调用一次首屏加载 loadAll() return { loading, error, data, refresh, isReady, lastFetched, dispose, } } ``` - [ ] **Step 2: 手动验证 composable 行为** 在 H5 dev console 跑: ```js const { useDashboardData } = await import('/composables/useDashboardData.js') const { data, loading, isReady, refresh } = useDashboardData({ starId: 1 }) // 等 ~1 秒 setTimeout(() => { console.log('isReady:', isReady.value) // true console.log('today:', data.value.today) // { crystal_balance: 2713, ... } console.log('curve.points.length:', data.value.curve.points.length) // 7 console.log('exhibition.top5.length:', data.value.exhibition.top5.length) // 5 }, 1500) ``` 预期:所有数据加载完成,形状如 mock。 - [ ] **Step 3: 验证局部刷新** ```js // 在同一 console 继续 await refresh('curve') console.log('loading.curve:', loading.value.curve) // 预期: false(load 完自动归位) console.log('data.curve:', data.value.curve !== null) // true ``` - [ ] **Step 4: Commit** ```bash git add frontend/composables/useDashboardData.js git commit -m "feat(dashboard): useDashboardData composable(7接口并发+section级refresh)" ``` --- ## Task 4: dashboard.vue 页面骨架 **Files:** - Create: `frontend/pages/dashboard/dashboard.vue` - [ ] **Step 1: 完整实现页面骨架** ```vue ``` - [ ] **Step 2: 创建 `DashboardHeader.vue` 占位(Task 5 会完整实现)** 文件 `frontend/pages/dashboard/components/DashboardHeader.vue`: ```vue ``` - [ ] **Step 3: 手动验证骨架页** 访问 H5 dev URL `/pages/dashboard/dashboard`: - 预期:红色装饰背景 + "数据看板" 标题 + 两个 Tab + "骨架页" 占位文字 - 切换 Tab:右半部分在占位和"赛季总览"间切换 - 打开 devtools console:1.5 秒后 `data.today` 等字段非空(可通过 Vue devtools 检查) - [ ] **Step 4: Commit** ```bash git add frontend/pages/dashboard/dashboard.vue frontend/pages/dashboard/components/DashboardHeader.vue git commit -m "feat(dashboard): 页面骨架 + Header 占位" ``` --- ## Task 5: DashboardHeader 完整实现(毛绒怪 + 渐变标题) **Files:** - Modify: `frontend/pages/dashboard/components/DashboardHeader.vue`(替换占位为完整实现) - [ ] **Step 1: 替换为完整实现** ```vue ``` - [ ] **Step 2: 手动验证** 访问 H5 dev URL `/pages/dashboard/dashboard`: - 预期:粉红渐变背景 + 红色光晕 + 圆形毛绒怪占位(带红色光晕阴影)+ 渐变文字"数据看板"+ 紫蓝粉渐变 Tab 胶囊 - 切换 Tab:胶囊平滑滑动 - [ ] **Step 3: Commit** ```bash git add frontend/pages/dashboard/components/DashboardHeader.vue git commit -m "feat(dashboard): Header 完整视觉(毛绒怪占位+渐变标题+Tab胶囊)" ``` --- ## Task 6: CrystalOverview 组件(顶部双卡) **Files:** - Create: `frontend/pages/dashboard/components/CrystalOverview.vue` - [ ] **Step 1: 完整实现** ```vue ``` - [ ] **Step 2: 在 dashboard.vue 中挂载组件** 修改 `frontend/pages/dashboard/dashboard.vue` 的 template: 把 ```vue 骨架页(组件将在 Task 5-10 添加) ``` 替换为: ```vue ``` 并在 components 中追加: ```js components: { DashboardHeader, CrystalOverview }, ``` - [ ] **Step 3: 手动验证** 刷新 H5 dev URL: - 预期:双卡显示"水晶余额 2713"和"今日收益 + 213",金黄色数字 + 渐变背景 - 1.5s 后才显示(loading 期间是骨架屏) - [ ] **Step 4: Commit** ```bash git add frontend/pages/dashboard/components/CrystalOverview.vue frontend/pages/dashboard/dashboard.vue git commit -m "feat(dashboard): CrystalOverview 双卡(水晶余额+今日收益)" ``` --- ## Task 7: IncomeCurve 组件(uCharts 七日柱状+折线) **Files:** - Create: `frontend/pages/dashboard/components/IncomeCurve.vue` - [ ] **Step 1: 完整实现 IncomeCurve.vue** ```vue ``` - [ ] **Step 3: 在 dashboard.vue 中挂载组件** 在 CrystalOverview 之后追加: ```vue ``` components 追加 `IncomeCurve`。 - [ ] **Step 4: 安装 uCharts** ```bash cd frontend && npm install qiun-data-charts ``` 如项目无 H5 组件库依赖关系导致构建失败,回退到 `chart-theme.json` + 占位文字(Step 2 中已留 fallback)。 - [ ] **Step 5: 手动验证** 刷新 H5 dev URL: - 预期:渐变卡片 + "七日收益曲线" 标题 + 柱状图(7 根柱,渐变填充)+ 折线(蓝黄渐变)+ 高亮当日 "+ 312" + 日期 - 加载中:渐变骨架屏闪烁 - [ ] **Step 6: Commit** ```bash 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)" ``` --- ## Task 8: ExhibitionCenter 组件(3 联 + 5 行表格) **Files:** - Create: `frontend/pages/dashboard/components/ExhibitionCenter.vue` - [ ] **Step 1: 完整实现** ```vue ``` - [ ] **Step 2: 在 dashboard.vue 中挂载** 在 IncomeCurve 后追加: ```vue ``` components 追加 `ExhibitionCenter`。 - [ ] **Step 3: 手动验证** 刷新 H5 dev URL: - 预期:玻璃拟态卡片 + "展出收益中心" 标题 + 3 联统计(21/33、712:13:56、39721)+ 5 行表格(每行 4×5 缩略图 + 时长 + 收益 + 平均) - [ ] **Step 4: Commit** ```bash git add frontend/pages/dashboard/components/ExhibitionCenter.vue frontend/pages/dashboard/dashboard.vue git commit -m "feat(dashboard): ExhibitionCenter 3联+5行表格" ``` --- ## Task 9: LikeIncomeBoard 组件 **Files:** - Create: `frontend/pages/dashboard/components/LikeIncomeBoard.vue` - [ ] **Step 1: 完整实现** ```vue ``` - [ ] **Step 2: 在 dashboard.vue 中挂载** 在 ExhibitionCenter 后追加: ```vue ``` components 追加 `LikeIncomeBoard`。 - [ ] **Step 3: 手动验证 + Commit** ```bash git add frontend/pages/dashboard/components/LikeIncomeBoard.vue frontend/pages/dashboard/dashboard.vue git commit -m "feat(dashboard): LikeIncomeBoard 左侧统计+右侧等级列表" ``` --- ## Task 10: CollectionMatrix + TopFiveAssets **Files:** - Create: `frontend/pages/dashboard/components/CollectionMatrix.vue` - Create: `frontend/pages/dashboard/components/TopFiveAssets.vue` - [ ] **Step 1: 完整实现 TopFiveAssets.vue** ```vue ``` - [ ] **Step 2: 完整实现 CollectionMatrix.vue(容器,先只接 TopFiveAssets)** ```vue ``` - [ ] **Step 3: 在 dashboard.vue 中挂载** 在 LikeIncomeBoard 后追加: ```vue ``` components 追加 `CollectionMatrix`。 - [ ] **Step 4: 手动验证 + Commit** ```bash git add frontend/pages/dashboard/components/TopFiveAssets.vue frontend/pages/dashboard/components/CollectionMatrix.vue frontend/pages/dashboard/dashboard.vue git commit -m "feat(dashboard): CollectionMatrix 容器 + TopFiveAssets" ``` --- ## Task 11: LevelDistribution 组件(5 个 conic-gradient 环形图) **Files:** - Create: `frontend/pages/dashboard/components/LevelDistribution.vue` - [ ] **Step 1: 完整实现** ```vue ``` - [ ] **Step 2: 在 CollectionMatrix.vue 中挂载** 在 `` 后追加: ```vue ``` 并追加 import 和 components 注册。 - [ ] **Step 3: 手动验证 + Commit** ```bash git add frontend/pages/dashboard/components/LevelDistribution.vue frontend/pages/dashboard/components/CollectionMatrix.vue git commit -m "feat(dashboard): LevelDistribution 5个conic-gradient环形图" ``` --- ## Task 12: UpcomingUpgrades + RecentUpgrades **Files:** - Create: `frontend/pages/dashboard/components/UpcomingUpgrades.vue` - Create: `frontend/pages/dashboard/components/RecentUpgrades.vue` - [ ] **Step 1: 完整实现 UpcomingUpgrades.vue** ```vue ``` - [ ] **Step 2: 完整实现 RecentUpgrades.vue** ```vue ``` - [ ] **Step 3: 在 CollectionMatrix.vue 中挂载(两列布局)** 替换 CollectionMatrix.vue 的 template 为: ```vue ``` 并在 ` ``` - [ ] **Step 2: 确认 `pages.json` 中 dashboard 条目未启用 `enablePullDownRefresh`** Task 1 写入的 dashboard 条目已正确(不包含 `enablePullDownRefresh`)。**不要**手动加这个字段——下拉刷新由 scroll-view 的 `:refresher-enabled="true"` 接管,两者重复会冲突。 - [ ] **Step 3: 手动验证** 刷新 H5 dev URL: - 拖动下拉 → 触发强制刷新(loading.overall 短暂为 true) - 切换 Tab 1 → Tab 2 → Tab 1:30 分钟内 cache-aware 静默刷新(不闪骨架) - 离开页面到其他页面 → 返回:onShow 触发 force refresh - 关闭网络(DevTools Network → Offline)→ 各 section 显示错误态 - 恢复网络 → 点击错误卡片 → 该 section 重试成功 - [ ] **Step 4: 验证 Figma 视觉一致性** 用 `superpowers:verification-before-completion` 流程对照 `docs/figma-analysis-data-dashboard.md` 第三节设计 token 逐项核对: - [ ] 颜色 token 与 §3.1 一致 - [ ] 阴影 token 与 §3.2 一致 - [ ] 圆角与 §3.3 一致 - [ ] 5 个等级色与 §2.7.2 一致 - [ ] **Step 5: Commit** ```bash git add frontend/pages/dashboard/dashboard.vue git commit -m "feat(dashboard): onShow 强制刷新 + Tab 缓存 + 下拉刷新(scroll-view refresher)" ``` ## 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(验证任务,无代码变更)** --- --- ## 风险与决策记录 | 风险 | 应对 | |------|------| | uCharts 在小程序/App 端需单独配置 | Task 7 留 fallback 占位;App 端后续用 `qiun-data-charts` 原生插件补充 | | 后端接口契约变更 | proto 是单一信源(spec §4.2),mock 与接口字段同步更新 | | 视觉稿 1:1 像素级还原 | Task 14 末提供核对清单;超出 token 范围的微调由 UI 评审 | | 7 接口并发 401 处理 | `utils/api.js` 已有统一处理(清除 token + 跳登录),无需 dashboard 额外处理 | ## 后续可扩展(不在本计划) - Tab 2 "赛季总览" 真实数据(spec §7.3) - IncomeCurve 点击柱子弹窗(spec §7.3) - 升级提醒推送(spec §7.3) - 视觉资源(Figma 素材)批量下载与替换占位 ## 变更记录 | 日期 | 变更 | 作者 | |------|------|------| | 2026-06-02 | 初版 | Claude |