Appearance
page-api-reference —— 前端页面 × 后端 API 反向索引(v1)
沐禾健康 C 端 uniapp(qingmu2026/qm-frontend
api分支)所有已上线页面调用的后端 API 总览。 入口文档:找页面,看它调什么;找 API,看谁在用。 写法契约:
- 字段表 / 错误码 / curl 一律见 前端联调 API 手册
- 调用读法 / 失败前端动作 见 uniapp/api/*
- 页面职责 / 关键逻辑 见 uniapp/pages/*
- 本文档聚合三者 —— 按 uniapp 页面分类,给出「该页调用哪些 API + 关键参数 + 响应读法」一行总结
总览
| 维度 | 数 |
|---|---|
| uniapp 页面 | 36(34 已上线 + 2 待建:city-select / rawfood-ranking) |
| 后端 API 端点 | 54(30 主干 REST/Action + 19 新接 + 5 city/ranking) |
| 按页面分类组 | 7 大类(登录启动 / 首页统计 / 详情 / 表单 / 个人中心 / 跑步 / 🆕 城市与生吃) |
| 最后更新 | 2026-09-29(city + rawfood 上线) |
⚠️ 页面 ↔ API 矩阵见
tests/reports/contract-matrix.json(CI 校验源)。 通用约定 G1-G7(统一包络 / X-Env / 鉴权三档 / rotate 单飞)见 手册 · 通用约定。
📑 按类别跳转
| 类别 | 页面数 | 锚点 |
|---|---|---|
| 🚪 登录与启动 | 5 | → §1 |
| 🏠 首页与统计 | 2 | → §2 |
| 📊 详情页(14 子页) | 14 | → §3 |
| 📝 表单 | 1 | → §4 |
| 👤 个人中心 | 7 | → §5 |
| 🏃 跑步会话 | 3 | → §6 |
| 🆕 选城市 + 生吃榜(待建) | 2 | → §7 |
§1 登录与启动
pages/login/index.vue — 手机号登录(513 行 · Vue3 setup)
| 步骤 | API 调用 | 关键 payload | 响应读法 | 错误码速查 |
|---|---|---|---|---|
| 进入页面 | POST /api/v1/auth/oauth {action:"captcha"} | {} | res.data.png → <image :src> 渲染 | 40101005 限流 |
| 点「获取验证码」 | POST /api/v1/auth/oauth {action:"sms-send"} | phone/captchaId/captchaCode/clientId | res.data.ok | 40101003/04/05/06 |
| 点「登录」 | POST /api/v1/auth/oauth {action:"sms-login"} | phone/smsCode/agreementAccepted + 可选 nickname/deviceInfo | res.data.tokens.{access,refresh} + res.data.user | 40101001/08/13 |
| 失败回退 | POST /api/v1/auth/oauth {action:"sms-verify"}(分步表单可选) | phone/smsCode/clientId | res.data.ok | 40101001/03 |
⚠️ 铁律:图形码无论成败即消费 —— 任何失败分支重试前必须重新
captcha(手册 §captcha 渲染三定律)。
调用详情:guide/uniapp/api/oauth.md · 契约字段表
pages/agreement/index.vue — 用户协议 + 隐私协议
| API 调用 | 用途 |
|---|---|
GET /api/v1/content/agreements | 列协议版本(决定 UI 是否要更新) |
GET /api/v1/content/agreements/{type} | 拉单篇协议正文(userAgreement / privacyPolicy) |
调用详情:guide/uniapp/api/agreement.md
pages/splash/index.vue — 开机信息屏
| API 调用 | 关键字段 | 用途 |
|---|---|---|
GET /api/v1/splash-screen | — | 拉开机图/视频 + 天气 + AI 文生图素材 |
POST /api/v1/splash-screen/track | eventType/eventPayload | 上报曝光/点击/完成埋点 |
⚠️ 不要把
splash.city(广告投放回传)当成「当前城市」—— 见 §7.1 city-select 跳引导页。
调用详情:guide/uniapp/api/splash.md
pages/webview/index.vue — 内嵌 WebView
text
无后端调用 —— 纯 WebView 容器,加载 splash 拉的 URL 或分享目标页pages/ai/index.vue — AI 智能助手
text
无 Action 调用(页面直调文 → 未收录到 src/api/*.ts —— 待接入)§2 首页与统计
pages/index/index.vue — 首页(健康仪表盘)
| 调用时机 | API | 用途 |
|---|---|---|
| 启动 / 下拉刷新 | POST /api/v1/stats {action:"getTodayOverview"} | 今日概览(热量/步数/心率/睡眠) |
| 启动 / 下拉刷新 | POST /api/v1/device {action:"myTodayHealth"} | 设备今日健康摘要(与 stats 互补) |
| onShow | POST /api/v1/city {action:"getCurrentCity"} | 🆕 当前城市(40431003 跳 city-select) |
⚠️
getTodayOverview——profileComplete:false时返null不编数(V1 老文档已错);budgetKcal/remainingKcal可空;basis.strengthMinutes反映真实力量分钟数。⚠️
myTodayHealth—— 静息心率走REST_HR专列(设备线),不走「当日最早一条」老口径。
调用详情:guide/uniapp/api/stats.md · guide/uniapp/api/page-direct.md
pages/my-plan/index.vue — 我的训练计划
| API 调用 | 关键字段 | 用途 |
|---|---|---|
GET /api/v1/training/plans | — | 列可选训练计划(无鉴权) |
POST /api/v1/training/enroll | planId | 报名计划 |
GET /api/v1/training/tasks | — | 我的训练任务 |
POST /api/v1/training/task/checkin | taskId/date | 任务打卡 |
GET /api/v1/training/reminders | — | 我的训练提醒 |
调用详情:guide/uniapp/api/training.md
§3 详情页
14 个 detail 页全部走
POST /api/v1/deviceaction 协议,按 URLtype路由:pages/detail/page-{X}-detail/index.vue(X ∈ sleep / heartRate / stress / oxygen / steps / calories / strength / weight / run-step / sport / health-summary / spots-list)
- 2 个 selector 页(
detail-select-date/detail-select-symmary-date)
| 详情页 | API 调用 | action | 入参关键 |
|---|---|---|---|
detail/page-sleep-detail | POST /api/v1/device | sleepDetail | date |
detail/page-heart-rate-detail | POST /api/v1/device | heartRateDetail | date |
detail/page-stress-detail | POST /api/v1/device | stressDetail | date |
detail/page-oxygen-detail | POST /api/v1/device | oxygenDetail | date |
detail/page-steps-detail | POST /api/v1/device | stepsDetail | date |
detail/page-calories-detail | POST /api/v1/device | caloriesDetail | date |
detail/page-strength-detail | POST /api/v1/device | strengthDetail | date |
detail/page-weight-detail | POST /api/v1/device | weightDetail | from/to(区间) |
detail/page-run-step-detail | POST /api/v1/sport | runSessionDetail | sessionId |
detail/page-sport-detail | POST /api/v1/sport | runSessionDetail | sessionId |
detail/page-health-summary | POST /api/v1/device | activitySummary | date |
detail/page-spots-list | POST /api/v1/device | recordDates | from/to |
detail/select-date | — | — | 纯 UI(日期选择器) |
detail/select-symmary-date | — | — | 纯 UI(汇总日期选择器) |
⚠️ 所有 device/sport detail 的
date默认昨天(CN 0-8 点取墙钟前一天,避开 UTC 差一天 bug,详 CLAUDE.md cn_today 修复)。
调用详情:guide/uniapp/api/device.md · guide/uniapp/api/sport.md
§4 表单
pages/form/add_weight-page/index.vue — 加体重表单
| API 调用 | action | 入参 | 用途 |
|---|---|---|---|
POST /api/v1/sport | addWeightRecord | weightKg/date | 写体重(InMemory + PG) |
调用详情:guide/uniapp/api/sport.md
§5 个人中心
pages/mine/index.vue — 我的(个人主页)
| API 调用 | 用途 |
|---|---|
POST /api/v1/user {action:"myProfile"} | 读全量画像(昵称/头像/签名/身高/体重/运动偏好) |
pages/mine/info/personal-form/index.vue — 个人资料表单
| API 调用 | 用途 |
|---|---|
POST /api/v1/user {action:"myProfile"} | 初始化表单(读当前) |
POST /api/v1/user {action:"updateProfile"} | 提交修改 |
pages/mine/info/health-records/index.vue — 健康档案
| API 调用 | 用途 |
|---|---|
POST /api/v1/health-archive {action:"archive"} | 拉档案 |
POST /api/v1/health-archive {action:"saveHealthIncident"} | 写健康事件 |
POST /api/v1/health-archive {action:"saveHeartRateZones"} | 写心率区间 |
调用详情:guide/uniapp/api/health-archive.md
pages/mine/account-security/index.vue — 账号安全
| API 调用 | 用途 |
|---|---|
POST /api/v1/user {action:"myProfile"} | 账号信息回显 |
pages/mine/account-security/device-sessions/index.vue — 设备会话管理
| API 调用 | 用途 |
|---|---|
POST /api/v1/auth/oauth {action:"mySessions"} | 列会话 |
POST /api/v1/auth/oauth {action:"revokeSession"} | 撤销指定会话 |
pages/mine/data/device-list/index.vue — 设备列表
| API 调用 | 用途 |
|---|---|
GET /api/v1/device/vendors | 列可用厂商 |
POST /api/v1/device/vendors/{id}/auth-url | 拿授权 URL |
POST /api/v1/device {action:"myBindings"} | 我的绑定 |
POST /api/v1/device {action:"unbind"} | 解绑 |
调用详情:guide/uniapp/api/device.md
pages/mine/data/heart-rate-list/index.vue — 心率历史列表
text
→ 跳转 detail/page-heart-rate-detailpages/mine/data/linked_app/index.vue — 联动应用
| API 调用 | 用途 |
|---|---|
GET /api/v1/device/vendors | 列联动厂商 |
pages/mine/sports/sports-data/index.vue — 运动数据列表
| API 调用 | 用途 |
|---|---|
POST /api/v1/sport {action:"myRunRecords"} | 列我的跑步记录 |
调用详情:guide/uniapp/api/sport.md
pages/mine/version/index.vue — 版本信息
text
无后端调用(包内常量)§6 跑步会话
pages/run/index.vue — 跑步主页(.nvue)
| API 调用 | 用途 |
|---|---|
POST /api/v1/sport {action:"startRun"} | 开跑(创建 session) |
POST /api/v1/sport {action:"reportRunCoordinate"} | 周期上报坐标 |
调用详情:guide/uniapp/api/sport.md
pages/run/sport-index/index.vue — 运动类型选择(.nvue)
text
无后端调用(包内常量)pages/run/setting/index.vue — 跑步设置
text
无后端调用(本地配置)pages/run/start-run-time/index.vue — 开跑计时
| API 调用 | 用途 |
|---|---|
POST /api/v1/sport {action:"finishRun"} | 完跑(结算 session + 写积分) |
§7 选城市 + 生吃榜(2026-09-29 新增 · 前端待建)
状态:后端已上线(worktree
claude/feat-city-rawfood,commit5dd25ae,等 MR #41 合 main)。前端页面待建 —— 本节是给前端同事的页面 + 调用范本。
pages/city-select/index.vue — 🆕 选城市页
入口触发:首页 getCurrentCity 返 40431003(未绑城市)→ uni.navigateTo
调用的 API
| 调用时机 | API | 入参 | 响应读法 |
|---|---|---|---|
| onShow | POST /api/v1/city {action:"getRecentCities"} | {limit: 5} | res.data.cities 渲染列表(当前绑定单条 + seed 10 热门去重垫底) |
| 用户输入 | POST /api/v1/city {action:"listCities"} | {keyword: "广州"} | res.data.cities + res.data.total 实时过滤 |
| 点选 | POST /api/v1/city {action:"selectCity"} | {adcode: "440100"} | res.data.{adcode,name,source,updatedAt} → uni.navigateBack() |
完整字段 + 错误码
- 完整字段表:
api-docs/v1/city - 错误码矩阵:
api-docs/v1/city/error-codes - 页面流图:
internal-wiki/operations/2026-09-29-city-rawfood-frontend-handoff
uniapp 写法范本
ts
// src/api/city.ts(前端同事按此接口新建)
import request from '@/utils/request'
export interface City {
adcode: string
name: string
parentCode: string | null
level: number // 1 省 / 2 市
pinyin: string | null
isHot: boolean
}
export function listCities(params: {
level?: 1 | 2 | 3
keyword?: string
hot?: boolean
page?: number
size?: number
}) {
return request({
url: '/api/v1/city',
method: 'POST',
data: { action: 'listCities', payload: params },
})
}
export function selectCity(payload: { adcode: string }) {
return request({
url: '/api/v1/city',
method: 'POST',
data: { action: 'selectCity', payload },
})
}
export function getCurrentCity() {
return request({
url: '/api/v1/city',
method: 'POST',
data: { action: 'getCurrentCity', payload: {} },
})
}
export function getRecentCities(limit = 5) {
return request({
url: '/api/v1/city',
method: 'POST',
data: { action: 'getRecentCities', payload: { limit } },
})
}vue
<!-- pages/city-select/index.vue 关键逻辑 -->
<script setup lang="ts">
import { listCities, selectCity, getRecentCities } from '@/api/city'
const cities = ref<City[]>([])
const keyword = ref('')
onShow(async () => {
const res = await getRecentCities(5)
cities.value = res.data.cities // 当前绑定 + 热门垫底
})
watch(keyword, async (kw) => {
if (!kw) return // 空 keyword 用 onShow 的初始数据
const res = await listCities({ keyword: kw, page: 1, size: 50 })
cities.value = res.data.cities
// res.data.total 显示「共 X 条」
})
async function onPick(city: City) {
await selectCity({ adcode: city.adcode })
uni.navigateBack() // 返回上一页,首页自动刷新 getCurrentCity
}
</script>⚠️ 必看红线
- 40431003 = 新用户常态,不打错误 toast —— 直接跳本页面
- 40431005 = v1 GPS 不可用,前端不做「自动定位」按钮(seed 无 geo →
SeedNearestCityLocator恒返 None) - 首页 city 字段 ≠ 产品城市 —— splash.city 是广告平台字段,唯一产品真源:
getCurrentCity
pages/rawfood-ranking/index.vue — 🆕 生吃榜页
入口触发:健康饮食 Tab / 启动按钮
调用的 API
| 调用时机 | API | 入参 | 响应读法 |
|---|---|---|---|
| Tab 默认 | POST /api/v1/ranking {action:"rawFoodRanking"} | {scope:"national", limit:50} | res.data.list[] 渲染(rank/foodId/name/rawBenefitScore/gi/badge) |
| 切换「我的城市」 | POST /api/v1/ranking {action:"rawFoodRanking"} | {scope:"city", cityAdcode:"<getCurrentCity 返回>", limit:50} | 同上 |
完整字段 + 错误码
uniapp 写法范本
ts
// src/api/ranking.ts(前端同事按此接口新建)
import request from '@/utils/request'
export interface RawFoodItem {
rank: number
foodId: string // rf-001 ~ rf-050
name: string
category: string // vegetable / fruit / nut / fish ...
rawBenefitScore: number // 0-100
gi: number // 升糖指数
suitableFor: string[] // diabetes / weight_loss / athlete ...
badge: string | null
imageUrl: string | null // v1 恒 null
}
export function rawFoodRanking(params: {
scope?: 'national' | 'city'
limit?: number // 默认 50,上限 100
cityAdcode?: string // scope=city 必传 6 位
}) {
return request({
url: '/api/v1/ranking',
method: 'POST',
data: { action: 'rawFoodRanking', payload: params },
})
}vue
<!-- pages/rawfood-ranking/index.vue 关键逻辑 -->
<script setup lang="ts">
import { ref } from 'vue'
import { rawFoodRanking } from '@/api/ranking'
import { getCurrentCity } from '@/api/city'
const scope = ref<'national' | 'city'>('national')
const list = ref<RawFoodItem[]>([])
async function loadList() {
const params: any = { scope: scope.value, limit: 50 }
if (scope.value === 'city') {
// 城市榜需要先取当前城市 adcode
const cur = await getCurrentCity()
params.cityAdcode = cur.data.city?.adcode
}
const res = await rawFoodRanking(params)
list.value = res.data.list
// res.data.generatedAt 是 5min TTL 缓存刷新时间
}
onMounted(loadList)
watch(scope, loadList)
</script>⚠️ 必看要点
- 榜单已按 rawBenefitScore 降序排好 —— 前端无需再排序
rawSafe=false食物已沉底 + 带警示 badge —— 前端无需再过滤(但 UI 仍可在 badge 上做警示色)imageUrlv1 全 null —— 图标走前端静态映射(/static/food-icons/rf-001.png等,按 foodId 前缀)- 缓存 5min TTL ——
generatedAt字段是缓存刷新时间,不是榜单生成时间
全局约定**(G1-G7)
完整约定见 前端联调 API 手册 · 通用约定
| # | 约定 | 一句话总结 |
|---|---|---|
| G1 | 统一包络 | res.data.xxx 取业务字段(不解包) |
| G2 | 请求头 | X-Env: Prd 主请求 + rotate 都要带 |
| G3 | 鉴权档位 | 大部分 Bearer <access>;rotate 用 <refresh> |
| G4 | 错误契约 | 业务码字符串 "0" 成功;用 String(res.code) === '0' 判定 |
| G5 | 401 rotate 单飞 | 收到 401 或 token_expiring=true 触发 rotate,避免循环 |
| G6 | snake_case 例外 | 响应几乎全 camelCase(memberExpireAt / weeklyUsed / isNewUser) |
| G7 | 响应无 schema | 类型断言靠字段路径,?. 链 + withDefaults 兜 undefined(不兜 null) |
相关文档
| 文档 | 看什么 |
|---|---|
| 前端联调 API 手册 | 30+19+5 端点字段表 / 错误码 / curl / 头号坑位 |
| uniapp/pages/* | 34 页每页职责 / 关键逻辑 / 变更记录 |
| uniapp/api/* | 每 API 调用契约(读法路径 + 前端动作) |
| city 模块 | 🆕 city 4 action 完整字段 |
| ranking 模块 | 🆕 ranking 2 action + D-10 公式 |
| 前端对接 handoff | 🆕 页面流图 + 城市/生吃榜关系 |
生成与维护:
docs/api-docs/v1/frontend/由契约实测生成(tests/reports/contract-matrix.json)docs/guide/uniapp/pages/*由scripts/gen_uniapp_docs.py从前端源码批量生成- 本文档手工维护 —— 新增页面/API 时手动同步到对应章节
- 一致性校验:
scripts/check_uniapp_api_docs.py(pre-commit 钩子,30 端点矩阵 + 死链 + 字段路径)