docs: 数据看板前端设计 spec
锁定 composables 方案、文件结构、组件契约、loading/error 策略, 并附实施顺序与风险记录。后端 dashboardService 尚未实现,前端先用 mock。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
29173ca55e
commit
1b1f9b22fe
@ -0,0 +1,334 @@
|
||||
# 数据看板前端 - 设计文档
|
||||
|
||||
> **配套文档**: `docs/figma-analysis-data-dashboard.md`(Figma 视觉稿 + 后端 API 契约)
|
||||
> **本文档焦点**: 前端架构与实现策略(视觉细节、token、API 字段、SQL 均见配套文档)
|
||||
|
||||
## 一、背景与范围
|
||||
|
||||
### 1.1 背景
|
||||
|
||||
数据看板是顶粉星城个人中心的核心数据可视化页,展示用户的水晶收益、藏品表现、升级进度。当前**后端 `/api/v1/dashboard/*` 7 个接口尚未实现**,前端需在 mock 数据下先行开发完整 UI 与交互。
|
||||
|
||||
### 1.2 范围
|
||||
|
||||
**In scope**:
|
||||
- 6 大模块完整 UI(水晶概览、七日曲线、展出收益、点赞收益、藏品矩阵含 4 子组件)
|
||||
- Tab 1 "水晶相关" 完整实现
|
||||
- Tab 2 "赛季总览" 占位骨架
|
||||
- 加载/空/错误三态
|
||||
- mock 数据接入
|
||||
|
||||
**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-ucharts`(H5)实现七日柱状+折线。
|
||||
|
||||
**理由**:
|
||||
- 移动端 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 个 section;Tab 2 渲染占位
|
||||
- 监听 `onShow`:Tab 1 切回时如有缓存则用旧数据 + 后台静默刷新
|
||||
|
||||
**Props**: 无
|
||||
**Key state**: `activeTab: 'crystal' | 'season'`
|
||||
**Key event**: `onShow` → `refresh({ force: true })`
|
||||
|
||||
### 4.2 `useDashboardData.js` Composable 契约
|
||||
|
||||
**签名**:
|
||||
```js
|
||||
const {
|
||||
loading, // { overall, today, curve, exhibition, likeIncome, topAssets, levels, upgrades }
|
||||
error, // 同 shape,string | null
|
||||
data, // { today, curve, exhibition, likeIncome, topAssets, levels, upgrades }
|
||||
refresh, // (section?: string | { section, force }) => Promise
|
||||
isReady, // computed: 所有 data 字段非空
|
||||
lastFetched, // timestamp,30 分钟内 refresh 不重发请求
|
||||
} = useDashboardData({ starId })
|
||||
```
|
||||
|
||||
**关键行为**:
|
||||
- 首次调用:7 个接口 `Promise.allSettled` 并发
|
||||
- 单 section 失败 → 该 section `data` 保持 `null`、`error` 有值、UI 显示重试
|
||||
- `refresh('curve')`:只重拉 curve 一个 section
|
||||
- `refresh({ force: true })`:绕过 `lastFetched` 缓存
|
||||
- `onUnmounted` 自动清空所有 state(依赖 Vue 3 组件卸载生命周期)
|
||||
|
||||
### 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`(项目已有该文件):
|
||||
|
||||
```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 个工厂函数:
|
||||
```js
|
||||
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 |
|
||||
Loading…
Reference in New Issue
Block a user