确保项目目录结构如下(和封装代码对应):
bash
utils/
└── request/
├── index.ts // 上面的封装代码
└── config.ts // 基础地址配置
config.ts 参考代码
javascript
// 声明process类型,解决TS警告(不用安装任何依赖)
declare const process: {
env: {
NODE_ENV: 'development' | 'production'
}
}
// 区分开发/生产环境(小程序可通过process.env判断,HBuilderX会自动注入)
const isProd = process.env.NODE_ENV === 'production'
// 基础接口地址
export const BASE_URL = isProd
? 'https://你的生产环境域名'
: 'https://你的测试环境域名'
// 导出默认值(兼容你原来的import写法)
export default BASE_URL
request.ts
javascript
// utils/request/index.ts
import BASE_URL from './config'
// 仅保留核心的GET/POST方法
type HttpMethod = 'GET' | 'POST'
// 对齐uni-app规范的配置项:loading默认值改为true
interface RequestOptions {
header?: Record<string, string>
timeout?: number
baseURL?: string
loading?: boolean // 默认true,可手动传false关闭
loadingText?: string
showErrorToast?: boolean
}
// 统一的响应数据格式
interface ResponseData<T = any> {
code: number
message: string
data: T
success: boolean
}
// 声明uni全局对象,避免TS报错
declare const uni: any
/**
* 构建完整URL(处理baseURL和路径拼接)
*/
const buildURL = (base: string, url: string): string => {
if (/^https?:\/\//i.test(url)) return url
const normalizedBase = base.endsWith('/') ? base.slice(0, -1) : base
const normalizedUrl = url.startsWith('/') ? url : `/${url}`
return `${normalizedBase}${normalizedUrl}`
}
/**
* 序列化GET请求参数
*/
const serializeQuery = (params: Record<string, any> = {}): string => {
const parts: string[] = []
Object.keys(params).forEach((key) => {
const val = params[key]
if (val === undefined || val === null) return
if (Array.isArray(val)) {
val.forEach((v) => parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(v))}`))
} else {
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(val))}`)
}
})
return parts.join('&')
}
/**
* 显示加载提示
*/
const showLoading = (text: string = '加载中...'): void => {
uni.showLoading({
title: text,
mask: true
})
}
/**
* 🌟 细化HTTP错误信息
* @param statusCode HTTP状态码
* @returns 友好的错误提示文本
*/
const getHttpErrorMessage = (statusCode: number): string => {
const errorMap: Record<number, string> = {
400: '请求参数错误,请检查输入',
401: '登录已过期,请重新登录',
403: '暂无权限访问该资源',
404: '请求的接口不存在',
408: '请求超时,请稍后重试',
500: '服务器内部错误,请稍后重试',
502: '网关错误,服务暂不可用',
503: '服务维护中,请稍后重试',
504: '网关超时,请稍后重试'
}
return errorMap[statusCode] || `请求失败 (${statusCode})`
}
/**
* 处理401未授权
*/
const handleUnauthorized = (): void => {
uni.removeStorageSync('token')
uni.showToast({
title: '登录已过期,请重新登录',
icon: 'none',
duration: 2000
})
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/index'
})
}, 1500)
}
/**
* 核心请求方法
*/
const request = <T = any>(
url: string,
method: HttpMethod,
data?: any,
options: RequestOptions = {}
): Promise<ResponseData<T>> => {
const finalBaseURL = options.baseURL ?? BASE_URL
// 🌟 关键修改:loading默认值设为true(未传时默认显示加载态)
const isLoading = options.loading ?? true
if (isLoading) {
showLoading(options.loadingText)
}
let fullURL = buildURL(finalBaseURL, url)
if (method === 'GET' && data && Object.keys(data).length > 0) {
fullURL += `?${serializeQuery(data)}`
}
const token = uni.getStorageSync ? uni.getStorageSync('token') : ''
const finalHeader: Record<string, string> = {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...(options.header || {})
}
return new Promise<ResponseData<T>>((resolve, reject) => {
uni.request({
url: fullURL,
method,
data: method === 'GET' ? undefined : data,
header: finalHeader,
timeout: options.timeout ?? 15000,
success: (res: any) => {
// 🌟 同步修改:用isLoading判断是否隐藏
if (isLoading) {
uni.hideLoading()
}
if (res.statusCode >= 200 && res.statusCode < 300) {
const responseData = res.data as any
if (responseData.code === 401) {
handleUnauthorized()
reject({
code: 401,
message: '登录已过期',
data: null,
success: false
})
return
}
if (responseData.code === 0 || responseData.success) {
resolve({
code: responseData.code || 0,
message: responseData.message || '操作成功',
data: responseData.data || responseData,
success: true
})
} else {
const errorMsg = responseData.message || '请求失败'
if (options.showErrorToast !== false) {
uni.showToast({
title: errorMsg,
icon: 'none'
})
}
reject({
code: responseData.code || -1,
message: errorMsg,
data: responseData.data,
success: false
})
}
} else {
// 🌟 使用细化的HTTP错误信息
const errorMsg = getHttpErrorMessage(res.statusCode)
if (options.showErrorToast !== false) {
uni.showToast({
title: errorMsg,
icon: 'none'
})
}
reject({
code: res.statusCode,
message: errorMsg,
data: res.data,
success: false
})
}
},
fail: (err: any) => {
// 🌟 同步修改:用isLoading判断是否隐藏
if (isLoading) {
uni.hideLoading()
}
uni.showToast({
title: err.errMsg || '网络连接失败,请检查网络',
icon: 'none'
})
reject({
code: -2,
message: err.errMsg || '网络错误',
data: err,
success: false
})
},
complete: () => {
// 🌟 同步修改:用isLoading判断是否隐藏(兜底)
if (isLoading) {
uni.hideLoading()
}
}
})
})
}
// 封装GET/POST快捷方法
export const get = <T = any>(url: string, params?: Record<string, any>, options?: RequestOptions) => {
return request<T>(url, 'GET', params, options)
}
export const post = <T = any>(url: string, body?: any, options?: RequestOptions) => {
return request<T>(url, 'POST', body, options)
}
// 导出默认对象
export default {
get,
post
}
基础使用
typescript
// 在页面/组件中导入
import { get, post } from '@/utils/request'
// 示例1:GET请求获取用户列表(默认显示"加载中...")
const fetchUserList = async () => {
try {
// 泛型<T>指定返回数据类型,TS会自动提示字段
const res = await get<{ list: Array<{ id: string; name: string }>; total: number }>(
'/api/user/list', // 接口路径
{ page: 1, size: 10 } // GET参数
)
// 请求成功(res.success === true)
if (res.success) {
console.log('用户列表:', res.data.list)
console.log('总条数:', res.data.total)
}
} catch (err) {
// 请求失败(HTTP错误/业务错误/网络错误)
console.error('获取用户列表失败:', err)
}
}
// 示例2:POST请求实现登录(默认显示"加载中...")
const login = async (username: string, password: string) => {
try {
const res = await post<{ token: string; userInfo: { id: string; name: string } }>(
'/api/login', // 接口路径
{ username, password } // POST请求体
)
if (res.success) {
// 登录成功,保存token到本地
uni.setStorageSync('token', res.data.token)
// 提示用户
uni.showToast({ title: '登录成功', icon: 'success' })
// 跳转到首页
uni.reLaunch({ url: '/pages/index/index' })
}
} catch (err) {
console.error('登录失败:', err)
}
}