当前位置: 首页 > news >正文

Next.js 实战 (二):搭建 Layouts 基础排版布局

前言

等了许久,Next.js 终于迎来了 v15.x 版本,刚好 Github 上面的旧项目重构完,终于可以放心大胆地去研究 Next.js了。

搭建最新项目可以参考官方文档:Installation

最新的 Next.js 版本,使用的是 React19.x 内测版本,可能存在与其他库不兼容的情况

项目开发规范配置

这块内容我都懒得写了,具体的可以参考我之前写的文章,配置大同小异:

  1. Nuxt3 实战 (二):配置 Eslint、Prettierrc、Husky等项目提交规范
  2. Nuxt3 实战 (三):使用 release-it 自动管理版本号和生成 CHANGELOG

UI 组件库的选择

  1. NextUI:我个人是比较喜欢 NextUI 的,这个库的 UI 设计比较符合我的审美,而且我之前的项目 今日热榜 中用的就是这个,感觉还不错,但我仔细看了下,它缺少了一个很重要的组件:Form表单,这个会给后面频繁的 CURD 表单操作带来麻烦,所以放弃了
  2. Ant-Design:Ant-Design 是我再熟悉不过的组件库了,公司的业务用的就是这个,但这个库还是有点偏业务风格,而且目前和 Next.js 的兼容性还存在点问题,自己也有点审美疲劳了,也放弃了。
  3. shadcn/ui:最终选择了这个,这个库是基于 tailwindcss 的,而且目前在市场上很受欢迎,Github 也保持不错的增长,而且是你想用什么组件,就把组件源码直接放到应用程序中的,值得推荐。

layout 排版布局

我们先搞定最常规的布局,shadcn/ui 的 构建块 中有一些常规的布局,我一下就看重这个:
在这里插入图片描述

  1. 左侧是 slibar,菜单顶部可以放 Logo 和标题
  2. 右侧顶部放用户头像和一些操作按钮,比如:面包屑暗黑模式全屏消息通知
  3. 中间就是业务模块,底部放版权信息

业务代码

  1. 新增 src/components/AppSideBar/index.tsx 文件:
'use client';import Image from 'next/image';
import * as React from 'react';import NavMain from '@/components/NavMain';
import NavUser from '@/components/NavUser';
import {Sidebar,SidebarContent,SidebarFooter,SidebarHeader,SidebarMenu,SidebarMenuButton,SidebarMenuItem,
} from '@/components/ui/sidebar';const data = {user: {name: '谢明伟',email: 'baiwumm@foxmail.com',avatar: 'logo.svg',},
};export default function AppSideBar({ ...props }: React.ComponentProps<typeof Sidebar>) {return (<Sidebar collapsible="icon" {...props}><SidebarHeader><SidebarMenu><SidebarMenuItem><SidebarMenuButton size="lg" asChild><div className="flex items-center gap-2 cursor-pointer"><Image src="/logo.svg" width={40} height={40} alt="logo" /><span className="truncate font-semibold">{process.env.NEXT_PUBLIC_PROJECT_NAME}</span></div></SidebarMenuButton></SidebarMenuItem></SidebarMenu></SidebarHeader><SidebarContent><NavMain /></SidebarContent><SidebarFooter><NavUser user={data.user} /></SidebarFooter></Sidebar>);
}
  1. 新增 src/components/NavMain/index.tsx 文件:
'use client';import { map } from 'lodash-es';
import { ChevronRight } from 'lucide-react';
import { usePathname, useRouter } from 'next/navigation';
import { useTranslations } from 'next-intl';
import { useState } from 'react';import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import {SidebarGroup,SidebarMenu,SidebarMenuButton,SidebarMenuItem,SidebarMenuSub,SidebarMenuSubButton,SidebarMenuSubItem,
} from '@/components/ui/sidebar';
import MenuList from '@/constants/MenuList';export default function NavMain() {const t = useTranslations('Route');// 路由跳转const router = useRouter();// 当前激活的菜单const pathname = usePathname();const [activeKey, setActiveKey] = useState(pathname);// 点击菜单回调const handleMenuClick = (path: string, redirect = '') => {if (redirect) {return;}router.push(path);setActiveKey(path);};return (<SidebarGroup><SidebarMenu>{map(MenuList, ({ path, icon, name, redirect, children = [] }) => (<Collapsible key={path} asChild defaultOpen={activeKey === path} className="group/collapsible"><SidebarMenuItem><CollapsibleTrigger asChild><SidebarMenuButtontooltip={t(name)}isActive={activeKey === path}onClick={() => handleMenuClick(path, redirect)}>{icon}<span>{t(name)}</span>{children?.length ? (<ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />) : null}</SidebarMenuButton></CollapsibleTrigger><CollapsibleContent><SidebarMenuSub>{map(children, (subItem) => (<SidebarMenuSubItem key={subItem.path}><SidebarMenuSubButton asChild onClick={() => handleMenuClick(subItem.path, subItem.redirect)}><a onClick={() => handleMenuClick(path, redirect)} className="cursor-pointer">{subItem.icon}<span>{t(subItem.name)}</span></a></SidebarMenuSubButton></SidebarMenuSubItem>))}</SidebarMenuSub></CollapsibleContent></SidebarMenuItem></Collapsible>))}</SidebarMenu></SidebarGroup>);
}
  1. 新增 src/components/NavUser/index.tsx 文件:
'use client';import { ChevronsUpDown, IdCard, LogOut } from 'lucide-react';import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import {DropdownMenu,DropdownMenuContent,DropdownMenuGroup,DropdownMenuItem,DropdownMenuLabel,DropdownMenuSeparator,DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from '@/components/ui/sidebar';export default function NavUser({user,
}: {user: {name: string;email: string;avatar: string;};
}) {const { isMobile } = useSidebar();return (<SidebarMenu><SidebarMenuItem><DropdownMenu><DropdownMenuTrigger asChild><SidebarMenuButtonsize="lg"className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"><Avatar className="h-8 w-8 rounded-lg"><AvatarImage src={`/${user.avatar}`} alt={user.name} /><AvatarFallback className="rounded-lg">CN</AvatarFallback></Avatar><div className="grid flex-1 text-left text-sm leading-tight"><span className="truncate font-semibold">{user.name}</span><span className="truncate text-xs">{user.email}</span></div><ChevronsUpDown className="ml-auto size-4" /></SidebarMenuButton></DropdownMenuTrigger><DropdownMenuContentclassName="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"side={isMobile ? 'bottom' : 'right'}align="end"sideOffset={4}><DropdownMenuLabel className="p-0 font-normal"><div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm"><Avatar className="h-8 w-8 rounded-lg"><AvatarImage src={`/${user.avatar}`} alt={user.name} /><AvatarFallback className="rounded-lg">CN</AvatarFallback></Avatar><div className="grid flex-1 text-left text-sm leading-tight"><span className="truncate font-semibold">{user.name}</span><span className="truncate text-xs">{user.email}</span></div></div></DropdownMenuLabel><DropdownMenuSeparator /><DropdownMenuGroup><DropdownMenuItem><IdCard />个人中心</DropdownMenuItem></DropdownMenuGroup><DropdownMenuSeparator /><DropdownMenuItem><LogOut />退出登录</DropdownMenuItem></DropdownMenuContent></DropdownMenu></SidebarMenuItem></SidebarMenu>);
}
  1. 新增 src/components/GlobalHeader/index.ts 文件:
'use client';import { compact, map } from 'lodash-es';
import { usePathname } from 'next/navigation';
import { useTranslations } from 'next-intl';
import { Fragment } from 'react';import LangSwitch from '@/components/LangSwitch';
import ThemeModeButton from '@/components/ThemeModeButton';
import {Breadcrumb,BreadcrumbItem,BreadcrumbList,BreadcrumbPage,BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import { Separator } from '@/components/ui/separator';
import { SidebarTrigger } from '@/components/ui/sidebar';export default function GlobalHeader() {const t = useTranslations('Route');const pathname = usePathname();const splitPath = compact(pathname.split('/'));return (<header className="flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12 border-b justify-between px-4 sticky top-0"><div className="flex items-center gap-2"><SidebarTrigger className="-ml-1" /><Separator orientation="vertical" className="mr-2 h-4" /><Breadcrumb><BreadcrumbList>{map(splitPath, (path, index) => (<Fragment key={path}><BreadcrumbItem><BreadcrumbPage>{t(path)}</BreadcrumbPage></BreadcrumbItem>{index < splitPath.length - 1 ? <BreadcrumbSeparator className="hidden md:block" /> : null}</Fragment>))}</BreadcrumbList></Breadcrumb></div><div className="flex gap-2"><ThemeModeButton /><LangSwitch /></div></header>);
}
  1. App/layout.tsx 文件:
import AppSideBar from '@/components/AppSideBar';
import GlobalHeader from '@/components/GlobalHeader';
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (<html suppressHydrationWarning><body><SidebarProvider><AppSideBar /><SidebarInset>{/* 头部布局 */}<GlobalHeader /><main className="p-4">{children}</main></SidebarInset></SidebarProvider></body></html>
);
}

最终效果

在这里插入图片描述

万事开头难,后续我们就可以在此基础上新增功能、主题配置等,比如:侧边栏宽度主题色头部是否固定

Github 仓库:next-admin

如果你也正在学习 Next.js ,关注我,我也刚起步,与我在互联网中共同进步!


http://www.mrgr.cn/news/79639.html

相关文章:

  • vue2 vue3 无限滚动
  • 池化在深度学习中增强特征的作用
  • HarmonyOS(65) ArkUI FrameNode详解
  • PCL点云环境错误收集(不断新增)
  • Windows下Docker Desktop+k8s安装和部署程序
  • OpenGL ES详解——多个纹理实现混叠显示
  • 组件上传图片不回显问题
  • 前端 —— Git
  • MVC基础——市场管理系统(二)
  • PCB设计规范
  • Centos7和9安装mysql5.7和mysql8.0详细教程(超详细)
  • Qt C++ 显示多级结构体,包括结构体名、变量名和值
  • TEA系列例题
  • 如何高效的向AI大模型提问? - 提示工程Prompt Engineering
  • 【考前预习】1.计算机网络概述
  • 深度学习实验十四 循环神经网络(1)——测试简单循环网络的记忆能力和梯度爆炸实验
  • 深入了解架构中常见的4种缓存模式及其实现
  • 在VMWare上安装openEuler 22.03-LTS
  • Mysql索引原理及优化——岁月云实战笔记
  • 嵌入式开发 - 工具记录
  • 【mysql】数据库存量数据双主实现
  • 北京大学《操作系统原理》课堂笔记(一)
  • LLM - 多模态大模型的开源评估工具 VLMEvalKit 部署与测试 教程
  • leetcode-54.螺旋矩阵-day1
  • Adobe Premiere Pro 2024 [24.6.1]
  • 2022 年“泰迪杯”数据分析技能赛A 题竞赛作品的自动评判