pmc 55ca2cbdaf
All checks were successful
Build and Deploy LTY / build-and-deploy (push) Successful in 47m12s
feat: update card views/serializers and admin sidebar
- Add new card API endpoints and serializers
- Update sidebar navigation
- Update claude settings permissions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 11:35:11 +08:00

194 lines
6.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState, useEffect } from "react"
import Link from "next/link"
import { usePathname, useRouter } from "next/navigation"
import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button"
import { logout } from "@/lib/api/auth"
import { hasPermission, getUserRole, type PermissionModule } from "@/lib/permissions"
import {
Brain,
Music,
Shirt,
Gift,
Home,
Utensils,
User,
Settings,
BarChart,
LogOut,
Lock,
Sparkles,
Heart,
Footprints,
Trophy,
type LucideIcon,
} from "lucide-react"
interface MenuItem {
label: string
href: string
icon: LucideIcon
module: PermissionModule
}
// AI 管理
const aiMenuItems: MenuItem[] = [
{ label: "大模型管理", href: "/ai-model", icon: Brain, module: "ai-model" },
]
// 内容管理
const contentMenuItems: MenuItem[] = [
{ label: "服装管理", href: "/outfits", icon: Shirt, module: "outfits" },
{ label: "道具管理", href: "/props", icon: Gift, module: "props" },
{ label: "家居装饰管理", href: "/home-decor", icon: Home, module: "home-decor" },
{ label: "歌曲管理", href: "/songs", icon: Music, module: "songs" },
{ label: "舞蹈管理", href: "/dances", icon: Footprints, module: "dances" },
{ label: "食物管理", href: "/food", icon: Utensils, module: "food" },
{ label: "成就管理", href: "/achievements", icon: Trophy, module: "achievements" },
{ label: "好感度系统", href: "/affinity", icon: Heart, module: "affinity" },
]
// 系统管理
const systemMenuItems: MenuItem[] = [
{ label: "用户管理", href: "/users", icon: User, module: "users" },
{ label: "权限管理", href: "/permissions", icon: Lock, module: "permissions" },
{ label: "系统设置", href: "/settings", icon: Settings, module: "settings" },
]
function NavButton({ item, pathname }: { item: MenuItem; pathname: string }) {
const isActive = pathname === item.href
const Icon = item.icon
return (
<Button
variant={isActive ? "default" : "ghost"}
className={cn(
"w-full justify-start",
isActive
? "bg-gradient-to-r from-pink-500 to-purple-600 text-white hover:from-pink-600 hover:to-purple-700"
: "hover:bg-gray-100",
)}
asChild
>
<Link href={item.href}>
<Icon className="mr-2 h-4 w-4" />
{item.label}
</Link>
</Button>
)
}
export function Sidebar() {
const pathname = usePathname()
const router = useRouter()
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
const handleLogout = async () => {
try {
await logout()
router.push("/login")
} catch (error) {
console.error("退出登录失败:", error)
}
}
// 根据权限过滤菜单项(挂载后才读 localStorage
const visibleAiItems = mounted ? aiMenuItems.filter((item) => hasPermission(item.module)) : []
const visibleContentItems = mounted ? contentMenuItems.filter((item) => hasPermission(item.module)) : []
const visibleSystemItems = mounted ? systemMenuItems.filter((item) => hasPermission(item.module)) : []
const role = mounted ? getUserRole() : ""
return (
<div className="flex h-screen border-r bg-gradient-to-b from-white to-gray-50 shadow-md">
<div className="flex w-full flex-col p-4">
<div className="flex h-16 items-center px-4 py-2 shrink-0">
<div className="flex items-center gap-2">
<div className="h-8 w-8 rounded-full bg-gradient-to-br from-pink-500 to-purple-600 flex items-center justify-center">
<Sparkles className="h-4 w-4 text-white" />
</div>
<div>
<h2 className="text-xl font-bold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-pink-600 to-purple-600">
</h2>
<p className="text-xs text-gray-400">{role}</p>
</div>
</div>
</div>
<div className="space-y-1.5 flex-1 overflow-y-auto mt-4">
{/* 仪表盘 - 所有角色都可见 */}
<Button
variant={pathname === "/" ? "default" : "ghost"}
className={cn(
"w-full justify-start",
pathname === "/"
? "bg-gradient-to-r from-pink-500 to-purple-600 text-white hover:from-pink-600 hover:to-purple-700"
: "hover:bg-gray-100",
)}
asChild
>
<Link href="/">
<BarChart className="mr-2 h-4 w-4" />
</Link>
</Button>
{/* AI 管理 */}
{visibleAiItems.length > 0 && (
<>
<div className="pt-4 pb-2">
<p className="px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider">AI </p>
</div>
{visibleAiItems.map((item) => (
<NavButton key={item.href} item={item} pathname={pathname} />
))}
</>
)}
{/* 内容管理 */}
{visibleContentItems.length > 0 && (
<>
<div className="pt-4 pb-2">
<p className="px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider"></p>
</div>
{visibleContentItems.map((item) => (
<NavButton key={item.href} item={item} pathname={pathname} />
))}
</>
)}
{/* 系统管理 */}
{visibleSystemItems.length > 0 && (
<>
<div className="pt-4 pb-2">
<p className="px-4 text-xs font-semibold text-gray-500 uppercase tracking-wider"></p>
</div>
{visibleSystemItems.map((item) => (
<NavButton key={item.href} item={item} pathname={pathname} />
))}
</>
)}
</div>
<div className="shrink-0 pt-4">
<Button
variant="ghost"
className="w-full justify-start hover:bg-red-50 hover:text-red-600 transition-colors"
onClick={handleLogout}
>
<LogOut className="mr-2 h-4 w-4" />
退
</Button>
</div>
</div>
</div>
)
}