// @/utils/request.js let isRedirectingToLogin = false; // 全局 SSO 处理标记(挂载到 uni 上,便于跨模块共享) uni.__isSSOHandling = false; const showToast = (message) => { if (uni.$u?.toast) { uni.$u.toast(message); } else { uni.showToast({ title: message, icon: "none" }); } }; const config = { baseURL: "/api", timeout: 60000, header: { "Content-Type": "application/json", "X-Business-System": "medical-system", }, loginPage: "/pages/login/Login", tokenExpiredCode: 401, noPermissionCode: 403, whiteList: ["/login", "/getToken", "/GiLink/getCode"], }; const isInWhiteList = (url) => { const relativeUrl = url.replace(config.baseURL, "").split("?")[0]; return config.whiteList.some((path) => { if (path.endsWith("/")) return relativeUrl.startsWith(path); return relativeUrl === path; }); }; const navigateToLogin = () => { // SSO 处理中,不重复跳转 if (isRedirectingToLogin || uni.__isSSOHandling) { console.log("正在跳转登录页或SSO处理中,跳过"); return; } isRedirectingToLogin = true; const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; const isLoginPage = currentPage && currentPage.route && currentPage.route.includes("login/Login"); if (isLoginPage) { console.log("当前已在登录页,不跳转"); isRedirectingToLogin = false; return; } console.log("跳转到登录页"); setTimeout(() => { uni.redirectTo({ url: config.loginPage + "?redirect=" + encodeURIComponent(getCurrentPagePath()), success: () => { console.log("跳转登录页成功"); isRedirectingToLogin = false; }, fail: () => { console.log("跳转登录页失败"); isRedirectingToLogin = false; }, }); }, 100); }; const getCurrentPagePath = () => { const pages = getCurrentPages(); return pages[pages.length - 1]?.route || ""; }; const checkTokenExpired = (statusCode) => { if (statusCode === config.tokenExpiredCode) { showToast("登录已过期,请重新登录"); navigateToLogin(); return true; } return false; }; const requestInterceptor = (options) => { const token = uni.getStorageSync("token"); if (isInWhiteList(options.url)) { console.log("白名单接口,跳过token校验:", options.url); return options; } if (!token) { const pages = getCurrentPages(); const currentPage = pages[pages.length - 1]; const isLoginPage = currentPage && currentPage.route && (currentPage.route.includes("login/Login") || currentPage.route.includes("login/DingTalkLogin")); if (isLoginPage) { console.log("当前是登录页,允许无token请求"); return options; } console.log("未登录且不在登录页,跳转到登录页"); navigateToLogin(); throw new Error("未登录"); } options.header = { ...options.header, Authorization: `Bearer ${token}`, }; return options; }; const responseInterceptor = (response) => { const { statusCode, data } = response; if (checkTokenExpired(statusCode)) return Promise.reject(data); if (statusCode === config.noPermissionCode) { showToast("无权限访问"); return Promise.reject(data); } if (statusCode !== 200) { showToast(`请求失败: ${statusCode}`); return Promise.reject(data); } if (data?.code !== 200) { showToast(data?.msg || "操作失败"); return Promise.reject(data); } return data; }; const request = async (options) => { try { options = { ...config, ...options, url: config.baseURL + options.url, header: { ...config.header, ...options.header }, }; options = await requestInterceptor(options); return new Promise((resolve, reject) => { uni.request({ ...options, success: (res) => resolve(responseInterceptor(res)), fail: (err) => { showToast("网络错误,请重试"); reject(err); }, }); }); } catch (err) { return Promise.reject(err); } }; const http = { get(url, data = {}, options = {}) { return request({ url, data, method: "GET", ...options }); }, post(url, data = {}, options = {}) { return request({ url, data, method: "POST", ...options }); }, put(url, data = {}, options = {}) { return request({ url, data, method: "PUT", ...options }); }, delete(url, data = {}, options = {}) { return request({ url, data, method: "DELETE", ...options }); }, upload(url, filePath, name = "file", formData = {}) { return request({ url, method: "POST", filePath, name, formData, header: { "Content-Type": "multipart/form-data" }, }); }, }; uni.$uapi = http; export default http;