// API base derives from Vite's BASE_URL (/ping/) so it matches the nginx prefix. const API_BASE = `${import.meta.env.BASE_URL}api`; const TOKEN_KEY = "opengoods_admin_token"; export function getToken(): string | null { return localStorage.getItem(TOKEN_KEY); } export function setToken(token: string) { localStorage.setItem(TOKEN_KEY, token); } export function clearToken() { localStorage.removeItem(TOKEN_KEY); } export class ApiError extends Error { status: number; constructor(status: number, message: string) { super(message); this.status = status; } } async function request(path: string, options: RequestInit = {}): Promise { const headers: Record = { "Content-Type": "application/json", ...(options.headers as Record), }; const token = getToken(); if (token) headers.Authorization = `Bearer ${token}`; const res = await fetch(`${API_BASE}${path}`, { ...options, headers }); if (res.status === 401) { clearToken(); throw new ApiError(401, "登录已过期,请重新登录"); } const text = await res.text(); const data = text ? JSON.parse(text) : null; if (!res.ok) { const msg = data?.error?.message || `请求失败 (${res.status})`; throw new ApiError(res.status, msg); } return data as T; } export const api = { login: (username: string, password: string) => request<{ token: string; username: string }>("/login", { method: "POST", body: JSON.stringify({ username, password }), }), me: () => request<{ username: string }>("/me"), listProducts: (q: string, page: number, size: number) => request<{ items: import("./types").ProductRow[]; page: number; size: number; total: number; completeness_fields: string[]; }>(`/products?q=${encodeURIComponent(q)}&page=${page}&size=${size}`), getProduct: (id: string) => request(`/products/${id}`), updateProduct: (id: string, body: unknown) => request(`/products/${id}`, { method: "PUT", body: JSON.stringify(body), }), listAudit: (id: string) => request<{ items: import("./types").AuditEntry[] }>(`/products/${id}/audit`), addImage: (id: string, url: string, kind: string) => request(`/products/${id}/images`, { method: "POST", body: JSON.stringify({ url, kind }), }), deleteImage: (id: string, imageId: string) => request<{ status: string }>(`/products/${id}/images/${imageId}`, { method: "DELETE", }), addMsrp: (id: string, body: unknown) => request(`/products/${id}/msrp`, { method: "POST", body: JSON.stringify(body), }), deleteMsrp: (id: string, msrpId: string) => request<{ status: string }>(`/products/${id}/msrp/${msrpId}`, { method: "DELETE", }), listBrands: () => request<{ items: import("./types").Brand[] }>("/brands"), listCategories: () => request<{ items: import("./types").Category[] }>("/categories"), listSubmissions: (status: string, page: number, size: number) => request<{ items: import("./types").SubmissionRow[]; page: number; size: number; total: number; pending: number; }>(`/submissions?status=${encodeURIComponent(status)}&page=${page}&size=${size}`), getSubmission: (id: string) => request(`/submissions/${id}`), approveSubmission: (id: string) => request(`/submissions/${id}/approve`, { method: "POST", }), rejectSubmission: (id: string, note: string) => request<{ status: string }>(`/submissions/${id}/reject`, { method: "POST", body: JSON.stringify({ note }), }), };