# 小程序前端开发与后端 API 对接经验汇总

适用范围：本仓库小程序前端（`qianduan/qianduan-code/miniprogram`）。目标是让新同学能在不踩坑的情况下完成页面开发、样式对齐和后端 API 对接。

## 1. 项目结构与入口

- 小程序目录：`qianduan/qianduan-code/miniprogram`
- 全局入口：`app.js / app.json / app.wxss`
- 页面目录：`pages/*`
- 通用能力：
  - 请求封装：`utils/api.js`
  - 登录态：`utils/auth.js`
  - 统一错误处理：`utils/errorHandler.js`
  - 图片 URL 处理：`utils/imageUrl.js`
- 环境与常量：`config/index.js`

## 2. 环境与 baseURL（非常关键）

后端 API 的 baseURL 由 `config/index.js` 管理：

- `ENV.{development,staging,production}.API_BASE_URL`
- `CURRENT_ENV`
- `REQUEST_TIMEOUT`（默认 30s）
- `PAGE_SIZE`（默认 20）
- `STORAGE_KEYS`（token/user 等 storage key 的唯一来源）

建议实践：

- 开发阶段用 `development`，发版/线上联调切 `production`。
- 不要在页面里硬编码域名或 `/api` 前缀，一律走 `config.API_BASE_URL`。

另外，`app.js` 会把 `config.API_BASE_URL` 去掉 `/api` 后写到 `globalData.baseUrl` 并落地到 `storage.baseUrl`，主要给 `utils_new` 那套请求封装使用。正常业务开发推荐统一使用 `utils/api.js` 这一套，避免 token key 和 baseUrl 来源混乱。

## 3. 请求封装与 header 格式（统一约定）

### 3.1 推荐做法：只用 `utils/api.js`

`utils/api.js` 内部封装了 `request()`，完成以下事情：

- URL 拼接：`config.API_BASE_URL + url`
- header 默认包含：
  - `Content-Type: application/json`
  - `Authorization: Bearer <token>`（token 从 `wx.getStorageSync(config.STORAGE_KEYS.TOKEN)` 取）
- 401（未登录/登录过期）处理：
  - 非 silent 模式会清理本地登录信息（token/user/userId/expiry）
  - 同步 `app.globalData.isLoggedIn = false`
  - 尝试调用当前页 `onAuthRequired()`（如果页面实现了该方法）
- `silent` 模式：用于不希望打断用户操作的接口；401 时不清本地登录态，只 reject

页面层调用建议：

```js
import api from '../../utils/api'
import { handleApiError } from '../../utils/errorHandler'

Page({
  async onLoad() {
    try {
      wx.showLoading({ title: '加载中' })
      const res = await api.user.getProfile()
      this.setData({ profile: res.data })
    } catch (err) {
      handleApiError(err)
    } finally {
      wx.hideLoading()
    }
  }
})
```

### 3.2 不推荐混用：`utils_new/request.js`

仓库里还有一套 `utils_new/request.js`（更偏调试/可切 baseUrl），但它使用的 token key 是写死的 `auth_token`，而主体系使用 `config.STORAGE_KEYS.TOKEN`（同为 `auth_token` 但请以配置为准）。混用会导致以下问题：

- 你以为登录了，实际请求没带对 token
- baseUrl 读取路径不同，导致部分页面请求走了另一个域名/端口

结论：业务开发优先只用 `utils/api.js`；除非明确是在做本地联调/临时调试，并保证 token/baseUrl 与主体系一致。

## 4. 登录态与鉴权（页面开发常见坑）

核心逻辑在 `utils/auth.js` 和 `app.js`：

- `app.js` 启动会调用 `checkLoginStatus()`：
  - 先本地检查 token
  - 再调用 `auth.verifyLogin()` 请求服务端 `/auth/me` 做校验
  - 网络异常时可能允许用本地缓存 userInfo 继续使用（提升弱网体验）
- 页面若必须登录才能访问，建议用 `auth.ensureLogin()` 做统一校验，校验失败会跳登录页
- token/用户信息写入请走 `auth.saveUserInfo(user, token, expiresAt)`

页面侧最佳实践：

- 需要登录的页面，在 `onLoad/onShow` 里先 `await auth.ensureLogin()` 再拉数据
- 有接口需要“静默拉取”（比如后台刷新余额），用 `api.request(url, { silent: true })` 或 API 方法暴露的 silent 选项（按现有实现为准）
- 需要 401 时做自定义交互的页面，提供 `onAuthRequired()` 方法（例如弹窗提示/引导登录），否则默认行为是清理登录态并由页面自行处理后续

## 5. 统一错误处理（减少页面重复代码）

`utils/errorHandler.js` 提供了：

- `handleApiError(err)`：统一 toast/提示文案
- `retryOnError(fn, { maxRetries, retryDelay })`：可重试逻辑（认证错误不会重试）

建议实践：

- 页面 catch 里优先调用 `handleApiError(err)`，不要每个页面自己写一套 toast 文案
- 对用户关键路径（下单/提现提交）：
  - 网络错误提示要明确
  - 禁止无限重试

## 6. 分页与列表加载（约定优先）

当前工程的分页没有抽象成统一 `paginate()`，更多是“API 方法里给默认分页参数 + 页面侧传参”：

- 常见参数：`page` + `pageSize`（或部分接口用 `limit`）
- 默认值可参考 `config.PAGE_SIZE`

建议实践：

- 页面 data 里维护：`page, pageSize, list, loading, hasMore`
- `onReachBottom` 时判 `hasMore && !loading` 再请求下一页
- 后端返回是否还有更多，以返回字段为准（如果没有统一字段，就以 `list.length < pageSize` 推断）

## 7. 上传与图片 URL（最容易漏 token）

### 7.1 上传

使用 `utils/api.js` 的 `uploadFile()`：

- 上传地址：`${config.API_BASE_URL}/upload`
- 携带 `Authorization: Bearer <token>`
- 支持 `formData.folder` 指定目录
- 兼容多种返回格式（`{code:0}` 或 `{success:true}`）

### 7.2 图片 URL 拼接

后端返回相对路径时，用 `utils/imageUrl.js` 的 `getFullImageUrl()` 拼成完整地址（基于 `API_BASE_URL` 去掉 `/api`）。

## 8. 头部与页面结构（统一样式框架）

工程里常见的头部结构是“固定导航 + 状态栏占位 + 标题 + 返回按钮”：

- 导航容器：`nav-container`
- 状态栏占位：`status-bar`
- 导航栏：`nav-bar`
- 返回按钮：`nav-back`
- 标题：`nav-title`

常见页面结构示例可参考：

- 提现页：`pages/withdraw/withdraw.wxml`、`pages/withdraw/withdraw.wxss`
- 充值页：`pages/recharge/recharge.wxml`
- 提现记录：`pages/withdraw-records/withdraw-records.wxml`

建议实践：

- 导航容器固定（fixed），内容区通过 `padding-top: {{totalNavHeight}}px` 或 `padding-top: {{totalNavHeight + n}}px` 避免被遮挡
- 页面根节点常用：`<view class="page safe-bottom">`，保证底部安全区

## 9. 整体样式风格（如何保持一致）

### 9.1 全局设计令牌（Design Tokens）

`app.wxss` 定义了全局 CSS 变量（建议优先使用）：

- `--primary`、`--primary-light`
- `--foreground`、`--muted`、`--border`
- `--radius`

并补齐了全局基础类：

- `.btn-reset`：用于 button，去掉默认边框与默认点击态差异
- `.safe-bottom`：适配底部安全区

### 9.2 页面级常用视觉语言

在本项目里，“一致感”主要来自以下元素：

- 背景：浅紫/粉色系渐变或纯色（如 `#E8C3D4`、`linear-gradient(180deg, #F8F5FF 0%, #FFFFFF 100%)`）
- 卡片：白底 + 大圆角（20rpx~48rpx）+ 轻阴影 + 适度边框
- 主按钮：紫色渐变（`#B06AB3 → #9B4D9E`）+ 胶囊圆角（999rpx）+ 统一阴影
- 文案层级：标题更粗更深（700~900），说明文字更浅（`#6B7280/#9CA3AF`）

建议做法：

- 新页面先找一个“风格相近”的现有页面抄结构与基础样式，再替换业务内容
- 少做随意的颜色与圆角，优先复用现有渐变、阴影、字号层级

## 10. 交互与组件（避免嵌套交互坑）

约定：交互元素不要互相嵌套（例如可点击容器里再放 button 或另一个可点击 view）。如果需要整块可点：

- 要么外层用 `bindtap`，内部不用 button（用普通 view/text 模拟按钮）
- 要么使用 button 做唯一点击源，外层不再绑事件

这类嵌套会在检查工具/规范中触发警告，也容易造成点击穿透/事件冒泡问题。

## 11. 新增/修改 API 的推荐流程

1. 在 `utils/api.js` 增加/修改对应模块方法（保持命名与路径一致）
2. 请求一律通过内部 `request()`（确保 header、401、timeout、错误格式一致）
3. 页面侧只调用 `api.xxx.yyy()`，不直接拼 URL、不直接 `wx.request`
4. 异常统一走 `handleApiError`，少在页面写自定义错误文案

## 12. 排查清单（定位问题最快）

- 请求没到后端：
  - `config.CURRENT_ENV` 是否正确
  - `config.API_BASE_URL` 是否正确（是否带了 `/api`）
  - 是否混用了 `utils_new` 导致 baseUrl/token 读取来源不一致
- 401/未登录：
  - storage 里是否存在 `config.STORAGE_KEYS.TOKEN`
  - 页面是否需要 `auth.ensureLogin()`
  - 是否误用了 silent 导致 401 没触发清理/引导
- 样式不一致：
  - 是否使用了 `.btn-reset`（button 默认样式会把你“设计稿一致性”破坏掉）
  - 是否使用了 `.safe-bottom`
  - 是否沿用了已有页面的卡片/按钮视觉语言

