成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

一個(gè)基于Vue.js+Mongodb+Node.js的博客內(nèi)容管理系統(tǒng)

wh469012917 / 2126人閱讀

摘要:三更新內(nèi)容在原來(lái)項(xiàng)目的基礎(chǔ)上,做了如下更新數(shù)據(jù)庫(kù)重新設(shè)計(jì),改成以用戶分組的數(shù)據(jù)庫(kù)結(jié)構(gòu)應(yīng)數(shù)據(jù)庫(kù)改動(dòng),所有接口重新設(shè)計(jì),并統(tǒng)一采用和網(wǎng)易立馬理財(cái)一致的接口風(fēng)格刪除原來(lái)游客模式,增加登錄注冊(cè)功能,支持彈窗登錄。

這個(gè)項(xiàng)目最初其實(shí)是fork別人的項(xiàng)目。當(dāng)初想接觸下mongodb數(shù)據(jù)庫(kù),找個(gè)例子學(xué)習(xí)下,后來(lái)改著改著就面目全非了。后臺(tái)和數(shù)據(jù)庫(kù)重構(gòu),前端增加了登錄注冊(cè)功能,僅保留了博客設(shè)置頁(yè)面,但是也優(yōu)化了。

一、功能特點(diǎn)

一個(gè)基本的博客內(nèi)容管理器功能,如發(fā)布并管理文章等

每個(gè)用戶可以通過(guò)注冊(cè)擁有自己的博客

支持markdown語(yǔ)法編輯

支持代碼高亮

可以管理博客頁(yè)面的鏈接

博客頁(yè)面對(duì)移動(dòng)端適配優(yōu)化

賬戶管理(修改密碼)

頁(yè)面足夠大氣、酷炫嘿

二、用到的技術(shù)和實(shí)現(xiàn)思路: 2.1 前端:Vue全家桶

Vue.js

Vue-Cli

Vue-Resource

Vue-Validator

Vue-Router

Vuex

Vue-loader

2.2 后端

Node.js

mongoDB (mongoose)

Express

2.3 工具和語(yǔ)言

Webpack

ES6

SASS

Jade

2.4 整體思路:

Node服務(wù)端除了主頁(yè)和首頁(yè)外,不做模板渲染,渲染交給瀏覽器完成

Node服務(wù)端不做任何路由切換的內(nèi)容,這部分交給Vue-Router完成

Node服務(wù)端只用來(lái)接收請(qǐng)求,查詢數(shù)據(jù)庫(kù)并用來(lái)返回值

所以這樣做前后端幾乎完全解耦,只要約定好restful風(fēng)格的數(shù)據(jù)接口,和數(shù)據(jù)存取格式就OK啦。

后端我用了mongoDB做數(shù)據(jù)庫(kù),并在Express中通過(guò)mongoose操作mongoDB,省去了復(fù)雜的命令行,通過(guò)Javascript操作無(wú)疑方便了很多。

三、更新內(nèi)容

在原來(lái)項(xiàng)目的基礎(chǔ)上,做了如下更新:

數(shù)據(jù)庫(kù)重新設(shè)計(jì),改成以用戶分組的subDocs數(shù)據(jù)庫(kù)結(jié)構(gòu)

應(yīng)數(shù)據(jù)庫(kù)改動(dòng),所有接口重新設(shè)計(jì),并統(tǒng)一采用和網(wǎng)易立馬理財(cái)一致的接口風(fēng)格

刪除原來(lái)游客模式,增加登錄注冊(cè)功能,支持彈窗登錄。

增加首頁(yè),展示最新發(fā)布文章和注冊(cè)用戶

增加修改密碼,登出,注銷等功能。

優(yōu)化pop彈窗組件,更加智能,更多配置項(xiàng),接近網(wǎng)易$.dialog組件。并且一套代碼僅修改了下css,實(shí)現(xiàn)相同接口下pc端彈窗和wap端toast功能。

增加移動(dòng)端適配

優(yōu)化原來(lái)代碼,修復(fù)部分bug。

更多的更新內(nèi)容請(qǐng)移步項(xiàng)目CMS-of-Blog_Production和CMS-of-Blog。

四、核心代碼分析

原作者也寫過(guò)分析的文章。這里,主要分析一下我更新的部分。

4.1. 數(shù)據(jù)庫(kù)

對(duì)原數(shù)據(jù)庫(kù)進(jìn)行重新設(shè)計(jì),改成以用戶分組的subDocs數(shù)據(jù)庫(kù)結(jié)構(gòu)。這樣以用戶為一個(gè)整體的數(shù)據(jù)庫(kù)結(jié)構(gòu)更加清晰,同時(shí)也更方便操作和讀取。代碼如下:

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
}

代碼一開(kāi)始新定義了三個(gè)Schema:articleSchema、linkSchema和userSchema。而userSchema里又嵌套了articleSchema和linkSchema,構(gòu)成了以用戶分組的subDocs數(shù)據(jù)庫(kù)結(jié)構(gòu)。Schema是一種以文件形式存儲(chǔ)的數(shù)據(jù)庫(kù)模型骨架,不具備數(shù)據(jù)庫(kù)的操作能力。然后將將該Schema發(fā)布為Model。Model由Schema發(fā)布生成的模型,具有抽象屬性和行為的數(shù)據(jù)庫(kù)操作對(duì)。由Model可以創(chuàng)建的實(shí)體,比如新注冊(cè)一個(gè)用戶就會(huì)創(chuàng)建一個(gè)實(shí)體。

數(shù)據(jù)庫(kù)創(chuàng)建了之后需要去讀取和操作,可以看下注冊(cè)時(shí)發(fā)送郵箱驗(yàn)證碼的這段代碼感受下。

router.post("/genEmailCode", function(req, res, next) {
    var email = req.body.email,
    resBody = {
        retcode: "",
        retdesc: "",
        data: {}
    }
    if(!email){
        resBody = {
            retcode: 400,
            retdesc: "參數(shù)錯(cuò)誤",
        }
        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: "該郵箱已注冊(cè)",
            }
            res.send(resBody)
        } else if(!doc){  // 第一次點(diǎn)擊獲取驗(yàn)證碼
            var emailCode = genRandomCode();
            var createdTime = Date.now();
            // setup e-mail data with unicode symbols
            var mailOptions = {
                from: ""CMS-of-Blog ?" ", // sender address
                to: email, // list of receivers
                subject: "親愛(ài)的用戶" + email, // Subject line
                text: "Hello world ?", // plaintext body
                html: [
                    "

您好!恭喜您注冊(cè)成為CMS-of-Blog博客用戶。

", "

這是一封發(fā)送驗(yàn)證碼的注冊(cè)認(rèn)證郵件,請(qǐng)復(fù)制一下驗(yàn)證碼填寫到注冊(cè)頁(yè)面以完成注冊(cè)。

", "

本次驗(yàn)證碼為:" + emailCode + "

", "

上述驗(yàn)證碼30分鐘內(nèi)有效。如果驗(yàn)證碼失效,請(qǐng)您登錄網(wǎng)站感謝您注冊(cè)成為CMS-of-Blog博客用戶!


", "

CMS-of-Blog開(kāi)發(fā)團(tuán)隊(duì)

", "

"+ (new Date()).toLocaleString() + "

" ].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) // 半小時(shí)內(nèi)如果不注冊(cè)成功,則在數(shù)據(jù)庫(kù)中刪除這條數(shù)據(jù),也就是說(shuō)驗(yàn)證碼會(huì)失效 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"){ // 在郵箱驗(yàn)證碼有效的時(shí)間內(nèi),再次點(diǎn)擊獲取驗(yàn)證碼(類似省略) ... } }) })

后臺(tái)接受到發(fā)送郵箱驗(yàn)證碼的請(qǐng)求后,會(huì)初始化一個(gè)tmp的用戶。通過(guò)new db.User()會(huì)創(chuàng)建一個(gè)User的實(shí)例,然后執(zhí)行save()操作會(huì)將這條數(shù)據(jù)寫到數(shù)據(jù)庫(kù)里。如果在半小時(shí)內(nèi)沒(méi)有注冊(cè)成功,通過(guò)匹配郵箱,然后db.User.remove()將這條數(shù)據(jù)刪除。更多具體用法請(qǐng)移步官方文檔。

4.2. 后臺(tái)

將所有請(qǐng)求分為三種:

ajax異步請(qǐng)求,統(tǒng)一路徑:/web/

公共頁(yè)面部分,如博客首頁(yè)、登錄、注冊(cè)等,統(tǒng)一路徑:/

與博客用戶id相關(guān)的博客部分,統(tǒng)一路徑:/:id/

這樣每個(gè)用戶都可以擁有自己的博客頁(yè)面,具體代碼如下:

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")));

// 公共ajax接口(index.js)
app.use("/web", routes);

// 公共html頁(yè)面,比如登錄頁(yè),注冊(cè)頁(yè)
app.get("/", function(req, res, next) {
    res.render("common", { title: "CMS-blog" });
})

// 跟用戶相關(guān)的博客頁(yè)面(路由的第一個(gè)參數(shù)只匹配與處理的相關(guān)的,不越權(quán)!)
app.get(/^/[a-z]{1}[a-z0-9_]{3,15}$/, function(req, res, next) {
    // format獲取請(qǐng)求的path參數(shù)
    var pathPara = req._parsedUrl.pathname.slice(1).toLocaleLowerCase()
    // 查詢是否對(duì)應(yīng)有相應(yīng)的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{
            // 自定義錯(cuò)誤處理
            res.status(403);
            res.render("error", {
                message: "該用戶尚未開(kāi)通博客。去注冊(cè)",
            });
        }
    })
})

// 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;

具體的ajax接口代碼大家可以看server文件夾下的index.js文件。

4.3. pop/toast組件

在原項(xiàng)目基礎(chǔ)上,優(yōu)化了pop彈窗組件,更加智能,更多配置項(xiàng),接近網(wǎng)易$.dialog組件。使并且一套代碼僅修改了下css,實(shí)現(xiàn)相同接口下pc端彈窗和wap端toast功能。因?yàn)橛胁糠指袷交瘏?shù)代碼在vuex的action里,有時(shí)間,可以將這個(gè)進(jìn)一步整理成一個(gè)vue組件,方便大家使用。

4.3.1 pop/toast組件配置參數(shù)說(shuō)明

pop: 彈窗的顯示與否, 根據(jù)content參數(shù),有內(nèi)容則為true

css: 自定義彈窗的class, 默認(rèn)為空

showClose: 為false則不顯示關(guān)閉按鈕, 默認(rèn)顯示

closeFn: 彈窗點(diǎn)擊關(guān)閉按鈕之后的回調(diào)

title: 彈窗的標(biāo)題,默認(rèn)"溫馨提示", 如果不想顯示title, 直接傳空

content(required): 彈窗的內(nèi)容,支持傳html

btn1: "按鈕1文案|按鈕1樣式class", 格式化后為btn1Text和btn1Css

cb1: 按鈕1點(diǎn)擊之后的回調(diào),如果cb1沒(méi)有明確返回true,則默認(rèn)按鈕點(diǎn)擊后關(guān)閉彈窗

btn2: "按鈕2文案|按鈕2樣式class", 格式化后為btn2Text和btn2Css

cb2: 按鈕2點(diǎn)擊之后的回調(diào),如果cb2沒(méi)有明確返回true,則默認(rèn)按鈕點(diǎn)擊后關(guān)閉彈窗。按鈕參數(shù)不傳,文案默認(rèn)"我知道了",點(diǎn)擊關(guān)閉彈窗

init: 彈窗建立后的初始化函數(shù),可以用來(lái)處理復(fù)雜交互(注意彈窗一定要是從pop為false變成true才會(huì)執(zhí)行)

destroy: 彈窗消失之后的回調(diào)函數(shù)

wapGoDialog: 在移動(dòng)端時(shí),要不要走彈窗,默認(rèn)false,走toast

4.3.2 pop/toast組件代碼

模板

腳本

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
            //  如果cb1函數(shù)沒(méi)有明確返回true,則默認(rèn)按鈕點(diǎn)擊后關(guān)閉彈窗
            if(typeof fn == "function"){
                closePop = fn()
            }
            // 初始值為false, 所以沒(méi)傳也默認(rèn)關(guān)閉
            if(!closePop){
                this.pop()
            }
            // !fn && this.pop()
        },
        fn2(){
            let fn = this.getPopPara.cb2
            let closePop = false
            //  如果cb1函數(shù)沒(méi)有明確返回true,則默認(rèn)按鈕點(diǎn)擊后關(guān)閉彈窗
            if(typeof fn == "function"){
                closePop = fn()
            }
            // 初始值為false, 所以沒(méi)傳也默認(rèn)關(guān)閉
            if(!closePop){
                this.pop()
            }
            // !fn && this.pop()
        },
        handleClose(){
            // this.pop()要放在最后,因?yàn)橄葓?zhí)行所有參數(shù)就都變了
            let fn = this.getPopPara.closeFn
            typeof fn == "function" && fn()
            this.pop()
        }
    },
    watch:{
        "showDialog": function(newVal, oldVal){
            // 彈窗打開(kāi)時(shí)
            if(newVal){
                // 增加彈窗支持鍵盤操作
                $(document).bind("keydown", (event)=>{
                    // 回車鍵執(zhí)行fn1,會(huì)出現(xiàn)反復(fù)彈窗bug
                    if(event.keyCode === 27){
                        this.pop()
                    }
                })
                var $dialog = $(".dialog-wrap");
                // 移動(dòng)端改成類似toast,通過(guò)更改樣式,既不需要增加toast組件,也不需要更改代碼,統(tǒng)一pop方法
                if(screen.width < 700 && !this.getPopPara.wapGoDialog){
                    $dialog.addClass("toast-wrap");
                    setTimeout(()=>{
                        this.pop();
                        $dialog.removeClass("toast-wrap");
                    }, 2000)
                }
                //調(diào)整彈窗居中
                let width = $dialog.width();
                let height = $dialog.height();
                $dialog.css("marginTop", - height/2);
                $dialog.css("marginLeft", - width/2);
                // 彈窗建立的初始化函數(shù)
                let fn = this.getPopPara.init;
                typeof fn == "function" && fn();
            }else{
                // 彈窗關(guān)閉時(shí)
                // 注銷彈窗打開(kāi)時(shí)注冊(cè)的事件
                $(document).unbind("keydown")
                // 彈窗消失回調(diào)
                let fn = this.getPopPara.destroy
                typeof fn == "function" && fn()
            }
        }
    }
}
4.3.3 pop/toast組件參數(shù)格式化代碼

為了使用方便,我們?cè)谑褂玫臅r(shí)候進(jìn)行了簡(jiǎn)寫。為了讓組件能識(shí)別,需要在vuex的action里對(duì)傳入的參數(shù)格式化。

function pop({dispatch}, para) {
    // 如果沒(méi)有傳入任何參數(shù),默認(rèn)關(guān)閉彈窗
    if(para === undefined){
        para = {}
    }
    // 如果只傳入字符串,格式化內(nèi)容為content的para對(duì)象
    if(typeof para === "string"){
        para = {
            content: para
        }
    }
    // 設(shè)置默認(rèn)值
    para.pop = !para.content? false: true
    para.showClose = para.showClose === undefined? true: para.showClose
    para.title = para.title === undefined? "溫馨提示": para.title
    para.wapGoDialog = !!para.wapGoDialog
    // 沒(méi)有傳參數(shù)
    if(!para.btn1){
        para.btn1 = "我知道了|normal"
    }
    // 沒(méi)有傳class
    if(para.btn1.indexOf("|") === -1){
        para.btn1 = para.btn1 + "|primary"
    }
    let array1 = para.btn1.split("|")
    para.btn1Text = array1[0]
    // 可能會(huì)傳多個(gè)class
    for(let i=1,len=array1.length; i

為了讓移動(dòng)端兼容pop彈窗組件,我們采用mediaQuery對(duì)移動(dòng)端樣式進(jìn)行了更改。增加參數(shù)wapGoDialog,表明我們?cè)谝苿?dòng)端時(shí),要不要走彈窗,默認(rèn)false,走toast。這樣可以一套代碼就可以兼容pc和wap。

后記

這里主要分析了下后臺(tái)和數(shù)據(jù)庫(kù),而且比較簡(jiǎn)單,大家可以去看源碼??傊@是一個(gè)不錯(cuò)的前端入手后臺(tái)和數(shù)據(jù)庫(kù)的例子。功能比較豐富,而且可以學(xué)習(xí)下vue.js。

歡迎大家star學(xué)習(xí)交流:github地址 | 我的博客

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/107525.html

相關(guān)文章

  • Vue.js實(shí)踐:一個(gè)Node.js+mongoDB+Vue.js博客內(nèi)容管理系統(tǒng)

    摘要:項(xiàng)目來(lái)源以前曾用過(guò)搭建自己的博客網(wǎng)站,但感覺(jué)很是臃腫。所以一直想自己寫一個(gè)博客內(nèi)容管理器。正好近日看完了各個(gè)插件的文檔,就用著嘗試寫了這個(gè)簡(jiǎn)約的博客內(nèi)容管理器。關(guān)于后端后端是用作為服務(wù)器的,使用了框架。 項(xiàng)目來(lái)源 以前曾用過(guò)WordPress搭建自己的博客網(wǎng)站,但感覺(jué)WordPress很是臃腫。所以一直想自己寫一個(gè)博客內(nèi)容管理器。 正好近日看完了Vue各個(gè)插件的文檔,就用著Vue嘗試寫...

    Dr_Noooo 評(píng)論0 收藏0
  • vue+node支持服務(wù)端渲染博客系統(tǒng)

    摘要:此項(xiàng)目我會(huì)長(zhǎng)期更新,希望能和大家一起學(xué)習(xí),共同進(jìn)步更新本項(xiàng)目的版本基于開(kāi)發(fā),后端用進(jìn)行了重寫。 感悟 歷時(shí)兩個(gè)多月,終于利用工作之余完成了這個(gè)項(xiàng)目的1.0版本,為什么要寫這個(gè)項(xiàng)目?其實(shí)基于vuejs+nodejs構(gòu)建的開(kāi)源博客系統(tǒng)有很多,但是大多數(shù)不支持服務(wù)端渲染,也不支持動(dòng)態(tài)標(biāo)題,只是做到了前后端分離,對(duì)于博客類系統(tǒng)seo肯定很重要,索性就自己動(dòng)手寫了這個(gè)項(xiàng)目,其中也遇到了不少問(wèn)題,...

    xiaoxiaozi 評(píng)論0 收藏0
  • vue+node支持服務(wù)端渲染博客系統(tǒng)

    摘要:此項(xiàng)目我會(huì)長(zhǎng)期更新,希望能和大家一起學(xué)習(xí),共同進(jìn)步更新本項(xiàng)目的版本基于開(kāi)發(fā),后端用進(jìn)行了重寫。 感悟 歷時(shí)兩個(gè)多月,終于利用工作之余完成了這個(gè)項(xiàng)目的1.0版本,為什么要寫這個(gè)項(xiàng)目?其實(shí)基于vuejs+nodejs構(gòu)建的開(kāi)源博客系統(tǒng)有很多,但是大多數(shù)不支持服務(wù)端渲染,也不支持動(dòng)態(tài)標(biāo)題,只是做到了前后端分離,對(duì)于博客類系統(tǒng)seo肯定很重要,索性就自己動(dòng)手寫了這個(gè)項(xiàng)目,其中也遇到了不少問(wèn)題,...

    solocoder 評(píng)論0 收藏0
  • 前端學(xué)習(xí)資源匯總

    摘要:建立該倉(cāng)庫(kù)的目的主要是整理收集學(xué)習(xí)資源,統(tǒng)一管理,方便隨時(shí)查找。目前整合的學(xué)習(xí)資源只是前端方向的,可能會(huì)存在漏缺比較好的資源,需要慢慢的完善它,歡迎在該上補(bǔ)充資源或者提供寶貴的建議。 說(shuō)明 平時(shí)的學(xué)習(xí)資源都比較的凌亂,看到好的資源都是直接收藏在瀏覽器的收藏夾中,這樣其實(shí)并不方便,整理在云筆記上,也不方便查看修改記錄,索性就整理在 github 上并開(kāi)源出來(lái),希望幫助大家能夠更快的找到需...

    SnaiLiu 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

wh469012917

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<