Files
aiops/frontend/src/services/http.ts
T

121 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useUserStore } from '@/store';
const BASE_URL = '/api/v1';
// 后端统一响应格式
export interface ApiResponse<T = any> {
code: number;
message: string;
data: T;
}
// 列表响应格式
export interface ListResponse<T = any> {
list: T[];
total: number;
}
// 通用请求函数
async function request<T = any>(
method: string,
url: string,
body?: any,
params?: Record<string, any>
): Promise<T> {
// 构建 URL(含 query params)
let fullUrl = `${BASE_URL}${url}`;
if (params) {
const searchParams = new URLSearchParams();
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
searchParams.append(key, String(value));
}
});
const qs = searchParams.toString();
if (qs) fullUrl += `?${qs}`;
}
// 获取 token
const token = useUserStore.getState().token || localStorage.getItem('token');
const headers: Record<string, string> = {
'Content-Type': 'application/json',
};
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(fullUrl, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
if (response.status === 401) {
useUserStore.getState().logout();
window.location.href = '/login';
throw new Error('登录已过期,请重新登录');
}
const json: ApiResponse<T> = await response.json();
if (json.code && json.code !== 200) {
throw new Error(json.message || '请求失败');
}
// Return json.data for standard responses, or full json for non-standard ones
return (json.data !== undefined ? json.data : json) as T;
}
// 导出便捷方法
export const http = {
get: <T = any>(url: string, params?: Record<string, any>) =>
request<T>('GET', url, undefined, params),
post: <T = any>(url: string, body?: any) =>
request<T>('POST', url, body),
put: <T = any>(url: string, body?: any) =>
request<T>('PUT', url, body),
delete: <T = any>(url: string, body?: any) =>
request<T>('DELETE', url, body),
getRaw: async (url: string) => {
const tk = useUserStore.getState().token;
const baseUrl = import.meta.env.VITE_API_BASE_URL || '';
const resp = await fetch(`${baseUrl}/api/v1${url}`, { headers: { Authorization: `Bearer ${tk}` } });
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
return resp.text();
},
putRaw: async (url: string, body: string, contentType: string) => {
const tk = useUserStore.getState().token;
const baseUrl = import.meta.env.VITE_API_BASE_URL || '';
const resp = await fetch(`${baseUrl}/api/v1${url}`, {
method: 'PUT',
headers: { Authorization: `Bearer ${tk}`, 'Content-Type': contentType },
body,
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
return resp.json();
},
// 二进制下载(blob)— 用于 tar.gz / 文件下载
download: async (url: string, filename?: string) => {
const tk = useUserStore.getState().token;
const baseUrl = import.meta.env.VITE_API_BASE_URL || '';
const resp = await fetch(`${baseUrl}/api/v1${url}`, {
headers: { Authorization: `Bearer ${tk}` },
});
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
const blob = await resp.blob();
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = filename || resp.headers.get('Content-Disposition')?.match(/filename="?(.+?)"?$/)?.[1] || 'download';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
},
};
export default http;