// 基础配置 import { showLoading, hideLoading } from '@/hooks/useLoading' import config1 from "@/config"; import ls from "@/utils/store2"; const baseUrl = config1.Host85; const timeout = 60000; // 不加loading const urlLoading = ['/list', '/page', '/field/params', '/dropdown', '/single', '/detail'] import { CLIENT, lang, userToken, shopToken } from "@/composables/config"; const LOGIN_PAGE_PATH = "/pages/login/index"; import useGlobalStore from "@/stores/use-global-store"; import useUserStore from "@/stores/use-user-store"; export const getCurrentPageUrl = () => { const pages = getCurrentPages(); // UniApp获取当前页面栈 const currentPage = pages[pages.length - 1]; return currentPage.route; // 返回当前页面路径(如:pages/login/index) }; // 请求拦截器 const requestInterceptor = (config) => { if (!config.header) { config.header = {}; } switch (config.type) { case 'HostShop': if (shopToken.value) { config.header["Access-Token"] = `${shopToken.value}`; } break; default: if (userToken.value) { config.header["Access-Token"] = `${userToken.value}`; } break; } if (lang.value) { config.header.Language = `${lang.value}`; } if (CLIENT.value) { config.header.CLIENT = `${CLIENT.value}`; } const userStore = useUserStore(); const cId = userStore.userInfo?.cId; const method = String(config.method || "GET").toUpperCase(); if (method === "GET") { config.data = { ...(config.data || {}) }; } else { config.data = { ...(config.data || {}) }; } if (!config.header["Content-Type"]) { config.header["Content-Type"] = "application/json"; } return config; }; // 记录是否正在跳转登录页 let isRedirectingToLogin = false; // 响应拦截器 const responseInterceptor = (response, options = {}) => { const { data, statusCode } = response; // 处理业务错误 if (statusCode === 200) { if (options.responseType === "arraybuffer" || data instanceof ArrayBuffer) { return data; } // 1. 捕获 401 未授权错误 if (data.code === 401 || data.code === 600) { // 关键:判断当前页面是否为登录页,避免循环跳转 const currentPage = getCurrentPageUrl(); if (currentPage === LOGIN_PAGE_PATH) { return Promise.reject({ ...data, msg: data.message || "登录失败,请重试", }); } userToken.value = ""; // 3. 判断是否需要跳转登录(支持单个请求忽略跳转) const ignore401 = options.ignore401 || false; // 单个请求的配置 if (ignore401) { return Promise.reject({ ...data, code: 401, }); } // 4. 提示并跳转登录页(防抖/防重复跳转处理) if (!isRedirectingToLogin) { isRedirectingToLogin = true; uni.showToast({ title: "登录已过期,请重新登录", icon: "none", }); setTimeout(() => { uni.reLaunch({ url: LOGIN_PAGE_PATH, success: () => { ls.set('mode', 'customer'); isRedirectingToLogin = false; }, fail: () => { isRedirectingToLogin = false; } }); }, 1500); } return Promise.reject({ ...data, code: 401, message: "登录已过期,请重新登录", }); } if (data.code === 200) { return data; } else if (data.code === 400) { return Promise.reject(data); } else { uni.showToast({ title: data.msg || "请求失败", icon: "none", }); return Promise.reject(data); } } else { uni.showToast({ title: `网络错误: ${statusCode}`, icon: "none", }); return Promise.reject(response); } }; // 错误处理 const errorHandler = (error) => { uni.hideLoading(); uni.showToast({ title: "网络异常,请稍后重试", icon: "none", }); return Promise.reject(error); }; // 核心请求函数 export const request = (options) => { // const host = config1[options.type || 'Host85'] || ''; const host = config1[options.type || 'Host80'] || ''; // 合并配置 const config = { ...options, url: `${host}${options.url}`, method: options.method || "GET", timeout, }; // 应用请求拦截器 const processedConfig = requestInterceptor(config); return new Promise((resolve, reject) => { const needLoading = urlLoading.some(item => config.url.includes(item)); if (!needLoading) { // showLoading(); } uni.request({ ...processedConfig, success: (response) => { try { const result = responseInterceptor(response, options); resolve(result); } catch (err) { reject(err); } finally { if (!needLoading) { // hideLoading(); } } }, fail: (error) => { const handledError = errorHandler(error); reject(handledError); if (!needLoading) { // hideLoading(); } }, }); }); }; // ---------------------- 图片上传封装(新增)---------------------- /** * 图片上传函数 * @param {Object} options - 上传配置 * @param {string} options.url - 上传接口路径(如 /Upload/Image) * @param {string|string[]} options.filePath - 图片临时路径(单文件:字符串;多文件:数组) * @param {string} [options.name=file] - 后端接收文件的字段名(默认file,需与后端一致) * @param {Object} [options.formData={}] - 额外表单参数(如业务ID、类型) * @param {Function} [options.onProgressUpdate] - 进度监听函数(可选) * @returns {Promise} - 上传结果 */ export const upload = (options) => { // 1. 处理基础配置 const uploadConfig = { ...options, url: `${baseUrl}${options.url}`, // 完整上传接口地址 timeout, name: options.name || "file", // 后端接收文件的字段名(默认file) filePath: options.filePath, // 图片临时路径 formData: options.formData || {}, // 额外表单参数 onProgressUpdate: options.onProgressUpdate, // 进度监听(可选) }; // 2. 应用请求拦截器(添加token等header) const processedConfig = requestInterceptor(uploadConfig); // 3. 区分单文件/多文件上传 return new Promise((resolve, reject) => { // 多文件上传(filePath为数组) if (Array.isArray(processedConfig.filePath)) { // 批量调用uni.uploadFile,并行上传 const uploadPromises = processedConfig.filePath.map((filePath) => { return new Promise((innerResolve, innerReject) => { uni.uploadFile({ ...processedConfig, filePath, // 单个文件路径 success: (res) => { // 注意:uni.uploadFile的res.data是字符串,需转为JSON res.data = JSON.parse(res.data || "{}"); try { const result = responseInterceptor(res); innerResolve(result); } catch (err) { innerReject(err); } }, fail: (err) => innerReject(errorHandler(err)), }); }); }); // 所有文件上传完成后 resolve Promise.all(uploadPromises).then(resolve).catch(reject); } // 单文件上传(filePath为字符串) else { uni.uploadFile({ ...processedConfig, success: (res) => { res.data = JSON.parse(res.data || "{}"); // 转为JSON try { const result = responseInterceptor(res); resolve(result); } catch (err) { reject(err); } }, fail: (err) => reject(errorHandler(err)), }); } }); }; /** * 兼容性上传函数(按需直接调用) * @param {string} url - 接口相对路径,如 `/wasabi/api/upload/file` * @param {string|Object|File} file - 文件路径(临时路径字符串)或包含 path/url/tempFilePath 的对象或 File * @param {Object} [data] - 额外表单字段 * @param {Object} [header] - 额外请求头 * @param {boolean} [checkCode=true] - 是否走响应码检查(默认走) */ export const uploadFile = (url, file, data = {}, header = {}, checkCode = true) => { return new Promise((resolve, reject) => { try { // 提取文件路径 let filePath = file; if (file && typeof file === "object") { filePath = file.path || file.url || file.tempFilePath || file.filePath || file; } const finalUrl = `${baseUrl}${url}`; // 构建 headers,优先使用传入 header const headers = { ...(header || {}), }; if (userToken.value) headers["Access-Token"] = `${userToken.value}`; if (lang.value) headers["Language"] = `${lang.value}`; if (CLIENT.value) headers["CLIENT"] = `${CLIENT.value}`; uni.uploadFile({ url: finalUrl, filePath: filePath, name: "file", header: headers, formData: data || {}, success: (res) => { try { // uni.uploadFile 的 res.data 是字符串 res.data = JSON.parse(res.data || "{}"); } catch (e) { res.data = {}; } // 适配 responseInterceptor 接口 const resp = { data: res.data, statusCode: res.statusCode }; if (checkCode) { try { const result = responseInterceptor(resp); resolve(result); } catch (err) { reject(err); } } else { resolve(resp.data); } }, fail: (err) => { reject(errorHandler(err)); }, }); } catch (err) { reject(err); } }); }; // 快捷方法 export const get = (url, data = {}, typeOrOptions = {}, options = {}) => { const mergedOptions = typeof typeOrOptions === "string" ? { type: typeOrOptions, ...(options || {}) } : (typeOrOptions || {}); return request({ url, method: "GET", data, ...mergedOptions, }); }; export const post = (url, data = {}, type, options = {}) => { return request({ url, method: "POST", data, type, ...options, }); };