refactor(layout, i18n, admin): 优化布局结构与多语言支持

调整 AdminShell 组件的子组件顺序,提升代码可读性。更新 admin-breadcrumb 组件,简化导航标签翻译逻辑,确保多语言支持的一致性。重构 admin-language-switcher 组件,优化语言切换的用户体验,增强界面交互性。更新多语言配置,新增登录界面的副标题,提升用户体验。
This commit is contained in:
2026-05-30 17:46:27 +08:00
parent 36117144dc
commit a550c418e5
64 changed files with 3405 additions and 1378 deletions

View File

@@ -2,40 +2,86 @@
import { useRouter } from "next/navigation";
import { useEffect, useState, type ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { AdminAuthCheckingScreen } from "@/components/admin/admin-auth-checking";
import { verifyStoredAdminSession } from "@/lib/admin-session-verify";
import { useAdminSessionStore } from "@/stores/admin-session";
import { readToken } from "@/stores/admin-token";
type ShellAuthGateProps = {
children: ReactNode;
};
type GateStatus = "pending" | "authed" | "guest";
function hasAdminToken(bearerToken: string | null): boolean {
const token = bearerToken ?? readToken();
return token != null && token.trim() !== "";
}
/**
* Shell route guard. Reads the auth token from localStorage on the client and
* redirects to the login page when no token is present.
* Shell 路由守卫:无 Token 或 `/auth/me` 校验失败时跳转登录页。
*/
export function ShellAuthGate({ children }: ShellAuthGateProps) {
const { t } = useTranslation("common");
const router = useRouter();
const [allowed, setAllowed] = useState(false);
const bearerToken = useAdminSessionStore((s) => s.bearerToken);
const [status, setStatus] = useState<GateStatus>("pending");
const setShellAuthPending = useAdminSessionStore((s) => s.setShellAuthPending);
useEffect(() => {
const token = readToken();
if (!token) {
router.replace("/admin/login");
return;
}
queueMicrotask(() => {
setAllowed(true);
});
}, [router]);
let cancelled = false;
setShellAuthPending(true);
if (!allowed) {
return (
<div className="flex min-h-[50vh] w-full flex-1 items-center justify-center text-sm text-muted-foreground">
{t("auth.checking", { defaultValue: "Checking sign-in status…" })}
</div>
);
async function run() {
if (!hasAdminToken(bearerToken)) {
if (!cancelled) {
setStatus("guest");
}
return;
}
if (!cancelled) {
setStatus("pending");
}
const ok = await verifyStoredAdminSession();
if (cancelled) {
return;
}
if (ok) {
setStatus("authed");
return;
}
setStatus("guest");
}
void run().finally(() => {
if (!cancelled) {
setShellAuthPending(false);
}
});
return () => {
cancelled = true;
setShellAuthPending(false);
};
}, [bearerToken, setShellAuthPending]);
useEffect(() => {
if (status === "guest") {
router.replace("/admin/login");
}
}, [status, router]);
if (status === "pending") {
return <AdminAuthCheckingScreen variant="shell" />;
}
if (status === "guest") {
return null;
}
return children;