Initial commit: One Pipe System

完整的管理系统,包含账户管理、卡片管理、套餐管理、财务管理等功能模块。

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
sexygoat
2026-01-22 16:35:33 +08:00
commit 222e5bb11a
495 changed files with 145440 additions and 0 deletions

262
src/utils/http/index.ts Normal file
View File

@@ -0,0 +1,262 @@
import axios, { InternalAxiosRequestConfig, AxiosRequestConfig, AxiosResponse } from 'axios'
import { ElMessage } from 'element-plus'
import { useUserStore } from '@/store/modules/user'
import EmojiText from '../ui/emojo'
import { ApiStatus } from './status'
import type { RequestOptions, ErrorMessageMode } from '@/types/api'
const axiosInstance = axios.create({
timeout: 15000, // 请求超时时间(毫秒)
baseURL: import.meta.env.MODE === 'development' ? '' : import.meta.env.VITE_API_URL, // API地址开发环境使用代理生产环境使用完整URL
withCredentials: true, // 异步请求携带cookie
transformRequest: [(data) => JSON.stringify(data)], // 请求数据转换为 JSON 字符串
validateStatus: (status) => status >= 200 && status < 300, // 只接受 2xx 的状态码
headers: {
get: { 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8' },
post: { 'Content-Type': 'application/json;charset=utf-8' }
},
transformResponse: [
(data, headers) => {
const contentType = headers['content-type']
if (contentType && contentType.includes('application/json')) {
try {
return JSON.parse(data)
} catch {
return data
}
}
return data
}
]
})
// 请求拦截器
axiosInstance.interceptors.request.use(
(request: InternalAxiosRequestConfig) => {
const { accessToken } = useUserStore()
// 如果 token 存在,则设置请求头
if (accessToken) {
request.headers.set({
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`
})
}
return request // 返回修改后的配置
},
(error) => {
ElMessage.error(`服务器异常! ${EmojiText[500]}`) // 显示错误消息
return Promise.reject(error) // 返回拒绝的 Promise
}
)
// Token 刷新相关
let isRefreshing = false // 是否正在刷新 token
let failedQueue: Array<{
resolve: (value?: any) => void
reject: (reason?: any) => void
}> = [] // 失败队列,存储因 token 过期而失败的请求
// 处理失败队列
const processQueue = (error: any, token: string | null = null) => {
failedQueue.forEach((prom) => {
if (error) {
prom.reject(error)
} else {
prom.resolve(token)
}
})
failedQueue = []
}
// 响应拦截器
axiosInstance.interceptors.response.use(
(response: AxiosResponse) => {
// 401 未授权 - 尝试刷新 token
if (response.data.code === ApiStatus.unauthorized) {
const userStore = useUserStore()
const originalRequest = response.config as any
// 如果没有 refreshToken直接退出登录
if (!userStore.refreshToken) {
logOut()
return Promise.reject(response)
}
// 如果正在刷新 token将请求加入队列
if (isRefreshing) {
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject })
})
.then((token) => {
originalRequest.headers['Authorization'] = `Bearer ${token}`
return axiosInstance.request(originalRequest)
})
.catch((err) => {
return Promise.reject(err)
})
}
// 标记正在刷新
originalRequest._retry = true
isRefreshing = true
// 调用刷新 token 接口
return new Promise((resolve, reject) => {
axiosInstance
.post('/api/auth/refresh', { refreshToken: userStore.refreshToken })
.then((res) => {
const { data } = res
// 刷新成功
if (data.code === ApiStatus.success && data.data?.access_token) {
// 更新 token
userStore.setToken(data.data.access_token, data.data.refresh_token)
// 更新请求头
originalRequest.headers['Authorization'] = `Bearer ${data.data.access_token}`
axiosInstance.defaults.headers.common['Authorization'] = `Bearer ${data.data.access_token}`
// 处理队列中的请求
processQueue(null, data.data.access_token)
// 重试原请求
resolve(axiosInstance.request(originalRequest))
} else {
// 刷新失败
processQueue(new Error('Token refresh failed'), null)
logOut()
reject(res)
}
})
.catch((err) => {
// 刷新失败
processQueue(err, null)
logOut()
reject(err)
})
.finally(() => {
isRefreshing = false
})
})
}
return response
},
(error) => {
if (axios.isCancel(error)) {
console.log('repeated request: ' + error.message)
}
// 注意错误处理现在在request函数中根据requestOptions处理
return Promise.reject(error)
}
)
// 扩展的请求配置接口
interface ExtendedRequestConfig extends AxiosRequestConfig {
requestOptions?: RequestOptions
}
// 处理请求配置
function processRequestConfig(config: ExtendedRequestConfig): AxiosRequestConfig {
const { requestOptions, ...axiosConfig } = config
// 应用自定义请求选项
if (requestOptions) {
// 处理是否携带token
if (requestOptions.withToken === false) {
axiosConfig.headers = { ...axiosConfig.headers }
delete axiosConfig.headers?.Authorization
}
// 处理是否添加时间戳
if (requestOptions.joinTime) {
const timestamp = Date.now()
if (axiosConfig.method?.toUpperCase() === 'GET') {
axiosConfig.params = { ...axiosConfig.params, _t: timestamp }
}
}
// 处理API URL
if (requestOptions.apiUrl) {
axiosConfig.baseURL = requestOptions.apiUrl
}
}
return axiosConfig
}
// 处理错误消息
function handleErrorMessage(error: any, mode: ErrorMessageMode = 'message') {
if (mode === 'none') return
const errorMessage = error.response?.data.msg
const message = errorMessage
? `${errorMessage} ${EmojiText[500]}`
: `请求超时或服务器异常!${EmojiText[500]}`
if (mode === 'modal') {
// TODO: 可以使用 ElMessageBox 显示模态框
ElMessage.error(message)
} else {
ElMessage.error(message)
}
}
// 请求
async function request<T = any>(config: ExtendedRequestConfig): Promise<T> {
const processedConfig = processRequestConfig(config)
// 对 POST | PUT 请求特殊处理
if (
processedConfig.method?.toUpperCase() === 'POST' ||
processedConfig.method?.toUpperCase() === 'PUT'
) {
// 如果已经有 data则保留原有的 data
if (processedConfig.params && !processedConfig.data) {
processedConfig.data = processedConfig.params
processedConfig.params = undefined // 使用 undefined 而不是空对象
}
}
try {
const res = await axiosInstance.request<T>(processedConfig)
return res.data
} catch (e) {
if (axios.isAxiosError(e)) {
// 处理错误消息
const errorMode = config.requestOptions?.errorMessageMode || 'message'
handleErrorMessage(e, errorMode)
}
return Promise.reject(e)
}
}
// API 方法集合
const api = {
get<T>(config: ExtendedRequestConfig): Promise<T> {
return request({ ...config, method: 'GET' }) // GET 请求
},
post<T>(config: ExtendedRequestConfig): Promise<T> {
return request({ ...config, method: 'POST' }) // POST 请求
},
put<T>(config: ExtendedRequestConfig): Promise<T> {
return request({ ...config, method: 'PUT' }) // PUT 请求
},
del<T>(config: ExtendedRequestConfig): Promise<T> {
return request({ ...config, method: 'DELETE' }) // DELETE 请求
},
request<T>(config: ExtendedRequestConfig): Promise<T> {
return request({ ...config }) // 通用请求
}
}
// 退出登录
const logOut = () => {
ElMessage.error(`登录已过期,请重新登录 ${EmojiText[500]}`)
setTimeout(() => {
useUserStore().logOut()
}, 1000)
}
export default api