feat: 云端优先架构 — 桌面/Web/PWA 全走服务器 API

This commit is contained in:
root
2026-10-16 17:34:26 +08:00
parent 629c7e4073
commit aea94de9e5
22 changed files with 472 additions and 445 deletions
+1 -2
View File
@@ -5,10 +5,9 @@ import { Header } from './Header';
import { ToastProvider } from '@/components/ui';
import { LoginPage } from '@/components/auth/LoginPage';
import { getToken } from '@/lib/api';
import { isTauriRef } from '@/lib/api';
export function AppShell() {
const [authed, setAuthed] = useState(() => isTauriRef() || !!getToken());
const [authed, setAuthed] = useState(() => !!getToken());
const [navOpen, setNavOpen] = useState(false);
useEffect(() => {
+25 -53
View File
@@ -1,8 +1,8 @@
import { useState } from 'react';
import { NavLink } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { CalendarDays, Rows3, ChartColumn, GraduationCap, Shapes, SlidersHorizontal, LogIn, ShieldCheck } from 'lucide-react';
import { syncStatus, serverMe, clearToken, isTauriRef, getToken } from '@/lib/api';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { CalendarDays, Rows3, ChartColumn, GraduationCap, Shapes, SlidersHorizontal, LogIn, ShieldCheck, Cloud } from 'lucide-react';
import { serverMe, clearToken, getToken } from '@/lib/api';
import { LoginDialog } from '@/components/auth/LoginDialog';
import { cn } from '@/lib/utils';
@@ -36,21 +36,16 @@ function LogoMark() {
);
}
function baseUrl() {
return isTauriRef() ? (localStorage.getItem('fp-server-url') || '') : '';
}
export function Sidebar({ open, onClose }: { open: boolean; onClose: () => void }) {
const isWeb = !isTauriRef();
const sync = useQuery({ queryKey: ['sync-status'], queryFn: syncStatus, refetchInterval: 15000 });
const qc = useQueryClient();
const meQ = useQuery({
queryKey: ['me'],
queryFn: () => serverMe(baseUrl()),
queryFn: () => serverMe(),
enabled: !!getToken(),
retry: 0
});
const me = meQ.data ?? null;
const [loginOpen, setLoginOpen] = useState(false);
const me = meQ.data ?? null;
return (
<aside
@@ -67,7 +62,7 @@ export function Sidebar({ open, onClose }: { open: boolean; onClose: () => void
<div className="text-[10px] font-medium tracking-[0.18em] text-ink-3">FOOTPRINTS</div>
</div>
</div>
<LoginDialog open={loginOpen} onClose={() => setLoginOpen(false)} defaultUrl={localStorage.getItem('fp-server-url') || 'https://footpr.linyux.cn'} />
<LoginDialog open={loginOpen} onClose={() => setLoginOpen(false)} defaultUrl={'https://footpr.linyux.cn'} />
<nav className="flex-1 overflow-y-auto px-2 py-3">
{NAV.map(group => (
<div key={group.section} className="mb-4">
@@ -97,41 +92,28 @@ export function Sidebar({ open, onClose }: { open: boolean; onClose: () => void
</nav>
<div className="border-t border-line px-4 py-3">
{/* 左下角:账号区 */}
{/* 左下角账号区 */}{getToken() ? (
<div className="mb-2">
{me && (
<div className="flex items-center justify-between gap-2">
{/* 左下角:账号区(云端模式) */}
{getToken() && me ? (
<div className="flex flex-col gap-1">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-1.5 min-w-0">
<Cloud size={13} className="shrink-0 text-success" />
<div className="min-w-0 truncate text-[12px] font-medium text-ink-1" title={me.username}>
{me.is_admin && <span className="mr-1 text-brand-text">管理员</span>}
{me.is_admin && <span className="mr-0.5 text-brand-text">管理员</span>}
{me.username}
</div>
<button
className="shrink-0 text-[11px] text-ink-3 underline transition-colors hover:text-danger"
onClick={() => {
clearToken();
localStorage.removeItem('fp-server-url');
window.location.reload();
}}
>
退出
</button>
</div>
)}
{!isWeb && (
<button
className="flex w-full items-center gap-1.5 rounded-md px-1 py-1 text-[12px] text-ink-2 transition-colors hover:bg-surface-3 hover:text-ink-1"
onClick={() => setLoginOpen(true)}
className="shrink-0 text-[11px] text-ink-3 underline transition-colors hover:text-danger"
onClick={() => { clearToken(); qc.clear(); window.location.reload(); }}
>
<LogIn size={13} />
{me ? "账号与同步设置" : "登录我的档案"}
退出
</button>
)}
{!isWeb && (
<div className="num text-[10px] text-ink-3">
{sync.data ? ("同步 · 待推送 " + sync.data.pending_ops) : "…"}
</div>
)}
</div>
<div className="flex items-center gap-1 text-[10px] text-success">
<Cloud size={10} />
云端同步已开启
</div>
</div>
) : (
<div className="flex flex-col gap-1.5">
@@ -140,22 +122,12 @@ export function Sidebar({ open, onClose }: { open: boolean; onClose: () => void
onClick={() => setLoginOpen(true)}
>
<LogIn size={13} />
我的档案 · 登录
登录账号
</button>
<div className="text-[10px] text-ink-3">未登录=本地模式 · 登录后各端互通</div>
</div>
)}
{!isWeb && (
<div className="mt-1.5 flex items-center gap-1 text-[11px] text-ink-3">
<ChevronsLeftSvg />
数据库 · 本地存储
<div className="text-[10px] text-ink-3">登录后数据云端互通</div>
</div>
)}
</div>
</aside>
);
}
function ChevronsLeftSvg() {
return <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M11 17l-5-5 5-5" /><path d="M18 17l-5-5 5-5" /></svg>
}
}