合肥千峰前端培训---React写后台管理注意点(使用AntDesign)

基础姿势要过一遍吗?请点这里!

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别名配置

  1. 直接改
    craco.config.js
const path = require('path');

  webpack: {
      alias: {
        "@": path.resolve(__dirname+'/src')
      },
      configure: (webpackConfig, { env, paths }) => { return webpackConfig; }
  },
  1. 用依赖
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点:

  1. 初始化
  2. 默认值
  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. 主前端,根据登录的身份来判断权限
    弊端:不利于维护,后期要修改路由太麻烦了(可访问角色表,单独渲染路由)

(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>
    )
  }
}
  1. 后端维护,但前端业务逻辑相对难一点

登录完成时,后端返回一个路由表
后端不可能返回一个组件的,顶多返回一个组件名称

{
  "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. 设置路由组件(主)
    (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
      })
    })
  }
}
  1. 配置目录菜单的路由指向
    需要注意的是:
    • 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>
  )
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值