文章分类管理

前面我们完成了基本的文章增删改查,这节我们来实现文章分类管理

新建分类 GET category/new POST category/new

分类列表 GET /category

删除文章 GET /category/:id/detele

对于分类管理来说,需要管理员才可以操作。即 UserModel isAdmin: true (直接手动在数据库中更改某个user的isAdmin字段为true即可 )

分类模型设计

和之前一样,还是先来设计模型,我们只需要 title:分类名如”前端“,name:用来在url中简洁的显示如”frontend”,desc:分类的描述。当然你只要个 title 字段也是可以的

  1. // models/category.js
  2. const mongoose = require('mongoose')
  3. const Schema = mongoose.Schema
  4. const CategorySchema = new Schema({
  5. name: {
  6. type: String,
  7. required: true
  8. },
  9. title: {
  10. type: String,
  11. required: true
  12. },
  13. desc: {
  14. type: String
  15. },
  16. meta: {
  17. createAt: {
  18. type: Date,
  19. default: Date.now()
  20. }
  21. }
  22. })
  23. module.exports = mongoose.model('Category', CategorySchema)

修改下models/posts.js 新增一个category字段

  1. ...
  2. category: {
  3. type: Schema.Types.ObjectId,
  4. ref: 'Category'
  5. }
  6. ...

分类管理主页

category

新建分类管理控制器 routes/category.js,增加一个list方法来展示渲染分类管理的主页

  1. const CategoryModel = require('../models/category')
  2. module.exports = {
  3. async list (ctx, next) {
  4. const categories = await CategoryModel.find({})
  5. await ctx.render('category', {
  6. title: '分类管理',
  7. categories
  8. })
  9. }
  10. }

接着编写分类管理的前端界面 views/category.html

  1. {% extends 'views/base.html' %}
  2. {% block body %}
  3. {% include "views/components/header.html" %}
  4. <div class="container margin-top">
  5. <div class="columns">
  6. <div class="column is-8 is-offset-2">
  7. <a href="/category/new" class="button is-small is-primary">新建分类</a>
  8. <table class="table margin-top is-bordered is-striped is-narrow is-hoverable is-fullwidth">
  9. <thead>
  10. <tr>
  11. <th>name</th>
  12. <th>分类名</th>
  13. <th>操作</th>
  14. </tr>
  15. </thead>
  16. <tbody>
  17. {% for category in categories %}
  18. <tr>
  19. <td>{{category.name}}</td>
  20. <td>{{category.title}}</td>
  21. <td>
  22. <a href="/category/{{category._id}}/delete" class="button is-small is-danger">删除</a>
  23. </td>
  24. </tr>
  25. {% endfor %}
  26. </tbody>
  27. </table>
  28. </div>
  29. </div>
  30. </div>
  31. {% endblock %}

现在打开这个页面并没有分类,因为我们还没有添加分类,接下来实现新增分类功能

新增分类

在category控制器中新建一个create方法

  1. // routes/category.js
  2. async create (ctx, next) {
  3. if (ctx.method === 'GET') {
  4. await ctx.render('create_category', {
  5. title: '新建分类'
  6. })
  7. return
  8. }
  9. await CategoryModel.create(ctx.request.body)
  10. ctx.redirect('/category')
  11. }

访问 http://localhost:3000/category/new 将返回create_category.html页面

  1. {% extends 'views/base.html' %}
  2. {% block body %}
  3. {% include "views/components/header.html" %}
  4. <div class="container margin-top">
  5. <div class="columns">
  6. <div class="column is-8 is-offset-2">
  7. <form action="/category/new" method="POST">
  8. <div class="field">
  9. <label class="label">分类名</label>
  10. <div class="control">
  11. <input name="name" class="input" type="text" placeholder="frontend">
  12. </div>
  13. </div>
  14. <div class="field">
  15. <label class="label">分类标题</label>
  16. <div class="control">
  17. <input name="title" class="input" type="text" placeholder="前端">
  18. </div>
  19. </div>
  20. <div class="field">
  21. <label class="label">描述</label>
  22. <div class="control">
  23. <textarea name="desc" class="textarea" placeholder="Textarea"></textarea>
  24. </div>
  25. </div>
  26. <button class="button is-primary">新建分类</button>
  27. </form>
  28. </div>
  29. </div>
  30. </div>
  31. {% endblock %}

create_category

在接受到 post 请求时,将分类数据存入数据库

删除分类

删除的功能和删除文章、删除评论基本一样

  1. async destroy (ctx, next) {
  2. await CategoryModel.findByIdAndRemove(ctx.params.id)
  3. ctx.flash = { success: '删除分类成功' }
  4. ctx.redirect('/category')
  5. }

新建文章时指定分类

前面完成了分类管理,我们需要把文章指定分类,修改views/create.htmlviews/edit.html 增加一个分类选择框。

  1. ...
  2. <div class="right-box">
  3. <div class="select is-small">
  4. <select name="category">
  5. <option disabled="disabled">分类</option>
  6. {% for category in categories %}
  7. <option value={{category._id}}>{{category.title}}</option>
  8. {% endfor %}
  9. </select>
  10. </div>
  11. <button type="submit" class="button is-small is-primary">发布</button>
  12. </div>
  13. ...

同时修改routes/post.js 的create和edit方法,把分类信息传给模板

  1. ...
  2. const categories = await CategoryModel.find({})
  3. await ctx.render('create', {
  4. title: '新建文章',
  5. categories
  6. })
  7. ...

新建一篇文章,看看数据库,多了一个 category 字段存着分类的id。

  1. // routes/posts.js
  2. async show (ctx, next) {
  3. const post = await PostModel.findById(ctx.params.id)
  4. .populate([
  5. { path: 'author', select: 'name' },
  6. { path: 'category', select: ['title', 'name'] }
  7. ])
  8. const comments = await CommentModel.find({ postId: ctx.params.id })
  9. .populate({ path: 'from', select: 'name' })
  10. await ctx.render('post', {
  11. title: post.title,
  12. post,
  13. comments
  14. })
  15. },

修改下文章详情页来展示分类名

  1. // views/posts.html
  2. {{marked(post.content) | safe}}
  3. <p>
  4. <a href="/posts?c={{post.category.name}}" class="tag is-primary">{{post.category.title}}</a>
  5. </p>

权限控制

现在每一个用户登录上去都可以管理分类,我们只想要管理员可以管理。在前面的章节中已经实现了权限控制,只要在相应的路由上使用 isAdmin 即可

  1. // routes/index.js
  2. ...
  3. router.get('/category', isAdmin, require('./category').list)
  4. router.get('/category/new', isAdmin, require('./category').create)
  5. router.post('/category/new', isAdmin, require('./category').create)
  6. router.get('/category/:id/delete', isAdmin, require('./category').destroy)

展示分类文章

前面基本上已经实现了分类功能,现在来实现根据URL参数返回相应的分类文章如 /posts?c=nodejs 返回分类为Node.js 的文章

  1. // routes/posts.js
  2. async index (ctx, next) {
  3. const cname = ctx.query.c
  4. let cid
  5. if (cname) {
  6. const cateogry = await CategoryModel.findOne({ name: cname })
  7. cid = cateogry._id
  8. }
  9. const query = cid ? { category: cid } : {}
  10. const posts = await PostModel.find(query)
  11. await ctx.render('index', {
  12. title: 'JS之禅',
  13. posts
  14. }
  15. }

修改 posts.js中的index 方法,通过 ctx.query 获取解析的查询字符串, 当没有查询字符串时,返回一个空对象。然后再去通过name去查出分类id,最后通过分类id去查询文章