《CMS后台系统》项目实战 详细分解(十)

简介: 《CMS后台系统》项目实战 详细分解(十)

文章编辑页面

PageHeader页头

传送门

ant.design/components/…

引入

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="文章编辑"
复制代码


  • 实现效果

微信截图_20221112121835.png


  • 使用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>
复制代码


微信截图_20221112121922.png


  • 富文本编辑器

改为外界声明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' }}
复制代码


设置对话框

对话框传输门

ant.design/components/…

引入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}>
复制代码


修改标题

微信截图_20221112121955.png


title="填写文章标题"
复制代码


简化函数代码

onClick={() =>  setIsModalVisible(true)}
onCancel={() =>  setIsModalVisible(false)}
复制代码


Form表单传送门

ant.design/components/…

引入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表单弹出层传送门

ant.design/components/…

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 ;
    });
  };
复制代码


  • 实现效果

c4196e06ea184964b18d4575370650f1_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


onOk添加onText、cancelText

okText="提交" cancelText="取消"
复制代码

981697d3d7174488bdf9f28d441f2f22_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


发送文章请求

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

a278d245e0a24398bbe04849dc2babb0_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


  • 书写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)
}
复制代码

a5544d9d4037456fa41607121883ae6b_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


cc082a6adbb3498292e19280d7df7ad2_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


-发送请求

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


实现效果

d5f3a8de3a454321b6805f2ca78ba7a4_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


编辑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>
复制代码


  • 实现效果

85c69da3ae104243bb20c4cabab34d7a_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


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

引入useParams

import { useParams } from 'react-router-dom'
const params = useParams()
复制代码


书写onBack

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


-实现效果

db83aa14908c44668893df172f2c8c7a_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


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) // 重新设置编辑器内容
    }
  })
}
复制代码


  • 实现效果(点击编辑之后会跳转到编辑页面,并且显示出文本内容)

4f82334466dd43a1817426d1304fd0ab_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


  • 设置title、subTitle
const [title, setTitle] = useState('')
const [subTitle, setSubTitle] = useState('')
setTitle(res.data.title)
setSubTitle(res.data.subTitle)
复制代码


  • 为表单添加初始值

在Form 标签中使用该属性

initialValues={{title:title,subTitle:subTitle}}
复制代码


  • 实现效果

4afe7b24bcc64d809b2e672323f8a3c4_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


修改更新文章

书写更新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)
    })
  }
复制代码


  • 实现效果

59e1db2a29804db4b12b942ee82d7030_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


  • 使用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

点击编辑一篇文章之后,再次点击菜单栏,文章编辑。页面的路径更改但是文本框并没有清除。d47f33b93b13485389ac577d1ad49b1e_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


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

引入location

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


8317754f9dcf442f8ec48acf0f01f864_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


  • 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)
复制代码

abf91e33431942229a185ac9957b94f8_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


  • 为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;
}
复制代码


  • 实现效果

1b780eb193ff460499afa251fe6e3cd8_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


  • 引入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);
        }
      })
  }, []);
复制代码


  • 实现效果

c69b556d60d9460e9f4036c55087bcd7_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


修改用户资料

  • 书写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上传

传送门

ant.design/components/…

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,并且修改

2b8a35678e374d3dac2daaca6b0dd3a5_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


// 点击了上传图片
 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)       
      }
      );
    }
  };
复制代码


  • 函数调用修改

3ac2897f250b4bceb63cbf7aea03097b_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


引入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';
复制代码


  • 实现效果

57bd39ebda2c4b35a3450ba4c3eaf66f_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


Upload组件添加请求头

headers={{"cms-token": localStorage.getItem('cms-token')}}
复制代码

9d93191e9ce14917ba58600059fe486f_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


  • 存储图片名称

这里需要打印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>
复制代码

3a236766d0684ac7a3d8cb6e533893c4_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


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)
      }
      );
    }
  };
复制代码


ea8aeb30087d4ad39c60209cdfa23ba2_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.png


添加强制刷新

window.location.reload()
复制代码


使用react-redux

安装react-redux

yarn add react-redux



目录
相关文章
|
6月前
|
监控 NoSQL Java
十八张图带你入门实时监控系统HertzBeat
我们经常讲:研发人员有两只眼睛,一只是监控平台,另一只是日志平台。在对性能和高可用讲究的场景里,监控平台的重要性再怎么强调也不过分。 这篇文章,我们聊聊开源实时监控告警系统 HertzBeat 赫兹跳动。
十八张图带你入门实时监控系统HertzBeat
|
6月前
|
架构师 Java
jvm性能调优实战 - 35电商APP后台系统如何对Full GC进行深度优化
jvm性能调优实战 - 35电商APP后台系统如何对Full GC进行深度优化
98 0
|
数据库
【自然框架】CMS之数据库设计
    在园子里也混了三年多,随笔200多,一开始只是想把自己的经验写一下,后来呢弄出来了一个“自然框架”,主要精力就放在了介绍自然框架的思路上面了。随笔多了就发现一个问题:有点乱。虽然博客有分组,但是只支持一级分组,不支持n级的。
1593 0
|
3月前
|
监控 小程序 数据处理
揭秘支付宝小程序性能优化秘籍:从加载到运行,每一步都快人一步!
【8月更文挑战第27天】本文深入探讨了支付宝小程序性能优化的关键技术和策略,包括减少网络请求、利用CDN加速、代码按需加载、图片压缩、懒加载以及性能监控等多方面内容,并提供了实用的示例代码,帮助开发者显著提升小程序的加载速度与运行效率,创造更佳用户体验。
79 1
|
6月前
|
Prometheus 监控 Cloud Native
JVM工作原理与实战(三十三):监控GC过程的工具
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了jstat工具、VisualVM插件、Prometheus + Grafana、GC日志等内容。
229 0
|
6月前
|
Arthas Prometheus 监控
JVM工作原理与实战(二十九):监控内存泄漏的工具
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了解决内存溢出的步骤、Top命令、VisualVM、Arthas、Prometheus + Grafana等内容。
405 0
|
小程序 数据可视化 数据库
云开发(微信-小程序)笔记(十七)---- cms(内容管理)及案例
云开发(微信-小程序)笔记(十七)---- cms(内容管理)及案例
527 0
|
存储 Java 对象存储
JavaWeb第八章课后题 会话跟踪
JavaWeb第八章课后题 会话跟踪
137 0
|
存储
《CMS后台系统》项目实战 详细分解(五)
《CMS后台系统》项目实战 详细分解(五)
93 0
《CMS后台系统》项目实战 详细分解(五)
|
存储
《CMS后台系统》项目实战 详细分解(四)
《CMS后台系统》项目实战 详细分解(四)
138 0
《CMS后台系统》项目实战 详细分解(四)