前端项目初始化,登录页支持暗色主题与禁止滑动

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fish
2026-04-26 14:40:55 +08:00
parent bd258e19c2
commit c91e038953
29 changed files with 994 additions and 53 deletions

View File

@@ -0,0 +1,95 @@
import axios, { AxiosError, type AxiosInstance, type AxiosRequestConfig } from 'axios'
import { Device, Language, type ApiRequest, type ApiResponse, type ErrorResponse } from '@/types/api'
import { useAuthStore } from '@/stores/auth'
// 根据环境选择基础地址
// Docker 开发环境Vite proxy 会将 /api 转发到后端
// 生产环境Nginx 代理 /api 到后端网关
const baseURL = import.meta.env.DEV ? '/' : '/api'
const client: AxiosInstance = axios.create({
baseURL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
},
})
// 请求拦截器:注入 JWT
client.interceptors.request.use((config) => {
const token = useAuthStore.getState().token
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器:统一错误处理
client.interceptors.response.use(
(response) => response,
(error: AxiosError<ErrorResponse>) => {
const status = error.response?.status
const message = error.response?.data?.message || error.message || '请求失败'
if (status === 401) {
useAuthStore.getState().logout()
window.location.href = '/login'
}
return Promise.reject(new Error(message))
}
)
/** 包装业务请求(注册/业务类接口) */
export async function apiPost<TReq, TRes>(
url: string,
data: TReq,
config?: AxiosRequestConfig
): Promise<TRes> {
const body: ApiRequest<TReq> = {
device: Device.Web,
language: Language.SimplifiedChinese,
data,
}
const response = await client.post<ApiResponse<TRes>>(url, body, config)
const result = response.data
if (!result.success) {
throw new Error(result.message)
}
if (result.data === null) {
throw new Error('接口返回数据为空')
}
return result.data
}
/** 原始 POST登录/认证类接口,不包装) */
export async function rawPost<TReq, TRes>(
url: string,
data: TReq,
config?: AxiosRequestConfig
): Promise<TRes> {
const response = await client.post<TRes>(url, data, config)
return response.data
}
/** 通用 GET */
export async function apiGet<TRes>(url: string, config?: AxiosRequestConfig): Promise<TRes> {
const response = await client.get<ApiResponse<TRes>>(url, config)
const result = response.data
if (!result.success) {
throw new Error(result.message)
}
if (result.data === null) {
throw new Error('接口返回数据为空')
}
return result.data
}
export default client