0


React实现后台管理系统(二)

React实现后台管理系统(二)

一、页面tag实现

1、tag显示和兄弟组件传值

①tag显示效果实现

使用antd中的Tag组件实现,在components文件夹下新建tag/index.js以及index.css文件
tag/index.js:

import React,{ useState }from'react'import{ Tag, Space }from'antd'import'./index.css'constMyTag=props=>{return(<Space className='tag-box' size={[0,8]} wrap><Tag>首页</Tag><Tag closeIcon onClose={handleClose} color='#55acee'>
        Prevent Default
      </Tag></Space>)}exportdefault MyTag

tag/index.css:

.tag-box{padding-top: 24px;padding-left: 24px;}

在layout中引入tag组件:

......import MyTag from'../components/tag'......constMyLayout=()=>{......return(<Layout className='main-container'>......<MyHeader collapsed={collapsed} Collapsed={Collapsed} setCollapsed={setCollapsed}/><MyTag></MyTag><Content
          ......><Outlet /></Content>......</Layout>)}exportdefault MyLayout

②使用redux实现兄弟组件传值

修改src/store/reducers/tab.js文件:

import{ createSlice }from'@reduxjs/toolkit'const tabSlice =createSlice({name:'tab',initialState:{......tagList:[{path:'/',name:'home',label:'首页'}]},reducers:{......selectTagList:(state,{payload: val })=>{if(val.name !=='home'){//如果已经存在的tag就不添加const res = state.tagList.findIndex(item=> item.name === val.name)if(res ===-1){
          state.tagList.push(val)}}}}})exportconst{ selectTagList }= tabSlice.actions
exportdefault tabSlice.reducer

修改conponents/aside/index.js文件:

......import{ useDispatch }from'react-redux'import{ selectTagList }from'../../store/reducers/tab'......constAside=props=>{......const dispatch =useDispatch()//添加数据到storeconstsetTagList=val=>{dispatch(selectTagList(val))}......constclickMenu=e=>{let data
    menuList.forEach(item=>{if(item.path === e.keyPath[e.keyPath.length -1]){
        data = item
        //如果有二级菜单if(e.keyPath.length >1){
          data = item.children.find(child=>{return child.path === e.key
          })}}})setTagList({path: data.path,name: data.name,label: data.label
    })navigate(e.key)}......}exportdefault Aside

修改components/tag/index.js文件:

......import{ useNavigate }from'react-router-dom'import{ useSelector }from'react-redux'constMyTag=props=>{const tagList =useSelector(state=> state.tab.tagList)consthandleClose=()=>{
    console.log(123, tagList)}......}exportdefault MyTag

2、tag选中和删除

①当前菜单存储

同样使用redux进行存储,修改src/store/reducers/tab.js文件:

......const tabSlice =createSlice({name:'tab',initialState:{......currentMenu:{}},reducers:{......selectTagList:(state,{payload: val })=>{if(val.name !=='home'){
        state.currentMenu = val
        ......}elseif(val.name !=='home'&& state.tagList.length ===1){
        state.currentMenu ={}}},setCurrentMenu:(state,{payload: val })=>{if(val.name ==='home'){
        state.currentMenu ={}}else{
        state.currentMenu = val
      }}}})......

②tag遍历显示

修改components/tag/index.js文件:

......constMyTag=props=>{const tagList =useSelector(state=> state.tab.tagList)const currentMenu =useSelector(state=> state.tab.currentMenu)consthandleCloseTag=()=>{
    console.log(123, tagList)}consthandleClickTag=tag=>{}//tag的显示,flag为是否选中标识constsetTag=(flag, item, index)=>{return flag ?(<Tag closeIcon onClose={()=>handleCloseTag(item, index)} color='#55acee' key={item.name}>{item.label}</Tag>):(<Tag key={item.name} onClick={()=>handleClickTag(item)}>{item.label}</Tag>)}return(<Space className='tag-box' size={[0,8]} wrap>{currentMenu.name &&
        tagList.map((item, index)=>setTag(item.path === currentMenu.path, item, index))}</Space>)}exportdefault MyTag

③删除tag

修改src/store/reducers/tab.js文件:

import{ createSlice }from'@reduxjs/toolkit'const tabSlice =createSlice({......reducers:{......closeTag:(state,{payload: val })=>{let res = state.tagList.findIndex(item=> item.name === val.name)
      state.tagList.splice(res,1)},setCurrentMenu:(state,{payload: val })=>{if(val.name ==='home'){
        state.currentMenu ={}}else{
        state.currentMenu = val
      }}}})exportconst{ changeCollapse, selectTagList, closeTag, setCurrentMenu }= tabSlice.actions
exportdefault tabSlice.reducer

修改components/tag/index.js文件:

......import{ useNavigate }from'react-router-dom'import{ useSelector, useDispatch }from'react-redux'import{ useLocation }from'react-router-dom'import{ closeTag, setCurrentMenu }from'../../store/reducers/tab'......constMyTag=props=>{const dispatch =useDispatch()const navigate =useNavigate()const tagList =useSelector(state=> state.tab.tagList)const currentMenu =useSelector(state=> state.tab.currentMenu)const location =useLocation()consthandleCloseTag=(tag, index)=>{let length = tagList.length -1dispatch(closeTag(tag))if(index === length){//设置当前数据const curData = tagList[index -1]dispatch(setCurrentMenu(curData))navigate(curData.path)}else{//关闭中间tagif(tagList.length >1){//如果tag至少存在一个数据,则选择后一个const curData = tagList[index +1]dispatch(setCurrentMenu(curData))navigate(curData.path)}}}consthandleClickTag=tag=>{dispatch(setCurrentMenu(tag))navigate(tag.path)}......

④菜单选中效果与tag、路由不一致问题

修改components/aside/index.js文件:

......import{ useNavigate, useLocation }from'react-router-dom'......constAside=props=>{......const location =useLocation()......return(......<Menu
        ......
        selectedKeys={[location.pathname]}....../>......)}

效果:
请添加图片描述

二、用户管理界面实现

1、用户筛选框显示

使用antd的Button、Form组件实现,修改page/user/index.js文件:

import React,{ useEffect, useState }from'react'import{ Button, Form, Input }from'antd'import{ PlusOutlined }from'@ant-design/icons'import'./index.css'constUser=()=>{consthandleModal=()=>{}constonFinish=()=>{}return(<div className='user-list'><div className='filter-box'><Button type='primary' onClick={()=>handleModal()} icon={<PlusOutlined />}>
          新建
        </Button><Form name='basic' layout='inline' onFinish={onFinish} autoComplete='off'><Form.Item name='keyword'><Input placeholder='请输入用户名'/></Form.Item><Form.Item><Button type='primary' htmlType='submit'>
              搜索
            </Button></Form.Item></Form></div></div>)}exportdefault User

page/user/index.css文件:

.user-list .filter-box{display: flex;justify-content: space-between;margin-bottom: 10px;}

2、用户列表table显示

①table数据获取

在src/mock文件夹下新建user.js文件:

import Mock from'mockjs'functionparam2Obj(url){const search = url.split('?')[1]if(!search){return{}}returnJSON.parse('{"'+decodeURIComponent(search).replace(/"/g,'\\"').replace(/&/g,'","').replace(/=/g,'":"')+'"}')}let List =[]const count =200for(let i =0; i < count; i++){
  List.push(
    Mock.mock({id: Mock.Random.guid(),name: Mock.Random.cname(),addr: Mock.mock('@county(true)'),'age|18-60':1,birth: Mock.Random.date(),sex: Mock.Random.integer(0,1)}))}const userApi ={/**
   * 获取列表
   * 参数 name,page,limit;name可以不填,page,limit有默认值。
   * @param name
   * @param page
   * @param limit
   * @return {{code:number,count:number,data:*[]}}
   */getUserList:config=>{const{ name, page =1, limit =20}=param2Obj(config.url)const mockList = List.filter(user=>{if(name && user.name.indexOf(name)===-1&& user.addr.indexOf(name)===-1)returnfalsereturntrue})const pageList = mockList.filter((item, index)=> index < limit * page && index >= limit *(page -1))return{code:200,count: mockList.length,list: pageList
    }}}exportdefault userApi

在utils/mock.js文件中新增查询mock接口:

......import userApi from'../mock/user'//拦截接口......
Mock.mock(/user\/getUser/, userApi.getUserList)

在src/page/user文件夹下新建services.js文件:

import{ get, post }from'../../utils/request'exportconstgetUser=asyncparams=>{returnget('/user/getUser', params)}

在src/page/user/index.js文件中调用接口:

......import{ getUser }from'./services'constUser=()=>{const[filterParam, setFilterParam]=useState({//查询条件name:''})constgetTableList=()=>{getUser(filterParam).then(res=>{
      console.log(res)})}useEffect(()=>{getTableList()},[])consthandleModal=()=>{}constonFinish=e=>{setFilterParam({name: e.keyword
    })}......exportdefault User

控制台打印:
在这里插入图片描述

②table表格数据渲染

修改src/page/user/index.js文件内容:

import React,{ useEffect, useState }from'react'import{ Button, Form, Input, Table, Space, Popconfirm }from'antd'......constUser=()=>{......const[tableData, setTableData]=useState([])const columns =[{title:'姓名',dataIndex:'name',key:'name'},{title:'年龄',dataIndex:'age',key:'age'},{title:'性别',dataIndex:'sex',key:'sex',render:val=>{return val ?'女':'男'}},{title:'出生日期',dataIndex:'birth',key:'birth'},{title:'地址',dataIndex:'addr',key:'addr'},{title:'操作',key:'x',render:(text, record)=>{return(<Space><Button onClick={()=>handleModal('edit', text)}>编辑</Button><Popconfirm
              title='提示'
              description='此操作将删除该用户,是否继续?'
              onConfirm={()=>handleDelete(text)}
              onCancel={handleCancel}
              okText='确认'
              cancelText='取消'><Button type='primary' danger>
                删除
              </Button></Popconfirm></Space>)}}]consthandleCancel=()=>{}consthandleDelete=()=>{}......return(<div className='user-list'>......<Table columns={columns} dataSource={tableData} rowKey={'id'}></Table></div>)}exportdefault User

效果:
在这里插入图片描述

3、用户新增编辑弹窗

①弹窗显示

使用antd的Modal组件搭配Form表单实现,修改src/page/user/index.js文件内容:

......import{
  Button,
  Form,
  Input,
  Table,
  Space,
  Popconfirm,
  Modal,
  InputNumber,
  Select,
  DatePicker
}from'antd'constUser=()=>{......const[isModalOpen, setIsModalOpen]=useState(false)const[method, setMethod]=useState('add')const[form]= Form.useForm()......consthandleCancel=()=>{setIsModalOpen(false)}consthandleModal=method=>{setMethod(method)setIsModalOpen(true)}consthandleSubmit=()=>{}return(<div className='user-list'>......<Table columns={columns} dataSource={tableData} rowKey={'id'}></Table><Modal
        title={method ==='edit'?'编辑用户':'新增用户'}
        open={isModalOpen}
        onOk={handleSubmit}
        onCancel={handleCancel}
        okText='确定'
        cancelText='取消'><Form
          form={form}
          name='basic'
          labelCol={{span:6}}
          wrapperCol={{span:18}}
          labelAlign='left'><Form.Item
            label='姓名'
            name='name'
            rules={[{required:true,message:'请输入姓名!'}]}><Input placeholder='请输入姓名'/></Form.Item><Form.Item
            label='年龄'
            name='age'
            rules={[{required:true,message:'请输入年龄!'}]}><InputNumber placeholder='请输入年龄'/></Form.Item><Form.Item
            label='性别'
            name='sex'
            rules={[{required:true,message:'请选择性别!'}]}><Select
              placeholder='请选择性别'
              options={[{value:0,label:'男'},{value:1,label:'女'}]}/></Form.Item><Form.Item
            label='出生日期'
            name='birth'
            rules={[{required:true,message:'请选择出生日期!'}]}><DatePicker placeholder='请选择' format={'YYYY/MM/DD'}/></Form.Item><Form.Item
            label='地址'
            name='addr'
            rules={[{required:true,message:'请填写地址!'}]}><Input placeholder='请填写地址'/></Form.Item></Form></Modal></div>)}exportdefault User

效果:
在这里插入图片描述

②弹窗功能实现

添加/编辑时提交的出生日期参数需要做日期转换,需要使用到的是dayjs,所以需要下载dayjs依赖:

npm install dayjs --save

在这里插入图片描述

Ⅰ、用户添加功能

在src/mock/user.js文件中新建添加用户mock接口:

......const userApi ={....../**
   * 增加用户
   * @param name
   * @param addr
   * @param age
   * @param birth
   * @param sex
   * @return {{code:number,data:{message:string}}}
   */createUser:config=>{const{ name, addr, age, birth, sex }=JSON.parse(config.body)
    List.unshift({id: Mock.Random.guid(),name: name,age: age,addr: addr,birth: birth,sex: sex
    })return{code:200,data:{message:'添加成功'}}}exportdefault userApi

在utils/mock.js文件中拦截接口:

......//拦截接口......
Mock.mock(/user\/createUser/,'post', userApi.createUser)

在src/pages/user/services.js文件新建前端接口:

......exportconstcreateUser=asyncdata=>{returnpost('/user/createUser', data)}

在src/pages/user/index.js文件调用接口:

......import{ getUser, createUser}from'./services'import dayjs from'dayjs'constUser=()=>{......consthandleCancel=()=>{setIsModalOpen(false)
    form.resetFields()}consthandleSubmit=()=>{
    form
      .validateFields().then(value=>{
        value.birth =dayjs(value.birth).format('YYYY-MM-DD')if(method ==='edit'){}else{createUser(value).then(res=>{handleCancel()getTableList()
            message.success(res.data.data.message)})}}).catch(e=>{
        console.log(e)})}......}exportdefault User

效果:
请添加图片描述

Ⅱ、用户编辑功能

在src/mock/user.js文件中新建编辑用户mock接口:

......const userApi ={....../**
   * 编辑用户
   * @param name
   * @param addr
   * @param age
   * @param birth
   * @param sex
   * @return {{code:number,data:{message:string}}}
   */updateUser:config=>{const{ id, name, addr, age, birth, sex }=JSON.parse(config.body)const sex_num =parseInt(sex)
    List.some(item=>{if(item.id === id){
        item.name = name
        item.age = age
        item.addr = addr
        item.birth = birth
        item.sex = sex_num
        returntrue}})return{code:200,data:{message:'编辑成功'}}}}exportdefault userApi

在utils/mock.js文件中拦截接口:

......//拦截接口......
Mock.mock(/user\/updateUser/,'post', userApi.updateUser)

在src/pages/user/services.js文件新建前端接口:

......exportconstupdateUser=asyncdata=>{returnpost('/user/updateUser', data)}

在src/pages/user/index.js文件增加数据回填逻辑并调用接口:

......import{ getUser, createUser, updateUser }from'./services'constUser=()=>{......consthandleModal=(method, data)=>{if(method ==='edit'){const cloneData =JSON.parse(JSON.stringify(data))
      cloneData.birth =dayjs(cloneData.birth)
      form.setFieldsValue(cloneData)}......}consthandleSubmit=()=>{
    form
      .validateFields().then(value=>{
        value.birth =dayjs(value.birth).format('YYYY-MM-DD')if(method ==='edit'){updateUser(value).then(res=>{handleCancel()getTableList()
            message.success(res.data.data.message)})}else{......}}).catch(e=>{
        console.log(e)})}......return(<div className='user-list'>......<Modal
        ......><Form
          ......>{method ==='edit'&&(<Form.Item name='id' hidden><Input /></Form.Item>)}......</Form></Modal></div>)}exportdefault User

效果:
请添加图片描述

③删除功能实现

在src/mock/user.js文件中新建删除用户mock接口:

......const userApi ={....../**
   * 删除用户
   * @param id
   * @return {*}
   */deleteUser:config=>{const{ id }=JSON.parse(config.body)if(!id){return{code:400,message:'删除失败'}}else{
      List = List.filter(item=> item.id !== id)return{code:200,message:'删除成功'}}}}exportdefault userApi

在utils/mock.js文件中拦截接口:

......
Mock.mock(/user\/deleteUser/,'post', userApi.deleteUser)

在src/pages/user/services.js文件新建前端接口:

......exportconstdeleteUser=asyncdata=>{returnpost('/user/deleteUser', data)}

在src/pages/user/index.js调用接口:

......import{ getUser, createUser, updateUser, deleteUser }from'./services'constUser=()=>{......consthandleDelete=data=>{const{ id }= data
    deleteUser({ id }).then(res=>{
      console.log(res.data)if(res.data.code ===200){getTableList()
        message.success(res.data.message)}else{
        message.error(res.data.message)}})}......}exportdefault User

效果:
请添加图片描述

④搜索功能实现

通过监听filterParam来实现搜索,点击搜索时修改filterParam的值,如果filterParam发生改变则调用查询接口,修改src/pages/user/index.js文件:

......constUser=()=>{......useEffect(()=>{
    console.log(123123, filterParam)getTableList()},[filterParam])......constonFinish=e=>{setFilterParam({name: e.keyword
    })}......}exportdefault User

效果:
请添加图片描述

二、登录页和用户鉴权实现

1、登录页显示

在pages文件夹下新建login/index.js、login/index.css文件:
在这里插入图片描述
增加路由,修改src/router/index.js文件:

......import Login from'../pages/login'const routes =[......{path:'login',Component: Login
  }]......

login/index.js文件内容:

import React from'react'import{ Form, Input, Button }from'antd'import'./index.css'constLogin=()=>{consthandleSubmit=()=>{}return(<Form className='login-box' onFinish={handleSubmit}><div className='title'>系统登录</div><Form.Item
        label='账号'
        name='username'
        rules={[{required:true,message:'Please input your username!'}]}><Input placeholder='请输入账号'/></Form.Item><Form.Item
        label='密码'
        name='password'
        rules={[{required:true,message:'Please input your password!'}]}><Input.Password placeholder='请输入密码'/></Form.Item><Form.Item className='login-button'><Button type='primary' htmlType='submit'>
          登录
        </Button></Form.Item></Form>)}exportdefault Login

login/index.css文件内容:

.login-box {width: 350px;border: 1px solid #eaeaea;margin: 180px auto;padding: 35px 35px 15px 35px;
  background-color: #fff;
  border-radius: 15px;
  box-shadow:00 25px #cac6c6;
  box-sizing: border-box;}.login-box .title {
  text-align: center;
  margin-bottom: 40px;color: #505458;
  font-size: 20px;}.login-box .login-button {
  text-align: center;}

效果:
在这里插入图片描述

2、用户鉴权实现

使用mock模拟登录接口返回token,存入localStorage来判断用户是否登录

①使用mock模拟登录接口

在src/mock文件夹下新建permission.js文件:

import{ message }from'antd'import Mock from'mockjs'const PermissionApi ={LoginApi:config=>{const{ username, password }=JSON.parse(config.body)//先判断用户是否存在//判断账号和密码是否对应if(username ==='admin'&& password ==='admin'){return{code:200,data:{token: Mock.Random.guid(),message:'登录成功'}}}elseif(username ==='account'&& password ==='account'){return{code:200,data:{token: Mock.Random.guid(),message:'登录成功'}}}else{return{code:400,message:'登录失败'}}}}exportdefault PermissionApi

在src/utils/mock.js文件新增以下代码:

......import PermissionApi from'../mock/permission'......
Mock.mock(/permission\/LoginApi/,'post', PermissionApi.LoginApi)

在src/pages/login文件夹下新建services.js文件:

import{ post }from'../../utils/request'exportconstLoginApi=asyncdata=>{returnpost('/permission/LoginApi', data)}

②界面调用接口

在src/pages/login/index.js文件中增加以下代码:

......import{ LoginApi }from'./services'import{ useNavigate, Navigate }from'react-router-dom'constLogin=()=>{const navigator =useNavigate()//在登陆状态下,需要跳转到home页面if(localStorage.getItem('token')){return<Navigate to='/home'/>}consthandleSubmit=val=>{if(!val.password ||!val.username){
      message.warning('请输入用户名和密码')}LoginApi(val).then(res=>{const{ data }= res
      localStorage.setItem('token', data.data.token)navigator('/home')})}......}exportdefault Login

③退出登录

在src/components/header/index.js文件中增加登出逻辑:

......import{ useNavigate }from'react-router-dom'......constMyHeader=props=>{......const navigat =useNavigate()//登出constlogout=()=>{//清除token
    localStorage.removeItem('token')navigat('/login')}}exportdefault MyHeader

④没有token情况下不让访问除login以外路由

使用高阶组件进行鉴权,在src/router文件下新建routerAuth.js文件:

import{ Navigate }from'react-router-dom'exportconstRouterAuth=({ children })=>{const token = localStorage.getItem('token')if(!token){return<Navigate to='/login' replace />}return children
}

修改src/pages/layout.js文件内容:

......import{ RouterAuth }from'../router/routerAuth'......constMyLayout=()=>{......return(<RouterAuth><Layout className='main-container'>........</Layout></RouterAuth>)}exportdefault MyLayout

项目到这,整体框架就完成噜,可以根据自身需求修改样式、内容哦。
。:.゚ヽ(。◕‿◕。)ノ゚.:。+゚


本文转载自: https://blog.csdn.net/qq_41051239/article/details/140803945
版权归原作者 今天也要攒钱 所有, 如有侵权,请联系我们删除。

“React实现后台管理系统(二)”的评论:

还没有评论