A, GIF figure
1. Effect drawing
2. Upload pictures and create articles
3. Edit the article
4, add banner with article
2. NPM package used
1, the relation
Reference links: pug.bootcss.com/api/getting…
Pug sample code:
const pug = require('pug')
const fs = require('fs')
function editArticle (htmlJson, fileName) {
const compiledFunction = pug.compileFile('views/index.pug');
letindexHtml = compiledFunction({ ... htmlJson,// list: [
/ / {
// type: 'title-level-1',
// text: 'Active rule:'
/ /}
// ]
})
fs.writeFile(`./public/article/${fileName}`, indexHtml, function (err) {
if (err) {
throwerr; }}); }// Generate the add article
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: 'Added article succeeded'}}))else {
res.send(({
code: 400.message: 'Failed to add article'}}})))// Get the list of articles
app.get('/article/list'.async function (req, res) {
const data = await getArticleList()
res.send(({
code: 200.data: data,
message: 'Article List'}})))// Get the article by 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: 'Article Details'}})))// Edit the article
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: 'Edited article successfully'}})))Copy the code
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')
Copy the code
React
The React part of the front end does not use new technology, and the knowledge required includes:
Routing, ANTD components (Button, Input, message, Modal, Checkbox,Table), controlled component, Lifecycle (componentDidMount), Scrollbars library, moment library (timestamp to date), Axios, etc.
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)}>Add the article</Button>
</div>
<div>
<Table
columns={columns}
dataSource={list}
rowKey="uid"
scroll={{ x: 900 }}
></Table>
</div>
</div>
<Modal
title="Add article"
visible={addArticleModalVisible}
onOk={this.handleAddArticle.bind(this)}
onCancel={this.handleHideModal.bind(this)}>
<div className="m-row">
<Input
type="text"
value={articleTitle}
placeholder="Please enter the title of the article"
onChange={this.handleInput.bind(this, 'articleTitle')} ></Input>
</div>
</Modal>
</Scrollbars>
</div>); }}// Lifecycle
Object.assign(Article.prototype, {
renderColumns () {
return[{title: 'ID'.dataIndex: 'uid'}, {title: 'title'.dataIndex: 'title'}, {title: 'Article path'.dataIndex: 'path'.key: 'path'.render: (text, record) = > {
return <a href={text} target="_blank">{text}</a>}}, {title: 'Creation time'.dataIndex: 'create_time'.key: 'create_time'.render: (text, record) = > {
return <span>{moment(text).format('YYYY-MM-DD HH:mm:ss')}</span>}}, {title: 'operation'.fixed: 'right'.width: 150.render: (text, record, index) = > {
return <div>
<Button onClick={this.handleEditArticle.bind(this, record)} >Edit the articles</Button>
</div>}}},componentDidMount() {
this.getArticleList()
}
})
/ / event
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}`)}})// Controlled components
Object.assign(Article.prototype, {
handleInput(field, e) {
this.setState({
[field]: e.target.value
})
},
})
export default withRouter(Article)
Copy the code
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)}>Return to article list</Button>
</div>
<div className="m-edit-article-title">Edit the articles</div>
<div>
<div>The article ID: {articleId}</div>
<div>FileName: {fileName}</div>
<div>Article links:<a href={articlePath} target="_blank">{articlePath}</a></div>
</div>
<div className="m-article-toolbar">
<Button className="m-toolbar-btn" onClick={this.handleShowAddHeaderImageModal.bind(this)}>Title and top picture</Button>
<Button className="m-toolbar-btn" onClick={this.handleShowAddParagraphModal.bind(this)}>Add paragraph text</Button>
<Button className="m-toolbar-btn" onClick={this.handleShowAddParagraphTitleLevelFirstModal.bind(this)}>Add a level 1 paragraph heading</Button>
<Button className="m-toolbar-btn" onClick={this.handleShowAddParagraphTitleLevelSecondModal.bind(this)}>Add secondary paragraph headings</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)}>save</Button>
</div>
</div>
<div>
<Modal
title="Modify title and top picture"
visible={addHeaderImageModal}
onOk={this.handleAddHeaderImage.bind(this)}
onCancel={this.handleHideModal.bind(this)}>
<div className="m-row">
<span className="m-input-label">The article title</span>
<Input
className="m-input"
type="text"
value={articleTitle}
placeholder="Please enter the title of the article"
onChange={this.handleInput.bind(this, 'articleTitle')} ></Input>
</div>
<div className="m-row">
<span className="m-input-label">Top image link</span>
<Input
className="m-input"
type="text"
value={headerImagePath}
placeholder="Please enter the address of the top picture"
onChange={this.handleInput.bind(this, 'headerImagePath')} ></Input>
</div>
</Modal>
<Modal
title="Add paragraph"
visible={addParagraphModal}
onOk={this.handleAddParagraph.bind(this)}
onCancel={this.handleHideModal.bind(this)}>
<div className="m-row">
<span className="m-input-label">Whether the text is bold</span>
<Checkbox
className="m-checkbox"
checked={isParagraphStrong}
onChange={this.handleCheckbox.bind(this, 'isParagraphStrong')} >bold</Checkbox>
</div>
<div className="m-row">
<span className="m-input-label">Paragraph text</span>
<TextArea
className="m-input"
type="text"
rows={6}
value={paragraph}
placeholder="Please enter paragraph text"
onChange={this.handleInput.bind(this, 'paragraph')} ></TextArea>
</div>
</Modal>
<Modal
title="Add level 1 paragraph heading"
visible={addParagraphTitleLevelFirstModal}
onOk={this.handleAddParagraphTitleLevelFirst.bind(this)}
onCancel={this.handleHideModal.bind(this)}>
<div className="m-row">
<span className="m-input-label">Level 1 paragraph headings</span>
<Input
className="m-input"
type="text"
value={paragraphTitleLevelFirst}
placeholder="Please enter a level 1 paragraph title"
onChange={this.handleInput.bind(this, 'paragraphTitleLevelFirst')} ></Input>
</div>
</Modal>
<Modal
title="Add secondary paragraph headings"
visible={addParagraphTitleLevelSecondModal}
onOk={this.handleAddParagraphTitleLevelSecond.bind(this)}
onCancel={this.handleHideModal.bind(this)}>
<div className="m-row">
<span className="m-input-label">Level 2 paragraph headings</span>
<Input
className="m-input"
type="text"
value={paragraphTitleLevelSecond}
placeholder="Please enter the second paragraph title"
onChange={this.handleInput.bind(this, 'paragraphTitleLevelSecond')} ></Input>
</div>
</Modal>
</div>
</Scrollbars>
</div>); }}// Lifecycle
Object.assign(EditArticle.prototype, {
componentDidMount() {
this.getArticleById()
}
})
/ / event
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('The text box input JSON format is not correct! ')
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('Edit succeeded')}})}}// Dialog box related
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,})}})// Top image, paragraph, paragraph level 1 heading, paragraph level 2 heading
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()
}
})
/ / tools
Object.assign(EditArticle.prototype, {
formatTextAreaString(htmlJson) {
let articleTextArea = JSON.stringify(htmlJson, null.2)
this.setState({
articleTextArea,
})
}
})
// Controlled components
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)
Copy the code