类型详情页面

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

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

控制器

打开 /controllers/genreController.js 并将 Book 模块导入到文件顶部(文件应该已经 require()Genre 模块和 "express-async-handler")。

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

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

js
// Display detail page for a specific Genre.
exports.genre_detail = asyncHandler(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,
    genre_books: booksInGenre,
  });
});

我们首先使用 Genre.findById() 获取特定 ID 的类型信息,并使用 Book.find() 获取所有具有相同关联类型 ID 的书籍记录。由于这两个请求彼此之间没有依赖关系,因此我们使用 Promise.all() 并行运行数据库查询(这种并行运行查询的方法在 首页 中演示过)。

我们对返回的 Promise 进行 await 操作,并在其稳定后检查结果。如果类型不存在于数据库中(即可能已被删除),那么 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://127.0.0.1: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 是否有效。

后续步骤