You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ReactJS:将Topbar与Sidebar合并为单一导航布局

问题描述

我正在构建一个React应用,包含两种布局:

  • 未登录时:使用无Sidebar和Topbar的布局
  • 登录后(通过context判断状态):使用包含Topbar和Sidebar的布局

当前遇到的问题:登录状态下,Topbar和Sidebar作为独立组件渲染,导致Dashboard等页面组件被挤到页面底部。需要将这两个栏合并为统一布局结构,让页面内容显示在布局的空白区域。

解决方案

一、重构布局与栏组件结构

问题核心是当前组件未遵循SB Admin 2的DOM结构规范,容器嵌套错误,且路由并行渲染布局与页面组件导致排版混乱,以下是修复步骤:

1. 调整Topbar组件结构

移除多余的外层容器,保留Topbar本身的导航结构:

// Topbar.jsx
import "startbootstrap-sb-admin-2-master/css/sb-admin-2.min.css";
import { useAuth } from "../../hooks/auth";

export const TopBar = () => {
  const user = useAuth().user;
  const { logout } = useAuth();

  const handleSubmit = async (e) => {
    e.preventDefault();
    logout();
  };

  return (
    <nav className="navbar navbar-expand navbar-light bg-white topbar mb-4 static-top shadow">
      <button
        id="sidebarToggleTop"
        className="btn btn-link d-md-none rounded-circle mr-3"
      >
        <i className="fa fa-bars"></i>
      </button>
      <form className="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search">
        <div className="input-group">
          <input
            type="text"
            className="form-control bg-light border-0 small"
            placeholder="Search for..."
            aria-label="Search"
            aria-describedby="basic-addon2"
          />
          <div className="input-group-append">
            <button className="btn btn-primary" type="button">
              <i className="fa-solid fa-magnifying-glass"></i>
            </button>
          </div>
        </div>
      </form>
      <ul className="navbar-nav ml-auto">
        <li className="nav-item dropdown no-arrow mx-1">
          <div
            className="nav-link dropdown-toggle"
            href="#"
            id="alertsDropdown"
            role="button"
            data-toggle="dropdown"
            aria-haspopup="true"
            aria-expanded="false"
          >
            <i className="fas fa-bell fa-fw"></i>
            <span className="badge badge-danger badge-counter">3+</span>
          </div>
        </li>
        <div className="topbar-divider d-none d-sm-block" />
        <li className="nav-item dropdown no-arrow">
          <div
            className="nav-link"
            id="userDropdown"
            role="button"
            data-toggle="dropdown"
            aria-haspopup="true"
            aria-expanded="false"
          >
            <span className="mr-2 d-none d-lg-inline text-gray-600 small">
              Hi, {user}!
            </span>
            <i className="fa-solid fa-user"></i>
          </div>
        </li>
        <li className="nav-item dropdown no-arrow">
          <div
            className="nav-link"
            id="userDropdown"
            role="button"
            data-toggle="dropdown"
            aria-haspopup="true"
            aria-expanded="false"
          >
            <button
              className="btn btn-secondary btn-circle"
              type="button"
              onClick={handleSubmit}
            >
              <i className="fa-solid fa-arrow-right-from-bracket"></i>
            </button>
          </div>
        </li>
      </ul>
    </nav>
  );
};

2. 调整Navbar组件结构

保留核心Sidebar列表,移除多余的外层容器嵌套:

// Navbar.jsx
import "startbootstrap-sb-admin-2-master/css/sb-admin-2.min.css";
import { useAuth } from "../../hooks/auth";

export const Navbar = () => {
  return (
    <ul
      className="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion"
      id="accordionSidebar"
    >
      <hr className="sidebar-divider my-0" />

      <li className="nav-item active">
        <a className="nav-link" href="/auth/dashboard">
          <i className="fas fa-fw fa-tachometer-alt"></i>
          <span>Dashboard</span>
        </a>
      </li>

      <hr className="sidebar-divider" />

      <div className="sidebar-heading">Interface</div>

      <li className="nav-item">
        <a
          className="nav-link collapsed"
          href="#"
          data-toggle="collapse"
          data-target="#collapseTwo"
          aria-expanded="true"
          aria-controls="collapseTwo"
        >
          <i className="fas fa-fw fa-cog"></i>
          <span>Components</span>
        </a>
        <div
          id="collapseTwo"
          className="collapse"
          aria-labelledby="headingTwo"
          data-parent="#accordionSidebar"
        >
          <div className="bg-white py-2 collapse-inner rounded">
            <h6 className="collapse-header">Custom Components:</h6>
            <a className="collapse-item" href="buttons.html">Buttons</a>
            <a className="collapse-item" href="cards.html">Cards</a>
          </div>
        </div>
      </li>

      <li className="nav-item">
        <a
          className="nav-link collapsed"
          href="#"
          data-toggle="collapse"
          data-target="#collapseUtilities"
          aria-expanded="true"
          aria-controls="collapseUtilities"
        >
          <i className="fas fa-fw fa-wrench"></i>
          <span>Utilities</span>
        </a>
        <div
          id="collapseUtilities"
          className="collapse"
          aria-labelledby="headingUtilities"
          data-parent="#accordionSidebar"
        >
          <div className="bg-white py-2 collapse-inner rounded">
            <h6 className="collapse-header">Custom Utilities:</h6>
            <a className="collapse-item" href="utilities-color.html">Colors</a>
            <a className="collapse-item" href="utilities-border.html">Borders</a>
            <a className="collapse-item" href="utilities-animation.html">Animations</a>
            <a className="collapse-item" href="utilities-other.html">Other</a>
          </div>
        </div>
      </li>

      <hr className="sidebar-divider" />

      <div className="sidebar-heading">Addons</div>

      <li className="nav-item">
        <a
          className="nav-link collapsed"
          href="#"
          data-toggle="collapse"
          data-target="#collapsePages"
          aria-expanded="true"
          aria-controls="collapsePages"
        >
          <i className="fas fa-fw fa-folder"></i>
          <span>Pages</span>
        </a>
        <div
          id="collapsePages"
          className="collapse"
          aria-labelledby="headingPages"
          data-parent="#accordionSidebar"
        >
          <div className="bg-white py-2 collapse-inner rounded">
            <h6 className="collapse-header">Login Screens:</h6>
            <a className="collapse-item" href="/">Login</a>
            <a className="collapse-item" href="register.html">Register</a>
            <a className="collapse-item" href="forgot-password.html">Forgot Password</a>
            <div className="collapse-divider"></div>
            <h6 className="collapse-header">Other Pages:</h6>
            <a className="collapse-item" href="404.html">404 Page</a>
            <a className="collapse-item" href="blank.html">Blank Page</a>
          </div>
        </div>
      </li>

      <li className="nav-item">
        <a className="nav-link" href="charts.html">
          <i className="fas fa-fw fa-chart-area"></i>
          <span>Charts</span>
        </a>
      </li>

      <li className="nav-item">
        <a className="nav-link" href="tables.html">
          <i className="fas fa-fw fa-table"></i>
          <span>Tables</span>
        </a>
      </li>

      <hr className="sidebar-divider d-none d-md-block" />

      <div className="text-center d-none d-md-inline">
        <button className="rounded-circle border-0" id="sidebarToggle" />
      </div>
    </ul>
  );
};

3. 重构ProtectedLayout组件

按照SB Admin 2的标准结构组织布局,将页面内容通过Outlet插入到指定区域:

// ProtectedLayout.jsx
import { Outlet } from "react-router";
import { Navbar } from "../components/common/Navbar";
import { TopBar } from "../components/common/Topbar";

export const ProtectedLayout = () => {
  return (
    <div id="wrapper">
      {/* Sidebar */}
      <Navbar />
      {/* Content Wrapper */}
      <div id="content-wrapper" className="d-flex flex-column">
        {/* Main Content */}
        <div id="content">
          {/* Topbar */}
          <TopBar />
          {/* Page Content */}
          <div className="container-fluid">
            <Outlet />
          </div>
        </div>
      </div>
    </div>
  );
};

二、路由配置优化

利用React Router的嵌套路由特性,简化路由结构,避免重复渲染布局组件:

// App.js
import React from "react";
import { Route, Routes } from "react-router-dom";
import { LoginLayout } from "./layout/LoginLayout";
import { ProtectedLayout } from "./layout/ProtectedLayout";
import { ProtectRoutes } from "./hooks/protectedRoute";
import { Login } from "./pages/LoginPage";
import { Dashboard } from "./pages/DashboardPage";
import { Hi } from "./pages/HiPage";
import { About } from "./pages/AboutPage";

export default function App() {
  return (
    <Routes>
      {/* 未登录路由 */}
      <Route element={<LoginLayout />}>
        <Route path="/" element={<Login />} />
      </Route>

      {/* 已登录保护路由 */}
      <Route path="/auth" element={<ProtectRoutes />}>
        <Route element={<ProtectedLayout />}>
          <Route path="dashboard" element={<Dashboard />} />
          <Route path="hi" element={<Hi />} />
          <Route path="about" element={<About />} />
        </Route>
      </Route>
    </Routes>
  );
}

路由优化说明

  • 让ProtectedLayout作为/auth下所有子路由的父布局,页面组件通过Outlet自动嵌入到布局的内容区域
  • 简化路由路径写法,子路由继承父路由前缀,代码更简洁易维护
  • 避免了原代码中并行渲染布局与页面组件的错误写法

内容的提问来源于stack exchange,提问作者cameronErasmus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 07:01:28