const KEYS = ["token", "role", "wallet", "username"]; export function session() { return { token: localStorage.getItem("token") || "", role: localStorage.getItem("role") || "", wallet: localStorage.getItem("wallet") || "", username: localStorage.getItem("username") || "", }; } export function saveSession(payload) { localStorage.setItem("token", payload.access_token); localStorage.setItem("role", payload.role); localStorage.setItem("wallet", payload.wallet_address); localStorage.setItem("username", payload.username || ""); } export function clearSession() { KEYS.forEach((key) => localStorage.removeItem(key)); } export async function api(path, options = {}) { const headers = { ...(options.headers || {}) }; if (options.body && !headers["Content-Type"]) headers["Content-Type"] = "application/json"; const token = localStorage.getItem("token"); if (token) headers.Authorization = `Bearer ${token}`; const response = await fetch(`/api/v1${path}`, { ...options, headers }); const text = await response.text(); const data = text ? JSON.parse(text) : null; if (response.status === 401) { clearSession(); if (location.pathname !== "/login") location.assign("/login"); throw new Error(messageOf(data) || "登录已失效"); } if (!response.ok) throw new Error(messageOf(data) || "请求失败"); return data; } export async function download(path, filename) { const headers = {}; const token = localStorage.getItem("token"); if (token) headers.Authorization = `Bearer ${token}`; const response = await fetch(`/api/v1${path}`, { headers }); if (response.status === 401) { clearSession(); if (location.pathname !== "/login") location.assign("/login"); throw new Error("登录已失效"); } if (!response.ok) { const text = await response.text(); const data = text ? JSON.parse(text) : null; throw new Error(messageOf(data) || "下载失败"); } const blob = await response.blob(); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = filename; link.click(); URL.revokeObjectURL(url); } function messageOf(data) { if (!data) return ""; if (typeof data.detail === "string") return data.detail; if (Array.isArray(data.detail)) return data.detail.map((item) => item.msg).filter(Boolean).join(";"); return ""; }