diff --git a/tone-page-web/app/console/(with-menu)/layout.tsx b/tone-page-web/app/console/(with-menu)/layout.tsx index 431eb97..e02b8eb 100644 --- a/tone-page-web/app/console/(with-menu)/layout.tsx +++ b/tone-page-web/app/console/(with-menu)/layout.tsx @@ -25,29 +25,52 @@ export default function ConsoleMenuLayout({ const router = useRouter(); + + const getInitialData = () => { + if (!window || !window.localStorage) return null; + const cache = localStorage.getItem(USER_ME_CACHE_KEY); + if (!cache) return; + try { + const user = JSON.parse(cache); + if (!user || !user.userId) throw new Error(); + return user; + } catch (error) { + localStorage.removeItem(USER_ME_CACHE_KEY); + } + return undefined; + } + + const USER_ME_CACHE_KEY = 'user-me-cache'; const { data: user, isLoading, error } = useSWR( '/api/user/me', - () => UserApi.me(), + async () => { + const data = await UserApi.me(); + localStorage.setItem(USER_ME_CACHE_KEY, JSON.stringify(data)); + return data; + }, { onError: (error) => { if (error.statusCode === 401) { localStorage.removeItem('token'); + localStorage.removeItem(USER_ME_CACHE_KEY); toast.info('登录凭证已失效,请重新登录'); router.replace('/console/login'); } - } + }, + fallbackData: getInitialData(), + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, } ); if (!isLoading && !error && !user) { router.replace('/console/login'); localStorage.removeItem('token'); + localStorage.removeItem(USER_ME_CACHE_KEY); toast.error('账户状态异常,请重新登录'); } - - - return ( diff --git a/tone-page-web/components/app-sidebar.tsx b/tone-page-web/components/app-sidebar.tsx index ef3d4b7..547816f 100644 --- a/tone-page-web/components/app-sidebar.tsx +++ b/tone-page-web/components/app-sidebar.tsx @@ -25,81 +25,85 @@ import { } from "@/components/ui/sidebar" import Link from "next/link" import { User } from "@/lib/types/user" -// This is sample data. -const data = { - user: { - name: "shadcn", - email: "m@example.com", - avatar: "/avatars/shadcn.jpg", - }, - navMain: [ - { - title: "网站管理", - url: "/console/web", - icon: SquareTerminal, - items: [ - { - title: "资源", - url: "/console/web/resource", - }, - { - title: "博客", - url: "/console/web/blog", - }, - ], - }, - { - title: "用户管理", - url: "/console/user/list", - icon: UsersRound, - }, - { - title: "邮件系统", - url: "/console/mail", - icon: Mail, - items: [ - { - title: "收件箱", - url: "/console/mail/inbox", - }, - { - title: "已发送", - url: "/console/mail/sent", - }, - { - title: "发送邮件", - url: "/console/mail/send", - }, - { - title: "邮件管理", - url: "/console/mail/manage" - } - ], - }, - { - title: "文件存储", - url: "/console/storage", - icon: CloudUpload, - }, - { - title: "虚拟云空间", - url: "/console/vspace", - icon: Inbox, - }, - { - title: "虚拟主机", - url: "/console/vserver", - icon: Server, - }, - { - title: "前往首页", - url: "/", - icon: Undo2, - }, - ], -} +import { Role } from "@/lib/types/role" export function AppSidebar({ user, isUserLoading, ...props }: React.ComponentProps & { user: User | undefined, isUserLoading: boolean }) { + const data = { + user: { + name: "shadcn", + email: "m@example.com", + avatar: "/avatars/shadcn.jpg", + }, + navMain: [ + { + title: "网站管理", + url: "/console/web", + icon: SquareTerminal, + isHidden: !user?.roles.includes(Role.Admin), + items: [ + { + title: "资源", + url: "/console/web/resource", + }, + { + title: "博客", + url: "/console/web/blog", + }, + ], + }, + { + title: "用户管理", + url: "/console/user/list", + icon: UsersRound, + isHidden: !user?.roles.includes(Role.Admin), + }, + { + title: "邮件系统", + url: "/console/mail", + icon: Mail, + items: [ + { + title: "收件箱", + url: "/console/mail/inbox", + }, + { + title: "已发送", + url: "/console/mail/sent", + }, + { + title: "发送邮件", + url: "/console/mail/send", + }, + { + title: "邮件管理", + url: "/console/mail/manage", + isHidden: !user?.roles.includes(Role.Admin), + }, + ], + }, + { + title: "文件存储", + url: "/console/storage", + icon: CloudUpload, + }, + { + title: "虚拟云空间", + url: "/console/vspace", + icon: Inbox, + }, + { + title: "虚拟主机", + url: "/console/vserver", + icon: Server, + }, + { + title: "前往首页", + url: "/", + icon: Undo2, + }, + ], + } + return ( diff --git a/tone-page-web/components/nav-main.tsx b/tone-page-web/components/nav-main.tsx index dd5e19d..2e19281 100644 --- a/tone-page-web/components/nav-main.tsx +++ b/tone-page-web/components/nav-main.tsx @@ -29,9 +29,11 @@ export function NavMain({ url: string icon?: LucideIcon isActive?: boolean + isHidden?: boolean items?: { title: string url: string + isHidden?: boolean }[] }[] }) { @@ -39,7 +41,7 @@ export function NavMain({ 菜单 - {items.map((item) => ( + {items.filter(i => !i.isHidden).map((item) => ( (item.items && item.items.length > 0) ? ( - {item.items?.map((subItem) => ( + {item.items.filter(i => !i.isHidden).map((subItem) => ( diff --git a/tone-page-web/lib/types/role.ts b/tone-page-web/lib/types/role.ts new file mode 100644 index 0000000..a286eb4 --- /dev/null +++ b/tone-page-web/lib/types/role.ts @@ -0,0 +1,3 @@ +export enum Role { + Admin = 'admin', +} \ No newline at end of file diff --git a/tone-page-web/lib/types/user.ts b/tone-page-web/lib/types/user.ts index 16c56e5..cfab4f1 100644 --- a/tone-page-web/lib/types/user.ts +++ b/tone-page-web/lib/types/user.ts @@ -1,3 +1,5 @@ +import { Role } from "./role"; + export interface User { userId: string; username: string; @@ -8,4 +10,5 @@ export interface User { createdAt: string; updatedAt: string; deletedAt: string | null; + roles: Role[]; } \ No newline at end of file