- Update food, outfits, props, home-decor pages and components - Add permissions page and sidebar updates - Update API client and all API modules (auth, food, dances, etc.) - Add card model migrations for optional fields - Update Django views, serializers, and authentication - Add affinity level migrations and user app updates - Add project documentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
194 lines
6.2 KiB
TypeScript
194 lines
6.2 KiB
TypeScript
"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 space-y-4 p-4">
|
||
<div className="flex h-16 items-center px-4 py-2">
|
||
<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">
|
||
{/* 仪表盘 - 所有角色都可见 */}
|
||
<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="mt-auto 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>
|
||
)
|
||
}
|