The project was originally fork’s project. At first, I wanted to contact the mongodb database to find an example to learn from, but later I changed it and it changed beyond recognition. Background and database reconstruction, front-end added login registration function, only retained the blog Settings page, but also optimized.

I. Functional features

  1. A basic blog content manager feature, such as publishing and managing articles
  2. Each user can have their own blog by registering
  3. Support markdown syntax editing
  4. Support code highlighting
  5. You can manage links to blog pages
  6. Blog pages are optimized for mobile
  7. Account Management (Changing passwords)
  8. Page enough atmosphere, cool hey

Two, the technology and implementation ideas:

2.1 Front end: Vue family barrel

  • Vue.js
  • Vue-Cli
  • Vue-Resource
  • Vue-Validator
  • Vue-Router
  • Vuex
  • Vue-loader

2.2 the back-end

  • Node.js
  • mongoDB (mongoose)
  • Express

2.3 Tools and Languages

  • Webpack
  • ES6
  • SASS
  • Jade

2.4 Overall idea:

  • The Node server does not render templates except for the home page and the home page. Rendering is left to the browser
  • The Node server does not perform any route switching, which is done by vue-Router
  • The Node server is only used to receive requests, query the database, and return values

So this is almost completely decoupled from the front and back ends, as long as you agree on restful data interfaces and data access formats.

At the back end, I used mongoDB to do the database, and operated mongoDB through Mongoose in Express, which omitted the complex command line. Undoubtedly, it was much more convenient to operate through Javascript.

Iii. Updated content

On the basis of the original project, the following updates were made:

  1. The database was redesigned to a subDocs database structure grouped by users
  2. The database shall be changed, all interfaces shall be redesigned, and the interface style shall be consistent with that of netease Instant Finance
  3. Delete the original tourist mode, increase login registration function, support pop-up login.
  4. Add a home page to display the latest published articles and registered users
  5. Add password change, logout, logout and other functions.
  6. Optimized pop popover component, more intelligent, more configuration items, close to netease $.dialog component. And a set of code only modified the CSS to realize the popover function on the PC side and toast function on the WAP side under the same interface.
  7. Added mobile adaptation
  8. Optimize the original code and fix some bugs.

More updates can be found in the cmS-of-Blog_production and CMS-of-Blog projects.

Core code analysis

The original author has also written analytical articles. Here, the main analysis of my updated part.

Database of 4.1.

The original database was redesigned to a subDocs database structure grouped by users. In this way, the database structure of the user as a whole is clearer and more convenient to operate and read. The code is as follows:

var mongoose =  require('mongoose'),
    Schema =    mongoose.Schema

    articleSchema = new Schema({
        title: String.date: Date.content: String,
    }),

    linkSchema = new Schema({
        name: String.href: String.newPage: Boolean
    }),

    userSchema = new Schema({
        name: String.password: String.email: String.emailCode: String.createdTime: Number.articles: [articleSchema],
        links: [linkSchema]
    }),

    User = mongoose.model('User', userSchema);

mongoose.connect('mongodb://localhost/platform')
mongoose.set('debug'.true)

var db = mongoose.connection
db.on('error'.function () {
    console.log('db error'.error)
})
db.once('open'.function () {
    console.log('db opened'.silly)
})

module.exports = {
    User: User
}
Copy the code

The code starts with three new schemas defined: articleSchema, linkSchema, and userSchema. The userSchema nested articleSchema and linkSchema to form the subDocs database structure grouped by users. Schema is a database model skeleton that is stored as a file and does not have the operation capability of a database. This Schema will then be published as a Model. Model A database action pair with abstract properties and behavior that is generated by a Schema publication. Entities that can be created by Model, such as when a new user is registered.

After the database is created, it needs to be read and operated. You can see the code that sends email verification code when registering.

router.post('/genEmailCode'.function(req, res, next) {
    var email = req.body.email,
    resBody = {
        retcode: ' '.retdesc: ' '.data: {}}if(! email){ resBody = {retcode: 400.retdesc: 'Parameter error',
        }
        res.send(resBody)
        return
    }
    function genRandomCode(){
        var arrNum = [];
        for(var i=0; i<6; i++){
            var tmpCode = Math.floor(Math.random() * 9);
            arrNum.push(tmpCode);
        }
        return arrNum.join(' ')
    }
    db.User.findOne({ email: email }, function(err, doc) {
        if (err) {
            return console.log(err)
        } else if(doc && doc.name ! = ='tmp') {
            resBody = {
                retcode: 400.retdesc: 'This mailbox is registered',
            }
            res.send(resBody)
        } else if(! doc){// First click to get the verification code
            var emailCode = genRandomCode();
            var createdTime = Date.now();
            // setup e-mail data with unicode symbols
            var mailOptions = {
                from: '" CMS - of - the Blog 👥 "< [email protected] >'.// sender address
                to: email, // list of receivers
                subject: 'Dear User' + email, // Subject line
                text: 'Hello world 🐴'.// plaintext body
                html: [
                    '< p > hello! Congratulations on registering as a CMS-of-Blog user. 

'
.'

This is a registration email sending a verification code, please copy the verification code to the registration page to complete the registration.

'
.'

This time the verification code is: '

+ emailCode + '</p>'.'

The above verification code is valid for 30 minutes. If the verification code is invalid, please visit the website

'
.'

Thank you for signing up as a CMS-of-Blog user!



'
.'

CMS-of-blog Development Team

'
.'<p>'+ (new Date()).toLocaleString() + '</p>' ].join(' ') // html body }; // send mail with defined transport object transporter.sendMail(mailOptions, function(error, info){ if(error){ return console.log(error); } // console.log('Message sent: ' + info.response); new db.User({ name: 'tmp'.password: '0000'.email: email, emailCode: emailCode, createdTime: createdTime, articles: [].links: [] }).save(function(err) { if (err) return console.log(err) // If the registration is not successful within half an hour, the data will be deleted from the database, that is, the verification code will be invalid setTimeout(function(){ db.User.findOne({ email: email }, function(err, doc) { if (err) { return console.log(err) } else if (doc && doc.createdTime === createdTime) { db.User.remove({ email: email }, function(err) { if (err) { return console.log(err) } }) } }) }, 30*60*1000); resBody = { retcode: 200.retdesc: ' ' } res.send(resBody) }) }); }else if(doc && doc.name === 'tmp') {// Click to obtain the verification code again within the validity period of the mailbox verification code (similar to omitted). }})})Copy the code

After receiving the request to send the mailbox verification code, the background initializes a TMP user. Using new db.user () creates an instance of User, and a save() operation writes this data to the database. If the registration is not successful within half an hour, remove the data by matching the mailbox and then db.user.remove (). Please refer to the official documentation for more details.

4.2. The background

Categorize all requests into three categories:

  • Ajax asynchronous request, uniform path:/web/
  • Public page parts, such as blog home page, login, registration, etc., unified path:/
  • The blog section associated with the blog user ID, the unified path:/:id/

In this way, each user can have their own blog page, with the following code:

var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./index');
var db = require('./db')
var app = express();

// view engine setup
app.set('views', path.join(__dirname, '.. / '));
app.set('view engine'.'jade');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use('/public',express.static(path.join(__dirname, '.. /public')));

// Public Ajax interface (index.js)
app.use('/web', routes);

// Public HTML pages, such as login page, registration page
app.get('/'.function(req, res, next) {
    res.render('common', { title: 'CMS-blog' });
})

// The first parameter of the route matches only those related to the process.
app.get(/ / ^ \ [a-z] {1} [a - z0-9 _] {3, 15} $/.function(req, res, next) {
    // format Gets the request path parameter
    var pathPara = req._parsedUrl.pathname.slice(1).toLocaleLowerCase()
    // query to see if there is a corresponding username
    db.User.count({name: pathPara}, function(err, num) {
        if (err) return console.log(err)
        if(num > 0){
            res.render('main', { title: 'CMS-blog' });
        }else{
            // Custom error handling
            res.status(403);
            res.render('error', {
                message: 'The user has not started a blog. }); }})})// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

// error handlers

// development error handler
// will print stacktrace
if (app.get('env') = = ='development') {
    app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
            message: err.message,
            error: err
        });
    });
}

module.exports = app;
Copy the code

The ajax interface code can be found in the index.js file in the server folder.

4.3. The pop/toast components

On the basis of the original project, optimized pop popover component, more intelligent, more configuration items, close to netease $.dialog component. Make and a set of code only modified the CSS, to realize the popover on the PC side and toast on the WAP side under the same interface. Because part of the formatting parameter code is in the Vuex action, if you have time, you can further organize this into a VUE component for your convenience.

4.3.1 Pop/TOAST Configuration Parameters

  • pop: Whether to display the popup window or not, according to the content parameter, true if there is content
  • css: Custom popup class, empty by default
  • showCloseIf: is false, the close button is not displayed, and is displayed by default
  • closeFn: Popup callback after clicking the close button
  • title: The title of the popover, default ‘warm tips ‘, if you do not want to display the title, directly pass blank
  • content(Required) : Popup content, HTML support
  • btn1: 1 copy | ‘button button style class 1’, for btn1Text and btn1Css after formatting
  • cb1: callback after button 1 is clicked. If CB1 does not explicitly return true, the default button is clicked to close the popover
  • btn2: ‘button 2 copy | button style class 2, for btn2Text and btn2Css after formatting
  • cb2: callback after button 2 is clicked. If cb2 does not explicitly return true, the default button is clicked to close the popover. Button parameters do not pass, copy default ‘I know’, click to close the popover
  • init: a popover initialization function that can be used to handle complex interactions (note that popovers must be executed if they change from pop to false to true)
  • destroy: The callback function after the popover disappears
  • wapGoDialog: On mobile, do you want to go to popover? Default: false, go toast

4.3.2 Pop/TOAST component code

The template

<template>
    <div class="m-dialog" :class="getPopPara.css">
        <div class="dialog-wrap">
            <span class="close" @click="handleClose" v-if="getPopPara.showClose">+</span>
            <div class="title" v-if="getPopPara.title">{{getPopPara.title}}</div>
            <div class="content">{{{getPopPara.content}}}</div>
            <div class="button">
                <p class="btn" :class="getPopPara.btn1Css" @click="fn1">
                    <span>{{getPopPara.btn1Text}}</span>
                </p>
                <p class="btn" :class="getPopPara.btn2Css" @click="fn2" v-if="getPopPara.btn2Text">
                    <span>{{getPopPara.btn2Text}}</span>
                </p>
            </div>
        </div>
    </div>
</template>
Copy the code

The script

import {pop}                from '.. /vuex/actions'
import {getPopPara}         from '.. /vuex/getters'
import $                    from '.. /js/jquery.min'

export default{
    computed:{
        showDialog(){
            return this.getPopPara.pop
        }
    },
    vuex: {
        getters: {
            getPopPara
        },
        actions: {
            pop
        }
    },
    methods: {
        fn1(){
            let fn = this.getPopPara.cb1
            let closePop = false
            // If cb1 does not explicitly return true, the default button is clicked to close the popover
            if(typeof fn == 'function'){
                closePop = fn()
            }
            // The initial value is false, so no transmission is disabled by default
            if(! closePop){this.pop()
            }
            / /! fn && this.pop()
        },
        fn2(){
            let fn = this.getPopPara.cb2
            let closePop = false
            // If cb1 does not explicitly return true, the default button is clicked to close the popover
            if(typeof fn == 'function'){
                closePop = fn()
            }
            // The initial value is false, so no transmission is disabled by default
            if(! closePop){this.pop()
            }
            / /! fn && this.pop()
        },
        handleClose(){
            // this.pop() should be left at the end, because all parameters change when executed first
            let fn = this.getPopPara.closeFn
            typeof fn == 'function' && fn()
            this.pop()
        }
    },
    watch: {'showDialog': function(newVal, oldVal){
            // When the popover opens
            if(newVal){
                // Add keyboard support for pop-ups
                $(document).bind('keydown', (event)=>{
                    // Repeat popup bug when executing fn1
                    if(event.keyCode === 27) {this.pop()
                    }
                })
                var $dialog = $('.dialog-wrap');
                // The mobile end is changed to resemble TOAST. By changing the style, there is no need to add TOAST components or change the code to unify the POP method
                if(screen.width < 700&&!this.getPopPara.wapGoDialog){
                    $dialog.addClass('toast-wrap');
                    setTimeout((a)= >{
                        this.pop();
                        $dialog.removeClass('toast-wrap');
                    }, 2000)}// Adjust the popover to center
                let width = $dialog.width();
                let height = $dialog.height();
                $dialog.css('marginTop', - height/2);
                $dialog.css('marginLeft', - width/2);
                // The initialization function created by the popover
                let fn = this.getPopPara.init;
                typeof fn == 'function' && fn();
            }else{
                // When the popover closes
                // Unregister the event registered when the popup opens
                $(document).unbind('keydown')
                // Popover disappear callback
                let fn = this.getPopPara.destroy
                typeof fn == 'function' && fn()
            }
        }
    }
}
Copy the code

4.3.3 Pop/TOAST component parameter formatting code

For the convenience of use, we use the shorthand. In order for the component to recognize it, the parameters passed in need to be formatted in the Vuex action.

function pop({dispatch}, para) {
    // If no arguments are passed, the popover closes by default
    if(para === undefined){
        para = {}
    }
    // If only strings are passed, format the para object with content
    if(typeof para === 'string'){
        para = {
            content: para
        }
    }
    // Set the default valuepara.pop = ! para.content?false: true
    para.showClose = para.showClose === undefined? true: para.showClose
    para.title = para.title === undefined? 'Warm reminder': para.title para.wapGoDialog = !! para.wapGoDialog// No parameters are passed
    if(! para.btn1){ para.btn1 ='I know | normal'
    }
    // Class is not passed
    if(para.btn1.indexOf('|') = = =- 1){
        para.btn1 = para.btn1 + '|primary'
    }
    let array1 = para.btn1.split('|')
    para.btn1Text = array1[0]
    // Multiple classes may be passed
    for(let i=1,len=array1.length; i<len; i++){
        if(i==1) {// Class is disabled without 'BTN -'
            para.btn1Css = array1[1] = ='disabled'? 'disabled': 'btn-' + array1[1]}else{
            para.btn1Css = array1[i]=='disabled'? ' disabled': para.btn1Css + ' btn-' + array1[i]
        }
    }

    if(para.btn2){
        if(para.btn2.indexOf('|') = = =- 1){
            para.btn2 = para.btn2 + '|normal'
        }
        let array2 = para.btn2.split('|')
        para.btn2Text = array2[0]
        for(let i=1,len=array2.length; i<len; i++){
            if(i==1){
                para.btn2Css = array2[1] = ='disabled'? 'disabled': 'btn-' + array2[1]}else{
                para.btn2Css = array2[i]=='disabled'? ' disabled': para.btn2Css + ' btn-' + array2[i]
            }
        }
    }
    dispatch('POP', para)
}
Copy the code

To make the mobile side compatible with the POP popover component, we changed the mobile side style using mediaQuery. – Added the wapGoDialog parameter to indicate whether we want to go to the popover window when we are on the move, default is false, go toast. This allows one set of code to be compatible with BOTH PC and WAP.

Afterword.

Here is the main analysis of the background and database, and relatively simple, we can go to see the source code. Anyway, this is a good example of starting with the front end and starting with the back end and the database. The function is more rich, and you can learn the vue.js.

Welcome to star learning communication: making the | my blog address