React路由v6+antd+路由鉴权

一、React路由简介

React 官方并没有提供对应的路由插件,因此,我们需要下载第三方的路由插件 —— React Router。

React Router 在 2021 年 11 月份的时候更新 v6 的版本。

二、路由配置

1、下载路由

在项目根目录中,通过以下命令

yarn add react-router-dom

2、路由配置

1)首先在react项目的入口文件index.js文件中,使用包裹起来

import {BrowserRouter} from 'react-router-dom'

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    
        
     
);

BrowserRouter:包裹这个应用,一个React应用只需使用一次

在 React Router 中提供了两种路由模式:hash 和 history。

对应的的路由组件分别是:

  • HashRouter:hash 模式的路由
  • BrowserRouter:history 模式的路由

实际使用时,任选其中一个模式引入即可

2)其次,在App.js文件中,使用设置路由出口,使用指定导航链接

import React from 'react'
import {Routes,Route} from 'react-router-dom'
import Login from './pages/Login'
import Register from './pages/Register'
import Home from './pages/Home'
export default function App() {
  return (
   
     }>
     }>
     }>
   
  )
}

核心组件作用说明

  • Routes:提供一个路由出口,满足条件的路由组件会渲染到组件内部
  • Route: 用于指定导航链接,完成路由跳转
    • path:path属性指定匹配的路径地址
    • element element属性指定要渲染的组件

三、路由跳转

React Router 中,路由的跳转分为两种方式:

  • 标签(组件)跳转
  • JS(API)跳转

1、通过Link组件跳转

import React from 'react'
import {Link} from 'react-router-dom'
export default function Login() {
  return (
    
        

用户登录

        没有账号,去注册     
  ) }

2、编程式路由跳转

实现步骤

  • 导入useNavigate钩子函数
import {useNavigate} from 'react-router-dom'
  • 执行钩子函数得到跳转函数
 let navigate=useNavigate();
  • 执行跳转函数完成跳转
import React from 'react'
import {useNavigate} from 'react-router-dom'

export default function Register() {
  const navigate=useNavigate()
  const register=(e)=>{
    e.preventDefault()
    navigate('/login')
  }
  return (
    
        

用户注册

        {register(e)}}>已注册,去登录     
  ) }

注意

  • 如果在跳转时不想加历史记录,可以添加额外参数replace为true
 const register=(e)=>{
    e.preventDefault()
    navigate('/login',{replace:true})
  }

四、嵌套路由

1、基础配置

实现步骤

  • 定义嵌套路由声明
 
     }>
     }>
     }>
       }>
       }>
     
 
  • 设置二级路由出口
export default function Home() {
    return (
        <>
          
          
                 {/* 二级路由出口 */}                            
             ) }

2、默认二级路由设置


   }>
   }>
   }>
       {/*默认二级路由,添加index属性,删除掉path属性*/}
       }>
       }>
       }>
   

3、404页配置

应用场景:当所有的路径都没有匹配的时候显示

语法说明:在各级路由的最后添加*号路由作为兜底


    }>
    }>
    }>
       {/*默认二级路由,添加index属性,删除掉path属性*/}
       }>
       }>
       }>
     
     {/*当所有路径都没有匹配时渲染此路由*/}
     }>

五、路由传参

1、searchParams传参

实现步骤

  • 传参
import {useNavigate} from 'react-router-dom'
export default function CategroyList() {
  let navigate=useNavigate();
  return (
    
        

CategroyList

        {navigate('/categroyDetail?id=12')}}>详情     
  ) }
  • 获取参数
import {useSearchParams} from 'react-router-dom'

export default function CategoryDetail() {
  let [params]=useSearchParams()
  return (
    
        

CategroyDetail

        
            ID:{params.get('id')}         
    
  ) }

2、params传参

实现步骤

  • 路由设置
 
     
        }>
           }>
         
      
 
  • 传参
import {useNavigate} from 'react-router-dom'
export default function CategroyList() {
  let navigate=useNavigate();
  return (
    
        

CategroyList

        {navigate('/home/categroy-detail/13')}}>详情     
  ) }
  • 获取参数
import React from 'react'
import {useParams} from 'react-router-dom'

export default function CategoryDetail() {
  let params=useParams()
  return (
    
        

CategroyDetail

        
            ID:{params.id}         
    
  ) }

六、集中式路由渲染

实现步骤

  • 在项目根目录创建router文件夹,并在该目录下创建index.jsx
  • 在router/index.jsx编写路由配置项
import Login from '../pages/Login'
import Register from '../pages/Register'
import Home from '../pages/Home'
import CategoryList from '../pages/Category'
import CategoryDetail from '../pages/Category/Detail'
import GoodsList from '../pages/Goods'
import Main from '../pages/Home/Main'
export default [
    {
        path:'/login',
        element:
    },
    {
        path:'/register',
        element:
    },
    {
        path:'/',
        element:,
        children:[
            {
                index:true,
                element:
            },             {                 path:'/categoryList',                 element:             },             {                 path:'/categoryDetail/:id',                 element:             },             {                 path:'/goodsList',                 element:             }         ]     } ]
  • 在App.jsx中通过useRoutes钩子函数来进行集中式配置
import {useRoutes} from 'react-router-dom'
import router from './router/index'
function App() {
  return useRoutes(router)
}
export default App;
  • 在项目根目录下的index.js中使用包裹
root.render(
    
        
    
)

七、路由懒加载

1、实现步骤

  • 使用lazy(()=>import('xxx'))方式导入组件
import {lazy} from 'react'
const Login=lazy(()=>import('../pages/Login'))
const Register=lazy(()=>import('../pages/Register'))
const Home=lazy(()=>import('../pages/Home'))
const CategoryList=lazy(()=>import('../pages/Category'))
const CategoryDetail=lazy(()=>import('../pages/Category/Detail'))
const GoodsList=lazy(()=>import('../pages/Goods'))
const Main=lazy(()=>import('../pages/Home/Main'))
export default [
    {
        path:'/login',
        element:
    },
    {
        path:'/register',
        element:
    },
    {
        path:'/',
        element:,
        children:[
            {
                index:true,
                element:
            },             {                 path:'/categoryList',                 element:             },             {                 path:'/categoryDetail/:id',                 element:             },             {                 path:'/goodsList',                 element:             }         ]     } ]
  • 通过 React 中提供了   组件,来实现路由的懒加载。
import {Suspense} from 'react'
function App() {
  return(
    loading}>
      {useRoutes(router)}
    
  )
}

 组件身上,必须设置一个 fallback 属性,属性值可以是一个 HTML 标签,也可以是一个自定义的组件。用于当路由组件还未加载出来前的提示。

2、解决路由闪屏

配置完路由懒加载后出现当进行路由跳转时,出现闪屏现象,要向解决这个问题可以使用 react-loadable插件进行解决

  • 先下载react-loadable依赖包
yarn add react-loadable
  • 建立一个loadable.js,放在src/utils/loadable.js
import Loadable from 'react-loadable';
export default function withLoadable(comp) {
    return Loadable({
     //懒加载组件页面
        loader: comp,
        loading: () => null,
        delay: "",
    })
}
  • 修改router/index.js
import loadable from '../utils/loadable'
const Login=loadable(()=>import('../pages/Login'))
const Register=loadable(()=>import('../pages/Register'))
const Home=loadable(()=>import('../pages/Home'))
const CategoryList=loadable(()=>import('../pages/Category'))
const CategoryDetail=loadable(()=>import('../pages/Category/Detail'))
const GoodsList=loadable(()=>import('../pages/Goods'))
const Main=loadable(()=>import('../pages/Home/Main'))
export default [
    {
        path:'/login',
        element:
    },
    {
        path:'/register',
        element:
    },
    {
        path:'/',
        element:,
        children:[
            {
                index:true,
                element:
            },             {                 path:'/categoryList',                 element:             },             {                 path:'/categoryDetail/:id',                 element:             },             {                 path:'/goodsList',                 element:             }         ]     } ]

八、使用antd美化中后台系统

antd 是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。

官网地址:Ant Design - 一套企业级 UI 设计语言和 React 组件库

1、引入antd

  • 安装antd
yarn add antd
  • 修改 src/App.css,在文件顶部引入 antd/dist/antd.css
@import '~antd/dist/antd.css'
  • 在App.js中引入antd的组件进行测试
import React, { Component } from 'react'
import './assets/styles/index.scss';
import {Button} from 'antd'
export default class App extends Component {
  render() {
    return (
      
        App         按钮       
    )   } }

2、用户登录

  • 在onFinish函数中编写登录的关键代码
import React from 'react'
import axios from 'axios'
import {useNavigate} from 'react-router-dom'
import {Form,Button, Input} from 'antd'
export default function Login() {
  const navigate=useNavigate()
  const login=async(value)=>{
    const {data:{code,data:{token}}}=await axios.post("http://www.zhaijizhe.cn:3001/users/login",value)
    if(code){
      localStorage.setItem('token',token)
      navigate("/home")
    }
  }
  return (
    
      
        账号
}           name="account"           rules={[             {               required: true,               message: '请输入账号',             }           ]}>                               密码
}           name="password"           rules={[             {               required: true,               message: '请输入密码!',             },           ]}>                                          登录                     
  ) }

在asseets/css下,新建login.css,内容如下

.login{
    display: flex;
    justify-content: center;
    align-items: center;
    background-color: #001529;
    height:100vh;
}
.login .ant-form{
    width: 450px;
}

然后在assets/style/index.scss中引入login.scss

@import 'login.css';
@import '~antd/dist/antd.css'

3、后台首页布局设计

后台首页布局布局可分为三个部分,我们可以将其做成三个组件,分别是

  • 头部组件
  • 左侧栏组件
  • 右侧内容组件

3.1、头部组件

import React from 'react'
import { Layout, Menu } from 'antd';
const { Header } = Layout
export default function MyHeader() {
    return (
        
            蜗牛商城
                             个人中心                 安全退出                           ) }  

关键代码分析

 
  • mode:菜单的类型,horizontal表示菜单为水平菜单
  • theme:表示菜单的主题颜色,这里有两种选择,可以为light亮色,或者dark为暗色

3.2、左侧栏菜单

import React from 'react'
import {Menu,Layout } from 'antd'
import { UserOutlined, LaptopOutlined, MailOutlined,UserSwitchOutlined } from '@ant-design/icons';
const { Sider } = Layout
export default function MySider() {
  return (
    
    
      } title="用户管理">
        用户列表
        添加用户
      
      } title="角色管理">
        角色列表
      
      } title="分类管理">
        分类列表
      
      } title="商品管理">
        商品列表
        添加商品
      
    
   
  )
}

3.3、右侧内容区域

import React from 'react'
import { Layout,Breadcrumb } from 'antd'
const {Content} = Layout
export default function MyContent() {
    return (
        
            
                首页
                用户管理
                用户列表
            
            
                Content
            
        
    )
}

在Home组件中引入这三个组件,如下

import React from 'react'
import MyHeader from './MyHeader';
import MySider from './MySider';
import MyContent from './MyContent';
import { Layout } from 'antd'
export default function Home() {
  return (
    
     
      
        
        
      
    
  )
}

4、完成安全退出功能

import React from 'react'
import { Layout, Menu,Popconfirm} from 'antd';
import {useNavigate} from 'react-router-dom'
const { Header } = Layout
export default function MyHeader() {
    const navigate=useNavigate()
    const logout=()=>{
        localStorage.removeItem('token');
        navigate('/login',{replace:true})
    }
    return (
        
            蜗牛商城
                             个人中心                                  安全退出                                            ) }  

5、菜单栏基本功能

5.1、配置路由

这里才使用集中式路由配置方式

import Register from "../views/Register";
import AuthComponent from '../component/AuthComponent'
import loadable from '../utils/loadable'
const Home=loadable(()=>import("../views/Home"))
const Login=loadable(()=>import("../views/Login"))
const Main=loadable(()=>import("../views/Main"))
const Product=loadable(()=>import("../views/Product"))
const ProductAdd=loadable(()=>import("../views/ProductAdd"))
const CategoryList=loadable(()=>import("../views/CategoryList"))
const RoleList=loadable(()=>import("../views/RoleList"))
const AccountList=loadable(()=>import("../views/AccountList"))
const AccountAdd=loadable(()=>import("../views/AccountAdd"))

export default [
    {
        path:'/login',
        element:
    },
    {
        path:'/register',
        element:
    },
    {
        path:'/home',
        element:,
        children:[
            {
                index:true,
                element:
            },             {                 path:'product/list',                 element:             },             {                 path:'shop',                 element:             },             {                 path:'product/category',                 element:             },             {                 path:'role',                 element:             },             {                 path:'user',                 element:             },             {                 path:'datav/flowers',                 element:             }         ]     } ]

然后再去修改App.jsx

import {useRoutes} from 'react-router-dom'
import {Suspense} from 'react'
import router from './router/index'
import './assets/css/app.css'
export default function App() {
  return(
    loading}>
      {useRoutes(router)}
    
  )
}

5.2、设置二级路由出口

在MyContent.jsx中设置二级路由出口

import React from 'react'
import { Layout,Breadcrumb } from 'antd'
import {Outlet} from 'react-router-dom'
const {Content} = Layout
export default function MyContent() {
    return (
        
            
                首页
                用户管理
                用户列表
            
            
                 //设置二级路由出口
            
        
    )
}

5.3、侧边栏设置路由跳转

import React from 'react'
import {Menu,Layout } from 'antd'
import {HomeOutlined, UserOutlined, LaptopOutlined, MailOutlined,UserSwitchOutlined } from '@ant-design/icons';
import {useNavigate} from 'react-router-dom'
const { Sider } = Layout
export default function MySider() {
  const navigate=useNavigate()
  return (
    
    {
        navigate(params.key);  //这里实现路由跳转的
      }}>
      }首页
      }用户管理
      }角色管理
      }店铺管理
      } title="商品管理">
        商品列表
        分类列表
      
      } title="统计报表">
        市场统计
        销售统计
      
    
   
  )
}

6、路由鉴权

路由鉴权就是能够实现未登录时访问拦截跳转到登录页

实现的思路:自己封装AuthComponent路由鉴权高阶组件,实现未登录拦截,并跳转到登录页面

思路为:判断本地是否有token,如果有则返回子组件,否则就重定向到登录页

实现步骤

  • 在components目录中,创建AuthComponent/index.jsx文件
  • 判断是否登录
  • 登录时,直接渲染相应页面组件
  • 未登录时,重定向到登录页面
  • 将需要鉴权的页面路由,替换成AuthRoute组件渲染
const TOKEN_KEY="giles_token";
const getToken=()=>localStorage.getItem(TOKEN_KEY)
const setToken=token=>localStorage.setItem(TOKEN_KEY,token)
const clearToken=()=>localStorage.removeItem(TOKEN_KEY)
const isAuth=()=>!!getToken()
export {isAuth,getToken,setToken,clearToken}

关键代码

import React,{useEffect} from 'react'
import {useNavigate} from 'react-router-dom'
import $http from '../api/http'
import {message} from 'antd'
export default function AuthComponent({children}) {
  //判断token是否存在
  const isAuth=!!localStorage.getItem('token')
  const nav=useNavigate()
  const getUserInfo=async()=>{
    try {
      await $http.users.getUserInfo()
     } catch (error) {
       message.warning('token失效请重新登录')
       nav('/login',{replace:true})
     }
  }
  useEffect(()=>{
    getUserInfo()
  },[])
  if(isAuth){
    return <>{children}
  }else{
    message.warning('没有token请重新登录')
    return <>{nav('/login')}
  }
}

在router/index.js中使用包裹中组件

export default [
    {
        path:'/login',
        element:
    },
    {
        path:'/register',
        element:
    },
    {
        path:'/',
        element:,
        children:[
            {
                index:true,
                element:
            },             {                 path:'/categoryList',                 element:             },             {                 path:'/categoryDetail/:id',                 element:             },             {                 path:'/goodsList',                 element:             }         ]     } ]

7、动态渲染侧边栏

首先在config文件夹下创建routes.js文件

import { UserOutlined, HomeOutlined,AppstoreOutlined,EuroCircleOutlined,DollarOutlined} from '@ant-design/icons';
const routes=[
    {path:'/home',icon:,title:'首页'},
    {path:'/home/user',icon:,title:'用户管理',roles:["超级管理员"]},
    {path:'/home/role',icon:,title:'角色管理',roles:["超级管理员"]},
    {path:'/home/shop',icon:,title:'店铺管理',roles:["超级管理员"]},
    {
        subMenuKey:'productManage',
        icon:,
        subMenuTitle:'商品管理',
        children:[
            {path:'/home/product/list',icon:,title:'商品列表'},
            {path:'/home/product/category',icon:,title:'分类列表'}
        ]
    },
    {
        subMenuKey:'accountManage',
        icon:,
        subMenuTitle:'统计管理',
        children:[
            {path:'/home/datav/flowers',icon:,title:'市场数据'},
            {path:'/home/datav/sale',icon:,title:'销售数据'}
        ]
    }
]
export default routes;

修改MySider.js文件

import React from 'react'
import { Menu, Layout } from 'antd'
import { useNavigate } from 'react-router-dom'
import routes from '../config/routes'
const { Sider } = Layout
export default function MySider() {
  const navigate = useNavigate()

  const renderMenu = (routes) => {
    const { role } = JSON.parse(localStorage.getItem('userInfo'));
    console.log('role',routes.filter((item) => !item.roles || item.roles.includes(role.name)));
    return routes.filter((item) => !item.roles || item.roles.includes(role.name)).map((item) => {
      const { path, title, subMenuKey, subMenuTitle, icon, children } = item;
      if (!subMenuKey) {
        return {title};
      } else {
        return 
          {renderMenu(children)}
        
      }
    });

  }
  return (
    
       {
          navigate(params.key);  //这里实现路由跳转的
        }}>
        {
          renderMenu(routes)
        }
      
    
  )
}

演示:分别以Giles/123456和xiaofei/666666两个用户登录,你会发现登录后的左侧栏菜单是不同的

8、Table表格渲染

8.1、普通列表展示

import React, { useEffect, useState } from 'react'
import $http from '../../api/http'
import {Table,Space,Button} from 'antd'
export default function ProductList() {
  const [list, setList] = useState([])
  const getProductList = async () => {
    const { data: { data } } = await $http.products.findGoods()
    console.log(data);
    setList(data)
  }
  useEffect(() => {
    getProductList()
  }, [])
  const columns = [
    {
      title: '商品名称',
      dataIndex: 'name',
      key: 'name',
    },
    {
      title: '商品标题',
      dataIndex: 'title',
      key: 'title',
    },
    {
      title: '商品价格',
      dataIndex: 'price',
      key: 'price',
    },
    {
      title: '商品图片',
      dataIndex: 'imgSrc',
      key: 'imgSrc',
      render:(item)=>{
        return 
      }
    },
    {
      title: '操作',
      key: 'action',
      render: (record) => (
        
          查看
          删除
        
      ),
    }
  ];

  return (
    
           
  ) }

8.2、解决列表的key值问题

页面已经正常显示,但是查看控制台的时候,会报如下错误

Warning: Each child in a list should have a unique "key" prop.

这个问题非常经典,是由于在列表循环的时候,没有加入key属性出现的,那么在antd的Table中专门提供了一个rowKey这个属性来解决该问题,rowKey 表示表格行 key 的取值,那么这里边应该写上列表中元素对象的_id值即可

 

8.3、分页实现

  • 使用useState定义状态数据
  const [list, setList] = useState([])
  const [total,setTotal]=useState(0)
  const [pageSize,setPageSize]=useState(0)
  • 在getAccountList中更新state中的total状态值
 const getProductList = async (params={pageNo:1,pageSize:2}) => {
    console.log('params',params);
    const {data:{data,pageSize,total}} = await $http.products.findGoods(params)
    setList(data)
    setTotal(total)
    setPageSize(pageSize)
  }
  • 定义pagination对象,然后在设置pageination为false,并重新添加分页组件
     return (
        
          
                 
  )
  • 定义onChange函数,并在该方法中实现请求查询商品列表功能
const onChange=(pageNo,pageSize)=>{
    getProductList({pageNo,pageSize});
  }

9、树形菜单实现

import React,{useEffect,useState} from 'react'
import $http from '../../api/http'
import {Table} from 'antd'
export default function CategoryList() {
  const [list,setList]=useState([])
  const getCategroy=async()=>{
    const {data:{data}}=await $http.products.findCategory()
    console.log(data);
    setList(data)
  }
  useEffect(()=>{
    getCategroy()
  },[])

  const columns = [
    {
      title: '标签',
      dataIndex: 'label',
      key: 'label',
    },
    {
      title: '值',
      dataIndex: 'value',
      key: 'value',
    }
  ];
  
  return (
    
  ) }

你可能感兴趣的:(react.js,javascript,前端)