This commit is contained in:
hexi
2026-11-06 16:56:49 +08:00
4 changed files with 166 additions and 142 deletions
+24 -123
View File
@@ -3,14 +3,12 @@
* 支持请求拦截、响应拦截、Loading显示、错误处理
*/
// 响应数据类型
export type ApiResponse<T> = {
code: number
message: string
data: T | null
}
// 请求配置类型
export type RequestConfig = {
url: string
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
@@ -21,37 +19,19 @@ export type RequestConfig = {
showError?: boolean
}
// 基础URL配置 - 虚拟接口
const BASE_URL = 'https://localhost:7145'
// 请求超时时间
const TIMEOUT = 10000
// 联调前统一使用测试响应,保证所有页面流程可完整验证。
const USE_MOCK_API = true
// Mock 接口模拟正常网络环境,响应延迟范围为 0 到 2 秒。
const MOCK_RESPONSE_MAX_DELAY = 2000
// Loading引用计数
let loadingCount = 0
/**
* 显示Loading
*/
function showLoading(text: string = '加载中...') {
loadingCount++
if (loadingCount === 1) {
uni.showLoading({
title: text,
mask: true
})
uni.showLoading({ title: text, mask: true })
}
}
/**
* 隐藏Loading
*/
function hideLoading() {
loadingCount--
if (loadingCount <= 0) {
@@ -60,36 +40,19 @@ function hideLoading() {
}
}
/**
* 获取Token
*/
function getToken(): string {
return uni.getStorageSync('token') as string ?? ''
}
/**
* 请求拦截器 - 添加token等
*/
function requestInterceptor(config: RequestConfig): RequestConfig {
// 添加完整URL
if (!config.url.startsWith('http')) {
config.url = BASE_URL + config.url
}
// 添加请求头
if (!config.url.startsWith('http')) config.url = BASE_URL + config.url
const token = getToken()
config.header = config.header ?? {}
config.header['Content-Type'] = 'application/json'
if (token) {
config.header['Authorization'] = `Bearer ${token}`
}
// 显示Loading
if (config.showLoading !== false) {
showLoading(config.loadingText)
}
if (token) config.header['Authorization'] = `Bearer ${token}`
if (config.showLoading !== false) showLoading(config.loadingText)
return config
}
@@ -97,17 +60,13 @@ function requestInterceptor(config: RequestConfig): RequestConfig {
* 响应拦截器 - 统一处理响应
*/
function responseInterceptor<T>(response: any, config: RequestConfig): ApiResponse<T> | null {
// 隐藏Loading
if (config.showLoading !== false) {
hideLoading()
}
// HTTP状态码检查
if (config.showLoading !== false) hideLoading()
if (response.statusCode !== 200) {
handleError(response.statusCode, '网络请求失败', config.showError)
return null
}
// 解析响应数据,兼容后端 PascalCase 与前端 camelCase 两种包装格式。
const rawResult = response.data as any
const result: ApiResponse<T> = {
@@ -115,50 +74,35 @@ function responseInterceptor<T>(response: any, config: RequestConfig): ApiRespon
message: String(rawResult?.message ?? rawResult?.Message ?? ''),
data: (rawResult?.data ?? rawResult?.Data ?? null) as T | null
}
// 业务状态码检查
if (result.code !== 200) {
handleError(result.code, result.message, config.showError)
return null
}
return result
}
/**
* 错误处理
*/
function handleError(code: number, message: string, showError: boolean = true) {
console.error(`[Request Error] Code: ${code}, Message: ${message}`)
if (!showError) {
return
}
// 根据错误码显示不同提示
if (!showError) return
let errorMsg = message
if (code === 401) {
errorMsg = '登录已过期,请重新登录'
// 清除token并跳转登录页
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')
uni.reLaunch({
url: '/pages/login/index'
})
uni.reLaunch({ url: '/pages/login/index' })
} else if (code === 403) {
errorMsg = '没有权限访问'
} else if (code === 404) {
errorMsg = '请求的资源不存在'
} else if (code === 500) {
errorMsg = '服务器错误,请稍后重试'
} else if (code === -1) {
errorMsg = '网络异常'
}
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 2000
})
uni.showToast({ title: errorMsg, icon: 'none', duration: 2000 })
}
/**
@@ -386,8 +330,7 @@ function createMockResponse<T>(config: RequestConfig): ApiResponse<T> {
* 通用请求方法
*/
export function request<T>(config: RequestConfig): Promise<ApiResponse<T> | null> {
return new Promise((resolve, reject) => {
// 请求拦截
return new Promise((resolve) => {
const finalConfig = requestInterceptor(config)
if (USE_MOCK_API && !finalConfig.url.includes('/api/v1/Parents/') && !finalConfig.url.includes('/api/v1/ParentStudentToday/') && !finalConfig.url.includes('/api/v1/ParentLearningWarning/') && !finalConfig.url.includes('/api/v1/ParentStudyPack/') && !finalConfig.url.includes('/api/v1/ParentLearningReport/')) {
@@ -408,70 +351,28 @@ export function request<T>(config: RequestConfig): Promise<ApiResponse<T> | null
data: finalConfig.data,
header: finalConfig.header,
timeout: TIMEOUT,
success: (res) => {
// 响应拦截
const result = responseInterceptor<T>(res, finalConfig)
resolve(result)
},
success: (res) => resolve(responseInterceptor<T>(res, finalConfig)),
fail: (err) => {
// 隐藏Loading
if (finalConfig.showLoading !== false) {
hideLoading()
}
// 网络错误处理
if (finalConfig.showLoading !== false) hideLoading()
handleError(-1, err.errMsg ?? '网络连接失败', finalConfig.showError)
// 将网络失败转换为统一的空响应,避免页面事件中出现未处理 Promise 异常。
resolve(null)
}
})
})
}
/**
* GET请求
*/
export function get<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<ApiResponse<T> | null> {
return request<T>({
url,
method: 'GET',
data,
...config
})
return request<T>({ url, method: 'GET', data, ...config })
}
/**
* POST请求
*/
export function post<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<ApiResponse<T> | null> {
return request<T>({
url,
method: 'POST',
data,
...config
})
return request<T>({ url, method: 'POST', data, ...config })
}
/**
* PUT请求
*/
export function put<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<ApiResponse<T> | null> {
return request<T>({
url,
method: 'PUT',
data,
...config
})
return request<T>({ url, method: 'PUT', data, ...config })
}
/**
* DELETE请求
*/
export function del<T>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<ApiResponse<T> | null> {
return request<T>({
url,
method: 'DELETE',
data,
...config
})
return request<T>({ url, method: 'DELETE', data, ...config })
}