会安装node,搭建node环境javascript
会运行node。css
Buffer:二进制数据处理模块html
Event:事件模块前端
fs:文件系统模块java
Net:网络模块node
Http:http模块jquery
...git
第三方node模块(包)的管理工具,可使用该下载工具安装第三方模块。,固然也能够建立上传本身的模块。github
假定已经理解并掌握了入门教程的全部内容。在易出错的地方将进行简要的说明。web
这是最不起眼,但也是最必不可少的——你得准备一个博客的静态文件。
博客的后台界面,登陆注册界面,文章展现界面,首页等。
一个博客应当具有哪些功能?
本项目采用了如下核心技术:
Node版本:6.9.1——基础核心的开发语言
(安装后查看版本:cmd窗口:node -v)
(查看方式:cmd窗口:node -v
)
Express
一个简洁灵活的node.js WEB应用框架,提供一系列强大的特性帮助咱们建立web应用。
Mongodb
用于保存产生的数据
还有一系列第三方模块和中间件:
...
在W ebStorm建立一个新的空工程,指定文件夹。
打开左下角的Terminal输入:
npm init
回车。而后让你输入name:(code),输入项目名称,而后后面均可以不填,最后在Is it OK?
处写上yes。
完成这一步操做以后,系统就会在当前文件夹建立一个package.json
的项目文件。
项目文件下面拥有刚才你所基本的信息。后期须要更改的话可直接在这里修改。
以Express为例
在命令行输入:
npm install --save express
耐心等待一段时间,安装完成后,json文件夹追加了一些新的内容:
{ //以前内容........ "author": "", "license": "ISC", "dependencies": { "express": "^4.14.0" }
表示安装成功。
同理,使用npm install --save xxx
的方法安装下载如下模块:
因此安装完以后的package.json文件是这样的。
{ "name": "blog", "version": "1.0.0", "description": "this is my first blog.", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "dependencies": { "body-parser": "^1.15.2", "cookies": "^0.6.2", "express": "^4.14.0", "markdown": "^0.5.0", "mongoose": "^4.7.5", "swig": "^1.4.2" } }
在这个json中,就能经过依赖模块(dependencies
)看到各个第三方模块的版本信息
切记:依赖模块安装,要联网!
第二个文件夹放的是你的第三方模块。
此外还须要别的文件,完整的结构是这样的——
接下来就把缺失的文件目录本身创建起来。
完成着一系列操做以后,就把app.js做为应用程序的启动(入口页面)。
如下代码建立应用,监听端口
// 加载express var express=require('express'); //建立app应用,至关于=>Node.js Http.createServer(); var app=express(); //监听http请求 app.listen(9001);
运行(ctrl
+shift
+c
)以后就能够经过浏览器访问了。
用户访问:
http://localhost:9001/
这时候会发现浏览器呈现的内容是这样的。
web后端根据用户访问的url处理不一样的业务逻辑。
路由绑定——
在Express框架下,能够经过app.get()
或app.post()
等方式,把一个url路径和(1-n)个函数进行绑定。当知足对应的规则时,对应的函数将会被执行,该函数有三个参数——
app.get('/',function(req,res,next){ // do sth. }); // req:request对象,保存客户请求相关的一些数据——http.request // res:response对象,服务端输出对象,停工了一些服务端相关的输出方法——http.response // next:方法,用于执行下一个和路径相匹配的函数(行为)。
内容输出
经过res.send(string)
发送内容到客户端。
app.get('/',function(req,res,next){ res.send('<h1>欢迎光临个人博客!</h1>'); });
运行。这时候网页就打印出了h1标题的内容。
注意,js文件编码若是不为UTF-8,网页文件显示中文会受到影响。
如今,我想向后端发送的内容可不是一个h1标题那么简单。还包括整个博客页面的html内容,若是仍是用上面的方法,麻烦就大了。
怎么办呢?关键步骤在于html和js页面相分离(相似结构和行为层的分离)。
模板的使用在于后端逻辑和前端表现的分离(先后端分离)。
基本配置以下
// 定义模板引擎,使用swig.renderFile方法解析后缀为html的文件 var swig=require('swig'); app.engine('html',swig.renderFile); // 设置模板存放目录 app.set('views','./views'); // 注册模板引擎 app.set('view engine','html'); swig.setDefaults({cache:false});
配置模板的基本流程是:
请求swig模块
=>定义模板引擎
=>注册模板引擎
=>设置调试方法
咱们可使用var swig=require('swig');
定义了swig方法。
如下进行逐行解析——
app.engine('html',swig.renderFile);
第一个参数:模板引擎的名称,同时也是模板引擎的后缀,你能够定义打开的是任何文件格式,好比json,甚至tdl等。
第二个参数表示用于解析处理模板内容的方法。
第三个参数:使用swig.renderFile方法解析后缀为html的文件。
如今就用express组件提供的set方法标设置模板目录:
app.set('views','./views');
定义目录时也有两个参数,注意,第一个参数必须为views
!第二个参数能够是咱们所给出的路径。由于以前已经定义了模板文件夹为views
。因此,使用对应的路径名为./views
。
app.set('view engine','html');
仍是使用express提供了set方法。
第一个参数必须是字符串'view engine'
。
第二个参数和app.engine
方法定义的模板引擎名称(第一个参数)必须是一致的(都是“html”)。
如今咱们回到app.get()方法里面,使用res.render()
方法从新渲染指定内容
app.get('/',function(req,res,next){ /* * 读取指定目录下的指定文件,解析并返回给客户端 * 第一个参数:模板文件,相对于views目录,views/index.html * */ res.render('index'); });
这时候,咱们定义了返回值渲染index文件,就须要在views文件夹下新建立一个index.html
。
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <h1>欢迎来到个人第一个博客!<h1> </body> </html>
render方法还能够接受第二个参数,用于传递模板使用的第二个数据。
好了。这时候再刷新页面,就出现了index的内容。
咱们在不中止服务器的状况下,从新修改index的文件内容,发现并无刷新。
什么问题呢?出于性能上考虑,node把第一次读取的index放到了内容中,下次访问时,就是缓存中的内容了,而不是真正的index文件。所以须要重启。
开发过程当中,为了减小麻烦,须要取消模板缓存。
swig.setDefaults({cache:false});
固然,当项目上线时,能够把这一段删除掉。
在写模板文件时,常常引入一些外链的css,js和图片等等。
若是咱们直接在首页的head区域这么写:
<link rel="stylesheet" type="text/css" href="css.css"/>
再刷新,发现对css.css的引用失败了。
问题不在于css.css是否存在,而在于请求失败。由于外链文件本质也是一个请求,可是在app.js中尚未对应设置。
若是这么写:
app.get('/css.css', function (req,res,next) { res.send('body {background: red;}'); });
发现没有效果。
打开http://localhost:9001/css.css
发现内容是这样的:
搞笑了。默认发送的是一个html。所以须要设定一个header
app.get('/css.css', function (req,res,next) { res.setHeader('content-type','text/css'); res.send('body {background: red;}'); });
ctrl+F5,就解析了红色背景了。
一样的,静态文件须要彻底分离,所以这种方法也是不行的。
最好的方法是,把全部的静态文件都放在一个public的目录下,划分并存放好。
而后在开头就经过如下方法,把public目录下的全部静态文件都渲染了:
app.use('/public',express.static(__dirname+'/public'));
以上方法表示:当遇到public文件下的文件,都调用第二个参数里的方法(注意是两个下划线)。
当用户访问的url以public开始,那么直接返回对应__dirname+'public'
下的文件。所以咱们的css应该放到public下。
引用方式为:
<link rel="stylesheet" type="text/css" href="../public/css.css"/>
而后到public文件下建立一个css.css,设置body背景为红色。原来的app.get方法就不要了。
至此,静态文件什么的均可以用到了
小结
在以上的内容中,咱们实现了初始化项目,能够调用html和css文件。基本过程逻辑是:
用户发送http请求(url)=>解析路由=>找到匹配的规则=>指定绑定函数,返回对应内容到用户。
访问的是public:静态——直接读取指定目录下的文件,返回给用户。
=>动态=>处理业务逻辑
那么整个基本雏形就搭建起来了。
把整个网站放到一个app.js中,是不利于管理和维护的。实际开发中,是按照不一样的功能,管理代码。
根据本项目的业务逻辑,分为三个模块就够了。
或者,使用app.use
(路由设置)划分:
app.use('/admin',require('./routers/admin'));
解释:当用户访问的是admin文件下的内容,这调用router文件夹下admin.js文件。下同。
app.use('/api',require('./routers/api'));
后台
app.use('/',require('./routers/main'));
前台
好了。重写下之前的代码,去掉多余的部分。
// 加载express var express=require('express'); //建立app应用,至关于=>Node.js Http.createServer(); var app=express(); // 设置静态文件托管 app.use('/public',express.static(__dirname+'/public')) // 定义模板引擎,使用swig.renderFile方法解析后缀为html的文件 var swig=require('swig'); app.engine('html',swig.renderFile); // 设置模板存放目录 app.set('views','./views'); // 注册模板引擎 app.set('view engine','html'); // 调试优化 swig.setDefaults({cache:false}); //app.use('/admin',require('./routers/admin')); //app.use('/api',require('./routers/api')); //app.use('/',require('./routers/main')); //监听http请求 app.listen(9001);
在routers
建立一个admin.js,同理再建立一个api.js,一个main.js
好比,我想访问一个如http://localhost:9001/admin/user
这样的地址,这样按理来讲就应该调用admin.js(分路由)。
因此编辑admin.js
var express=require('express'); // 建立一个路由对象,此对象将会监听admin文件下的url var router=express.Router(); router.get('/user',function(req,res,next){ res.send('user'); }); module.exports=router;//把router的结果做为模块的输出返回出去!
注意,在分路由中,不须要写明路径,就当它是在admin文件下的相对路径就能够了。
储存,而后回到app.js,应用app.use('/admin',require('./routers/admin'));
再打开页面,就看到结果了。
同理,api.js也如法炮制。
var express=require('express'); // 建立一个路由对象,此对象将会监听api文件夹下的url var router=express.Router(); router.get('/user',function(req,res,next){ res.send('api-user'); }); module.exports=router;//把router的结果做为模块的输出返回出去!
再应用app.use('api/',require('./routers/api'))
。重启服务器,结果以下
首页也如法炮制
前台路由涉及了至关多的内容,所以再细化分多若干个路由也是不错的选择。
每一个内容包括基本的分类和增删改
main模块
/
——首页
/view
——内容页
api模块
/
——首页
/login
——用户登录
/register
——用户注册
/comment
——评论获取
/comment/post
——评论提交
admin模块
/
——首页
用户管理
/user
——用户列表
分类管理
/category
——分类目录
/category/add
——分类添加
/category/edit
——分类编辑
/category/delete
——分类删除
文章管理
/article
——内容列表
/article/add
——添加文章
/article/edit
——文章修改
/article/delete
——文章删除
评论管理
/comment
——评论列表
/comment/delete
——评论删除
用户——栏目——内容——评论
一切操做依赖于用户,因此先须要用户。
栏目也分为先后台,优先作后台。
内容和评论相互关联。
好比用户,在SCHEMA文件夹下新建一个users.js
如何定义一个模块呢?这里用到mongoose模块
var mongoose=require('mongoose');//引入模块
除了在users.js请求mongoose模块之外,在app.js也须要引入mongoose。
// 加载express var express=require('express') //建立app应用,至关于=>Node.js Http.createServer(); var app=express(); // 加载数据库模块 var mongoose=require('mongoose'); // 设置静态文件托管 app.use('/public',express.static(__dirname+'/public')) // 定义模板引擎,使用swig.renderFile方法解析后缀为html的文件 var swig=require('swig'); app.engine('html',swig.renderFile); // 设置模板存放目录 app.set('views','./views'); // 注册模板引擎 app.set('view engine','html'); // 调试优化 swig.setDefaults({cache:false}); /* * 根据不一样的内容划分路由器 * */ app.use('/admin',require('./routers/admin')); app.use('/api',require('./routers/api')); app.use('/',require('./routers/main')); //监听http请求 mongoose.connect(); app.listen(9001);
mongoose使用须要安装mongodb数据库。
mongodb安装比较简单,在官网上下载了,制定好路径就能够了。
找到mongodb的bin文件夹。启动mongod.exe——经过命令行
命令行依次输入:
f: cd Program Files\MongoDB\Server\3.2\bin
总之就是根据本身安装的的路径名来找到mongod.exe就好了。
开启数据库前须要指定参数,好比数据库的路径。我以前已经在项目文件夹下建立一个db文件夹,而后做为数据库的路径就能够了。
除此以外还得指定一个端口。好比27018
mongod --dbpath=G:\node\db --port=27018
而后回车
信息显示:等待连接27018,证实开启成功
下次每次关机后开启服务器,都须要作如上操做。
接下来要开启mongo.exe。
命令行比较原始,仍是可使用一些可视化的工具进行链接。在这里我用的是robomongo。
直接在国外网站上下载便可,下载不通可能须要科学上下网。
名字随便写就好了,端口写27018
点击连接。
回到命令行。发现新出现如下信息:
表示正式创建链接。
连接已经创建起来。但里面空空如也。
接下来使用mongoose操做数据库。
能够上这里去看看文档。文档上首页就给出了mongoose.connect()
方法。
var mongoose = require('mongoose'); mongoose.connect('mongodb://localhost/test'); var Cat = mongoose.model('Cat', { name: String }); var kitty = new Cat({ name: 'Zildjian' }); kitty.save(function (err) { if (err) { console.log(err); } else { console.log('meow'); } });
connect方法接收的第一个参数,就是这个'mongodb://localhost:27018'
。第二个参数是回调函数。
数据库连接失败的话,是不该该开启监听的,因此要把listen放到connect方法里面。
mongoose.connect('mongodb://localhost:27018/blog',function(err){ if(err){ console.log('数据库链接错误!'); }else{ console.log('数据库链接成功!'); app.listen(9001); } });
运行,console显示,数据库连接成功。
注意,若是出现错误,仍是得看看编码格式,必须为UTF-8。
回到users.js的编辑上来,继续看mongoose文档。
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var blogSchema = new Schema({ title: String, author: String, body: String, comments: [{ body: String, date: Date }], date: { type: Date, default: Date.now }, hidden: Boolean, meta: { votes: Number, favs: Number } });
经过mongoose.Schema构造函数,生成一个Schema对象。
new出的Schema对象包含不少内容,传入的对象表明数据库中的一个表。每一个属性表明表中的每个字段,每一个值表明该字段存储的数据类型。
在这里,users.js须要暴露的内容就是用户名和密码。
// 加载数据库模块 var mongoose=require('mongoose'); // 返回用户的表结构 module.exports= new mongoose.Schema({ // 用户名 username: String, // 密码 password: String });
而后在经过模型类来操做表结构。在项目的models文件夹下建立一个User.js
var mongoose=require('mongoose'); var usersSchema=require('../schemas/users'); module.exports=mongoose.model('User',usersSchema);
这样就完成了一个模型类的建立。
模型怎么用?仍是看看文档给出的使用方法。
// 建立一个表结构对象 var schema = new mongoose.Schema({ name: 'string', size: 'string' }); // 根据表结构对象建立一个模型类 var Tank = mongoose.model('Tank', schema);
构造函数如何使用:
var Tank = mongoose.model('Tank', yourSchema); var small = new Tank({ size: 'small' }); small.save(function (err) { if (err) return handleError(err); // saved! }) // or Tank.create({ size: 'small' }, function (err, small) { if (err) return handleError(err); // saved! })
用户注册首先得加载一个首页。
在views下面新建一个main文件夹,而后把你以前写好的index.html放进去。
因此回到main.js中。渲染你已经写好的博客首页。
var express=require('express'); // 建立一个路由对象,此对象将会监听前台文件夹下的url var router=express.Router(); router.get('/',function(req,res,next){ res.render('main/index'); }); module.exports=router;//把router的结果做为模块的输出返回出去!
保存,而后重启app.js,就能在localhost:9001看到首页了。
固然这个首页很丑,你能够本身写一个。
原来的路径所有按照项目文件夹的结构进行修改。
注册登陆一共有三个状态。
一开始就是注册,若是已有帐号就点击登陆,出现登陆弹窗。
若是已经登陆,则显示已经登陆状态。并有注销按钮。
<div class="banner-wrap"> <div class="login" id="register"> <h3>注册</h3> <span>用户:<input name="username" type="text"/></span><br/> <span>密码:<input name="password" type="text"/></span><br/> <span>确认:<input name="repassword" type="text"/></span><br/> <span><input class="submit" type="button" value="提交"/></span> <span>已有帐号?立刻<a href="javascript:;">登陆</a></span> </div> <div class="login" id="login" style="display:none;"> <h3>登陆</h3> <span>用户:<input type="text"/></span><br/> <span>密码:<input type="text"/></span><br/> <span><input type="button" value="提交"/></span> <span>没有帐号?立刻<a href="javascript:;">注册</a></span> </div>
jquery能够这么写:
$(function(){ // 登陆注册的切换 $('#register a').click(function(){ $('#login').show(); $('#register').hide(); }); $('#login a').click(function(){ $('#login').hide(); $('#register').show(); }); });
当点击注册按钮,应该容许ajax提交数据。地址应该是api下的user文件夹的register,该register文件暂时没有建立,因此不理他照写便可。
// 点击注册按钮,经过ajax提交数据 $('#register .submit').click(function(){ // 经过ajax提交交 $.ajax({ type:'post', url:'/api/user/register', data:{ username:$('#register').find('[name="username"]').val(), password:$('#register').find('[name="password"]').val(), repassword:$('#register').find('[name="repassword"]').val() }, dataType:'json', success:function(data){ console.log(data); } }); });
容许网站,输入用户名密码点击注册。
虽然报错,可是在chrome的network下的header能够看到以前提交的信息。
挺好,挺好。
首先,找到API的模块,增长一个路由,回到api.js——当收到前端ajax的post请求时,路由打印出一个register字符串。
var express=require('express'); // 建立一个路由对象,此对象将会监听api文件夹下的url var router=express.Router(); router.post('/user/register',function(req,res,next){ console.log('register'); }); module.exports=router;//把router的结果做为模块的输出返回出去!
这时候,就不会显示404了。说明路由处理成功。
这就须要用到新的第三方模块——body-parser
。
相关文档地址:https://github.com/expressjs/body-parser
bodyParser.urlencoded(options)
Returns middleware that only parses
urlencoded
bodies. This parser accepts only UTF-8 encoding of the body and supports automatic inflation ofgzip
anddeflate
encodings.A new
body
object containing the parsed data is populated on therequest
object after the middleware (i.e.req.body
). This object will contain key-value pairs, where the value can be a string or array (whenextended
isfalse
), or any type (whenextended
istrue
).
var bodyParser=require('body-parser'); app.use(bodyParser.urlencoded(extended:true));
在app.js中,加入body-parser。而后经过app.use()方法调用。此时的app.js是这样的:
// 加载express var express=require('express'); //建立app应用,至关于=>Node.js Http.createServer(); var app=express(); // 加载数据库模块 var mongoose=require('mongoose'); // 加载body-parser,用以处理post提交过来的数据 var bodyParser=require('body-parser'); // 设置静态文件托管 app.use('/public',express.static(__dirname+'/public')) // 定义模板引擎,使用swig.renderFile方法解析后缀为html的文件 var swig=require('swig'); app.engine('html',swig.renderFile); // 设置模板存放目录 app.set('views','./views'); // 注册模板引擎 app.set('view engine','html'); // 调试优化 swig.setDefaults({cache:false}); // bodyParser设置 app.use(bodyParser.urlencoded({extended:true})); /* * 根据不一样的内容划分路由器 * */ app.use('/admin',require('./routers/admin')); app.use('/api',require('./routers/api')); app.use('/',require('./routers/main')); //监听http请求 mongoose.connect('mongodb://localhost:27018/blog',function(err){ if(err){ console.log('数据库链接错误!'); }else{ console.log('数据库链接成功!'); app.listen(9001); } });
配置好以后,回到api.js,就能在router.post方法中,经过req.body
获得提交过来的数据。
router.post('/user/register',function(req,res,next){ console.log(req.body); });
重启app.js,而后网页再次提交数据。
出现console信息:
拿到数据以后,就是进行基本的表单验证。好比
其中,检测用户名是否被注册须要用到数据库查询。
因此按照这个逻辑,从新归下类:
// 基本验证=>用户不得为空(错误代码1),密码不得为空(错误代码2),两次输入必须一致(错误代码3) // 数据库查询=>用户是否被注册。
咱们要对用户的请求进行响应。对于返回的内容,应该作一个初始化,指定返回信息和错误代码
// 统一返回格式 var responseData=null; router.use(function(req,res,next){ responseData={ code:0, message:'' } next(); });
res.json方法就是把响应的数据转化为一个json字符串。再直接return出去。后面代码再也不执行。
router.post('/user/register',function(req,res,next){ var username=req.body.username; var password=req.body.password; var repassword=req.body.repassword; //用户名是否为空 if(username==''){ responseData.code=1; responseData.message='用户名不得为空!'; res.json(responseData); return; } if(password==''){ responseData.code=2; responseData.message='密码不得为空!'; res.json(responseData); return; } if(repassword!==password){ responseData.code=3; responseData.message='两次密码不一致!'; res.json(responseData); return; } responseData.message='注册成功!'; res.json(responseData); });
基本运行就成功了。
以前已经完成了简单的验证,基于数据库怎么验证呢?
首先得请求模型中的user.js。
var User=require('../model/User');
这个对象有很是多的方法,再看看mongoose文档:http://mongoosejs.com/docs/api.html#model-js
其中
// #方法表示必须new出一个具体对象才能使用 Model#save([options], [options.safe], [options.validateBeforeSave], [fn])
在这里,咱们实际上就使用这个方法就够了。
Model.findOne([conditions], [projection], [options], [callback])
在router.post方法内追加:
// 用户名是否被注册? User.findOne({ username:username }).then(function(userInfo){ console.log(userInfo); });
重启运行发现返回的是一个null——若是存在,表示数据库有该记录。若是为null,则保存到数据库中。
因此完整的验证方法是:
router.post('/user/register',function(req,res,next){ var username=req.body.username; var password=req.body.password; var repassword=req.body.repassword; //基本验证 if(username==''){ responseData.code=1; responseData.message='用户名不得为空!'; res.json(responseData); return; } if(password==''){ responseData.code=2; responseData.message='密码不得为空!'; res.json(responseData); return; } if(repassword!==password){ responseData.code=3; responseData.message='两次密码不一致!'; res.json(responseData); return; } // 用户名是否被注册? User.findOne({ username:username }).then(function(userInfo){ if(userInfo){ responseData.code=4; responseData.message='该用户名已被注册!'; res.json(responseData); return; }else{//保存用户名信息到数据库中 var user=new User({ username:username, password:password, }); return user.save(); } }).then(function(newUserInfo){ console.log(newUserInfo); responseData.message='注册成功!'; res.json(responseData); }); });
再查看console内容
若是你再次输入该用户名。会发现后台console信息为undefined,网页控制台显示该用户名已被注册。
回到久违的Robomongo,能够看到数据库中多了一条注册用户的内容。
里面确确实实存在了一条记录。
在实际工做中,应该以加密的形式存储内容。在这里就不加密了。
如今后端的基本验证就结束了。前端收到数据后应当如何使用?
回到index.js
我要作两件事:
#loginInfo
)展示用户名信息。这里我把它加到导航栏最右边。暂时就这样写吧:
$(function(){ // 登陆注册的切换 $('#register a').click(function(){ $('#login').show(); $('#register').hide(); }); $('#login a').click(function(){ $('#login').hide(); $('#register').show(); }); // 点击注册按钮,经过ajax提交数据 $('#register .submit').click(function(){ // 经过ajax移交 $.ajax({ type:'post', url:'/api/user/register', data:{ username:$('#register').find('[name="username"]').val(), password:$('#register').find('[name="password"]').val(), repassword:$('#register').find('[name="repassword"]').val() }, dataType:'json', success:function(data){ alert(data.message); if(!data.code){ // 注册成功 $('#register').hide(); $('#login').show(); } } }); }); });
用户登陆的逻辑相似,当用户点击登陆按钮,一样发送ajax请求到后端。后端再进行验证。
因此在index.js中,ajax方法也如法炮制:
// 点击登陆按钮,经过ajax提交数据 $('#login .submit').click(function(){ // 经过ajax提交 $.ajax({ type:'post', url:'/api/user/login', data:{ username:$('#login').find('[name="username"]').val(), password:$('#login').find('[name="password"]').val(), }, dataType:'json', success:function(data){ console.log(data); } }); });
回到后端api.js,新增一个路由:
// 登陆验证 router.post('/user/login',function(res,req,next){ var username=req.body.username; var password=req.body.password; if(username==''||password==''){ responseData.code=1; responseData.message='用户名和密码不得为空!'; res.json(responseData); return; } });
一样也是用到findOne方法。
router.post('/user/login',function(req,res,next){ //console.log(req.body); var username=req.body.username; var password=req.body.password; if(username==''||password==''){ responseData.code=1; responseData.message='用户名和密码不得为空!'; res.json(responseData); return; } // 查询用户名和对应密码是否存在,若是存在则登陆成功 User.findOne({ username:username, password:password }).then(function(userInfo){ if(!userInfo){ responseData.code=2; responseData.message='用户名或密码错误!'; res.json(responseData); return; }else{ responseData.message='登陆成功!'; res.json(responseData); return; } }); });
以前登录之后在#userInfo
里面显示内容。
如今咱们来从新设置如下前端应该提示的东西:
这一切都是在导航栏面板上完成。
后端须要把用户名返回出来。在后端的userInfo参数里,已经包含了username的信息。因此把它也加到responseData中去。
<nav class="navbar"> <ul> <li><a href="index.html">首页</a></li> <li><a href="article.html">文章</a></li> <li><a href="portfolio.html">做品</a></li> <li><a href="about.html">关于</a></li> <li> <a id="loginInfo"> <span>未登陆</span> </a> </li> <li><a id="logout" href="javascript:;"> 注销 </a></li> </ul> </nav>
导航的结构大体如是,而后有一个注销按钮,display为none。
因而index.js能够这么写:
// 点击登陆按钮,经过ajax提交数据 $('#login .submit').click(function(){ // 经过ajax提交 $.ajax({ type:'post', url:'/api/user/login', data:{ username:$('#login').find('[name="username"]').val(), password:$('#login').find('[name="password"]').val(), }, dataType:'json', success:function(data){ alert(data.message); if(!data.code){ $('#login').slideUp(1000,function(){ $('#loginInfo span').text('你好,'+data.userInfo) $('#logout').show(); }); } } }); });
这一套简单的逻辑也完成了。
当你登录成功以后再刷新页面,发现并非登陆状态。这很蛋疼。
记录登陆状态应该反馈给浏览器。
在app.js中引入cookie模块——
var Cookies=require('cookies'); app.use(function(req,res){ req.cookies=new Cookies(req,res); next(); });
回到api.js,在登录成功以后,还得作一件事情,就是把cookies发送给前端。
}else{ responseData.message='登陆成功!'; responseData.userInfo=userInfo.username; //每当用户访问站点,将保存用户信息。 req.cookies.set('userInfo',JSON.stringify({ _id:userInfo._id, username:userInfo.username }); );//把id和用户名做为一个对象存到一个名字为“userInfo”的对象里面。 res.json(responseData); return; }
重启服务器,登陆。在network上看cookie信息
再刷新浏览器,查看headers
也多了一个userInfo,证实可用。
//设置cookie app.use(function(req,res,next){ req.cookies=new Cookies(req,res); // 解析cookie信息把它由字符串转化为对象 if(req.cookies.get('userInfo')){ try { req.userInfo=JSON.parse(req.cookies.get('userInfo'));; }catch(e){} } next(); });
调用模板去使用这些数据。
回到main.js
var express=require('express'); var router=express.Router(); router.get('/',function(req,res,next){ res.render('main/index',{ userInfo:req.userInfo }); }); module.exports=router;
而后就在index.html中写模板。
模板语法是根据从后端返回的信息在html里写逻辑的方法。
全部逻辑内容都在{%%}
里面
简单的应用就是if else
{% if userInfo._id %} <div id="div1"></div> {% else %} <div id="div2"></div> {% endif %}
若是后端返回的内容存在,则渲染div1,不然渲染div2,这个语句到div2就结束。
因此,如今咱们的渲染逻辑是:
若是我须要显示userInfo里的username,须要双大括号{{userInfo.username}}
这样一来,登录后的效果就不必了。直接重载页面。
if(!data.code){ window.location.reload(); }
而后顺便把注销按钮也作了。
注销无非是把cookie设置为空,而后前端所作的事情就是一个一个ajax请求,一个跳转。
index.js
// 注销模块 $('#logout').click(function(){ $.ajax({ type:'get', url:'/api/user/logout', success:function(data){ if(!data.code){ window.location.reload(); } } }); });
在api.js写一个退出的方法
// 退出方法 router.get('/user/logout',function(req,res){ req.cookies.set('userInfo',JSON.stringify({ _id:null, username:null })); res.json(responseData); return; });
管理员用户表面上看起来也是用户,可是在数据库结构是独立的一个字段,
打开users.js,新增一个字段
var mongoose=require('mongoose'); // 用户的表结构 module.exports= new mongoose.Schema({ username: String, password: String, // 是否管理员 isAdmin:{ type:Boolean, default:false } });
为了记录方便,我直接在RoboMongo中设置。
添加的帐号这么写:
保存。
那么这个管理员权限的帐户就建立成功了。
注意,管理员的帐户最好不要记录在cookie中。
回到app.js,重写cookie代码
//请求User模型 var User=require('./models/User'); //设置cookie app.use(function(req,res,next){ req.cookies=new Cookies(req,res); // 解析cookie信息 if(req.cookies.get('userInfo')){ try { req.userInfo=JSON.parse(req.cookies.get('userInfo')); // 获取当前用户登陆的类型,是否管理员 User.findById(req.userInfo._id).then(function(userInfo){ req.userInfo.isAdmin=Boolean(userInfo.isAdmin); next(); }); }catch(e){ next(); } }else{ next(); } });
整体思路是,根据isAdmin判断是否为真,
以前html显示的的判断是:{{userInfo.username}}
。
如今把欢迎信息改写成“管理员”,并提示“进入后台按钮”
<li> <a id="loginInfo"> {% if userInfo.isAdmin %} <span id="admin" style="cursor:pointer;">管理员你好,进入管理</span> {% else %} <span>{{userInfo.username}}</span> {% endif %} </a> </li>
很棒吧!
打开网站,登陆管理员用户,以前已经作出了进入管理连接。
咱们要求打开的网址是:http://localhost:9001/admin
。后台管理是基于admin.js上进行的。
先对admin.js作以下测试:
var express=require('express'); var router=express.Router(); router.use(function(req,res,next){ if(!req.userInfo.isAdmin){ // 若是当前用户不是管理员 res.send('不是管理员!'); return; }else{ next(); } }); router.get('/',function(res,req,next){ res.send('管理首页'); }); module.exports=router;
当登陆用户不是管理员。直接显示“不是管理员”
后台意味着你要写一个后台界面。这个index页面放在view>admin文件夹下。因此router应该是:
router.get('/',function(req,res,next){ res.render('admin/index'); });
因此你还得在admin文件夹写一个index.html
后台管理基于如下结构:
由于是临时写的,凑合着看大概是这样。
<header> <h1>后台管理系统</h1> </header> <span class="userInfo">你好,{{userInfo.username}}! <a href="javascript:;">退出</a></span> <aside> <ul> <li><a href="javascript:;">首页</a></li> <li><a href="javascript:;">设置</a></li> <li><a href="/admin/user">用户管理</a></li> <li><a href="javascript:;">分类管理</a></li> <li><a href="javascript:;">文章管理</a></li> <li><a href="javascript:;">评论管理</a></li> </ul> </aside> <section> {% block main %}{% endblock %} </section> <footer></footer>
这个代码应该是可复用的。所以可使用父类模板的功能。
在同文件夹下新建一个layout.html。把前端代码所有剪切进去。这时候admin/index.html一个字符也不剩了。
怎么访问呢?
在index下面,输入:
{% extends 'layout.html' %}
再刷新localhost:9001/admin,发现页面又回来了。
有了父类模板的功能,咱们能够作不少事情了。
相似面向对象的继承,右下方区域是不一样的内容,不该该写进layout中,所以能够写为
<section> {% block 占位区块名称 %}{% endblock %} </section>
而后回到index.html,定义这个区块的内容
{% block main %} <!-- 你的html内容 --> {% endblock %}
需求:点击“用户管理”,右下方的主体页面显示博客的注册用户数量。
因此连接应该是:
<li><a href="/admin/user">用户管理</a></li>
其实作到这块,应该都熟悉流程了。每增长一个新的页面,意味着写一个新的路由。在路由里渲染一个新的模板。在渲染的第二个参数里,以对象的方式写好你准备用于渲染的信息。
回到admin.js
router.get('/user/',function(req,res,next){ res.render('admin/user_index',{ userInfo:req.userInfo }) });
为了和index区分,新的页面定义为user_index。所以在view/admin文件夹下建立一个user_index.html
先作个简单的测试吧
{% extends 'layout.html' %} {% block main %} 用户列表 {% endblock %}
点击就出现了列表。
接下来就是要从数据库中读取全部的用户数据。而后传进模板中。
model下的User.js输出的对象含有咱们须要的方法。
咱们的User.js是这样的
var mongoose=require('mongoose'); // 用户的表结构 var usersSchema=require('../schemas/users'); module.exports=mongoose.model('User',usersSchema);
回到admin.js
var User=reuire('/model/User.js');
User有一个方法是find方法,返回的是一个promise对象
试着打印出来:
User.find().then(function(user){ console.log(user); });
结果一看,厉害了:
当前博客的两个用户都打印出来了。
接下来就是把这个对象传进去了,就跟传ajax同样:
var User=require('../models/User'); //用户管理 User.find().then(function(user){ router.get('/user/', function (req,res,next) { res.render('admin/user_index',{ userInfo:req.userInfo, users:user }) }) });
模板就能使用用户数据了。
main的展现区中,应该是一个标题。下面是一串表格数据。
大体效果如图
这须要模板中的循环语法
{% extends 'layout.html' %} {% block main %} <h3>用户列表</h3> <table class="users-list"> <thead> <tr> <th>id</th> <th>用户名</th> <th>密码</th> <th>是否管理员</th> </tr> </thead> <tbody> {% for user in users %} <tr> <td>{{user._id.toString()}}</td> <td>{{user.username}}</td> <td>{{user.password}}</td> <td> {% if user.isAdmin %} 是 {% else %} 不是 {% endif %} </td> </tr> {% endfor %} </tbody> </table> {% endblock %}
显示结果如图
实际上用户多了,就须要分页
假设咱们分页只须要对User对象执行一个limit方法。好比我想每页只展现1条用户数据:
router.get('/user/', function (req,res,next) { User.find().limit(1).then(function(user){ res.render('admin/user_index',{ userInfo:req.userInfo, users:user }); }); });
User的skip方法用于设置截取位置。好比skip(2),表示从第3条开始取。
好比我想每页设置两条数据:
skip(0)
skip(1)
好比我要展现第二页数据:
router.get('/user/', function (req,res,next) { var page=2; var limit=1; var skip=(page-1)*limit; User.find().limit(limit).skip(skip).then(function(user){ res.render('admin/user_index',{ userInfo:req.userInfo, users:user }); }); });
可是究竟有多少页不是咱们所能决定的。
首先要解决怎么用户怎么访问下一页的问题,通常来讲,在网页中输入http://localhost:9001/admin/user?pages=数字
就能够经过页面访问到。
既然page不能定死,那就把page写活。
var page=req.query.page||1;
这样就解决了
又回到了前端。
分页按钮是直接作在表格的后面。
到目前为止,写一个“上一页”和“下一页”的逻辑就行了——当在第一页时,上一页不显示,当在第最后一页时,下一页不显示
首先,把page传到前端去:
router.get('/user/', function (req,res,next) { var page=req.query.page||1; var limit=1; var skip=(page-1)*limit; User.find().limit(limit).skip(skip).then(function(user){ res.render('admin/user_index',{ userInfo:req.userInfo, users:user, page:page }); }); });
注意,传到前端的page是个字符串形式的数字,因此使用时必须转化为数字。
user.count是一个promise对象,
User.count().then(function(count){ console.log(count); })
这个count就是总记录条数。把这个count获取到以后,计算出须要多少页(向上取整),传进渲染的对象中。注意,这些操做都是异步的。因此不能用变量储存count。而应该把以前的渲染代码写到then的函数中
还有一个问题是页面取值。不该当出现page=200这样不合理的数字。因此用min方法取值。
router.get('/user/', function (req,res,next) { var page=req.query.page||1; var limit=1; var count=0; User.count().then(function(_count){ count=_count; var pages=Math.ceil(count/limit); console.log(count); page=Math.min(page,pages); page=Math.max(page,1); var skip=(page-1)*limit; User.find().limit(limit).skip(skip).then(function(user){ res.render('admin/user_index',{ userInfo:req.userInfo, users:user, page:page, pages:pages }); }); });//获取总页数 });
须要在表头作一个简单的统计,包括以下信息
所以应该这么写:
router.get('/user/', function (req,res,next) { var page=req.query.page||1; var limit=1; var count=0; User.count().then(function(_count){ count=_count; var pages=Math.ceil(count/limit); page=Math.min(page,pages); page=Math.max(page,1); var skip=(page-1)*limit; User.find().limit(limit).skip(skip).then(function(user){ res.render('admin/user_index',{ userInfo:req.userInfo, users:user, page:page, pages:pages, limit:limit, count:count }); }); });//获取总页数 });
前端模板能够这样写:
{% extends 'layout.html' %} {% block main %} <h3>用户列表 <small>(第{{page}}页)</small></h3> <table class="users-list"> <thead> <tr> <th>id</th> <th>用户名</th> <th>密码</th> <th>是否管理员</th> </tr> </thead> <tbody> {% for user in users %} <tr> <td>{{user._id.toString()}}</td> <td>{{user.username}}</td> <td>{{user.password}}</td> <td> {% if user.isAdmin %} 是 {% else %} 不是 {% endif %} </td> </tr> {% endfor %} </tbody> </table> <p class="table-info">一共有{{count}}个用户,每页显示{{limit}}个。</p> <ul class="page-btn"> {% if Number(page)-1!==0 %} <li><a href="/admin/user?page={{Number(page)-1}}">上一页</a></li> {% else %} <li>再往前..没有了</li> {% endif %} {% if Number(page)+1<=pages %} <li><a href="/admin/user?page={{Number(page)+1}}">下一页</a></li> {% else %} <li>已经是最后一页</li> {% endif %} </ul> {% endblock %}
效果如图
分页是一个极其经常使用的形式,能够考虑把它封装一下。
同目录下新建一个page.html
把按钮组件放进去。
{%include 'page.html'%}
结果有个问题,里面有一条写死的url(admin/xxx),为了解决,能够设置为...admin/{{type}}?page=yyy
,而后把回到admin.js,把type做为一个属性传进去。
那么用户管理部分就到此结束了。
前面已经实现了那么多页面,如今尝试实现博客内容的分类管理。
首先把分类管理的连接修改成/category/
,在admin.js中增长一个对应的路由。渲染的模板为admin/category_inndex.html
。
路由器基本写法:
router.get('/category/',function(req,res,next){ res.render('admin/category_index',{ userInfo:req.userInfo }); });
模板基本结构:
{% extends 'layout.html' %} {% block main %} {% endblock %}
点击“分类管理”,请求的页面就出来了。固然仍是一个空模板。
分类管理的特殊之处在于,它下面有两个子菜单(分类首页,管理分类)。对此咱们能够用jQuery实现基本动效。
html结构
<li id="category"> <a href="/admin/category">分类管理</a> <ul class="dropdown"> <li><a href="javascript:;">管理首页</a></li> <li><a href="/admin/category/add">添加分类</a></li> </ul> </li>
jq
$('#category').hover(function(){ $(this).find('.dropdown').stop().slideDown(400); },function(){ $(this).find('.dropdown').stop().slideUp(400); });
仍是得布局。
布局的基本设置仍是遵循用户的列表——一个大标题,一个表格。
分类页面下面单独有个页面,叫作“添加分类“。
根据上面的逻辑再写一个添加分类的路由
admin.js:
// 添加分类 router.get('/category/add',function(req,res,next){ res.render('admin/category_add',{ userInfo:req.userInfo }); });
同理,再添加一个category_add
模板,大体这样:
{% extends 'layout.html' %} {% block main %} <h3>添加分类 <small>>表单</small></h3> <form> <span>分类名</span><br/> <input type="text" name="name"/> <button type="submit">提交</button> </form> {%include 'page.html'%} {% endblock %}
目前还很是简陋可是先实现功能再说。
添加提交方式为post。
<form method="post"> <!--balabala--> </form>
因此路由器还得写个post形式的函数。
// 添加分类及保存方法:post router.post('/category/add',function(req,res,next){ });
post提交的结果,仍是返回当前的页面。
post提交到哪里?固然仍是数据库。因此在schemas中新建一个提交数据库。categories.js
var mongoose=require('mongoose'); // 博客分类的表结构 module.exports= new mongoose.Schema({ // 分类名称 name: String, });
好了。跟用户注册同样,再到model文件夹下面添加一个model添加一个Categories.js:
var mongoose=require('mongoose'); // 博客分类的表结构 var categoriessSchema=require('../schemas/categories'); module.exports=mongoose.model('Category',categoriessSchema);
文件看起来不少,但思路清晰以后至关简单。
完成这一步,就能够在admin.js添加Category对象了。
还记得bodyparser么?前端提交过来的数据都由它进行预处理:
// app.js app.use(bodyParser.urlencoded({extended:true}));
有了它,就能够经过req.body
来进行获取数据了。
刷新,提交内容。
在post方法函数中打印req.body:
在这里我点击了两次,其中第一次没有提交数据。记录为空字符串。这在规则中是不容许的。因此应该返回一个错误页面。
// 添加分类及保存方法:post var Category=require('../models/Categories'); router.post('/category/add',function(req,res,next){ //处理前端数据 var name=req.body.name||''; if(name===''){ res.render('admin/error',{ userInfo:req.userInfo }); } });
错误页面,最好写一个返回上一步(javascript:window.history.back()
)。
<!--error.html--> {% extends 'layout.html' %}} {% block main %} <h3>出错了</h3> <h4>你必定有东西忘了填写!</h4> <a href="javascript:window.history.back()">返回上一步</a> {% endblock %}
错误页面应该是可复用的。但的渲染须要传递哪些数据?
就当前项目来讲,大概这样就好了。
res.render('admin/error',{ userInfo:req.userInfo, message:'提交的内容不得为空!', operation:{ url:'javascript:window.history.back()', operation:'返回上一步' } });
模板页面:
{% extends 'layout.html' %}} {% block main %} <h3>出错了</h3> <h4>{{message}}</h4> <a href={{operation.url}}>{{operation.operation}}</a> {% endblock %}
显然,这个和用户名的验证是同样的。用findOne方法,在返回的promise对象执行then。返回一个新的目录,再执行then。进行渲染。
其次,须要一个成功页面。基本结构和错误界面同样。只是h3标题不一样
// 查询数据是否为空 Category.findOne({ name:name }).then(function(rs){ if(rs){//数据库已经有分类 res.render('admin/error',{ userInfo:req.userInfo, message:'数据库已经有该分类了哦。', operation:{ url:'javascript:window.history.back()', operation:'返回上一步' } }); return Promise.reject(); }else{//不然表示数据库不存在该记录,能够保存。 return new Category({ name:name }).save(); } }).then(function(newCategory){ res.render('admin/success',{ userInfo:req.userInfo, message:'分类保存成功!', operation:{ url:'javascript:window.history.back()', operation:'返回上一步' } }) }); });
接下来的事就又交给前端了。
显然,渲染的分类管理页面应该还有一个表格。如今顺便把它完成了。其实基本逻辑和以前的用户分类显示是同样的。并且代码极度重复:
// 添加分类及保存方法 var Category=require('../models/Categories'); router.get('/category/', function (req,res,next) { var page=req.query.page||1; var limit=2; var count=0; Category.count().then(function(_count){ count=_count; var pages=Math.ceil(count/limit); page=Math.min(page,pages); page=Math.max(page,1); var skip=(page-1)*limit; Category.find().limit(limit).skip(skip).then(function(categories){ res.render('admin/category_index',{ type:'category', userInfo:req.userInfo, categories:categories, page:page, pages:pages, limit:limit, count:count }); }); });//获取总页数 });
能够封装成函数了——一下就少了三分之二的代码量。
function renderAdminTable(obj,type,limit){ router.get('/'+type+'/', function (req,res,next) { var page=req.query.page||1; var count=0; obj.count().then(function(_count){ count=_count; var pages=Math.ceil(count/limit); page=Math.min(page,pages); page=Math.max(page,1); var skip=(page-1)*limit; obj.find().limit(limit).skip(skip).then(function(data){ res.render('admin/'+type+'_index',{ type:type, userInfo:req.userInfo, data:data, page:page, pages:pages, limit:limit, count:count }); }); });//获取总页数 }); } //调用时, //用户管理首页 var User=require('../models/User'); renderAdminTable(User,'user',1); //分类管理首页 // 添加分类及保存方法 var Category=require('../models/Categories'); renderAdminTable(Category,'category',2);
模板
{% extends 'layout.html' %} {% block main %} <h3>分类列表</h3> <table class="users-list"> <thead> <tr> <th>id</th> <th>分类名</th> <th>备注</th> <th>操做</th> </tr> </thead> <tbody> {% for category in data %} <tr> <td>{{category._id.toString()}}</td> <td>{{category.name}}</td> <td> <a href="/admin/category/edit">修改 </a> |<a href="/admin/category/edit"> 删除</a> </td> <td></td> </tr> {% endfor %} </tbody> </table> {%include 'page.html'%} {% endblock %}
删除的按钮是/admin/category/delete?id={{category._id.toString()}}
,同理修改的按钮是/admin/category/edit?id={{category._id.toDtring()}}
(带id的请求)。
这意味着两个新的页面和路由:
分类修改,分类删除。
删除和修改都遵循一套比较严谨的逻辑。其中修改的各类判断至关麻烦,可是,修改和删除的逻辑基本是同样的。
当一个管理员在进行修改时,另外一个管理员也可能修改(删除)了数据。所以须要严格判断。
修改首先作的是逻辑,根据发送请求的id值进行修改。若是id不存在则返回错误页面,若是存在,则切换到新的提交页面
// 分类修改 router.get('/category/edit',function(req,res,next){ // 获取修改的分类信息,并以表单的形式呈现,注意不能用body,_id是个对象,不是字符串 var id=req.query.id||''; // 获取要修改的分类信息 Category.findOne({ _id:id }).then(function(category){ if(!category){ res.render('admin/error',{ userInfo:req.userInfo, message:'分类信息不存在!' }); return Promise.reject(); }else{ res.render('admin/edit',{ userInfo:req.userInfo, category:category }); } }); });
而后是一个提交页,post返回的是当前页面
{% extends 'layout.html' %} {% block main %} <h3>分类管理 <small>>编辑分类</small></h3> <form method="post"> <span>分类名</span><br/> <input type="text" value="{{category.name}}" name="name"/> <button type="submit">提交</button> </form>
仍是以post请求保存数据。
提交数据一样也须要判断id,当id不存在时,跳转到错误页面。
当id存在,并且用户没有作任何修改,就提交,直接跳转到“修改为功”页面。实际上不作任何修改。
当id存在,并且用户提交过来的名字和非原id({$ne: id}
)下的名字不一样时,作两点判断:
数据库是否存在同名数据?是则跳转到错误页面。
若是数据库不存在同名数据,则更新同id下的name数据值,并跳转“保存成功”。
更新的方法是
Category.update({ _id:你的id },{ 要修改的key:要修改的value })
根据此逻辑能够写出这样的代码。
//分类保存 router.post('/category/edit/',function(req,res,next){ var id=req.query.id||''; var name=req.body.name||name; Category.findOne({ _id:id }).then(function(category){ if(!category){ res.render('admin/error',{ userInfo:req.body.userInfo, message:'分类信息不存在!' }); return Promise.reject(); }else{ // 若是用户不作任何修改就提交 if(name==category.name){ res.render('admin/success',{ userInfo:req.body.userInfo, message:'修改为功!', operation:{ url:'/admin/category', operation:'返回分类管理' } }); return Promise.reject(); }else{ // id不变,名称是否相同 Category.findOne({ _id: {$ne: id}, name:name }).then(function(same){ if(same){ res.render('admin/error',{ userInfo:req.body.userInfo, message:'已经存在同名数据!' }); return Promise.reject(); }else{ Category.update({ _id:id },{ name:name }).then(function(){ res.render('admin/success',{ userInfo:req.body.userInfo, message:'修改为功!', operation:{ url:'/admin/category', operation:'返回分类管理' } }); }); } }); } } }); });
为了防止异步问题,能够写得更加保险一点。让它每一步都返回一个promise对象,
//分类保存 router.post('/category/edit/',function(req,res,next){ var id=req.query.id||''; var name=req.body.name||name; Category.findOne({ _id:id }).then(function(category){ if(!category){ res.render('admin/error',{ userInfo:req.body.userInfo, message:'分类信息不存在!' }); return Promise.reject(); }else{ // 若是用户不作任何修改就提交 if(name==category.name){ res.render('admin/success',{ userInfo:req.body.userInfo, message:'修改为功!', operation:{ url:'/admin/category', operation:'返回分类管理' } }); return Promise.reject(); }else{ // 再查询id:不等于当前id return Category.findOne({ _id: {$ne: id}, name:name }); } } }).then(function(same){ if(same){ res.render('admin/error',{ userInfo:req.body.userInfo, message:'已经存在同名数据!' }); return Promise.reject(); }else{ return Category.update({ _id:id },{ name:name }); } }).then(function(resb){ res.render('admin/success',{ userInfo:req.body.userInfo, message:'修改为功!', operation:{ url:'/admin/category', operation:'返回分类管理' } }); }); });
这样就能实现修改了。
删除的逻辑相似。可是要简单一些,判断页面是否还存在该id,是就删除,也不须要专门去写删除界面。,只须要一个成功或失败的界面就OK了。
删除用的是remove方法——把_id属性为id的条目删除就行啦
// 分类的删除 router.get('/category/delete',function(req,res){ var id=req.query.id; Category.findOne({ _id:id }).then(function(category){ if(!category){ res.render('/admin/error',{ userInfo:req.body.userInfo, message:'该内容不存在于数据库中!', operation:{ url:'/admin/category', operation:'返回分类管理' } }); return Promise.reject(); }else{ return Category.remove({ _id:id }) } }).then(function(){ res.render('admin/success',{ userInfo:req.body.userInfo, message:'删除分类成功!', operation:{ url:'/admin/category', operation:'返回分类管理' } }); }); });
前台的导航分类是写死的,如今是时候把它换成咱们须要的内容了。
由于我我的项目的关系,我一级导航是固定的。因此就在文章分类下实现下拉菜单。
从数据库读取前台首页内容,基于main.js
为此还得引入Category
var Category=require('../models/Categories'); router.get('/',function(req,res,next){ // 读取分类信息 Category.find().then(function(rs){ console.log(rs) }); res.render('main/index',{ userInfo:req.userInfo }); });
运行后打印出来的信息是:
就成功拿到了后台数据。
接下来就是把数据加到模板里面去啦
var Category=require('../models/Categories'); router.get('/',function(req,res,next){ // 读取分类信息 Category.find().then(function(categories){ console.log(categories); res.render('main/index',{ userInfo:req.userInfo, categories:categories }); }); });
前端模板这么写:
<ul class="nav-article"> {% if !userInfo._id %} <li><a href="javascript:;">仅限注册用户查看!</a></li> {% else %} {% for category in categories %} <li><a href="javascript:;">{{category.name}}</a></li> {% endfor %} {% endif %} </ul>
你在后台修改分类,
结果就出来了。挺好,挺好。
然而有一个小问题,就是咱们拿到的数据是倒序的。
思路1:在后端把这个数组reverse一下。就符合正常的判断逻辑了。
res.render('main/index',{ userInfo:req.userInfo, categories:categories.reverse() });
但这不是惟一的思路,从展现后端功能的考虑,最新添加的理应在最后面,因此有了思路2
思路2:回到admin.js对Category进行排序。
id表面上看是一串毫无规律的字符串,然而它确实是按照时间排列的。
那就好了,根据id用sort方法排序
obj.find().sort({_id:-1})...... //-1表示降序,1表示升序
博客分类管理这部分到此结束了。
文章管理仍是基于admin.js
<!--layout.html--> <li><a href="/admin/content">文章管理</a></li>
增长一个管理首页
<!--content.html--> {% extends 'layout.html' %} {% block main %} <h3>文章管理 </h3> <a href="content/add">添加新的文章!</a> <!--表格--> {% endblock %}
再增长一个编辑文章的界面,其中,要获取分类信息
{% extends 'layout.html' %} {% block main %} <h3>文章管理 <small>>添加文章</small></h3> <form method="post"> <span>标题</span> <input type="text" name="title"/> <span>分类</span> <select name="categories"> {% for category in categories %} <option value="{{category._id.toString()}}">{{category.name}}</option> {% endfor %} </select> <button type="submit">提交</button><br> <span style="line-height: 30px;">内容摘要</span><br> <textarea id="description" cols="150" rows="3" placeholder="请输入简介" name="description"> </textarea> <br> <span style="line-height: 20px;">文章正文</span><br> <textarea id="article-content"> </textarea> </form> {% endblock %}
效果以下
再写两个路由。
// admin.js // 内容管理 router.get('/content',function(req,res,next){ res.render('admin/content_index',{ userInfo:req.userInfo }); }); // 添加文章 router.get('/content/add',function(req,res,next){ Category.find().then(function(categories){ console.log(categories) res.render('admin/content_add',{ userInfo:req.userInfo, categories:categories }); }) });
仍是用到了schema设计应该存储的内容。
最主要的固然是文章相关——标题,简介,内容,发表时间。
还有一个不可忽视的问题,就是文章隶属分类。咱们是根据分类id进行区分的
// schemas文件夹下的content.js var mongoose=require('mongoose'); module.exports=new mongoose.Schema({ // 关联字段 -分类的id category:{ // 类型 type:mongoose.Schema.Tpyes.ObjectId, // 引用,其实是说,存储时根据关联进行索引出分类目录下的值。而不是存进去的值。 ref:'Category' }, // 标题 title:String, // 简介 description:{ type:String, default:'' }, // 文章内容 content:{ type:String, default:'' }, // 当前时间 date:String });
接下来就是建立一个在models下面建立一个Content模型
// model文件夹下的Content.js var mongoose=require('mongoose'); var contentsSchema=require('../schemas/contents'); module.exports=mongoose.model('Content',contentsSchema);
内容保存是用post方式提交的。
所以再写一个post路由
//admin.js // 内容保存 router.post('/content/add',function(req,res,next){ console.log(req.body); });
在后台输入内容,提交,就看到提交上来的数据了。
不错。
简单的验证规则:不能为空
验证不能为空的时候,应该调用trim方法处理以后再进行验证。
// 内容保存 router.post('/content/add',function(req,res,next){ console.log(req.body) if(req.body.category.trim()==''){ res.render('admin/error',{ userInfo:req.userInfo, message:'分类信息不存在!' }); return Promise.reject(); } if(req.body.title.trim()==''){ res.render('admin/error',{ userInfo:req.userInfo, message:'标题不能为空!' }); return Promise.reject(); } if(req.body.content.trim()==''){ res.render('admin/error',{ userInfo:req.userInfo, message:'内容忘了填!' }); return Promise.reject(); } });
还有个问题。就是简介(摘要)
保存和渲染相关的方法都是经过引入模块来进行的。
var Content=require('../models/Contents'); ···· new Content({ category:req.body.category, title:req.body.title, description:req.body.description, content:req.body.content, date:new Date().toDateString() }).save().then(function(){ res.render('admin/success',{ userInfo:req.userInfo, message:'文章发布成功!' }); }); ····
而后你发布一篇文章,验证无误后,就会出现“发布成功”的页面。
而后你就能够在数据库查询到想要的内容了
这个对象有当前文章相关的内容,也有栏目所属的id,也有内容本身的id。还有日期
为了显示内容,能够用以前封装的renderAdminTable函数
{% extends 'layout.html' %} {% block main %} <h3>文章管理 </h3> <a href="content/add">添加新的文章!</a> <table class="users-list"> <thead> <tr> <th>标题</th> <th>所属分类</th> <th>发布时间</th> <th>操做</th> </tr> </thead> <tbody> {% for content in data %} <tr> <td>{{content.title}}</td> <td>{{content.category}}</td> <td> {{content.date}} </td> <td> <a href="/admin/content/edit?id={{content._id.toString()}}">修改 </a> |<a href="/admincontent/delete?id={{content._id.toString()}}"> 删除</a> </td> </tr> {% endfor %} </tbody> </table> {%include 'page.html'%} {% endblock %}
分类名显示出来的是个object
分类名用的是data.category
。
但若是换成data.category.id
就能获取到一个buffer对象,这个buffer对象转换后,应该就是分类信息。
可是直接用的话,又显示乱码。
这就有点小麻烦了。
回看schema中的数据库,当存储后,会自动关联Category
模对象(注意:这里的Category
固然是admin.js的Category)进行查询。查询意味着有一个新的方法populate
。populate方法的参数是执行查询的属性。在这里咱们要操做的属性是category
。
// 这是一个功能函数 function renderAdminTable(obj,type,limit,_query){ router.get('/'+type+'/', function (req,res,next) { var page=req.query.page||1; var count=0; obj.count().then(function(_count){ count=_count; var pages=Math.ceil(count/limit); page=Math.min(page,pages); page=Math.max(page,1); var skip=(page-1)*limit; /* * sort方法排序,根据id, * */ var newObj=_query?obj.find().sort({_id:-1}).limit(limit).skip(skip).populate(_query):obj.find().sort({_id:-1}).limit(limit).skip(skip); newObj.then(function(data){ console.log(data); res.render('admin/'+type+'_index',{ type:type, userInfo:req.userInfo, data:data, page:page, pages:pages, limit:limit, count:count }); }); });//获取总页数 }); }
diao调用时写法为:renderAdminTable(Content,'content',2,'category');
打印出来的data数据为:
发现Category的查询结果就返回给data的category属性了
很棒吧!那就把模板改了
不错不错。
修改和删除基本上遵守同一个逻辑。
请求的文章id若是在数据库查询不到,那就返回错误页面。不然渲染一个编辑页面(content_edit)——注意,这里得事先获取分类。
// 修改 router.get('/content/edit',function(req,res,next){ var id=req.query.id||''; Content.findOne({ _id:id }).then(function(content){ if(!content){ res.render('admin/error',{ userInfo:req.userInfo, message:'该文章id事先已被删除了。' }); return Promise.reject(); }else{ Category.find().then(function(categories){ // console.log(content); res.render('admin/content_edit',{ userInfo:req.userInfo, categories:categories, data:content }); }); } }); });
把前端页面显示出来以后就是保存。
保存的post逻辑差很少,但实际上能够简化。
// 保存文章修改 router.post('/content/edit',function(req,res,next){ var id=req.query.id||''; Content.findOne({ _id:id }).then(function(content){ if(!content){ res.render('admin/error',{ userInfo:req.body.userInfo, message:'文章id事先被删除了!' }); return Promise.reject(); }else{ return Content.update({ _id:id },{ category:req.body.category, title:req.body.title, description:req.body.description, content:req.body.content }); } }).then(function(){ res.render('admin/success',{ userInfo:req.body.userInfo, message:'修改为功!', operation:{ url:'/admin/content', operation:'返回分类管理' } }); }); });
基本差很少。
router.get('/content/delete',function(req,res,next){ var id=req.query.id||''; Content.remove({ _id:id }).then(function(){ res.render('admin/success',{ userInfo:req.userInfo, message:'删除文章成功!', operation:{ url:'/admin/content', operation:'返回分类管理' } }); }); });
能够在数据表结构中再添加两个属性
user: { //类型 type:mongoose.Schema.Types.objectId, //引用 ref:'User' }, views:{ type:Number, default:0 }
而后在文章添加时,增添一个user属性,把req.userInfo._id传进去。
显示呢?实际上populate方法接受一个字符串或者有字符串组成的数组。因此数组应该是xxx.populate(['category','user'])
。这样模板就能拿到user的属性了。
而后修改模板,让它展示出来:
先给博客写点东西吧。当前的文章确实太少了。
当咱们写好了文章,内容就已经存放在服务器上了。前台怎么渲染是一个值得考虑的问题。显然,这些事情都是main.js完成的。
这时候注意了,入门一个领域,知道本身在干什么是很是重要的。
因为业务逻辑,个人博客内容设置为不在首页展现,须要在/article
页专门展现本身的文章,除了所有文章,分类连接渲染的是:/article?id=xxx
。
先看所有文章下的/article
怎么渲染吧。
文章页效果预期是这样的:
文章页须要接收的信息比较多,因此写一个data对象,把这些信息放进去,到渲染时直接用这个data就好了。
//main.js var express=require('express'); var router=express.Router(); var Category=require('../models/Categories'); var Content=require('../models/Content'); /* *省略首页路由 * */ router.get('/article',function(req,res,next){ var data={ userInfo:req.userInfo, categories:[], count:0, page:Number(req.query.page||1), limit:3, pages:0 }; // 读取分类信息 Category.find().then(function(categories){ data.categories=categories; return Content.count(); }).then(function(count){ data.count=count; //计算总页数 data.pages=Math.ceil(data.count/data.limit); // 取值不超过pages data.page=Math.min(data.page,data.pages); // 取值不小于1 data.page=Math.max(data.page,1); // skip不须要分配到模板中,因此忽略。 var skip=(data.page-1)*data.limit; return Content.find().limit(data.limit).skip(skip).populate(['category','user']).sort(_id:-1); }).then(function(contents){ data.contents=contents; console.log(data);//这里有你想要的全部数据 res.render('main/article',data); }) });
该程序反映了data一步步获取内容的过程。
我只须要对文章展现作个for循环,而后把数据传进模板中就能够了。
{% for content in contents %} <div class="cell"> <div class="label"> <time>{{content.date.slice(5,11)}}</time> <div>{{content.category.name.slice(0,3)+'..'}}</div> </div> <hgroup> <h3>{{content.title}}</h3> <h4>{{content.user.username}}</h4> </hgroup> <p>{{content.description}}</p> <address>推送于{{content.date}}</address> </div> {% endfor %}
侧边栏有一个文章内容分类区,把数据传进去就好了。
分页按钮能够这样写
<div class="pages-num"> <ul> <li><a href="/article?page=1">第一页</a></li> {% if page-1!==0 %} <li><a href="/article?page={{page-1}}">上一页</a></li> {%endif%} {% if page+1<=pages %} <li><a href="/article?page={{page+1}}">下一页</a></li> {% endif %} <li><a href="/article?page={{pages}}">最后页</a></li> </ul> </div>
效果:
你会发现,模板的代码越写越简单。
如今来解决分类的问题。
以前咱们写好的分类页面地址为/article?category={{category._id.toString()}}
因此要对当前的id进行响应。若是请求的category值为不空,则调用where
显示。
router.get('/article',function(req,res,next){ var data={ userInfo:req.userInfo, category:req.query.category||'', categories:[], count:0, page:Number(req.query.page||1), limit:3, pages:0 }; var where={}; if(data.category){ where.category=data.category } //... return Content.where(where).find().limit(data.limit).skip(skip).sort({_id:-1}).populate(['category','user']);
这样点击相应的分类,就能获取到相应的资料了。
可是页码仍是有问题。缘由在于count的获取,也应该根据where进行查询。
return Content.where(where).count();
另一个页码问题是,页码的连接写死了。
只要带上category就好了。
因此比较完整的页码判断是:
<ul> {% if pages>0 %} <li><a href="/article?category={{category.toString()}}&page=1">第一页</a></li> {% if page-1!==0 %} <li><a href="/article?category={{category.toString()}}&page={{page-1}}">上一页</a></li> {%endif%} <li style="background:rgb(166,96,183);"><a style="color:#fff;" href="javascript:;">{{page}}/{{pages}}</a></li> {% if page+1<=pages %} <li><a href="/article?category={{category.toString()}}&page={{page+1}}">下一页</a></li> {% endif %} <li><a href="/article?category={{category.toString()}}&page={{pages}}">最后页</a></li> {% else %} <li style="width: 100%;text-align: center;">当前分类没有任何文章!</li> {% endif %} </ul>
而后作一个当前分类高亮显示的判断
<ul> {% if category=='' %} <li><a style="border-left: 6px solid #522a5c;" href="/article">所有文章</a></li> {%else%} <li><a href="/article">所有文章</a></li> {% endif %} {% for _category in categories %} {% if category.toString()==_category._id.toString() %} <li><a style="border-left: 6px solid #522a5c;" href="/article?category={{_category._id.toString()}}">{{_category.name}}</a></li> {% else %} <li><a href="/article?category={{_category._id.toString()}}">{{_category.name}}</a></li> {% endif %} {% endfor %} </ul>
同理内容详情页须要给个连接,而后就再写一个路由。在这里我用的是/view?contentid={{content._id}}
。
须要哪些数据?
查询方式:contentId
router.get('/view/',function(req,res,next){ var contentId=req.query.contentId||''; var data={ userInfo:req.userInfo, categories:[], content:null }; Category.find().then(function(categories){ data.categories=categories; return Content.findOne({_id:contentId}); }).then(function(content){ data.content=content; console.log(data); res.render('main/view',data); }); });
发现能够打印出文章的主要内容了。
接下来就是写模板。
新建一个article_layout.html模板,把article.html的全部内容剪切进去。
博客展现页的主要区域在于以前的内容列表。因此把它抽离出来。
把一个个内容按照逻辑加上去,大概就是这样。
很简单,每当用户点击文章,阅读数就加1.
router.get('/view/',function(req,res,next){ var contentId=req.query.contentId||''; var data={ userInfo:req.userInfo, categories:[], content:null }; Category.find().then(function(categories){ data.categories=categories; return Content.findOne({_id:contentId}); }).then(function(content){ data.content=content; content.views++;//保存阅读数 content.save(); console.log(data); res.render('main/view',data); }); });
先把评论的样式写出来吧!大概是这样
评论是经过ajax提交的。是在ajax模块——api.js
评论的post提交到数据库,应该放到数据库的contents.js中。
// 评论 comments: { type:Array, default:[] }
每条评论包括以下内容:
评论者,评论时间,还有评论的内容。
在api.js中写一个post提交的路由
// 评论提交 router.post('/comment/post',function(req,res,next){ // 文章的id是须要前端提交的。 var contentId=req.body.contentId||''; var postData={ username:req.userInfo.username, postTime: new ConvertDate().getDate(), content: req.body.content }; // 查询当前内容信息 Content.findOne({ _id:contentId }).then(function(content){ content.comments.push(postData); return content.save() }).then(function(newContent){//最新的内容在newContent! responseData.message='评论成功!'; res.json(responseData); }) });
而后在你的view页面相关的文件中写一个ajax方法,咱们要传送文章的id
可是文章的id最初并无发送过去。能够在view页面写一个隐藏的input#contentId
,把当前文章的id存进去。而后经过jQuery拿到数据。
// 评论提交 $('#messageComment').click(function(){ $.ajax({ type:'POST', url:'/api/comment/post', data:{ contentId:$('#contentId').val(), content:$('#commentValue').val(), }, success:function(responseData){ console.log(responseData); } }); return false; });
很简单吧!
评论提交后,清空输入框,而后下方出现新增长的内容。
最新的内容从哪来呢?在newContent处。因此咱们只须要让responseData存进newContent,就能实现内容添加。
// api.js //... // 查询当前内容信息 Content.findOne({ _id:contentId }).then(function(content){ content.comments.push(postData); return content.save() }).then(function(newContent){ responseData.message='评论成功!'; responseData.data=newContent; res.json(responseData); }) //...
看,这样就拿到数据了。
接下来就在前端渲染页面:
用这个获取内容。
function renderComment(arr){ var innerHtml=''; for(var i=0;i<arr.length;i++){ innerHtml='<li><span class="comments-user">'+arr[i].username+' </span><span class="comments-date">'+arr[i].postTime+'</span><p>'+arr[i].content+'</p></li>'+innerHtml; } return innerHtml; }
// 评论提交 $('#messageComment').click(function(){ $.ajax({ type:'POST', url:'/api/comment/post', data:{ contentId:$('#contentId').val(), content:$('#commentValue').val(), }, success:function(responseData){ console.log(responseData); alert(responseData.message); var arr= responseData.data.comments; //console.log(renderComment(arr)); $('.comments').html(renderComment(arr)); } }); return false; });
这样就能够显示出来了。可是发现页面一刷新,内容就又没有了——加载时就调用ajax方法。
api是提供一个虚拟地址,ajax可以从这个地址获取数据。
重新写一个路由:
//api.js // 获取指定文章的全部评论 router.get('/comment',function(req,res,next){ var contentId=req.query.contentId||''; Content.findOne({ _id:contentId }).then(function(content){ responseData.data=content; res.json(responseData); }) });
注意这里是get方式
//每次文章重载时获取该文章的全部评论 $.ajax({ type:'GET', url:'/api/comment', data:{ contentId:$('#contentId').val(), content:$('#commentValue').val(), }, success:function(responseData){ console.log(responseData); var arr= responseData.data.comments; //console.log(renderComment(arr)); $('.comments').html(renderComment(arr)); $('#commentValue').val(''); $('#commentsNum').html(arr.length) } });
分由于是ajax请求到的数据,因此彻底能够在前端完成。
评论分页太老旧了。不如作个伪瀑布流吧!
预期效果:点击加载更多按钮,出现三条评论。
之因此说是伪,由于评论一早就拿到手了。只是分段展现而已。固然你也能够写真的。每点击一次都触发新的ajax请求。只请求三条新的数据。
评论部分彻底能够写一个对象。重置方法,加载方法,获取数据方法。
写下来又是一大篇文章。
// 加载评论的基本逻辑 function Comments(){ this.count=1; this.comments=0; }
在ajax请求评论内容是时,给每条评论的li加一个data-index值。
// 获取评论内容 Comments.prototype.getComment=function(arr){ var innerHtml=''; this.comments=arr.length;//获取评论总数 for(var i=0;i<arr.length;i++){ innerHtml= '<li data-index='+(arr.length-i)+'><span class="comments-user">'+ arr[i].username+ ' </span><span class="comments-date">'+ arr[i].postTime+ '</span><p>'+ arr[i].content+ '</p></li>'+innerHtml; } return innerHtml; };
在每次加载页面,每次发完评论的时候,都初始化评论页面。首先要作的是解绑加载按钮可能的事件。当评论数少于三条,加载按钮变成“没有更多了”。超过三条时,数据自动隐藏。
Comments.prototype.resetComment=function (limit){ this.count=1; this.comments=$('.comments').children().length;//获取评论总数 $('#load-more').unbind("click"); if(this.comments<limit){ $('#load-more').text('..没有了'); }else{ $('#load-more').text('加载更多'); } for(var i=1;i<=this.comments;i++){ if(i>limit){ $('.comments').find('[data-index='+ i.toString()+']').css('display','none'); } } };
点击加载按钮,根据点击计数加载评论
Comments.prototype. loadComments=function(limit){ var _this=this; $('#load-more').click(function(){ //console.log([_this.comments,_this.count]); if((_this.count+1)*limit>=_this.comments){ $(this).text('..没有了'); } _this.count++; for(var i=1;i<=_this.comments;i++){ if(_this.count<i*_this.count&&i<=(_this.count)*limit){ $('.comments').find('[data-index='+ i.toString()+']').slideDown(300); } } }); };
而后就是在网页中应用这些方法:
$(function(){ //每次文章重载时获取该文章的全部评论 $.ajax({ type:'GET', url:'/api/comment', data:{ contentId:$('#contentId').val(), content:$('#commentValue').val(), }, success:function(responseData){ var arr= responseData.data.comments; //渲染评论的必要方法 var renderComments=new Comments(); //获取评论内容 $('.comments').html(renderComments.getComment(arr)); //清空评论框 $('#commentValue').val(''); //展现评论条数 $('#commentsNum').html(arr.length); //首次加载展现三条,每点击一次加载3条 renderComments.resetComment(3); renderComments.loadComments(3); // 评论提交 $('#messageComment').click(function(){ $.ajax({ type:'POST', url:'/api/comment/post', data:{ contentId:$('#contentId').val(), content:$('#commentValue').val(), }, success:function(responseData){ alert(responseData.message); var arr= responseData.data.comments; $('.comments').html(renderComments.getComment(arr)); $('#commentValue').val(''); $('#commentsNum').html(arr.length); renderComments.resetComment(3); renderComments.loadComments(3); } }); return false; }); } }); });
get方式获取的内容中虽然有了文章做者id,可是没有做者名。也缺失当前文章的内容。因此在get获取以后,须要发送发布者的信息。
另外一方面,因为view.html继承的是article的模板。而article是须要在在发送的一级目录下存放一个category属性,才能在模板判断显示。
所以须要把data.content.category移到上层数性来。
}).then(function(content){ //console.log(content); data.content=content; content.views++; content.save(); return User.find({ _id:data.content.user }); }).then(function(rs){ data.content.user=rs[0]; data.category=data.content.category; res.render('main/view',data); });
如今的博客内容是混乱无序的。
那就用到最后一个模块——markdown
按照逻辑来讲,内容渲染不该该在后端进行。尽管你也能够这么作。可是渲染以后,编辑文章会发生很大的问题。
因此我仍是采用熟悉的marked.js,由于它能比较好的兼容hightlight.js的代码高亮。
<script type="text/javascript" src="../../public/js/marked.js"></script> <script type="text/javascript" src="../../public/js/highlight.pack.js"></script> <script >hljs.initHighlightingOnLoad();</script>
// ajax方法 success:function(responseData){ // console.log(responseData); var a=responseData.data.content; var rendererMD = new marked.Renderer(); marked.setOptions({ renderer: rendererMD, gfm: true, tables: true, breaks: false, pedantic: false, sanitize: false, smartLists: true, smartypants: false }); marked.setOptions({ highlight: function (code,a,c) { return hljs.highlightAuto(code).value; } }); //后文略...
在经过ajax请求到数据集以后,对内容进行渲染。而后插入到内容中去。
那么模板里的文章内容就不要了。
可是,浏览器自带的html标签样式实在太丑了!在引入样式库吧
highlight.js附带的样式库提供了多种基本的语法高亮设置。
而后你能够参考bootstrap的code部分代码。再改改行距,自适应图片等等。让文章好看些。
到目前为止,这个博客就基本实现了。
前端须要一些后端的逻辑,才能对产品有较为深入的理解。