Skip to content

Commit d6a69af

Browse files
authored
Merge pull request #8 from DevilGenius/dev
feat(AccountsPage): 优化布局
2 parents 8a29240 + 45968dd commit d6a69af

18 files changed

Lines changed: 918 additions & 497 deletions

File tree

backend/internal/app/account/service.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ func (s *Service) ToggleScheduling(ctx context.Context, id int) (ToggleResult, e
358358
} else {
359359
newState = "disabled"
360360
if s.stateWriter != nil {
361-
if err := s.stateWriter.ManualDisable(ctx, id, "管理员手动关闭调度"); err != nil {
361+
if err := s.stateWriter.ManualDisable(ctx, id, "手动关闭"); err != nil {
362362
logger.Error("account_manual_disable_failed",
363363
sdk.LogFieldAccountID, id, sdk.LogFieldError, err)
364364
return ToggleResult{}, err

deploy/dev.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ function Start-Dev {
447447

448448
$frontend = Start-Process `
449449
-FilePath "pwsh" `
450-
-ArgumentList @("-NoLogo", "-NoProfile", "-Command", "pnpm dev -- --port $FrontendDevPort --strictPort") `
450+
-ArgumentList @("-NoLogo", "-NoProfile", "-Command", "pnpm dev --port $FrontendDevPort --strictPort") `
451451
-WorkingDirectory $WebDir `
452452
-WindowStyle Hidden `
453453
-RedirectStandardOutput $FrontendOut `

web/pnpm-workspace.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
allowBuilds:
2+
esbuild: true

web/src/app/layout/AppShell.tsx

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { useTheme } from '../providers/ThemeProvider';
1313
import { useSiteSettings, defaultLogoUrl } from '../providers/SiteSettingsProvider';
1414
import { effectiveDocUrl } from '../../shared/utils/docUrl';
1515
import { useIsMobile } from '../../shared/hooks/useMediaQuery';
16+
import { usePersistentBoolean } from '../../shared/hooks/usePersistentBoolean';
1617
import { TopLoadingLine } from '../../shared/components/PageLoading';
1718
import {
1819
LayoutDashboard,
@@ -77,6 +78,8 @@ const apiKeyMenuItems: MenuItem[] = [
7778
{ path: '/usage', labelKey: 'nav.my_usage', icon: <ReceiptText className="h-5 w-5" />, sectionKey: 'nav.personal' },
7879
];
7980

81+
const SIDEBAR_COLLAPSED_STORAGE_KEY = 'airgate:sidebar:collapsed';
82+
8083
/**
8184
* 拉取插件菜单:所有登录用户均可调用 /plugins/menu,再按 page.audience 过滤显示。
8285
* audience = "admin"(或空,向后兼容)— 仅管理员可见,挂在「插件」分组
@@ -158,7 +161,7 @@ export function AppShell({ children }: AppShellProps) {
158161
const { t, i18n } = useTranslation();
159162
const { theme, toggleTheme } = useTheme();
160163
const site = useSiteSettings();
161-
const [collapsed, setCollapsed] = useState(false);
164+
const [collapsed, setCollapsed] = usePersistentBoolean(SIDEBAR_COLLAPSED_STORAGE_KEY, false);
162165
const [mobileOpen, setMobileOpen] = useState(false);
163166
const isMobile = useIsMobile();
164167
const matchRoute = useMatchRoute();
@@ -377,21 +380,20 @@ export function AppShell({ children }: AppShellProps) {
377380
<div
378381
className="fixed inset-0 z-40 bg-black/40"
379382
onClick={() => setMobileOpen(false)}
380-
style={{ animation: 'ag-fade-in 0.15s ease-out' }}
381383
/>
382384
)}
383385

384386
{/* Sidebar */}
385387
{isMobile ? (
386388
<aside
387-
className="fixed inset-y-0 left-0 z-50 flex flex-col bg-surface border-r border-border transition-transform duration-300 ease-in-out"
389+
className="fixed inset-y-0 left-0 z-50 flex flex-col bg-surface border-r border-border transition-transform duration-150 ease-out"
388390
style={{ width: 'var(--ag-sidebar-width)', transform: mobileOpen ? 'translateX(0)' : 'translateX(-100%)' }}
389391
>
390392
{sidebarContent}
391393
</aside>
392394
) : (
393395
<aside
394-
className="relative flex flex-col border-r border-border bg-surface transition-all duration-300 ease-in-out"
396+
className="relative flex flex-col border-r border-border bg-surface transition-[width] duration-150 ease-out"
395397
style={{ width: collapsed ? 'var(--ag-sidebar-collapsed)' : 'var(--ag-sidebar-width)' }}
396398
>
397399
{sidebarContent}

web/src/app/providers/AuthProvider.tsx

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
1+
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode } from 'react';
22
import type { UserResp } from '../../shared/types';
33
import {
44
setToken,
@@ -40,36 +40,57 @@ function normalizeSessionUser(user: UserResp, token = getToken()): UserResp {
4040
export function AuthProvider({ children }: { children: ReactNode }) {
4141
const [user, setUser] = useState<UserResp | null>(null);
4242
const [loading, setLoading] = useState(true);
43+
const authRevisionRef = useRef(0);
4344

4445
useEffect(() => {
46+
let cancelled = false;
47+
const revision = authRevisionRef.current;
4548
const token = getToken();
4649
if (token) {
4750
usersApi.me()
4851
.then((userData) => {
49-
if (getToken() === token) setUser(normalizeSessionUser(userData, token));
52+
const currentToken = getToken();
53+
if (!cancelled && authRevisionRef.current === revision && currentToken) {
54+
setUser(normalizeSessionUser(userData, currentToken));
55+
}
5056
})
5157
.catch(() => {
52-
if (getToken() === token) setToken(null);
58+
if (!cancelled && authRevisionRef.current === revision && getToken() === token) {
59+
setToken(null);
60+
setUser(null);
61+
}
5362
})
54-
.finally(() => setLoading(false));
63+
.finally(() => {
64+
if (!cancelled && authRevisionRef.current === revision) setLoading(false);
65+
});
5566
} else {
5667
setLoading(false);
5768
}
69+
70+
return () => {
71+
cancelled = true;
72+
};
5873
}, []);
5974

6075
const login = useCallback((token: string, userData: UserResp) => {
76+
authRevisionRef.current += 1;
77+
const revision = authRevisionRef.current;
6178
setToken(token);
6279
setUser(normalizeSessionUser(userData, token));
6380
// 登录响应可能不包含全部用户字段(例如 API Key 登录时缺少 quota / expires_at),
6481
// 异步用 /me 拉一次完整数据补齐,避免首屏额度等信息显示不准。
6582
usersApi.me()
6683
.then((freshUser) => {
67-
if (getToken() === token) setUser(normalizeSessionUser(freshUser, token));
84+
const currentToken = getToken();
85+
if (authRevisionRef.current === revision && currentToken) {
86+
setUser(normalizeSessionUser(freshUser, currentToken));
87+
}
6888
})
6989
.catch(() => {});
7090
}, []);
7191

7292
const logout = useCallback(() => {
93+
authRevisionRef.current += 1;
7394
setToken(null);
7495
setSessionAPIKey(null);
7596
setUser(null);

web/src/app/router.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,8 @@ const authLayout = createRoute({
247247
});
248248

249249
function HomePage() {
250-
const { user, isAPIKeySession } = useAuth();
250+
const { user, loading, isAPIKeySession } = useAuth();
251+
if (loading) return <PageLoading />;
251252
if (!user) return null;
252253

253254
const isAdmin = getTokenRole() === 'admin' || user.role === 'admin';

0 commit comments

Comments
 (0)