# 小程序前端开发与后端 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 从 `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 ` - 支持 `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` 避免被遮挡 - 页面根节点常用:``,保证底部安全区 ## 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` - 是否沿用了已有页面的卡片/按钮视觉语言