【Next】4. 全局通用布局快速搭建

笔记来源:编程导航

基础布局

Next.js 支持全局根布局(每个页面都会生效)以及嵌套布局(可以只对部分页面生效),详情可 参考文档

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在 src 下新建 layouts 目录,用于存放项目中的各种布局。在该目录下新建一个布局 BasicLayout, 是一个文件夹,包括 index.tsx 页面和 index.css 样式文件。

可以直接使用 Ant Design Procomponents 的布局组件 快速实现包含导航栏、内容、底部栏的响应式布局。

在 app 目录下的 layout.tsx 全局布局文件(可以理解为页面入口)中引入 BasicLayout:

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="zh">
      <body>
        <AntdRegistry>
          <BasicLayout>{children}</BasicLayout>
        </AntdRegistry>
      </body>
    </html>
  );
}

可以将 lang=“en” 改为 lang=“zh”,适配国内用户访问。

然后按需精简和修改 BasicLayout 中复制来的布局代码,直到项目可以正常运行并符合预期。

整个过程中,需要注意下面这些事项:

1)BasicLayout 和 layout.tsx 页面开头添加 “use client” 声明,表示客户端渲染

2)移除 useState(useState 只能在 客户端使用,否则会出现水合报错)、将获取 pathname 改为使用 Next.js 的 usePathname 钩子获取

3)移除无用代码,比如 token、siderMenuType

4)定义布局的 children 属性:

interface Props {
  children: React.ReactNode;
}

export default function BasicLayout({ children }: Props) {
  // ...
}

5)修改菜单渲染函数:

// 菜单渲染
menuItemRender={(item, dom) => (
  <Link href={item.path || "/"} target={item.target}
    {dom}
  </Link>
)}

6)移除 window 对象的使用,解决服务端和客户端水合不一致的问题

全局底部栏

在 src 下新建 components 目录,表示全局公用组件。

创建全局底部栏 GlobalFooter,通常用于展示版权信息,然后在 ProLayout 中展示:

footerRender={() => <GlobalFooter />

需要在 BasicLayout 的样式文件中补充底部内边距,否则有些内容可能会被底部栏遮挡住。示例样式如下:

.ant-pro-layout .ant-pro-layout-content {
  padding-bottom: 96px !important;
}

全局顶部导航栏

可以直接利用 Ant Design Procomponents 的 ProLayout 组件实现,不用自己编写。

将 ProLayout 的 layout 属性设置为 top,可开启顶部导航栏:

<ProLayout
  layout="top"
/>

ProLayout 将顶部导航栏从左到右分为几个区:标题区、菜单区、操作区、头像区。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1)标题区:用于展示网站图标和标题。该渲染函数有 logo 和 title 参数,可以在 ProLayout 中添加对应的属性,以展示网站图标和标题。

2)菜单区:用于展示导航栏的菜单,供用户切换页面。

3)操作区:可用于配置右侧的操作栏,比如搜索条、小按钮等。移动端可以不展示操作。

4)头像区:用于展示登录用户头像、用户昵称,鼠标悬浮上去还可以展示更多用户有关的操作按钮。

export default function BasicLayout({ children }: Props) {
  const pathname = usePathname();
  const loginUser = useSelector((state: RootState) => state.loginUser);
  return (
    <div
      id="basicLayout"
      style={{
        height: "100vh",
        overflow: "auto",
      }}
    >
      <ProLayout
        title="面面 - 专为面试而生"
        layout="top"
        logo={
          <Image
            src={"/assets/logo.png"}
            height={32}
            width={32}
            alt={"面面-只为面试而生"}
          />
        }
        location={{
          pathname,
        }}
        avatarProps={{
          src: loginUser.userAvatar || "/assets/notLoginUser.png",
          size: "small",
          title: loginUser.userName || "访客",
          render: (props, dom) => {
            return (
              <Dropdown
                menu={{
                  items: [
                    {
                      key: "logout",
                      icon: <LogoutOutlined />,
                      label: "退出登录",
                    },
                  ],
                }}
              >
                {dom}
              </Dropdown>
            );
          },
        }}
        // 操作渲染
        actionsRender={(props) => {
          if (props.isMobile) return [];
          return [
            <SearchInput key={"search"} />,
            <a
              key={"github"}
              href={"https://gitee.com/hao-xiugong/mianmian-frontend"}
              target={"_blank"}
            >
              <GithubFilled key="GithubFilled" />,
            </a>,
          ];
        }}
        headerTitleRender={(logo, title, _) => {
          return (
            <a>
              {logo}
              {title}
            </a>
          );
        }}
        footerRender={() => {
          return <GlobalFooter />;
        }}
        onMenuHeaderClick={(e) => console.log(e)}
        // 定义菜单
        menuDataRender={() => {
          return [
            {
              path: "/",
              name: "主页",
              target: "_blank"
            },
            {
              path: "/banks",
              name: "题库",
              target: "_blank"
            },
          ];
        }}
        menuItemRender={(item, dom) => (
          <Link href={item.path || "/"} target={item.target}>
            {dom}
          </Link>
        )}
      >
        {children}
      </ProLayout>
    </div>
  );
}

导航菜单配置

可以通过独立的配置文件更方便地修改导航菜单项,不用每次都修改布局代码。

实现步骤如下:

1)在 /config 目录下编写通用配置文件 menus.tsx,核心是菜单项数组,可以用 ProLayout 提供的 TypeScript 类型来规范:

import { MenuDataItem } from "@ant-design/pro-layout";
import { CrownOutlined } from "@ant-design/icons";

// 菜单列表
const menus = [
  {
    path: "/",
    name: "主页",
  },
  {
    path: "/banks",
    name: "题库",
  },
  {
    path: "/questions",
    name: "题目",
  },
  {
    path: "/admin",
    name: "管理",
    icon: <CrownOutlined />,
    children: [
      {
        path: "/admin/user",
        name: "用户管理",
      }
    ],
  },
] as MenuDataItem[];

// 导出
export default menus;

2)在全局布局的 ProLayout 中引入菜单数据。

3)同步路由的更新到菜单项高亮。

同步高亮原理:可以使用 usePathname 客户端钩子函数获取到当前页面路径,然后传递给 ProLayout 的 location 属性即可自动匹配到对应 path 的菜单项。

4)扩展能力:在 ProLayout 的菜单渲染函数中可以根据菜单项的属性来自定义菜单项渲染逻辑,比如支持配置超链接是否在新页面打开。target 的值为 _blank 表示在新页面打开。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秀秀_heo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值