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
项目到这,整体框架就完成噜,可以根据自身需求修改样式、内容哦。
。:.゚ヽ(。◕‿◕。)ノ゚.:。+゚
版权归原作者 今天也要攒钱 所有, 如有侵权,请联系我们删除。