欢迎来到我的博客
博主是一名大学在读本科生,主要学习方向是前端。
目前已经更新了【Vue】、【React–从基础到实战】、【TypeScript】等等系列专栏
目前正在学习的是 R e a c t / 小程序 React/小程序 React/小程序,中间穿插了一些基础知识的回顾
博客主页codeMak1r.小新的博客本文目录
- Layout模块
- 1. 基本结构搭建
- 2. 二级路由配置
- 3. 菜单高亮显示
- 4. 展示个人信息
- 5. 退出登录实现
- 6. 处理Token失效
- 7. 首页Home图表展示
本文被专栏【React–从基础到实战】收录
坚持创作✏️,一起学习,码出未来!
最近在学习React过程中,找到了一个实战小项目,在这里与大家分享。
本文遵循项目开发流程,逐步完善各个需求
gitee完整项目地址:极客园完整代码
Layout模块
1. 基本结构搭建
本节目标:
能够使用antd搭建基础布局
实现步骤
- 打开 antd/Layout 布局组件文档,找到示例:顶部-侧边布局-通栏
- 拷贝示例代码到我们的 Layout 页面中
- 分析并调整页面布局
代码实现
pages/Layout/index.js
import { Layout, Menu, Popconfirm } from 'antd'import { HomeOutlined, DiffOutlined, EditOutlined, LogoutOutlined} from '@ant-design/icons'import './index.scss'const { Header, Sider } = Layoutconst GeekLayout = () => { return ( <Layout> <Header className="header"> <div className="logo" /> <div className="user-info"> <span className="user-name">user.name</span> <span className="user-logout"> <Popconfirm title="是否确认退出?" okText="退出" cancelText="取消"> <LogoutOutlined /> 退出 </Popconfirm> </span> </div> </Header> <Layout> <Sider width={200} className="site-layout-background"> <Menu mode="inline" theme="dark" defaultSelectedKeys={['1']} style={{ height: '100%', borderRight: 0 }} > <Menu.Item icon={<HomeOutlined />} key="1"> 数据概览 </Menu.Item> <Menu.Item icon={<DiffOutlined />} key="2"> 内容管理 </Menu.Item> <Menu.Item icon={<EditOutlined />} key="3"> 发布文章 </Menu.Item> </Menu> </Sider> <Layout className="layout-content" style={{ padding: 20 }}>内容</Layout> </Layout> </Layout> )}export default GeekLayout
pages/Layout/index.scss
.ant-layout { height: 100%;}.header { padding: 0;}.logo { width: 200px; height: 60px; background: url('~@/assets/logo.png') no-repeat center / 160px auto;}.layout-content { overflow-y: auto;}.user-info { position: absolute; right: 0; top: 0; padding-right: 20px; color: #fff; .user-name { margin-right: 20px; } .user-logout { display: inline-block; cursor: pointer; }}.ant-layout-header { padding: 0 !important;}
2. 二级路由配置
本节目标:
能够在右侧内容区域展示左侧菜单对应的页面内容
使用步骤
- 在 pages 目录中,分别创建:Home(数据概览)/Article(内容管理)/Publish(发布文章)页面文件夹
- 分别在三个文件夹中创建 index.js 并创建基础组件后导出
- 在app.js中配置嵌套子路由,在layout.js中配置二级路由出口
- 使用 Link 修改左侧菜单内容,与子路由规则匹配实现路由切换
代码实现
pages/Home/index.js
const Home = () => { return <div>Home</div>}export default Home
pages/Article/index.js
const Article = () => { return <div>Article</div>}export default Article
pages/Publish/index.js
const Publish = () => { return <div>Publish</div>}export default Publish
src/routes/index.js
export default [ // 不需要鉴权的组件Login { path: "/login", element: <Login /> }, // 需要鉴权的组件Layout { path: "/", element: <AuthRoute> <Layout /> </AuthRoute>, children: [ { path: "home", element: <AuthRoute> <Home /> </AuthRoute> }, { path: "article", element: <AuthRoute> <Article /> </AuthRoute> }, { path: "publish", element: <AuthRoute> <Publish /> </AuthRoute> }, { path: "", element: <Navigate to="home" replace /> } ] }]
pages/Layout/index.js
// 配置Link组件<Menu mode="inline" theme="dark" defaultSelectedKeys={['1']} style={{ height: '100%', borderRight: 0 }}> <Menu.Item icon={<HomeOutlined />} key="1" onClick={() => navigate('home')}> 数据概览</Menu.Item><Menu.Item icon={<DiffOutlined />} key="2" onClick={() => navigate('article')}> 内容管理 </Menu.Item> <Menu.Item icon={<EditOutlined />} key="3" onClick={() => navigate('publish')}>发布文章 </Menu.Item></Menu><Layout className="layout-content" style={{ padding: 20 }}><Outlet /></Layout>
3. 菜单高亮显示
本节目标:
能够在页面刷新的时候保持对应菜单高亮
思路
- Menu组件的selectedKeys属性与Menu.Item组件的key属性发生匹配的时候,Item组件即可高亮
- 页面刷新时,将
当前访问页面的路由地址
作为 Menu 选中项的值(selectedKeys)即可
实现步骤
- 将 Menu 的
key
属性修改为与其对应的路由地址 - 获取到当前正在访问页面的路由地址
- 将当前路由地址设置为
selectedKeys
属性的值
代码实现
pages/Layout/index.js
import { useLocation } from 'react-router-dom'const GeekLayout = () => { const { pathname: selectedKey } = useLocation() console.log(selectedKey) return ( // ... <Menu mode="inline" theme="dark" selectedKeys={[selectedKey]} style={{ height: '100%', borderRight: 0 }} > <Menu.Item icon={<HomeOutlined />} key="/home" onClick={() => navigate('home')}> 数据概览</Menu.Item><Menu.Item icon={<DiffOutlined />} key="/article" onClick={() => navigate('article')}> 内容管理</Menu.Item><Menu.Item icon={<EditOutlined />} key="/publish" onClick={() => navigate('publish')}> 发布文章</Menu.Item> </Menu> )}
4. 展示个人信息
本节目标:
能够在页面右上角展示登录用户名
实现步骤
- 在store中新增user.Store.js模块,在其中定义获取用户信息的mobx代码
- 在store的入口文件中组合新增的userStore模块
- 在Layout组件中调用action函数获取用户数据
- 在Layout组件中获取个人信息并展示
代码实现
store/user.Store.js
// 用户模块import { makeAutoObservable } from "mobx"import { http } from '@/utils'class UserStore { userInfo = {} constructor() { makeAutoObservable(this) } async getUserInfo() { const res = await http.get('/user/profile') this.userInfo = res.data }}export default UserStore
store/index.js
import React from "react"import LoginStore from './login.Store'import UserStore from './user.Store'class RootStore { // 组合模块 constructor() { this.loginStore = new LoginStore() this.userStore = new UserStore() }}const StoresContext = React.createContext(new RootStore())export const useStore = () => React.useContext(StoresContext)
pages/Layout/index.js
import { useEffect } from 'react'import { observer } from 'mobx-react-lite'const GeekLayout = () => { const { userStore } = useStore() // 获取用户数据 useEffect(() => { try { userStore.getUserInfo() } catch { } }, [userStore]) return ( <Layout> <Header className="header"> <div className="logo" /> <div className="user-info"> <span className="user-name">{userStore.userInfo.name}</span> </div> </Header> {/* 省略无关代码 */} </Layout> )}export default observer(GeekLayout)
5. 退出登录实现
本节目标:
能够实现退出登录功能
实现步骤
- 为气泡确认框添加确认回调事件
- 在
store/login.Store.js
中新增退出登录的action函数,在其中删除token - 在回调事件中,调用loginStore中的退出action
- 退出后,返回登录页面
代码实现
store/login.Store.js
class LoginStore { // 退出登录 loginOut = () => { this.token = '' clearToken() }}export default LoginStore
clearToken()是utils/token.js中定义好的清除token的工具函数。
pages/Layout/index.js
// login outconst navigate = useNavigate()const onLogout = () => { loginStore.loginOut() navigate('/login')}<span className="user-logout"> <Popconfirm title="是否确认退出?" okText="退出" cancelText="取消" onConfirm={onLogout}> <LogoutOutlined /> 退出 </Popconfirm></span>
6. 处理Token失效
本节目标:
能够在响应拦截器中处理token失效
说明:为了能够在非组件环境下拿到路由信息,需要我们安装一个history包
实现步骤
- 安装history包:
yarn add history
- 创建
utils/history.js
文件 - 在app.js中使用我们新建的路由并配置history参数
- 通过响应拦截器处理 token 失效,如果发现是401跳回到登录页
代码实现
utils/history.js
// https://github.com/remix-run/react-router/issues/8264import { createBrowserHistory } from 'history'const history = createBrowserHistory()export { history }
index.js入口文件
...省略无关代码import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";import { history } from "./utils/history";const root = ReactDOM.createRoot(document.getElementById('root'))root.render( <HistoryRouter history={history}> <App /> </HistoryRouter>)
utils/http.js
import { history } from './history'http.interceptors.response.use( response => { return response.data }, error => { if (error.response.status === 401) { // 清除失效的token removeToken() // 跳转到登录页 history.push('/login') } return Promise.reject(error) })
7. 首页Home图表展示
本节目标:
实现首页echart图表封装展示
需求描述:
- 使用eharts配合react封装柱状图组件Bar
- 要求组件的标题title,横向数据xData,纵向数据yData,样式style可定制
代码实现
components/Bar/index.js
// 封装图表bar组件// 思路:// 1. 看官方文档 把echarts加入项目// 如何在react中获取dom => useRef// 在什么地方获取dom节点 => useEffect// 2. 不抽离定制化参数 先把最小化的demo跑起来// 3. 按照需求:哪些参数需要自定义 抽象出来import { useRef, useEffect } from 'react';import * as echarts from 'echarts'export default function Bar({ title, xData, yData, style }) { const domRef = useRef() // 执行这个初始化的函数 useEffect(() => { const chartInit = () => { // 基于准备好的dom,初始化echarts实例 const myChart = echarts.init(domRef.current); // 绘制图表 myChart.setOption({ title: { text: title }, tooltip: {}, xAxis: { data: xData }, yAxis: {}, series: [ { name: '框架', type: 'bar', data: yData } ] }); } chartInit() }, [title, xData, yData]) return ( <div> {/* 为echart准备一个dom节点 */} <div ref={domRef} style={style}></div> </div> )}
pages/Home/index.js
import React from 'react'import Bar from '@/components/Bar'export default function Home() { return ( <div> <Bar title='主流框架使用满意度' xData={['React', 'Vue', 'Angular']} yData={[40, 50, 30]} style={{ width: '500px', height: '400px' }} /> <Bar title='主流框架使用满意度2' xData={['React', 'Vue', 'Angular']} yData={[70, 80, 40]} style={{ width: '300px', height: '200px' }} /> </div> )}
pages/Home/index.scss
.home { width: 100%; height: 100%; align-items: center;}
下篇文章:内容管理模块的实现
专栏订阅入口【React–从基础到实战】