會安裝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部分代碼。再改改行距,自適應圖片等等。讓文章好看些。
到目前爲止,這個博客就基本實現了。
前端須要一些後端的邏輯,才能對產品有較爲深入的理解。