topfans/docs/superpowers/specs/2026-06-02-data-dashboard-frontend-design.md
zheng020 e3f28a82c9 docs: 数据看板 spec 补充 effectScope 与 6/7 映射说明
应用 spec reviewer 的两条建议:澄清 composable 用 effectScope 释放
资源(非依赖 onUnmounted),并显式说明 6 组件消费 7 接口的映射
(CollectionMatrix 内部消费 3 个)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-03 01:20:47 +08:00

14 KiB
Raw Blame History

数据看板前端 - 设计文档

配套文档: docs/figma-analysis-data-dashboard.mdFigma 视觉稿 + 后端 API 契约) 本文档焦点: 前端架构与实现策略视觉细节、token、API 字段、SQL 均见配套文档)

一、背景与范围

1.1 背景

数据看板是顶粉星城个人中心的核心数据可视化页,展示用户的水晶收益、藏品表现、升级进度。当前后端 /api/v1/dashboard/* 7 个接口尚未实现,前端需在 mock 数据下先行开发完整 UI 与交互。

1.2 范围

In scope:

  • 6 大模块完整 UI水晶概览、七日曲线、展出收益、点赞收益、藏品矩阵含 4 子组件)
  • Tab 1 "水晶相关" 完整实现
  • Tab 2 "赛季总览" 占位骨架
  • 加载/空/错误三态
  • mock 数据接入

6 组件 ↔ 7 接口映射

  • CrystalOverview → 1 个接口today-overview
  • IncomeCurve → 1 个接口income-curve
  • ExhibitionCenter → 1 个接口exhibition-summary
  • LikeIncomeBoard → 1 个接口like-income-by-level
  • CollectionMatrix容器3 个接口top-assets、level-distribution、upgrade-progress子组件各消费其一

Out of scope不在本文档:

  • 后端 dashboardService 实现(详见配套文档第 4 节)
  • Figma 素材下载(项目自有 static/components/ 已含同类资源)
  • 数据看板分享、7日曲线交互弹窗、升级提醒等扩展项配套文档 7.3
  • 视觉稿 1:1 像素级还原本文档保证结构、token、文案一致像素级由前端开发阶段人工对齐

二、架构决策

2.1 状态管理Composables不引入新 Vuex 模块)

决策: 使用 Vue 3 Composition API 的 useDashboardData composable 聚合 7 个接口的请求、loading、错误、刷新。

理由:

  1. dashboard 数据完全页面内,不跨页面共享(水晶余额走单独的 getUserProfileApi,已在 utils/api.js 中)
  2. 移动端内存友好——页面 onUnmounted 时 composable 自动清理状态
  3. 与项目既有 composables/useHolographicPreview.js 等 Vue 3 模式一致
  4. 减少一个 Vuex 模块文件,组件测试更易隔离

取捨:

  • 失去:跨页面数据共享(不需)、统一 Vuex devtools 视图(影响小)
  • 获得移动端页面切换内存自动回收、composable 单元测试简单

2.2 API 函数组织:追加到 utils/api.js(不新建 api/ 目录)

决策: 7 个 dashboard 接口以 dashboardApi 命名空间对象追加到 utils/api.js 末尾。

理由:

  • 项目 24+ 个 *Api 函数全在 utils/api.js(如 loginApi/getAssetDetailApi),惯例一致
  • 配套文档 5.3 节建议的 frontend/api/dashboard.js 路径会引入新目录,与项目惯例冲突

取捨:

  • 失去dashboard 相关的 API 物理上不分组
  • 获得:与项目惯例一致;后续接真实后端时只改 mock 触发逻辑,文件位置不动

2.3 图表方案uCharts 插件

决策: 引入 qiun-data-charts(跨端)/ @qiun/vue-uchartsH5实现七日柱状+折线。

理由:

  • 移动端 Canvas 手绘跨端适配成本高
  • uCharts 包体积 +200KB 可接受
  • 多端一致,交互能力强(后续可扩展点击柱子查看明细)

取捨:

  • 失去:零依赖的轻量优势
  • 获得:跨端一致、避免重复造轮子

2.4 Mock 数据触发:复用 USE_MOCK_API 开关

决策: 沿用 utils/api.js 顶部已有的 USE_MOCK_API 常量mock 数据集中在 utils/mock/dashboard.js

理由:

  • 不引入新机制,沿用项目已有模式
  • 后续接真实后端只需将该常量翻为 false

取捨:

  • 失去mock 与真实接口文件分离mock 在 utils/mock/,真实在 utils/api.js
  • 获得:触发逻辑统一,未来切真实接口 1 行代码

三、文件结构

frontend/
├── pages/
│   └── dashboard/
│       ├── dashboard.vue                       # 页面tab 切换、调用 composable、分发 props
│       └── components/
│           ├── DashboardHeader.vue             # 装饰背景 + 标题 + Tab
│           ├── CrystalOverview.vue             # 水晶余额+今日收益双卡
│           ├── IncomeCurve.vue                 # uCharts 七日柱状+折线
│           ├── ExhibitionCenter.vue            # 展出收益中心3联+5行
│           ├── LikeIncomeBoard.vue             # 点赞收益看板
│           ├── CollectionMatrix.vue            # 藏品矩阵容器
│           ├── TopFiveAssets.vue               # TOP5 横向卡片
│           ├── LevelDistribution.vue           # 5 个环形图CSS conic-gradient
│           ├── UpcomingUpgrades.vue            # 即将升级(左列)
│           ├── RecentUpgrades.vue              # 最近升级(右列)
│           ├── SectionSkeleton.vue             # 通用骨架屏(被各 section 复用)
│           └── EmptyState.vue                  # 通用空态
├── composables/
│   └── useDashboardData.js                     # 核心:聚合 7 个接口、loading、错误、刷新
├── utils/
│   ├── api.js                                  # 追加 dashboardApi 命名空间 + mock 触发逻辑
│   └── mock/
│       └── dashboard.js                        # 7 个接口的 mock 数据工厂
└── pages.json                                  # 注册 dashboard 页面

四、组件设计

4.1 页面 dashboard.vue

职责:

  • 调用 useDashboardData({ starId }) 获取全部数据
  • 顶部 <DashboardHeader> 含 Tab 切换,本地状态 activeTab
  • Tab 1 渲染所有 6 个 sectionTab 2 渲染占位
  • 监听 onShowTab 1 切回时如有缓存则用旧数据 + 后台静默刷新

Props: 无 Key state: activeTab: 'crystal' | 'season' Key event: onShowrefresh({ force: true })

4.2 useDashboardData.js Composable 契约

签名:

const {
  loading,        // { overall, today, curve, exhibition, likeIncome, topAssets, levels, upgrades }
  error,          // 同 shapestring | null
  data,           // { today, curve, exhibition, likeIncome, topAssets, levels, upgrades }
  refresh,        // (section?: string | { section, force }) => Promise
  isReady,        // computed: 所有 data 字段非空
  lastFetched,    // timestamp30 分钟内 refresh 不重发请求
} = useDashboardData({ starId })

关键行为:

  • 首次调用7 个接口 Promise.allSettled 并发
  • 单 section 失败 → 该 section data 保持 nullerror 有值、UI 显示重试
  • refresh('curve'):只重拉 curve 一个 section
  • refresh({ force: true }):绕过 lastFetched 缓存
  • 资源释放composable 内部用 effectScope 包裹请求与状态dashboard.vue 在 onUnmounted 中调用 composable 暴露的 dispose() 释放 effectScope确保 setTimeout/Promise 回调不污染已卸载组件

4.3 子组件 Props 契约

组件 Props 备注
DashboardHeader activeTab, @update:activeTab 受控模式
CrystalOverview { balance, today }, loading, error, @retry 单一对象loading 顶层
IncomeCurve points: DailyIncomePoint[], loading, error, @retry uCharts 主题色 props
ExhibitionCenter summary: ExhibitionIncomeSummary, loading, error, @retry 含 3 联 + 5 行
LikeIncomeBoard stats: { total, income }, levels: LikeIncomeLevelItem[], loading, error, @retry
CollectionMatrix topFive, levels, upgrades, loadingMap, errorMap, @retrySection 容器,加载态按子组件维度分发
TopFiveAssets items: TopAssetItem[]
LevelDistribution items: AssetLevelItem[] 5 个环形 conic-gradient
UpcomingUpgrades items: UpcomingLevelUpItem[]
RecentUpgrades items: RecentLevelUpItem[]
SectionSkeleton variant: 'card'|'list'|'chart'|'matrix' 通用骨架屏
EmptyState text?: string, icon?: string 默认 "暂无数据"

五、Loading / Empty / Error 三态

5.1 Loading

  • 整体首次加载loading.overall === true → 渲染全屏骨架屏(所有 6 个 section 同步显示 SectionSkeleton
  • 单 section 加载:该 section 渲染 SectionSkeleton 变体,其他 section 保持数据
  • 下拉刷新loading.overall 短暂为 true复用骨架屏

5.2 Empty

  • 各 section 内置判断:if (data && data.length === 0) → <EmptyState />
  • 文案区分:
    • 顶部水晶/收益:永不为空(接口兜底返回 0
    • 七日曲线:空态文案"暂无收益记录"
    • 展出/点赞/藏品矩阵:空态文案"暂无数据"
  • Tab 2 占位:显示插画 + "赛季总览 · 即将上线" 文案

5.3 Error

  • 单 section 失败 → 该 section 内显示错误占位(红色边框 + "加载失败,点击重试" + 触发 @retry
  • 整页不弹 toast移动端网络抖动频繁全屏 toast 体验差)
  • 接口 401 走 utils/api.js 已有的统一处理(清除 token + 跳登录)

六、视觉规范

6.1 设计 Token 落地

将配套文档 3.x 节的 token 翻译成 SCSS 变量,写入 uni.scss(项目已有该文件):

/* 颜色 */
$color-text-data: #FFFABD;
$color-tab-active: linear-gradient(231deg, #A8A6ED 0%, #88C8D8 64%, #F380EF 100%);
$color-card-gradient-1: linear-gradient(135deg, #FFDF77 0%, #8E95E2 40%, #F48CFF 100%);
$color-progress-cyan: linear-gradient(90deg, #5EDFFF 0%, #FFC8C8 100%);
$color-progress-pink: linear-gradient(90deg, #FFF375 0%, #FF6B84 100%);
$color-bar-blue-yellow: linear-gradient(90deg, #1BAFEE 0%, #FFCC14 100%);
// ... 其他 token

/* 阴影 */
$shadow-card: 0px 4px 4px rgba(189, 50, 50, 0.25);
$shadow-data-text: -1px 1px 4px rgba(206, 9, 9, 0.84);
// ...

/* 圆角 */
$radius-thumb: 3px;
$radius-card-sm: 14px;
$radius-card-lg: 22px;
// ...

/* 字体 */
$font-num-xl: 35px 'Baloo Bhai', sans-serif;
// ...

页面/组件内通过 var(--xxx) 引用uni.scss 编译后生成 CSS 变量)。

6.2 5 种等级环

LevelDistribution.vue 用 CSS conic-gradient 实现 5 个环形图:

  • 等级色UR/SSR/SR/R/N 各自对应一套渐变色(写在 SCSS 变量)
  • 外环 17px、内环 5px按配套文档 2.7.2 节)
  • 中心数字 + 百分比覆盖在环上

6.3 uCharts 主题

IncomeCurve.vue 引用 uCharts 主题配置文件 static/dashboard/chart-theme.json

  • 5 种渐变(黄→紫→粉、青→粉、黄→红、蓝→黄)映射到 UR/SSR/SR/R/N 等级
  • 高亮柱is_peak=true用更亮的渐变

6.4 资源占位

配套文档 7.1 节的 Figma 资源毛绒怪头像、TOP 徽章、等级徽章等):

  • 本期不下载:项目 static/components/ 已有同类风格资源
  • 占位策略:用纯 CSS 渐变方块 + 文字标签代替(如 [UR] [SSR] 文字徽章)
  • 后续:设计资源到位后逐个替换

七、数据契约(与配套文档 4.2 一致,本节为前端视角)

7.1 7 个接口

接口 方法 路径 数据形状(前端)
今日概览 GET /api/v1/dashboard/today-overview?star_id=X { crystal_balance, today_income, week_rank? }
七日曲线 GET /api/v1/dashboard/income-curve?star_id=X { points: DailyIncomePoint[], total_income, avg_income }
展出收益 GET /api/v1/dashboard/exhibition-summary?star_id=X ExhibitionIncomeSummary
点赞收益 GET /api/v1/dashboard/like-income-by-level?star_id=X { total_like_count, total_income, levels }
TOP 藏品 GET /api/v1/dashboard/top-assets?star_id=X { items: TopAssetItem[] }
等级分布 GET /api/v1/dashboard/level-distribution?star_id=X { items: AssetLevelItem[] }
升级进度 GET /api/v1/dashboard/upgrade-progress?star_id=X { upcoming, recent }

类型定义在 composables/useDashboardData.js 顶部用 JSDoc 注释表达,配套文档 4.2 节的 proto 字段是单一信源。

7.2 Mock 数据形状

utils/mock/dashboard.js 导出 7 个工厂函数:

export const mockTodayOverview = (params) => ({ crystal_balance: 2713, today_income: 213, week_rank: 12 })
export const mock7DayIncomeCurve = (params) => ({ points: [...7 items with is_today + is_peak], total_income: 1823, avg_income: 260 })
// ... 其他 5 个

工厂内根据 params.star_id 返回不同值mock 至少 2 个 star_id 的数据以验证切换)。


八、测试策略

8.1 单元测试

  • composables/useDashboardData.js 核心:覆盖 4 个场景
    • 首次加载 7 个接口并发
    • 单 section 失败不阻塞其他
    • refresh('curve') 只刷一个
    • onUnmounted 清空 state
  • 测试框架:项目未引入测试框架,本期不引入;如必要,使用 @vue/test-utils + vitest 作为 P2 任务

8.2 手动验证清单

  • 视觉对齐 Figma 稿
  • Tab 切换不重发请求
  • 下拉刷新拉新数据
  • 飞行模式下各 section 独立重试
  • 后端 401 走统一跳登录
  • 移动端 320px 小屏无溢出

8.3 E2E

  • 本期不做(项目无 Playwright 基建)

九、实施顺序(与配套文档 6.1 一致,标注前端部分)

阶段 内容 估时 验收
M2 pages.json 注册、空页面路由、composable、API 封装、mock 1 天 页面可访问、loading/empty/error 三态正确
M3 DashboardHeader + CrystalOverview + IncomeCurve 2 天 视觉稿对齐
M4 ExhibitionCenter + LikeIncomeBoard 2 天 视觉稿对齐
M5 CollectionMatrix含 4 个子组件) 2 天 视觉稿对齐
M6 联调、tab 切换、刷新、空态、错误处理 1 天 全链路通过
总计 8 工作日

十、风险与决策记录

风险/决策 影响 应对/理由
uCharts 跨端版本差异 IncomeCurve 实现 #ifdef H5#ifdef APP-PLUS 条件编译,按官方文档配置
移动端首屏 7 个接口并发 网络抖动场景 Promise.allSettled + 单 section 独立重试
后端接口契约变更 联调阶段 proto 是单一信源,前端 mock 与 proto 字段同步
Figma 资源未下载 视觉稿 1:1 还原 本期用 CSS 占位;资源到位后逐个替换(不影响架构)
USE_MOCK_API 开关切换 后端就绪时 utils/api.js 顶部 1 行常量改动

十一、变更记录

日期 变更 作者
2026-06-02 初版 Claude