类型详情页

类型详情页需要使用其自动生成的 _id 字段值作为标识符来显示特定类型实例的信息。所需类型记录的 ID 编码在 URL 的末尾,并根据路由定义(/genre/:id)自动提取。然后在控制器中通过请求参数 req.params.id 访问它。

页面应显示类型名称以及该类型下所有图书的列表,并包含指向每本图书详情页的链接。

控制器

打开 /controllers/genreController.js 并在文件顶部引入 Book 模块(文件应该已经 require()Genre 模块)。

js
const Book = require("../models/book");

找到导出的 genre_detail() 控制器方法,并用以下代码替换它。

js
// Display detail page for a specific Genre.
exports.genre_detail = async (req, res, next) => {
  // Get details of genre and all associated books (in parallel)
  const [genre, booksInGenre] = await Promise.all([
    Genre.findById(req.params.id).exec(),
    Book.find({ genre: req.params.id }, "title summary").exec(),
  ]);
  if (genre === null) {
    // No results.
    const err = new Error("Genre not found");
    err.status = 404;
    return next(err);
  }

  res.render("genre_detail", {
    title: "Genre Detail",
    genre,
    genre_books: booksInGenre,
  });
};

我们首先使用 Genre.findById() 获取特定 ID 的类型信息,并使用 Book.find() 获取所有具有相同关联类型 ID 的图书记录。由于两个请求不互相依赖,我们使用 Promise.all() 并行执行数据库查询(在 主页 中演示了并行查询的相同方法)。

我们 await 返回的 promise,一旦它得到解决,我们就检查结果。如果数据库中不存在该类型(即可能已被删除),那么 findById() 将成功返回但没有结果。在这种情况下,我们希望显示一个“未找到”页面,因此我们创建一个 Error 对象并将其传递给链中的 next 中间件函数。

注意: 传递给 next 中间件函数的错误会传播到我们的错误处理代码(这是在我们 生成应用骨架 时设置的)。有关更多信息,请参阅 处理错误处理路由函数中的错误和异常)。

如果找到 genre,则调用 render() 来显示视图。视图模板是 genre_detail (.pug)。标题、genrebooksInGenre 的值通过对应的键(titlegenregenre_books)传递到模板中。

视图

创建 /views/genre_detail.pug 并用以下文本填充它

pug
extends layout

block content

  h1 Genre: #{genre.name}

  div(style='margin-left:20px;margin-top:20px')

    h2(style='font-size: 1.5rem;') Books
    if genre_books.length
      dl
        each book in genre_books
          dt
            a(href=book.url) #{book.title}
          dd #{book.summary}
    else
      p This genre has no books.

该视图与我们所有其他模板非常相似。主要区别在于,我们不使用传递的 title 作为第一个标题(尽管它在底层的 layout.pug 模板中用于设置页面标题)。

它看起来怎么样?

运行应用程序,然后在浏览器中打开 https://:3000/。选择“所有类型”链接,然后选择其中一个类型(例如,“奇幻”)。如果一切设置正确,您的页面应该看起来像下面的截图。

Genre Detail Page - Express Local Library site

注意: 如果 req.params.id(或任何其他 ID)无法强制转换为 mongoose.Types.ObjectId(),您可能会遇到与下方类似的错误。

bash
Cast to ObjectId failed for value " 59347139895ea23f9430ecbb" at path "_id" for model "Genre"

最可能的原因是传递给 mongoose 方法的 ID 实际上不是一个 ID。可以使用 Mongoose.prototype.isValidObjectId() 来检查特定 ID 是否有效。

后续步骤