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

@@ -0,0 +1,128 @@
"use client";
import Link from "next/link";
import type { ReactElement } from "react";
import { useTranslation } from "react-i18next";
import { ArrowRight, Clock, Ticket } from "lucide-react";
import { buttonVariants } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { useAdminDateTimeFormatter } from "@/hooks/use-admin-datetime-formatter";
import { cn } from "@/lib/utils";
import type { DrawCurrentSnapshot } from "@/types/api/public-draw";
function isOpenLikeStatus(status: string): boolean {
const lower = status.toLowerCase();
return lower.includes("open") || lower.includes("sale");
}
type DashboardCurrentDrawCardProps = {
hall: DrawCurrentSnapshot | null;
drawId: number | null;
loading?: boolean;
};
export function DashboardCurrentDrawCard({
hall,
drawId,
loading = false,
}: DashboardCurrentDrawCardProps): ReactElement {
const { t } = useTranslation("dashboard");
const formatDt = useAdminDateTimeFormatter();
if (loading) {
return (
<Card className="admin-list-card overflow-hidden py-0">
<CardContent className="p-5">
<Skeleton className="h-[4.5rem] w-full rounded-xl" />
</CardContent>
</Card>
);
}
if (!hall) {
return (
<Card className="admin-list-card overflow-hidden py-0">
<CardContent className="flex min-h-[5.5rem] flex-col items-center justify-center gap-2 p-6 text-center">
<Ticket className="size-9 text-muted-foreground/40" aria-hidden />
<p className="text-sm font-medium text-muted-foreground">{t("sections.currentDraw")}</p>
<p className="text-xs text-muted-foreground">{t("states.noData", { ns: "common" })}</p>
</CardContent>
</Card>
);
}
const openLike = isOpenLikeStatus(hall.status);
return (
<Card className="admin-list-card overflow-hidden border-primary/15 py-0 shadow-sm">
<CardContent className="relative p-0">
<div
aria-hidden
className="pointer-events-none absolute inset-0 bg-gradient-to-r from-primary/[0.07] via-transparent to-transparent"
/>
<div className="relative flex flex-col gap-4 p-5 sm:flex-row sm:items-center sm:justify-between sm:gap-6">
<div className="flex min-w-0 items-center gap-4">
<div
className="flex size-12 shrink-0 items-center justify-center rounded-2xl bg-primary text-primary-foreground shadow-sm"
aria-hidden
>
<Ticket className="size-5" />
</div>
<div className="min-w-0">
<p className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
{t("sections.currentDraw")}
</p>
<p className="mt-1 font-mono text-2xl font-bold leading-none tracking-tight text-foreground sm:text-[1.65rem]">
{hall.draw_no}
</p>
<div className="mt-2.5 flex flex-wrap items-center gap-2">
<span className="text-xs text-muted-foreground">
{t("drawSequence", { sequence: hall.sequence_no ?? "—" })}
</span>
<span
className={cn(
"inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium",
openLike
? "bg-emerald-500/12 text-emerald-700 ring-1 ring-emerald-500/20 dark:text-emerald-400"
: "bg-muted/80 text-muted-foreground ring-1 ring-border/60",
)}
>
<span
className={cn(
"size-1.5 rounded-full",
openLike ? "bg-emerald-500" : "bg-muted-foreground/70",
)}
/>
{hall.status}
</span>
</div>
</div>
</div>
<div className="flex flex-wrap items-center gap-3 sm:justify-end">
{hall.draw_time ? (
<p className="inline-flex items-center gap-1.5 rounded-lg bg-muted/40 px-3 py-2 text-xs text-muted-foreground ring-1 ring-border/50">
<Clock className="size-3.5 shrink-0" aria-hidden />
<span>{t("scheduledDrawTime", { time: formatDt(hall.draw_time) })}</span>
</p>
) : null}
{drawId != null ? (
<Link
href={`/admin/draws/${drawId}/finance`}
className={cn(
buttonVariants({ variant: "default", size: "sm" }),
"h-9 gap-1.5 px-4 shadow-sm",
)}
>
{t("drawFinanceDetails")}
<ArrowRight className="size-3.5" aria-hidden />
</Link>
) : null}
</div>
</div>
</CardContent>
</Card>
);
}