写在前头

这个项目是跟着b站up主 单排哥 学习的。

传送门

https://www.bilibili.com/video/BV1ta411b7eo?p=1

具体功能

  • 查看文章
  • 文章编辑
  • 修改资料

一、项目介绍

《CMS后台系统》项目主要是为CMS官网提供文章编辑、上传等功能。其中包括富文本编辑器调用、路由管理、权限管控、图片上传等主体模块。

项目预览路径:http://codesohigh.com/cms-manage/

UI框架使用:Ant Design

二、项目起步

1、创建项目

$ npx create-react-app cms-manage

在一个文件夹目录下点击,输入cmd,进去终端命令框,然后使用上面命名,进行项目创建。

显示以下类似的文字就是表面项目已经创建成功。

2、安装依赖

本项目用到的依赖可以在此预先安装好:

  • antd
  • redux与react-redux
  • react-router-dom
  • axios
  • less与less-loader
$ npm i antd redux react-redux react-router-dom@6 axios less less-loader@6.0.0 --save
  • 在vscode中打开终端使用。

3、Antd引入和测试

Ant Design 传送门

https://ant.design/index-cn

  • 首先将src文件夹下的所有文件删除

在像图片下面一样新建文件夹及文件。

  • 在bass.css中引入antd。
@import '~antd/dist/antd.css';

  • 初始化App.jsx
import React from 'react';
import "./assets/base.css"const App = () => {return (<div>App</div>);
}export default App;

  • index.js
import ReactDOM from 'react-dom'ReactDOM.render(<Router />,document.getElementById('root')
)
  • 测试antd

import React from 'react';
import "./assets/base.css"
import { Button} from 'antd';const App = () => {return (<div><Button type="primary">primary</Button></div>);
}export default App;
  • 测试成功

4、路由配置

  • 安装插件

  • 创建页面pages

在每一个jsx文件下使用rfc代码片段快速生成函数组件。(注意:需要安装插件)

import React from 'react'export default function Edit() {return (<div>Edit</div>)
}
  • 新建router文件夹,书写index.jsx文件

/*App > List + Edit + MeansLoginRegisterHistory模式 BrowserRouterHash模式 HashRouter
*/import App from '../App'
import List from '../pages/List'
import Edit from '../pages/Edit'
import Means from '../pages/Means'
import Login from '../pages/Login'
import Register from '../pages/Register'
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom'const BaseRouter = () => (<Router><Routes><Route path='/' element={<App />}><Route path='/list' element={<List />}> </Route><Route path='/edit' element={<Edit />}> </Route><Route path='/means' element={<Means />}> </Route></Route><Route path='/login' element={<Login />}> </Route><Route path='/register' element={<Register />}> </Route></Routes></Router>
)export default BaseRouter
  • 将index.js修改顶级组件

import ReactDOM from 'react-dom'
import Router from "./router"ReactDOM.render(<Router />,document.getElementById('root')
)
  • 更改路径可以检查是否成功

在App.js组件中使用Outlet

import React from 'react';
import "./assets/base.css"
import { Button} from 'antd';
import { Outlet } from 'react-router-dom';const App = () => {return (<div><Button type="primary">primary</Button><Outlet/>   </div>);
}export default App;
  • 检查成功?

#5.解包配置Less

1、解包并配置Less

  • 在项目根目录下运行解包命令:
# 解包前必须做git提交,否则无法解包(这是为了方便随时做版本回滚)
$ git init
$ git add .
$ git commit -m '解包前'
$ npm run eject

  • 解包之后,项目根目录下将出现config目录,找到webpack.config.js,搜索 sassModuleRegex 后,在其下方添加:
{test: /\.less$/,use: getStyleLoaders({//暂不配置},'less-loader'),
},

修改了配置文件,记得重跑项目哦!

  • 测试Less

删除入口文件index.js下对antd.css的引入,然后在src下创建 assets>base.less:

@import '~antd/dist/antd.css';    // 有个波浪线
@bgcolor: pink;body{background: @bgcolor;
}

此时看网页是否按钮正常,并且body背景变为粉色。若是,则Less配置成功。

三、页面布局

登录页布局

  • 基本样式
  1. 修改页面背景颜色
@import '~antd/dist/antd.css';
@bgcolor: #efefef;body{font-family: "微软雅黑";font-size: 14px;color: #333;background: @bgcolor;
}
  1. 删除APP组件中的button按钮。
import React from 'react';
import "./assets/base.less"
import { Outlet } from 'react-router-dom';const App = () => {return (<div><Outlet/>   </div>);
}export default App;

  • 进入Login路由组件

form表单-Ant Design 传送门

https://ant.design/components/form-cn/#header

  • 在第一个表单下复制代码
import React from 'react'
import { Form, Input, Button, Checkbox } from 'antd';export default function Login() {const onFinish = (values: any) => {console.log('Success:', values);};const onFinishFailed = (errorInfo: any) => {console.log('Failed:', errorInfo);};return (<div><Formname="basic"labelCol={{ span: 8 }}wrapperCol={{ span: 16 }}initialValues={{ remember: true }}onFinish={onFinish}onFinishFailed={onFinishFailed}autoComplete="off"><Form.Itemlabel="Username"name="username"rules={[{ required: true, message: 'Please input your username!' }]}><Input /></Form.Item><Form.Itemlabel="Password"name="password"rules={[{ required: true, message: 'Please input your password!' }]}><Input.Password /></Form.Item><Form.Item name="remember" valuePropName="checked" wrapperCol={{ offset: 8, span: 16 }}><Checkbox>Remember me</Checkbox></Form.Item><Form.Item wrapperCol={{ offset: 8, span: 16 }}><Button type="primary" htmlType="submit">Submit</Button></Form.Item></Form></div>)
}
  • 改变路径检查是否成功。

添加类名

  <div className='login'><div className='login_box'></div></div>

书写less

.login {background: #fff;width: 100vw;height: 100vh;position:relative;.login_box{width: 500px;// 表单位置居中position: absolute;left:50%;top:50%;transform: translate(-50%,-50%); }
}

  • 引入图片

在src-> assets 下添加logo图片

在login组件添加图片

  1. 引入图片
import logoImg from '../assets/logo.png'
  1. 设置图片标签
  <div className='login_box'>

  1. 设置图片属性
 img {display: block;margin: 0 auto 20px;}

  • 改写表单

将复制的代码删除部分不需要的,得到下方代码

        <Formname="basic"initialValues={{ remember: true }}onFinish={onFinish}onFinishFailed={onFinishFailed}autoComplete="off"><Form.Itemname="username"rules={[{ required: true, message: 'Please input your username!' }]}><Input /></Form.Item><Form.Itemname="password"rules={[{ required: true, message: 'Please input your password!' }]}><Input.Password /></Form.Item><Form.Item wrapperCol={{ offset: 8, span: 16 }}><Button type="primary" htmlType="submit">Submit</Button></Form.Item></Form>
  • 修改按钮
   <Form.Item><Button type="primary" htmlType="submit" block  size='large'>登录</Button></Form.Item>
  • 改写输入框

添加icon和提示字体

import { UserOutlined, LockOutlined } from '@ant-design/icons';<Input size="large" prefix={<UserOutlined className="site-form-item-icon" />} placeholder='请输入用户名'/><Input.Password size="large" prefix={<LockOutlined className="site-form-item-icon" />} placeholder='请输入密码'/></Form.Item>
  • 添加跳转链接
import {Link} from 'react-router-dom'<Form.Item><Link to="/register">还没账号?立即注册</Link>
</Form.Item>
  • 最终实现效果图

注册页布局

  • 将登录的代码复制到注册页面

  • 添加验证密码框

 <Form.Itemname="confirm"dependencies={['password']}hasFeedbackrules={[{required: true,message: 'Please confirm your password!',},({ getFieldValue }) => ({validator(_, value) {if (!value || getFieldValue('password') === value) {return Promise.resolve();}return Promise.reject(new Error('The two passwords that you entered do not match!'));},}),]}><Input.Password size="large" prefix={<LockOutlined className="site-form-item-icon" />} placeholder='请再次输入密码'/></Form.Item>

将路由跳转修改

  <Form.Item><Link to="/login">已有账号?前往登录</Link></Form.Item>

将按钮修改为“立即注册”

  <Form.Item><Button type="primary" htmlType="submit" block  size='large'>立即注册</Button></Form.Item>
  • 实现效果

Request封装

1、接口文档

本项目的接口文档:http://xiaoyaoji.cn/project/1kSQB8SHnDV/share/1mfpzz0vdw0 (opens new window), 密码:zhaowenxian

在src下创建request目录,并在其中创建request.js及api.js。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tq59j8wZ-1651547144160)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fc0c34a8a23e4f3eb153b9e85afe3597~tplv-k3u1fbpfcp-watermark.image?)]

2、封装axios请求

request.js:

import axios from 'axios'// 配置项
const axiosOption = {baseURL: 'http://47.93.114.103:6688/manage',timeout: 5000
}// 创建一个单例
const instance = axios.create(axiosOption);// 添加请求拦截器
instance.interceptors.request.use(function (config) {return config;
}, function (error) {// 对请求错误做些什么return Promise.reject(error);
});// 添加响应拦截器
instance.interceptors.response.use(function (response) {// 对响应数据做点什么return response.data;
}, function (error) {// 对响应错误做点什么return Promise.reject(error);
});export default instance;

注意:这里并没有考虑token,后面会添加。

  • 测试baseUrl

http://47.93.114.103:6688/manage

3、api.js

api.js暂时可定以下格式,后续项目中再修改:

import request from './request'export const xxApi = () => request.get('/xx')
  • 书写注册接口
// 引入request
import request from './request'// 注册
export const RegisterApi = (params) => request.post('/register', params)
  • 在Register.jsx中引入RegisterApi
import {RegisterApi} from '../request/api'

  • 在 onFinish函数中使用RegisterApi,但是需要先解决跨域问题

1、解决跨域

如果你已经进行了 npm run eject ,建议你直接修改 config>webpackDevServer.config.js :

proxy: {'/api': {target: 'http://47.93.114.103:6688/manage', // 后台服务地址以及端口号changeOrigin: true, //是否跨域pathRewrite: { '^/api': '/' }}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EmfE4WGJ-1651547144163)(https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/98249599bd0349af843f591cb9c276a3~tplv-k3u1fbpfcp-watermark.image?)]

http://47.93.114.103:6688/manage 替换成为api。

将 request.js: 的baseUrl进行修改

// 配置项
const axiosOption = {baseURL: '/api',timeout: 5000
}
  • 在Request.js文件的onFinish函数书写请求。
  const onFinish = (values) => {RegisterApi({username: values.username,password: values.password}).then(res=>{if(res.errCode===0){message.success(res.message);// 跳到登录页setTimeout(()=>navigate('/login'), 1500)}else{message.error(res.message);}})};

  • 跳转路由是使用的hook。
import {Link, useNavigate} from 'react-router-dom'if(res.errCode===0){message.success(res.message);// 跳到登录页setTimeout(()=>navigate('/login'), 1500)}
  • 未注册

  • 已注册

登录

  • 在api.js 下面添加登录请求
// 登录
export const LoginApi = (params) => request.post('/login', params)

  • 在Login.js引入

在OnFinish里面调用

import {LoginApi} from '../request/api'const onFinish = (values) => {console.log('Success:',values);LoginApi({username:values.username,password:values.password}).then(res=>{console.log(res)})};

  • 使用条件语句判断是否成功
 if(res.errCode === 0) {}else {message.error(res.message)}
  • 存储数据 (不使用对象,方便存取)
 // 存储数据localStorage.setItem('avatar', res.data.avatar)localStorage.setItem('cms-token', res.data['cms-token'])localStorage.setItem('editable', res.data.editable)localStorage.setItem('player', res.data.player)localStorage.setItem('username', res.data.username)
  • 使用setTimeout()跳转页面
import {Link, useNavigate} from 'react-router-dom'const navigate = useNavigate()// 跳转到根路径setTimeout(()=>{navigate('/')}, 1500)
  • 总代码(登录)
const onFinish = (values) => {console.log('Success:',values);LoginApi({username:values.username,password:values.password}).then(res=>{console.log(res)if(res.errCode===0){message.success(res.message)// 存储数据localStorage.setItem('avatar', res.data.avatar)localStorage.setItem('cms-token', res.data['cms-token'])localStorage.setItem('editable', res.data.editable)localStorage.setItem('player', res.data.player)localStorage.setItem('username', res.data.username)// 跳转到根路径setTimeout(()=>{navigate('/')}, 1500)}else{message.error(res.message)}})};

  • 最终效果图

App布局

布局传送门

https://ant.design/components/layout-cn/

import { Layout } from 'antd';const { Header, Footer, Sider, Content } = Layout;<Layout><Header>Header</Header><Layout><Sider>Sider</Sider><Content>Content</Content></Layout><Footer>Footer</Footer>
</Layout>
  • 略微修改
import React from 'react';
import "./assets/base.less"
import { Outlet } from 'react-router-dom';
import { Layout } from 'antd';const App = () => {const {Sider, Content } = Layout;return (<Layout><header>Header</header><Layout><Sider>Sider</Sider><Content><div><Outlet/>   </div></Content></Layout><footer>Footer</footer></Layout>);
}export default App;
  • 书写样式 base.less

设置header 和 footer

header {height: 70px;background-color: pink;
}footer {height: 70px;background: #001529;color:#fff;text-align: center;line-height: 70px;
}

实现效果图

  • 导入logo图片
import logoImg from '../assets/logo.png'<img src={logoImg} alt="" className="logo" />

实现效果图

  • 给logo图片设置间距
header {height: 70px;background-color: #fff;padding: 0 20px;display: flex;justify-content: space-between;align-items: center;
}

实现效果图

  • 底边框可以随着F12的操作框动态移动

App.jsx

<Layout id='app'><header><img src={logoImg} alt="" className="logo" /></header><Layout>

base.less

#app {height: 100vh;
}

实现效果图

下拉菜单

  • 将header抽出成为一个Header组件。

在components下面创建Header文件

Header组件

import React from 'react'
import logoImg from '../assets/logo.png'export default function Header() {return (<div><header><img src={logoImg} alt="" className="logo" /><div className='right'>右侧</div></header></div>)
}

注意:引入图片的路径记得更改,因为是在components文件之外引入图片

  • 在App.js中引入Header组件
import Header from './components/Header'<Layout id='app'><Header/>
<Layout>

  • 下拉菜单

传送门(记得使用3.x的版本,不然没有Menu.Item)

https://3x.ant.design/components/dropdown-cn/

复制代码

import { Menu, Dropdown } from 'antd';<Dropdown overlay={menu}><a className="ant-dropdown-link" onClick={e => e.preventDefault()}>Hover me </a>
</Dropdown>const menu = (<Menu><Menu.Item><a target="_blank" rel="noopener noreferrer" href="http://www.alipay.com/">1st menu item</a></Menu.Item><Menu.Item><a target="_blank" rel="noopener noreferrer" href="http://www.taobao.com/">2nd menu item</a></Menu.Item><Menu.Item><a target="_blank" rel="noopener noreferrer" href="http://www.tmall.com/">3rd menu item</a></Menu.Item></Menu>);

整体展示


import React from 'react'
import logoImg from '../assets/logo.png'
import { Menu, Dropdown } from 'antd';export default function Header() {const menu = (<Menu><Menu.Item><a target="_blank" rel="noopener noreferrer" href="http://www.alipay.com/">1st menu item</a></Menu.Item><Menu.Item><a target="_blank" rel="noopener noreferrer" href="http://www.taobao.com/">2nd menu item</a></Menu.Item><Menu.Item><a target="_blank" rel="noopener noreferrer" href="http://www.tmall.com/">3rd menu item</a></Menu.Item></Menu>);return (<div><header><img src={logoImg} alt="" className="logo" /><div className='right'><Dropdown overlay={menu}><a className="ant-dropdown-link" onClick={e => e.preventDefault()}>Hover me </a></Dropdown></div></header></div>)
}

效果展示

  • 进行修改

添加阴影线

<Menu.Divider />

    const menu = (<Menu><Menu.Item>修改资料</Menu.Item><Menu.Divider /><Menu.Item>退出登录</Menu.Item></Menu>);

  • 修改图标

传送门

https://3x.ant.design/components/icon-cn/

import { CaretDownOutlined } from '@ant-design/icons';<CaretDownOutlined />

效果展示

import React from 'react'
import logoImg from '../assets/logo.png'
import { Menu, Dropdown} from 'antd';
import { CaretDownOutlined } from '@ant-design/icons';export default function Header() {const menu = (<Menu><Menu.Item>修改资料</Menu.Item><Menu.Divider /><Menu.Item>退出登录</Menu.Item></Menu>);return (<div><header><img src={logoImg} alt="" className="logo" /><div className='right'><Dropdown overlay={menu}><a className="ant-dropdown-link" onClick={e => e.preventDefault()}>Hover me <CaretDownOutlined /></a></Dropdown></div></header></div>)
}

下拉菜单

引入用户头像

// 引入useState
import React, {useEffect,useState} from 'react'// 引入默认头像图片
import defaultAvatar from '../assets/defaultAvatar.jpg'// 使用useState
const [avatar,setAvatar] = useState(defaultAvatar)// 插入图片<img src={avatar} className="avatar" alt='' />

添加默认用户名

// 使用useState
const [username,setUsername] = useState('游客')<span>{username}</span>

注意:添加key!

const menu = (<Menu><Menu.Item key={1}>修改资料</Menu.Item><Menu.Divider /><Menu.Item key ={2}>>退出登录</Menu.Item></Menu>);
  • 实现效果图

修改样式

在base.less 下书写样式

    .right {height: 40px;.avatar {width: 40px;height: 40px;border-radius: 50%;}span {margin-left: 10px;margin-right: 10px;}}
  • 实现效果图

修改下拉菜单边距

  • 原样

在base.less中书写对应a标签的属性

        .ant-dropdown-link{height: 60px;display: block;color: #333;&:hover{color: #1890ff;}}
  • 实现效果图

  • 使用useEffect

在Application中会找到请求存储的数据。

我们将使用这些数据替代默认的用户名和用户头像。

  • 用户名
    // 模拟componentDidMountuseEffect(()=>{let username1 = localStorage.getItem('username')if(username1){setUsername(username1)}},[])
  • 实现效果图

  • 用户头像
    // 模拟componentDidMountuseEffect(()=>{let username1 = localStorage.getItem('username')let avatar1 = localStorage.getItem('avatar')if(username1) {setUsername(username1)}if(avatar1) {setAvatar('http://47.93.114.103:6688/' + avatar1)}},[])

注意:传入图片时,记得添加路径。

  • 实现效果图

退出登录

不可以直接使用Link,进行跳转,因为在Application中数据依然存在。退出时必须清除数据。

import {Link, useNavigate} from 'react-router-dom'const navigate = useNavigate()// 退出登录const logout = () => {message.success('退出成功,即将返回登录页')localStorage.clear();   // 清除localStorage中的数据setTimeout(() => navigate('/login'), 1500)
}<Link to="/login"  onClick={logout} >退出登录</Link>
  • 实现效果

侧边栏布局

  • 创建Aside组件

  • 修改App组件

const App = () => {const {Sider, Content } = Layout;return (<Layout id='app'><Header/><Layout><Aside /><Content><div><Outlet/>   </div></Content></Layout><footer>Footer</footer></Layout>);
}export default App;
  • 书写Aside组件

Menu

传送门

https://ant.design/components/menu-cn/

Aside组件

import React from 'react'
import { Menu } from 'antd';
import { ReadOutlined, EditOutlined, DatabaseOutlined } from '@ant-design/icons';export default function Aside() {const handleClick = e => {console.log('click',e)};return (<MenuonClick={handleClick}style={{ width: 180 }}mode="inline"theme="dark" // 黑色主题><Menu.Item key="3">Option 3</Menu.Item><Menu.Item key="4">Option 4</Menu.Item></Menu>)
}

App组件

import Aside from './components/Aside'
import React from 'react';
import "./assets/base.less"
import { Outlet } from 'react-router-dom';
import { Layout } from 'antd';
import Header from './components/Header'
import Aside from './components/Aside'const App = () => {const {Content } = Layout;return (<Layout id='app'><Header/><Layout><Aside /><Content><div><Outlet/>   </div></Content></Layout><footer>Footer</footer></Layout>);
}export default App;
  • 实现效果图

  • 为Aside设置类名
    <MenuonClick={handleClick}style={{ width: 180 }}mode="inline"className='aside'theme="dark" // 黑色主题><Menu.Item key="3">Option 3</Menu.Item><Menu.Item key="4">Option 4</Menu.Item></Menu>
  • App组件内设置属性名
import React from 'react';
import "./assets/base.less"
import { Outlet } from 'react-router-dom';
import { Layout } from 'antd';
import Header from './components/Header'
import Aside from './components/Aside'function App() {return (<Layout id='app'><Header/><div className='container'><Aside />       <div className='container_box'><div><Outlet/>   </div></div></div>  <footer>Footer</footer></Layout>);
}export default App;
  • base.less 书写属性
.container{display: flex;// justify-content: space-between;.aside{height: calc(100vh - 140px);}.container_box{flex: 1;box-sizing: border-box;padding: 20px;display: flex;flex-direction: column;.container_content{height: calc(100vh - 210px);overflow: hidden;}}
}

-实现效果

添加图标

      <Menu.Item key="1"><ReadOutlined /> 查看文章列表</Menu.Item><Menu.Item key="2"><EditOutlined /> 文章编辑</Menu.Item><Menu.Item key="3"><DatabaseOutlined /> 修改资料</Menu.Item>

在文字和图标紧凑在一起的时候,我们需要可以敲进一个空格来使得排版更加美观。

  • 实现效果图

点击实现路由跳转

  • 修改key值
      <Menu.Item key="list"><ReadOutlined /> 查看文章列表</Menu.Item><Menu.Item key="edit"><EditOutlined /> 文章编辑</Menu.Item><Menu.Item key="means"><DatabaseOutlined /> 修改资料</Menu.Item>
  • 使用hook跳转
import {useNavigate} from 'react-router-dom'const navigate = useNavigate()
// 修改handleClickconst handleClick = e => {navigate('/'+e.key)};
  // 默认路由defaultSelectedKeys={['list']}

  • 实现效果

遇到的bug

刷新之后路径并未改变,菜单栏改变了。

  • 片段代码

引入useLocation

import React, { useEffect, useState } from 'react'
import {useNavigate, useLocation} from 'react-router-dom'const location = useLocation()
const [defaultKey, setDefaultKey] = useState('')// 一旦渲染立刻获取动态的路由路径,不在使用默认的useEffect(() => {let path = location.pathname;let key = path.split('/')[1];setDefaultKey(key)}, []);// 及时更新路由路径const handleClick = e => {navigate('/'+e.key)setDefaultKey(e.key)};
  • 实现效果图(自己刷新试试咯)

面包屑

传送门

https://ant.design/components/breadcrumb-cn/#header

  • 创建Bread组件

初始化书写Bread组件

import React from 'react';
import { Breadcrumb } from 'antd';
import { HomeOutlined } from '@ant-design/icons';const Bread = () => {return (<Breadcrumb><Breadcrumb.Item href=""><HomeOutlined /></Breadcrumb.Item><Breadcrumb.Item>Application</Breadcrumb.Item></Breadcrumb>);
}export default Bread;

在App组件中引入一个Bread组件

import Bread from './components/Bread'<div className='container_box'><Bread/><Outlet/>
</div>
  • 根据路径更新面包屑

更新面包屑名字(useEffect取过来,useState再赋值更新上)

// 引入hook
import React, {useState,useEffect} from 'react';// 设置变量
const [breadName, setBreadName] = useState('')// 获取路径
const {pathname} = useLocation()// 不是在组件mounted时去获取路径,而是路径一旦变化,就要获取对应的路径名称,并且修改breadName
// 监听路由的路径(/list /edit /means)
useEffect(() => {switch (pathname) {case "/list":setBreadName('查看文章列表');break;case "/edit":setBreadName('文章编辑');break;case "/means":setBreadName('修改资料');break;default:break;}
}, [pathname])

内容补充(表单,表格)

  • 创建2个路由组件

  • 配置路由(index.jsx)
import ListTable from '../pages/ListTable'
import ListList from '../pages/ListList'const BaseRouter = () => (<Router><Routes><Route path='/' element={<App />}><Route path='/listtable' element={<ListTable />}></Route><Route path='/listlist' element={<ListList />}></Route><Route path='/edit' element={<Edit />}></Route><Route path='/means' element={<Means />}> </Route></Route><Route path='/login' element={<Login />}> </Route><Route path='/register' element={<Register />}> </Route></Routes></Router>
)
  • 修改面包屑
    useEffect(() => {switch (pathname) {case "/listlist":setBreadName('查看文章列表List');break;case "/listtable":setBreadName('查看文章列表Table');break;case "/edit":setBreadName('文章编辑');break;case "/means":setBreadName('修改资料');break;default:setBreadName(pathname.includes('edit') ? '文章编辑' : "");break;}}, [pathname])
  • 修改侧边栏
            <Menu.Item key="listlist"><ReadOutlined /> 查看文章列表List</Menu.Item><Menu.Item key="listtable"><ReadOutlined /> 查看文章列表Table</Menu.Item>
  • 实现效果

ListTable 书写样式

  • 在APP组件中设置布局属性
<div className='container_box'><Bread/><Outlet/>
</div>

base.less

.container .container_box {flex: 1;box-sizing: border-box;padding: 20px;display: flex;flex-direction: column;
}
  • 创建less文件

.list_table{width: 100%;background: #fff;height: 100%;}
  • 引入less样式到ListTable
import './less/ListTable.less'// 不要忘记添加类名
<div className='list_table'>ListTable</div>
  • 实现效果图

表格结构搭建

传送门

https://ant.design/components/table-cn/#components-table-demo-basic

  • 删除标签、删除data中的tags、删除年龄一列

完整代码

import React from 'react'
import './less/ListTable.less'
import { Table, Tag, Space } from 'antd';
export default function ListTable() {// 真正从后端拿的数据要替换这个data
const data = [{key: '1',name: 'John Brown',address: 'New York No. 1 Lake Park',},{key: '2',name: 'Jim Green',address: 'London No. 1 Lake Park',},{key: '3',name: 'Joe Black',address: 'Sidney No. 1 Lake Park',},
];// 每一列
const columns = [{title: 'Name',dataIndex: 'name',key: 'name',render: text => <a>{text}</a>,},{title: 'Age',dataIndex: 'age',key: 'age',},{title: 'Address',dataIndex: 'address',key: 'address',},{title: 'Action',key: 'action',render: (text, record) => (<Space size="middle"><a>Invite {record.name}</a><a>Delete</a></Space>),},
];return (<div className='list_table'>{/* columns列 dataSource数据 */}<Table columns={columns} dataSource={data} /></div>)
}
  • 引入button(在Action下面)
// 引入buttonimport { Table, Button, Space } from 'antd';//书写button编辑、删除<Button type='primary' >编辑</Button><Button type='danger'>删除</Button>

  • 隐藏表头

  • 在Table标签是添加showHeader属性
 <Table columns={columns} showHeader = {false} dataSource={data} />
  • 在columns中删除标题
// 每一列
const columns = [{dataIndex: 'name',key: 'name',render: text => <a>{text}</a>,},{dataIndex: 'age',key: 'age',},{dataIndex: 'address',key: 'address',},{key: 'action',render: (text, record) => (<Space size="middle"><Button type='primary' >编辑</Button><Button type='danger'>删除</Button></Space>),},
];
  • 显示效果

  • 渲染标题副标题

在第一列下面修改

  {dataIndex: 'name',key: 'name',render: text => (<><h4>标题</h4><p>简直是大家</p></>),},

  • 渲染时间

在第二列添加渲染

  {dataIndex: 'address',key: 'address',render: text => (<p>2022-03-03 20:33:06</p>)},

  • 改变第一列的宽度

使用width属性

  • 修改副标题颜色
 {dataIndex: 'name',key: 'name',width:'60%',render: text => (<><h4>标题</h4><p style={{color:'#999'}}>简直是大家</p></>),},

在p标签中添加style属性

    <p style={{color:'#999'}}>简直是大家</p>
  • 实现效果

  • 修改标题实现跳转

引入Link

import {Link} from 'react-router-dom'

修改标签

 <Link to="/" className='table_title'>标题</Link>
// 标题样式.table_title{color: #333;&:hover {color: #1890ff;}}

  • 使用useState更新data数据

将原先的data数组传入arr变量中

// 引入useState
import React,{useState} from 'react'// 初始化const [arr,setArr] = useState([{key: '1',name: 'John Brown',address: 'New York No. 1 Lake Park',}])// 更改Table标签的属性、dataSource的属性值<Table columns={columns} showHeader = {false} dataSource={arr} />

axios请求格式

get请求必须书写params

axios.get({params: {num:1}
})axios.post({num:1
})
  • 书写获取文章的api

api.js文件下

// 获取文章列表
export const ArticleListApi = (params) => request.get('/article', {params})

在ListTable.jsx中引入api

import { ArticleListApi } from '../request/api';
  • 使用useEffect来请求文章列表
// 引入useEffect
import React,{useState, useEffect} from 'react'// 请求文章列表useEffect(() => {ArticleListApi().then(res=>{console.log(res.data)})}, []);

数据处理

  • 解决副标题无法渲染,数组无key值

生成一个新数组,然后map遍历赋值一个新key值

  // 请求文章列表useEffect(() => {ArticleListApi().then(res=>{if(res.errCode === 0) {let newArr = JSON.parse(JSON.stringify(res.data.arr))/* 1. 要给每个数组项加key,让key=id2. 需要有一套标签结构,赋予一个属性*/newArr.map(item=> {item.key = item.id;item.mytitle = `<><Link to="/" className='table_title'>标题</Link><p style={{color:'#999'}}>简直是大家</p></>`;})console.log(newArr)}})}, []);
  • 工作台

列表渲染

  • 使用setArr传入newArr

  • 规范渲染时间date

在第二列将dataIndex、key修改为date

  {dataIndex: 'date',key: 'date',render: text => (<p>{text}</p>)},
  • 安装moment

yarn add moment

  • 引入moment
import moment from 'moment'
  • 整改date
   item.date = moment(item.date).format("YYYY-MM-DD hh:mm:ss")
  • 渲染文章标题
 item.mytitle = `<div><Link to="/" className='table_title'>${item.title}</Link><p style={{color:'#999'}}>${item.subTitle}</p></div>`;render: text => <div dangerouslySetInnerHTML={{__html:text}}></div>

记得修改

dataIndex: 'mytitle',
key: 'mytitle',
  • 实现效果

更换更好的渲染标题的方法

创建一个myArr数组保存对象obj。每次遍历newArr的时候就创建一个obj。通过props属性将获得的标题传递给MyTitle组件。

将mytitle的值改写为MyTitle组件 记得去掉$ 和修改props。

function MyTitle(props) {return (<div><Link to="/" className='table_title'>{props.title}</Link><p style={{color:'#999'}}>{props.subTitle}</p></div>)
}

创建对象obj

   // 声明一个空数组let myarr = []newArr.map(item => {let obj = {key: item.id,date: moment(item.date).format("YYYY-MM-DD hh:mm:ss"),mytitle: <MyTitle id={item.id} title={item.title} subTitle={item.subTitle} />}myarr.push(obj)
})
setArr(myarr)// 注意在对应的列中更改渲染render: text =>  <div>{text}</div>
  • 实现效果图

a标签跳转

将Link标签修改为a标签

添加href

跳转新窗口 target=“_blank”

<a to="/" className='table_title' href={"http://codesohigh.com:8765/article/" + props.id} target="_blank">{props.title}</a>

id的获取

先输出text,看打印内容是什么

代码显示

    render: (text, record) => {console.log(text)return (<Space size="middle"><Button type='primary' >编辑</Button><Button type='danger'>删除</Button></Space>)}
  • 操作台显示

使用点击事件获取id值(text.key就是我们需要的id)

        <Button type='primary' onClick={()=>console.log(text.key)}>编辑</Button><Button type='danger' onClick={()=>console.log(text.key)}>删除</Button>
  • 实现效果图(点击按钮输出id到控制台)

封装请求文章

将useEffect内部的代码全部剪切到新定义的getArticleList的函数内部。

  // 提取请求的代码const getArticleList = ()=> {ArticleListApi().then(res=>{if(res.errCode === 0) {let newArr = JSON.parse(JSON.stringify(res.data.arr))/* 1. 要给每个数组项加key,让key=id2. 需要有一套标签结构,赋予一个属性*/// 声明一个空数组let myarr = []newArr.map(item => {let obj = {key: item.id,date: moment(item.date).format("YYYY-MM-DD hh:mm:ss"),mytitle: <MyTitle id={item.id} title={item.title} subTitle={item.subTitle} />}myarr.push(obj)})setArr(myarr)}})}// 请求文章列表useEffect(() => {getArticleList();}, []);

分页函数

  • 分页传送门

https://ant.design/components/pagination-cn/#API

在Table标签中添加onChange事件

<Table
columns={columns}
showHeader = {false}dataSource={arr}onChange={pageChange}/>

书写分页函数

// 分页的函数
const pageChange = (pagination) => {console.log(pagination)
}

点击换页标签时,操作台会输出以下内容

  • 在Table标签中pagination

使用useState设置分页

  //分页const [pagination,setPagination] = useState({current:1,pageSize:1,total:0})
       <Tablecolumns={columns} showHeader = {false}dataSource={arr}onChange={pageChange}pagination={pagination}/>
  • 请求十条数据

为请求函数设置参数,传入current,pageSize

设置形参

  // 提取请求的代码const getArticleList = (current,pageSize)=> {ArticleListApi({num:current,count:pageSize}).then(res=>{if(res.errCode === 0) {let newArr = JSON.parse(JSON.stringify(res.data.arr))/* 1. 要给每个数组项加key,让key=id2. 需要有一套标签结构,赋予一个属性*/// 声明一个空数组let myarr = []newArr.map(item => {let obj = {key: item.id,date: moment(item.date).format("YYYY-MM-DD hh:mm:ss"),mytitle: <MyTitle id={item.id} title={item.title} subTitle={item.subTitle} />}myarr.push(obj)})setArr(myarr)}})}

调用getArticleList() 传入实参

  // 请求文章列表useEffect(() => {getArticleList(pagination.current,pagination.pageSize);}, []);

实现效果

换页按钮变为了一页,原因没有设置总条数。

找到分页的total属性

注意:这里视频突然发现Table有total属性。这里再度尝试输出res.data,看是否返回数据有total。

        console.log(res.data)

请求数据之后更改pagination

 // 更改pagination
let { num, count, total } = res.data;
setPagination({ current: num, pageSize: count, total })

点击分页按钮后会返回一串数据

在分页函数中调用getArticleList封装请求函数(更新点击就调用getArticleList)

// 分页的函数
const pageChange = (arg) => getArticleList(arg.current, arg.pageSize);

滚动样式

想要实现的效果

实际存在的问题,内容超出

  • 给面包屑设置高度
<Breadcrumb style={{height: '30px',background:'red', lineHeight: '30px'}}>

  • 修改整体页面高度

在base.less中

.container_content{height: calc(100vh - 210px);overflow: hidden;
}

App组件

function App() {return (<Layout id='app'><Header/><div className='container'><Aside />       <div className='container_box'><Bread/><div className="container_content"><Outlet /></div></div></div>  <footer>Respect | Copyright &copy; 2022 Author 你单排吧</footer></Layout>);
}

在ListTable.less中添加滚动

.list_table{width: 100%;background: #fff;height: 100%;overflow-y: scroll;&::-webkit-scrollbar {/*滚动条整体样式*/width: 10px;height: 100%;background: #fff;border-radius: 10px;}&::-webkit-scrollbar-track {/*滚动条里面轨道*/box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);border-radius: 10px;background: #EDEDED;}&::-webkit-scrollbar-thumb {/*滚动条里面小方块*/border-radius: 10px;box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);background: #535353;}.table_title{color: #333;&:hover{color: #1890ff;}}
}

注意记得把那个面包屑背景颜色去掉!!!

  • 最终实现效果

列表组件引入

  • 借用ListTable的css样式

直接在div盒子上添加对应属性

    <div className='list_table'>ListList</div>

列表传送门

https://ant.design/components/list-cn/

  • 复制对应代码
import { List, Avatar, Button, Skeleton } from 'antd';const count = 3; const fakeDataUrl = `https://randomuser.me/api/?results=${count}&inc=name,gender,email,nat,picture&noinfo`;state = { initLoading: true, loading: false, data: [], list: [], };const { initLoading, loading, list } = this.state;const loadMore =!initLoading && !loading ? (<divstyle={{textAlign: 'center',marginTop: 12,height: 32,lineHeight: '32px',}}><Button onClick={this.onLoadMore}>loading more</Button></div>) : null;<div className='list_table'><ListclassName="demo-loadmore-list"loading={initLoading}itemLayout="horizontal"loadMore={loadMore}dataSource={list}renderItem={item => (<List.Itemactions={[<a key="list-loadmore-edit">edit</a>, <a key="list-loadmore-more">more</a>]}><Skeleton avatar title={false} loading={item.loading} active><List.Item.Metaavatar={<Avatar src={item.picture.large} />}title={<a href="https://ant.design">{item.name.last}</a>}description="Ant Design, a design language for background applications, is refined by Ant UED Team"/><div>content</div></Skeleton></List.Item>)}/></div>

修改代码

  • 使用useState
// 引入useState
import React,{useState} from 'react'将List转换就行其余删掉const [list, setList] = useState([])

删除 fakeDataUrl 、count

删除List中的initLoading、loadMore、 avatar

  • 最后代码
import React,{useState} from 'react'
import { List,Skeleton } from 'antd';export default function ListList() {const [list, setList] = useState([])return (<div className='list_table'><ListclassName="demo-loadmore-list"itemLayout="horizontal"dataSource={list}renderItem={item => (<List.Itemactions={[<a key="list-loadmore-edit">edit</a>, <a key="list-loadmore-more">more</a>]}><Skeleton  title={true} loading={item.loading} active><List.Item.Metatitle={<a href="!#">标题</a>}description="副标题"/><div>日期</div></Skeleton></List.Item>)}/></div>)
}

实现效果

使用useEffect发送请求

// 引入useEffect
import React,{useState,useEffect} from 'react'// 引入api
import { ArticleListApi } from '../request/api';
  // 请求列表数据useEffect(()=>{ArticleListApi().then(res=>{console.log(res.data.arr)if(res.errCode === 0) {let {arr,total,num,count} = res.data;setList(arr)}})},[])
  • 渲染数据
 <Skeleton  loading={false}><List.Item.Metatitle={<a href="!#">{item.title}</a>}description={item.subTitle}/><div>{item.date}</div></Skeleton>
  • 实现效果

  • 将标题挤开部分
style={{ padding: '20px' }}

  • 设置分页

引入分页

import { List,Skeleton,Pagination } from 'antd';

使用API

<Pagination onChange={onChange} total={50} />

书写onChange事件

    // 分页const onChange = (pages) => {console.log(pages)}

使用useState设置total、current、pageSize

  const [total, setTotal] = useState(0)const [current, setCurrent] = useState(1)const [pageSize, setPageSize] = useState(10)

在Pagination中可以直接使用total、current、pageSize

<Pagination onChange={onChange} total={50} total={total} current={current} pageSize={pageSize} />

请求更新total、current、pageSize

  // 请求列表数据useEffect(()=>{ArticleListApi({num: current,count: pageSize}).then(res=>{console.log(res.data.arr)if(res.errCode === 0) {let {arr,total,num,count} = res.data;setList(arr);setTotal(total);setCurrent(num);setPageSize(count)}})},[])
  • 请求封装
  // 请求封装const getList = (num) => {ArticleListApi({num: num,count: pageSize}).then(res=>{console.log(res.data.arr)if(res.errCode === 0) {let {arr,total,num,count} = res.data;setList(arr);setTotal(total);setCurrent(num);setPageSize(count)}})}
  • 书写点击事件
  // 分页const onChange = (pages) => {getList(pages);}

将分页按钮书写到右边

style={{float: 'right',marginTop: '20px'}}

日期域按钮

日期

// 引入moment
import moment from 'moment'//规范时间
<div>{moment(item.date).format("YYYY-MM-DD hh:mm:ss")}</div>

按钮

// 引入buttonimport { ArticleListApi } from '../request/api';
  actions={[<Button type='primary' onClick={()=>console.log(item.id)}>编辑</Button>, <Button type='danger' onClick={()=>console.log(item.id)}>删除</Button>]}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6saeGWrN-1651547144225)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f34bc508b8d44b6ea2a8e60cc1f20944~tplv-k3u1fbpfcp-watermark.image?)]

文章编辑页面

PageHeader页头

传送门

https://ant.design/components/page-header-cn/#header

引入

import { PageHeader, Button } from 'antd';

复制页头去掉描述

    <PageHeaderghost={false}onBack={() => window.history.back()}title="Title"subTitle="This is a subtitle"extra={[<Button key="3">Operation</Button>,<Button key="2">Operation</Button>,<Button key="1" type="primary">Primary</Button>,]}></PageHeader>

修改按钮

extra={<Button key="1" type="primary">提交文章</Button>}

设置时间和标题

引入moment

import moment from 'moment'

修改时间

subTitle={"当前日期:" + moment(new Date()).format("YYYY-MM-DD")}

修改标题

        title="文章编辑"
  • 实现效果

  • 使用wangEditor

安装依赖

npm i wangeditor  --save

引入对象E

import E from 'wangeditor'

创建对象实例放入div盒子

使用useEffect

import React,{useEffect} from 'react'// 模拟componentDidMount
useEffect(()=>{const editor = new E('#div1');editor.create()
},[])<div id="div1"></div>

  • 富文本编辑器

改为外界声明editor

let editor = null;

书写editor的onChange函数

  editor.config.onchange = (newHtml) => {SVGTextContentElement(newHtml)}

销毁editor

  return () => {editor.destroy()}

注意使用useState创建content。

import React, { useEffect, useState } from 'react'const [content, setContent] = useState('')

为文本编辑框设置边距

 style={{ padding: '0 20px 20px', background: '#fff' }}

设置对话框

对话框传输门

https://ant.design/components/modal-cn/#header

引入Modal

import { Modal, Button } from 'antd';

设置button

  <Button key="1" type="primary" onClick={showModal}>提交文章</Button>}

添加方法和模块

const showModal = () => {setIsModalVisible(true);};const handleOk = () => {setIsModalVisible(false);};const handleCancel = () => {setIsModalVisible(false);};<Modal title="Basic Modal" visible={isModalVisible} onOk={handleOk} onCancel={handleCancel}>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>

处理层级问题

在Modal设置zIndex={99999}

<Modal zIndex={99999} title="Basic Modal" visible={isModalVisible} onOk={handleOk} onCancel={handleCancel}>

修改标题

 title="填写文章标题"

简化函数代码

onClick={() =>  setIsModalVisible(true)}onCancel={() =>  setIsModalVisible(false)}

Form表单传送门

https://ant.design/components/form-cn/

引入Form、Input

import {Form, Input, PageHeader, Button ,Modal} from 'antd';//在Model下添加表单代码<Formname="basic"labelCol={{ span: 3 }}wrapperCol={{ span: 21 }}autoComplete="off"
><Form.Itemlabel="标题"name="title"rules={[{ required: true, message: '请填写标题!' }]}><Input /></Form.Item><Form.Itemlabel="副标题"name="subTitle"><Input /></Form.Item>
</Form>

对话框获取表单的值

Form表单弹出层传送门

https://ant.design/components/form-cn/#components-form-demo-form-in-modal

 const [form] = Form.useForm();

Form表单上添加属性

          form={form}

对话框点击了提交

  // 对话框点击了提交const handleOk = () => {// setIsModalVisible(false); // 关闭对话框form.validateFields().then(values => {form.resetFields();onCreate(values);}).catch(info => {console.log('Validate Failed:', info);});};

删除onCreate(values)、修改ok函数

  // 对话框点击了提交const handleOk = () => {// setIsModalVisible(false); // 关闭对话框form.validateFields().then(values => {form.resetFields();}).catch(() => {return ;});};
  • 实现效果

onOk添加onText、cancelText

okText="提交" cancelText="取消"

发送文章请求

  • 添加token
  let token = localStorage.getItem('cms-token')if(token){config.headers = {'cms-token': token}}

  • 书写api
// 添加文章
export const ArticleAddApi = (params) => request.post('/article/add', params)

引入api

import {ArticleAddApi } from '../request/api'

验证content是否取到

    // setIsModalVisible(false); // 关闭对话框form.validateFields().then(values => {// form.resetFields(); // reset重置console.log('Received values of form: ', values);let {title,subTitle} = valuesconsole.log(content)}).catch(() => {return ;});

-发送请求

  // 请求ArticleAddApi({title,subTitle,content}).then(res => {console.log(res)})

实现效果

编辑id控制 实现页面跳转

在ListList中

引入useNavigate

import {  useNavigate, } from 'react-router-dom'const navigate = useNavigate()

在onClick中实现路由跳转

  actions={[<Button type='primary' onClick={()=>navigate('/edit/'+item.id)}>编辑</Button>, <Button type='danger' onClick={()=>console.log(item.id)}>删除</Button>]}
  • 修改路由文件index

是添加!不是修改

<Route path='/edit' element={<Edit />}></Route>
<Route path='/edit/:id' element={<Edit />}></Route>
  • 实现效果

  • 实现只有路径带有id值是才会有返回箭头

引入useParams

import { useParams } from 'react-router-dom'const params = useParams()

书写onBack

 onBack={ params.id ? () => window.history.back() : null}

-实现效果

wangeditor 内容渲染

  • 书写查看文章api
// 查看文章
export const ArticleSearchApi = (params) => request.get(`/article/${params.id}`)

引入查看文章api

import {ArticleAddApi,ArticleSearchApi } from '../request/api'

根据地址栏id做请求

// 根据地址栏id做请求
if(params.id) {ArticleSearchApi({id:params.id}).then(res=>{if(res.errCode === 0) {let {title,subTitle} = res.data;editor.txt.html(res.data.content) // 重新设置编辑器内容}})
}
  • 实现效果(点击编辑之后会跳转到编辑页面,并且显示出文本内容)

  • 设置title、subTitle
const [title, setTitle] = useState('')
const [subTitle, setSubTitle] = useState('')setTitle(res.data.title)
setSubTitle(res.data.subTitle)
  • 为表单添加初始值

在Form 标签中使用该属性

      initialValues={{title:title,subTitle:subTitle}}
  • 实现效果

修改更新文章

书写更新api

// 重新编辑文章
export const ArticleUpdateApi = (params) => request.put('/article/update', params)

调用api

  // 地址栏有id代表现在想要更新一篇文章if(params.id) {ArticleUpdateApi({title,subTitle,content}).then(res => {console.log(res)})}else {// 添加文章的请求ArticleAddApi({title,subTitle,content}).then(res => {console.log(res)})}
  • 实现效果

  • 使用message提示修改成功并且跳转页面
  // 地址栏有id代表现在想要更新一篇文章if(params.id) {ArticleUpdateApi({title,subTitle,content}).then(res => {if(res.errCode === 0) {message.success(res.message);//跳转到list页面navigate('/listlist')}else{message.error(res.message)}setIsModalVisible(false) // 关闭对话框})}else {// 添加文章的请求ArticleAddApi({title,subTitle,content}).then(res => {console.log(res)})}

解决bug

点击编辑一篇文章之后,再次点击菜单栏,文章编辑。页面的路径更改但是文本框并没有清除。

解决方案:监听路由的变化

引入location

import { useParams, useNavigate, useLocation } from 'react-router-dom'const location = useLocation()

  • Aside监听路由
    // 一般加个空数组就是为了模仿componentDidMounteduseEffect(()=>{let path = location.pathname;let key = path.split('/')[1];setDefaultKey(key)}, [location.pathname])
  • 封装函数(使用message提示修改成功并且跳转页面)

删除文章

书写删除api

// 删除文章
export const ArticleDelApi = (params) => request.post('/article/remove', params)

在ListList中引入api

import { ArticleListApi ,ArticleDelApi} from '../request/api';

在button中修改点击事件

<Button type='danger' onClick={()=>delFn(item.id)}>删除</Button>

书写删除函数delFn

重新刷页面,要么重新请求这个列表的数据 window.reload 调用getList(1) 增加变量的检测

  // 删除const delFn = (id) => {ArticleDelApi({id}).then(res=>{if(res.errCode===0){message.success(res.message)// 重新刷页面,要么重新请求这个列表的数据   window.reload   调用getList(1)  增加变量的检测setUpdate(update+1)}else{message.success(res.message)}})}

监听刷新页面

const [update, setUpdate] = useState(1)

  • 为Table添加编辑、删除功能

注意引入的内容和函数(太冗杂不写了,傲娇~)

用户资料表单布局

书写类名,设置样式

import "./less/Means.less"import React from 'react'export default function Means() {return (<div className='means'>Means</div>)
}.means{background: #fff;height: 100%;padding: 20px;box-sizing: border-box;
}
  • 实现效果

  • 引入form表单(设置宽度、修改按钮,修改宽高)
import React from 'react'
import { Form, Input,Button} from 'antd';
import "./less/Means.less"export default function Means() {return (<div className='means'><Formstyle={{width: '400px'}}name="basic"initialValues={{remember: true,}}autoComplete="off"><Form.Itemlabel="修改用户名"name="username"rules={[{required: true,message: 'Please input your username!',},]}><Input placeholder='请输入新用户名' /></Form.Item><Form.Itemlabel="修 改 密 码"name="password"><Input.Password placeholder='请输入新密码' /></Form.Item><Form.Item><Button type="primary" htmlType="submit" style={{float: 'right'}}>提交</Button></Form.Item></Form></div>)
}

获取用户请求

  • 书写api
// 获取用户资料
export const GetUserDataApi = () => request.get('/info')import {GetUserDataApi} from '../request/api'

使用useEffect、更新请求用户,设置初始值。

  const [username1,setUsername1] = useState("");const [password1,setPassword1] = useState("")useEffect(() => {GetUserDataApi().then(res => {console.log(res)if(res.errCode === 0) {message.success(res.message);setUsername1(res.data.username);setPassword1(res.data.Password);}})}, []);
  • 实现效果

修改用户资料

  • 书写api
// 修改用户资料
export const ChangeUserDataApi = (params) => request.put('/info', params)
  • 引入
import {GetUserDataApi, ChangeUserDataApi} from '../request/api'
  • 发送请求
  // 表单提交的事件const onFinish = (values) => {// 如果表单的username有值,并且不等于初始化时拿到的username,同时密码非空if(values.username && values.username!==sessionStorage.getItem('username') && values.password.trim() !== ""){// 做表单的提交...ChangeUserDataApi({username: values.username,password: values.password}).then(res=>{console.log(res)// 当你修改成功的时候,不要忘了重新登录})}}

当你修改成功的时候,不要忘了重新登录!

Upload引入

添加标题

      <p>点击下方修改头像:</p>

Upload上传

传送门

https://ant.design/components/upload-cn/#header

upload组件中直接书写请求体

书写action接口

action="/api/upload"

上传前 beforeUpload

// 限制图片大小只能是200KB
function beforeUpload(file) {const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png';if (!isJpgOrPng) {message.error('You can only upload JPG/PNG file!');}const isLt2M = file.size / 1024 / 1024 / 1024  < 200;if (!isLt2M) {message.error('请上传小于200KB的图!');}return isJpgOrPng && isLt2M;
}
  • 使用useState改写loading、imageUrl
import React, { useEffect, useState } from 'react'const [loading, setLoading] = useState(false)const [imageUrl, setImageUrl] = useState("")
  • 添加handleChange,并且修改

  // 点击了上传图片const  handleChange = info => {if (info.file.status === 'uploading') {setLoading(true);return;}if (info.file.status === 'done') {// Get this url from response in real world.getBase64(info.file.originFileObj, imageUrl => {setLoading(false)setImageUrl(imageUrl)       });}};
  • 函数调用修改

引入base64函数

// 将图片路径改位base64
function getBase64(img, callback) {const reader = new FileReader();reader.addEventListener('load', () => callback(reader.result));reader.readAsDataURL(img);
}

注意引入

import { Form, Input,Button, message,Upload} from 'antd';
import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';
  • 实现效果

Upload组件添加请求头

headers={{"cms-token": localStorage.getItem('cms-token')}}

  • 存储图片名称

这里需要打印info.file找到上传图片的名称。

// 存储图片名称
localStorage.setItem('avatar', info.file.response.data.filePath)

更新Header组件

使用useState设置mykey

    <Layout id='app'><Header key={mykey} /><div className='container'><Aside />       <div className='container_box'><Bread/><div className="container_content"><Outlet  setMyKey={setMyKey} /></div></div></div>  <footer>Respect | Copyright &copy; 2022 Author 你单排吧</footer></Layout>

Means组件

注意记得函数接收产生props

  // 点击了上传图片const  handleChange = info => {if (info.file.status === 'uploading') {setLoading(true);return;}if (info.file.status === 'done') {// Get this url from response in real world.getBase64(info.file.originFileObj, imageUrl => {setLoading(false)setImageUrl(imageUrl)       // 存储图片名称localStorage.setItem('avatar', info.file.response.data.filePath)// 触发Header组件更新props.setMyKey(props.myKey+1)});}};

添加强制刷新

window.location.reload()

使用react-redux

安装react-redux

yarn add react-redux

《CMS后台系统》项目实战 详细分解相关推荐

  1. 全程配图超清晰的Springboot权限控制后台管理项目实战第二期(Springboot+shiro+mybatis+redis)

    全程配图超清晰的Springboot权限控制后台管理项目实战第二期(Springboot+shiro+mybatis+redis) 众所周知,作为一个后端新手学习者,通过项目来学习,增长项目经验,是一 ...

  2. 个人博客系统--项目实战

    个人博客系统–项目实战 先附上设计文档和项目源码. 个人博客设计文档 Github 这项目大概做了十多天,其基本功能都实现了,因为没有美工的关系,界面可能有点丑,请谅解. 后台采用SSH技术,版本为: ...

  3. 儒猿秒杀季!互联网大厂的企业级风控系统项目实战

    疯狂秒杀季:499元秒杀 原价 1899元 的 <互联网大厂的企业级风控系统项目实战> 今天 上午11点,仅 20 套,先到先得! === 课程介绍 === | SparkStreamin ...

  4. 电影购票系统项目实战

    电影购票系统项目实战 电影购票系统简介.项目功能演示. 日志框架搭建.系统角色分析 首页.登录.商家界面.用户界面实现 商家功能-展示详情.影片上架.退出 商家功能-影片下架.影片修改 用户功能-展示 ...

  5. 【Spark】基于Spark的大型电商网站交互式行为分析系统项目实战

    1.项目背景 (1)Spark在美团的实践 美团是数据驱动的互联网服务,用户每天在美团上的点击.浏览.下单支付行为都会产生海量的日志,这些日志数据将被汇总处理.分析.挖掘与学习,为美团的各种推荐.搜索 ...

  6. Java 游戏账号出租系统项目(超详细注解)

    Java 游戏账号出租系统项目 一级目录为包.二级目录为类 注解详细 debug调试更清晰 AccountRentApp(main) package accountrentsystem; import ...

  7. java cms视频_【视频+源码】JAVA CMS系统项目实战

    01 CMS系统功能需求简介02 如何采用用例分析方法来理解需求03 后台管理系统用例04 实现验证码的初步思路05 生成验证码06 判断验证码是否正确07 返回登录页面时,把刚刚输入的用户名和密码回 ...

  8. java cms视频_领航致远JAVA CMS系统项目实战 视频+源码精品实战项目

    [课程内容] 01 CMS系统功能需求简介 02 如何采用用例分析方法来理解需求 03 后台管理系统用例 04 实现验证码的初步思路 05 生成验证码 06 判断验证码是否正确 07 返回登录页面时, ...

  9. 【笔记】Vue Element+Node.js开发企业通用管理后台系统——项目需求分析

    文章目录 一.项目技术架构 1.三个应用 2.项目目标 二.技术难点分析 1.登录 2.电子书上传 3.电子书解析 4.电子书增删改 5.epub 电子书 三.Nginx 服务器搭建 1.安装 ngi ...

  10. 软件测试项目实战案例分解,跟着我一步一步操作【人力资源管理系统】

    目录 一.引言 二.项目概述 三.平台.角色和权限 四.人资管理员 五.最后 一.引言 1.编写目的 本文档将列举实现人力资源管理系统(以下简称HR系统)所需要的全部功能,并对每个功能给出简单的描述. ...

最新文章

  1. zb如何导出自己画的_zbrush纹理贴图(zbrush怎么导出映射贴图)
  2. xrdp安装包linux,linux xrdp0.6 安装
  3. 小而美的个人博客——前端——about
  4. android不能使用udp获取数据解决
  5. DevOps(过程、方法与系统的统称)是什么
  6. Joint Face Detection and Alignment using Multi-task Cascaded Convolutional Networks -译文
  7. 汇编语言第三章检测题
  8. chrome vue 未响应_vue之骨架屏踩坑之路
  9. Sklearn专题实战——针对Category特征进行分类
  10. Windowserver2012部署always on
  11. GitHub使用教程详解(上)——官网操作指南[翻译]
  12. 如何关掉 pyg解密小组声明窗口 (飘云阁番茄插件)
  13. 积极响应号召,ModStart支持用户主动注销账号功能
  14. Java基础之类加载器
  15. java和ssm开发的医院体检预约系统有论文
  16. 函数模板和类模板的使用
  17. 【信息安全】信息安全三要素CIA
  18. 40000字 Matplotlib 实操干货,真的全!
  19. 微型计算机ALE,微型计算机技术课后习题一二三章答案
  20. 如何证明地球是圆的呢

热门文章

  1. MySQL 一起重新认识下count(*) ,count(1),count(id)
  2. C语言日期计算器vs2022
  3. 在阿里云上设置CDN
  4. 【UIAutomator2】实现微信自动加好友功能
  5. 《卓有成效的管理者(The Effective Executive)》读后感
  6. 中国电信 CTExcel - 亲测境外首选电话卡(推荐码:SHQL 或 KJNC)
  7. 高德地图大头针功能_关于高德地图遇到的自定义大头针的坑
  8. 【在线教育直播】直播很卡怎么办?
  9. led大屏按实际尺寸设计画面_led显示屏尺寸大小的计算方式
  10. 路由器安全——破解wifi密码,同时中间人攻击