preface

Page Display Screenshot

1. Create a database

Open the database and create a new database, react_blog. Create two new tables, type and article.

Type table: 1. id: type number int type 2. Typename: article typename VARCHAR type 3. OrderNum: Type number int Article type table: 1. id: article number int type 2. Article type number int type 3. Title: article title, varchar type 4. Article_content: article body content, text type 5. Publish time, date typeCopy the code

2. Middle stage construction

2.1 Preparation Phase

1. Create a service folder and use it as the middle stage

    egg-init --type=simple
    npm install
    npm run dev
Copy the code

2. Create two folders in the service/ Controller folder: admin(the management terminal uses all APIS) and default(the client uses all apis).

3. Create a Router folder under the APP folder to separate routes into the front and back ends. Create two new files default.js and admin.js under the folder.

default.js

module.exports = app =>{
    const {router,controller} = app
    router.get('/default/index',controller.default.home.index)
}
Copy the code

service/app/router.js

'use strict';
module.exports = app => {

  require('./router/default')(app)
};
Copy the code

Here is the directory screenshot

2.2 Using a Database

  1. To use the mysql database in egg.js, you need to install the egg-mysql module.
yarn add egg-mysql
Copy the code
  1. plugin.js

/server/config/plugin.js

'use strict';
exports.mysql = {
  enable: true,
  package: 'egg-mysql'
}
Copy the code
  1. / config/config. Default. Js configuration database connection
config.mysql = { // database configuration client: { // host host: 'localhost', // port port: '3306', // username user: 'root', // password password: '',// enter the password of the database // database database: 'react_blog', }, // load into app, default is open app: true, // load into agent, default is close agent: false, };Copy the code

2.3 Cross-domain Solution

1. The egg-CORS module is specially designed to solve the problem of egg.js cross-domain. The cross-domain setting can be completed with simple configuration

yarn add egg-cors
Copy the code

2. After the completion of the need for/service/config/plugin. Js file modifications, add an egg – cors module

exports.cors: {
    enable: true,
    package: 'egg-cors'
}
Copy the code

3. After configuring the plugin.js file, you also need to configure the config.default.js file. This file mainly sets what domain names and request methods are allowed to be accessed across domains. The configuration code is as follows.

Config. security = {CSRF: {enable: false}, domainWhiteList: ['*']}; Config. cors = {origin: 'http://localhost:3000', // Only this domain is allowed to access interface credentials: true, // enable authentication allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS' };Copy the code

3. Front desk construction

3.1 Preparation

  1. Create front-end folders
npx create-next-app blog
yarn dev
Copy the code

2. Use the yarn command to install the @zeit/next-css package. Its main function is to enable Next-.js to load CSS files.

yarn add @zeit/next-css
Copy the code

In the blog root directory, create a new file next. Config.js. This is the general configuration file for next.js. Write the following code:

const withCss = require('@zeit/next-css') if(typeof require ! == 'undefined'){ require.extensions['.css']=file=>{} } module.exports = withCss({})Copy the code

3. Install ANTD using YARN

yarn add antd 
yarn add babel-plugin-import
Copy the code

Create a. Babelrc file in the project root directory and write the following configuration file.

{"presets":["next/ Babel "], // the general configuration file of next.js, which is equivalent to inheriting all of its own configuration "plugins": ["import", {"libraryName":"antd"}]]}Copy the code

4. In the Pages directory, create a _app.js file to import CSS globally

import App from 'next/app'
import 'antd/dist/antd.css'
export default App
Copy the code

5. Create the root directory config folder under blog and create the apiurl. js file to obtain the data interface of the middle platform

Let ipUrl = 'http://127.0.0.1:7001/default/' / / write their middle data interface let servicePath = {} export default servicePath;Copy the code

Here is a screenshot of the directory

3.2 Head production + middle stage data acquisition

Create a header. js file under /blog/components/ directory. The user name is displayed on the left side of the Header, and the home page and article type are displayed on the right side for users to click

1. The first in China to obtain type data service/app/default/home. Set the SQL query language id in js, type type

'use strict'; const Controller = require('egg').Controller class HomeController extends Controller { async getTypeInfo(){ const result  = await this.app.mysql.select('type') this.ctx.body ={data:result} } async getListById(){ let id = this.ctx.params.id let sql = 'SELECT article.id as id,'+ 'article.title as title,'+ 'article.introduce as introduce,'+ "DATE_FORMAT(article.addTime,'%Y-%m-%d') as addTime,"+ 'article.view_count as view_count ,'+ 'type.typeName as typeName '+ 'FROM article LEFT JOIN type ON article.type_id = type.Id '+ 'WHERE type_id='+id const result = await this.app.mysql.query(sql) this.ctx.body={data:result} } } module.exports = HomeControllerCopy the code

2. In the service/app/router/default route that is configured in the js

module.exports = app =>{
    const {router,controller} = app
    router.get('/default/index',controller.default.home.index)
    router.get('/default/getTypeInfo',controller.default.home.getTypeInfo)
 router.get('/default/getListById/:id',controller.default.home.getListById)
}
Copy the code

3. Add the middle end to blog/config/ apiurl.js

let servicePath = {
    getTypeInfo:ipUrl + 'getTypeInfo',
    getListById:ipUrl + 'getListById/', 
}
Copy the code

4. Blog/components/Header. Js for terminal types, according to the data type page jump after the parameter id to page type classification

import React, { useState, useEffect } from 'react' import './header.css' import { Row, Col, Menu, Icon } from 'antd' import Router from 'next/router' import Link from 'next/link' import axios from 'axios' import servicePath from '.. /config/apiUrl' const Header = () => { const [navArray , setNavArray] = useState([]) useEffect(()=>{ const fetchData = async ()=>{ const result= await Axios (servicepath.gettypeinfo).then((res)=>{console.log(res.data,res.data.data,' line 16 in header.js ') // setNavArray(res.data.data) return res.data.data } ) setNavArray(result) } fetchData() },[]) const handleClick = (e)=>{ if(e.key==0){ Router.push('/') }else if(e){ Router.push('/list? id='+e.key) } } return ( <div className="header"> <Row type="flex" justify="center"> <Col xs={24} sm={24} md={10} Lg ={15} XL ={12}> <span className=" header-LOGO "> Abba Abba abba </span> < SPAN className="header-txt"> front-end learning </span> </Col> <Col className="memu-div" xs={0} sm={0} md={14} lg={10} xl={7}> <Menu mode="horizontal" onClick={handleClick} > <Menu.Item Key ="0"> <Icon type="home" /> home </ menu. Item> {navarray.map ((Item) => {return (< menu. Item key={item.id}> <Icon type={item.icon} /> {item.typeName} </Menu.Item> ) }) } </Menu> </Col> </Row> </div> ) } export default HeaderCopy the code

Next provides the getInitialProps method to run on the server, where we can execute the interface to get the page data. One of the parameters that gets passed in to getInitialProps is a reQ object the parameter after the URL domain name that you can parse, It is important to note that when the getInitialProps method is refreshed, it may not get the REQ object during the jump to the page. In this case, res.query is used to get the parameters in the path.

import Head from 'next/head' import { Row, Col, List, Icon, Breadcrumb } from 'antd' import Header from '.. /components/Header' import Author from '.. /components/Author' import Advert from '.. /components/Advert' import Footer from '.. /components/Footer' import '.. /styles/pages/index.css' import React, { useState, useEffect } from 'react' import axios from 'axios' import servicePath from '.. /config/apiUrl' import Link from 'next/link' const Mylist = (list) => { const [mylist, setMylist] = useState(list.data); useEffect(()=>{ setMylist(list.data) }) return ( <> <Header /> <Row className="comm-main" type="flex" justify="center"> <Col className="comm-left" xs={24} sm={24} md={16} lg={18} xl={14} > <div> <div className="header"> Search results </div> <List itemLayout="vertical" dataSource={mylist} renderItem={item => ( <List.Item> <div className="list-title"> <Link href={{ pathname: '/detail', query: { id: item.id } }}> <a>{item.title}</a> </Link> </div> <div className="list-icon"> <span><Icon type="calendar" />{item.addTime}</span> <span><Icon type="folder" /> {item.typeName}</span> </div> <div className="list-context">{item.introduce}</div> </List.Item> )} /> </div> </Col> <Col className="comm-right" xs={0} sm={0} md={7} lg={5} xl={4}> <Author /> <Advert /> </Col> </Row> <Footer /> </> ) } Mylist.getInitialProps = async (context) => { let id = context.query.id const promise = new Promise((resolve) => { axios(servicePath.getListById + id).then( (res) => resolve(res.data) ) }) return await promise } export default MylistCopy the code

Implementation effect screenshot

3.3 Sidebar author column

Screenshot of implementation result:

Here directly put code blog/components/Author. Js

import {Avatar,Divider} from 'antd' import '.. /styles/components/Author.css' const Author =()=>{ return ( <div className="author-div comm-box"> <div> <Avatar Size ={100} SRC ="" /> </div> <div className="author-introduction"> WeChat: abaabaaba0222 < / div > < div > github:https://github.com/duskya/- < / div > < / div > < / div >)} export default AuthorCopy the code

3.4 Home page display + middle access to article data

  1. First get the article, in the service in the service/app/default/home. Js using SQL query to get the data
async getArticleList() { let sql = 'SELECT article.id as id,'+ 'article.title as title,'+ 'article.introduce as Introduce, 'main code + / / -- -- -- -- -- -- -- -- -- -- start "DATE_FORMAT (article addTime,' Y - m - % d % %) as addTime," main code + / / -- -- -- -- -- -- -- -- -- -- the end 'article.view_count as view_count ,'+ 'type.typeName as typeName '+ 'FROM article LEFT JOIN type ON article.type_id = type.Id' const results = await this.app.mysql.query(sql) this.ctx.body = { data: results } }Copy the code
  1. In the service/app/router/default. Js file configuration under routing
router.get('/default/getArticleList',controller.default.home.getArticleList)
Copy the code

3. Configure interface request data under blog config apiurl.js

GetArticleList :ipUrl + 'getArticleList', // Interface for listing articles on the home pageCopy the code

4. Blog \pages\index.js to get the article data, use the link tag to jump to the details page

import Head from 'next/head' import Header from '.. /components/Header' import { Row, Col, List } from 'antd' import axios from 'axios' import Link from 'next/link' import React, { useState } from 'react' import '.. /styles/pages/index.css' import Author from '.. /components/Author' import Footer from '.. /components/Footer' import servicePath from '.. /config/apiUrl' const Home = (list) => { console.log(list,'------') const [mylist, setMylist] = useState(list.data); return ( <> <Head> <title>Home</title> </Head> <Header /> <Row className="comm-main" type="flex" justify="center"> <Col The className = "comm - left" xs = = {24} {24} sm md = = {and} {16} lg xl = {and} > < div > < List header = {< div > < / div >} all notes itemLayout="vertical" dataSource={mylist} renderItem={item => ( <List.Item> <div className="list-title"> <Link href={{ pathname: '/detail/', query: { id: item.id } }}> <a>{item.title}</a> </Link> </div> <div className="list-icon"> <span> {item.addTime}</span> < span > {item. TypeName} < / span > {/ * < span > < Icon type = "fire" / > {item. View_count} people * /} < / span > < / div > < div className="list-context">{item.introduce}</div> </List.Item> )} /> </div> </Col> <Col className="comm-right" xs={0} sm={0} md={7} lg={5} xl={4}> <Author /> {/* <Advert /> */} </Col> </Row> <Footer /> </> ) } Home.getInitialProps = async  ()=>{ const promise = new Promise((resolve)=>{ axios(servicePath.getArticleList).then( (res)=>{ resolve(res.data) } ) }) return await promise } export default HomeCopy the code

3.5 Details jump + Id data display in the center

1. The first to get id in the service content, in the service/app/default/home. Js using SQL query parameters

Async getArticleById(){// Configure dynamic route transmission, Let SQL = 'SELECT article. Id as id,'+ 'article. Title as title,'+ 'article as introduce,'+ 'article.article_content as article_content,'+ "DATE_FORMAT(article.addTime,'%Y-%m-%d') as addTime,"+ 'article.view_count as view_count ,'+ 'type.typeName as typeName ,'+ 'type.id as typeId '+ 'FROM article LEFT JOIN type ON article.type_id = type.Id '+ 'WHERE article.id='+id const result = await this.app.mysql.query(sql) this.ctx.body={data:result} }Copy the code

2. In the service/app/router/default configuration under routing js file

router.get('/default/getArticleById/:id',controller.default.home.getArticleById)
Copy the code

3. Configure interface request data under blog config apiurl.js

GetArticleById :ipUrl + 'getArticleById/', // Article details page content interface, need to receive parametersCopy the code
  1. Details page
  • Start by installing two modules, Mark and Highlight, to make the code highlighted and the article format better. After downloading the LoDash file, introduce the tocify. TSX directory to automatically generate the directory structure
yarn add marked
yarn add highlight
yarn add lodash
Copy the code
  • Tocify. Benchmark code
import React from 'react'; import { Anchor } from 'antd'; import { last } from 'lodash/array'; const { Link } = Anchor; // To jump to the specified location on the page. export interface TocItem { anchor: string; level: number; text: string; children? : TocItem[]; } export type TocItems = TocItem[]; Export default class Tocify {tocItems: tocItems = []; index: number = 0; constructor() { this.tocItems = []; this.index = 0; } add(text: string, level: number) { const anchor = `toc${level}${++this.index}`; const item = { anchor, level, text }; const items = this.tocItems; If (items.length === 0) {// The first item is pushed items.push(item); } else { let lastItem = last(items) as TocItem; If (item.level > lastitem. level) {// item is lastItem's children for (let I = lastitem. level + 1; i <= 2; i++) { const { children } = lastItem; if (! Lastitem. children = [item]; // lastitem. children = [item]; break; } lastItem = last(children) as TocItem; // reset lastItem to the lastItem of children if (item.level <= lastitem. level) {// if the item level is less than or equal to lastItem level, it is regarded as and Children at the children. Push (item); break; }}} else {// Put the top items.push(item); } } return anchor; } reset = () => { this.tocItems = []; this.index = 0; }; renderToc(items: TocItem[]) {// recursion render return items.map(item => (<Link key={item.anchor} href={' #${item.anchor} '} title={item.text}>  {item.children && this.renderToc(item.children)} </Link> )); } render() { return ( <Anchor affix showInkInFixed> {this.renderToc(this.tocItems)} </Anchor> ); }}Copy the code
  • Tocify is introduced in the details page, where the article format is configured using marked and highlight, and the ID is passed remotely to obtain data
import React, { useState } from 'react' import Head from 'next/head' import { Row, Col, Affix, Icon, Breadcrumb } from 'antd' import Header from '.. /components/Header' import Author from '.. /components/Author' import Advert from '.. /components/Advert' import Footer from '.. /components/Footer' import servicePath from '.. /config/apiUrl' import '.. /styles/pages/detailed.css' import axios from 'axios' import marked from 'marked' import hljs from "highlight.js"; import 'highlight.js/styles/monokai-sublime.css'; import Tocify from '.. /components/tocify.tsx' const Detailed = (props) => { let articleContent = props.article_content const tocify = new Tocify() const renderer = new marked.Renderer(); renderer.heading = function (text, level, raw) { const anchor = tocify.add(text, level); return `<a id="${anchor}" href="#${anchor}" class="anchor-fix"><h${level}>${text}</h${level}></a>\n`; }; SetOptions ({renderer: renderer,// custom renderer render custom format GFM: Pedantic: false,// Only sanitize that matches Markdown's definition: False,// Raw output, ignoring HTML tags tables: true,// Support Github form tables, must open GFM option breaks: false,// Support Github break smartLists: Smartypants: false,// highlight: function (code) {return hljs.highlightauto (code).value; }// highlight rules}); Let HTML = marked(props. Article_content) return (<> <Head> <title> </Head> <Header /> <Row className="comm-main" type="flex" justify="center"> <Col className="comm-left" xs={24} sm={24} md={16} lg={18} xl={14} >  <div> <div> <div className="detailed-title"> {props.title} </div> <div className="list-icon center"> <span> {props.addTime}</span> <span> {props.typeName}</span> </div> <div className="detailed-content" dangerouslySetInnerHTML={{ __html: html }} > </div> </div> </div> </Col> <Col className="comm-right" xs={0} sm={0} md={7} lg={5} xl={4}> <Author /> <Advert /> <Affix offsetTop={5}> <div className=" Detailed -nav comm-box"> <div className="nav-title"> </div> <div className="toc-list"> {tocify && tocify.render()} </div> </div> </Affix> </Col> </Row> <Footer /> </> ) } Detailed.getInitialProps = async (context) => { console.log(context.query.id) let id = context.query.id const promise = new Promise((resolve) => { axios(servicePath.getArticleById + id).then( (res) => { // console.log(res.data,'+++++') Resolve (res.data.data[0])// SQL returns array})}) return await promise} export default DetailedCopy the code

4. Background construction

4.1 Preparation Phase

1. Use create-react-app admin to generate a directory and install some plug-ins

yarn add antd
yarn add react-router-dom

Copy the code
  1. You can delete app.js and create Pages/ main.js folder

src/index

import React from 'react';
import ReactDOM from 'react-dom';
import Main from './Pages/Main';

ReactDOM.render(
    <Main />,
  document.getElementById('root')
);
Copy the code

Main.js

import React from 'react';
import { BrowserRouter as Router, Route} from "react-router-dom";
import Login from './Login'
import AdminIndex from './AdminIndex'
function Main(){
    return (
        <Router>      
            <Route path="/" exact component={Login} />
            <Route path="/index/"  component={AdminIndex} />
        </Router>
    )
}
export default Main
Copy the code

The admin/ SRC/directory is captured

4.2 Login Page + Medium Platform Query whether the ID exists

1. Create a new table admin_user in our database and place user data in it

2. The admin/SRC/Pages/Login. Js, write the Login interface, access to the user name and password, transferred to China to judge whether there is the database user, if have to jump to the main page

import React , {useState,useEffect,createContext} from 'react'; import 'antd/dist/antd.css'; import '.. /static/css/Login.css'; import { Card, Input, Button ,Spin,message } from 'antd'; import axios from 'axios' import servicePath from '.. /config/apiUrl' const openIdContext = createContext() function Login(props){ const [userName , setUserName] = useState('') const [password , setPassword] = useState('') const [isLoading, setIsLoading] = useState(false) useEffect(()=>{ },[]) const checkLogin = ()=>{ setIsLoading(true) if(! UserName){message.error(' userName cannot be empty ') return false}else if(! Password){message.error(' password cannot be empty ') return false} let dataProps = {'userName':userName, 'password':password } axios({ method:'post', url:servicePath.checkLogin, data:dataProps, withCredentials: true, // header:{'Access-Control-Allow-Origin':'*' } }).then( res=>{ console.log(res.data) setIsLoading(false) SetItem ('openId', res.data.openid) message. Success (' already registered ') if(res.data.data==' successfully registered '){localstorage.setitem ('openId', res.data.openid) message. Push ('/index')}else{message.error(' username/password ')}}) setTimeout(()=>{setIsLoading(false)},1000)} return  ( <div className="login-div"> <Spin tip="Loading..." spinning={isLoading}> <Card title="Blog System" bordered={true} style={{ width: 400 }} > <Input id="userName" size="large" placeholder="Enter your userName" onChange={(e)=>{setUserName(e.target.value)}} /> <br/><br/> <Input.Password id="password" size="large" placeholder="Enter your password" onChange={(e)=>{setPassword(e.target.value)}} /> <br/><br/> <Button type="primary" size="large" block onClick={checkLogin} > Login in </Button> </Card> </Spin> </div> ) } export default LoginCopy the code

3. Set the database query id to check whether service\app\controller\admin\main.js exists

'use strict'; const Controller = require('egg').Controller class MainController extends Controller{ async checkLogin(){ let userName =this.ctx.request.body.userName let password = this.ctx.request.body.password const sql = "SELECT userName FROM admin_user WHERE userName = '" +userName + "'AND password = '"+password+"'" const res = await this.app.mysql.query(sql) if(res.length>0){ let openId =new Date().getTime() this.ctx.session.openId=openId this.ctx.body = {' data ':' login successful ', 'the openid: openid}. The console log (' -- -- -- -- -- -- -- -')} else {this. CTX. Body = {data: 'login failed'}}}}. The module exports = MainControllerCopy the code

4. The service/app/router/admin. Js configuration routing

module.exports = app =>{
    const {router,controller} =app
    router.get('/admin/index',controller.admin.main.index)
    router.post('/admin/checkLogin',controller.admin.main.checkLogin)

}
Copy the code

5. The admin/SRC/config/apiUrl. Js configuration request middle interface

Let ipUrl = "http://127.0.0.1:7001/admin/" let servicePath = {checkLogin: ipUrl + 'checkLogin', // Check whether the username and password are correct} export default servicePathCopy the code

4.3 the main page

  1. Admin/Pages/AdminIndex. Js set the sidebar and routing jump
import React,{useState} from 'react'; import { Layout, Menu, Breadcrumb } from 'antd'; import '.. /static/css/AdminIndex.css' import {Route, Router} from 'react-router-dom' import AddArticle from './AddArticle' import ArticleList from './ArticleList' const { Header, Content, Footer, Sider } = Layout; const { SubMenu } = Menu; function AdminIndex(props){ const [collapsed,setCollapsed] = useState(false) const onCollapse = collapsed => { setCollapsed(collapsed) }; const handleClickArticle = e =>{ console.log(e.item.props) if(e.key =='addArticle'){ props.history.push('/index/add') }else{ props.history.push('/index/list') } } return ( <Layout style={{ minHeight: Collapsible collapsible collapsible collapsible collapsible collapsible collapsible collapsible collapsible collapsible collapsible collapsible collapsible collapsible collapsible collapse  */} <Sider collapsible collapsed={collapsed} onCollapse={onCollapse}> <div className="logo" /> <Menu theme="dark" DefaultSelectedKeys ={['1']} mode="inline"> < menu. Item key="1"> <span> Workbench </span> </ menu. Item> < menu. Item key="addArticle" OnClick ={handleClickArticle}> <span> Add article </span> </ menu. Item> < menu. Item key="articleList" onClick={handleClickArticle}> <span> Article list </span> </ menu. Item> < menu. Item key="9"> <span> Message Management </span> </ menu. Item> </Menu> </Sider> <Layout> <Header style={{ background: '#fff', padding: 0 }} /> <Content style={{ margin: '0 16px' }}> <div style={{ padding: 24, background: '#fff', minHeight: 360 }}> <div> <Route path ='/index/' exact component={AddArticle} /> <Route path="/index/add/" exact component={AddArticle} /> <Route path="/index/add/:id" exact component={AddArticle} /> <Route path="/index/list/" component={ArticleList} /> </div> </div> </Content> <Footer style={{ textAlign: 'center' }}>abaabaaba.com</Footer> </Layout> </Layout> ) } export default AdminIndexCopy the code

4.4 Article List

1. Service \app\controller\admin\main.js database gets all articles and sends them to admin\ SRC \Pages\ articlelist.js display, and can delete edit articles. To get the ID, go to admin\ SRC \Pages\AddArticle. Js and display the content, title and so on.

admin\main.js

async getArticleList(){

        let sql = 'SELECT article.id as id,'+
                    'article.title as title,'+
                    'article.introduce as introduce,'+
                    "DATE_FORMAT(article.addTime,'%Y-%m-%d') as addTime,"+
                    'type.typeName as typeName '+
                    'FROM article LEFT JOIN type ON article.type_id = type.Id '+
                    'ORDER BY article.id DESC '
    
            const resList = await this.app.mysql.query(sql)
            this.ctx.body={list:resList}
    
    }
     async delArticle(){
        let id = this.ctx.params.id
        const res = await this.app.mysql.delete('article',{'id':id})
        this.ctx.body={data:res}
        console.log(id)
    }
 
    async getArticleById(){
        let id = this.ctx.params.id
    
        let sql = 'SELECT article.id as id,'+
        'article.title as title,'+
        'article.introduce as introduce,'+
        'article.article_content as article_content,'+
        "DATE_FORMAT(article.addTime,'%Y-%m-%d') as addTime,"+
        'article.view_count as view_count ,'+
        'type.typeName as typeName ,'+
        'type.id as typeId '+
        'FROM article LEFT JOIN type ON article.type_id = type.Id '+
        'WHERE article.id='+id
        const result = await this.app.mysql.query(sql)
        this.ctx.body={data:result}
    }
Copy the code

2. Service \app\router\admin.js Configure the route

    router.get('/admin/getArticleList',controller.admin.main.getArticleList)
    router.get('/admin/delArticle/:id',controller.admin.main.delArticle)
    router.get('/admin/getArticleById/:id',controller.admin.main.getArticleById
Copy the code

3.admin\src\config\apiUrl.js

GetArticleList :ipUrl + 'getArticleList', GetArticleById :ipUrl + 'getArticleById ', // get the details of the article according to the IDCopy the code

Admin \ SRC \Pages\ articlelist.js displays a list of articles and can be deleted and edited

import React,{useState,useEffect} from 'react'; import '.. /static/css/ArticleList.css' import {List,Row,Col,Modal,message,Button,Switch} from 'antd'; import axios from 'axios' import servicePath from '.. /config/apiUrl' const {confirm} =Modal function ArticleList(props){ const [list,setList] = useState([]) useEffect(()=>{ getList() },[]) const getList = ()=>{ axios({ method:'get', url: servicePath.getArticleList, withCredentials: true, header:{ 'Access-Control-Allow-Origin':'*' } }).then( res=>{ setList(res.data.list) } ) } const delArticle = (id)=>{ Confirm ({title: 'Are you sure you want to delete this blog post? ', Content: 'If you click the OK button, the article will be deleted forever and cannot be recovered. ', onOk() { axios(servicePath.delArticle+id,{ withCredentials: True}). Then (res=>{message.success(' message.success ') getList()})}, onCancel() {message.success(' nothing changed ')},}); } const updateArticle = (id,checked)=>{ props.history.push('/index/add/'+id) } return ( <div> <List header={ <Row The className = "list - div >" < Col span = {6} > < b > title < / b > < / Col > < Col span = {6} > < b > categories < / b > < / Col > < Col span = {6} > < / b > < b > release time < / Col > <Col SPAN ={6}> < B > Operation </ B > </Col> </Row>} bordered dataSource={list} renderItem={item => (< list.Item> <Row className="list-div"> <Col span={6}> {item.title} </Col> <Col span={6}> {item.typeName} </Col> <Col span={6}> {item.addtime} </Col> <Col span={6}> <Button type="primary" onClick={()=>{updateArticle(item.id)}}> modify </Button>&nbsp; < Button onClick = {() = > {delArticle (item. Id)}} > delete < / Button > < / Col > < / Row > < / List item >)} / > < / div >)} export default ArticleListCopy the code

4.5 Adding Articles

1. Set the text input box, Introduction, category, and date picker. Check whether to pass the parameter ID, if there is, it indicates that the article is modified, get the article value to display on the page.

import React, { useState, useEffect } from 'react';
import marked from 'marked'
import '../static/css/AddArticle.css'
import { Row, Col, Input, Select, Button, DatePicker, message } from 'antd'
import axios from 'axios'
import servicePath from '../config/apiUrl'

const { Option } = Select;
const { TextArea } = Input
function AddArticle(props) {

    const [articleId, setArticleId] = useState(0)  // 文章的ID,如果是0说明是新增加,如果不是0,说明是修改
    const [articleTitle, setArticleTitle] = useState('')   //文章标题
    const [articleContent, setArticleContent] = useState('')  //markdown的编辑内容
    const [markdownContent, setMarkdownContent] = useState('预览内容') //html内容
    const [introducemd, setIntroducemd] = useState()            //简介的markdown内容
    const [introducehtml, setIntroducehtml] = useState('等待编辑') //简介的html内容
    const [showDate, setShowDate] = useState()   //发布日期
    const [updateDate, setUpdateDate] = useState() //修改日志的日期
    const [typeInfo, setTypeInfo] = useState([]) // 文章类别信息
    const [selectedType, setSelectType] = useState(1) //选择的文章类别

    useEffect(() => {
        getTypeInfo()
        let tmpId = props.match.params.id
        if(tmpId){
            setArticleId(tmpId)
            getArticleById(tmpId)
        }
    }, [])

    marked.setOptions({
        renderer: marked.Renderer(),
        gfm: true,
        pedantic: false,
        sanitize: false,
        tables: true,
        breaks: false,
        smartLists: true,
        smartypants: false,
    });

    const changeContent = (e) => {
        setArticleContent(e.target.value)
        let html = marked(e.target.value)
        setMarkdownContent(html)
    }
    const changeIntroduce = (e) => {
        setIntroducemd(e.target.value)
        let html = marked(e.target.value)
        setIntroducehtml(html)
    }
    // 从中台得到文章类别信息
    const getTypeInfo = () => {

        axios({
            method: 'get',
            url: servicePath.getTypeInfo,
            header: { 'Access-Control-Allow-Origin': '*' },
            withCredentials: true
        }).then(
            res => {
                console.log(res.data.data)
                //    if(res.data.data=="没有登录"){
                //      localStorage.removeItem('openId')
                //  props.history.push('/')  
                //    }else{
                setTypeInfo(res.data.data)
                //    }

            }
        )
    }
    const selectTypeHandler = (value) => {
        setSelectType(value)
    }

    const saveArticle = () => {

        // markedContent()//先进行转换

        if (!selectedType) {
            message.error('必须选择文章类别')
            return false
        } else if (!articleTitle) {
            message.error('文章名称不能为空')
            return false
        } else if (!articleContent) {
            message.error('文章内容不能为空')
            return false
        } else if (!introducemd) {
            message.error('简介不能为空')
            return false
        } else if (!showDate) {
            message.error('发布日期不能为空')
            return false
        }
        // message.success('检验通过')
        let dataProps ={}
        dataProps.type_id = selectedType
        dataProps.title = articleTitle
        dataProps.article_content = articleContent
        dataProps.introduce = introducemd
        // let datetext = showDate.replace('-','/')
        // let datetext = showDate
        // dataProps.addTime =(new Date(datetext).getTime())/1000
        dataProps.addTime = showDate
        
        if(articleId==0){
            console.log('articleId=:'+articleId)
            // dataProps.view_count = Math.ceil(Math.random()*100)+1000
            axios({
                method:'post',
                url:servicePath.addArticle,
                header:{ 'Access-Control-Allow-Origin':'*' },
                data:dataProps,
                withCredentials:true
            }).then(
                res=>{
                    setArticleId(res.data.insertId)
                    if(res.data.isSuccess){
                        message.success('文章保存成功')
                    }else{
                        message.error('文章保存失败');
                    }
                }
            )
        }else{
            dataProps.id=articleId
            axios({
                method:'post',
                url:servicePath.updateArticle,
                header:{ 'Access-Control-Allow-Origin':'*' },
                data:dataProps,
                withCredentials:true
            }).then(
                res=>{
                    if(res.data.isScuccess){
                        message.success('文章保存成功')
                    }else{
                        message.error('保存失败');
                    }            
                }
            )
        }

    }

    const getArticleById =(id)=>{
        axios(servicePath.getArticleById+id,{
            withCredentials:true,
            header:{'Access-Control-Allow-Orign':'*'}
        }).then(
            res=>{
                setArticleTitle(res.data.data[0].title)
                setArticleContent(res.data.data[0].article_content)
                let html=marked(res.data.data[0].article_content)
                setMarkdownContent(html)
                setIntroducemd(res.data.data[0].introduce)
                let tmpInt = marked(res.data.data[0].introduce)
                setIntroducehtml(tmpInt)
                setShowDate(res.data.data[0].addTime)
                setSelectType(res.data.data[0].typeId)  
            }  
        )
    }

    return (
        <div>
            <Row gutter={5}>
                <Col span={18}>
                    <Row gutter={10} >
                        <Col span={20}>
                            <Input
                                value={articleTitle}
                                onChange={e => {
                                    setArticleTitle(e.target.value)
                                }}
                                placeholder="博客标题"
                                size="large" />
                        </Col>
                        {/* <Col span={4}>
                            &nbsp;
                            <Select defaultValue="Sign Up" size="large">
                                <Option value="Sign Up">视频教程</Option>
                            </Select>
                        </Col> */}
                        <Col span={4}>
                            &nbsp;
                            <Select defaultValue={selectedType} size="large" onChange={selectTypeHandler}>
                                {
                                    typeInfo.map((item, index) => {
                                        return (<Option key={index} value={item.Id}>{item.typeName}</Option>)
                                    })
                                }
                            </Select>
                        </Col>
                    </Row>
                    <br />
                    <Row gutter={10} >
                        <Col span={12}>
                            <TextArea
                                value={articleContent}
                                className="markdown-content"
                                rows={35}
                                placeholder="文章内容"
                                onChange={changeContent}
                                onPressEnter={changeContent}
                            />
                        </Col>
                        <Col span={12}>
                            <div
                                className="show-html"
                                dangerouslySetInnerHTML={{ __html: markdownContent }}>

                            </div>
                        </Col>
                    </Row>
                </Col>
                <Col span={6}>
                    <Row>
                        <Col span={24}>
                            {/* <Button size="large">暂存文章</Button>&nbsp; */}
                            <Button type="primary" size="large" onClick={saveArticle}>
                                发布文章
                            </Button>
                        </Col>
                        <Col span={24}>
                            <br />
                            <TextArea
                                rows={4}
                                value={introducemd}
                                onChange={changeIntroduce}
                                onPressEnter={changeIntroduce}
                                placeholder="文章简介"
                            />
                            <br /><br />
                            <div
                                className="introduce-html"
                                dangerouslySetInnerHTML={{ __html: introducehtml }} >
                            </div>
                        </Col>
                        <Col span={12}>
                            <div className="date-select">
                                <DatePicker
                                    onChange={(date, dateString) => setShowDate(dateString)}
                                    placeholder="发布日期"
                                    size="large"
                                />
                            </div>
                        </Col>
                    </Row>
                </Col>
            </Row>
        </div>
    )
}
export default AddArticle
Copy the code

2. Service \app\controller\admin\main.js configates SQL language to add and edit articles

async getTypeInfo(){ const resType = await this.app.mysql.select('type') this.ctx.body={data:resType} } async addArticle(){ let tmpArticle = this.ctx.request.body //tmpArticle const result = await This.app.mysql. Insert ('article',tmpArticle) const insertSuccess = result.affectedRows ===1 insertId = result.insertId this.ctx.body={ isSuccess:insertSuccess, insertId:insertId } } async updateArticle(){ let tmpArticle= this.ctx.request.body const result = await this.app.mysql.update('article', tmpArticle); const updateSuccess = result.affectedRows === 1; console.log(updateSuccess) this.ctx.body={ isScuccess:updateSuccess } }Copy the code

3. Service \app\router\admin.js Configure the route

    router.get('/admin/getTypeInfo',controller.admin.main.getTypeInfo)
    router.post('/admin/addArticle',controller.admin.main.addArticle)
    router.post('/admin/updateArticle',controller.admin.main.updateArticle)
Copy the code

4. Admin \ SRC \config\ apiurl. js Configure the route

getTypeInfo:ipUrl + 'getTypeInfo' , AddArticle :ipUrl +'addArticle',// updateArticle:ipUrl +' updateArticle',// modify the addArticle API addressCopy the code

The project is an imitation of the technology fat personal blog, the original video address (jspang.com/detailed?id… I have uploaded it to my Github project (github.com/duskya/-), please download it.