220 lines
8.8 KiB
Markdown
220 lines
8.8 KiB
Markdown
# 小程序前端开发与后端 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`
|
||
- 是否沿用了已有页面的卡片/按钮视觉语言
|
||
|