---
url: /docs/guide/uniapp.md
---
# uniapp 前端开发指引

> 沐禾健康 C 端（uniapp Vue3 + TS + Vite CLI 工程）开发指引 — 源仓库 [qingmu2026/qm-frontend](https://cnb.cool/qingmu2026/qm-frontend) `api` 分支。
> 本文由人工精写 + 脚本生成（`scripts/gen_uniapp_docs.py`）混合维护，最后更新 2026-09-20。

## 文档分工边界（必读）

四套文档并存，**各写各的、不互相复述** —— 接触面只有"锚点"，接触面越窄越不会互相矛盾：

> `api/*.md` 只写「前端函数 → action → 必需 payload 键 → 响应读法 → 失败时的前端动作」；
> **字段表、错误码、curl 一律见 [前端联调 API 手册](/api-docs/v1/frontend/)**。

| 内容 | 唯一归属 | 另一侧怎么写 |
|---|---|---|
| 请求/响应**字段表**（类型、必填、可空）、**错误码具体码值**、全部 **curl** | [前端联调 API 手册](/api-docs/v1/frontend/)（契约权威） | 本文只写**读法路径**（`res.data.calorie.budgetKcal`）与**前端动作**（「提示并回退重拉」）+ 指锚点；**不写类型、不写 curl** |
| **通用约定 G1-G7**（统一包络 / X-Env / 鉴权三档 / 401 分档 / rotate 单飞 / snake\_case 例外 / 响应无 schema） | [手册 · 通用约定](/api-docs/v1/frontend/#通用约定-7-条-·-所有端点共用) | 本文只写一句「与 [手册 G2](/api-docs/v1/frontend/#g2-请求头) 一致」 |
| **10 条头号坑位** | 手册 [头号坑位速查](/api-docs/v1/frontend/#🔴-头号坑位速查-联调前必读-踩了会浪费半天) + [联调台账](/internal-wiki/operations/frontend-integration-issues) | 本文只在**自己页面确实踩到**时指锚点 |
| 真机/框架坑（编译器锁版本、`t.__f__` 崩溃、`www` 缓存、`adb logcat`、验证码渲染三定律） | **本文（本页铁律）** | 手册一字未提 —— 这是本文的**独占资产** |
| 页面 ↔ API 映射、调用链时序、`storage` 键名、乐观更新写法 | 本文 `pages/*.md` + `api/*.md` | 手册只在 `page-direct.md` 涉及 3 端点 |
| 生成装置（`gen_uniapp_docs.py` / `_meta.json` / `CONTRIBUTION.md`） | **本文** | 手册无此维度 |
| OAuth **从零施工**手册（拷 SDK、自定义基座、微信登录 manifest） | [OAuth 登录链落地手册](/api-docs/v1/oauth/uniapp-dev-guide) | 该页页首已声明「契约见手册」，本文不复述其施工步骤 |

> ⚠️ **一致性由脚本兜底，不靠自觉**：`scripts/check_uniapp_api_docs.py`（pre-commit 钩子）机械校验
> 「矩阵 30 端点覆盖 / 已证伪字面量 / 字段路径层级 / 跨目录死链」四项 —— 文档字段与本目录任何一处对不上，提交即拦。

## 这是什么

面向「给 C 端加页面 / 改功能 / 调后端接口」的开发者。四件事：

1. **看结构** — [structure.md](./structure.md)：uniapp CLI 编译要求的规范目录树，现状对照与改进标注
2. **看页面/组件** — `pages/`（34 页）与 `components/`（20 组件）说明：职责 / 依赖 / 调用的 API / git 变更记录
3. **调接口** — `api/`（9 页）：调用契约（oauth 登录链 / user 向导链 / splash 开机屏 / device 设备线 / training 训练计划 / stats 今日概览 / agreement 协议 / page-direct 页面直调 + [目录总览](./api/index.md)）——
   **消费视角**（读法路径 + 前端动作），**字段表 / 错误码 / curl 零复述、一律指向手册**（见上「文档分工边界」）
4. **看差异** — [comparison.md](./comparison.md)：三层对比（仓库两代形态 / V1 小程序↔V2 功能映射 / CNB↔本地同步）

## 工程速览

| 维度 | 现状 |
|---|---|
| 框架 | uniapp Vue3 + TypeScript + Vite（CLI 形态，无 HBuilderX） |
| 编译器 | `@dcloudio/* 3.0.0-5020420260813001`（**精确锁版本**，caret 会拉 alpha 崩） |
| 注册页面 | 34（含 2 个 .nvue 原样渲染页：run/index、run/sport-index） |
| 组件 | 20 个 `qm-*` 项目组件 + uview（u-popup/u-picker/u-icon） |
| API 层 | `src/api/*.ts` **6 文件**（agreement / device / login / splash / training / user；导出函数 27 个）+ **仅剩首页 `index.vue` 内联 request 2 处**（`getTodayOverview` / `myTodayHealth` — 按手册口径归「页面直调」，非收敛缺口）；`api/stats.ts`、`api/sport.ts` 尚未建 |
| 请求封装 | `src/utils/request.ts`：业务码 `"0"` 判定 + **`resolve(body)` 不解包**（调用方必须 `?.data`）+ 401 rotate 单飞续期 |
| 后端 | QM-ServerV2（action 协议 POST + REST 混合），`X-Env: Dev\|Prd` 双库路由 |

## 目录

* [structure.md](./structure.md) — 规范结构树
* [comparison.md](./comparison.md) — 三层差异对比
* [page-api-reference.md](./page-api-reference.md) — ⭐ **前端页面 × 后端 API 反向索引**（36 页 × 54 端点）
* [api/](./api/) — [总览](./api/index.md) / oauth / user / splash ⏳ / device / training / stats / agreement / page-direct
* `pages/*.md` — 页面说明（34 篇，P2 批量生成）
* `components/*.md` — 组件说明（20 篇，P2）
* `CONTRIBUTION.md` — 多用户提交记录（P2，脚本生成）

## 关键铁律（新人必读，血泪沉淀）

1. **request 返回整个 body 不解包**：`const res = await xxxApi(); res.data 才是业务数据` — 漏 `?.data` 会在远端爆 TypeError（真机实锤：向导 enroll 三连 bug）
2. **编译器锁版本**：`package.json` 中 `@dcloudio/*` 禁止改 caret
3. **APP 端模板事件**：绑具名方法（内联箭头 → `t.__f__ is not a function` 崩）
4. **JS 静默崩调试**：`adb logcat -d | grep -i console` 直接看 APP 端报错
5. **每次重装 APK 必须清 www 缓存**：`rm -rf /sdcard/Android/data/<pkg>/apps/__UNI__F1DDAB4/www`

## 平台差异坑速查表（App / H5 / 小程序）

> 本表原在 OAuth 登录链落地手册 §7，2026-09-20 **并入本页铁律**（避免同一坑三处各写一份）；
> 该页 §7 现只留一个指回本页的指针。**本页是唯一维护处**。

| 坑 | H5 | App | 小程序 | 应对 |
|---|---|---|---|---|
| `rich-text` 渲染 SVG | ✅ | ❌ | 部分 | 服务端直出 PNG（captcha 响应的 `png` 字段，`data:image/png;base64`）→ `<image :src>` 全端直显 |
| localStorage / storage | 异步语义 | 同步 | 同步 | 统一用 `uni.getStorageSync`（`src/utils/storage.ts` 已封装）|
| 跨域 | dev proxy | 无跨域概念 | 无 | App/H5 生产包必须 HTTPS；manifest 不留 http 明文白名单 |
| http 明文 | 浏览器允许 | manifest 白名单控制 | 域名校验 | 生产全 HTTPS |
| cookie | 自动携带 | 不自动 | 不自动 | 本链路不依赖 cookie（SDK 用 header token）✅ |
| 微信登录 | ❌ | ✅ **需自定义基座** | — | HBuilderX 标准基座测不了（原生 SDK）；见落地手册 §5 |

## 文档如何再生成

```bash
# ⚠️ --frontend 必须指向 canonical 工作区（与 contract-matrix.json / 手册同源）：
#    旧写法 `<qm-frontend工作区>` 曾漂到 qm-frontend/.claude/worktrees/api-fix → 文档状态系统性滞后一版
python3 scripts/gen_uniapp_docs.py \
  --frontend ~/Documents/Claude/Projects/qm-frontend-api \
  --out docs/guide/uniapp/_meta.json --gen-md
```

脚本输出元数据 JSON（页面/组件/工具/git 记录/API 调用），文档批量生成基于它 — 源码变更后重跑即可刷新 git 记录与调用清单。
生成后请跑 `scripts/check_uniapp_api_docs.py` 校验链接与字段（pre-commit 也会拦）。
