// ============================================================ // ZCLAW SaaS Admin — 类型化 HTTP 客户端 // ============================================================ import { getToken, logout } from './auth' import type { AccountPublic, ApiError, ConfigItem, CreateTokenRequest, DashboardStats, LoginRequest, LoginResponse, Model, OperationLog, PaginatedResponse, Provider, RelayTask, TokenInfo, UsageByModel, UsageRecord, } from './types' // ── 错误类 ──────────────────────────────────────────────── export class ApiRequestError extends Error { constructor( public status: number, public body: ApiError, ) { super(body.message || `Request failed with status ${status}`) this.name = 'ApiRequestError' } } // ── 基础请求 ────────────────────────────────────────────── const BASE_URL = process.env.NEXT_PUBLIC_SAAS_API_URL || 'http://localhost:8080' async function request( method: string, path: string, body?: unknown, ): Promise { const token = getToken() const headers: Record = { 'Content-Type': 'application/json', } if (token) { headers['Authorization'] = `Bearer ${token}` } const res = await fetch(`${BASE_URL}${path}`, { method, headers, body: body ? JSON.stringify(body) : undefined, }) if (res.status === 401) { logout() if (typeof window !== 'undefined') { window.location.href = '/login' } throw new ApiRequestError(401, { error: 'unauthorized', message: '登录已过期,请重新登录' }) } if (!res.ok) { let errorBody: ApiError try { errorBody = await res.json() } catch { errorBody = { error: 'unknown', message: `请求失败 (${res.status})` } } throw new ApiRequestError(res.status, errorBody) } // 204 No Content if (res.status === 204) { return undefined as T } return res.json() as Promise } // ── API 客户端 ──────────────────────────────────────────── export const api = { // ── 认证 ────────────────────────────────────────────── auth: { async login(data: LoginRequest): Promise { return request('POST', '/api/auth/login', data) }, async register(data: { username: string password: string email: string display_name?: string }): Promise { return request('POST', '/api/auth/register', data) }, async me(): Promise { return request('GET', '/api/auth/me') }, }, // ── 账号管理 ────────────────────────────────────────── accounts: { async list(params?: { page?: number page_size?: number search?: string role?: string status?: string }): Promise> { const qs = buildQueryString(params) return request>('GET', `/api/accounts${qs}`) }, async get(id: string): Promise { return request('GET', `/api/accounts/${id}`) }, async update( id: string, data: Partial>, ): Promise { return request('PATCH', `/api/accounts/${id}`, data) }, async updateStatus( id: string, data: { status: AccountPublic['status'] }, ): Promise { return request('PATCH', `/api/accounts/${id}/status`, data) }, }, // ── 服务商管理 ──────────────────────────────────────── providers: { async list(params?: { page?: number page_size?: number }): Promise> { const qs = buildQueryString(params) return request>('GET', `/api/providers${qs}`) }, async create(data: Partial>): Promise { return request('POST', '/api/providers', data) }, async update( id: string, data: Partial>, ): Promise { return request('PATCH', `/api/providers/${id}`, data) }, async delete(id: string): Promise { return request('DELETE', `/api/providers/${id}`) }, }, // ── 模型管理 ────────────────────────────────────────── models: { async list(params?: { page?: number page_size?: number provider_id?: string }): Promise> { const qs = buildQueryString(params) return request>('GET', `/api/models${qs}`) }, async create(data: Partial>): Promise { return request('POST', '/api/models', data) }, async update(id: string, data: Partial>): Promise { return request('PATCH', `/api/models/${id}`, data) }, async delete(id: string): Promise { return request('DELETE', `/api/models/${id}`) }, }, // ── API 密钥 ────────────────────────────────────────── tokens: { async list(params?: { page?: number page_size?: number }): Promise> { const qs = buildQueryString(params) return request>('GET', `/api/tokens${qs}`) }, async create(data: CreateTokenRequest): Promise { return request('POST', '/api/tokens', data) }, async revoke(id: string): Promise { return request('DELETE', `/api/tokens/${id}`) }, }, // ── 用量统计 ────────────────────────────────────────── usage: { async daily(params?: { days?: number }): Promise { const qs = buildQueryString(params) return request('GET', `/api/usage/daily${qs}`) }, async byModel(params?: { days?: number }): Promise { const qs = buildQueryString(params) return request('GET', `/api/usage/by-model${qs}`) }, }, // ── 中转任务 ────────────────────────────────────────── relay: { async list(params?: { page?: number page_size?: number status?: string }): Promise> { const qs = buildQueryString(params) return request>('GET', `/api/relay/tasks${qs}`) }, async get(id: string): Promise { return request('GET', `/api/relay/tasks/${id}`) }, }, // ── 系统配置 ────────────────────────────────────────── config: { async list(params?: { category?: string }): Promise { const qs = buildQueryString(params) return request('GET', `/api/config${qs}`) }, async update(id: string, data: { value: string | number | boolean }): Promise { return request('PATCH', `/api/config/${id}`, data) }, }, // ── 操作日志 ────────────────────────────────────────── logs: { async list(params?: { page?: number page_size?: number action?: string }): Promise> { const qs = buildQueryString(params) return request>('GET', `/api/logs${qs}`) }, }, // ── 仪表盘 ──────────────────────────────────────────── stats: { async dashboard(): Promise { return request('GET', '/api/stats/dashboard') }, }, } // ── 工具函数 ────────────────────────────────────────────── function buildQueryString(params?: Record): string { if (!params) return '' const entries = Object.entries(params).filter( ([, v]) => v !== undefined && v !== null && v !== '', ) if (entries.length === 0) return '' const qs = entries .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`) .join('&') return `?${qs}` }