Project front-end warehouse address: github.com/Nick930826/…

Project server warehouse address: github.com/Nick930826/…

First, Egg. Js basic introduction

1. Egg. Js development environment construction and project catalog generation

2. Understand the routing mechanism of egg.js

3. Write simple GET and POST interfaces

4. How to use front-end templates in egg.js

2. React Diary interface

1. Build React development environment and access Ant Design Mobile

2. Adapt the mobile terminal scheme through VW

3, diary list page development

4, diary details page development

5, diary editing page development

Third, egg.js server development

1. Install the Mysql database locally

Navicat operates on the database to create a diary table

3. Write and add diary interface and update diary interface

4, write get diary list interface, get diary details interface, delete diary interface

5. Joint debugging interface

Four,

Egg. Js basic introduction

Introduction to the

What is egg.js? An egg? Just a little joke. Egg.js is the upper architecture based on Koa. Simply put, egg.js is a back-end node solution based on secondary development of Koa. As of now (2020-01-06), the latest version of the Egg is v2.26.0, and the stars on Github are high, currently reaching 14.6K +. This shows how much people like Egg.

So why do I choose Egg as a server development framework over Nest, think.js, hapi, etc? First of all, Egg is developed by Alibaba team, and it is a leading large factory in China. You don’t have to worry about the ecology of the framework, let alone the fact that it will not be maintained, because many systems inside Ali are also made using the framework. Secondly, Egg has done a good job in the documents. The Chinese and English documents are very friendly to Chinese people. To be honest, my English ability is limited. When you have a problem, you can go to the community or technical group to shout a few words, and friends who have similar problems will spare no effort to support you. (Common small developers do not like light spray)

Another important reason is that Egg inherits from Koa, and some enhancements have been made on its basic model, which is very convenient in writing. Koa, by contrast, is basic, and too many things need to be repackaged. You’ll see how powerful Egg can be in later development.

Egg. Js development environment building and generating project directory explanation

My environment:

  • Operating system: macOS
  • Node version: 12.6.0
  • NPM version: 6.9.0

Initialize the project with the following script:

mkdir egg-demo && cdEgg -demo NPM init egg // Select NPM install in simple modeCopy the code

If NPM is unavailable, install YARN

The initialization project directory is as follows:

Analysis of project file structure

Here I pick the important, because some of the development we do not often to modify, do not waste too much energy to understand, of course, interested partners themselves can be thoroughly studied some.

  • ** App folder: ** Our main logic will almost be completed in this folder, controller is the controller folder, mainly write some business code, then we will create a service folder in the App folder, specially used to operate the database, let the business logic and database operation divide and conquer, the code will be much clearer.
  • **public folder: ** Public folder, put some public resources in this folder.
  • Config folder: This is where the project configurations are stored, such as cross-domain configurations, database configurations, and so on.
  • Logs folder: The log folder. You do not need to modify or view the contents of the log folder.
  • ** Run folder: ** The configuration file generated when the project is run, and the files in it are basically not modified.
  • Test folder: configuration files used for testing interfaces.
  • .auto-conf. js: a file automatically generated by the project. Generally, it does not need to be modified.
  • .eslintignore and.eslintrc: Code formatting configuration files.
  • .gitignore: The file that git ignores when committing.
  • Package. json: Package management and command configuration files that need to be modified frequently during development.

Egg.js directory convention specification

Koa is not suitable for team project development because it lacks specification. Egg.js has some specifications based on Koa, so when we place script files, we will follow the egg.js specification.

App /router.js is where the route is placed

The public folder contains public resources such as images, scripts, and so on

The App/Service folder holds the contents of database operations

The View folder is the natural place to put the front end template

Middleware is a place for middleware, which is very important. Authentication and other operations can be added to routes through middleware, known as route guards

There are a lot of specifications are not here one by one, you can move to the official documents, Chinese documents are very friendly, want to further study students can burn the midnight oil to read some.

All this talk seems to have forgotten one thing. Let’s start the project and have a look. Let’s make a few changes before we start:

// /app/controller/home.js
'use strict';

const Controller = require('egg').Controller;

class HomeController extends Controller {
  async index(a) {
    const { ctx } = this;
    ctx.body = 'hi, egg';
  }
  async test(a) {
    const { ctx } = this;
    ctx.body = 'Test interface'; }}module.exports = HomeController;
Copy the code
// app/router.js
'use strict';

/ * * *@param {Egg.Application} app - egg application
 */
module.exports = app= > {
  const { router, controller } = app;
  router.get('/', controller.home.index);
  router.get('/test', controller.home.test);
};

Copy the code

Start the project from the project root directory with the following command line:

NPM run dev // or yarn devCopy the code

In normal cases, egg.js starts port 7001 by default. If you see the following figure, the project starts successfully.

We look at it in a browser as follows:

The test method we wrote in the /app/controller/home.js file was successfully executed.

Understand the routing mechanism of egg.js

A Router is used to describe the mapping between the request URL and the specific Controller that is responsible for execution. Egg.js specifies the app/router.js file to unify all routing rules.

In this example, we write the test method in the app/controller/home.js file and throw the test method as GET in the app/router.js file. So that’s the URL and the Controller. The convenience of egg.js is that the context is already there for us, and the app is the context of the global application. Routing and controllers are stored in the global application context app, so you only need to worry about your business logic and database operations, and don’t have to worry about other trivial distractions.

For example, if I want to create a new user-related Controller, we can write this:

// Create user.js under app/controller/
'use strict';

const Controller = require('egg').Controller;

class UserController extends Controller {
  async index() {
    const { ctx } = this;
    ctx.body = 'users'; }}module.exports = UserController;
Copy the code

UserController inherits from Controller and can internally write functions with async and await.

Write simple GET and POST interfaces

Let’s add a little bit more knowledge here. Let’s GET the query parameter on the route, which is /user? Username = Nick

/ / in the app/controller/user. Js
'use strict';

const Controller = require('egg').Controller;

class UserController extends Controller {
  async index() {
    const { ctx } = this;
    const{ username } = ctx.query; ctx.body = username; }}module.exports = UserController;
Copy the code

Notice You need to add route parameters

'use strict';

/ * * *@param {Egg.Application} app - egg application
 */
module.exports = app= > {
  const { router, controller } = app;
  router.get('/', controller.home.index);
  router.get('/test', controller.home.test);
  router.get('/user', controller.user.index);
};

Copy the code

Go to the browser again and see if you can display the query parameters:

There is another way to get claim parameters, which can be obtained by CTX /params:

/ / in the app/controller/user. Js
'use strict';

const Controller = require('egg').Controller;

class UserController extends Controller {
  async index() {
    const { ctx } = this;
    const { username } = ctx.query;
    ctx.body = username;
  }
  
  async getid() {
    const { ctx } = this;
    const{ id } = ctx.params; ctx.body = id; }}module.exports = UserController;
Copy the code
'use strict';

/ * * *@param {Egg.Application} app - egg application
 */
module.exports = app= > {
  const { router, controller } = app;
  router.get('/', controller.home.index);
  router.get('/test', controller.home.test);
  router.get('/user', controller.user.index);
  router.get('/getid/:id', controller.user.getid);
};
Copy the code

As shown in the figure, the 999 after getid/999 is returned to the web page as the ID in ctx.params.

After GET, we’ll talk about POST. When we’re developing a project, we’ll use a POST interface when we need to operate on something, because we’re going to be sending a lot of data, so I’m not going to go into the difference between GET and POST, otherwise it’s going to be an interview course. If ANYTHING, they’re all based on TCP.

To look at the application of the POST interface in the Egg, add a method to the app/controller/user.js mentioned above:

.async add() {
  const { ctx } = this;
  const { title, content } = ctx.request.body;
  // The bodyParser middleware is built into the framework to parse these two types of request bodies into objects and mount them on ctx.request.body
  // The HTTP protocol does not recommend passing the body in the GET and HEAD methods, so we cannot use this method to GET the content in the GET and HEAD methods.ctx.body = { title, content, }; }...Copy the code
'use strict';

/ * * *@param {Egg.Application} app - egg application
 */
module.exports = app= > {
  const { router, controller } = app;
  router.get('/', controller.home.index);
  router.get('/test', controller.home.test);
  router.get('/user', controller.user.index);
  router.get('/getid/:id', controller.user.getid);
  router.post('/add', controller.user.add);
};

Copy the code

The browser is not convenient to request the POST interface, we use Postman to send POST requests, students who have not downloaded can download one, Postman can be said to be a necessary tool for development, testing the interface is very convenient. When you click on Postman to send a request, you will not receive a return because the request is cross-domain, so we need to use the NPM package egg-cors to solve the cross-domain problem. First install it, and then introduce the following in config/plugin.js:

// config/plugin.js
'use strict';

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

Then add the following code to config/config.default.js:

// config/config.default.js
config.security = {
  csrf: {
    enable: false.ignoreJSON: true,},domainWhiteList: [ The '*'].// Configure the whitelist
};

config.cors = {
  // Origin: '*', // allow all cross-domain access, comment out to allow whitelist access above
  credentials: true.// Allow cookies to cross domains
  allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH,OPTIONS'};Copy the code

I am currently configured to be fully accessible. Then restart the project and open the Postman request Add interface as shown below. Note that the request body needs JSON(Application/ JSON) form:

We have to talk about Service. Our above interface business logic is in the Controller, if I need to operate the database, we need to operate the database method in the Service.

First, create a new folder app/service, and create user.js in the folder as follows:

'use strict';

const Service = require('egg').Service;

class UserService extends Service {
  async user() {
    return {
      title: 'What's your mother's name?'.content: Your name is Li.}; }}module.exports = UserService;
Copy the code

Then go to app/controller/user.js and call:

.async index() {
  const { ctx } = this;
  const { title, content } = awaitctx.service.user.user(); ctx.body = { title, content, }; }...Copy the code
// app/router.js. router.post('/getUser', controller.user.index);
Copy the code

Every time you add a method in the controller, do not forget to add a route in the router, JS.

We haven’t connected to the database yet, so let’s make do with this. When we connect to the database, we will create some database related scripts in the Service folder, which will be explained later.

How do I use front end templates in egg.js

If students need to make simple static pages, such as the company’s official website, publicity pages, etc., you can consider using front-end templates to write pages.

First we install the template plug-in egg-view-ejs:

npm install egg-view-ejs -save
Copy the code

Then declare the required plug-ins in config/plugin.js

exports.ejs = {
  enable: true.package: 'egg-view-ejs'};Copy the code

Next we need to go to config/config.default.js to configure ejs. In this step we will change the suffix of.ejs to the suffix of.html.

config.view = {
   mapping: {'.html': 'ejs'} // The left side is written with a.html suffix, which will automatically render.html files
};
Copy the code

Create a view folder in the app directory and create a new index. HTML file like this:

<! DOCTYPEhtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title><%-title%></title>
</head>
<body>
    <! -- Using template data -->
    <h1><%-title%></h1> 
</body>
</html>
Copy the code

Modify the app/controller/home.js script as follows:

// app/controller/home.js
'use strict';

const Controller = require('egg').Controller;

class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    // the default is to go back to the view folder. The Egg is already wrapped in this layer
    await ctx.render('index.html', {
      title: 'What's your mother's name?'}); }async test() {
    const { ctx } = this;
    ctx.body = 'Test interface'; }}module.exports = HomeController;
Copy the code

Restart the entire project and view http://localhost:7001 in the browser as shown below:

The title variable has been loaded and the template is displayed.

To the students smoothly with this step down, basically on the Egg have a general understanding of light, of course, understand the basic knowledge is not enough to complete the whole project to write, but the foundation is very important, after all, an Egg is based on the Koa secondary packaging, many built-in Settings that need to be familiar with through a small case, hope that the students don’t be lazy, Follow the above content, it is best not to copy and paste, line by line to type to truly become their own knowledge.

React writing diary interface

Introduction to the

Since React 16.8, React has introduced Hooks, which support state management within function components. The idea is that when we write React code, we can pretty much ditch the Class. I say “almost” because there are still places where you need to use Class, but Dan, author of React, said, “Hooks will be the future of React.” So we’re going to use Hooks throughout this time and type in the diary items.

React development environment builds access to Ant Design Mobile

In this course, we use the create-React-app to initialize the React environment. If your NPM version is greater than 5.2, you can use the following command line to initialize the project:

npx create-react-app diary
cd diary
npm run start
Copy the code

If the startup is successful, port 3000 is enabled by default. Open the browser and enter http://localhost:3000 to see the following page:

Clean up some files in the SRC directory of the Diary project. The final directory structure looks like the following:

To introduce Ant Design Mobile, first we need to download it to the project, open the command line tool and enter the following command in the project root directory:

npm install antd-mobile --save
Copy the code

Then introduce the and style file in diary/ SRC /index.js:

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import 'antd-mobile/dist/antd-mobile.css';

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

Diary/SRC/app.js

// App.js
import React from 'react';
import { Button } from 'antd-mobile';

function App() {
  return (
    <div className="App">
      <Button type='primary'>test</Button>
    </div>
  );
}

export default App;
Copy the code

Then restart the project, open the browser and start mobile mode to see the effect:

Mobile web pages have a 300ms delay when clicking, so we need to add a script to the diary/public/index.html file:

// index.html. <script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
<script>
  if ('addEventListener' in document) {
    document.addEventListener('DOMContentLoaded'.function() {
      FastClick.attach(document.body);
    }, false);
  }
  if(!window.Promise) {
    document.writeln('< script SRC = "https://as.alipayobjects.com/g/component/es6-promise/3.2.2/es6-promise.min.js" "+'>'+'<'+'/'+'script>');
  }
</script>.Copy the code

The antD style can be loaded on demand. If you want to learn how to load on demand, you can go to the official website to learn how to introduce it

Adapt the mobile terminal scheme through VW

As we all know, the resolution of mobile terminal is ever-changing, and it is difficult for us to perfectly adapt to each resolution to display the page perfectly. It can’t be perfect, but at least we should try our best to achieve a rough one, and adapt the resolution of mobile terminals through VW. It can convert px units in a page to VW, VH, to adapt to the varied resolution of mobile phones. If you don’t want to do the adaptation, you can skip this step and continue with the following study.

First we need to release the hidden WebPack configuration of the project, using the following command line:

npm run eject
Copy the code

After running, the project directory structure looks like the figure below:

Two more configuration items are added, as shown in the figure. If running NPM run eject does not work, you are advised to delete the.git file of the project, rm -rf. git, and then run NPM run eject again.

Then install a few more plugins with the following command:

npm install postcss-aspect-ratio-mini postcss-px-to-viewport postcss-write-svg postcss-cssnext postcss-viewport-units cssnano cssnano-preset-advanced
Copy the code

After the installation is complete, open the diary/config/webpack config. Js script, to modify postcss loader plug-in.

First introduce the package installed above, which can be placed below line 28:

/ / 28 lines
const postcssNormalize = require('postcss-normalize');

const postcssAspectRatioMini = require('postcss-aspect-ratio-mini');
const postcssPxToViewport = require('postcss-px-to-viewport');
const postcssWriteSvg = require('postcss-write-svg');
const postcssCssnext = require('postcss-cssnext');
const postcssViewportUnits = require('postcss-viewport-units');
const cssnano = require('cssnano');

const appPackageJson = require(paths.appPackageJson);
////
Copy the code

Then go to line 100 and start adding some postCSS configurations:

{
  // Options for PostCSS as we reference these options twice
  // Adds vendor prefixing based on your specified browser support in
  // package.json
  loader: require.resolve('postcss-loader'),
    options: {
      // Necessary for external CSS imports to work
      // https://github.com/facebook/create-react-app/issues/2677
      ident: 'postcss'.plugins: () = > [
          require('postcss-flexbugs-fixes'),
          require('postcss-preset-env') ({autoprefixer: {
              flexbox: 'no-2009',},stage: 3,}).// Adds PostCSS Normalize as the reset css with default options,
          // so that it honors browserslist config in package.json
          // which in turn let's users customize the target behavior as per their needs.
          postcssNormalize(),
          postcssAspectRatioMini({}),
          postcssPxToViewport({ 
            viewportWidth: 750.// the design for the iphone6
            viewportHeight: 1334.// the design for the iphone6
            unitPrecision: 3.viewportUnit: 'vw'.selectorBlackList: ['.ignore'.'.hairlines'.'am'].// This is because the antD-Mobile component library is introduced. Otherwise, the units in the component library will be changed to VW units
            minPixelValue: 1.mediaQuery: false
          }),
          postcssWriteSvg({
            utf8: false
          }),
          postcssCssnext({}),
          postcssViewportUnits({}),
          cssnano({
            preset: "advanced".autoprefixer: false."postcss-zindex": false})].sourceMap: isEnvProduction && shouldUseSourceMap,
    },
  },
Copy the code

After the addition, restart the project and check whether the unit has changed through the browser:

Similarly, other component libraries can also be adapted to mobile projects in this form, but note that the selectorBlackList attribute needs to add the corresponding component library name instead of converting to VW

Diary list page development

After that, we’ll develop some pages, but before we can do that, we need to add a routing mechanism. The React-router-dom plugin controls the routing of the project. Install it first:

npm i react-router-dom -save
Copy the code

SRC = “Home” SRC = “Home” SRC = “Home” SRC = “Home” SRC = “Home” SRC = “Home” SRC = “Home” SRC = “Home” SRC = “Home” SRC = “Home” SRC = “Home”

// Home/index.jsx
import React from 'react'
import './style.css'

const Home = () = > {
  return (
    <div>
      Home
    </div>)}export default Home
Copy the code

Next, we edit the routing configuration page. The principle of routing is that the page dynamically loads the component page corresponding to the browser address by changing the browser address. For example, I now configure a Home component for the/Home page, so that when the browser visits http://localhost:3000, the page will render the corresponding Home component. So let’s change app.js to router.js as follows:

// Router.js
import React from 'react';
import Home from './Home';

import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link
} from "react-router-dom";

const RouterMap = () = > {
  return <Router>
    <Switch>
      <Route exact path="/">
        <Home />
      </Route>
    </Switch>
  </Router>
}

export default RouterMap;
Copy the code

For a bit of explanation, the Switch behaves much like a JavaScript Switch, that is, when the corresponding route is matched, it no longer matches down. We will introduce the RouterMap in the SRC /index.js script as follows:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import RouterMap from './Router';
import 'antd-mobile/dist/antd-mobile.css';

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

Then restart the project and check the browser performance:

Let’s write the Home page of the diary project in the Home component. The Home page will be presented as a list, so we can use the Card component in ANTD. Let’s see how the code does that:

// Home/index.jsx
import React from 'react'
import { Card } from 'antd-mobile'
import './style.css'
const list = [0.1.2.3.4.5.6.7.8.9]

const Home = () = > {
  return (
    <div className='diary-list'>
      {
        list.map(item => <Card className='diary-item'>
          <Card.Header
            title="I'm going hide-and-seek with Xiao Ming."
            thumb="https://gw.alipayobjects.com/zos/rmsportal/MRhHctKOineMbKAZslML.jpg"
            extra={<span>A sunny day</span>} / ><Card.Body>
            <div>{item}</div>
          </Card.Body>
          <Card.Footer content="2020-01-09" />
        </Card>)}</div>)}export default Home
Copy the code
// Home/style.css
.diary-list .diary-item {
  margin-bottom: 20px;
}

.diary-item .am-card-header-content {
  flex: 7 1;
}
Copy the code

You can query elements in the browser to change the internal style of the component, such as changing the width of the title with.am-card-header-content. The rational use of component libraries is conducive to the improvement of work efficiency. Although this page is simple, but it is also a role of the atND component library for detailed research, in the work of business needs analysis, can do a thorough study, promotion and salary is just around the corner.

Diary details page developed

In a new Detail folder under SRC, let’s write the details page:

// Detail/index.jsx
import React from 'react'
import { NavBar, Icon, List } from 'antd-mobile'

const Detail = () = > {
  return (<div className='diary-detail'>
    <NavBar
      mode="light"
      icon={<Icon type="left" />} onLeftClick={() => console.log('onLeftClick')} > Xiao Ming and I play hide and seek</NavBar>
    <List renderHeader={()= >'2020-01-09 '} className="my-list"><List.Item wrap>Today, XIAO Ming and I went to the West Lake hide-and-seek, Xiao Ming diving, hiding in the bottom of the lake, I looked for a long time in the west Lake did not find, then I went home, not with him.</List.Item>
    </List>
  </div>)}export default Detail
Copy the code

The NavBar TAB is used in the header to display the title and the back button. Content Select the List List component, which simply displays the content portion of the journal. Don’t forget to add the Detail route to the router-js routing script:

const RouterMap = () = > {
  return <Router>
    <Switch>
      <Route exact path="/">
        <Home />
      </Route>
      <Route exact path="/detail">
        <Detail />
      </Route>
    </Switch>
  </Router>
}
Copy the code

Browser enter http://localhost:3000/detail view effect:

We associate the list on the home page with the details page, so that click the list item on the home page, jump to the corresponding details page, bring the ID parameter to the route, and then get the ID parameter of the browser query string through filtering in the details page. Let’s first modify the code on the home page:

import React from 'react'
import { Card } from 'antd-mobile'
import { Link } from 'react-router-dom'
import './style.css'
const list = [0.1.2.3.4.5.6.7.8.9]

const Home = () = > {
  return (
    <div className='diary-list'>
      {
        list.map(item => <Link to={{ pathname: 'detail', search: `?id=${item}` }}><Card className='diary-item'>
          <Card.Header
            title="I'm going hide-and-seek with Xiao Ming."
            thumb="https://gw.alipayobjects.com/zos/rmsportal/MRhHctKOineMbKAZslML.jpg"
            extra={<span>A sunny day</span>} / ><Card.Body>
            <div>{item}</div>
          </Card.Body>
          <Card.Footer content="2020-01-09" />
        </Card></Link>)}</div>)}export default Home
Copy the code

Introduce the Link tag, wrap the Card component, and set the jump path and the parameters attached to the path through the TO attribute as shown in the code above. Next we accept this parameter in the Detail component. We write a tool method to get the desired parameter. We create a folder under SRC utils and create a script inside the folder index.js as follows:

function getQueryString(name) {
  var reg = new RegExp("(^ | &)"+ name +"= (/ ^ & *) (& | $)");
  var r = window.location.search.substr(1).match(reg);
  if(r ! =null) {
      return  unescape(r[2]); 
  } else{
      return null
  };
}

module.exports = {
  getQueryString
}
Copy the code

This method is the way to get the browser query string, so we’re going to open up the Detail component, introduce utils to get the getQueryString method, and we’re going to hit the back button in the details page, Hooks writing react – the router – dom useHistory method was provided for us to realize the fallback, specific code under the figure shown below:

// Detail/index.jsx
import React from 'react'
import { NavBar, Icon, List } from 'antd-mobile'
import { useHistory } from 'react-router-dom'
import { getQueryString } from '.. /utils'

const Detail = () = > {
 const history = useHistory()
 const id = getQueryString('id')
 return (<div className='diary-detail'>
   <NavBar
     mode="light"
     icon={<Icon type="left" />} onLeftClick={() => history.goback ()} ></NavBar>
   <List renderHeader={()= >'2020-01-09 '} className="my-list"><List.Item wrap>Today, XIAO Ming and I went to the West Lake hide-and-seek, Xiao Ming diving, hiding in the bottom of the lake, I looked for a long time in the west Lake did not find, then I went home, not with him.</List.Item>
   </List>
 </div>)}export default Detail
Copy the code

When we get the ID attribute and display it in the title, let’s see how it looks in the browser:

Diary editing page development

After playing hide-and-seek with Xiao Ming for ten days, I felt very bored. Let’s get the edit page out of the way, add some interesting diary information. As usual, we’ll start writing our diary input component by creating a new Edit folder under SRC:

// Detail/index.jsx
import React, { useState } from 'react'
import { List, InputItem, TextareaItem, DatePicker, ImagePicker } from 'antd-mobile'
import './style.css'

const Edit = () = > {
  const [date, setDate] = useState()
  const [files, setFile] = useState([])
  const onChange = (files, type, index) = > {
    console.log(files, type, index);
    setFile(files)
  }

  return (<div className='diary-edit'>
    <List renderHeader={()= >'Edit diary '}><InputItem
        clear
        placeholder="Please enter a title"
      >The title</InputItem>
      <TextareaItem
        rows={6}
        placeholder="Please enter your diary."
      />
      <DatePicker
        mode="date"
        title="Please select a date"
        extra="Please select a date"
        value={date}
        onChange={date= > setDate(date)}
      >
        <List.Item arrow="horizontal">The date of</List.Item>
      </DatePicker>
      <ImagePicker
        files={files}
        onChange={onChange}
        onImageClick={(index, fs) = > console.log(index, fs)}
        selectable={files.length < 1}
        multiple={false}
      />
    </List>
  </div>)}export default Edit
Copy the code
// Detail/style.css
.diary-edit {
  height: 100vh;
  background: #fff;
}
Copy the code

The above code adds four pieces of content, namely the title, the content, the date, and the picture. The collocation between components is entirely arranged by yourself. Students can set the layout according to their own preferences. Pay attention to add routing addresses on the routing page after writing:

// Router.js
import React from 'react';
import Home from './Home';
import Detail from './Detail';
import Edit from './Edit';

import {
  BrowserRouter as Router,
  Switch,
  Route,
  Link
} from "react-router-dom";

const RouterMap = () = > {
  return <Router>
    <Switch>
      <Route exact path="/">
        <Home />
      </Route>
      <Route exact path="/detail">
        <Detail />
      </Route>
      <Route exact path="/edit">
        <Edit />
      </Route>
    </Switch>
  </Router>
}

export default RouterMap;

Copy the code

Then go to the browser and preview what the interface looks like:

Next can record and xiao Hong’s happy story ~ ~

Egg.js server development

Remember when we first created the Egg-demo project? We used that project for server-side development. The first thing we need to do is to install the MySQL database locally.

Install the Mysql database locally

1. Download and install MySQL

Go to the MySQL official website and download the MySQL Database Community edition

Please choose the version suitable for your own, I am a MacOS system, so choose the first installation package, pay attention to choose not to log in to download

After the download is complete, follow the navigation instructions to install. When the root user configures the password, be sure to remember the password. The following will be used:

After the installation is complete, you can enter the system Settings to start the database:

Navicat operates on the database to create a diary table

The graphical interface is very user-friendly for beginners. Navicat for MySQL is a lightweight database visualization tool. It is not available for download here, for fear of being sued for infringement. You can go to the Internet to search their own download resources, or a lot of, this ability we still want to cultivate.

With the database started, we open the Navicat tool and link to the local database, as shown in the figure below:

After saving, there will be test database items in the list on the left, which will turn green after successfully linking to the database:

We can see the version number and port number of our local database, so we are linked to the local database.

When creating a new table, note that we fill in the name of the table first, save the name of the table after filling in the name of the table. Select utF8MB4 as character set, otherwise Chinese input is not supported:

Make sure you set the id to increment and use it as the primary key:

Then click the Save button in the upper left corner to save the table. Let’s add a record to the diary table:

Here, our database work is almost over, there are not understand the students can also private message me, I will personally for you to solve the problem.

Next we can open the egg-demo project. To link to the database, we need to install the egg-mysql package and run the following command line from the root of the project:

npm i --save egg-mysql
Copy the code

To enable plug-ins:

// config/plugin.js
exports.mysql = {
  enable: true.package: 'egg-mysql'};Copy the code
// config/config.default.js
exports.mysql = {
    // Single database configuration
    client: {
      // host
      host: 'localhost'./ / the port number
      port: '3306'./ / user name
      user: 'root'./ / password
      password: '* * * * * *'.// Database name
      database: 'diary',},// Whether to load to the app, enabled by default
    app: true.// Whether to load to the agent, default off
    agent: false};Copy the code

The password needs to be the one you were told to remember

Let’s go to ‘server’ folder and create a new file diary. Js to add a search list method:

// server/diary.js
'use strict';

const Service = require('egg').Service;

class DiaryService extends Service {
  async list() {
    const { app } = this;
    try {
      const result = await app.mysql.select('diary');
      return result;
    } catch (error) {
      console.log(error);
      return null; }}}module.exports = DiaryService;

Copy the code

Then add a new method to get the list of diaries by reference in controller/home.js:

'use strict';

const Controller = require('egg').Controller;

class HomeController extends Controller {
  async list() {
    const { ctx } = this;
    const result = await ctx.service.diary.list();
    if (result) {
      ctx.body = {
        status: 200.data: result,
      };
    } else {
      ctx.body = {
        status: 500.errMsg: 'Failed to obtain'}; }}}module.exports = HomeController;
Copy the code

Note that each time you add a new method, you need to add the corresponding interface to the routing file:

// router.js
'use strict';

/ * * *@param {Egg.Application} app - egg application
 */
module.exports = app= > {
  const { router, controller } = app;
  router.get('/list', controller.home.list);
};
Copy the code

Restart the project and run the following command:

npm run dev
Copy the code

After the smooth startup, go to the browser to get the interface to see if the data can be requested. The successful obtain is as follows:

At this time, how many will have a sense of achievement, so we will pinch and, the other several interfaces are written.

Adding a Diary interface

To add the interface, we need to use the POST request mode. We have already described how POST gets the parameters passed in the request body, so we won’t go into that here. To write the interface directly, first open the service/diary. Js script and add the add method:

async add(params) {
  const { app } = this;
  try {
    const result = await app.mysql.insert('diary', params);
    return result;
  } catch (error) {
    console.log(error);
    return null; }}Copy the code

Then add the interface to the controller/home.js script:

async add() {
  const { ctx } = this;
  constparams = { ... ctx.request.body, };const result = await ctx.service.diary.add(params);
  if (result) {
    ctx.body = {
      status: 200.data: result,
    };
  } else {
    ctx.body = {
      status: 500.errMsg: 'Failed to add'}; }}Copy the code

Then go to the router-js route script and add a route configuration:

'use strict';

/ * * *@param {Egg.Application} app - egg application
 */
module.exports = app= > {
  const { router, controller } = app;
  router.get('/list', controller.home.list);
  router.post('/add', controller.home.add);
};
Copy the code

The POST interface must pass the Postman test:

After adding the record successfully, return the corresponding id and other information of the record, let’s see if the list has the data added on the previous day:

At this point it must be successful, and the addition of the interface is complete.

Modify Diary interface

First of all, to modify a diary, we need to find its ID first, because ID is the primary key, we use the ID to update the field of the entry. Let’s go to service/diary. Js and add a database operation method:

async update(params) {
  const { app } = this;
  try {
    const result = await app.mysql.update('diary', params);
    return result;
  } catch (error) {
    console.log(error);
    return null; }}Copy the code

Then open the contoller/home.js and add the modification method:

async update() {
  const { ctx } = this;
  constparams = { ... ctx.request.body, };const result = await ctx.service.diary.update(params);
  if (result) {
    ctx.body = {
      status: 200.data: result,
    };
  } else {
    ctx.body = {
      status: 500.errMsg: 'Editing failed'}; }}Copy the code

Finally, go to router.js to add the interface configuration:

'use strict';

/ * * *@param {Egg.Application} app - egg application
 */
module.exports = app= > {
  const { router, controller } = app;
  router.get('/list', controller.home.list);
  router.post('/add', controller.home.add);
  router.post('/update', controller.home.update);
};
Copy the code

Go to Postman and change the second record:

The second record was successfully modified.

Get article details interface

Select * from service where id = 0; select * from service where id = 0;

async diaryById(id) {
  const { app } = this;
  if(! id) {console.log('ID cannot be empty');
    return null;
  }
  try {
    const result = await app.mysql.select('diary', {
      where: { id },
    });
    return result;
  } catch (error) {
    console.log(error);
    return null; }}Copy the code

controller/home.js

async getDiaryById() {
  const { ctx } = this;
  console.log('ctx.params', ctx.params);
  const result = await ctx.service.diary.diaryById(ctx.params.id);
  if (result) {
    ctx.body = {
      status: 200.data: result,
    };
  } else {
    ctx.body = {
      status: 500.errMsg: 'Failed to obtain'}; }}Copy the code

router.js

'use strict';

/ * * *@param {Egg.Application} app - egg application
*/
module.exports = app= > {
 const { router, controller } = app;
 router.get('/list', controller.home.list);
 router.post('/add', controller.home.add);
 router.post('/update', controller.home.update);
 router.get('/detail/:id', controller.home.getDiaryById);
};
Copy the code

Delete the interface

Delete the interface. Find the corresponding ID record and delete it:

service/diary.js

async delete(id) {
  const { app } = this;
  try {
    const result = await app.mysql.delete('diary', { id });
    return result;
  } catch (error) {
    console.log(error);
    return null; }}Copy the code

controller/home.js

async delete() {
  const { ctx } = this;
  const { id } = ctx.request.body;
  const result = await ctx.service.diary.delete(id);
  if (result) {
    ctx.body = {
      status: 200.data: result,
    };
  } else {
    ctx.body = {
      status: 500.errMsg: 'Deletion failed'}; }}Copy the code

router.js

'use strict';

/ * * *@param {Egg.Application} app - egg application
 */
module.exports = app= > {
  const { router, controller } = app;
  router.get('/list', controller.home.list);
  router.post('/add', controller.home.add);
  router.post('/update', controller.home.update);
  router.get('/detail/:id', controller.home.getDiaryById);
  router.post('/delete', controller.home.delete);
};
Copy the code

After deletion, only the record with ID 2 is left, so the interface part is basically completed. Let’s go to the front end to connect to the corresponding interface.

Alignment interface

The front end of the line, debugging interface. Let’s switch to the Diary front-end project and first install Axios:

npm i axios --save
Copy the code

Then add the script axios.js to the utils folder to encapsulate it twice. The reason for secondary encapsulation is that we can process the return of the unified processing interface in one place, instead of having to modify the return of each request.

// utils/axios.js
import axios from 'axios'
import { Toast } from 'antd-mobile'

// Based on the process.env.node_env environment variable to determine whether the development environment or production environment, our server local start port is 7001
axios.defaults.baseURL = process.env.NODE_ENV == 'development' ? '//localhost:7001' : ' ' 
// Indicates whether credentials are required for cross-domain requests
axios.defaults.withCredentials = false
axios.defaults.headers['X-Requested-With'] = 'XMLHttpRequest'
// The post request is in JSON form
axios.defaults.headers.post['Content-Type'] = 'application/json'

axios.interceptors.response.use(res= > {
  if (typeofres.data ! = ='object') {
    console.error('Data format response error :', res.data)
    Toast.fail('Server exception! ')
    return Promise.reject(res)
  }
  if(res.data.status ! =200) {
    if (res.data.message) Toast.error(res.data.message)
    return Promise.reject(res.data)
  }
  return res.data
})

export default axios
Copy the code

Remember to throw the Axios out when you’re done with the secondary encapsulation.

The next step is to go to the Home page request list interface, open SRC /home/index.jsx:

// src/Home/index.jsx
import React, { useState, useEffect } from 'react'
import { Card } from 'antd-mobile'
import { Link } from 'react-router-dom'
import axios from '.. /utils/axios'
import './style.css'

const Home = () = > {
  // Define the list variable with the useState Hook function
  const [list, setList] = useState([])
  useEffect(() = > {
    // Request the list interface and return the list data
    axios.get('/list').then(({ data }) = > {
      setList(data)
    })
  }, [])
  return (
    <div className='diary-list'>
      {
        list.map(item => <Link to={{ pathname: 'detail', search: `?id=${item.id}` }}><Card className='diary-item'>
          <Card.Header
            title={item.title}
            thumb={item.url}
            extra={<span>A sunny day</span>} / ><Card.Body>
            <div>{item.content}</div>
          </Card.Body>
          <Card.Footer content={item.date} />
        </Card></Link>)}</div>)}export default Home
Copy the code
.diary-list .diary-item {
  margin-bottom: 20px;
}

.diary-item .am-card-header-content {
  flex: 7 1;
}

.diary-item .am-card-header-content img {
  width: 30px;
}
Copy the code

Open your browser and enter http://localhost:3000 to display something like the picture below:

Compilation of details page

Next let’s go to the details page and open SRC /Detail/index.jsx:

import React, { useState, useEffect } from 'react'
import { NavBar, Icon, List } from 'antd-mobile'
import { useHistory } from 'react-router-dom'
import { getQueryString } from '.. /utils'
import axios from '.. /utils/axios'

const Detail = () = > {
  const [detail, setDetail] = useState({})
  const history = useHistory()
  const id = getQueryString('id')

  useEffect(() = > {
    axios.get(`/detail/${id}`).then(({ data }) = > {
      if (data.length) {
        setDetail(data[0])}})}, [])return (<div className='diary-detail'>
    <NavBar
      mode="light"
      icon={<Icon type="left" />}
      onLeftClick={() => history.goBack()}
    >{detail.title || ''}</NavBar>
    <List renderHeader={()= >'${detail. Date'} className="my-list"><List.Item wrap>
        {detail.content}
      </List.Item>
    </List>
  </div>)}export default Detail
Copy the code

Edit page

To add an article page, we open SRC /Edit/index.jsx:

import React, { useState } from 'react'
import { List, InputItem, TextareaItem, DatePicker, ImagePicker, Button, Toast } from 'antd-mobile'
import moment from 'moment'
import axios from '.. /utils/axios'
import './style.css'

const Edit = () = > {
  const [title, setTitle] = useState(' ') / / title
  const [content, setContent] = useState(' ') / / content
  const [date, setDate] = useState(' ') / / date
  const [files, setFile] = useState([]) // Image file
  const onChange = (files, type, index) = > {
    console.log(files, type, index);
    setFile(files)
  }

  const publish = () = > {
    if(! title || ! content || ! date) { Toast.fail('Please fill in the necessary parameters')
      return
    }
    const params = {
      title,
      content,
      date: moment(date).format('YYYY-MM-DD'),
      url: files.length ? files[0].url : ' '
    }
    axios.post('/add', params).then(res= > {
      Toast.success('Add successful')})}return (<div className='diary-edit'>
    <List renderHeader={()= >'Edit diary '}><InputItem
        clear
        placeholder="Please enter a title"
        onChange={(value)= >SetTitle (value)} > title</InputItem>
      <TextareaItem
        rows={6}
        placeholder="Please enter your diary."
        onChange={(value)= > setContent(value)}
      />
      <DatePicker
        mode="date"
        title="Please select a date"
        extra="Please select a date"
        value={date}
        onChange={date= > setDate(date)}
      >
        <List.Item arrow="horizontal">The date of</List.Item>
      </DatePicker>
      <ImagePicker
        files={files}
        onChange={onChange}
        onImageClick={(index, fs) = > console.log(index, fs)}
        selectable={files.length < 1}
        multiple={false}
      />
      <Button type='primary' onClick={()= >The publish ()} ></Button>
    </List>
  </div>)}export default Edit
Copy the code

Note that since I didn’t buy the CDN service, I don’t have the resource upload interface, so we use Base64 for the images here.

After the addition is successful, browse the list page.

Delete the article

We need the details page to add a button, because we don’t have the background management system, the delete button is arguably need on the backstage management page, but in order to convenient I wrote them all in one project, because a diary is a show themselves, said that is why I wrote the diary project rather than the cause of the blog project, actually a name change, this is a blog project.

Let’s put the delete button in the details page, open SRC /Detail/index. JSX, and add a delete button to the right of the header as follows:

import React, { useState, useEffect } from 'react'
import { NavBar, Icon, List } from 'antd-mobile'
import { useHistory } from 'react-router-dom'
import { getQueryString } from '.. /utils'
import axios from '.. /utils/axios'

const Detail = () = > {
  const [detail, setDetail] = useState({})
  const history = useHistory()
  const id = getQueryString('id')

  useEffect(() = > {
    axios.get(`/detail/${id}`).then(({ data }) = > {
      if (data.length) {
        setDetail(data[0])}})}, [])const deleteDiary = (id) = > {
    axios.post('/delete', { id }).then(({ data }) = > {
      // After the deletion is successful, return to the home page
      history.push('/')})}return (<div className='diary-detail'>
    <NavBar
      mode="light"
      icon={<Icon type="left" />}
      onLeftClick={() => history.goBack()}
      rightContent={[
        <Icon onClick={()= > deleteDiary(detail.id)} key="0" type="cross-circle-o" />
      ]}
    >{detail.title || ''}</NavBar>
    <List renderHeader={()= >'${detail. Date'} className="my-list"><List.Item wrap>
        {detail.content}
      </List.Item>
    </List>
  </div>)}export default Detail
Copy the code

Modify the article

To modify the article, just get the id of the article and then pass the modified parameters to the modify interface. We first add a modify button to the details page, open SRC /Detail/index.jsx, and then add a section of code

<NavBar
      mode="light"
      icon={<Icon type="left" />}
      onLeftClick={() = > history.goBack()}
      rightContent={[
        <Icon style={{ marginRight: 10 }} onClick={()= > deleteDiary(detail.id)} key="0" type="cross-circle-o" />.<img onClick={()= >history.push(`/edit? id=${detail.id}`)} style={{ width: 26 }} src="//s.weituibao.com/1578721957732/Edit.png" alt=""/>
      ]}
    >{detail.title || ' '}</NavBar>
Copy the code

The above code adds an IMG tag, which is clicked to jump to the edit page, along with the corresponding ID. We can get the details by id on the Edit page, assign a value to the variable and Edit it by opening the SRC /Edit/index.jsx page:

import React, { useState, useEffect } from 'react'
import { List, InputItem, TextareaItem, DatePicker, ImagePicker, Button, Toast } from 'antd-mobile'
import moment from 'moment'
import axios from '.. /utils/axios'
import { getQueryString } from '.. /utils'
import './style.css'

const Edit = () = > {
  const [title, setTitle] = useState(' ')
  const [content, setContent] = useState(' ')
  const [date, setDate] = useState(' ')
  const [files, setFile] = useState([])
  const id = getQueryString('id')
  const onChange = (files, type, index) = > {
    console.log(files, type, index);
    setFile(files)
  }

  useEffect(() = > {
    if (id) {
      axios.get(`/detail/${id}`).then(({ data }) = > {
        if (data.length) {
          setTitle(data[0].title)
          setContent(data[0].content)
          setDate(new Date(data[0].date))
          setFile([{ url: data[0].url }])
        } 
      })
    }
  }, [])

  const publish = () = > {
    if(! title || ! content || ! date) { Toast.fail('Please fill in the necessary parameters')
      return
    }
    const params = {
      title,
      content,
      date: moment(date).format('YYYY-MM-DD'),
      url: files.length ? files[0].url : ' '
    }
    if (id) {
      params['id'] = id
      axios.post('/update', params).then(res= > {
        Toast.success('Modified successfully')})return
    }
    axios.post('/add', params).then(res= > {
      Toast.success('Add successful')})}return (<div className='diary-edit'>
    <List renderHeader={()= >'Edit diary '}><InputItem
        clear
        placeholder="Please enter a title"
        value={title}
        onChange={(value)= >SetTitle (value)} > title</InputItem>
      <TextareaItem
        rows={6}
        placeholder="Please enter your diary."
        value={content}
        onChange={(value)= > setContent(value)}
      />
      <DatePicker
        mode="date"
        title="Please select a date"
        extra="Please select a date"
        value={date}
        onChange={date= > setDate(date)}
      >
        <List.Item arrow="horizontal">The date of</List.Item>
      </DatePicker>
      <ImagePicker
        files={files}
        onChange={onChange}
        onImageClick={(index, fs) = > console.log(index, fs)}
        selectable={files.length < 1}
        multiple={false}
      />
      <Button type='primary' onClick={()= >The publish ()} ></Button>
    </List>
  </div>)}export default Edit
Copy the code

When the details are obtained, they are displayed on the input page.

The whole project front-end process has been run through, although the database is only one table, but as a programmer, need to have the ability to draw inferences from other cases. Of course, if you want to make the project more complex, you need some basic database design.

conclusion

Ten thousand words long, see the last friend must also love learning, hope to improve their own people. The full text may be a little rough, but as the old saying goes, the master leads the way, and the cultivation depends on the individual. More good articles can follow my personal blog and my zhihu column. If you have any questions, you can add the wechat group in my personal blog to learn and discuss. This long article wrote that I spit blood, I hope to help you.