Files
xin-procurement-weapp/src/utils/request.ts
T
2026-08-06 15:24:03 +08:00

200 lines
5.0 KiB
TypeScript
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 Taro from '@tarojs/taro'
import type { ApiResponse, RequestConfig } from '@/types/api'
/** 存储 key(与 AuthContext 保持一致) */
const STORAGE_TOKEN_KEY = 'auth_token'
/** 登录页路径 */
const LOGIN_PATH = '/pages/login/index'
/** 默认请求超时(ms */
const DEFAULT_TIMEOUT = 15000
/** 接口根地址(uploadFile 等原生请求同样使用) */
export const BASE_URL = "http://localhost:8000/index.php"
/**
* HTTP 状态码 → 错误提示映射
*/
const HTTP_ERROR_MAP: Record<number, string> = {
400: '参数不正确',
401: '登录已过期,请重新登录',
403: '您没有权限操作',
404: '请求的资源不存在',
408: '请求超时',
500: '服务器内部错误',
502: '网关错误',
503: '服务暂时不可用',
504: '网关超时',
}
/** 业务状态码常量 */
const BIZ_CODE = {
SUCCESS: 0,
} as const
/**
* 获取本地存储的 token
*/
export function getToken(): string | null {
try {
return Taro.getStorageSync(STORAGE_TOKEN_KEY) || null
} catch {
return null
}
}
/**
* 清除本地认证信息
*/
function clearAuth(): void {
try {
Taro.removeStorageSync(STORAGE_TOKEN_KEY)
Taro.removeStorageSync('auth_user')
} catch {
// noop
}
}
/**
* 处理 HTTP 状态码错误
* @param statusCode - HTTP 状态码
*/
function handleHttpError(statusCode: number): void {
// 401 → 清除登录态并跳转登录页
if (statusCode === 401) {
clearAuth()
Taro.showToast({ title: '登录已过期,请重新登录', icon: 'none' })
// 避免在登录页重复跳转
const pages = Taro.getCurrentPages()
const currentPage = pages[pages.length - 1]
if (currentPage?.route !== 'pages/login/index') {
setTimeout(() => {
Taro.navigateTo({ url: LOGIN_PATH })
}, 800)
}
return
}
const message = HTTP_ERROR_MAP[statusCode] || `请求失败 (状态码: ${statusCode})`
Taro.showToast({ title: message, icon: 'none' })
}
/**
* 处理业务错误
* @param data - 接口返回数据
*/
function handleBusinessError(data: ApiResponse): void {
const { msg } = data
if (msg) {
Taro.showToast({ title: msg, icon: 'none' })
}
}
/**
* 发起网络请求
*
* @example
* ```ts
* // GET 请求
* const res = await request({ url: '/api/user/info' })
*
* // POST 请求
* const res = await request({ url: '/api/order/create', method: 'POST', data: { id: 1 } })
*
* // 跳过 token(如登录接口)
* const res = await request({ url: '/api/auth/login', method: 'POST', skipToken: true })
* ```
*/
export function request<T = any>(config: RequestConfig): Promise<ApiResponse<T>> {
const { skipToken, skipErrorToast, ...restConfig } = config
// 构建请求头
const header: Record<string, string> = {
'Content-Type': 'application/json',
...((restConfig.header as Record<string, string>) || {}),
}
// 自动附加 token
if (!skipToken) {
const token = getToken()
if (token) {
header['Authorization'] = `Bearer ${token}`
}
}
return new Promise((resolve, reject) => {
Taro.request({
...restConfig,
url: BASE_URL + restConfig.url,
header,
timeout: restConfig.timeout || DEFAULT_TIMEOUT,
success(res) {
const { statusCode, data } = res
// HTTP 状态码异常
if (statusCode < 200 || statusCode >= 300) {
if (!skipErrorToast) {
handleHttpError(statusCode)
}
reject(res)
return
}
const responseData = data as ApiResponse<T>
// 业务成功
if (responseData.success) {
resolve(responseData)
return
}
// 业务失败
if (!skipErrorToast) {
handleBusinessError(responseData)
}
reject(responseData)
},
fail(err) {
// 网络错误 / 超时
const errMsg = err.errMsg || ''
if (errMsg.includes('timeout')) {
Taro.showToast({ title: '请求超时,请稍后重试', icon: 'none' })
} else if (errMsg.includes('fail')) {
Taro.showToast({ title: '网络连接失败,请检查网络', icon: 'none' })
} else {
Taro.showToast({ title: '网络错误,请稍后重试', icon: 'none' })
}
reject(err)
},
})
})
}
/**
* GET 请求快捷方法
*/
export function get<T = any>(url: string, config?: Omit<RequestConfig, 'url' | 'method'>) {
return request<T>({ ...config, url, method: 'GET' })
}
/**
* POST 请求快捷方法
*/
export function post<T = any>(url: string, data?: any, config?: Omit<RequestConfig, 'url' | 'method' | 'data'>) {
return request<T>({ ...config, url, method: 'POST', data })
}
/**
* PUT 请求快捷方法
*/
export function put<T = any>(url: string, data?: any, config?: Omit<RequestConfig, 'url' | 'method' | 'data'>) {
return request<T>({ ...config, url, method: 'PUT', data })
}
/**
* DELETE 请求快捷方法
*/
export function del<T = any>(url: string, config?: Omit<RequestConfig, 'url' | 'method'>) {
return request<T>({ ...config, url, method: 'DELETE' })
}