Files
aiops/frontend/src/utils/request.ts
T
attacker a7838aaf65 refactor(i18n): U41 残余硬编码中文清零 — 工具函数+审计RDP标签+AIOps分析+Monitor残余+审批选项
18 文件 ~35 处 UI 硬编码中文→t()/i18n.t() 调用:
- agent-utils.ts timeAgo() 秒前/分钟前/小时前
- utils.ts formatDistanceToNow() 分钟前/小时前/天前
- request.ts 响应解析失败错误消息
- ActiveSessionsPanel/HistorySessionsPanel RDP 在线/历史会话标签
- MetricsExplorer 清除/已选/最近1小时
- HostMonitor 当前主机 subtitle
- AIAnalysisResult 证据链 (新增 useTranslation hook)
- capacity-predict.ts 容量预测摘要模板
- ApprovalResourcePage 阿里云/腾讯云/华为云
- ApprovalPermissionPage 堡垒机
- site-monitor/helpers.ts 频率/时间/证书天数格式化
- site-monitor/analytics.ts 证书剩余/检查失败/延迟波动事件
- middleware-monitor MwTargetChartsPanel/MwTrendChart/helpers.ts
- snmp-monitor SnmpMetricTrendChart/SnmpDeviceChartsPanel
- host-monitor/utils.ts 图表点数标签 + PortPolicyDialog 系统进程说明

新增 34 翻译键 (common.duration.secAgo/hourAgo/dayAgo + common.selected/
responseParseError/rdpActiveSessions/rdpHistorySessions/rdpSessionCount/
evidenceChain/last1Hour/currentHost + monitor.lbl 10键 + monitor.siteMonitor
8键 + approval.provider 3键 + approval.permType 1键 +
monitor.portPolicyDialog.systemProcessDesc + aiops.capacity.summary)

zh/en 7813→7847 对齐; tsc -b=0, npm build✓ (17.31s), ESLint=0/43/any=0,
go build=0, 17/17 测试通过
2026-07-13 22:21:47 +08:00

194 lines
6.1 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';
import { getToken, clearToken } from './storage';
import i18n from '@/i18n';
const API_PREFIX = '/api/v1';
// 后端统一响应格式
export interface ApiResponse<T = unknown> {
code: number;
message: string;
data: T;
}
// 列表响应格式
export interface ListResponse<T = unknown> {
list: T[];
total: number;
}
type Json = unknown;
/** 统一 API 根路径,避免 get/post 与 download/upload 行为不一致 // Logan.Li */
export function getApiBase(): string {
const envBase = import.meta.env.VITE_API_BASE_URL;
if (envBase) {
const normalized = String(envBase).replace(/\/$/, '');
return normalized.endsWith(API_PREFIX) ? normalized : `${normalized}${API_PREFIX}`;
}
return API_PREFIX;
}
/** 对外展示的 API 根路径(curl/CI 文档),无环境变量时拼接当前浏览器域名 // Logan.Li */
export function getPublicApiBase(): string {
const base = getApiBase();
if (/^https?:\/\//i.test(base)) return base;
return `${window.location.origin}${base}`;
}
/** 拼接 REST API 完整路径(含 /api/v1 前缀) // Logan.Li */
export function buildApiUrl(path: string, params?: URLSearchParams): string {
const p = path.startsWith('/') ? path : `/${path}`;
let url = `${getApiBase()}${p}`;
const qs = params?.toString();
if (qs) url += `?${qs}`;
return url;
}
function resolveApiUrl(path: string): string {
return buildApiUrl(path);
}
/** 获取 WebSocket 根路径(不含 /api/v1) // Logan.Li */
export function getWsBase(): string {
const envBase = import.meta.env.VITE_API_BASE_URL;
if (envBase) {
const origin = String(envBase).replace(/\/$/, '').replace(/\/api\/v1$/, '');
return origin.replace(/^http/, 'ws');
}
const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
return `${proto}//${window.location.host}`;
}
export function getAuthToken(): string {
return getToken();
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
async function request<T = any>(
method: string,
url: string,
body?: Json,
params?: Record<string, string | number | boolean | undefined>,
): Promise<T> {
let fullUrl = resolveApiUrl(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;
}
const token = getAuthToken();
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) {
clearToken();
useUserStore.getState().logout();
window.location.href = '/login';
throw new Error(i18n.t('common.request.tokenExpired'));
}
let json: ApiResponse<T>;
try {
json = await response.json();
} catch {
throw new Error(`HTTP ${response.status}: ${i18n.t('common.responseParseError')}`);
}
if (!response.ok || (json.code && json.code !== 200)) {
const msg = json.message || `HTTP ${response.status}`;
throw new Error(msg);
}
return (json.data !== undefined ? json.data : json) as T;
}
export const http = {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get: <T = any>(url: string, params?: Record<string, string | number | boolean | undefined>) =>
request<T>('GET', url, undefined, params),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
post: <T = any>(url: string, body?: Json) => request<T>('POST', url, body),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
put: <T = any>(url: string, body?: Json) => request<T>('PUT', url, body),
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete: <T = any>(url: string, body?: Json) => request<T>('DELETE', url, body),
getRaw: async (url: string): Promise<string> => {
const resp = await fetch(resolveApiUrl(url), {
headers: { Authorization: 'Bearer ' + getAuthToken() },
});
if (!resp.ok) throw new Error('HTTP ' + resp.status);
return resp.text();
},
putRaw: async (url: string, body: string, contentType: string): Promise<unknown> => {
const resp = await fetch(resolveApiUrl(url), {
method: 'PUT',
headers: {
Authorization: 'Bearer ' + getAuthToken(),
'Content-Type': contentType,
},
body,
});
if (!resp.ok) throw new Error('HTTP ' + resp.status);
return resp.json();
},
download: async (url: string, filename?: string): Promise<void> => {
const resp = await fetch(resolveApiUrl(url), {
headers: { Authorization: 'Bearer ' + getAuthToken() },
});
if (!resp.ok) {
const ct = resp.headers.get('Content-Type') || '';
if (ct.includes('json')) {
try {
const json = await resp.json() as { message?: string; Message?: string };
throw new Error(json.message || json.Message || `HTTP ${resp.status}`);
} catch (e) {
if (e instanceof Error && !e.message.startsWith('HTTP ')) throw e;
}
}
throw new Error(`HTTP ${resp.status}`);
}
const blob = await resp.blob();
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
const disp = resp.headers.get('Content-Disposition') || '';
const m = disp.match(/filename="?(.+?)"?$/);
a.download = filename || (m ? m[1] : 'download');
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(a.href);
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
upload: async <T = any>(url: string, formData: FormData): Promise<T> => {
const resp = await fetch(resolveApiUrl(url), {
method: 'POST',
headers: { Authorization: 'Bearer ' + getAuthToken() },
body: formData,
});
if (!resp.ok) throw new Error('HTTP ' + resp.status);
return resp.json();
},
};
export default http;