Merge pull request #36 from snowykami/fix/30

feat: 重构侧边栏组件,合并 NavMain 和 NavUserCenter 为 NavGroup,添加动态激活状态
This commit is contained in:
2025-09-25 13:38:04 +08:00
committed by GitHub
5 changed files with 32 additions and 74 deletions

View File

@ -4,7 +4,7 @@ import {
IconInnerShadowTop,
} from "@tabler/icons-react"
import { NavMain } from "@/components/console/nav-main"
import { NavGroup } from "@/components/console/nav-group"
import { NavUser } from "@/components/console/nav-user"
import {
Sidebar,
@ -17,13 +17,16 @@ import {
} from "@/components/ui/sidebar"
import config from "@/config"
import Link from "next/link"
import { NavUserCenter } from "./nav-ucenter"
import { sidebarData } from "./data"
import { ThemeModeToggle } from "../common/theme-toggle"
import { useState } from "react"
import { useTranslations } from "next-intl"
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const [activeId, setActiveId] = useState("dashboard")
const consoleT = useTranslations("Console")
return (
<Sidebar collapsible="offcanvas" {...props}>
<SidebarHeader>
@ -42,8 +45,8 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</SidebarMenu>
</SidebarHeader>
<SidebarContent>
<NavMain items={sidebarData.navMain} />
<NavUserCenter items={sidebarData.navUserCenter} />
<NavGroup title={consoleT("general")} activeId={activeId} setActiveId={setActiveId} items={sidebarData.navMain.map((item) => ({...item, title: consoleT(item.title)}))} />
<NavGroup title={consoleT("personal")} activeId={activeId} setActiveId={setActiveId} items={sidebarData.navUserCenter.map((item) => ({...item, title: consoleT(item.title)}))} />
</SidebarContent>
<SidebarFooter>
<div className="mb-2 flex justify-center">

View File

@ -6,6 +6,7 @@ import { Folder, Gauge, MessageCircle, Newspaper, Palette, Settings, ShieldCheck
export interface SidebarItem {
id: string;
title: string;
url: string;
icon: IconType;
@ -15,36 +16,42 @@ export interface SidebarItem {
export const sidebarData: { navMain: SidebarItem[]; navUserCenter: SidebarItem[] } = {
navMain: [
{
id: "dashboard",
title: "dashboard.title",
url: consolePath.dashboard,
icon: Gauge,
permission: isAdmin
},
{
id: "post",
title: "post.title",
url: consolePath.post,
icon: Newspaper,
permission: isEditor
},
{
id: "comment",
title: "comment.title",
url: consolePath.comment,
icon: MessageCircle,
permission: isEditor
},
{
id: "file",
title: "file.title",
url: consolePath.file,
icon: Folder,
permission: () => true
},
{
id: "user",
title: "user.title",
url: consolePath.user,
icon: Users,
permission: isAdmin
},
{
id: "global",
title: "global.title",
url: consolePath.global,
icon: Settings,
@ -53,19 +60,22 @@ export const sidebarData: { navMain: SidebarItem[]; navUserCenter: SidebarItem[]
],
navUserCenter: [
{
id: "user_profile",
title: "user_profile.title",
url: consolePath.userProfile,
icon: UserPen,
permission: () => true
},
{
id: "user_security",
title: "user_security.title",
url: consolePath.userSecurity,
icon: ShieldCheck,
permission: () => true
},
{
title: "user-preference.title",
id: "user_preference",
title: "user_preference.title",
url: consolePath.userPreference,
icon: Palette,
permission: () => true

View File

@ -10,39 +10,34 @@ import {
} from "@/components/ui/sidebar"
import Link from "next/link"
import { usePathname } from "next/navigation";
import { User } from "@/models/user";
import { useAuth } from "@/contexts/auth-context";
import { IconType } from "@/types/icon";
import { useTranslations } from "next-intl";
import { consolePath } from "@/hooks/use-route";
import { SidebarItem } from "./data";
export function NavMain({
export function NavGroup({
items,
title,
activeId,
setActiveId
}: {
items: {
title: string
url: string
icon?: IconType;
permission: ({ user }: { user: User }) => boolean
}[]
items: SidebarItem[],
title: string,
activeId: string,
setActiveId? :(id: string) => void
}) {
const t = useTranslations("Console")
const { user } = useAuth();
const pathname = usePathname() ?? "/"
if (!user) return null;
return (
<SidebarGroup>
<SidebarGroupContent className="flex flex-col gap-2">
<SidebarGroupLabel>{t("general")}</SidebarGroupLabel>
<SidebarGroupLabel>{title}</SidebarGroupLabel>
<SidebarMenu>
{items.map((item) => (
item.permission({ user }) && <SidebarMenuItem key={item.title}>
<Link href={item.url}>
<SidebarMenuButton tooltip={item.title} isActive={item.url != consolePath.dashboard && pathname.startsWith(item.url) || item.url === pathname}>
<Link href={item.url} onClick={() => setActiveId && setActiveId(item.id)}>
<SidebarMenuButton tooltip={item.title} isActive={activeId === item.id}>
{item.icon && <item.icon />}
<span>{t(item.title)}</span>
<span>{item.title}</span>
</SidebarMenuButton>
</Link>
</SidebarMenuItem>

View File

@ -1,50 +0,0 @@
"use client"
import {
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar"
import { User } from "@/models/user"
import Link from "next/link"
import { usePathname } from "next/navigation"
import { useAuth } from "@/contexts/auth-context"
import { IconType } from "@/types/icon"
import { useTranslations } from "next-intl"
export function NavUserCenter({
items,
}: {
items: {
title: string
url: string
icon?: IconType;
permission: ({ user }: { user: User }) => boolean
}[]
}) {
const t = useTranslations("Console")
const { user } = useAuth();
const pathname = usePathname() ?? "/"
if (!user) return null;
return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarGroupLabel>{t("personal")}</SidebarGroupLabel>
<SidebarMenu>
{items.map((item) => (
item.permission({ user }) && <SidebarMenuItem key={item.title}>
<Link href={item.url}>
<SidebarMenuButton tooltip={item.title} isActive={pathname === item.url}>
{item.icon && <item.icon />}
<span>{t(item.title)}</span>
</SidebarMenuButton>
</Link>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroup>
)
}

View File

@ -133,7 +133,7 @@
"update_password_success": "密码已更新",
"verify_code": "验证码"
},
"user-preference": {
"user_preference": {
"title": "个性化"
}
},