First commit

This commit is contained in:
2026-09-21 12:51:00 +08:00
commit 4fad31e438
145 changed files with 17216 additions and 0 deletions

136
frontend/src/api/request.ts Normal file
View File

@@ -0,0 +1,136 @@
import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'
import { ElMessage } from 'element-plus'
/** 令牌在 localStorage 中的键名 */
const TOKEN_KEY = 'lab-token'
/**
* 读取本地令牌。
*
* @returns JWT 字符串或空串
*/
export function getToken(): string {
return localStorage.getItem(TOKEN_KEY) ?? ''
}
/**
* 写入本地令牌。
*
* @param token JWT 字符串
*/
export function setToken(token: string): void {
localStorage.setItem(TOKEN_KEY, token)
}
/** 清除本地令牌 */
export function clearToken(): void {
localStorage.removeItem(TOKEN_KEY)
}
/** axios 实例,统一以 /api 为前缀(开发环境由 Vite 代理) */
const request: AxiosInstance = axios.create({
baseURL: '/api',
timeout: 15000
})
/**
* 请求拦截器:附加 Bearer 令牌。
*/
request.interceptors.request.use((config) => {
const token = getToken()
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
/**
* 响应拦截器:统一解包业务码,401 时跳转登录页。
*/
request.interceptors.response.use(
(response) => {
const body = response.data
if (body && typeof body.code === 'number' && body.code !== 0) {
ElMessage.error(body.message || '请求失败')
return Promise.reject(new Error(body.message))
}
return response
},
(error) => {
const status = error?.response?.status
if (status === 401) {
clearToken()
ElMessage.error('登录已失效,请重新登录')
if (window.location.pathname !== '/login') {
window.location.href = '/login'
}
} else if (status === 403) {
ElMessage.error('无权限执行该操作')
} else {
ElMessage.error(error?.response?.data?.message || error.message || '网络异常')
}
return Promise.reject(error)
}
)
/**
* 发起 GET 请求。
*
* @param url 接口路径
* @param params 查询参数
* @returns 业务数据
*/
export async function get<T>(url: string, params?: Record<string, unknown>): Promise<T> {
const { data } = await request.get(url, { params })
return data.data as T
}
/**
* 发起 POST 请求。
*
* @param url 接口路径
* @param body 请求体
* @param config 额外配置
* @returns 业务数据
*/
export async function post<T>(url: string, body?: unknown, config?: AxiosRequestConfig): Promise<T> {
const { data } = await request.post(url, body, config)
return data.data as T
}
/**
* 发起 PUT 请求。
*
* @param url 接口路径
* @param body 请求体
* @returns 业务数据
*/
export async function put<T>(url: string, body?: unknown): Promise<T> {
const { data } = await request.put(url, body)
return data.data as T
}
/**
* 发起 PATCH 请求。
*
* @param url 接口路径
* @param body 请求体
* @returns 业务数据
*/
export async function patch<T>(url: string, body?: unknown): Promise<T> {
const { data } = await request.patch(url, body)
return data.data as T
}
/**
* 发起 DELETE 请求。
*
* @param url 接口路径
* @returns 业务数据
*/
export async function del<T>(url: string): Promise<T> {
const { data } = await request.delete(url)
return data.data as T
}
export default request