摘要:項(xiàng)目來源以前曾用過搭建自己的博客網(wǎng)站,但感覺很是臃腫。所以一直想自己寫一個(gè)博客內(nèi)容管理器。正好近日看完了各個(gè)插件的文檔,就用著嘗試寫了這個(gè)簡約的博客內(nèi)容管理器。關(guān)于后端后端是用作為服務(wù)器的,使用了框架。
項(xiàng)目來源
以前曾用過WordPress搭建自己的博客網(wǎng)站,但感覺WordPress很是臃腫。所以一直想自己寫一個(gè)博客內(nèi)容管理器。
正好近日看完了Vue各個(gè)插件的文檔,就用著Vue嘗試寫了這個(gè)簡約的博客內(nèi)容管理器(CMS)。
嗯,我想完成的功能:一個(gè)基本的博客內(nèi)容管理器功能,如后臺(tái)登陸,發(fā)布并管理文章等
支持markdown語法實(shí)時(shí)編輯
支持代碼高亮
管理博客頁面的鏈接
博客頁面對(duì)移動(dòng)端適配優(yōu)化
賬戶管理(修改密碼)
Demo登陸后臺(tái)按鈕在頁面最下方“站長登陸”,可以以游客身份登入后臺(tái)系統(tǒng)。
源碼 用到的技術(shù)和實(shí)現(xiàn)思路: 前端:Vue全家桶Vue.js
Vue-Cli
Vue-Resource
Vue-Router
Vuex
后端:NodeNode.js
mongoDB (mongoose)
Express
工具和語言Webpack
ES6
SASS
整體思路:Node服務(wù)端不做路由切換,這部分交給Vue-Router完成
Node服務(wù)端只用來接收請(qǐng)求,查詢數(shù)據(jù)庫并用來返回值
所以這樣做前后端幾乎完全解耦,只要約定好restful數(shù)據(jù)接口,和數(shù)據(jù)存取格式就OK啦。
后端我用了mongoDB做數(shù)據(jù)庫,并在Express中通過mongoose操作mongoDB,省去了復(fù)雜的命令行,通過Javascript操作無疑方便了很多。
Vue的各個(gè)插件:vue-cli:官方的腳手架,用來初始化項(xiàng)目
vue-resource:可以看作一個(gè)Ajax庫,通過在跟組件引入,可以方便的注入子組件。子組件以this.$http調(diào)用
vue-router:官方的路由工具,用來切換子組件,是用來做SPA應(yīng)用的關(guān)鍵
vuex:規(guī)范組件中數(shù)據(jù)流動(dòng),主要用于異步的http請(qǐng)求后數(shù)據(jù)的刷新。通過官方的vue-devtools可以無縫對(duì)接
文件目錄│ .babelrc babel配置 │ .editorconfig │ .eslintignore │ .eslintrc.js eslintrc配置 │ .gitignore │ index.html 入口頁面 │ package.json │ README.md │ setup.html 初始化賬戶頁面 │ webpack.config.js webpack配置 │ ├─dist 打包生成 │ ├─server 服務(wù)端 │ api.js Restful接口 │ db.js 數(shù)據(jù)庫 │ index.js │ init.json 初始數(shù)據(jù) │ └─src │ main.js 項(xiàng)目入口 │ setup.js 初始化賬戶 │ ├─assets 外部引用文件 │ ├─css │ ├─fonts │ ├─img │ └─js │ ├─components vue組件 │ ├─back 博客控制臺(tái)組件 │ ├─front 博客頁面組件 │ └─share 公共組件 │ ├─router 路由 │ ├─store vuex文件 │ └─style 全局樣式
前端的文件統(tǒng)一放到了src目錄下,有兩個(gè)入口文件,分別是main.js和setup.js,有過WordPress經(jīng)驗(yàn)應(yīng)該知道,第一次進(jìn)入博客是需要設(shè)置用戶名密碼和數(shù)據(jù)庫的,這里的setup.js就是第一次登入時(shí)的頁面腳本,而main.js則是剩余所有文件的入口
main.jsimport Vue from "vue" import VueResource from "vue-resource" import {mapState} from "vuex" //三個(gè)頂級(jí)組件,博客主頁和控制臺(tái)共享 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")
而后所有頁面分割成一個(gè)單一的vue組件,放在components中,通過入口文件main.js,由webpack打包生成,生成的文件放在dist文件夾下。
后端文件放在server文件夾內(nèi),這就是基于Express的node服務(wù)器,在server文件夾內(nèi)執(zhí)行
node index
就可以啟動(dòng)Node服務(wù)器,默認(rèn)偵聽3000端口。
?關(guān)于 WebpackWebpack的配置文件主體是有vue-cli生成的,但為了配合后端自動(dòng)刷新、支持Sass和生成獨(dú)立的css文件,稍微修改了一下:
webpack.config.jsconst 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生成獨(dú)立文件 extractCSSFromVue, extractCSSFromSASS, new CopyWebpackPlugin([ {from: "./src/assets/img", to: "./"} ]) ], resolve: { alias: { "vue$": "vue/dist/vue" } }, //服務(wù)器代理,便于開發(fā)時(shí)所有http請(qǐng)求轉(zhuǎn)到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 }) ]) }
運(yùn)行
npm start
后,node端開啟了3000端口,接著運(yùn)行
npm run dev
打開webpack在8080端口服務(wù)器,具有動(dòng)態(tài)加載的功能,并且所有的http請(qǐng)求會(huì)代理到3000端口
關(guān)于Vue-Router因?yàn)閷懙氖堑矐?yīng)用(SPA),服務(wù)器不負(fù)責(zé)路由,所以路由方面交給Vue-Router來控制。
router.jsimport Vue from "vue" import Router from "vue-router" //博客頁面 import Archive from "../components/front/Archive.vue" import Article from "../components/front/Article.vue" //控制臺(tái)頁面 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 ?
cms2simple
可以看到路由控制在body元素下的router-view中。前面的spinner,toast元素分別是等待效果(轉(zhuǎn)圈圈)的彈出層和信息的彈出層,和背景樣式的切換。
關(guān)于后端后端是用node.js作為服務(wù)器的,使用了express框架。
其中代碼非常簡單:
index.jsconst 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")) })
服務(wù)器做的事情很簡單,畢竟路由在前端。在接受請(qǐng)求的時(shí)候判斷一下數(shù)據(jù)庫是否初始化,如果初始化就轉(zhuǎn)向主頁,否則轉(zhuǎn)向setup.html,之所以沒有直接sendfile是因?yàn)榭紤]到之后添加服務(wù)端渲染(雖然主頁并沒有啥值得渲染的,因?yàn)楹芎唵危?/p>
express框架中使用了mongoose來連接mongoDB數(shù)據(jù)庫,在接收請(qǐng)求時(shí)做對(duì)應(yīng)的curd操作,比如這就是在接收保存文章時(shí)對(duì)應(yīng)的操作:
api.jsrouter.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() })后記
當(dāng)然還有很多沒提及的地方,最早寫這個(gè)博客管理器的時(shí)候用的還是vue 1.x,后來用2.0改寫后文檔一直沒改,所以最近更新了一下,避免誤解。
其實(shí)整個(gè)管理器最復(fù)雜的地方時(shí)vuex異步數(shù)據(jù)視圖的部分,不過這一部能講的太多,就不在這里展開了,可以看官方文檔后,參考源代碼的注釋。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/80406.html
摘要:利用中間件實(shí)現(xiàn)異步請(qǐng)求,實(shí)現(xiàn)兩個(gè)用戶角色實(shí)時(shí)通信。目前還未深入了解的一些概念。往后會(huì)寫更多的前后臺(tái)聯(lián)通的項(xiàng)目。刪除分組會(huì)連同組內(nèi)的所有圖片一起刪除。算是對(duì)自己上次用寫后臺(tái)的一個(gè)強(qiáng)化,項(xiàng)目文章在這里。后來一直沒動(dòng),前些日子才把后續(xù)的完善。 歡迎訪問我的個(gè)人網(wǎng)站:http://www.neroht.com/? 剛學(xué)vue和react時(shí),利用業(yè)余時(shí)間寫的關(guān)于這兩個(gè)框架的訓(xùn)練,都相對(duì)簡單,有的...
摘要:三更新內(nèi)容在原來項(xiàng)目的基礎(chǔ)上,做了如下更新數(shù)據(jù)庫重新設(shè)計(jì),改成以用戶分組的數(shù)據(jù)庫結(jié)構(gòu)應(yīng)數(shù)據(jù)庫改動(dòng),所有接口重新設(shè)計(jì),并統(tǒng)一采用和網(wǎng)易立馬理財(cái)一致的接口風(fēng)格刪除原來游客模式,增加登錄注冊功能,支持彈窗登錄。 這個(gè)項(xiàng)目最初其實(shí)是fork別人的項(xiàng)目。當(dāng)初想接觸下mongodb數(shù)據(jù)庫,找個(gè)例子學(xué)習(xí)下,后來改著改著就面目全非了。后臺(tái)和數(shù)據(jù)庫重構(gòu),前端增加了登錄注冊功能,僅保留了博客設(shè)置頁面,但是...
摘要:開發(fā)一個(gè)完整博客流程前言前段時(shí)間剛把自己的個(gè)人網(wǎng)站寫完,于是這段時(shí)間因?yàn)槭虑椴皇翘啵阏砹艘幌?,寫了個(gè)簡易版的博客系統(tǒng)服務(wù)端用的是框架進(jìn)行開發(fā)技術(shù)棧目錄結(jié)構(gòu)講解的配置文件放置代碼文件項(xiàng)目參數(shù)配置的文件日志打印文件項(xiàng)目依賴模塊 Vue + Node + Mongodb 開發(fā)一個(gè)完整博客流程 前言 前段時(shí)間剛把自己的個(gè)人網(wǎng)站寫完, 于是這段時(shí)間因?yàn)槭虑椴皇翘啵阏砹艘幌?,寫了個(gè)簡易...
摘要:建立該倉庫的目的主要是整理收集學(xué)習(xí)資源,統(tǒng)一管理,方便隨時(shí)查找。目前整合的學(xué)習(xí)資源只是前端方向的,可能會(huì)存在漏缺比較好的資源,需要慢慢的完善它,歡迎在該上補(bǔ)充資源或者提供寶貴的建議。 說明 平時(shí)的學(xué)習(xí)資源都比較的凌亂,看到好的資源都是直接收藏在瀏覽器的收藏夾中,這樣其實(shí)并不方便,整理在云筆記上,也不方便查看修改記錄,索性就整理在 github 上并開源出來,希望幫助大家能夠更快的找到需...
閱讀 1013·2021-11-22 09:34
閱讀 2201·2021-11-11 16:54
閱讀 2226·2021-09-27 14:00
閱讀 965·2019-08-30 15:55
閱讀 1555·2019-08-29 12:46
閱讀 630·2019-08-26 18:42
閱讀 657·2019-08-26 13:31
閱讀 3209·2019-08-26 11:52