2026-06-09 19:29:04 +08:00
|
|
|
|
import { useUserStore } from '@/store'
|
|
|
|
|
|
|
|
|
|
|
|
// TODO: 替换为实际的 API 地址
|
2026-06-16 19:26:48 +08:00
|
|
|
|
export const baseUrl = 'https://lygcgs.com.cn:8078/shjapi/api/'
|
2026-06-09 19:29:04 +08:00
|
|
|
|
|
|
|
|
|
|
// ===== 防止重复请求 =====
|
|
|
|
|
|
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) // 网络失败也要清理标识
|
2026-06-16 19:26:48 +08:00
|
|
|
|
uni.hideLoading()
|
2026-06-09 19:29:04 +08:00
|
|
|
|
uni.showToast({ title: '网络连接失败', icon: 'none' })
|
|
|
|
|
|
reject(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GET 快捷方法
|
2026-06-11 09:43:42 +08:00
|
|
|
|
export const get = (url, params = {}) => {
|
2026-06-09 19:29:04 +08:00
|
|
|
|
return request({ url, method: 'GET', data: params })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// POST 快捷方法
|
2026-06-11 09:43:42 +08:00
|
|
|
|
export const post = (url, data = {}) => {
|
2026-06-09 19:29:04 +08:00
|
|
|
|
return request({ url, method: 'POST', data })
|
|
|
|
|
|
}
|
2026-06-11 09:43:42 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
};
|