如何结合Ant Design Layout管理路由?解决登录页与子路由异常
路由结构优化方案及认证集成指导
一、核心问题分析
- 访问
/produk报错的原因是根路由/设置了exact,导致只有严格匹配/时才会渲染布局和内容,子路由无法匹配到父路由,进而找不到对应路由。 - 当前嵌套路由的写法不符合React Router v6规范,应使用
Outlet实现布局嵌套。
二、路由结构重构步骤
1. 重构主路由结构
移除根路由的exact属性,将带侧边栏的布局封装为独立组件,用Outlet替代原AppContent渲染子路由内容,同时将原AppContent内的子路由移至主路由的children中:
import { Routes, Route, Outlet, Navigate } from 'react-router-dom'; import { Layout, Sider, Content, Footer } from 'antd'; import LoginPage from './LoginPage'; import Dashboard from './Dashboard'; import KalkulasiProduk from './KalkulasiProduk'; import Produk from './Produk'; import Supplier from './Supplier'; import LokasiKargo from './LokasiKargo'; import Aktivitas from './Aktivitas'; import Settings from './Settings'; import AppAlert from './AppAlert'; import SideNav from './SideNav'; import logo from './logo.png'; import style from './styles.module.css'; // 带侧边栏的主布局组件 const MainLayout = () => { return ( <> <AppAlert /> <Layout style={{ minHeight: "100vh" }}> <Sider> <div className={style.logo_wrapper} style={{ height: 32, margin: 16 }} > <img src={logo} alt="logo" /> <p>Shipping Calculation</p> </div> <SideNav /> </Sider> <Layout className="site-layout"> <Content style={{ margin: "0 16px" }}> <div style={{ marginTop: "16px", padding: 24, minHeight: 360 }}> {/* 用Outlet渲染子路由内容 */} <Outlet /> </div> </Content> <Footer style={{ textAlign: "center" }}> Shipping Calculation ©2023 Created by Owenn Gimli </Footer> </Layout> </Layout> </> ); }; const AppRoutes = () => { return ( <div> <Routes> {/* 无需侧边栏的登录页 */} <Route path="/login" element={<LoginPage />} /> {/* 带侧边栏的主布局路由,包含所有子路由 */} <Route path="/" element={<MainLayout />}> <Route index element={<Dashboard />} /> {/* 根路径默认渲染Dashboard */} <Route path="kalkulasi-produk" element={<KalkulasiProduk />} /> <Route path="produk" element={<Produk />} /> <Route path="supplier" element={<Supplier />} /> <Route path="lokasi-kargo" element={<LokasiKargo />} /> <Route path="aktivitas" element={<Aktivitas />} /> <Route path="settings" element={<Settings />} /> </Route> {/* 404路由,处理未定义路径 */} <Route path="*" element={<Navigate to="/" replace />} /> </Routes> </div> ); }; export default AppRoutes;
2. 集成Firebase认证的私有路由
创建私有路由组件,保护需要登录才能访问的主布局路由:
import { useEffect, useState } from 'react'; import { Navigate, Outlet } from 'react-router-dom'; import { getAuth, onAuthStateChanged } from 'firebase/auth'; const PrivateRoute = () => { const [isAuthenticated, setIsAuthenticated] = useState(null); const auth = getAuth(); useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { setIsAuthenticated(!!user); }); return () => unsubscribe(); }, [auth]); // 加载状态可替换为自定义loading组件 if (isAuthenticated === null) { return <div>Loading...</div>; } return isAuthenticated ? <Outlet /> : <Navigate to="/login" replace />; };
修改主路由,用私有路由包裹主布局:
const AppRoutes = () => { return ( <div> <Routes> <Route path="/login" element={<LoginPage />} /> {/* 用私有路由保护主布局下的所有子路由 */} <Route element={<PrivateRoute />}> <Route path="/" element={<MainLayout />}> <Route index element={<Dashboard />} /> <Route path="kalkulasi-produk" element={<KalkulasiProduk />} /> <Route path="produk" element={<Produk />} /> <Route path="supplier" element={<Supplier />} /> <Route path="lokasi-kargo" element={<LokasiKargo />} /> <Route path="aktivitas" element={<Aktivitas />} /> <Route path="settings" element={<Settings />} /> </Route> </Route> <Route path="*" element={<Navigate to="/" replace />} /> </Routes> </div> ); };
3. 登录页跳转逻辑
在LoginPage中实现登录成功后的页面跳转:
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth'; import { useNavigate } from 'react-router-dom'; import { Form, Input, Button } from 'antd'; const LoginPage = () => { const navigate = useNavigate(); const auth = getAuth(); const handleLogin = async (values) => { try { await signInWithEmailAndPassword(auth, values.email, values.password); // 登录成功跳转到首页 navigate('/', { replace: true }); } catch (error) { console.error('登录失败:', error); // 可添加错误提示组件 } }; return ( <div style={{ maxWidth: 300, margin: '100px auto' }}> <h2>登录</h2> <Form onFinish={handleLogin}> <Form.Item name="email" rules={[{ required: true, message: '请输入邮箱' }]} > <Input placeholder="邮箱" /> </Form.Item> <Form.Item name="password" rules={[{ required: true, message: '请输入密码' }]} > <Input.Password placeholder="密码" /> </Form.Item> <Form.Item> <Button type="primary" htmlType="submit" block>登录</Button> </Form.Item> </Form> </div> ); }; export default LoginPage;
三、关键说明
- 移除根路由
exact属性:React Router v6中父路由默认匹配所有以其路径开头的子路由,无需手动设置exact。 - 使用
Outlet实现嵌套:这是React Router v6推荐的布局嵌套写法,替代传统子路由渲染方式。 - 私有路由作用:确保未登录用户无法直接访问主布局页面,自动跳转至登录页。
- 404路由:处理所有未定义路径,避免出现路由匹配错误。
内容的提问来源于stack exchange,提问作者Owenn
相关产品推荐
相关产品推荐

