import { useUserStore } from '@/store' // TODO: 替换为实际的 API 地址 export const baseUrl = 'https://sggl.sedin.com.cn/sgglapi/api/' // ===== 防止重复请求 ===== const pendingRequests = new Map() /** * 生成请求唯一标识 */ function getRequestKey(config) { const { url = '', method = 'GET', data = {} } = config const paramStr = JSON.stringify(data) return `${method}:${url}:${paramStr}` } /** * 检查是否为重复请求 */ function checkDuplicate(config) { const key = getRequestKey(config) if (pendingRequests.has(key)) { const meta = config.meta || {} if (meta.toast !== false) { uni.showToast({ title: '请求过于频繁,请稍后再试', icon: 'none' }) } return new Error('duplicate request') } pendingRequests.set(key, true) return null } /** * 移除请求标识 */ function removePending(config) { const key = getRequestKey(config) pendingRequests.delete(key) } // ======================== // uview-pro 全局请求配置 export const httpRequestConfig = { baseUrl, header: { 'content-type': 'application/json' }, meta: { originalData: true, toast: true, loading: true } } // 全局请求/响应拦截器 export const httpInterceptor = { request: (config) => { // 检查重复请求 const dupError = checkDuplicate(config) if (dupError) throw dupError const meta = config.meta || {} if (meta.loading) { uni.showLoading({ title: '加载中...', mask: true }) } const userStore = useUserStore() if (userStore.token) { config.header.token = userStore.token } return config }, response: (response) => { const meta = response.config?.meta || {} if (meta.loading) { uni.hideLoading() } // 请求完成,移除标识 removePending(response.config) // 根据业务处理错误、例如登录失效等处理接口返回错误码 if (response.data.code !== 1) { if (meta.toast) { uni.showToast({ title: response.data.message || '请求失败', icon: 'none' }) } // 401 登录失效,跳转登录页 if (response.data.code === 401) { const userStore = useUserStore() userStore.logout() uni.reLaunch({ url: '/pages/login/index' }) } throw new Error(response.data.message || '请求失败') } uni.showToast({ title: response.data.message || '请求成功', icon: 'none' }) return response.data } } /** * 便捷请求方法(基于 uni.request 封装) * 也可用 uni.$http.get / uni.$http.post(uview-pro) */ export function request(options) { return new Promise((resolve, reject) => { // 运行请求拦截器 const config = { ...httpRequestConfig, ...options } const finalConfig = httpInterceptor.request({ ...config, header: { ...httpRequestConfig.header, ...options.header } }) uni.request({ ...finalConfig, url: `${finalConfig.baseUrl}${finalConfig.url}`, success: (res) => { try { const data = httpInterceptor.response({ data: res.data, config: finalConfig }) resolve(data) } catch (err) { reject(err) } }, fail: (err) => { removePending(finalConfig) // 网络失败也要清理标识 uni.showToast({ title: '网络连接失败', icon: 'none' }) reject(err) } }) }) } // GET 快捷方法 export const get = (url, params = {}) => { return request({ url, method: 'GET', data: params }) } // POST 快捷方法 export const post = (url, data = {}) => { return request({ url, method: 'POST', data }) } export const getUrlParam = (url, name) => { if (!url || !name) return null; // 构造正则:匹配 ?name= 或 &name= 后面的值 // [?&] : 匹配起始分隔符 // ${name}: 匹配具体的参数名 // = : 匹配等号 // ([^&]*) : 捕获组,匹配直到下一个 & 或字符串结束的内容 const regex = new RegExp(`[?&]${name}=([^&]*)`, 'i'); // 'i' 表示忽略大小写 const match = url.match(regex); if (match && match[1]) { return decodeURIComponent(match[1]); } return null; };