feat: 增加角色管理与奖池配置迁移,优化管理端权限与样式

This commit is contained in:
2026-05-19 14:40:04 +08:00
parent d625c59393
commit a1fb163f1b
45 changed files with 1080 additions and 518 deletions

View File

@@ -17,7 +17,7 @@ export function AdminShell({ children }: { children: ReactNode }) {
<SidebarProvider defaultOpen>
<AdminAppSidebar />
<SidebarInset className="max-md:overflow-x-hidden">
<header className="sticky top-0 z-30 flex min-h-14 items-center gap-3 border-b border-border bg-background/80 pl-4 pr-4 py-2 backdrop-blur-md">
<header className="sticky top-0 z-30 flex h-14 shrink-0 items-center gap-3 border-b border-border bg-card/90 px-4 shadow-[0_1px_0_rgb(216_230_251_/_45%)] backdrop-blur-md">
<SidebarTrigger />
<Separator orientation="vertical" className="mr-1.5 h-4" />
<AdminBreadcrumb />
@@ -25,7 +25,7 @@ export function AdminShell({ children }: { children: ReactNode }) {
<ShellToolbar />
</div>
</header>
<div className="flex flex-1 flex-col px-6 pt-4 pb-6 md:px-8 md:pt-4 md:pb-8">
<div className="flex flex-1 flex-col px-4 pt-4 pb-6 md:px-5 md:pt-4 md:pb-6">
{children}
</div>
</SidebarInset>

View File

@@ -3,7 +3,6 @@
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useMemo } from "react";
import { SparklesIcon } from "lucide-react";
import { useTranslation } from "react-i18next";
import {
@@ -39,28 +38,38 @@ export function AdminAppSidebar() {
const visibleNav = useMemo(() => profile?.navigation ?? [], [profile?.navigation]);
return (
<Sidebar collapsible="icon" variant="inset">
<SidebarHeader className="border-b border-sidebar-border px-2 py-1.5">
<SidebarMenu>
<SidebarMenuItem>
<Sidebar collapsible="icon" className="overflow-hidden">
<SidebarHeader className="flex h-14 shrink-0 items-center gap-0 border-b border-sidebar-border p-0 px-2">
<SidebarMenu className="h-full w-full">
<SidebarMenuItem className="h-full">
<SidebarMenuButton
render={<Link href={ADMIN_BASE} />}
className="gap-2 px-0 hover:bg-transparent"
className="h-full min-h-0 justify-start px-1 py-0 hover:bg-transparent group-data-[collapsible=icon]:justify-center"
>
<SparklesIcon data-icon="inline-start" aria-hidden />
<div className="flex flex-col items-start gap-0 group-data-[collapsible=icon]:hidden">
<span className="font-semibold tracking-tight text-sidebar-foreground">
{t("app.title", { ns: "common" })}
</span>
<span className="text-[11px] leading-tight text-sidebar-foreground/70">
Lottery Admin
</span>
<div className="flex h-12 w-full items-center group-data-[collapsible=icon]:size-10 group-data-[collapsible=icon]:justify-center">
<img
src="/logo.png"
alt="N lotto"
className="h-auto max-h-11 w-full object-contain object-left group-data-[collapsible=icon]:max-h-8 group-data-[collapsible=icon]:object-center"
/>
</div>
</SidebarMenuButton>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<SidebarContent className="relative overflow-hidden">
<div
className="pointer-events-none absolute inset-x-0 bottom-0 h-[22rem] opacity-55 group-data-[collapsible=icon]:hidden"
aria-hidden
>
<img
src="/image6.png"
alt=""
className="h-full w-full object-cover object-bottom"
/>
<div className="absolute inset-x-0 top-0 h-28 bg-linear-to-b from-sidebar to-transparent" />
<div className="absolute inset-0 bg-sidebar/20" />
</div>
<SidebarGroup>
<SidebarGroupLabel>{t("sidebar.workspace", { ns: "common", defaultValue: "Workspace" })}</SidebarGroupLabel>
<SidebarGroupContent>
@@ -73,6 +82,7 @@ export function AdminAppSidebar() {
tooltip={t(`nav.${item.segment}`, { ns: "common", defaultValue: item.label })}
isActive={isActive(pathname, item)}
render={<Link href={item.href} />}
className="font-medium text-sidebar-foreground/90 hover:text-sidebar-accent-foreground data-active:bg-red-600 data-active:text-white data-active:shadow-sm"
>
<Icon data-icon="inline-start" aria-hidden />
<span>{t(`nav.${item.segment}`, { ns: "common", defaultValue: item.label })}</span>

View File

@@ -9,5 +9,5 @@ type ModuleScaffoldProps = {
/** 内容区容器;模块标题由侧栏导航体现,此处不再重复大标题与说明。 */
export function ModuleScaffold({ children, className }: ModuleScaffoldProps) {
return <div className={cn("mx-auto max-w-5xl", className)}>{children}</div>;
return <div className={cn("mx-auto w-full max-w-none", className)}>{children}</div>;
}