之前曾用過WordPress搭建本身的博客網站,但感受WordPress非常臃腫。因此一直想本身寫一個博客內容管理器。css
正好近日看完了Vue各個插件的文檔,就用着Vue嘗試寫了這個簡約的博客內容管理器(CMS)。html
一個基本的博客內容管理器功能,如後臺登錄,發佈並管理文章等前端
支持markdown
語法實時編輯vue
支持代碼高亮node
管理博客頁面的連接webpack
博客頁面對移動端適配優化git
帳戶管理(修改密碼)github
登錄後臺按鈕在頁面最下方「站長登錄」,能夠以遊客身份登入後臺系統。web
Vue.jsvue-router
Vue-Cli
Vue-Resource
Vue-Router
Vuex
Node.js
mongoDB (mongoose)
Express
Webpack
ES6
SASS
Node
服務端不作路由切換,這部分交給Vue-Router
完成
Node
服務端只用來接收請求,查詢數據庫並用來返回值
因此這樣作先後端幾乎徹底解耦,只要約定好restful數據接口,和數據存取格式就OK啦。
後端我用了mongoDB
作數據庫,並在Express
中經過mongoose
操做mongoDB,省去了複雜的命令行,經過Javascript操做無疑方便了不少。
vue-cli
:官方的腳手架,用來初始化項目
vue-resource
:能夠看做一個Ajax
庫,經過在跟組件引入,能夠方便的注入子組件。子組件以this.$http
調用
vue-router
:官方的路由工具,用來切換子組件,是用來作SPA應用的關鍵
vuex
:規範組件中數據流動,主要用於異步的http
請求後數據的刷新。經過官方的vue-devtools
能夠無縫對接
│ .babelrc babel配置 │ .editorconfig │ .eslintignore │ .eslintrc.js eslintrc配置 │ .gitignore │ index.html 入口頁面 │ package.json │ README.md │ setup.html 初始化帳戶頁面 │ webpack.config.js webpack配置 │ ├─dist 打包生成 │ ├─server 服務端 │ api.js Restful接口 │ db.js 數據庫 │ index.js │ init.json 初始數據 │ └─src │ main.js 項目入口 │ setup.js 初始化帳戶 │ ├─assets 外部引用文件 │ ├─css │ ├─fonts │ ├─img │ └─js │ ├─components vue組件 │ ├─back 博客控制檯組件 │ ├─front 博客頁面組件 │ └─share 公共組件 │ ├─router 路由 │ ├─store vuex文件 │ └─style 全局樣式
前端的文件統一放到了src
目錄下,有兩個入口文件,分別是main.js
和setup.js
,有過WordPress
經驗應該知道,第一次進入博客是須要設置用戶名密碼和數據庫的,這裏的setup.js
就是第一次登入時的頁面腳本,而main.js
則是剩餘全部文件的入口
main.js
import Vue from 'vue' import VueResource from 'vue-resource' import {mapState} from 'vuex' //三個頂級組件,博客主頁和控制檯共享 import Spinner from './components/share/Spinner.vue' import Toast from './components/share/Toast.vue' import MyCanvas from './components/share/MyCanvas.vue' import store from './store' import router from './router' import './style/index.scss' Vue.use(VueResource) new Vue({ router, store, components: {Spinner, Toast, MyCanvas}, computed: mapState(['isLoading', 'isToasting']) }).$mount('#CMS2')
然後全部頁面分割成一個單一的vue
組件,放在components
中,經過入口文件main.js
,由webpack
打包生成,生成的文件放在dist
文件夾下。
後端文件放在server
文件夾內,這就是基於Express
的node
服務器,在server
文件夾內執行
node index
就能夠啓動Node
服務器,默認偵聽3000
端口。
Webpack的配置文件主體是有vue-cli
生成的,但爲了配合後端自動刷新、支持Sass
和生成獨立的css
文件,稍微修改了一下:
webpack.config.js
const path = require('path') const webpack = require('webpack') const ExtractTextPlugin = require('extract-text-webpack-plugin') const CopyWebpackPlugin = require('copy-webpack-plugin') //萃取css文件,在此命名 const extractCSSFromVue = new ExtractTextPlugin('styles.css') const extractCSSFromSASS = new ExtractTextPlugin('index.css') module.exports = { entry: { main: './src/main.js', setup: './src/setup.js' }, output: { path: path.resolve(__dirname, './dist'), publicPath: '/dist/', filename: '[name].js' }, resolveLoader: { moduleExtensions: ['-loader'] }, module: { rules: [ { test: /\.vue$/, loader: 'vue', //使用postcss處理加工後的scss文件 options: { preserveWhitespace: false, postcss: [ require('autoprefixer')({ browsers: ['last 3 versions'] }) ], loaders: { sass: extractCSSFromVue.extract({ loader: 'css!sass!', fallbackLoader: 'vue-style-loader' }) } } }, { test: /\.scss$/, loader: extractCSSFromSASS.extract(['css', 'sass']) }, { test: /\.js$/, loader: 'babel', exclude: /node_modules/ }, { test: /\.(png|jpg|gif|svg)$/, loader: 'file', options: { name: '[name].[ext]?[hash]' } }, //字體文件 { test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' }, { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'file-loader' } ] }, plugins: [ //取出css生成獨立文件 extractCSSFromVue, extractCSSFromSASS, new CopyWebpackPlugin([ {from: './src/assets/img', to: './'} ]) ], resolve: { alias: { 'vue$': 'vue/dist/vue' } }, //服務器代理,便於開發時全部http請求轉到node的3000端口,而不是前端的8080端口 devServer: { historyApiFallback: true, noInfo: true, proxy: { '/': { target: 'http://localhost:3000/' } } }, devtool: '#eval-source-map' } if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), new webpack.LoaderOptionsPlugin({ minimize: true }) ]) }
運行
npm start
後,node端開啓了3000端口,接着運行
npm run dev
打開webpack在8080端口服務器,具備動態加載的功能,而且全部的http請求會代理到3000端口
由於寫的是但也應用(SPA),服務器不負責路由,因此路由方面交給Vue-Router
來控制。
router.js
import Vue from 'vue' import Router from 'vue-router' //博客頁面 import Archive from '../components/front/Archive.vue' import Article from '../components/front/Article.vue' //控制檯頁面 import Console from '../components/back/Console.vue' import Login from '../components/back/Login.vue' import Articles from '../components/back/Articles.vue' import Editor from '../components/back/Editor.vue' import Links from '../components/back/Links.vue' import Account from '../components/back/Account.vue' Vue.use(Router) export default new Router({ mode: 'history', routes: [ {path: '/archive', name: 'archive', component: Archive}, {path: '/article', name: 'article', component: Article}, {path: '/', component: Login}, { path: '/console', component: Console, children: [ {path: '', component: Articles}, {path: 'articles', name: 'articles', component: Articles}, {path: 'editor', name: 'editor', component: Editor}, {path: 'links', name: 'links', component: Links}, {path: 'account', name: 'account', component: Account} ] } ] })
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>cms2simple</title> <link rel="stylesheet" href="dist/index.css"> <link rel="stylesheet" href="dist/styles.css"> </head> <body> <div id="CMS2" style="height: 100%"> <my-canvas></my-canvas> <spinner v-show="isLoading"></spinner> <Toast v-show="isToasting"></Toast> <router-view ></router-view> </div> <script src="/dist/main.js"></script> </body> </html>
能夠看到路由控制在body
元素下的router-view
中。前面的spinner
,toast
元素分別是等待效果(轉圈圈)的彈出層和信息的彈出層,和背景樣式的切換。
後端是用node.js
做爲服務器的,使用了express
框架。
其中代碼很是簡單:
index.js
const fs = require('fs') const path = require('path') const express = require('express') const favicon = require('serve-favicon') const bodyParser = require('body-parser') const cookieParser = require('cookie-parser') const db = require('./db') const resolve = file => path.resolve(__dirname, file) const api = require('./api') const app = express() // const createBundleRenderer = require('vue-server-renderer').createBundleRenderer app.set('port', (process.env.port || 3000)) app.use(favicon(resolve('../dist/favicon.ico'))) app.use(bodyParser.json()) app.use(bodyParser.urlencoded({extended: false})) app.use(cookieParser()) app.use('/dist', express.static(resolve('../dist'))) app.use(api) app.post('/api/setup', function (req, res) { new db.User(req.body) .save() .then(() => { res.status(200).end() db.initialized = true }) .catch(() => res.status(500).end()) }) app.get('*', function (req, res) { const fileName = db.initialized ? 'index.html' : 'setup.html' const html = fs.readFileSync(resolve('../' + fileName), 'utf-8') res.send(html) }) app.listen(app.get('port'), function () { console.log('Visit http://localhost:' + app.get('port')) })
服務器作的事情很簡單,畢竟路由在前端。在接受請求的時候判斷一下數據庫是否初始化,若是初始化就轉向主頁,不然轉向setup.html
,之因此沒有直接sendfile
是由於考慮到以後添加服務端渲染(雖然主頁並無啥值得渲染的,由於很簡單)
express
框架中使用了mongoose
來鏈接mongoDB
數據庫,在接收請求時作對應的curd
操做,好比這就是在接收保存文章時對應的操做:
api.js
router.post('/api/saveArticle', (req, res) => { const id = req.body._id const article = { title: req.body.title, date: req.body.date, content: req.body.content } if (id) { db.Article.findByIdAndUpdate(id, article, fn) } else { new db.Article(article).save() } res.status(200).end() })
固然還有不少沒說起的地方,最先寫這個博客管理器的時候用的仍是vue 1.x
,後來用2.0
改寫後文檔一直沒改,因此最近更新了一下,避免誤解。
其實整個管理器最複雜的地方時vuex
異步數據視圖的部分,不過這一部能講的太多,就不在這裏展開了,能夠看官方文檔後,參考源代碼的註釋。