diff --git a/track-uniapp/src/utils/config.js b/track-uniapp/src/utils/config.js new file mode 100644 index 0000000..1e38b9b --- /dev/null +++ b/track-uniapp/src/utils/config.js @@ -0,0 +1,24 @@ +/** + * 全局环境配置 — 后端域名/接口地址的唯一来源。 + * + * 此前 request.js 与 App.vue 各自硬编码了一份生产域名,改域名要改两处, + * 漏一处就会出现「接口通了但 OTA 检查更新接不上」这类极难排查的偏差, + * 故统一收口到本文件。新增环境地址一律加在这里,不要在业务代码里写死。 + */ + +// 生产环境 API 地址 +// +// ⚠️ HTTPS 说明(2026-09 实测):生产域名 track_back.iris-rs.cn 的 443 端口 +// 是通的、也确实配有证书,但证书【已过期】—— 现在切 https 会让 App 的 +// 全部请求直接失败。待运维完成证书续期后,只需把下面 PROD_URL 与 +// LOCAL_URL 的协议头改成 https:// 即可,业务代码无需任何改动。 +// 续期前请勿改动,保持 http:// 以保证可用性。 +export const PROD_URL = "http://track_back.iris-rs.cn/api/v1"; + +// 本地开发地址 — 真机调试时指向开发机局域网 IP +export const LOCAL_URL = "http://192.168.9.80:8011/api/v1"; + +// 生产基础地址:供只认「生产环境」的场景直接引用 +export const BASE_URL = PROD_URL; + +export default { PROD_URL, LOCAL_URL, BASE_URL }; diff --git a/track-uniapp/src/utils/request.js b/track-uniapp/src/utils/request.js index 086bd27..57edc6c 100644 --- a/track-uniapp/src/utils/request.js +++ b/track-uniapp/src/utils/request.js @@ -7,8 +7,8 @@ * 3. 生产默认 PROD_URL */ -const PROD_URL = "http://track_back.iris-rs.cn/api/v1"; -const LOCAL_URL = "http://192.168.9.80:8011/api/v1"; +// 域名/环境地址统一来自 utils/config.js,勿在此处硬编码 +import { PROD_URL, LOCAL_URL } from "./config"; // ============================================================ // 环境 URL(自动感知:缓存配置 > NODE_ENV > 生产默认) @@ -170,17 +170,35 @@ export default function request(options) { return; } - if (code === 403) uni.showToast({ title: extractErrorDetail(res.data, "无权操作"), icon: "none", duration: 3000 }); - else if (code === 400) uni.showToast({ title: extractErrorDetail(res.data, "请求参数有误"), icon: "none", duration: 2500 }); - else if (code === 409) uni.showToast({ title: extractErrorDetail(res.data, "操作冲突"), icon: "none", duration: 3000 }); - else if (code === 422) { - // 表单/参数校验失败(Pydantic)—— detail 是校验项数组,必须解析出 msg 再提示 - uni.showToast({ title: extractErrorDetail(res.data, "提交内容不符合要求"), icon: "none", duration: 3000 }); + // silent:调用方自行兜底提示时(如 feedback 的 404 回退 mock)跳过通用 toast, + // 否则用户会先看到一句「请求失败 (404)」,再看到「感谢反馈」,自相矛盾。 + if (!options.silent) { + if (code === 403) uni.showToast({ title: extractErrorDetail(res.data, "无权操作"), icon: "none", duration: 3000 }); + else if (code === 400) uni.showToast({ title: extractErrorDetail(res.data, "请求参数有误"), icon: "none", duration: 2500 }); + else if (code === 409) uni.showToast({ title: extractErrorDetail(res.data, "操作冲突"), icon: "none", duration: 3000 }); + else if (code === 422) { + // 表单/参数校验失败(Pydantic)—— detail 是校验项数组,必须解析出 msg 再提示 + uni.showToast({ title: extractErrorDetail(res.data, "提交内容不符合要求"), icon: "none", duration: 3000 }); + } + else uni.showToast({ title: extractErrorDetail(res.data, `请求失败 (${code})`), icon: "none", duration: 3000 }); } - else uni.showToast({ title: extractErrorDetail(res.data, `请求失败 (${code})`), icon: "none", duration: 3000 }); reject(res); }, - fail() { uni.showToast({ title: "网络连接失败", icon: "none" }); reject(new Error("network")); }, + fail(err) { + // ⚠️ fail = 压根没拿到后端响应(超时/断网),与 4xx/5xx 有本质区别: + // 后端可能已经执行成功,只是响应没回来。故保留原始 errMsg 并打上标记, + // 让上层能识别出「网络级失败」从而打断用户的连点重试。 + const errMsg = (err && (err.errMsg || err.message)) || ""; + const isTimeout = /timeout/i.test(errMsg); + if (!options.silent) { + uni.showToast({ title: isTimeout ? "网络超时,请稍后重试" : "网络连接失败", icon: "none" }); + } + const e = new Error(errMsg || "network"); + e.errMsg = errMsg; + e.isNetworkError = true; + e.isTimeout = isTimeout; + reject(e); + }, }); }); }