Appearance
Stats 今日概览(前端调用视角)
后端契约:手册 · page-direct#gettodayoverview(字段类型 / curl / 错误码 —— 本页零复述)。 调用现状:页面直调(未收敛为
src/api/*.ts)—— 首页src/pages/index/index.vue:105-146loadToday()。消费页:pages/index
getTodayOverview(页面直调 · 首页热量 / 连续天数卡)
一句话:首页 loadToday() 在 onShow 时拉一次 —— 给三环提供目标值(预算热量)与当日打卡统计;「实际值」(步数 / 消耗 / 距离)由 myTodayHealth 提供(见 device · myTodayHealth)。
前端函数签名
| 项 | 值 |
|---|---|
| 封装 | 页面直调(未收敛)—— src/pages/index/index.vue:105-146 loadToday() |
| 触发时机 | 首页 onShow(index.vue:147);与 myTodayHealth 并行(同一 Promise.all) |
| 鉴权 | Bearer access(request.ts 默认档) |
必需 payload 键
| 键 | 从哪来 | 必填 |
|---|---|---|
action | 字面量 'getTodayOverview' | ✔ |
payload | 该 action 无入参 —— 前端连 payload 键都没传,后端按忽略处理 | ✖ |
响应读法(⚠️ 两层嵌套 —— 曾读顶层导致「热量目标恒 400」,前端已修,勿写回)
ts
const res = await request({ url: 'api/v1/stats', method: 'post', data: { action: 'getTodayOverview' } });
const c = res?.data?.calorie; // 中文注解:先取 calorie 块 —— 它是嵌套对象,不是顶层字段
calorieGoal.value = c?.budgetKcal ?? 400; // 中文注解:⚠️ budgetKcal 现可能为 null(画像不完整)—— `?? 400` 会把空值伪装成有数据,应改为判空分支
const doneCount = res?.data?.checkin?.count ?? 0; // 中文注解:连续打卡天数卡
const doneKm = res?.data?.checkin?.distanceKm ?? 0; // 中文注解:同日打卡距离合计✅ 正确读法:
res.data.calorie.budgetKcal(先calorie再取键) ❌ 错误读法:res.data.budgetKcal—— 恒undefined→?? 400兜底生效 → 热量目标静默恒为默认 400。 历史(防回归):这个 bug 在生产真实存在过(前端api分支24902e9,2026-09-20 修复); 根因是文档把data.calorie.budgetKcal简写成扁平的{budgetKcal, ...},与后端自始的嵌套结构不一致 —— 本页现在写的是修正后的真值,勿改回。 ⚠️ 另注:400是index.vue:87的渲染初值,不是契约值;修复前生产实测calorie.budgetKcal = 1423—— 那个 1423 是后端画像没读到、用默认资料(30岁/165cm/60kg)编出来的假数(2026-09-20 定位)。 现契约:画像完整 → 真实资料算出的值;画像缺失 →null(不再编数) —— 见下表。
响应真值(嵌套两层)
| 路径 | 前端拿它干什么 |
|---|---|
data.date | 今日(CN 时区 YYYY-MM-DD)—— 与设备侧 myTodayHealth.date 同口径,可对拍 |
data.calorie.budgetKcal / .remainingKcal | 三环目标值 / 剩余额度(服务端按 BMR 算;画像不完整时为 null —— 2026-09-20 起不编默认值) |
data.calorie.intakeKcal / .burnKcal | 摄入 / 消耗(无数据为 0,不是 null —— 判空时别把 0 当"没数据") |
data.calorie.profileComplete | 画像是否完整;false ⇒ budgetKcal/remainingKcal 为 null(不编数)→ 前端必须判空(提示补全资料 / 隐藏三环),禁止 ?? 400 之类兜底把空值伪装成有数据(400 只是渲染初值) |
data.checkin.count | 当日打卡数(连续天数卡) |
data.checkin.distanceKm | 当日打卡距离 |
字段类型 / 可空性 / 错误码见 手册 · page-direct#gettodayoverview。
失败时的前端动作
| 情形 | 前端动作 |
|---|---|
| 任意业务失败 | catch 内 console.error,UI 保默认值 —— 不弹 toast(首页首屏不打扰;request.ts 的非 200 分支仍会 toast,属兜底) |
⚠️ Promise.all 一损俱损 | loadToday() 是 Promise.all([stats, myTodayHealth]):stats 失败会连带丢弃 myTodayHealth 已经成功拿到的数据(两者走同一个 catch)。旧表述「单接口失败不阻塞首页」只对了"首页不崩",漏了这层耦合 —— 结果是「热量卡无目标 + 健康卡明明有数据却也不更新」。 要让两者各自降级需改前端为 Promise.allSettled(前端改动,非本轮文档范围) |
| 401 | request.ts 拦截器 rotate 单飞续期并重放一次(与 手册 G5 一致);失败则强登出回登录页 —— 页面 catch 不额外处理 |
⚠️ 坑位
- 嵌套 vs 平铺:同类问题已出现 2 次(本端点把
calorie.budgetKcal读成顶层;myTodayHealth顶层distancevstodayActivity.totalDistanceKm)。 写新代码前先把响应console.log出来看一眼层级,别照平铺猜 —— 后端 29/30 端点无 Pydantic 响应模型,读错层级不会有任何报错。 40422xxx与ranking硬撞车:stats 源码用40422xxx,而 错误码注册表 把该段登记为ranking—— 前端无法靠前缀区分两者异常。属台账 #6 的根因,见 联调台账。X-Env全程一致(与 手册 G2 一致);App 产线包X_ENV = "Prd"(src/config/index.ts+build-prod.sh构建期硬断言)。