欢迎来到我的博客
博主是一名大学在读本科生,主要学习方向是前端。
目前已经更新了【Vue】、【React–从基础到实战】、【TypeScript】等等系列专栏
目前正在学习的是 R e a c t / 小程序 React/小程序 React/小程序,中间穿插了一些基础知识的回顾
博客主页codeMak1r.小新的博客本文目录
- Layout模块
- 1. 基本结构搭建
- 2. 二级路由配置
- 3. 菜单高亮显示
- 4. 展示个人信息
- 5. 退出登录实现
- 6. 处理Token失效
- 7. 首页Home图表展示
本文被专栏【React–从基础到实战】收录
坚持创作✏️,一起学习,码出未来!
最近在学习React过程中,找到了一个实战小项目,在这里与大家分享。
本文遵循项目开发流程,逐步完善各个需求
gitee完整项目地址:极客园完整代码
本节目标:
能够使用antd搭建基础布局
实现步骤
代码实现
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 } = Layout
const 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;
}
本节目标:
能够在右侧内容区域展示左侧菜单对应的页面内容
使用步骤
代码实现
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>
本节目标:
能够在页面刷新的时候保持对应菜单高亮
思路
当前访问页面的路由地址
作为 Menu 选中项的值(selectedKeys)即可实现步骤
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>
)
}
本节目标:
能够在页面右上角展示登录用户名
实现步骤
代码实现
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)
本节目标:
能够实现退出登录功能
实现步骤
store/login.Store.js
中新增退出登录的action函数,在其中删除token代码实现
store/login.Store.js
class LoginStore {
// 退出登录
loginOut = () => {
this.token = ''
clearToken()
}
}
export default LoginStore
clearToken()是utils/token.js中定义好的清除token的工具函数。
pages/Layout/index.js
// login out
const navigate = useNavigate()
const onLogout = () => {
loginStore.loginOut()
navigate('/login')
}
<span className="user-logout">
<Popconfirm title="是否确认退出?" okText="退出" cancelText="取消" onConfirm={onLogout}>
<LogoutOutlined /> 退出
</Popconfirm>
</span>
本节目标:
能够在响应拦截器中处理token失效
说明:为了能够在非组件环境下拿到路由信息,需要我们安装一个history包
实现步骤
yarn add history
utils/history.js
文件代码实现
utils/history.js
// https://github.com/remix-run/react-router/issues/8264
import { 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)
}
)
本节目标:
实现首页echart图表封装展示
需求描述:
代码实现
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–从基础到实战】