refactor(http): 重构HTTP模块结构,将相关文件迁移至src/http目录

将原本分散在src/utils和src/interceptors下的HTTP相关代码统一迁移至src/http目录,包括请求工具、拦截器、类型定义等
移除不再使用的src/interceptors目录
调整相关文件的引用路径
新增统一的HTTP模块入口文件
This commit is contained in:
feige996
2025-07-08 16:59:32 +08:00
parent c0118df836
commit dc5fdda452
21 changed files with 14 additions and 181 deletions

View File

@@ -1,150 +0,0 @@
import dayjs from 'dayjs'
import calendar from 'dayjs/plugin/calendar'
import quarterOfYear from 'dayjs/plugin/quarterOfYear'
import relativeTime from 'dayjs/plugin/relativeTime'
import updateLocale from 'dayjs/plugin/updateLocale'
import utc from 'dayjs/plugin/utc'
import weekday from 'dayjs/plugin/weekday'
import 'dayjs/locale/zh-cn'
dayjs.extend(calendar)
dayjs.extend(quarterOfYear)
dayjs.extend(relativeTime)
dayjs.extend(updateLocale)
dayjs.extend(utc)
dayjs.extend(weekday)
dayjs.locale('zh-cn')
dayjs.updateLocale('zh-cn', {
calendar: {
sameDay: 'HH:mm',
nextDay: '[明天]',
nextWeek: 'dddd',
lastDay: '[昨天] HH:mm',
lastWeek: 'dddd HH:mm',
sameElse: 'YYYY年M月D日 HH:mm',
},
relativeTime: {
future: '%s后',
past: '%s前',
s: '几秒',
m: '1分钟',
mm: '%d分钟',
h: '1小时',
hh: '%d小时',
d: '1天',
dd: '%d天',
M: '1个月',
MM: '%d个月',
y: '1年',
yy: '%d年',
},
})
/** 时间工具 */
export const dateUtil = dayjs
export const DATETIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'
export const DATE_FORMAT = 'YYYY-MM-DD'
export const TIME_FORMAT = 'HH:mm'
/**
* 格式化日期
* @param _date 日期对象、时间戳或字符串
* @param format 格式字符串
* @returns 格式化后的日期字符串
*/
function _format(_date: dayjs.ConfigType, format: string): string {
if (!_date) {
return _date as any
}
const date = dateUtil(_date)
return date.isValid() ? date.format(format) : (_date as string)
}
/**
* 格式化为日期时间字符串
* @param date 日期对象、时间戳或字符串
* @param format 格式字符串,默认为 DATETIME_FORMAT
* @returns 格式化后的日期时间字符串
*/
export function formatToDatetime(date: dayjs.ConfigType = undefined, format: string = DATETIME_FORMAT): string {
return _format(date, format)
}
/**
* 格式化为日期字符串
* @param date 日期对象、时间戳或字符串
* @param format 格式字符串,默认为 DATE_FORMAT
* @returns 格式化后的日期字符串
*/
export function formatToDate(date: dayjs.ConfigType = undefined, format: string = DATE_FORMAT): string {
return _format(date, format)
}
/**
* 格式化为日期字符串
* @param date 日期对象、时间戳或字符串
* @param format 格式字符串,默认为 TIME_FORMAT
* @returns 格式化后的日期字符串
*/
export function formatToTime(date: dayjs.ConfigType = undefined, format: string = TIME_FORMAT): string {
return _format(date, format)
}
/**
* 时间人性化显示
* @param date 要格式化的日期
* @param oppositeDate 参考日期,默认为当前时间
* @returns 人性化的时间字符串
*/
export function humanizedDate(date: dayjs.ConfigType, oppositeDate: dayjs.ConfigType = undefined): string {
if (!date || !dateUtil(date).isValid()) {
return ''
}
const now = oppositeDate ? dateUtil(oppositeDate) : dateUtil()
const diffSeconds = now.diff(date, 'second')
const diffMinutes = now.diff(date, 'minute')
const diffHours = now.diff(date, 'hour')
const diffDays = now.diff(date, 'day')
if (diffSeconds < 60) {
return `${diffSeconds}秒前`
}
else if (diffMinutes < 60) {
return `${diffMinutes}分钟前`
}
else if (diffHours < 24) {
return `${diffHours}小时前`
}
else if (diffDays < 7) {
return `${diffDays}天前`
}
else {
return formatToDatetime(date)
}
}
/**
* 获取时辰问候语
* @returns 根据当前时间返回相应的问候语
*/
export function getGreeting(): string {
const currentHour = dateUtil().hour()
if (currentHour >= 5 && currentHour < 12) {
return '早上好'
}
else if (currentHour >= 12 && currentHour < 14) {
return '中午好'
}
else if (currentHour >= 14 && currentHour < 18) {
return '下午好'
}
else if (currentHour >= 18 && currentHour < 24) {
return '晚上好'
}
else {
return '深夜了'
}
}

View File

@@ -1,112 +0,0 @@
import type { CustomRequestOptions } from '@/interceptors/request'
export function http<T>(options: CustomRequestOptions) {
// 1. 返回 Promise 对象
return new Promise<IResData<T>>((resolve, reject) => {
uni.request({
...options,
dataType: 'json',
// #ifndef MP-WEIXIN
responseType: 'json',
// #endif
// 响应成功
success(res) {
// 状态码 2xx参考 axios 的设计
if (res.statusCode >= 200 && res.statusCode < 300) {
// 2.1 提取核心数据 res.data
resolve(res.data as IResData<T>)
}
else if (res.statusCode === 401) {
// 401错误 -> 清理用户信息,跳转到登录页
// userStore.clearUserInfo()
// uni.navigateTo({ url: '/pages/login/login' })
reject(res)
}
else {
// 其他错误 -> 根据后端错误信息轻提示
!options.hideErrorToast
&& uni.showToast({
icon: 'none',
title: (res.data as IResData<T>).msg || '请求错误',
})
reject(res)
}
},
// 响应失败
fail(err) {
uni.showToast({
icon: 'none',
title: '网络错误,换个网络试试',
})
reject(err)
},
})
})
}
/**
* GET 请求
* @param url 后台地址
* @param query 请求query参数
* @param header 请求头默认为json格式
* @returns
*/
export function httpGet<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
return http<T>({
url,
query,
method: 'GET',
header,
...options,
})
}
/**
* POST 请求
* @param url 后台地址
* @param data 请求body参数
* @param query 请求query参数post请求也支持query很多微信接口都需要
* @param header 请求头默认为json格式
* @returns
*/
export function httpPost<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
return http<T>({
url,
query,
data,
method: 'POST',
header,
...options,
})
}
/**
* PUT 请求
*/
export function httpPut<T>(url: string, data?: Record<string, any>, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
return http<T>({
url,
data,
query,
method: 'PUT',
header,
...options,
})
}
/**
* DELETE 请求(无请求体,仅 query
*/
export function httpDelete<T>(url: string, query?: Record<string, any>, header?: Record<string, any>, options?: Partial<CustomRequestOptions>) {
return http<T>({
url,
query,
method: 'DELETE',
header,
...options,
})
}
http.get = httpGet
http.post = httpPost
http.put = httpPut
http.delete = httpDelete

View File

@@ -1,29 +0,0 @@
/**
* 将对象序列化为URL查询字符串用于替代第三方的 qs 库,节省宝贵的体积
* 支持基本类型值和数组,不支持嵌套对象
* @param obj 要序列化的对象
* @returns 序列化后的查询字符串
*/
export function stringifyQuery(obj: Record<string, any>): string {
if (!obj || typeof obj !== 'object' || Array.isArray(obj))
return ''
return Object.entries(obj)
.filter(([_, value]) => value !== undefined && value !== null)
.map(([key, value]) => {
// 对键进行编码
const encodedKey = encodeURIComponent(key)
// 处理数组类型
if (Array.isArray(value)) {
return value
.filter(item => item !== undefined && item !== null)
.map(item => `${encodedKey}=${encodeURIComponent(item)}`)
.join('&')
}
// 处理基本类型
return `${encodedKey}=${encodeURIComponent(value)}`
})
.join('&')
}

View File

@@ -1,4 +1,4 @@
import type { CustomRequestOptions } from '@/interceptors/request'
import type { CustomRequestOptions } from '@/http/interceptor'
/**
* 请求方法: 主要是对 uni.request 的封装,去适配 openapi-ts-request 的 request 方法

View File

@@ -1,111 +0,0 @@
import type { uniappRequestAdapter } from '@alova/adapter-uniapp'
import type { IResponse } from './types'
import AdapterUniapp from '@alova/adapter-uniapp'
import { createAlova } from 'alova'
import { createServerTokenAuthentication } from 'alova/client'
import VueHook from 'alova/vue'
import { toast } from '@/utils/toast'
import { ContentTypeEnum, ResultEnum, ShowMessage } from './enum'
// 配置动态Tag
export const API_DOMAINS = {
DEFAULT: import.meta.env.VITE_SERVER_BASEURL,
SECONDARY: import.meta.env.VITE_API_SECONDARY_URL,
}
/**
* 创建请求实例
*/
const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication<
typeof VueHook,
typeof uniappRequestAdapter
>({
refreshTokenOnError: {
isExpired: (error) => {
return error.response?.status === ResultEnum.Unauthorized
},
handler: async () => {
try {
// await authLogin();
}
catch (error) {
// 切换到登录页
await uni.reLaunch({ url: '/pages/common/login/index' })
throw error
}
},
},
})
/**
* alova 请求实例
*/
const alovaInstance = createAlova({
baseURL: import.meta.env.VITE_API_BASE_URL,
...AdapterUniapp(),
timeout: 5000,
statesHook: VueHook,
beforeRequest: onAuthRequired((method) => {
// 设置默认 Content-Type
method.config.headers = {
ContentType: ContentTypeEnum.JSON,
Accept: 'application/json, text/plain, */*',
...method.config.headers,
}
const { config } = method
const ignoreAuth = !config.meta?.ignoreAuth
console.log('ignoreAuth===>', ignoreAuth)
// 处理认证信息 自行处理认证问题
if (ignoreAuth) {
const token = 'getToken()'
if (!token) {
throw new Error('[请求错误]:未登录')
}
// method.config.headers.token = token;
}
// 处理动态域名
if (config.meta?.domain) {
method.baseURL = config.meta.domain
console.log('当前域名', method.baseURL)
}
}),
responded: onResponseRefreshToken((response, method) => {
const { config } = method
const { requestType } = config
const {
statusCode,
data: rawData,
errMsg,
} = response as UniNamespace.RequestSuccessCallbackResult
// 处理特殊请求类型(上传/下载)
if (requestType === 'upload' || requestType === 'download') {
return response
}
// 处理 HTTP 状态码错误
if (statusCode !== 200) {
const errorMessage = ShowMessage(statusCode) || `HTTP请求错误[${statusCode}]`
console.error('errorMessage===>', errorMessage)
toast.error(errorMessage)
throw new Error(`${errorMessage}${errMsg}`)
}
// 处理业务逻辑错误
const { code, message, data } = rawData as IResponse
if (code !== ResultEnum.Success) {
if (config.meta?.toast !== false) {
toast.warning(message)
}
throw new Error(`请求错误[${code}]${message}`)
}
// 处理成功响应,返回业务数据
return data
}),
})
export const http = alovaInstance

View File

@@ -1,66 +0,0 @@
export enum ResultEnum {
Success = 0, // 成功
Error = 400, // 错误
Unauthorized = 401, // 未授权
Forbidden = 403, // 禁止访问原为forbidden
NotFound = 404, // 未找到原为notFound
MethodNotAllowed = 405, // 方法不允许原为methodNotAllowed
RequestTimeout = 408, // 请求超时原为requestTimeout
InternalServerError = 500, // 服务器错误原为internalServerError
NotImplemented = 501, // 未实现原为notImplemented
BadGateway = 502, // 网关错误原为badGateway
ServiceUnavailable = 503, // 服务不可用原为serviceUnavailable
GatewayTimeout = 504, // 网关超时原为gatewayTimeout
HttpVersionNotSupported = 505, // HTTP版本不支持原为httpVersionNotSupported
}
export enum ContentTypeEnum {
JSON = 'application/json;charset=UTF-8',
FORM_URLENCODED = 'application/x-www-form-urlencoded;charset=UTF-8',
FORM_DATA = 'multipart/form-data;charset=UTF-8',
}
/**
* 根据状态码,生成对应的错误信息
* @param {number|string} status 状态码
* @returns {string} 错误信息
*/
export function ShowMessage(status: number | string): string {
let message: string
switch (status) {
case 400:
message = '请求错误(400)'
break
case 401:
message = '未授权,请重新登录(401)'
break
case 403:
message = '拒绝访问(403)'
break
case 404:
message = '请求出错(404)'
break
case 408:
message = '请求超时(408)'
break
case 500:
message = '服务器错误(500)'
break
case 501:
message = '服务未实现(501)'
break
case 502:
message = '网络错误(502)'
break
case 503:
message = '服务不可用(503)'
break
case 504:
message = '网络超时(504)'
break
case 505:
message = 'HTTP版本不受支持(505)'
break
default:
message = `连接出错(${status})!`
}
return `${message},请检查网络或联系管理员!`
}

View File

@@ -1,22 +0,0 @@
// 通用响应格式
export interface IResponse<T = any> {
code: number | string
data: T
message: string
status: string | number
}
// 分页请求参数
export interface PageParams {
page: number
pageSize: number
[key: string]: any
}
// 分页响应数据
export interface PageResult<T> {
list: T[]
total: number
page: number
pageSize: number
}