一、登录功能
-
首先要使用antd,要先下载
yarn add antd
-
登录页面关键代码
import React from 'react'
/*1、如果要在react中完成样式隔离,需要如下操作1)命名一个xx.module.scss webpack要求2) 在需要的组件中通过ES6方式进行导入,导入的格式如下import 模块名 from 'xx.module.scss的路径'3) 在页面的标签中引用,引用的格式<标签名 className={模块名.类样式名称}/>或者className={模块名['类样式名称']}/>
*/
/*2.antd中Form组件的使用2.1 触发Form中的onFinish事件的回调函数它的的条件1)必须让<Button>的类型是submit,具体的做法是<Button htmlType="submit">2) 如何获取表单中文本框或者密码中的数据,需要在<Form.Item>设置name属性
*/
import loginStyle from '../assets/css/login.module.scss'
import {Form,Input,Button,message} from 'antd'
import {useNavigate} from 'react-router-dom'
import api from '../api'
export default function Login() {const [messageApi,contextHolder]=message.useMessage()const nav=useNavigate()const login=async values=>{const {code,message,token}=await api.users.login(values)if(code){messageApi.open({type:'success',content:message})//保存token到localStorage中localStorage.setItem('token',token)//跳转到后台首页nav('/home')}else{messageApi.open({type: 'error',content: '用户名或者密码有误',});}}return (
<div>{contextHolder}<div className={loginStyle.container}><div className={loginStyle['login-box']}><Form onFinish={login}><Form.Item name="username"rules={[{required:true,message:'账号不能为空'}]}className={loginStyle.item}><Input className={loginStyle.txt}></Input></Form.Item><Form.Item name="password"rules={[{required:true,message:'密码不能为空'}]}><Input.Password></Input.Password></Form.Item><Form.Item className={loginStyle.item}><Button type='primary' htmlType='submit' className={loginStyle.loginBtn}>登录</Button></Form.Item></Form></div></div></div>)
}
二、后台页面设计
import React,{useState,useEffect} from 'react'
import homeStyle from '../assets/css/home.module.scss'
import {Layout,Menu} from 'antd'
import {WindowsOutlined,TrademarkCircleOutlined,UserOutlined} from '@ant-design/icons'
import {useNavigate,Outlet} from 'react-router-dom'
const {Header,Sider,Content,Footer}=Layout
export default function Home() {const [menuList,setMenuList]=useState([])const nav=useNavigate()useEffect(()=>{const list=[{key:'sub1',label:'日常业务',icon:<WindowsOutlined />,children:[{label:'学员管理',key:'/home/students'},{label:'班级管理',key:'/home/classes'}]},{key:'sub2',label:'校区管理',icon:<TrademarkCircleOutlined />,children:[{label:'班主任管理',key:'/home/directors'},{label:'专业管理',key:'/home/subjects'}]},{key:'sub3',label:'系统管理',icon:<UserOutlined />,children:[{key:'/home/users',label:'用户管理'}]}]setMenuList(list)},[])const go=(item)=>{nav(item.key)}return (<><Layout><Header><div className={homeStyle.logo}>蜗牛BOSS管理系统</div></Header> <Layout style={{height:'750px'}}><Sider><Menu items={menuList} onClick={go}mode="inline"theme="dark"defaultOpenKeys={['sub1','sub2']}defaultSelectedKeys={['/home/students']}></Menu></Sider><Content>{/* 设置子路由出口 */}<Outlet></Outlet></Content></Layout><Footer style={{ textAlign: 'center'}}>Ant Design ©2023 Created by Ant UED</Footer></Layout> </>)
}
三、用户列表
import React, { useState, useEffect } from 'react'
import { Card, Table, Avatar, Button, Space, Popconfirm } from 'antd';
import api from '../api'
export default function Users() {const [list, setList] = useState([])const columns = [{title: '用户名',dataIndex: 'username'},{title: '邮箱',dataIndex: 'email'},{title: '手机',dataIndex: 'phone'},{title: '角色',dataIndex: 'auth',render: (item) => {return item == 1 ? '超级管理员' : item == 2 ? '普通管理员' : '暂无'}},{title: '头像',dataIndex: 'image',render: (item) => {return <Avatar shape="square" size={64} src={item} />}},{title: '操作',key: 'action',render: (arg1) => {return (<Space><Button type='primary'>查看</Button><Popconfirmplacement="top"title="提示"description="您确定要删除吗?"onConfirm={()=>{deleteUser(arg1._id)}}okText="确认"cancelText="取消"><Button type='primary' danger>删除</Button></Popconfirm></Space>)}}
]useEffect(() => {getUsers()}, [])const getUsers = async () => {const result = await api.users.getUsers()setList(result.data.result)}
const deleteUser = (_id) => {console.log('_id', _id);}return (<div style={{ display: 'flex', justifyContent: 'center' }}><Card bordered={true} style={{ width: '98%', marginTop: '10px' }}><Table dataSource={list} columns={columns} rowKey="_id"></Table></Card></div>)
}
四、动态菜单
-
在api/modules/users下编写获取权限菜单的接口
import request from '../../utils/request'
export default{getAuthMenus:()=>request.get('/menus/getAuthMenus')
}
-
在SysMenus.jsx中调用getAuthMenus接口来完成权限菜单数据的获取
useEffect(()=>{getAuthMenus()},[])const getAuthMenus=async()=>{const result=await api.users.getAuthMenus()console.log(result.data);const rlist=transformDataToMenus(result.data)console.log('转换后的结果',rlist);setMenuList(rlist)}
-
将后台的权限菜单数据转成antd格式的菜单数据
/*** 将后台的权限菜单数据转成antd格式的菜单数据*/const transformDataToMenus=(list)=>{return list.map(item=>{let menuItem={label:item.title,key:item.path,icon:React.createElement(icons[item.icon])}if(item.children){menuItem.children=transformDataToMenus(item.children)}return menuItem})}
-
渲染导航列表
<Menuitems={menuList}mode="inline"theme="dark"onClick={goNav}></Menu>
五、路由鉴权
-
在components文件夹下创建函数Auth组件
-
在router/index.js的路由配置中使用
<Auth>
将<Home>
包裹起来 -
关键代码如下
import React,{useEffect} from 'react'
import {Navigate,useNavigate} from 'react-router-dom'
import api from '../api'
import {message} from 'antd'
export default function Auth({children}) {//从localStorage获取token//如何将结果转成boolean类型const nav=useNavigate()const isAuth=!!localStorage.getItem('token')useEffect(()=>{getUserInfo()},[])const getUserInfo=async()=>{try {await api.users.getUserInfo()} catch (error) {message.warning('您的token已失效,请重新登录')nav('/login')}}if(isAuth){return (<>{children}</>)}else{message.warning('您还没有登录请登录')return (<><Navigate to={"/login"}></Navigate></>)}
}
注意:需要在request.js的响应拦截器中完成Promise.reject()
axios.interceptors.response.use(response=>{return response.data
},error=>{return Promise.reject(error)
})
六、分页操作
import React, { useState, useEffect } from 'react'
import { Card, Table, Avatar, Button, Space, Popconfirm,Pagination } from 'antd';
import api from '../../api'
export default function Users() {const [list, setList] = useState([])const [total,setTotal]=useState(0)const [pageSize,setPageSize]=useState(10)const [current,setCurrent]=useState(1)const columns = [{title: '用户名',dataIndex: 'username'},{title: '邮箱',dataIndex: 'email'},{title: '手机',dataIndex: 'phone'},{title: '角色',dataIndex: 'auth',render: (item) => {return item == 1 ? '超级管理员' : item == 2 ? '普通管理员' : '暂无'}},{title: '头像',dataIndex: 'image',render: (item) => {return <Avatar shape="square" size={64} src={item} />}},{title: '操作',key: 'action',render: (arg1) => {return (<Space><Button type='primary'>查看</Button><Popconfirmplacement="top"title="提示"description="您确定要删除吗?"onConfirm={()=>{deleteUser(arg1._id)}}okText="确认"cancelText="取消"><Button type='primary' danger>删除</Button></Popconfirm></Space>)}}]useEffect(() => {getUsers()}, [])const getUsers = async (params={pageSize:10,currentPage:1}) => {const result = await api.users.getUsers(params)setList(result.data.result)setTotal(result.data.total)}const deleteUser = (_id) => {console.log('_id', _id);}const onChange=(page,pageSize)=>{let params={pageSize,currentPage:page}getUsers(params)setCurrent(page)setPageSize(pageSize)}const onSizeChange=(current,pageSize)=>{console.log('pageSize',pageSize);console.log('current',current);let params={pageSize,currentPage:current}getUsers(params)}return (<div style={{ display: 'flex', justifyContent: 'center' }}><Card bordered={true} style={{ width: '98%', marginTop: '10px' }}><Table dataSource={list} columns={columns} rowKey="_id"pagination={false}></Table><Pagination style={{marginTop:'20px'}}total={total}pageSize={pageSize}current={current}showSizeChanger={true}pageSizeOptions={[3,5,10,15,20]}onChange={onChange}onShowSizeChange={onSizeChange}></Pagination></Card></div>)
}
七、面包屑
import React,{useMemo,useEffect,useState} from 'react'
import { Breadcrumb } from 'antd';
import {useLocation} from 'react-router-dom'
import api from '../api'
export default function MyBreadcrumb() {const location=useLocation()let pathname=location.pathnameconst [breadcrumbAry,setBreadcrumbAry]=useState([])useEffect(()=>{getAuthMenu()},[])const getAuthMenu=async()=>{const result=await api.users.getAuthMenus()transformAry(result.data)}const transformAry=(list)=>{let breadcrumbData={}list.forEach(item=>{if(item.children){item.children.forEach(subItem=>{breadcrumbData[subItem.path]=[item,subItem]})}})setBreadcrumbAry(breadcrumbData[pathname])}return (<><Breadcrumb>{!breadcrumbAry?[].map((item,index)=><Breadcrumb.Item key={index}>{item.title}</Breadcrumb.Item>):breadcrumbAry.map((item,index)=><Breadcrumb.Item key={index}>{item.title}</Breadcrumb.Item>)}</Breadcrumb></>)
}
八、增加操作
import React,{useEffect,useState} from 'react'
import MyBreadcrumb from '../../components/MyBreadcrumb';
import {Button,Card,Modal,Form, Input, Select,Upload,Radio} from 'antd'
import { PlusOutlined,LoadingOutlined } from '@ant-design/icons';
import api from '../../api'export default function StudentList() {const [addForm]=Form.useForm()const [classesOptions,setClassesOptions]=useState([])const [subjectsOptions,setSubjectsOptions]=useState([])const [loading, setLoading] = useState(false);const [imageUrl, setImageUrl] = useState();useEffect(()=>{getAllSubjects()},[])const getAllSubjects=async()=>{const result=await api.subjects.getSubjects()setSubjectsOptions(result.data.result)}const changeSubject=async(arg)=>{const result=await api.classes.getClassesBySubjectsId(arg)setClassesOptions(result.data.result)}const handleChange = (info) => {if (info.file.status === 'uploading') {setLoading(true);return;}if (info.file.status === 'done') {setLoading(false);setImageUrl(`http://www.zhaijizhe.cn:3005/${info.file.response.data[0]}`)}}const uploadButton = (<div>{loading ? <LoadingOutlined /> : <PlusOutlined />}<divstyle={{marginTop: 8,}}>上传头像</div></div>);const [isModalOpen, setIsModalOpen] = useState(false);const showModal = () => {setIsModalOpen(true);};const handleOk = () => {setIsModalOpen(false);const params={name:addForm.getFieldValue(['name']),age:addForm.getFieldValue(['age']),gender:addForm.getFieldValue(['gender'])?addForm.getFieldValue(['gender']):"男",subjectsId:addForm.getFieldValue(['subjectsId']),classesId:addForm.getFieldValue(['classesId']),}if(addForm.getFieldValue(['imagePath']).file.response){params.imageUrl=`http://www.zhaijizhe.cn:3005${addForm.getFieldValue(['imagePath']).file.response.data[0]}`}console.log('params',params);};const handleCancel = () => {setIsModalOpen(false);};return (<div> <MyBreadcrumb></MyBreadcrumb><Card><Button type="primary" onClick={showModal}>添加学生</Button><Modal title="添加学生" open={isModalOpen} onOk={handleOk} onCancel={handleCancel} cancelText="取消" okText="确定"><Form form={addForm}><Form.Item label="姓名" name="name"><Input></Input></Form.Item><Form.Item label="年龄" name="age"><Input></Input></Form.Item><Form.Item label="性别" name="gender"><Radio.Group name="radiogroup" defaultValue={"男"}><Radio value={"男"}>男</Radio><Radio value={"女"}>女</Radio></Radio.Group></Form.Item><Form.Item label="专业" name="subjectsId"><Select options={subjectsOptions} fieldNames={{label:'name',value:'_id'}}onChange={changeSubject}></Select></Form.Item><Form.Item label="专业" name="classesId"><Select options={classesOptions} fieldNames={{label:'name',value:'_id'}}></Select></Form.Item><Form.Item label="头像" name="imagePath"><Uploadname="file"listType="picture-card"showUploadList={false}onChange={handleChange}action="http://www.zhaijizhe.cn:3005/images/uploadImages">{imageUrl ? (<imgsrc={imageUrl}alt="avatar"style={{width: '100%',}}/>) : (uploadButton)}</Upload></Form.Item></Form></Modal></Card></div>)
}