一、登录功能
- 首先要使用antd,要先下载
javascript
yarn add antd
- 登录页面关键代码
javascript
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>
)
}
二、后台页面设计
javascript
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>
</>
)
}
三、用户列表
javascript
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>
<Popconfirm
placement="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下编写获取权限菜单的接口
javascript
import request from '../../utils/request'
export default{
getAuthMenus:()=>request.get('/menus/getAuthMenus')
}
- 在SysMenus.jsx中调用getAuthMenus接口来完成权限菜单数据的获取
javascript
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格式的菜单数据
javascript
/**
* 将后台的权限菜单数据转成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
})
}
- 渲染导航列表
javascript
<Menu
items={menuList}
mode="inline"
theme="dark"
onClick={goNav}>
</Menu>
五、路由鉴权
-
在components文件夹下创建函数Auth组件
-
在router/index.js的路由配置中使用
<Auth>
将<Home>
包裹起来 -
关键代码如下
javascript
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()
javascript
axios.interceptors.response.use(response=>{
return response.data
},error=>{
return Promise.reject(error)
})
六、分页操作
javascript
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>
<Popconfirm
placement="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>
)
}
七、面包屑
javascript
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.pathname
const [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>
</>
)
}
八、增加操作
javascript
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 />}
<div
style={{
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">
<Upload
name="file"
listType="picture-card"
showUploadList={false}
onChange={handleChange}
action="http://www.zhaijizhe.cn:3005/images/uploadImages">
{imageUrl ? (
<img
src={imageUrl}
alt="avatar"
style={{
width: '100%',
}}
/>
) : (
uploadButton
)}
</Upload>
</Form.Item>
</Form>
</Modal>
</Card>
</div>
)
}