Vue.js实践:一个Node.js+mongoDB+Vue.js的博客内容管理系统

项目来源
之前曾用过WordPress搭建本身的博客网站,但感受WordPress非常臃肿。因此一直想本身写一个博客内容管理器。javascript

正好近日看完了Vue各个插件的文档,就用着Vue尝试写了这个简约的博客内容管理器(CMS)。css

完成的功能html

  • 一个基本的博客内容管理器功能,如后台登录,发布并管理文章等 支持markdown语法编辑
  • 支持代码高亮
  • 能够管理博客页面的连接
  • 博客页面对移动端适配优化
  • 帐户管理(修改密码)
  • 页面足够大气、酷炫~

因而,为了彰显一向的酷炫,我给后台写了一套星空主题。。。前端

登录页面vue

这里写图片描述

后台管理页面java

这里写图片描述
但博客页面最后没用后台的星空主题,主要是以为黑色不太好搭配。node

因而我想,既然前端都是用Vue.js写的,那就参chao考xi一下Vue.js尤雨溪的博客样式吧!webpack

在这基础上博客页面又加了本身写的canvas动画,应该是足够优雅了~
这里写图片描述git

Demogithub

登录后台按钮在页面最下方“站长登录”,能够以游客身份登入后台系统。

源码

用到的技术和实现思路
前端:Vue全家桶

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

后端

Node.js
mongoDB (mongoose) Express 

工具和语言

Webpack
ES6
SASS
Jade

总体思路

Node服务端除了主页外,不作模板渲染,渲染交给浏览器完成
Node服务端不作任何路由切换的内容,这部分交给Vue-Router完成
Node服务端只用来接收请求,查询数据库并用来返回值

因此这样作先后端几乎彻底解耦,只要约定好restful风格的数据接口,和数据存取格式就OK啦。

后端我用了mongoDB作数据库,并在Express中经过mongoose操做mongoDB,省去了复杂的命令行,经过Javascript操做无疑方便了不少。

简单的说一下Vue的各个插件:

Vue-Cli:官方的脚手架,用来初始化项目
Vue-Resource:能够看做一个Ajax库,经过在跟组件引入,能够方便的注入子组件。子组件以this.$http调用
Vue-Validator:用来验证表单
Vue-Router:官方的路由工具,用来切换子组件,是用来作SPA应用的关键
Vuex:控制组件中数据的流动,使得数据流动更加清晰,有迹可循。经过官方的vue-devtools能够无缝对接
Vue-loader:webpack中对Vue文件的加载器

文件目录
这里写图片描述
我将前端的文件统一放到了src目录下,其中的mian.js是webpack的入口。

全部页面分割成一个单一的vue组件,放在componentss中,经过入口文件mian.js,由webpack打包生成,生成的文件放在public文件夹下。

后端文件放在server文件夹内,这就是基于Express的node服务器,在server文件夹内执行

node www

就能够启动Node服务器,默认侦听3000端口。
关于Vue-Cli
我只是使用了simple的template,相对于默认的template,simple的配置简单得多,但已经有了浏览器自动刷新,生产环境代码压缩等功能。

vue init simple CMS-of-Blog

如下是Vue-Cli生成的webpack的配置文件,我只作了一点小改动。

话说React.js里就没用像Vue-Cli这样好用的脚手架,仍是Vue.js简单优雅。。

Webpack.config.js

var path = require('path')
var webpack = require('webpack')

module.exports = {
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, './public'),
        publicPath: '/public/',
        filename: 'build.js',
    },
    resolveLoader: {
        root: path.join(__dirname, 'node_modules'),
    },
    module: {
        loaders: [
            {
                test: /\.vue$/,
                loader: 'vue'
            },
            {
                test: /\.js$/,
                loader: 'babel',
                exclude: /node_modules/
            },
            {
                test: /\.json$/,
                loader: 'json'
            },
            {
                test: /\.html$/,
                loader: 'vue-html'
            },
            {
                test: /\.(png|jpg|gif|svg)$/,
                loader: 'url',
                query: {
                    limit: 10000,
                    name: '[name].[ext]?[hash]'
                }
            }
            , {
                test: /\.(woff|svg|eot|ttf)\??.*$/,
                loader: 'url-loader?limit=50000&name=[path][name].[ext]'
            }
        ]
    },
    babel: {
        presets: ['es2015'],
    },
    devServer: {
        historyApiFallback: true,
        noInfo: true
    },
    devtool: '#eval-source-map'
}

if (process.env.NODE_ENV === 'production') {
    module.exports.devtool = '#source-map'
    // http://vue-loader.vuejs.org/en/workflow/production.html
    module.exports.plugins = (module.exports.plugins || []).concat([
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: '"production"'
            }
        }),
        new webpack.optimize.UglifyJsPlugin({
            output: {
                comments: false,
            },
            compress: {
                warnings: false
            }
        }),
        new webpack.optimize.OccurenceOrderPlugin()
    ])
}

能够看出尤大大帮咱们把浏览器自动刷新,热加载和生产环境的代码压缩都写好了,简直超贴心。

然而实际项目中,我仍是碰到一个麻烦的问题,下面是package.json中的script脚本

"scripts": {
    "dev": "webpack-dev-server --inline --hot",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
    "watch": "webpack --progress --color --watch",
    "server": "supervisor ./server/www"
  },

运行

npm run dev

后,浏览器在8080端口开了一个服务器,然而这个服务器是用来服务前端页面的,也就是说,从这里启动服务器而不是开启Node服务器会形成数据没法交互,毕竟这个服务器不能链接数据库。但这个端口是能够在文件修改以后自动刷新浏览器的。

而后,经过分别执行

npm run watch
npm run server

来侦听文件改动,并重启Node服务器,此时浏览器是不能自动刷新的。找了一些方法但终归没解决。习惯了自动浏览器刷新,碰到这种状况蛮蛋疼的。

因而只好这样:

在修改样式的时候使用8080端口的服务器,在修改数据交互的时候手动刷新在3000端口服务器的浏览器。

虽然不是很方便,但至少经过supervisor不用本身重启Node服务器了。。。

关于Vue-Router
由于写的是但也应用(SPA),服务器不负责路由,因此路由方面交给Vue-Router来控制。

下面是根组件,路由控制就在这里,组件挂载在body元素下:

main.js

let router = new VueRouter()

router.map({
    '/': {
        component: Archive
    },
    'login': {
        component: Login
    },
    '/article': {
        component: Article
    },
    '/console': {
        component: Console,
        subRoutes: {
            '/': {
                component: ArticleList
            },
            '/editor': {
                component: Editor
            },
            '/articleList': {
                component: ArticleList
            },
            '/menu': {
                component: Links
            },
            'account': {
                component: Account
            },
        },
    },
})
let App = Vue.extend({
    data(){
        return {}
    },
    components: {Waiting,Pop,NightSky,MyCanvas},
    http: {
        root: '/'
    },
    computed: {
        waiting: ()=>store.state.waiting,
        pop:()=>store.state.popPara.pop,
        bg:()=>store.state.bg,
    },
    store
})

router.start(App, 'body')

对应的文档首页 index.html

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <title>Blog-CMS</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <waiting v-if="waiting"></waiting>
    <pop v-show="pop"></pop>
    <component :is="bg"></component>
    <router-view></router-view>
    <script src="public/build.js"></script>
  </body>
</html>

能够看到路由控制在body元素下的router-view中。以前的waiting,pop和component元素分别是等待效果(就是转圈圈)的弹出层,信息的弹出层,和背景样式的切换。

其实这个index.html是有express经过jade生成的,实际项目中并无html文件,我是把生成好的html放在这里方便展现。

关于Vue-loader
Vue-loader是Vue官方支持webpack的工具,用来将组件写在一个文件里。以前的目录中,有不少分割好的vue文件,每个文件是一个独立的组件。

好比,这是一个弹出层的组件:

Pop.vue

<template> <div class="shade"> <div class="content"> <p>{{getPopPara.content}}</p> <div class="button"> <button class="ok" @click="ok">肯定</button> <button class="cancel" @click="cancel" v-if="getPopPara.cb2">取消 </button> </div> </div> </div> </template> <script> import {getPopPara} from '../vuex/getters' export default{ vuex: { getters: { getPopPara, } }, methods: { ok(){ let fn = this.getPopPara.cb1 typeof fn == 'function' && fn() }, cancel(){ let fn = this.getPopPara.cb2 typeof fn == 'function' && fn() } } } </script> <style lang="sass"> @import "../SCSS/Pop.scss"; </style> 

每个vue文件都有三个部分(实际上是可选的),分别是template,script和style,这也很好理解,就是把html,JS和CSS合并在一块儿写了嘛。

这个弹窗组件,经过vuex得到其余组件传递过来的参数,参数是一个对象,包括弹出层的展现信息和点击肯定或取消时的回调函数。

由于编辑器不支持在vue文件中用sass语法,因此我把sass文件放在外部,经过@import引入。

关于Vue-Resource
Vue-Resource能够当作一个与Vue集成的Ajax库,用来建立xhr和获取xhr的response。

由于和Vue高度集成,因此在vue组件中使用很方便。

Article.vue

<template> <div class="wrap"> <my-header></my-header> <section class="article"> <article class="post-block"> <div class="post-title">{{title}}</div> <div class="post-info">{{date}}</div> <div class="post-content" v-html="content | marked"> </div> </article> </section> <my-footer></my-footer> </div> </template> <script> import myHeader from './MyHeader.vue' import myFooter from './MyFooter.vue' import marked from '../js/marked.min.js' import {bgToggle} from '../vuex/actions' export default{ data(){ return { title: '', date: '', content: '' } }, filters: { marked }, created(){ let id = this.$route.query.id this.$http.get('/article?id=' + id) .then((response)=> { let body = JSON.parse(response.body) this.content = body.content this.title = body.title let d = new Date(body.date) this.date = d.getFullYear() + '年' + (d.getMonth() + 1) + '月' + d.getDate() + '日' }, (response)=> { console.log(response) }) }, components: { myHeader, myFooter }, ready(){ this.bgToggle('MyCanvas') }, vuex:{ actions:{ bgToggle } } } </script> <style lang="sass"> @import "../SCSS/Article.scss"; </style> 

Article.vue组件种在created生命周期时建立并发送了一个xhr的get请求,在获取成果后把response对象中的属性在赋值给data中相应的属性,vue会自动更新视图。

博客所支持的markdown语法的关键所在也在这个组件里。

<div class="post-content"> {{{content | marked}}} </div>

经过引入markdown的filter使得输出的html直接被转换成html结构,仍是很方便的。

关于后端
后端是用node.js做为服务器的,使用了最流行Express框架。

主体是由Express生成,自己十分精简。在实践中修改的地方主要是添加了各类前端发送的get和post请求。

router.get('/article', function (req, res, next) {
    var id = req.query.id
    db.Article.findOne({_id: id}, function (err, doc) {
        if (err) {
            return console.log(err)
        } else if (doc) {
            res.send(doc)
        }
    })
})

好比这个请求处理来自前端的get请求,经过mongoose来查询数据库并返回数据。

前端页面经过promise控制异步操做,把获得的数据放入组件的data对象中,Vue侦测变化并更新视图。

数据库的初始化文件放在了init.js中,第一次运行的时候会新建名为admin的用户,初始密码为111,能够在控制台的帐号管理中修改。

后记
其实还有不少不少没有在这篇文章说起的地方。毕竟这个博客框架相对是比较大的东西。

写过这个博客管理器后,感觉仍是蛮多的,对Vue.js中的数据绑定,组件化和数据流了解的更深刻了一层,同时也对Node.js的后端有了一次优雅的实践。

因此,学过东西以后,实践是很是有必要的。前端不少时候就是不断踩坑的过程。一路踩坑再爬坑,获益匪浅。。。

相关文章
相关标签/搜索