前言
模块化开发,主要是以提高代码的复用性,可维护性等。再这个博客项目中,到处体现有模块化开发的思想,如使用 MVC 的设计模式,路由的分文件处理等。在 ejs 模板引擎中,类似 jsp 一般可以将里面的各个内容分成不同的模块,然后再分别引入相关模块组成页面,这样一来就可以做到了代码的复用性,同时维护起来的成本也降低。
ejs 模板引擎的模块化
在 /views/admin 中新建一个名为 header.ejs 的模板,当做后台管理的页头,将 /views/admin/index.ejs 的导航部分剪切出来放到 header.ejs 中,header.ejs 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <link rel="stylesheet" href="/public/css/bootstrap.min.css"> <title>控制台</title> </head> <body> <header> <nav class="navbar navbar-default navbar-inverse"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="/admin">后台管理</a> </div> <div> <ul class="nav navbar-nav"> <li><a href="/admin/user">用户管理</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> 分类管理 <b class="caret"></b> </a> <ul class="dropdown-menu"> <li><a href="/admin/category">分类首页</a></li> <li><a href="/admin/category/add">添加分类</a></li> </ul> </li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> 内容管理 <b class="caret"></b> </a> <ul class="dropdown-menu"> <li><a href="/admin/content">内容首页</a></li> <li><a href="/admin/content/add">添加内容</a></li> </ul> </li> </ul> <ul class="nav navbar-nav pull-right"> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> <%=userInfo.username%> <b class="caret"></b> </a> <ul class="dropdown-menu"> <li><a href="/">返回主页</a></li> <li class="divider"></li> <li><a href="javascript:;" id="logout">退出</a></li> </ul> </li> </ul> </div> </div> </nav> </header> <script src="/public/js/3.3.1-jquery-min.js"></script> <script src="/public/js/bootstrap.min.js"></script> <script> $("#logout").on("click", function() { $.ajax({ url: "/api/user/logout", success: function(result) { if (result) { window.location = "http://localhost:8080/"; } } }); }); </script>
|
然后在后台首页界面中引用 header.ejs 模板:
1 2 3
| <%- include("header") %>
|
同时用户管理页面将 header 标签的内容删掉后引入 header.ejs:
1 2 3
| <%- include("../header") %>
|
内容显示分页的实现
分页功能是数据展示必备的功能,如果需要查询 1000 条数据,一次性的进行展示不仅浪费用户等待的时间,宽带,而且还占用服务器的 I/O 时间从而影响性能,用户一次性看到这么多的数据也很头疼,用户体验不好。所以就需要分页功能来展示数据,就像看书一样,一页展示一点数据,分页展示。
前端组件
分页功能估计后面需要经常用到,所以我们就将它抽离出来分成一个组件,一个模块。在 /views/ 目录下新建一个模板文件 pagination.ejs 当做分页组件,代码如下:
1 2 3 4 5 6 7 8 9
| <nav aria-label="..."> <ul class="pager"> <li><a href="/admin/user?page=<%=page-1%>">上一页</a></li> <li> 一共有 <%=count%> 条数据,每页显示 <%=limit%> 条数据,当前第 <%=page%>/<%=pages%> 页 </li> <li><a href="<%=url%>?page=<%=page+1%>">下一页</a></li> </ul> </nav>
|
后端模块
前端通过 GET 的方式向后端提交当前所在的页数,后端进行相应的相应。在 mongoose 中,可以通过 skip() 跳过需要查询的数据条数,limit() 每次查询几条数据,sort() 排序,等方法进行相应的组合达到分页的效果。
同样,分页功能以后会在很多地方使用,所以将其写成一个独立的模块,以便以后重复的使用。在项目的根目录下新建一个目录 my_modules 用来存放我们自己编写的相应模块,在该目录下新建一个 pagination.js 文件,添加如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
|
const pagination = (object) => {
let page = object.req.query.page || 1; let limit = object.limit || 10; let pages = 0; let populate = object.populate || []; object.model.countDocuments(object.where).then((count) => { pages = Math.ceil(count / limit); page = Math.min(page, pages); page = Math.max(page, 1); let skip = (page - 1) * limit; object.model.find(object.where).populate(populate).skip(skip).limit(limit).then((docs) => { object.res.render(object.ejs, { userInfo: object.req.userInfo, docs: docs, page: page, pages: pages, limit: limit, url: object.url, count: count }); }); }); };
module.exports = pagination;
|
自定义的分页模块完成后,在需要调用的地方引入它。在后台管理的路由文件 admin.js 中的用户管理首页中,将其代码修改为如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| router.get("/user", (req, res, next) => { pagination({ limit: 10, model: userModel, url: "/admin/user", ejs: "admin/user", where: {}, res: res, req: req }); });
|
然后给数据库添加多添加几条用户数据,重启服务器进行进入客户端刷新进行测试:
到此用户分页管理,暂时完成。
博客的分类管理
博客的分类管理主要是对博客的分类进行添加,修改和删除等。管理员用户在控制台进行相应的操作,服务器端则将对数据库进行相应的操作。
分类的数据结构
先创建博客文章分类在 mongodb 中的存储结构,在 schemas 中新建一个文件 categories.js 用来限制文章分类的数据结构,其代码如下:
1 2 3 4 5 6 7 8 9 10 11 12
| const mongoose = require("mongoose");
module.exports = new mongoose.Schema({ name: String });
|
创建了分类的Schema对象后,我们通过Schema对象来创建文章分类的数据模型,通过数据模型来对数据库进行相应的操作。在 models 文件夹中新建一个 category.js 来当做文章分类的模型,在 category.js 中添加如下代码:
1 2 3 4 5 6 7
| const mongoose = require("mongoose");
const categoriesSchema = require("../schemas/categories");
module.exports = mongoose.model("category", categoriesSchema);
|
分类管理首页
在文章分类的模型创建完毕后,就可以创建分类管理的首页了,分类管理首页的路由设计为 GET /admin/crtegory 。我们新建一个分类管理首页的视图文件,在 /views/admin 目录下,新建一个 /categroy/index.ejs 的文件,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <%- include("../header") %>
<div class="container-fluid"> <ol class="breadcrumb"> <li><a href="/admin">管理首页</a></li> <li class="active">分类首页</li> </ol> <table class="table table-hover table-striped table-bordered"> <thead> <tr> <th>ID</th> <th>分类名称</th> <th>操作</th> </tr> </thead> <tbody> <%for(let i = 0, len = docs.length; i < len; i++){%> <tr> <td><%=docs[i]._id%></td> <td><%=docs[i].name%></td> <td> <a href="/admin/category/edit?id=<%=docs[i]._id%>" class="btn btn-default">修改</a> <a href="/admin/category/delete?id=<%=docs[i]._id%>" class="btn btn-danger">删除</a> </td> </tr> <%}%> </tbody> </table> </div>
<%- include("../../pagination") %> </body> </html>
|
重启服务器刷新浏览器客户端,进入后台管理首页,点击分类管理:
分类管理的添加
新建一个文章分类添加的页面 /views/admin/category/add.ejs 并添加如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <%- include("../header") %>
<div class="container-fluid"> <ol class="breadcrumb"> <li><a href="/admin">管理首页</a></li> <li><a href="/admin/category">分类首页</a></li> <li class="active">分类添加</li> </ol> <form method="post"> <div class="form-group"> <label for="name">分类名称</label> <input type="text" class="form-control" id="name" name="name" placeholder="请输入分类名称"> </div> <button type="submit" class="btn btn-primary">提交</button> </form> </div> </body> </html>
|
同时在后台路由文件 routes/admin.js 中对其进行路由配置,分类添加的界面路由为 GET /admin/categorry/add 添加如下代码:
1 2 3 4 5 6 7
| // 分类添加的首页 router.get("/category/add", (req, res, next) => { // 渲染分类添加模板 res.render("admin/category/add", { userInfo: req.userInfo }); });
|
此时,添加文章的页面已经被渲染出来了,但是我们并没有编写保存的处理。前端使用 POST 方式向服务器提交数据,在后台进行相应的验证后,若不出错则保存进入数据库中。但在此之前,我们需要一个提示的界面,以此来提示用户相应的信息。
在 /views/admin 中新建一个 error.ejs 和 success.ejs 分别用来提示用户操作的成功与失败。
error.ejs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <%- include('header') %>
<div class="container-fluid"> <ol class="breadcrumb"> <li><a href="/admin">管理首页</a></li> <li class="active">错误提示</li> </ol> <div class="panel panel-danger"> <div class="panel-heading"> <h3 class="panel-title">错误提示</h3> </div> <div class="panel-body"> <%=message%> </div> <div class="panel-footer"> <%if(url){%> <a href="<%=url%>">跳转</a> <%}else{%> <a href="javascript:window.history.back();">返回上一步</a> <%}%> </div> </div> </div> </body> </html>
|
success.ejs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <%- include('header') %>
<div class="container-fluid"> <ol class="breadcrumb"> <li><a href="/admin">管理首页</a></li> <li class="active">成功提示</li> </ol> <div class="panel panel-success"> <div class="panel-heading"> <h3 class="panel-title">成功提示</h3> </div> <div class="panel-body"> <%=message%> </div> <div class="panel-footer"> <%if(url){%> <a href="<%=url%>">跳转</a> <%}else{%> <a href="javascript:window.history.back();">返回上一步</a> <%}%> </div> </div> </div> </body> </html>
|
最后我们再 /routes/admin.js 中添加如下代码,对前面添加的分类数据进行保存,保存的路由为 POST /category/add :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| router.post("/category/add", (req, res, next) => { let name = req.body.name || "";
if (name === "") { res.render("admin/error", { userInfo: req.userInfo, url: null, message: "分类名称不能为空!" }); return; } categoryModel.findOne({name: name}, (err, docs) => { if (docs) { res.render("admin/error", { userInfo: req.userInfo, url: null, message: "该分类名称已存在!" }); return; } else { categoryModel.create({ name: name }, (err) => { if (!err) { res.render("admin/success", { userInfo: req.userInfo, message: "添加成功!", url: "/admin/category" }); return; } }); } }); });
|
最后重启服务器进行测试:
分类的添加完成。
分类管理的修改及删除
分类的修改是通过 GET 方式将需要修改的分类提交到服务器,然后服务器渲染出其修改的界面,管理员再对该数据进行相应的修改。
在 /views/admin/category 目录下新建一个 ejs 模板,分类修改的界面,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <%- include("../header") %>
<div class="container-fluid"> <ol class="breadcrumb"> <li><a href="/admin">管理首页</a></li> <li><a href="/admin/category">分类首页</a></li> <li class="active">分类修改</li> </ol> <form method="post"> <div class="form-group"> <label for="name">请输入新名称</label> <input type="text" class="form-control" id="name" name="name" placeholder="请输入分类名称" value="<%=category.name%>"> </div> <button type="submit" class="btn btn-default">提交</button> </form> </div> </body> </html>
|
接着在后台管理的路由文件 admin.js 中添加分类修改的路由 GET /admin/category/edit 处理,添加代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| router.get("/category/edit", (req, res, next) => { let id = req.query.id || ""; categoryModel.findOne({_id: id}, (err, category) => { if (category) { res.render("admin/category/edit", { userInfo: req.userInfo, category: category }); } else { res.render("admin/error", { userInfo: req.userInfo, url: null, message: "该分类不存在!" }); } }); });
|
修改界面的路由完成之后,我们再写分类修改的保存,修改的保存是以 POST 的方式向服务器提交数据。在 admin.js 中添加如下代码处理前端提交过来的数据进行保存:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| router.post("/category/edit", (req, res, next) => { let id = req.query.id; let name = req.body.name;
categoryModel.findById(id, (err, category) => { if (category) { if (name === category.name) { res.render("admin/success", { url: "/admin/category", userInfo: req.userInfo, message: "修改成功!" }); return; } categoryModel.findOne({ _id: {$ne: id}, name: name }, (err, docs) => { if (docs) { res.render("admin/error", { userInfo: req.userInfo, url: null, message: "该分类已存在!" }); return; } else { categoryModel.update({_id: id}, {$set: {name: name}}, (err) => { if (!err) { res.render("admin/success", { userInfo: req.userInfo, url: "/admin/category", message: "修改成功!" }); return; } else { res.render("admin/error", { userInfo: req.userInfo, url: null, message: "修改失败!" }); return; } }); } }); } else { res.render("admin/error", { userInfo: req.userInfo, url: null, message: "该分类不存在!" }); return; } }); });
|
分类的删除同样也是通过 GET 方式将需要删除分类的 id 提交到服务器进行数据库删除,分类删除的路由设计为 GET /admin/category/delete 。在 admin.js 添加如下代码,处理删除分类逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| router.get("/category/delete", (req, res, next) => { let id = req.query.id || ""; categoryModel.remove({_id: id}, (err) => { if (!err) { res.render("admin/success", { url: "/admin/category", userInfo: req.userInfo, message: "删除成功!" }); } else { res.render("admin/error", { url: null, userInfo: req.userInfo, message: "删除失败!" }); } }); });
|
最后重启服务器,在客户端中进行测试:
好了,到此分类的添加,删除,修改基本上已经完成了。