refactor(http): 重构HTTP模块结构,将相关文件迁移至src/http目录
将原本分散在src/utils和src/interceptors下的HTTP相关代码统一迁移至src/http目录,包括请求工具、拦截器、类型定义等 移除不再使用的src/interceptors目录 调整相关文件的引用路径 新增统一的HTTP模块入口文件
This commit is contained in:
@@ -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 '深夜了'
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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('&')
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CustomRequestOptions } from '@/interceptors/request'
|
||||
import type { CustomRequestOptions } from '@/http/interceptor'
|
||||
|
||||
/**
|
||||
* 请求方法: 主要是对 uni.request 的封装,去适配 openapi-ts-request 的 request 方法
|
||||
|
||||
@@ -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
|
||||
@@ -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},请检查网络或联系管理员!`
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user