初始提交:OCI 面板前端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:29 +08:00
commit db45a669e3
135 changed files with 25220 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
import { useAuthStore } from '@/stores/auth'
const BASE = '/api/v1'
export class ApiError extends Error {
status: number
constructor(status: number, message: string) {
super(message)
this.status = status
}
}
interface RequestOptions {
method?: string
body?: unknown
query?: Record<string, string | number | boolean | undefined>
}
function buildUrl(path: string, query?: RequestOptions['query']): string {
if (!query) return BASE + path
const qs = new URLSearchParams()
for (const [k, v] of Object.entries(query)) {
if (v !== undefined && v !== '') qs.set(k, String(v))
}
const s = qs.toString()
return s ? `${BASE}${path}?${s}` : BASE + path
}
async function parseError(resp: Response): Promise<never> {
let message = `请求失败(${resp.status}`
try {
const body = (await resp.json()) as { error?: string; hint?: string }
if (body.error) message = body.hint ? `${body.hint}${body.error}` : body.error
} catch {
/* 非 JSON 响应体,保留默认消息 */
}
if (resp.status === 401) useAuthStore().logout()
// 全局限速(429)整页拦截;登录接口的 429 是账号锁定,留在表单内提示
if (resp.status === 429 && !resp.url.includes('/auth/login') && location.pathname !== '/blocked') {
location.assign('/blocked')
}
throw new ApiError(resp.status, message)
}
/** 统一请求封装:注入 JWT、401 登出、错误转 ApiError */
export async function request<T>(path: string, opts: RequestOptions = {}): Promise<T> {
const auth = useAuthStore()
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
if (auth.token) headers.Authorization = `Bearer ${auth.token}`
const resp = await fetch(buildUrl(path, opts.query), {
method: opts.method ?? 'GET',
headers,
body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
})
if (!resp.ok) await parseError(resp)
if (resp.status === 204) return undefined as T
return (await resp.json()) as T
}
export const mockOn = import.meta.env.VITE_MOCK === '1'
/** mock 数据延时返回,模拟网络延迟 */
export function mocked<T>(data: T, ms = 200): Promise<T> {
return new Promise((resolve) => setTimeout(() => resolve(structuredClone(data)), ms))
}