基本結構和樣式reset
結構創建
實現步驟
- 打開
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 items = [{label: '首頁',key: '1',icon: <HomeOutlined />,},{label: '文章管理',key: '2',icon: <DiffOutlined />,},{label: '創建文章',key: '3',icon: <EditOutlined />,},
]const GeekLayout = () => {return (<Layout><Header className="header"><div className="logo" /><div className="user-info"><span className="user-name">柴柴老師</span><span className="user-logout"><Popconfirm title="是否確認退出?" okText="退出" cancelText="取消"><LogoutOutlined /> 退出</Popconfirm></span></div></Header><Layout><Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"defaultSelectedKeys={['1']}items={items}style={{ height: '100%', borderRight: 0 }}></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;
}
樣式reset
npm install normalize.css
html,
body {margin: 0;height: 100%;
}#root {height: 100%;
}
二級路由配置
使用步驟
- 在 pages 目錄中,分別創建:Home(數據概覽)/Article(內容管理)/Publish(發布文章)頁面文件夾
- 分別在三個文件夾中創建 index.jsx 并創建基礎組件后導出
- 在
router/index.js
中配置嵌套子路由,在Layout
中配置二級路由出口 - 使用 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
router/index.js
import { createBrowserRouter } from 'react-router-dom'import Login from '@/pages/Login'
import Layout from '@/pages/Layout'
import Publish from '@/pages/Publish'
import Article from '@/pages/Article'
import Home from '@/pages/Home'
import { AuthRoute } from '@/components/Auth'const router = createBrowserRouter([{path: '/',element: (<AuthRoute><Layout /></AuthRoute>),children: [{index: true,element: <Home />,},{path: 'article',element: <Article />,},{path: 'publish',element: <Publish />,},],},{path: '/login',element: <Login />,},
])export default router
配置二級路由出口
<Layout className="layout-content" style={{ padding: 20 }}><Outlet />
</Layout>
路由菜單點擊交互實現
點擊菜單跳轉路由
import { Outlet, useNavigate } from 'react-router-dom'const items = [{label: '首頁',key: '/',icon: <HomeOutlined />,},{label: '文章管理',key: '/article',icon: <DiffOutlined />,},{label: '創建文章',key: '/publish',icon: <EditOutlined />,},
]const GeekLayout = () => {const navigate = useNavigate()const menuClick = (route) => {navigate(route.key)}return (<Menumode="inline"theme="dark"selectedKeys={selectedKey}items={items}style={{ height: '100%', borderRight: 0 }}onClick={menuClick}/> )
}
export default GeekLayout
菜單反向高亮
const GeekLayout = () => {// 省略部分代碼const location = useLocation()const selectedKey = location.pathnamereturn (<Layout><Header className="header"><div className="logo" /><div className="user-info"><span className="user-name">{name}</span><span className="user-logout"><Popconfirm title="是否確認退出?" okText="退出" cancelText="取消"><LogoutOutlined /> 退出</Popconfirm></span></div></Header><Layout><Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"selectedKeys={selectedKey}items={items}style={{ height: '100%', borderRight: 0 }}onClick={menuClickHandler}></Menu></Sider><Layout className="layout-content" style={{ padding: 20 }}><Outlet /></Layout></Layout></Layout>)
}
展示個人信息
實現步驟
- 在Redux的store中編寫獲取用戶信息的相關邏輯
- 在Layout組件中觸發action的執行
- 在Layout組件使用使用store中的數據進行用戶名的渲染
代碼實現
store/userStore.js
import { createSlice } from '@reduxjs/toolkit'
import { http } from '@/utils/request'
import { getToken, setToken } from '@/utils'
const userStore = createSlice({name: 'user',// 數據initialState: {token: getToken() || '',userInfo: {}},// 同步修改方法reducers: {setUserToken (state, action) {state.token = action.payload// 存入本地setToken(state.token)},setUserInfo (state, action) {state.userInfo = action.payload}}
})// 解構出actionCreater
const { setUserToken, setUserInfo } = userStore.actions// 獲取reducer函數
const userReducer = userStore.reducerconst fetchLogin = (loginForm) => {return async (dispatch) => {const res = await http.post('/authorizations', loginForm)dispatch(setUserToken(res.data.token))}
}const fetchUserInfo = () => {return async (dispatch) => {const res = await http.get('/user/profile')dispatch(setUserInfo(res.data))}
}export { fetchLogin, fetchUserInfo }export default userReducer
pages/Layout/index.js
// 省略部分代碼
import { fetchUserInfo } from '@/store/modules/user'
import { useDispatch, useSelector } from 'react-redux'const GeekLayout = () => {const dispatch = useDispatch()const name = useSelector(state => state.user.userInfo.name)useEffect(() => {dispatch(fetchUserInfo())}, [dispatch])return (<Layout><Header className="header"><div className="logo" /><div className="user-info"><span className="user-name">{name}</span><span className="user-logout"><Popconfirm title="是否確認退出?" okText="退出" cancelText="取消"><LogoutOutlined /> 退出</Popconfirm></span></div></Header><Layout><Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"defaultSelectedKeys={['1']}items={items}style={{ height: '100%', borderRight: 0 }}></Menu></Sider><Layout className="layout-content" style={{ padding: 20 }}><Outlet /></Layout></Layout></Layout>)
}
export default GeekLayout
退出登錄實現
實現步驟
- 為氣泡確認框添加確認回調事件
- 在
store/userStore.js
中新增退出登錄的action函數,在其中刪除token - 在回調事件中,調用userStore中的退出action
- 清除用戶信息,返回登錄頁面
代碼實現
store/modules/user.js
import { createSlice } from '@reduxjs/toolkit'
import { http } from '@/utils/request'
import { clearToken, getToken, setToken } from '@/utils'
const userStore = createSlice({name: 'user',// 數據initialState: {token: getToken() || '',userInfo: {}},// 同步修改方法reducers: {setUserToken (state, action) {state.token = action.payload// 存入本地setToken(state.token)},setUserInfo (state, action) {state.userInfo = action.payload},clearUserInfo (state) {state.token = ''state.userInfo = {}clearToken()}}
})// 解構出actionCreater
const { setUserToken, setUserInfo, clearUserInfo } = userStore.actions// 獲取reducer函數
const userReducer = userStore.reducerexport { fetchLogin, fetchUserInfo, clearUserInfo }export default userReducer
pages/Layout/index.js
const GeekLayout = () => {// 退出登錄const loginOut = () => {dispatch(clearUserInfo())navigator('/login')}return (<Layout><Header className="header"><div className="logo" /><div className="user-info"><span className="user-name">{name}</span><span className="user-logout"><Popconfirm title="是否確認退出?" okText="退出" cancelText="取消" onConfirm={loginOut}><LogoutOutlined /> 退出</Popconfirm></span></div></Header><Layout><Sider width={200} className="site-layout-background"><Menumode="inline"theme="dark"selectedKeys={selectedKey}items={items}style={{ height: '100%', borderRight: 0 }}onClick={menuClickHandler}></Menu></Sider><Layout className="layout-content" style={{ padding: 20 }}><Outlet /></Layout></Layout></Layout>)
}
處理Token失效
業務背景:如果用戶一段時間不做任何操作,到時之后應該清除所有過期用戶信息跳回到登錄
http.interceptors.response.use((response) => {// 2xx 范圍內的狀態碼都會觸發該函數。// 對響應數據做點什么return response.data
}, (error) => {// 超出 2xx 范圍的狀態碼都會觸發該函數。// 對響應錯誤做點什么console.dir(error)if (error.response.status === 401) {clearToken()router.navigate('/login')window.location.reload()}return Promise.reject(error)
})
首頁Home圖表展示
圖表基礎Demo實現
圖表類業務渲染,我們可以通過下面的順序來實現
- 跑通基礎DEMO
- 按照實際業務需求進行修改
安裝echarts
npm i echarts
實現基礎Demo
import { useEffect, useRef } from 'react'
import * as echarts from 'echarts'const Home = () => {const chartRef = useRef(null)useEffect(() => {// 1. 生成實例const myChart = echarts.init(chartRef.current)// 2. 準備圖表參數const option = {xAxis: {type: 'category',data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']},yAxis: {type: 'value'},series: [{data: [120, 200, 150, 80, 70, 110, 130],type: 'bar'}]}// 3. 渲染參數myChart.setOption(option)}, [])return (<div><div ref={chartRef} style={{ width: '400px', height: '300px' }} /></div >)
}export default Home
組件封裝
基礎抽象
import { useRef, useEffect } from 'react'
import * as echarts from 'echarts'const BarChart = () => {const chartRef = useRef(null)useEffect(() => {// 1. 生成實例const myChart = echarts.init(chartRef.current)// 2. 準備圖表參數const option = {xAxis: {type: 'category',data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']},yAxis: {type: 'value'},series: [{data: [120, 200, 150, 80, 70, 110, 130],type: 'bar'}]}// 3. 渲染參數myChart.setOption(option)}, [])return <div ref={chartRef} style={{ width: '400px', height: '300px' }}></div>
}export { BarChart }
抽象可變參數
import { useRef, useEffect } from 'react'
import * as echarts from 'echarts'const BarChart = ({ xData, sData, style = { width: '400px', height: '300px' } }) => {const chartRef = useRef(null)useEffect(() => {// 1. 生成實例const myChart = echarts.init(chartRef.current)// 2. 準備圖表參數const option = {xAxis: {type: 'category',data: xData},yAxis: {type: 'value'},series: [{data: sData,type: 'bar'}]}// 3. 渲染參數myChart.setOption(option)}, [sData, xData])return <div ref={chartRef} style={style}></div>
}export { BarChart }
import { BarChart } from './BarChart'const Home = () => {return (<div><BarChartxData={['Vue', 'React', 'Angular']}sData={[2000, 5000, 1000]} /><BarChartxData={['Vue', 'React', 'Angular']}sData={[200, 500, 100]}style={{ width: '500px', height: '400px' }} /></div >)
}export default Home