用React+Antd写一个后台小项目

安装React脚手架不多说了,上一个代码:


npx create-react-app my-app

一.使用SCSS预处理器

  1. 装包:yarn add sass -D 需要安装,react中内置了SASS的配置,所以装包就行,生产环境不需要这个包,加-D;

  1. 创建全局样式文件:index.scss


body{
    margin: 0;
}
#root{
    height: 100%;
}

二.配置基础路由

  1. 安装路由:react-router-dom;

  1. pages下创建layout和login两个文件夹

App.js中代码:


import React from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import Layout from './pages/Layout'
import Login from './pages/Login'
export default function App() {
  return (
    //配置路由
    
      
{/* 创建路由path和对应组件关系 */} }> }>
) }

三.antd组件库的使用


npm i antd -S

导入antd样式文件


import 'antd/dist/antd.min.css'

四.配置别名路径(不做了,我的报错)

  1. 用@代替根路径src

  1. .安装修改CRA配置的包:yarn add -D @craco/craco;

  1. 项目根目录下创建文件 craco.config.js


//添加自定义对于webpack的配置
const path = require('path')
module.exports = {
    //webpack配置
    webpack: {
        //配置别名
        alias: {
            //约定:使用@表示src文件所在路径
            '@':path.resolve(__dirname,'src')
        }
    }
}

package.json


//将 start/build/test 三个命令改为craco方式
 "scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject"
  },

五.@别名路径提示

在根目录下创建jsconfig.json配置文件


{
    "compilerOptions":{
        "baseUrl":"./",
        "paths":{
            "@/*":["src/*"]
        }
    }
}

六.登录模块

(1)登录表单: pages/Login/index.js:


import { Card } from 'antd'
import logo from '../../assets/images/logo.png'
import './index.scss'//导入样式文件
export default function Login() {
    return (
        
{/* 登录表单 */}
) }

样式文件index.scss


.login {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    background: center/cover url('../../assets/images/login.png');
  
    .login-logo {
      width: 200px;
      height: 60px;
      display: block;
      margin: 0 auto 20px;
    }
  
    .login-container {
      width: 440px;
      height: 360px;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      box-shadow: 0 0 50px rgba(0, 0, 0, 0.1);
    }
  
    .login-checkbox-label {
      color: #1890ff;
    }
  }
  

(2)创建表单结构+添加校验功能

为Form.Item添加rules属性,用来添加表单校验.


import { Card, Form, Input, Checkbox, Button,message } from 'antd'
import logo from '../../assets/images/logo.png'
import './index.scss'//导入样式文件
export default function Login() {
    return (
        
{/* 登录表单 */} {/* 子项用到的触发事件 需要在Form中都声明一下才可以 */}
我已阅读并同意「用户协议」和「隐私条款」
) }

(3)获取登录表单数据

1.为Form组件添加onFinish属性,该事件会在点击登录按钮时触发;

2.创建onFinish函数,通过函数参数values拿到表单值,

  1. Form组件添加initialValues属性,来初始化表单值.


     function onFinish(values) {
        //values:放置的是所有表单项中用户输入的内容
        console.log(values);
    }
   

(4)登录axios统一封装处理


npm i axios -S

1.创建utils/http.js文件;

2.创建axios实例,配置baseURL,请求拦截器,响应拦截器;

在utils/http.js中,统一导出http.


// 封装axios
// 实例化  请求拦截器 响应拦截器

import axios from 'axios'
const http = axios.create({
    baseURL: 'http://geek.itheima.net/v1_0',
    timeout: 5000
})
// 添加请求拦截器
http.interceptors.request.use((config) => {
    return config
}, (error) => {
    return Promise.reject(error)
})
// 添加响应拦截器
http.interceptors.response.use((response) => {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response
}, (error) => {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    console.dir(error)

    return Promise.reject(error)
})
export { http }

(5)配置登录Mobx

Mobx类似redux和vuex.

装包:yarn add mobx mobx-react-lite,管理用户登录的store

store/login.Store.js中:(接口应该是有错了,报400错误)


// login 模块
import { makeAutoObservable } from 'mobx'
import { http } from '../utils'
class LoginStore {
    token = ''
    constructor() {
        //响应式
        makeAutoObservable(this)
    }
    getToken = async ({ mobile, code }) => {
        //调用登录接口
        const res = await http.post('http://geek.itheima.net/v1_0/authorizations', {
            mobile, code
        })
        //存入token
        this.token = res.data.token
        
    }
}
export default LoginStore

store/index.js中,管理所有的模块


// 把所有的模块做统一处理
// 导出一个统一的方法 useStore
import React from "react"
import LoginStore from './login.Store'
class RootStore{
    constructor() {
        this.loginStore = new LoginStore()
    }
}


// 实例化根
// 导出useStore context
const rootStore = new RootStore()
const context = React.createContext(rootStore)
const useStore = () => React.useContext(context)

export { useStore }

(6)token持久化

1.封装工具函数

创建utils/token.js


// 封装ls存取token

const key = 'pc-key'

const setToken = (token) => {
  return window.localStorage.setItem(key, token)
}

const getToken = () => {
  return window.localStorage.getItem(key)
}

const removeToken = () => {
  return window.localStorage.removeItem(key)
}

export {
  setToken,
  getToken,
  removeToken
}

在utils/index.js统一封装的工具函数中:


// 先把所有的工具函数导出的模块在这里导入
// 然后再统一导出
import { http } from './http'
import {
  setToken,
  getToken,
  removeToken
} from './token'




export {
  http,
  setToken,
  getToken,
  removeToken
}

在store/login.Store.js中新增下列代码:


import { setToken,getToken} from '../utils'

  token =getToken() || ''//取的时候优先从本地拿token
  setToken(this.token)//往本地存一份

(7)请求拦截器注入token

在utils/http.js中新增下列代码


import {getToken} from './token'//新增
// 添加请求拦截器
http.interceptors.request.use((config) => {
    const token = getToken()//新增
    if (token) {                    //新增
        config.headers.Authorization = `Bearer ${token}`
    }
    return config
}, (error) => {
    return Promise.reject(error)
})

(8)路由鉴权实现

思路:自己封装AuthRoute路由鉴权高阶组件,实现未登陆拦截,并返回到登录页面.

判断本地是否有token,有的话,就返回子组件,否则就重定向到登录Login.

components/AuthComponent.js中:


// 1. 判断token是否存在
// 2. 如果存在 直接正常渲染
// 3. 如果不存在 重定向到登录路由

// 高阶组件:把一个组件当成另外一个组件的参数传入
// 然后通过一定的判断 返回新的组件
import { getToken } from '../utils'
import { Navigate } from 'react-router-dom'
function AuthComponent({ children }) {
    const isToken = getToken()
    if (isToken) {
        return <>{children}
    } else {
        return 
    }
}
//   
// 登录:<>--登录就渲染对应的组件
// 非登录:

export {
    AuthComponent
}

在App.js中:(接口有误,我自己项目就不添加这步了)


import { AuthComponent } from './components/AuthComponent'
  

                    {/* 创建路由path和对应组件关系 */}
                    {/* Layout需要鉴权处理的 */}
                    {/* 这里的Layout一定不能写死 要根据是否登录进行判断 */}
                    //包裹一下
                            
                        

                    }>
                        {/* 这个不需要,登录页嘛 */}
                    }>

                

看下登录页面的效果静态图:

用React+Antd写一个后台小项目_第1张图片

五.layout模块

看下要实现的效果图:

用React+Antd写一个后台小项目_第2张图片

(1)解决layout首页显示高度的问题

App.scss中:


.App{
    height: 100vh;
}

Layout/index.js结构:


import { Layout, Menu, Popconfirm } from 'antd'
import { Outlet, Link } from 'react-router-dom'

import {
  HomeOutlined,
  DiffOutlined,
  EditOutlined,
  LogoutOutlined
} from '@ant-design/icons'
import './index.scss'

const { Header, Sider } = Layout

const GeekLayout = () => {
 
  return (
    
      
用户名 < LogoutOutlined/> 退出
{/* 高亮原理:defaultSelectedKeys === item key */} {/* 获取当前激活的path路径? */} {/* defaultSelectedKeys: 初始化渲染的时候生效一次 selectedKeys: 每次有值更新时都会重新渲染视图 */} } key="/"> 数据概览 } key="/article"> 内容管理 } key="/publish"> 发布文章 {/* 二级路由出口 */}
) } export default GeekLayout

样式index.scss


.ant-layout {
    height: 100%;
  }
  
  .header {
    padding: 0;
  }
  
  .logo {
    width: 200px;
    height: 60px;
    background: url('../../assets//images/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)二级路由跳转

新建Home,Article和Publish三个文件

在App.js中引入,并注册二级路由,默认展示Home页:


import Article from './pages/Article'
import Home from './pages/Home'
import Publish from './pages/Publish'

 }>
             }>
               }>
                 }>
   

此处注意,必须要在Layout/index.js中,Link可以实现跳转到响应的页面


import { Outlet} from 'react-router-dom'//这是出口,不写的话页面什么也没有

(3)路由跳转配置

Layout/index.js中


import { Link } from 'react-router-dom'

            } key="/">
              数据概览
            
            } key="/article">
              内容管理
            
            } key="/publish">
               发布文章
            
          

(4)菜单反向高亮

Layout/index.js中


import { useLocation } from 'react-router-dom'

 const {pathname} = useLocation()//解构一下
  {/* 高亮原理:defaultSelectedKeys === item key */}
          {/* 获取当前激活的path路径? */}
          {/* 
             defaultSelectedKeys: 初始化渲染的时候生效一次
             selectedKeys: 每次有值更新时都会重新渲染视图
      */}
      
        
            } key="/">//每个key也是
              数据概览
            
            } key="/article">//每个key也是
              内容管理
            
            } key="/publish">//每个key也是
               发布文章
            
          

(5)展示用户信息

store/user.Store.js中:


import { makeAutoObservable } from 'mobx'
import {http} from '../utils'
class UserStore{
    userInfo = {}
    constructor() {
        makeAutoObservable(this)
    }
    getUserInfo =async () => {
        //调用接口
        const res = await http.get('/user/profile')
        this.userInfo=res.data
    }
}
export default UserStore

store/index.js中:


import UserStore from './user.Store'

Layout/index.js中:

import {observer} from 'mobx-react-lite' //引入中间件
import {useStore} from '../../store'
import { useEffect } from 'react'
+  const {userStore}=useStore()
+ useEffect(() => {
    userStore.getUserInfo()
  },[userStore])//填入userStore仅仅是让他不提醒没有使用
  
    用户名//正常这里的用户名是{{userStore.userInfo.name}}
    +export default observer(GeekLayout) //observer解决用户名一刷新就丢失的问题

(6)退出登录模块

store/login.Store.js中:


import { http ,setToken,getToken,+removeToken} from '../utils'
+ loginOut = () => {
        this.token = ''
        removeToken()
    }
   

Layout/index.js中:


import { Outlet, Link, useLocation, +useNavigate } from 'react-router-dom'

 //点击退出的回调
  const navigate = useNavigate()
  const onConfirm = () => {
    //退出登录 清空token 返回登录页
    loginStore.loginOut()
    navigate('/login')
  }
  
    
              < LogoutOutlined /> 退出
            

(7)处理Token失效

思路:在响应拦截器中处理token失效

**难点:怎么在组件之外实现退出登录返回首页:

来一个链接,详细讲解的:

在组件之外实现退出登录返回首页


//示例代码
import { createBrowserHistory } from 'history';
import { +unstable_HistoryRouter as HistoryRouter } from 'react-router-dom';

let history = createBrowserHistory();

function App() {
  return (
    
      // The rest of your app
    
  );
}

history.push("/foo");

安装包:yarn add history (这一部分我就在这展示,要不然我就登录不上去了)

常规的js文件中,那些useXXX文件都是失效的,所以得从history 中引入一个方法,需要在路由中配置

在util中新建history.js


import { createBrowserHistory} from 'history'
const history = createBrowserHistory()

export {history}

在util/http.js中:


 import { history } from './history'
 if (error.response.status === 401) {
        // 跳回到登录 reactRouter默认状态下 并不支持在组件之外完成路由跳转
        // 需要自己来实现
        console.log('login')
        history.push('/login')
    }

在App.js中:


+import { +unstable_HistoryRouter as HistoryRouter , Routes, Route } from 'react-router-dom'
+ 
 
   

六.首页echart实现

用React+Antd写一个后台小项目_第3张图片

yarn add echarts

echarts官网

components中新建Bar.js:


// 封装图表bar组件
import { useEffect, useRef } from 'react'
import * as echarts from 'echarts';
export default function Bar({title,xData,yData,style}) {
    const domRef = useRef()
    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
                }
            ]
        });
    }
    //执行这个初始化的函数
    useEffect(() => {
     chartInit()   
    },[])
    return (
        
{/* 准备一个挂载节点 */}
) }

Home/index.js中:


// 思路:
// 1. 看官方文档 把echarts加入项目  
// 如何在react获取dom -> useRef
// 在什么地方获取dom节点 -> useEffect
// 2. 不抽离定制化的参数 先把最小化的demo跑起来
// 3. 按照需求,哪些参数需要自定义 抽象出来
import Bar from '../../components/Bar'

export default function Home() {

    return (
        
{/* 渲染Bar组件 */}
) }

七.内容管理

用React+Antd写一个后台小项目_第4张图片

(1)筛选区结构


import { Link } from 'react-router-dom'
import { Card, Breadcrumb, Form, Button, Radio, DatePicker, Select } from 'antd'
import 'moment/locale/zh-cn'
import locale from 'antd/es/date-picker/locale/zh_CN'
import './index.scss'

const { Option } = Select
const { RangePicker } = DatePicker
const Article = () => {
    const onFinish = (values) => {
        console.log(values);
    }
    return (
        
{/* 筛选区域 */} 首页 内容管理 } style={{ marginBottom: 20 }} >
全部 草稿 待审核 审核通过 审核失败 {/* 传入locale属性 控制中文显示*/}
{/* 文章列表区域 */}
) } export default Article

(2)表格区域结构


import { +Table,+Tag, +Space,Card, Breadcrumb, Form, Button, Radio, DatePicker, Select } from 'antd'
import { EditOutlined, DeleteOutlined } from '@ant-design/icons'
import img404 from '../../assets/images/error.png'

 const columns = [
        {
          title: '封面',
          dataIndex: 'cover',
          width: 120,
          render: cover => {
            return 
          }
        },
        {
          title: '标题',
          dataIndex: 'title',
          width: 220
        },
        {
          title: '状态',
          dataIndex: 'status',
          render: data => 审核通过
        },
        {
          title: '发布时间',
          dataIndex: 'pubdate'
        },
        {
          title: '阅读数',
          dataIndex: 'read_count'
        },
        {
          title: '评论数',
          dataIndex: 'comment_count'
        },
        {
          title: '点赞数',
          dataIndex: 'like_count'
        },
        {
          title: '操作',
          render: data => {
            return (