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

View File

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

View File

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