refactor(frontend): P1-P2 架构统一 — utils/hooks/api 模块化
P1: - utils/storage.ts、theme.ts、validate.ts + index 统一出口 - request/store/useTheme 接入 storage;清零 confirm/alert - PipelineManager、TerminalAuditTab 拆分子模块(<700行红线) P2: - api/monitor、api/deploy 子目录化 - hooks/api 扩展:servers/pipelines/alerts/audit/sessionCommands - PipelineManager、AuditOperationsTab 接入 TanStack Query
This commit is contained in:
1 parent
d19a03d427
commit
f45e203e21
196 files changed
+3228
-2018
No files matched your search
@@ -1,4 +1,4 @@
|
||||
// 通用工具出口 — 对齐 Vue 后台惯例(request / export 等) // Logan.Li
|
||||
// 通用工具出口 — 对齐 Vue 后台惯例 // Logan.Li
|
||||
|
||||
export {
|
||||
http,
|
||||
@@ -11,4 +11,36 @@ export {
|
||||
type ListResponse,
|
||||
} from './request';
|
||||
|
||||
export { exportAsCSV, exportAsJSON } from './export';
|
||||
export { exportAsCSV, exportAsJSON } from './export';
|
||||
|
||||
export {
|
||||
STORAGE_KEYS,
|
||||
getItem,
|
||||
setItem,
|
||||
removeItem,
|
||||
getToken,
|
||||
setToken,
|
||||
clearToken,
|
||||
} from './storage';
|
||||
|
||||
export {
|
||||
type Theme,
|
||||
type ThemeOption,
|
||||
THEME_OPTIONS,
|
||||
THEME_ORDER,
|
||||
DEFAULT_THEME,
|
||||
migrateLegacyTheme,
|
||||
getInitialTheme,
|
||||
persistTheme,
|
||||
applyThemeToDocument,
|
||||
getNextTheme,
|
||||
} from './theme';
|
||||
|
||||
export {
|
||||
isNotEmpty,
|
||||
isValidPort,
|
||||
isValidIPv4,
|
||||
isValidHostname,
|
||||
isValidUrl,
|
||||
parsePortList,
|
||||
} from './validate';
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useUserStore } from '@/store';
|
||||
import { getToken, clearToken } from './storage';
|
||||
|
||||
const API_PREFIX = '/api/v1';
|
||||
|
||||
@@ -52,7 +53,7 @@ export function getWsBase(): string {
|
||||
}
|
||||
|
||||
export function getAuthToken(): string {
|
||||
return useUserStore.getState().token || localStorage.getItem('token') || '';
|
||||
return getToken();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@@ -89,6 +90,7 @@ async function request<T = any>(
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
clearToken();
|
||||
useUserStore.getState().logout();
|
||||
window.location.href = '/login';
|
||||
throw new Error('登录已过期,请重新登录');
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// 本地存储封装 — token / 通用 KV // Logan.Li
|
||||
|
||||
import { useUserStore } from '@/store';
|
||||
|
||||
export const STORAGE_KEYS = {
|
||||
token: 'token',
|
||||
theme: 'aiops-theme',
|
||||
user: 'user-storage',
|
||||
} as const;
|
||||
|
||||
export function getItem(key: string): string | null {
|
||||
if (typeof window === 'undefined') return null;
|
||||
try {
|
||||
return localStorage.getItem(key);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function setItem(key: string, value: string): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
localStorage.setItem(key, value);
|
||||
} catch {
|
||||
/* ignore quota errors */
|
||||
}
|
||||
}
|
||||
|
||||
export function removeItem(key: string): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
localStorage.removeItem(key);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
/** 读取 JWT — zustand 优先,localStorage 兜底 // Logan.Li */
|
||||
export function getToken(): string {
|
||||
return useUserStore.getState().token || getItem(STORAGE_KEYS.token) || '';
|
||||
}
|
||||
|
||||
export function setToken(token: string): void {
|
||||
setItem(STORAGE_KEYS.token, token);
|
||||
useUserStore.getState().setToken(token);
|
||||
}
|
||||
|
||||
export function clearToken(): void {
|
||||
removeItem(STORAGE_KEYS.token);
|
||||
useUserStore.getState().setToken(null);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// 主题常量与持久化 — 供 useTheme / ThemeProvider 使用 // Logan.Li
|
||||
|
||||
import { getItem, setItem, STORAGE_KEYS } from './storage';
|
||||
|
||||
export type Theme =
|
||||
| 'ocean-deep'
|
||||
| 'light-professional'
|
||||
| 'midnight-purple'
|
||||
| 'pine-green'
|
||||
| 'orange-vitality';
|
||||
|
||||
export interface ThemeOption {
|
||||
id: Theme;
|
||||
name: string;
|
||||
description: string;
|
||||
isDark: boolean;
|
||||
primaryColor: string;
|
||||
}
|
||||
|
||||
export const THEME_OPTIONS: ThemeOption[] = [
|
||||
{ id: 'ocean-deep', name: '深海蓝', description: '专业稳重的深蓝色调', isDark: true, primaryColor: '#3b82f6' },
|
||||
{ id: 'light-professional', name: '明亮白', description: '清爽干净的浅色主题', isDark: false, primaryColor: '#2563eb' },
|
||||
{ id: 'midnight-purple', name: '午夜紫', description: '神秘优雅的紫色调', isDark: true, primaryColor: '#a855f7' },
|
||||
{ id: 'pine-green', name: '松林绿', description: '自然清新的绿色主题', isDark: true, primaryColor: '#22c55e' },
|
||||
{ id: 'orange-vitality', name: '活力橙', description: '温暖活力的橙色主题', isDark: true, primaryColor: '#f97316' },
|
||||
];
|
||||
|
||||
export const THEME_ORDER: Theme[] = THEME_OPTIONS.map((o) => o.id);
|
||||
export const DEFAULT_THEME: Theme = 'ocean-deep';
|
||||
|
||||
/** 兼容旧版 dark/light 主题名 // Logan.Li */
|
||||
export function migrateLegacyTheme(value: string | null): Theme | null {
|
||||
if (value === 'dark') return 'ocean-deep';
|
||||
if (value === 'light') return 'light-professional';
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getInitialTheme(): Theme {
|
||||
if (typeof window === 'undefined') return DEFAULT_THEME;
|
||||
const stored = getItem(STORAGE_KEYS.theme);
|
||||
const migrated = migrateLegacyTheme(stored);
|
||||
if (migrated) {
|
||||
setItem(STORAGE_KEYS.theme, migrated);
|
||||
return migrated;
|
||||
}
|
||||
if (stored && THEME_ORDER.includes(stored as Theme)) {
|
||||
return stored as Theme;
|
||||
}
|
||||
return DEFAULT_THEME;
|
||||
}
|
||||
|
||||
export function persistTheme(theme: Theme): void {
|
||||
setItem(STORAGE_KEYS.theme, theme);
|
||||
}
|
||||
|
||||
export function applyThemeToDocument(theme: Theme): void {
|
||||
if (typeof document === 'undefined') return;
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}
|
||||
|
||||
export function getNextTheme(current: Theme): Theme {
|
||||
const idx = THEME_ORDER.indexOf(current);
|
||||
return THEME_ORDER[(idx + 1) % THEME_ORDER.length];
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// 通用表单校验 // Logan.Li
|
||||
|
||||
export function isNotEmpty(value: unknown): boolean {
|
||||
if (value === null || value === undefined) return false;
|
||||
return String(value).trim().length > 0;
|
||||
}
|
||||
|
||||
export function isValidPort(port: unknown): boolean {
|
||||
const n = Number(port);
|
||||
return Number.isInteger(n) && n > 0 && n <= 65535;
|
||||
}
|
||||
|
||||
export function isValidIPv4(ip: string): boolean {
|
||||
const parts = ip.trim().split('.');
|
||||
if (parts.length !== 4) return false;
|
||||
return parts.every((p) => {
|
||||
const n = Number(p);
|
||||
return Number.isInteger(n) && n >= 0 && n <= 255 && String(n) === p;
|
||||
});
|
||||
}
|
||||
|
||||
export function isValidHostname(host: string): boolean {
|
||||
const h = host.trim();
|
||||
if (!h) return false;
|
||||
return /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(h);
|
||||
}
|
||||
|
||||
export function isValidUrl(url: string): boolean {
|
||||
try {
|
||||
const u = new URL(url);
|
||||
return u.protocol === 'http:' || u.protocol === 'https:';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function parsePortList(text: string): number[] {
|
||||
return text
|
||||
.split(',')
|
||||
.map((s) => parseInt(s.trim(), 10))
|
||||
.filter((n) => isValidPort(n));
|
||||
}
|
||||
Reference in new issue
Block a user