website-01/lib/api.ts

99 lines
2.6 KiB
TypeScript
Raw Normal View History

2026-06-22 14:43:46 +08:00
import axios, {
AxiosError,
AxiosInstance,
InternalAxiosRequestConfig,
} from 'axios';
/** 后端统一响应体 */
export interface ApiResult<T> {
code: number;
msg: string;
data: T;
path?: string;
timestamp?: string;
}
/** 分页结果 */
export interface Paginated<T> {
list: T[];
total: number;
page: number;
pageSize: number;
}
const API_URL = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3001/api';
const TOKEN_KEY = process.env.NEXT_PUBLIC_TOKEN_KEY ?? 'admin_token';
/** ---------- Token 存取(仅浏览器端) ---------- */
export const tokenStorage = {
get(): string | null {
if (typeof window === 'undefined') return null;
return window.localStorage.getItem(TOKEN_KEY);
},
set(token: string): void {
if (typeof window === 'undefined') return;
window.localStorage.setItem(TOKEN_KEY, token);
},
clear(): void {
if (typeof window === 'undefined') return;
window.localStorage.removeItem(TOKEN_KEY);
},
};
/** ---------- axios 实例 ---------- */
export const http: AxiosInstance = axios.create({
baseURL: API_URL,
timeout: 15000,
});
// 请求拦截:自动注入 JWT
http.interceptors.request.use((config: InternalAxiosRequestConfig) => {
const token = tokenStorage.get();
if (token) {
config.headers.set('Authorization', `Bearer ${token}`);
}
return config;
});
// 响应拦截:拆包 + 401 跳登录
http.interceptors.response.use(
(response) => {
const body = response.data as ApiResult<unknown>;
if (body && typeof body.code === 'number') {
if (body.code === 200) {
return body.data;
}
// 业务错误抛出UI 层 try/catch 处理
return Promise.reject(new Error(body.msg || '请求失败'));
}
return body;
},
(error: AxiosError<ApiResult<unknown>>) => {
const status = error.response?.status;
const msg =
error.response?.data?.msg ?? error.message ?? '网络异常,请稍后重试';
if (status === 401) {
tokenStorage.clear();
// 避免循环跳转:仅在 /admin 下跳登录
if (typeof window !== 'undefined') {
const path = window.location.pathname;
if (path.startsWith('/admin') && path !== '/admin/login') {
window.location.href = '/admin/login?expired=1';
}
}
}
return Promise.reject(new Error(msg));
},
);
/** ---------- SWR 通用 fetcher ---------- */
export const fetcher = async <T>(url: string): Promise<T> => {
return (await http.get<unknown, T>(url)) as T;
};
/** ---------- 工具:表单提交包装 ---------- */
export async function submitForm<T>(fn: () => Promise<T>): Promise<T> {
return fn();
}