加上登录和角色权限,并用 Vue 控制台区分用户与管理员。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-10 22:43:31 +08:00
parent 78601aae5e
commit 8b55b9cf55
36 changed files with 2947 additions and 1007 deletions
+45
View File
@@ -0,0 +1,45 @@
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;
}
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 "";
}