Files
sh-app/utils/request.js
T
2026-06-16 19:26:48 +08:00

168 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useUserStore } from '@/store'
// TODO: 替换为实际的 API 地址
export const baseUrl = 'https://lygcgs.com.cn:8078/shjapi/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.postuview-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.hideLoading()
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;
};