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
- A basic blog content manager feature, such as publishing and managing articles
- Each user can have their own blog by registering
- Support markdown syntax editing
- Support code highlighting
- You can manage links to blog pages
- Blog pages are optimized for mobile
- Account Management (Changing passwords)
- 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:
- The database was redesigned to a subDocs database structure grouped by users
- The database shall be changed, all interfaces shall be redesigned, and the interface style shall be consistent with that of netease Instant Finance
- Delete the original tourist mode, increase login registration function, support pop-up login.
- Add a home page to display the latest published articles and registered users
- Add password change, logout, logout and other functions.
- 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.
- Added mobile adaptation
- 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 contentcss
: Custom popup class, empty by defaultshowClose
If: is false, the close button is not displayed, and is displayed by defaultcloseFn
: Popup callback after clicking the close buttontitle
: The title of the popover, default ‘warm tips ‘, if you do not want to display the title, directly pass blankcontent
(Required) : Popup content, HTML supportbtn1
: 1 copy | ‘button button style class 1’, for btn1Text and btn1Css after formattingcb1
: callback after button 1 is clicked. If CB1 does not explicitly return true, the default button is clicked to close the popoverbtn2
: ‘button 2 copy | button style class 2, for btn2Text and btn2Css after formattingcb2
: 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 popoverinit
: 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 disappearswapGoDialog
: 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