Skip to content

页面直调端点(不在 src/api/*.ts 里的 3 条) ​

后端契约:手册 · page-direct(3 端点字段表 / curl / 错误码)。 为什么单列:这 3 条没有 src/api/*.ts 封装(页面里内联 request / uni.uploadFile)—— 自动化提取器的盲区(生成器只扫 src/api/*.ts),也是「读法出错」的高危区。 本页只写正文 1 条(upload);另 2 条已在专页写过,本页只做索引与交叉引用(不重复,避免两处维护)。

一、端点归属索引 ​

端点谁在调正文在哪
POST api/v1/device myTodayHealth首页健康卡 src/pages/index/index.vue:124-141device —— 块状结构读法 + todayActivity.totalDistanceKm 坑
POST api/v1/stats getTodayOverview首页热量 / 连续天数卡 src/pages/index/index.vue:120-122stats —— 嵌套两层读法 + 防回归块
POST api/v1/upload?type=avatar个人资料页换头像 src/pages/mine/info/personal-form.vue:281-350本页正文(下)

⚠️ 前两条共用同一个 Promise.all(loadToday())—— 它们的失败是耦合的, 见 stats · 失败时的前端动作「Promise.all 一损俱损」。

upload(页面直调 · 头像上传) ​

一句话:个人资料页换头像 —— uni.chooseImage 选图 → multipart 直传拿 URL → 再调 updateProfile 落库(两步,别只做第一步)。

前端函数签名

项值
封装页面直调(非 src/api/*.ts)—— src/pages/mine/info/personal-form.vue:282-318 uploadAvatarFile();调用链 chooseAvatar(:320-330)→ handleAvatarUpload(:332-350)
触发时机用户点「头像」行 → uni.chooseImage({ count: 1, sizeType: ['compressed'] }) → 选中即传;uploading 期间入口自锁(重复点击直接 return)
鉴权Bearer access + X-Env —— 页面手动拼 header(⚠️ 不走 request.ts 拦截器)
由此失去的能力无自动 rotate —— token 过期不会自动续期,直接失败(其余端点的 401 链在拦截器里,这条没有)

必需 payload 键

键从哪来必填
type(query)字面量 'avatar',拼在 URL 上(${BASE_URL}api/v1/upload?type=avatar)✔
file(multipart)uni.chooseImage 的 res.tempFilePaths[0];uni.uploadFile 的 name 参数必须写 file✔
Authorization / X-Env(header)storage.token.get() + config.X_ENV —— 手动拼(不是拦截器注入)✔

响应读法

ts
// uni.uploadFile 的 res.data 是【字符串】(不是对象)—— 与 uni.request 的关键差异,必须先 parse
const body = typeof res.data === 'string' ? JSON.parse(res.data) : res.data;
if (res.statusCode === 200 && String(body?.code ?? '') === '0' && body?.data?.url) {
  const url = body.data.url;                     // 中文注解:业务字段在 body.data.url(COS/CDN 或本地 /uploads/...)
  const r = await updateProfile({ avatarUrl: url });   // 中文注解:上传 ≠ 落库 —— 落库要走 user 端点(这条经拦截器)
  form.avatarUrl = r?.data?.user?.avatarUrl || url;     // 中文注解:以后端回吐为准,取不到才用上传返回的 url
}

✅ 正确读法:body.data.url(body = 已 JSON.parse 的响应体) ❌ 错误读法:直接 res.data.url —— res.data 是字符串,取属性恒 undefined → 上传其实成功了、前端却判失败(resolve 分支进不去)。 与其余端点的读法同一原则:业务字段在 data 之下,只是这里的 data 要先 parse 出来。 字段表 / 约束(大小上限、MIME 白名单)/ 错误码见 手册 · page-direct#upload。

失败时的前端动作

情形前端动作
上传层失败(HTTP 非 200 / code ≠ "0" / 无 url)uploadAvatarFile 内 reject(new Error(body?.msg || '头像上传失败'));页面 catch 用 e instanceof Error 判定 → toast e.message
网络失败(uni.uploadFile 的 fail)reject new Error('网络异常,请检查网络连接') → 同上 toast
落库失败(updateProfile 被拒)该层的失败已由拦截器统一 toast,e 是普通对象而非 Error → 页面 catch 不再重复提示。⚠️ 此时图片已上传成功,只是没落库 —— 重试只需重调 updateProfile,不必重传文件
未取到 token前置 reject(new Error('请先登录'))(本条不发起请求)
401无自动 rotate —— 需手动刷新 token 后重试(或引导重新登录)。别指望"等一下再试"能自愈

⚠️ 坑位

  • 不是 action 协议:没有 {action, payload} 信封 —— type 在 query、file 在 multipart body、header 要手动拼。 别的端点靠 request.ts 托管的三件事(鉴权头 / X-Env / 401 rotate),这条全得自己来。
  • 漏 X-Env 会静默落错库:后端对缺头 / 非法值有 Dev 兜底 —— 上传会「成功」,但记录写进了测试库, 线上访问图片时才暴露(与 手册 G2 同一原则:X-Env 全程一致)。
  • res.data 是字符串:uni.uploadFile 不像 uni.request 那样给结构化响应 —— 忘了 parse 就是「后端成功、前端报错」。
  • 上传成功 ≠ 头像生效:必须走 updateProfile({ avatarUrl }) 落库(读法见 user);页面还顺手写了本地缓存 (persistLocalCache() → uni.setStorageSync,下次进入表单预填用),缓存与后端不一致时以 res.data.user.avatarUrl 为准。
  • 上传前先自查 token 有效性:这条没有 rotate 兜底,过期即失败,且失败提示是通用文案(不指向"重新登录")。