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

react

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-span'

p.m-paragraph-text-span=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',

width: 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-span' : '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)

以上是 React(1702H)文章管理-cms系统 的全部内容, 来源链接: utcz.com/z/381549.html

回到顶部