• React(1702H)文章管理-cms系统


    一、GIF图

    1、效果图

     2、上传图片和创建文章

    3、编辑文章

    4、添加带文章的banner

    二、使用到的npm包

    1、pug

    参考链接:https://pug.bootcss.com/api/getting-started.html

    pug示例代码:

    const pug = require('pug')
    const fs = require('fs')
    
    function editArticle (htmlJson, fileName) {
      const compiledFunction = pug.compileFile('views/index.pug');
      let indexHtml = compiledFunction({
        ...htmlJson,
        // list: [
        //   {
        //     type: 'title-level-1',
        //     text: '活动规则:'
        //   }
        // ]
      })
      fs.writeFile(`./public/article/${fileName}`, indexHtml, function (err) {
        if (err) {
          throw err;
        }
      });
    }
    
    //生成添加文章
    app.post('/add/article', async function (req, res) {
      let { htmlJson } = req.body
      let fileName = (new Date()).getTime() + '.html'
      editArticle(htmlJson, fileName)
    
      let articlePath = `http://localhost:8888/article/${fileName}`
      let uid = getID(10)
      let createTime = new Date().getTime()
      let sqlData = await addArticle(
        uid, 
        htmlJson.articleTitle, 
        fileName, 
        articlePath, 
        JSON.stringify(htmlJson), 
        createTime)
    
      if (sqlData) {
        let data = {
          fileName,
          articlePath
        }
        res.send(({
          code: 200,
          data: data,
          message: '添加文章成功'
        }))
      } else {
        res.send(({
          code: 400,
          message: '添加文章失败'
        }))    
      }
    })
    
    //获取文章列表
    app.get('/article/list', async function (req, res) {
      const data = await getArticleList()
      res.send(({
        code: 200,
        data: data,
        message: '文章列表'
      }))
    })
    
    //通过id获取文章
    app.get('/article_detail', async function (req, res) {
      let {id} = req.query
      const data = await getArticleDetail(id)
      res.send(({
        code: 200,
        data: data,
        message: '文章详情'
      }))
    })
    
    //编辑文章
    app.post('/article_edit', async function (req, res) {
      let {articleId, title, fileName, htmlJson} = req.body
      const data = await editArticleDetail(articleId, title, JSON.stringify(htmlJson))
      editArticle(htmlJson, fileName)
      res.send(({
        code: 200,
        message: '编辑文章成功'
      }))
    })

    views/index.pug:

    doctype html
    html
      head
        title=articleTitle
        link(rel="stylesheet" type='text/css' href='/css/index.css')
      body
        div.m-warp
          div.m-hearder-wrap
            img(class="m-header-img" src=headerImagePath)
          div.m-content-wrap
            if list
              each item in list
                case item.type 
                  when 'p'
                    p.m-paragraph-text=item.text    
                  when 'p-strong'
                    p.m-paragraph-text-strong=item.text    
                  when 'title-level-1'
                    div.m-title-level-1=item.text
                    div.m-division
                  when 'title-level-2'
                    div.m-title-level-2=item.text
    
    
        script(src='/common/js/jquery.min.js')
        script(src='/js/index.js')

    三、前端React

    前端React部分没有用到新技术,需要具备知识包括:

    路由、antd组件(Button, Input, message, Modal, Checkbox,Table)、受控组件、生命周期(componentDidMount)、Scrollbars库、moment库(时间戳转日期)、axios等。

    Article.js:

    import React from 'react';
    import { withRouter } from 'react-router-dom'
    import { Button, Input, message, Modal, Table } from 'antd';
    import { Scrollbars } from 'react-custom-scrollbars'
    import moment from 'moment'
    import Api from '../../api/index.js'
    import * as keyCode from '../../api/keyCode.js'
    import './index.css'
    
    const { TextArea } = Input;
    class Article extends React.Component {
      constructor(props) {
        super(props)
        this.state = {
          addArticleModalVisible: false,
          articleTitle: '',
          list: [],
        }
      }
      render() {
        let {
          addArticleModalVisible,
          articleTitle,
          list,
        } = this.state
        let columns = this.renderColumns()
        return (
          <div className="m-content">
            <Scrollbars>
              <div className="m-content-inner">
                <div className="m-article-toolbar">
                  <Button onClick={this.handleShowAddArticleModal.bind(this)}>添加文章</Button>
                </div>       
                <div>
                  <Table 
                    columns={columns} 
                    dataSource={list} 
                    rowKey="uid"
                    scroll={{ x: 900 }}
                    ></Table>                     
                </div> 
              </div>
              <Modal
                title="添加文章"
                visible={addArticleModalVisible}
                onOk={this.handleAddArticle.bind(this)}
                onCancel={this.handleHideModal.bind(this)}>
                <div className="m-row">
                  <Input 
                    type="text" 
                    value={articleTitle}
                    placeholder="请输入文章标题"
                    onChange={this.handleInput.bind(this, 'articleTitle')}></Input>
                </div>         
              </Modal>          
            </Scrollbars>        
    			</div>
        );
      }
    }
    
    //生命周期
    Object.assign(Article.prototype, {
      renderColumns () {
        return [
          {
            title: 'ID',
            dataIndex: 'uid',
          },
          {
            title: '标题',
            dataIndex: 'title',
          },       
          {
            title: '文章路径',
            dataIndex: 'path',
            key: 'path',
            render: (text, record) => {
              return <a href={text} target="_blank">{text}</a>
            }
          },
          {
            title: '创建时间',
            dataIndex: 'create_time',
            key: 'create_time',
            render: (text, record) => {
              return <span>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</span>
            }
          },
          {
            title: '操作',
            fixed: 'right',   
             150,
            render: (text, record, index) => {
              return <div>
                <Button onClick={this.handleEditArticle.bind(this, record)}>编辑文章</Button>
              </div>
            }
          }                     
        ]
      },   
      componentDidMount() {
        this.getArticleList()
      }
    })
    
    //事件
    Object.assign(Article.prototype, {
      handleShowAddArticleModal() {
        this.setState({
          addArticleModalVisible: true,
          articleTitle: ''
        })
      },
      handleHideModal() {
        this.setState({
          addArticleModalVisible: false,
          addHeaderImageModal: false
        })
      },
      handleAddArticle() {
        let {articleTitle} = this.state
        let htmlJson = {
          articleTitle,
          list: [],
        }
        let data = {
          htmlJson
        }
        Api.addArticle(data).then((res) => {
          console.log(res)
          this.getArticleList()
          this.handleHideModal()
        })
      },
      getArticleList() {
        Api.getArticleList().then((res) => {
          if (res.code = keyCode.SUCCESS) {
            this.setState({
              list: res.data.list
            })
          }
        })
      },
      handleEditArticle(record) {
        this.props.history.push(`/management/edit_article/${record.uid}`)
      }
    })
    
    //受控组件
    Object.assign(Article.prototype, {
      handleInput(field, e) {
        this.setState({
          [field]: e.target.value
        })
      },
    })
    
    export default withRouter(Article)
    

    EditArticle.js:

    import React from 'react';
    import { withRouter } from 'react-router-dom'
    import { Button, Input, message, Modal, Checkbox } from 'antd';
    import { Scrollbars } from 'react-custom-scrollbars'
    import Api from '../../api/index.js'
    import * as keyCode from '../../api/keyCode.js'
    import './index.css'
    
    const { TextArea } = Input;
    class EditArticle extends React.Component {
      constructor(props) {
        super(props)
        this.state = {
          articleId: '',
          fileName: '',
          articlePath: '',
          articleTitle: '',
          headerImagePath: '',
          htmlJson: {
          },
          articleTextArea: '',
          paragraph: '',
          isParagraphStrong: false,
          paragraphTitleLevelFirst: '',
          paragraphTitleLevelSecond: '',
          addHeaderImageModal: false,
          addParagraphModal:false,
          addParagraphTitleLevelFirstModal: false,
          addParagraphTitleLevelSecondModal: false,
        }
      }
      render() {
        let {
          articleId,
          fileName,
          articlePath,
          articleTitle,
          headerImagePath,
          articleTextArea,
          paragraph,
          isParagraphStrong,
          paragraphTitleLevelFirst,
          paragraphTitleLevelSecond,
          addHeaderImageModal,
          addParagraphModal,
          addParagraphTitleLevelFirstModal,
          addParagraphTitleLevelSecondModal,
        } = this.state
        return (
          <div className="m-content">
            <Scrollbars>
              <div className="m-content-inner">
                <div>
                  <Button onClick={this.handleGoBack.bind(this)}>返回文章列表</Button>
                </div>
                <div className="m-edit-article-title">编辑文章</div>
                <div>
                  <div>文章ID: {articleId}</div>
                  <div>文件名: {fileName}</div>
                  <div>文章链接: <a href={articlePath} target="_blank">{articlePath}</a></div>
                </div>
                
                <div className="m-article-toolbar">
                  <Button className="m-toolbar-btn" onClick={this.handleShowAddHeaderImageModal.bind(this)}>标题和顶部图片</Button>
                  <Button className="m-toolbar-btn" onClick={this.handleShowAddParagraphModal.bind(this)}>添加段落文本</Button>
                  <Button className="m-toolbar-btn" onClick={this.handleShowAddParagraphTitleLevelFirstModal.bind(this)}>添加一级段落标题</Button>
                  <Button className="m-toolbar-btn" onClick={this.handleShowAddParagraphTitleLevelSecondModal.bind(this)}>添加二级段落标题</Button>
                </div>    
                <div className="m-article-textarea-wrap">
                  <TextArea 
                    rows={10} 
                    value={articleTextArea}
                    onChange={this.handleInput.bind(this, 'articleTextArea')}
                    />
                </div>   
                <div className="m-login-row">
                  <Button onClick={this.handleEditArticle.bind(this)}>保存</Button>
                </div>    
              </div>        
              <div>
                <Modal
                  title="修改标题和顶部图片"
                  visible={addHeaderImageModal}
                  onOk={this.handleAddHeaderImage.bind(this)}
                  onCancel={this.handleHideModal.bind(this)}>
                  <div className="m-row">
                    <span className="m-input-label">
                      文章标题
                    </span>
                    <Input 
                      className="m-input"
                      type="text" 
                      value={articleTitle}
                      placeholder="请输入文章标题"
                      onChange={this.handleInput.bind(this, 'articleTitle')}></Input>
                  </div>                
                  <div className="m-row">
                    <span className="m-input-label">
                      顶部图片链接
                    </span>                
                    <Input 
                      className="m-input"
                      type="text" 
                      value={headerImagePath}
                      placeholder="请输入顶部图片地址"
                      onChange={this.handleInput.bind(this, 'headerImagePath')}></Input>
                  </div>          
                </Modal>  
                <Modal
                  title="添加段落"
                  visible={addParagraphModal}
                  onOk={this.handleAddParagraph.bind(this)}
                  onCancel={this.handleHideModal.bind(this)}>
                  <div className="m-row">
                    <span className="m-input-label">
                      文本是否加粗
                    </span>
                    <Checkbox 
                      className="m-checkbox"
                      checked={isParagraphStrong} 
                      onChange={this.handleCheckbox.bind(this, 'isParagraphStrong')}>加粗</Checkbox>
                  </div>
                  <div className="m-row">
                    <span className="m-input-label">
                      段落文本
                    </span>                
                    <TextArea 
                      className="m-input"
                      type="text" 
                      rows={6}
                      value={paragraph}
                      placeholder="请输入段落文本"
                      onChange={this.handleInput.bind(this, 'paragraph')}></TextArea>
                  </div>          
                </Modal> 
                <Modal
                  title="添加一级段落标题"
                  visible={addParagraphTitleLevelFirstModal}
                  onOk={this.handleAddParagraphTitleLevelFirst.bind(this)}
                  onCancel={this.handleHideModal.bind(this)}>
                  <div className="m-row">
                    <span className="m-input-label">
                      一级段落标题
                    </span>                
                    <Input 
                      className="m-input"
                      type="text" 
                      value={paragraphTitleLevelFirst}
                      placeholder="请输入一级段落标题"
                      onChange={this.handleInput.bind(this, 'paragraphTitleLevelFirst')}></Input>
                  </div>          
                </Modal>  
                <Modal
                  title="添加二级段落标题"
                  visible={addParagraphTitleLevelSecondModal}
                  onOk={this.handleAddParagraphTitleLevelSecond.bind(this)}
                  onCancel={this.handleHideModal.bind(this)}>
                  <div className="m-row">
                    <span className="m-input-label">
                      二级段落标题
                    </span>                
                    <Input 
                      className="m-input"
                      type="text" 
                      value={paragraphTitleLevelSecond}
                      placeholder="请输入二级段落标题"
                      onChange={this.handleInput.bind(this, 'paragraphTitleLevelSecond')}></Input>
                  </div>          
                </Modal>                         
              </div>         
            </Scrollbars>        
    			</div>
        );
      }
    }
    
    //生命周期
    Object.assign(EditArticle.prototype, { 
      componentDidMount() {
        this.getArticleById()
      }
    })
    
    //事件
    Object.assign(EditArticle.prototype, {
      handleGoBack() {
        this.props.history.push('/management/article')
      },
      getArticleById() {
        let {match} = this.props
        let articleId = match.params.id
        this.setState({
          articleId
        })
        Api.getArticleDetail(`?id=${articleId}`).then((res) => {
          console.log(res)
          if (res.code === keyCode.SUCCESS) {
            let articleTextArea = JSON.stringify(res.data[0].content, null, 2)
            this.setState({
              fileName: res.data[0].file_name,
              articlePath: res.data[0].path,
              articleTextArea,
              htmlJson: res.data[0].content
            })
          }
        })
      },
      handleEditArticle() {
        let {articleId, fileName, articleTextArea} = this.state
        let htmlJson
        try {
          htmlJson = JSON.parse(articleTextArea)
        } catch (err) {
          console.log(err)
          message.info('文本框里输入的json格式不对!')
          return
        }
        
        let title = ''
        if (htmlJson.articleTitle) {
          title = htmlJson.articleTitle
        }
        let data = {
          articleId,
          title,
          fileName,
          htmlJson,
        }
        Api.editArticle(data).then((res) => {
          console.log(res)
          if (res.code === keyCode.SUCCESS) {
            this.setState({
              htmlJson
            })
            message.info('编辑成功')
          }
        })
      }
    })
    
    //对话框相关
    Object.assign(EditArticle.prototype, {
      handleShowAddHeaderImageModal() {
        let {htmlJson} = this.state
        this.setState({
          addHeaderImageModal: true,
          articleTitle: htmlJson.articleTitle,
          headerImagePath: htmlJson.headerImagePath,
        })
      },
      handleShowAddParagraphModal(){
        this.setState({
          addParagraphModal: true,
          paragraph: '',
          isParagraphStrong: false,
        })
      },
      handleShowAddParagraphTitleLevelFirstModal() {
        this.setState({
          addParagraphTitleLevelFirstModal: true,
          paragraphTitleLevelFirst: '',
        })
      },
      handleShowAddParagraphTitleLevelSecondModal() {
        this.setState({
          addParagraphTitleLevelSecondModal: true,
          paragraphTitleLevelSecond: '',
        })
      },
      handleHideModal() {
        this.setState({
          addHeaderImageModal: false,
          addParagraphModal: false,
          addParagraphTitleLevelFirstModal: false,
          addParagraphTitleLevelSecondModal: false,
        })
      },
    })
    
    
    
    //顶部图片、段落、段落一级标题、段落二级标题
    Object.assign(EditArticle.prototype, {
      handleAddHeaderImage() {
        let {htmlJson, articleTitle, headerImagePath} = this.state
        htmlJson.headerImagePath = headerImagePath
        htmlJson.articleTitle = articleTitle
        this.setState({
          htmlJson,
        })
        this.formatTextAreaString(htmlJson)
        this.handleHideModal()
      },
      handleAddParagraph() {
        let {htmlJson, paragraph, isParagraphStrong } = this.state
        if (!htmlJson.list) {
          htmlJson.list = []
        }
        htmlJson.list.push({
          type: isParagraphStrong ? 'p-strong' : 'p',
          text: paragraph
        })    
    
        this.setState({
          htmlJson
        })
        this.formatTextAreaString(htmlJson)
        this.handleHideModal()
      },
      handleAddParagraphTitleLevelFirst() {
        let {htmlJson, paragraphTitleLevelFirst } = this.state
        if (!htmlJson.list) {
          htmlJson.list = []
        }
        htmlJson.list.push({
          type: 'title-level-1',
          text: paragraphTitleLevelFirst
        })    
    
        this.setState({
          htmlJson
        })
        this.formatTextAreaString(htmlJson)
        this.handleHideModal()
      },
      handleAddParagraphTitleLevelSecond() {
        let {htmlJson, paragraphTitleLevelSecond } = this.state
        if (!htmlJson.list) {
          htmlJson.list = []
        }
        htmlJson.list.push({
          type: 'title-level-2',
          text: paragraphTitleLevelSecond
        })    
    
        this.setState({
          htmlJson
        })
        this.formatTextAreaString(htmlJson)
        this.handleHideModal()
      }
    })
    
    //工具
    Object.assign(EditArticle.prototype, {
      formatTextAreaString(htmlJson) {
        let articleTextArea = JSON.stringify(htmlJson, null, 2)
        this.setState({
          articleTextArea,
        })
      }
    })
    
    //受控组件
    Object.assign(EditArticle.prototype, {
      handleInput(field, e) {
        this.setState({
          [field]: e.target.value
        })
      },
      handleCheckbox(field, e) {
        this.setState({
          [field]: e.target.checked
        })
      },
    })
    
    export default withRouter(EditArticle)
    
  • 相关阅读:
    Delphi播放铃声
    小技巧
    Delphi线程中使用waitfor返回值
    window安装、启动consul
    kali2020-bash: openvas-setup:未找到命令 ,解决办法
    zookeeper 客户端
    redis 集群
    activeMQ
    Shiro
    Eclipse Java注释模板设置详解
  • 原文地址:https://www.cnblogs.com/xutongbao/p/11915676.html
Copyright © 2020-2023  润新知