feat: 登录持久化修复 + 左下角账号区 + 管理页面(管理员/邀请码)

This commit is contained in:
root
2026-10-16 16:02:39 +08:00
parent 1c78fa77a0
commit 1156f97c66
19 changed files with 616 additions and 332 deletions
+34 -9
View File
@@ -5,6 +5,7 @@ import type {
} from './types';
const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window;
export function isTauriRef() { return isTauri; }
const TOKEN_KEY = 'fp-token';
export function getToken() { return localStorage.getItem(TOKEN_KEY); }
@@ -14,10 +15,10 @@ export const isLocalMode = () => isTauri || !getToken();
export interface ServerSession { token: string; user_id: string; device_id: string; device_name: string; }
export async function serverLogin(username: string, password: string): Promise<ServerSession> {
const res = await fetch('/auth/login', {
export async function serverLogin(username: string, password: string, baseUrl = ''): Promise<ServerSession> {
const res = await fetch(baseUrl + '/auth/login', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password, device_name: 'Web 浏览器' })
body: JSON.stringify({ username, password, device_name: isTauri ? '桌面应用' : 'Web 浏览器' })
});
const json = await res.json();
if (!json.ok) throw new Error(json.error || '登录失败');
@@ -25,10 +26,10 @@ export async function serverLogin(username: string, password: string): Promise<S
return json.data;
}
export async function serverRegister(username: string, password: string, invite_code: string): Promise<ServerSession> {
const res = await fetch('/auth/register', {
export async function serverRegister(username: string, password: string, invite_code: string, baseUrl = ''): Promise<ServerSession> {
const res = await fetch(baseUrl + '/auth/register', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password, invite_code, device_name: 'Web 浏览器' })
body: JSON.stringify({ username, password, invite_code, device_name: isTauri ? '桌面应用' : 'Web 浏览器' })
});
const json = await res.json();
if (!json.ok) throw new Error(json.error || '注册失败');
@@ -36,9 +37,9 @@ export async function serverRegister(username: string, password: string, invite_
return json.data;
}
export interface MeInfo { user_id: string; username: string; device_id: string; }
export async function serverMe(): Promise<MeInfo> {
const res = await fetch('/auth/me', { headers: { Authorization: 'Bearer ' + getToken() } });
export interface MeInfo { user_id: string; username: string; is_admin?: boolean; device_id: string; }
export async function serverMe(baseUrl = ''): Promise<MeInfo> {
const res = await fetch(baseUrl + '/auth/me', { headers: { Authorization: 'Bearer ' + getToken() } });
const json = await res.json();
if (!json.ok) throw new Error(json.error || '会话失效');
return json.data;
@@ -125,3 +126,27 @@ export const viewList = () => call<{ items: SavedView[] }>('view_list');
export const viewUpsert = (p: { id?: number; name: string; filter: Record<string, unknown>; sort?: Record<string, unknown>; is_default?: boolean }) =>
call<{ item: SavedView }>('view_upsert', p);
export const viewDelete = (id: number) => call<{ ok: true }>('view_delete', { id });
// ---- 管理端(is_admin) ----
export async function adminListInvites(baseUrl = '') {
const res = await fetch(baseUrl + '/admin/invites', { headers: { Authorization: 'Bearer ' + getToken() } });
return res.json();
}
export async function adminCreateInvites(count: number, baseUrl = '') {
const res = await fetch(baseUrl + '/admin/invites/generate', {
method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + getToken() },
body: JSON.stringify({ count })
});
return res.json();
}
export async function adminRevokeInvite(id: number, baseUrl = '') {
const res = await fetch(baseUrl + '/admin/invites/revoke', {
method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + getToken() },
body: JSON.stringify({ id })
});
return res.json();
}
export async function adminListUsers(baseUrl = '') {
const res = await fetch(baseUrl + '/admin/users', { headers: { Authorization: 'Bearer ' + getToken() } });
return res.json();
}