0


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

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

一、页面tag实现

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

①tag显示效果实现

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

  1. 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'>
  2. Prevent Default
  3. </Tag></Space>)}exportdefault MyTag

tag/index.css:

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

在layout中引入tag组件:

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

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

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

  1. 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){
  2. state.tagList.push(val)}}}}})exportconst{ selectTagList }= tabSlice.actions
  3. exportdefault tabSlice.reducer

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

  1. ......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
  2. menuList.forEach(item=>{if(item.path === e.keyPath[e.keyPath.length -1]){
  3. data = item
  4. //如果有二级菜单if(e.keyPath.length >1){
  5. data = item.children.find(child=>{return child.path === e.key
  6. })}}})setTagList({path: data.path,name: data.name,label: data.label
  7. })navigate(e.key)}......}exportdefault Aside

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

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

2、tag选中和删除

①当前菜单存储

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

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

②tag遍历显示

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

  1. ......constMyTag=props=>{const tagList =useSelector(state=> state.tab.tagList)const currentMenu =useSelector(state=> state.tab.currentMenu)consthandleCloseTag=()=>{
  2. 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 &&
  3. tagList.map((item, index)=>setTag(item.path === currentMenu.path, item, index))}</Space>)}exportdefault MyTag

③删除tag

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

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

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

  1. ......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文件:

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

效果:
请添加图片描述

二、用户管理界面实现

1、用户筛选框显示

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

  1. 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 />}>
  2. 新建
  3. </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'>
  4. 搜索
  5. </Button></Form.Item></Form></div></div>)}exportdefault User

page/user/index.css文件:

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

2、用户列表table显示

①table数据获取

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

  1. 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++){
  2. List.push(
  3. 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 ={/**
  4. * 获取列表
  5. * 参数 name,page,limit;name可以不填,page,limit有默认值。
  6. * @param name
  7. * @param page
  8. * @param limit
  9. * @return {{code:number,count:number,data:*[]}}
  10. */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
  11. }}}exportdefault userApi

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

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

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

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

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

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

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

②table表格数据渲染

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

  1. 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
  2. title='提示'
  3. description='此操作将删除该用户,是否继续?'
  4. onConfirm={()=>handleDelete(text)}
  5. onCancel={handleCancel}
  6. okText='确认'
  7. cancelText='取消'><Button type='primary' danger>
  8. 删除
  9. </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文件内容:

  1. ......import{
  2. Button,
  3. Form,
  4. Input,
  5. Table,
  6. Space,
  7. Popconfirm,
  8. Modal,
  9. InputNumber,
  10. Select,
  11. DatePicker
  12. }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
  13. title={method ==='edit'?'编辑用户':'新增用户'}
  14. open={isModalOpen}
  15. onOk={handleSubmit}
  16. onCancel={handleCancel}
  17. okText='确定'
  18. cancelText='取消'><Form
  19. form={form}
  20. name='basic'
  21. labelCol={{span:6}}
  22. wrapperCol={{span:18}}
  23. labelAlign='left'><Form.Item
  24. label='姓名'
  25. name='name'
  26. rules={[{required:true,message:'请输入姓名!'}]}><Input placeholder='请输入姓名'/></Form.Item><Form.Item
  27. label='年龄'
  28. name='age'
  29. rules={[{required:true,message:'请输入年龄!'}]}><InputNumber placeholder='请输入年龄'/></Form.Item><Form.Item
  30. label='性别'
  31. name='sex'
  32. rules={[{required:true,message:'请选择性别!'}]}><Select
  33. placeholder='请选择性别'
  34. options={[{value:0,label:'男'},{value:1,label:'女'}]}/></Form.Item><Form.Item
  35. label='出生日期'
  36. name='birth'
  37. rules={[{required:true,message:'请选择出生日期!'}]}><DatePicker placeholder='请选择' format={'YYYY/MM/DD'}/></Form.Item><Form.Item
  38. label='地址'
  39. name='addr'
  40. rules={[{required:true,message:'请填写地址!'}]}><Input placeholder='请填写地址'/></Form.Item></Form></Modal></div>)}exportdefault User

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

②弹窗功能实现

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

  1. npm install dayjs --save

在这里插入图片描述

Ⅰ、用户添加功能

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

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

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

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

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

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

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

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

效果:
请添加图片描述

Ⅱ、用户编辑功能

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

  1. ......const userApi ={....../**
  2. * 编辑用户
  3. * @param name
  4. * @param addr
  5. * @param age
  6. * @param birth
  7. * @param sex
  8. * @return {{code:number,data:{message:string}}}
  9. */updateUser:config=>{const{ id, name, addr, age, birth, sex }=JSON.parse(config.body)const sex_num =parseInt(sex)
  10. List.some(item=>{if(item.id === id){
  11. item.name = name
  12. item.age = age
  13. item.addr = addr
  14. item.birth = birth
  15. item.sex = sex_num
  16. returntrue}})return{code:200,data:{message:'编辑成功'}}}}exportdefault userApi

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

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

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

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

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

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

效果:
请添加图片描述

③删除功能实现

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

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

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

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

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

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

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

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

效果:
请添加图片描述

④搜索功能实现

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

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

效果:
请添加图片描述

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

1、登录页显示

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

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

login/index.js文件内容:

  1. 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
  2. label='账号'
  3. name='username'
  4. rules={[{required:true,message:'Please input your username!'}]}><Input placeholder='请输入账号'/></Form.Item><Form.Item
  5. label='密码'
  6. name='password'
  7. rules={[{required:true,message:'Please input your password!'}]}><Input.Password placeholder='请输入密码'/></Form.Item><Form.Item className='login-button'><Button type='primary' htmlType='submit'>
  8. 登录
  9. </Button></Form.Item></Form>)}exportdefault Login

login/index.css文件内容:

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

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

2、用户鉴权实现

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

①使用mock模拟登录接口

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

  1. 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文件新增以下代码:

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

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

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

②界面调用接口

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

  1. ......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){
  2. message.warning('请输入用户名和密码')}LoginApi(val).then(res=>{const{ data }= res
  3. localStorage.setItem('token', data.data.token)navigator('/home')})}......}exportdefault Login

③退出登录

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

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

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

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

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

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

  1. ......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实现后台管理系统(二)”的评论:

还没有评论