文章编辑页面
PageHeader页头
传送门
引入
import { PageHeader, Button } from 'antd'; 复制代码
复制页头去掉描述
<PageHeader ghost={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' }} 复制代码
设置对话框
对话框传输门
引入Modal
import { Modal, Button } from 'antd'; 复制代码
设置button
<Button key="1" type="primary" onClick={showModal}> 提交文章 </Button> 复制代码
添加方法和模块
const [isModalVisible, setIsModalVisible] = useState(false); 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表单传送门
引入Form、Input
import {Form, Input, PageHeader, Button ,Modal} from 'antd'; //在Model下添加表单代码 <Form name="basic" labelCol={{ span: 3 }} wrapperCol={{ span: 21 }} autoComplete="off" > <Form.Item label="标题" name="title" rules={[{ required: true, message: '请填写标题!' }]} > <Input /> </Form.Item> <Form.Item label="副标题" name="subTitle" > <Input /> </Form.Item> </Form> 复制代码
对话框获取表单的值
Form表单弹出层传送门
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} = values console.log(content) }) .catch(() => { return ; }); 复制代码
editor.config.onchange = (newHtml) => { setContent(newHtml) } 复制代码
-发送请求
// 请求 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监听路由
// 一般加个空数组就是为了模仿componentDidMounted useEffect(()=>{ 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'> <Form style={{width: '400px'}} name="basic" initialValues={{ remember: true, }} autoComplete="off" > <Form.Item label="修改用户名" name="username" rules={[ { required: true, message: 'Please input your username!', }, ]} > <Input placeholder='请输入新用户名' /> </Form.Item> <Form.Item label="修 改 密 码" 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上传
传送门
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 © 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