(七)项目功能的实现之文章管理
BLOG -- 源码目录
└── model -- 数据库操作
└──article.js 文章规则
├── public -- 静态资源
└── route -- 路由
└── admin --博客管理
├── article.js --文章列表页
├── article-add.js --新增文章
├── userPage.js --用户列表页
├── user-edit.js --用户管理页
└── article-edit.js --文章管理页
└── admin.js --博客管理页面路由
└── views -- 模板
└── admin --博客管理页面art模板
└── common --公共模板
└── aside.art --侧边栏
├── article.art --文章列表模板
└── article-edit.art --文章管理模板
└── app.js -- 创建网站服务
admin.js
- 添加文章列表页面和文章编辑页面的路由
- 添加实现文章添加功能的路由
// 文章列表页面路由
admin.get('/article', require('./admin/article'));
// 文章编辑页面路由
admin.get('/article-edit', require('./admin/article-edit'));
// 实现文章添加功能的路由
admin.post('/article-add', require('./admin/article-add'))
article.js
- 渲染article.art的模板文件
- app.locals.currentlink标识表示当前访问的是文章列表管理页面
- files中存储了cover就是所有上传的路径
- 将文章集合构造函数导入,查询所有文章数据
- 进行多集合联合查询,populate()方法,查询到了作者的全部方法作为对象,所以可以通过.username来修改作者的格式为登录用户而不是id值
// 将文章集合的构造函数导入到当前文件中
const { Article } = require('../../model/article');
module.exports = async (req, res) => {
// 标识 标识当前访问的是文章列表页面
req.app.locals.currentLink = 'article';
// 查询所有文章数据
let articles = await Article.find().populate('author');
// res.send(articles);
// 渲染文章列表页面模板
res.render('admin/article.art', {
articles: articles
});
}
article-edit.js
- 渲染article-edit.art的模板文件
- app.locals.currentlink标识表示当前访问的是文章管理页面
module.exports = (req, res) => {
// 标识 标识当前访问的是文章管理页面
req.app.locals.currentLink = 'article';
res.render('admin/article-edit.art');
}
aside.art
- 为用户管理和文章管理添加对应的用户跳转herf
- 对于item后面是否添加active进行判断,判断条件是是否是选中的是currentlink是article还是user
<!-- 侧边栏 -->
<div class="aside fl">
<ul class="menu list-unstyled">
<li>
<a class="item {{currentLink == 'user' ? 'active' : ''}}" href="/admin/user">
<span class="glyphicon glyphicon-user"></span>
用户管理
</a>
</li>
<li>
<a class="item {{currentLink == 'article' ? 'active' : ''}}" href="/admin/article">
<span class="glyphicon glyphicon-th-list"></span>
文章管理
</a>
</li>
</ul>
<div class="cprt">
Powered by <a href="http://www.itheima.com/" target="_blank">黑马程序员</a>
</div>
</div>
<!-- 侧边栏 -->
userPage.js
- app.locals.currentlink标识表示当前访问的是用户管理页面
- user-edit.js同样也是不做赘述
// 标识 标识当前访问的是用户管理页面
req.app.locals.currentLink = 'user';
article.js
文章集合创建:
- 引入mongoose模块
- 创建文章集合规则 articleSchema
- 根据规则创造集合mongoose.model
- 将集合规则作为模块成员进行导出
// 1.引入mongoose模块
const mongoose = require('mongoose');
// 2.创建文章集合规则
const articleSchema = new mongoose.Schema({
title: {
type: String,
maxlength: 20,
minlength: 4,
required: [true, '请填写文章标题']
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: [true, '请传递作者']
},
publishDate: {
type: Date,
default: Date.now
},
cover: {
type: String,
default: null
},
content: {
type: String
}
});
// 3.根据规则创建集合
const Article = mongoose.model('Article', articleSchema);
// 4.将集合做为模块成员进行导出
module.exports = {
Article
}
article.art
- 对于发布新文章按钮进行调准href到article-edit
- 去掉所有的tr标签,进行循环each articles使得变为动态数据,对于Id进行原文输出@
- 对于发布时间的格式进行处理
{{extend './common/layout.art'}}
{{block 'main'}}
{{include './common/header.art'}}
<!-- 主体内容 -->
<div class="content">
{{include './common/aside.art'}}
<div class="main">
<!-- 分类标题 -->
<div class="title">
<h4>文章</h4>
<span>找到1篇文章</span>
<a href="/admin/article-edit" class="btn btn-primary new">发布新文章</a>
</div>
<!-- /分类标题 -->
<!-- 内容列表 -->
<table class="table table-striped table-bordered table-hover custom-table">
<thead>
<tr>
<th>ID</th>
<th>标题</th>
<th>发布时间</th>
<th>作者</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{{each articles.records}}
<tr>
<td>{{@$value._id}}</td>
<td>{{$value.title}}</td>
<td>{{dateFormat($value.publishDate, 'yyyy-mm-dd')}}</td>
<td>{{$value.author.username}}</td>
<td>
<a href="article-edit.html" class="glyphicon glyphicon-edit"></a>
<i class="glyphicon glyphicon-remove" data-toggle="modal" data-target=".confirm-modal"></i>
</td>
</tr>
{{/each}}
</tbody>
</table>
<!-- /内容列表 -->
<!-- 分页 -->
<ul class="pagination">
{{if articles.page > 1}}
<li>
<a href="/admin/article?page={{articles.page - 1}}">
<span>«</span>
</a>
</li>
{{/if}}
{{each articles.display}}
<li><a href="/admin/article?page={{$value}}">{{$value}}</a></li>
{{/each}}
{{if articles.page < articles.pages}}
<li>
<a href="/admin/article?page={{articles.page - 0 + 1}}">
<span>»</span>
</a>
</li>
{{/if}}
</ul>
<!-- /分页 -->
</div>
</div>
<!-- /主体内容 -->
<!-- 删除确认弹出框 -->
<div class="modal fade confirm-modal">
<div class="modal-dialog modal-lg">
<form class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
<h4 class="modal-title">请确认</h4>
</div>
<div class="modal-body">
<p>您确定要删除这篇文章吗?</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<input type="submit" class="btn btn-primary">
</div>
</form>
</div>
</div>
{{/block}}
article-edit.art
- 为表单添加请求方式method和请求地址action到article-add
- 对于标题、作者、发布时间、文章封面都添加对应的name属性
文件上传必须是二进制,enctype是指定表单数据的编码类型
- enctype 指定表单数据的编码类型
application/x-www-form-urlencoded
name=zhangsan&age=20
multipart/form-data 将表单数据编码成二进制类型
- enctype 指定表单数据的编码类型
在文章封面添加id值,如果想要一次性能选择多个在Input 后面可以添加mutiple则就可以一次选择多个了。为其添加onchange事件,选择文件上传空间,选择完之后进行文件的读取,用FileReader,因为readAsDataURL是异步的,
- 对图片添加Id值preview,并将文件处理的结果显示在页面当中
{{extend './common/layout.art'}}
{{block 'main'}}
{{include './common/header.art'}}
<!-- 主体内容 -->
<div class="content">
{{include './common/aside.art'}}
<div class="main">
<!-- 分类标题 -->
<div class="title">
<h4>5b9a716cb2d2bf17706bcc0a</h4>
</div>
<!--
enctype 指定表单数据的编码类型
application/x-www-form-urlencoded
name=zhangsan&age=20
multipart/form-data 将表单数据编码成二进制类型
-->
<!-- /分类标题 -->
<form class="form-container" action="/admin/article-add" method="post" enctype="multipart/form-data">
<div class="form-group">
<label>标题</label>
<input type="text" class="form-control" placeholder="请输入文章标题" name="title">
</div>
<div class="form-group">
<label>作者</label>
<input name="author" type="text" class="form-control" readonly value="{{@userInfo._id}}">
</div>
<div class="form-group">
<label>发布时间</label>
<input name="publishDate" type="date" class="form-control">
</div>
<div class="form-group">
<label for="exampleInputFile">文章封面</label>
<!--
multiple 允许用户一次性选择多个文件
-->
<input type="file" name="cover" id="file" >
<div class="thumbnail-waper">
<img class="img-thumbnail" src="" id="preview">
</div>
</div>
<div class="form-group">
<label>内容</label>
<textarea name="content" class="form-control" id="editor"></textarea>
</div>
<div class="buttons">
<input type="submit" class="btn btn-primary">
</div>
</form>
</div>
</div>
<!-- /主体内容 -->
{{/block}}
{{block 'script'}}
<script type="text/javascript">
// 选择文件上传控件
var file = document.querySelector('#file');
var preview = document.querySelector('#preview');
// 当用户选择完文件以后
file.onchange = function () {
// 1 创建文件读取对象
var reader = new FileReader();
// 用户选择的文件列表
// console.log(this.files[0])
// 2 读取文件
reader.readAsDataURL(this.files[0]);
// 3 监听onload事件
reader.onload = function () {
console.log(reader.result)
// 将文件读取的结果显示在页面中
preview.src = reader.result;
}
}
</script>
{{/block}}
article-add.js
body-parser只能接受普通表单的数据post,不能接受二进制数据的传输所以我们应该用哪个formidable来进行
npm install formidable
- 作用:解析表单,支持get请求参数,post请求参数、文件上传。
- 引入fomidable模块,创建表单解析对象,配置文件上传路径到public/uploads,写绝对路径,所以还要导入path模块
- 同时要保留上传文件的后缀,默认是不保留keepExtensions
- 对表单进行解析
- 对文件路径进行截取,uploads即后面的路径以public进行切割,利用split方法
- 引入文章集合的构造函数,利用Article.create方法来创造这些对象,是异步的
- 文章内容添加成功之后重定向文章列表页面
// 引入formidable第三方模块
const formidable = require('formidable');
const path = require('path');
const { Article } = require('../../model/article')
module.exports = (req, res) => {
// 1.创建表单解析对象
const form = new formidable.IncomingForm();
// 2.配置上传文件的存放位置
form.uploadDir = path.join(__dirname, '../', '../', 'public', 'uploads');
// 3.保留上传文件的后缀
form.keepExtensions = true;
// 4.解析表单
form.parse(req, async (err, fields, files) => {
// 1.err错误对象 如果表单解析失败 err里面存储错误信息 如果表单解析成功 err将会是null
// 2.fields 对象类型 保存普通表单数据
// 3.files 对象类型 保存了和上传文件相关的数据
// res.send(files.cover.path.split('public')[1])
await Article.create({
title: fields.title,
author: fields.author,
publishDate: fields.publishDate,
cover: files.cover.path.split('public')[1],
content: fields.content,
});
// 将页面重定向到文章列表页面
res.redirect('/admin/article');
})
// res.send('ok');
}
app.js
- 引入dataFormat来进行日期的处理
同时引入art-template向模板中导入外部变量dataFormat,我们就可以在其他模板中调用这个方法了
// 导入dateformat第三方模块 const dateFormat = require('dateformat'); // 导入art-tempate模板引擎 const template = require('art-template'); // 向模板内部导入dateFormate变量 template.defaults.imports.dateFormat = dateFormat;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。