基础姿势要过一遍吗?请点这里!
react常用的ui组件库
pc antdesign
移动 antdesignMobile
蚂蚁金服
快速上手
cnpm i antd redux redux-thunk react-redux react-router-dom -S
cnpm i customize-cra less less-loader node-sass -D
使用
入口函数中引入css样式文件,组件文件导入组件使用
index.js
import "antd/dist/antd.css"
import { Button } from 'antd';
less配置实现自定义主题
按照 配置主题 的要求,自定义主题需要用到类似 less-loader 提供的 less 变量覆盖功能。我们可以引入 craco-less 来帮助加载 less 样式和修改变量。
cnpm i @craco/craco craco-less -D
修改package.json
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
}
在项目文件加下创建craco.config.js
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true,
},
},
},
},
],
};
国际化
- 组件国际化
默认antd里的组件文字语言为Eng,需要改下
import zhCN from 'antd/es/locale/zh_CN';
import {ConfigProvider} from 'antd';
return (
<ConfigProvider locale={zhCN}>
<App />
</ConfigProvider>
);
- 项目国际化
i18next
后台接口管理(模拟)
easy-mock.com
rap2.taobao.org (header参数设置为必选有问题)
项目路由分析
- 登录页
- 404
- 首页,管理系统主页
- 设置页
- 文章列表
- 文章编辑
- 文章添加
路由管理
src/routes/index.js
import Login from '../views/Login'
import NotFound from '../views/NotFound'
import DashBoard from '../views/DashBoard'
import ArtList from '../views/Article'
import ArtEdit from '../views/Article/ArtEdit'
import ArtAdd from '../views/Article/ArtAdd'
import Settings from '../views/Article/Settings'
const baseRoutes = [{
path: '/login',
component: Login
},{
path: '/404',
component: NotFound
}];
const adminRoutes = [{
path: '/dashBoard',
component: DashBoard
},{
path: '/artList',
component: ArtList
},{
path: '/settings',
component: Settings
},{
path: '/artEdit',
component: ArtEdit
},{
path: '/artAdd',
component: ArtAdd
}]
export {
baseRoutes,
adminRoutes
}
一级路由渲染
src/index.js
import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import "antd/dist/antd.less"
import zhCN from 'antd/es/locale/zh_CN';
import {HashRouter as Router, Switch, Route, Redirect} from 'react-router-dom'
import {ConfigProvider} from 'antd'
import {baseRoutes} from './routes'
ReactDom.render(
<Router>
<ConfigProvider locale={zhCN}>
{/* 渲染基础路由 */}
<Switch>
{
baseRoutes.map(el=>{
return (
<Route key={el.path} path={el.path} component={el.component}/>
)
})
}
<Route path="/admin" component={App}/>
<Redirect to="/admin" from="/" exact />
<Redirect to="/404"/>
</Switch>
</ConfigProvider>
</Router>,
document.querySelector("#root")
)
后台系统主界面局部刷新功能实现
将主界面组件(Admin)单独提炼出来,将组件在App
组件中使用,实际上App
组件才是主界面组件,只是让组件文件更精简些
局部刷新:布局main部分,是子路由,通过this.props.children渲染
注意Layout布局记得引入css样式
Layout aside侧边导航渲染,要给路由一些配置信息
src/routes/index.js
import Login from '../views/Login'
import NotFound from '../views/NotFound'
import DashBoard from '../views/DashBoard'
import Settings from '../views/Settings'
import ArtList from '../views/Article'
import ArtEdit from '../views/Article/ArtEdit'
import ArtAdd from '../views/Article/ArtAdd'
// 导入图标
import {RadarChartOutlined,UnorderedListOutlined,SettingOutlined} from '@ant-design/icons'
const baseRoutes = [{
path: '/login',
component: Login
},{
path: '/404',
component: NotFound
}];
const adminRoutes = [{
path: '/dashBoard',
component: DashBoard,
title: '仪表盘',
icon: RadarChartOutlined,
isNav: true
},{
path: '/settings',
component: Settings,
icon: SettingOutlined,
title: '设置',
isNav: true
},{
path: '/artList',
component: ArtList,
title: '文章管理',
icon: UnorderedListOutlined,
isNav: true
},{
path: '/artEdit',
component: ArtEdit,
title: '编辑文章',
isNav: false
},{
path: '/artAdd',
component: ArtAdd,
title: '添加文章',
isNav: false
}]
export {
baseRoutes,
adminRoutes
}
Layout中 Menu 组件icon属性值类型是ReactNode,要通过导入依赖@ant-design/icons
获得
// 由于Admin是单独提炼的组件,不是路由组件,props上没有路由信息,需要使用高阶组件withRouter
import { withRouter } from 'react-router-dom'
// 导入二级路由
import { adminRoutes } from '../../routes'
// 过滤得到侧边栏
const navRoutes = adminRoutes.filter((el)=>el.isNav)
...
var {history} = this.props
<Menu
theme="dark"
mode="inline"
defaultSelectedKeys={['1']}
onClick={({key})=>{
// 组件的点击事件实现二级路由切换
history.push('/admin'+key)
}}>
{
navRoutes.map(el=>{
return(
<Menu.Item key={el.path} icon={<el.icon />}>
{el.title}
</Menu.Item>
)
})
}
</Menu>
···
<Content
className="site-layout-background"
style={{
margin: '24px 16px',
padding: 24,
minHeight: 280,
}}
>
{/*局部路由切换实现*/}
{this.props.children}
</Content>
...
export default withRouter(Admin)
主界面组件使用 二级路由配置(记得配404)
App是路由组件,Admin不是路由组件
src/App.js
import React from 'react'
import Admin from './components/Admin'
import { Switch,Route,Redirect } from 'react-router-dom'
// 导入二级路由
import { adminRoutes } from './routes'
const App = () => {
return (
<div>
<Admin>
<Switch>
{
adminRoutes.map(el=>{
return (
<Route key={el.path} path={'/admin'+el.path} component={el.component}/>
)
})
}
<Redirect to="/admin/dashBoard" from="/admin" exact/>
<Redirect to="/404"/>
</Switch>
</Admin>
</div>
)
}
export default App
列表页渲染使用Table、Card
src/views/Article/index.js
columns
数组指定Table
组件的字段信息
render
属性可以进行同一的行渲染
import React, { Component } from 'react'
import { Card, Button, Table } from 'antd';
// 指定表格的数据源 dataSource 为一个数组。
const dataSource = [
{
key: '1',
name: '胡彦斌',
age: 32,
address: '西湖区湖底公园1号'
},
{
key: '2',
name: '胡彦祖',
age: 42,
address: '西湖区湖底公园1号',
}
];
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: '住址',
dataIndex: 'address',
key: 'address',
},
{
title: '操作',
dataIndex: 'control',
key: 'control',
render: ()=>{
return (
<>
<Button type="primary">编辑</Button>
<Button type="danger" style={{marginLeft: 5}}>删除</Button>
</>
)
}
},
];
export default class ArticleList extends Component {
render() {
return (
<div>
<Card
title={
<>
<span>文章管理</span>
<Button type="primary" style={{marginLeft: 5}}>添加</Button>
</>
}
extra={
<>
<Button>导出</Button>
</>
}>
<Table
loading={false}
dataSource={dataSource}
columns={columns}
pagination={{
position: ['bottomCenter '],
pageSize: 5,
total:20,
onChange(page, pageSize){
console.log(page, pageSize)
}
}} />;
</Card>
</div>
)
}
}
craco别名配置
- 直接改
craco.config.js
const path = require('path');
webpack: {
alias: {
"@": path.resolve(__dirname+'/src')
},
configure: (webpackConfig, { env, paths }) => { return webpackConfig; }
},
- 用依赖
npm i -D craco-alias
const CracoAlias = require("craco-alias");
plugins: [
{
plugin: CracoAlias,
options: {
source: "options",
baseUrl: "./",
aliases: {
"@": "./src"
}
}
}
]
craco配置代理
craco.config.js
module.exports = {
devServer: {
port: 9527,
proxy: {
'/api': {
target: 'http://rap2.taobao.org:38080/app/mock/259725',
ws: false,
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
};
配置axios默认信息
src/utils/http.js
import axios from 'axios'
const http = axios.create({
baseURL: '/api'
})
http.interceptors.request.use(function (config) {
return config
}, function (error) {
return Promise.reject(error)
})
http.interceptors.response.use(function (response) {
return response
}, function (error) {
return Promise.reject(error)
})
export default http
接口文件中使用
src/api/index.js
import http from "@/utils/http"
// ***这些api是函数!!!!
// 请求文章列表数据
const getArtList = (params={}) => http.post('/api/v1/artList',params)
// 按id删除列表数据
const delArtItem = (id) => http.post('/api/v1/delArtItem',{id})
// 根据id获取列表数据
const getArtItem = (id) => http.post('/api/v1/getArtItem',{id})
export {
getArtList,
delArtItem,
getArtItem
}
excel导出
https://github.com/SheetJS/sheetjs/blob/master/demos/react/sheetjs.jsx
cnpm i js-xlsx -S
cnpm i xlsx -S
使用
import XLSX from 'xlsx'
...
this.state = {
// 导出的excel数据(二维数组)
data: [['行标题1','行标题2'],['第一行内容一','第一行内容二'],['第二行内容一','第二行内容二']]
}
···
// 导出excel文件
exportFile = ()=> {
const {data,list} = this.state
data.push(Object.keys(list[0]))
list.forEach(el=>{
data.push(Object.values(el))
})
const ws = XLSX.utils.aoa_to_sheet(this.state.data);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "SheetJS");
XLSX.writeFile(wb, "文章列表.xlsx")
}
文章编辑页
点击按钮进入编辑页传入id
<Button
type="primary"
onClick={()=>{
this.props.history.push('/admin/artEdit/'+record.id)
}}>编辑</Button>
动态路由
{
path:"/artEdit/:id",
title:"文章编辑",
isNav:false,
component:Loadable({
loader: () => import('../views/Article/ArtEdit'),
loading: Loading,
})
}
获取
componentWillUnmount () {
console.log(this.props.match.params.id)
}
antd initialValues组件
表单默认值,只有初始化以及重置时生效
当表单数据为state时,在componentDidMount生命周期里面发请求获取数据,但这时候表单元素已经挂载完成了,可以写个三目渲染
return (
this.state.dataList.id
?
<Form
{...this.state.layout}
name="basic"
initialValues={this.state.dataList}
onFinish={this.onFinish}
onFinishFailed={()=>{}}
>
</Form>
:
<Loading/>
)
componentDidMount () {
getArtItem(this.props.match.params.id).then(res=>{
let {item} = res.data
this.setState({
// 等state有数据了,条件不为undefined假,渲染from,初始化,可读initialValues
dataList: item
})
})
}
富文本编辑器
TinyMCE UEditor WangEditor
使用通常3点:
- 初始化
- 默认值
- 获取编辑好的超文本
WangEditor在antd-react项目中使用
安装
cnpm i wangeditor
import Editor from 'wangeditor'
constructor (){
super()
this.state = {
// 定义富文本编译器
editor: null
}
}
<Form.Item
label="内容"
name="content"
rules={[{ required: false}]}
>
// 定义富文本编辑器容器
// 注意:***它给自身加了z-index为10000;Modal确认层遮罩层要考虑它的z-index
<div id="txt"></div>
</Form.Item>
// 注意千万不要在更新生命钩子里更新数据
componentDidMount () {
getArtItem(this.props.match.params.id).then(res=>{
let {item} = res.data
this.setState({
dataList: item
},()=>{
// setState是异步的,dom可能创好了,但感觉不好
// 1.** 创建富文本实例
this.setState({
editor: new Editor('#txt')
},()=>{
this.state.editor.create()
// 2.** 设置富文本编辑器默认值
this.state.editor.txt.html(this.state.dataList.content)
})
})
})
}
// 3.** 获取编辑后的超文本
submitData.content = this.state.editor.txt.html()
Antd Modal组件(弹出确认层)
import {Modal,message} from 'antd'
constructor (){
super()
this.state = {
// 遮罩层的显隐
modalVisible: false,
submitData: {}
}
}
// 设置遮罩层的显隐
setModalVisible(modalVisible) {
this.setState({ modalVisible })
}
<Modal
title="20px to Top"
visible={this.state.modalVisible}
onOk={this.onOk}
onCancel={() => this.setModalVisible(false)}
>
<p>确定保存修改吗?</p>
</Modal>
// 询问层确认执行的回调
onOk = () => {
this.setModalVisible(false)
updateArtItem(this.state.submitData).then(res=>{
// 注意:message为对象时,content接受的参数类型为ReactNode
var Msg = ()=>{
return (
<span>{res.data.msg}</span>
)
}
message.success({
content: <Msg/>,
duration: 0.3,
onClose: ()=>{
this.props.history.go(-1)
}
})
})
}
// 表单api 提交表单且数据验证成功后回调事件
onFinish= (submitData)=>{
submitData.id = this.props.match.params.id
submitData.content = this.state.editor.txt.html()
this.setState({
submitData
})
this.setModalVisible(true)
}
图表绘制
echart的使用
https://echarts.apache.org/zh/index.html
https://gallery.echartsjs.com/explore.html#sort=ranktimeframe=allauthor=all
cnpm i echarts -S
<!-- 为 ECharts 准备一个具备大小(宽高)的 DOM -->
<div id="main" style="width: 600px;height:400px;"></div>
import echarts from 'echarts'
var myChart = echarts.init(document.getElementById('main'));
// 指定图表的配置项和数据
var option = {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
legend: {
data:['销量']
},
xAxis: {
data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
};
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
行转列工具方法
@/utils/index.js
const row2Col = (dataList,key,title) => {
// 返回:
// {
// name: 传入的title
// data: 字段所有的数据,是一个数组
// }
let obj = {};
let keys = Object.keys(dataList[0]);
let index = keys.indexOf(key);
var arr = [];
dataList.forEach(el=>{
arr.push(Object.values(el)[index])
})
obj = {
title,
data: arr
}
return obj
}
export {
row2Col
}
在项目中使用
import echarts from 'echarts'
import { row2Col } from '@/utils'
this.state = {
myChart: null,
option: {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
legend: {
data: ['销量','价格']
},
series: [],
xAxis: {
data: []
},
yAxis: {}
}
}
}
// 通过ref获取dom对象
<Col span={10}>
<div id="chart" ref={(dom) => this.chart = dom}></div>
</Col>
componentDidMount() {
setTimeout(()=>{
let data = [{
name: '衬衫',
sale: 5,
price: 5
},{
name: '羊毛衫',
sale: 8,
price: 6
},{
name: '雪纺衫',
sale: 7,
price: 5.5
},{
name: '裤子',
sale: 11,
price: 6
},{
name: '高跟鞋',
sale: 13,
price: 3
},{
name: '袜子',
sale: 3,
price: 1
}]
var xAxis = row2Col(data,'name','')
let {option} = this.state
option.xAxis.data = xAxis.data
var s1 = row2Col(data,'sale','销量')
option.series.push({
name: s1.title,
type: 'bar',
data: s1.data
})
var s2 = row2Col(data,'price','价格')
option.series.push({
name: s2.title,
type: 'bar',
data: s2.data
})
this.setState(()=>{
return {
myChart: echarts.init(this.chart),
option
}
}, () => {
console.log(this.state.option)
this.state.myChart.setOption(this.state.option);
})
},2000)
}
redux的使用(登录)
(1) 建store仓库
pro/src/store/index.js
import {createStore,applyMiddleware,compose} from 'redux'
import rootReducer from './reducer'
// 支持异步action
import thunk from 'redux-thunk'
// 合并函数处理多个中间件
let enhancer = compose(
applyMiddleware(thunk),
(window &&window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__() : (f) => f)
const store = createStore(rootReducer,enhancer)
export default store
pro/src/store/reducer.js
import userReducer from './user'
// 模块化管理
import {combineReducers} from 'redux'
let rootReducer = combineReducers({
userReducer
})
export default rootReducer
模块化管理
pro/src/store/user/actionCreator.js
import {login} from '@/api'
import { message } from 'antd'
import React from 'react'
import {SET_USER_INFO} from './actionTypes'
var setUserInfo = (userInfo) => {
return {
type: SET_USER_INFO,
userInfo
}
}
var loginAsync = (formData,push) => {
return (dispatch) => {
login(formData).then(res=>{
console.log(res)
let {msg,data:{token},code} = res.data
var Msg = ()=>{
return (
<span>{msg}</span>
)
}
if(code === 200){
window.localStorage.setItem('accessToken1',token)
message.success({
content: <Msg/>,
duration: 0.3,
onClose: ()=>{
dispatch(setUserInfo(res.data.data))
push('/admin')
}
})
}
})
}
}
export {
loginAsync
}
pro/src/store/user/actionTypes.js
const SET_USER_INFO = 'setUserInfo'
export {
SET_USER_INFO
}
pro/src/store/user/index.js
import {SET_USER_INFO} from './actionTypes'
// 持久化缓存
var user = localStorage.getItem('userInfo')?JSON.parse(localStorage.getItem('userInfo')):null
var defalutState = {
name: '',
icon: '',
role: '',
token: ''
}
if (user) {
defalutState = user
}
var userReducer = (state=defalutState,action) => {
// 深克隆
var newState = JSON.parse(JSON.stringify(state))
// 处理action
switch(action.type){
case SET_USER_INFO:
newState.name = action.userInfo.name
newState.icon = action.userInfo.icon
newState.role = action.userInfo.role
newState.token = action.userInfo.token
localStorage.setItem('userInfo',JSON.stringify(newState))
break;
default:
break;
}
return newState
}
export default userReducer
(2)使用
redux是解耦的,使用依赖于store,通过使用react-redux更方便的使用redux
包裹根组件
import React from 'react'
import ReactDom from 'react-dom'
// 让store可以在整个组件树中使用
import {HashRouter as Router, Switch, Route, Redirect} from 'react-router-dom'
import {Provider} from 'react-redux'
import store from '@/store'
ReactDom.render(
<Router>
<Provider store={store}>
...
</Provider>
</Router>,
document.querySelector("#root"))
组件中使用(登录成功时将用户信息存储起来)
pro/src/views/Login/index.js
// 更方便的使用redux,将方法扔到props上
import { connect } from 'react-redux'
import {loginAsync} from '@/store/user/actionCreator'
class Login extends Component {
render(){
...
}
// 表单提交
onFinish = (v) => {
this.props.loginAsync(v,this.props.history.push)
}
}
const mapStateToProps = () => {
return {}
}
const mapDispatchToProps = (dispatch) => {
return {
loginAsync: (formData,push) => {
dispatch(loginAsync(formData,push))
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Login)
状态提升,使用状态的时候记得在父组件中mapStateToProps,通过设置props可以在子组件内部使用
pro/src/App.js
import React,{Component} from 'react'
import Admin from './components/Admin'
import { Switch,Route,Redirect } from 'react-router-dom'
// 导入二级路由
import { adminRoutes } from './routes'
// 更方便的使用redux
import {connect} from 'react-redux'
class App extends Component {
render(){
return (
<div>
{/* 要在组件自己内部使用,通过props传下去 */}
<Admin
icon={this.props.icon}
name={this.props.name}>
</Admin>
<div>
)
}
}
const mapStateToProps = (state) => {
return {
icon: state.userReducer.icon,
name: state.userReducer.name,
role: state.userReducer.role,
}
}
export default connect(mapStateToProps)(App)
权限系统
- 主前端,根据登录的身份来判断权限
弊端:不利于维护,后期要修改路由太麻烦了(可访问角色表,单独渲染路由)
(1)前端路由信息配置添加roles属性,标记可访问的角色
pro/src/routes/index.js
const adminRoutes = [{
path: '/dashBoard',
component: Loadable({
loader: () => import('@/views/DashBoard'),
loading: Loading
}),
title: '仪表盘',
icon: RadarChartOutlined,
isNav: true,
roles: ['001','002']
},{
path: '/artList',
component: Loadable({
loader: () => import('@/views/Article'),
loading: Loading,
}),
title: '文章管理',
icon: UnorderedListOutlined,
isNav: true,
roles: ['001','002']
},{
path: '/settings',
component: Loadable({
loader: () => import('@/views/Settings'),
loading: Loading,
}),
icon: SettingOutlined,
title: '设置',
isNav: true,
roles: ['002']
},{
path: '/artEdit/:id',
component: Loadable({
loader: () => import('@/views/Article/ArtEdit'),
loading: Loading,
}),
title: '编辑文章',
isNav: false,
roles: ['001','002']
},{
path: '/artAdd',
component: Loadable({
loader: () => import('@/views/Article/ArtAdd'),
loading: Loading,
}),
title: '添加文章',
isNav: false,
roles: ['001','002']
}]
(2) 使用Route的render属性渲染组件(记住render渲染的路由组件没有路由信息,要参数传过去)
pro/src/App.js
class App extends Component {
render(){
return (
<div>
{/* 要在组件自己内部使用,通过props传下去 */}
<Admin
icon={this.props.icon}
name={this.props.name}>
<Switch>
<Route path="/admin/noAuth" component={NoAuth}/>
{
adminRoutes.map(el=>{
return (
// 用户权限系统
// 1. 创建2级路由/admin/noAuth 无权限提示页面
// 2. 前端路由信息中,每个路由存储可访问身份列表,roles数组(['001','002'])
// 3. 登录成功的接口返回用户信息(其中有该用户的身份标识role),redux持久化存储
// 4. 循环创建路由组件Route,用render代替component对接组件
// 5. 用数组的some方法判断用户是否可访问该页面,即render是渲染还是重定向(记住,render方法的组件没有props,要将函数的预置形参传过去)
<Route
key={el.path}
path={'/admin'+el.path}
render={(routerProps) => {
return (
// 判断这个角色是否有权限访问这个路由
el.roles.some(el=>el===this.props.role)
?
<el.component {...routerProps}/>
:
<Redirect to="/admin/noAuth"/>
)
}}/>
)
})
}
<Redirect to="/admin/dashBoard" from="/admin" exact/>
<Redirect to="/404"/>
</Switch>
</Admin>
</div>
)
}
}
- 后端维护,但前端业务逻辑相对难一点
登录完成时,后端返回一个路由表
后端不可能返回一个组件的,顶多返回一个组件名称
{
"msg": "登录成功",
"code": 0,
"data": {
"adminRoutes": [{
"path": "/dashBoard",
"component": "DashBoard",
"title": "仪表盘",
"icon": "RadarChartOutlined",
"isNav": true
},{
"path": "/artList",
"component": "ArtList",
"title": "文章管理",
"icon": "UnorderedListOutlined",
"isNav": true
},{
"path": "/settings",
"component": "Settings",
"icon": "SettingOutlined",
"title": "设置",
"isNav": true
},{
"path": "/artEdit/:id",
"component": "ArtEdit",
"title": "编辑文章",
"isNav": false
},{
"path": "/artAdd",
"component": "ArtAdd",
"title": "添加文章",
"isNav": false
}]
}
}
- 设置路由组件(主)
(1)获取后台传过来的路由表,本地缓存一下
(2)由于后台只能传组件名,这边要先导入组件,然后扔对象里面去,(图标组件只有列表菜单需要),再之后根据组件名重置路由表
(3) 将重置过的路由表拿来渲染路由组件,记得加个三目,数组判断要用length判断,jsx ?<>{}</>:
回顾一下:
- 组件作为 /admin 的路由组件
- 整个后台系统界面结构提炼为一个 组件
- 在组件中使用组件
- 在组件中配置路由组件
- 组件内部使用了layout布局,main部分通过
this.props.children
获取路由组件,实现局部更新
pro/src/App.js
import React,{Component} from 'react'
import Admin from './components/Admin'
import { Switch,Route,Redirect } from 'react-router-dom'
// 更方便的使用redux
import {connect} from 'react-redux'
// 使用路由懒加载
import Loadable from 'react-loadable';
// 导入loading界面组件
import Loading from '@/components/Loading'
// 导入图标
import {
RadarChartOutlined,
UnorderedListOutlined,
SettingOutlined
} from '@ant-design/icons'
// 获取图标组件
const icons = {
RadarChartOutlined,
UnorderedListOutlined,
SettingOutlined
}
// 获取组件
const components = {
DashBoard: Loadable({
loader: () => import('@/views/DashBoard'),
loading: Loading
}),
ArtList: Loadable({
loader: () => import('@/views/Article'),
loading: Loading
}),
Settings: Loadable({
loader: () => import('@/views/Settings'),
loading: Loading
}),
ArtEdit: Loadable({
loader: () => import('@/views/Article/ArtEdit'),
loading: Loading
}),
ArtAdd: Loadable({
loader: () => import('@/views/Article/ArtAdd'),
loading: Loading
})
}
render(){
return (
<div>
{/* 要在组件自己内部使用,通过props传下去 */}
<Admin
icon={this.props.icon}
name={this.props.name}
adminRoutes={this.state.adminRoutes}>
<Switch>
{
this.state.adminRoutes.length
?
<>
{
this.state.adminRoutes.map(el=>{
return (
<Route
key={el.path}
path={'/admin'+el.path}
component={el.component}/>
)
})
}
<Redirect to="/admin/dashBoard" from="/admin" exact/>
{/* <Redirect to="/404"/> */}
</>
:
<Loading/>
}
</Switch>
</Admin>
</div>
)
}
componentDidMount () {
if (!localStorage.getItem('adminRoutes')){
// 本地找不到
getAdminRoutes.then(adminRoutes=>{
// 本地存一下
localStorage.setItem('adminRoutes',JSON.stringify(adminRoutes))
// 根据后端传过来的图标组件名赋值为真的组件
adminRoutes.forEach(el=>{
if (el.isNav){
el.icon = icons[el.icon]
}
})
// 根据后端传过来的组件名赋值为真的组件
adminRoutes.forEach(el=>{
el.component = components[el.component]
})
this.setState({
adminRoutes
},()=>{
var Msg = () => {
return (
<span>路由信息加载完成</span>
)
}
message.success({
content: <Msg/>,
duration: 0.5
})
})
})
} else {
var adminRoutes = JSON.parse(localStorage.getItem('adminRoutes'))
// 根据后端传过来的图标组件名赋值为真的组件
adminRoutes.forEach(el=>{
el.icon = icons[el.icon]
})
// 根据后端传过来的组件名赋值为真的组件
adminRoutes.forEach(el=>{
el.component = components[el.component]
})
this.setState({
adminRoutes
},()=>{
var Msg = () => {
return (
<span>本地路由信息加载完成</span>
)
}
message.success({
content: <Msg/>,
duration: 0.5
})
})
}
}
- 配置目录菜单的路由指向
需要注意的是:- Menu组件中遍历渲染Menu.item会出错,原因是Menu的事件选择器是事件委托型的,好吧,大概这意思;解决方法没路由表示直接不渲染Menu,把三目给Menu
- 对传入的props要进行修改(通过传入的路由表isNav属性晒选出目录),使用
static getDerivedStateFromProps()
生命钩子创建目录组件表
pro/src/components/Admin/index.js
// 基于外部的props生成states
static getDerivedStateFromProps(props){
// 过滤得到侧边栏
const navRoutes = props.adminRoutes.filter((el)=>el.isNav)
console.log(navRoutes)
return{
navRoutes
}
}
render() {
return (
<div id="myadmin">
<Layout>
<Sider trigger={null} collapsible collapsed={this.state.collapsed}>
<div className="logo" />
{
this.state.navRoutes.length
?
<Menu
theme="dark"
mode="inline"
defaultSelectedKeys={['1']}
onClick={({key})=>{
history.push('/admin'+key)
}}>
{
this.state.navRoutes.map(el=>{
return(
<Menu.Item key={el.path} icon={<el.icon />}>
{el.title}
</Menu.Item>
)
})
}
</Menu>
:
''
}
</Sider>
</Layout>
</div>
)