当前位置: 首页 > 后端技术 > Node.js

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

时间:2023-04-03 21:00:14 Node.js

项目源码之前用WordPress搭建自己的博客站点,但是感觉WordPress很臃肿。所以一直想自己写一个博客内容管理器。就在最近,看了Vue各种插件的文档,尝试用Vue写了这个简单的博客内容管理器(CMS)。嗯,我要完成的功能:一个基本的博客内容管理器功能,比如后台登录,发布和管理文章等支持markdown语法实时编辑支持代码高亮管理博客页面链接博客页面适配移动端优化账号管理(修改密码)Demo登录后台按钮在“站长登录”页面底部,您可以游客身份登录后台系统。源码中使用的技术和实现思路:前端:Vue全家桶Vue.jsVue-CliVue-ResourceVue-RouterVuex后端:NodeNode.jsmongoDB(mongoose)Express工具和语言WebpackES6SASS总体思路:Nodeserver不做路由切换,这部分交给Vue-Router来完成,Node服务器只是用来接收请求,查询数据库,并用它来返回值,所以这样前后端几乎完全解耦了,只要restful数据接口和数据访问格式都约定好了,就OK了。我使用mongoDB作为后台数据库,在Express中通过mongoose操作mongoDB,省去了复杂的命令行,通过Javascript操作无疑方便很多。Vue的各种插件:vue-cli:官方脚手架,用于初始化项目vue-resource:可以看做是一个Ajax库,在后续组件中引入,可以方便的注入到子组件中。子组件用this调用vue-router。$http:官方路由工具,用于切换子组件,是SPA应用的关键。vuex:调节组件中的数据流,主要用于异步http请求后的数据刷新。官方vue-devtools可以无缝对接文件目录│.babelrcbabel配置│.editorconfig│.eslintignore│.eslintrc.jseslintrc配置│.gitignore│index.html入口页面│package.json│README.md│setup。html初始化账号页面│webpack.config.jswebpack配置│├─dist包生成│├─server服务器│api.jsRestful接口│db.js数据库│index.js│init.json初始数据│└─src│main.js项目入口│setup.js初始化账号│├─assets外部引用文件│├─css│├─fonts│├─img│└─js│├─componentsvue组件│├─back博客控制台组件│├─front博客页面组件│└─共享公共组件│├─router路由│├─storevuex文件│└─style全局样式前端文件放在src目录下,有两个入口文件,分别是main.js和setup。js,如果你有WordPress经验,应该知道第一次进入博客需要设置用户名、密码和数据库。这里的setup.js是第一次登录时的页面脚本,main.js是剩下的所有文件。条目main.jsimportVuefrom'vue'importVueResourcefrom'vue-resource'import{mapState}from'vuex'//三个顶层组件,博客主页和控制台分享'从'./components/share/MyCanvas.vue'导入MyCanvas'从'./store'导入商店'从'./router'导入路由器'./style/index.scss'Vue.use(VueResource)newVue({router,store,components:{Spinner,Toast,MyCanvas},computed:mapState(['isLoading','isToasting'])}).$mount('#CMS2')然后将所有页面拆分为单个vue组件,放在components中,通过入口文件main.js,通过webpack打包生成,生成的文件放在dist文件夹中,后端文件放在server文件夹中。这是基于Express的node服务器,在server文件夹下执行nodeindex启动Node服务器,默认监听3000端口。Webpack配置文件主体由vue-cli生成,但为了配合后台自动刷新,支持Sass和生成独立的css文件,略作修改:webpack.config.jsconstpath=require('path')constwebpack=require('webpack')constExtractTextPlugin=require('extract-text-webpack-plugin')constCopyWebpackPlugin=require('copy-webpack-plugin')//提取css文件,命名这里constextractCSSFromVue=newExtractTextPlugin('styles.css')constextractCSSFromSASS=newExtractTextPlugin('index.css')module.exports={entry:{main:'./src/main.js',setup:'./src/setup.js'},output:{path:path.resolve(__dirname,'./dist'),publicPath:'/dist/',文件名:'[name].js'},resolveLoader:{moduleExtensions:['-loader']},module:{rules:[{test:/\.vue$/,loader:'vue',//使用postcss处理处理后的scss文件options:{preserveWhitespace:false,postcss:[require('autoprefixer')({browsers:['last3versions']})],加载ers:{sass:extractCSSFromVue.extract({loader:'css!sass!',fallbackLoader:'vue-style-loader'})}}},{test:/\.scss$/,加载程序: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'}]},插件:[//取出css生成独立文件extractCSSFromVue,extractCSSFromSASS,newCopyWebpackPlugin([{from:'./src/assets/img',to:'./'}])],resolve:{alias:{'vue$':'vue/dist/vue'}},//服务器代理,让开发时所有的http请求都走node的3000端口,而不是前端的8080端口devServer:{historyApiFallback:true,noInfo:真,代理:{'/':{目标:'http://localhost:3000/'}}},devtool:'#eval-source-map'}if(process.env.NODE_ENV==='production'){模块。exports.devtool='#source-map'module.exports.plugins=(module.exports.plugins||[]).concat([newwebpack.DefinePlugin({'process.env':{NODE_ENV:'"production"'}}),newwebpack.optimize.UglifyJsPlugin({compress:{warnings:false}}),newwebpack.LoaderOptionsPlugin({minimize:true})])}运行npmstart后,节点端3000端口开启,然后运行npmrundev在8080端口开启webpackserver,具有动态加载的功能,所有的http请求都会代理到3000端口。关于Vue-Router,因为写的也是应用(SPA),服务器不负责路由,所以路由交给Vue-Router来控制router.jsimportVuefrom'vue'importRouterfrom'vue-router'//博客页面importArchivefrom'../components/front/Archive.vue'importArticlefrom'../components/front/Article.vue'//控制面板importConsolefrom'../components/back/Console.vue'importLoginfrom'../components/back/Login.vue'importArticlesfrom'../components/back/Articles.vue'从'../components/back/Editor.vue'导入编辑器从'../components/back/Links.vue'导入链接从'../components/back/Account.vue'导入帐户Vue.use(路由器)exportdefaultnewRouter({mode:'history',routes:[{path:'/archive',name:'archive',component:Archive},{path:'/article',name:'article',component:文章},{路径:'/',组件:登录},{路径:'/console',组件:控制台,子项:[{路径:'',组件:文章},{路径:'文章',名称:'文章',组件:文章},{路径:'编辑器',名称:'编辑器',组件:编辑or},{path:'links',name:'links',component:Links},{path:'account',name:'account',component:Account}]}]})文档首页index.htmlcms2simple<正文>

可以看到路由控件在下面body元素router-view中最前面的spinner和toast元素分别是等待效果(循环)的弹出层、信息的弹出层、背景样式的切换。关于后端后端使用node.js作为服务端,使用express框架。其中代码非常简单:index.jsconstfs=require('fs')constpath=require('path')constexpress=require('express')constfavicon=require('serve-favicon')constbodyParser=require('body-parser')constcookieParser=require('cookie-parser')constdb=require('./db')constresolve=file=>path.resolve(__dirname,file)constapi=require('./api')constapp=express()//constcreateBundleRenderer=require('vue-server-renderer').createBundleRendererapp.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){newdb.User(req.body).save().then(()=>{res.status(200).end()db.initialized=true}).catch(()=>res.status(500).end())})app.get('*',function(req,res){constfileName=db.initia利兹?'index.html':'setup.html'consthtml=fs.readFileSync(resolve('../'+fileName),'utf-8')res.send(html)})app.listen(app.get('port'),function(){console.log('访问http://localhost:'+app.get('port'))})server做的事情很简单,毕竟接受了路由在前端请求时,判断数据库是否初始化。如果已初始化,则转到首页,否则转到setup.html。之所以没有直接sendfile是因为考虑后面加上服务端渲染(虽然首页没有什么值得渲染的,因为很简单)express框架使用mongoose连接mongoDB数据库,并执行相应的接收请求时进行curd操作。比如接收和保存文章时对应的操作是这样的:api.jsrouter.post('/api/saveArticle',(req,res)=>{constid=req.body._idconstarticle={title:req.body.title,日期:req.body.date,内容:req.body.content}if(id){db.Article.findByIdAndUpdate(id,article,fn)}else{newdb.Article(article).save()}res.status(200).end()})后记当然还有很多地方没有提到,第一个博客管理员我当时用的是vue1.x,重写后文档一直没改与2.0,所以最近更新了以避免误解。其实整个manager最复杂的就是vuex异步数据视图的部分,不过这部分可以说的太多了,这里就不展开了。阅读官方文档后可以参考源码注释。