Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zh-cn: update the translation of "Create genre form" #24220

Merged
merged 55 commits into from
Nov 1, 2024
Merged
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
0b86ea0
update create_genre_form doc
ikenk Oct 25, 2024
9f99424
[ko] sync translated content (#24211)
mdn-bot Oct 24, 2024
6dec4bb
Update index.md one missing word in french translation (#24210)
hoel44 Oct 24, 2024
0986649
Typofix for Notions de base en HTML: "Comment ..." => "Comme ..." (#2…
FacuBotta Oct 24, 2024
b1b6036
Fix #23755 (#23964)
SphinxKnight Oct 24, 2024
b349fb2
[ja] sync translated content
hiroya-uga Oct 24, 2024
572d291
2024/10/02 時点の英語版に基づき更新
mfuji09 Oct 20, 2024
15aa53b
2024/10/09 時点の英語版に基づき更新
mfuji09 Oct 20, 2024
c512425
2024/09/05 時点の英語版に基づき新規翻訳
mfuji09 Oct 20, 2024
9e64393
2024/09/29 時点の英語版に基づき更新
mfuji09 Oct 20, 2024
6aba2c1
2024/09/27 時点の英語版に基づき更新
mfuji09 Oct 20, 2024
04b5f82
2024/09/27 時点の英語版に基づき更新
mfuji09 Oct 20, 2024
a47a153
2024/09/23 時点の英語版に基づき更新 (#24158)
mfuji09 Oct 25, 2024
8809e9e
2024/10/11 時点の英語版に基づき更新
mfuji09 Oct 20, 2024
206f2eb
2024/10/16 時点の英語版に基づき更新
mfuji09 Oct 20, 2024
a642f5c
2024/04/17 時点の英語版に基づき新規翻訳
mfuji09 Oct 20, 2024
6b11f48
2024/10/09 時点の英語版に基づき更新 (#24167)
mfuji09 Oct 25, 2024
7f00afc
2024/07/26 時点の英語版に基づき新規翻訳
mfuji09 Oct 21, 2024
70529e6
2024/10/15 時点の英語版に基づき更新
mfuji09 Oct 21, 2024
7f30ff7
2024/10/11 時点の英語版に基づき更新
mfuji09 Oct 21, 2024
566620c
[es] Add missing 'element copy event' page (#24223)
rafael-encinas Oct 25, 2024
d939ba7
[zh-cn]: fix sidebar missing for Web/Performance/* (#24224)
Yanko1013 Oct 26, 2024
2f55cb1
[zh-cn]: create doc for HTMLMarqueeElement (#24087)
fuchunhui Oct 26, 2024
d7434e6
2024/10/11 時点の英語版に基づき更新 (#24200)
mfuji09 Oct 26, 2024
5b227d5
2024/09/03 時点の英語版に基づき更新
mfuji09 Oct 22, 2024
df8671e
2024/10/16 時点の英語版に基づき更新
mfuji09 Oct 22, 2024
7b7dbf1
日本語索引項目を追加
mfuji09 Oct 22, 2024
da09f3e
Fix link paths
hiroya-uga Oct 26, 2024
6ef1e58
原文にない Compat 情報の削除
ihasq Oct 26, 2024
d601f5c
[ru] improve wording in `Web/HTTP/MIME_types` (#24222)
vbrovenk Oct 26, 2024
cc04d44
zh-cn: sync the translation of "event loop" (#24197)
familyboat Oct 27, 2024
5df52fc
[ko] 띄어쓰기와 오타 수정 (#24258)
ICE0208 Oct 27, 2024
8aedd65
[ko] Intl.Locale.prototype.calendar 신규 번역 (#22084)
wisedog Oct 27, 2024
a1e5f9f
[ko] TypedArray.prototype.filter() 신규 번역 외 (#22136)
wisedog Oct 27, 2024
107c23b
[ko] Intl.Locale.prototype.getTextInfo() 신규 번역 외 (#22295)
wisedog Oct 27, 2024
fd0fe33
[ko] TypedArray.from() 신규 번역 외 (#22330)
wisedog Oct 27, 2024
b839938
[ko] DataView.prototype.getFloat16() 신규 번역 외 (#22391)
wisedog Oct 27, 2024
f8e6c04
[ko] SharedArrayBuffer.prototype.growable 신규 번역 외 (#22396)
wisedog Oct 27, 2024
f19e82f
[ko] Iterator.prototype.filter() 신규 번역 (#22881)
wisedog Oct 27, 2024
5af3e5b
ko: add translation for `Element: mousedown event` (#22886)
psst54 Oct 27, 2024
3bfc164
[ko] Iterator.prototype.find() 신규 번역 (#22897)
wisedog Oct 27, 2024
e4b709a
[ko] SharedArrayBuffer[Symbol.species] 신규 번역 외 (#22903)
wisedog Oct 27, 2024
05b6d39
[ko] double colon placeholder 번역 (#22925)
givvemee Oct 27, 2024
09aadd4
[ko] accent color 신규 번역 (#23154)
givvemee Oct 27, 2024
2f73fa5
[ko] ExtendableEvent: waitUntil() 메서드 신규 번역 (#23212)
etoile-j Oct 27, 2024
7785aad
[ko] css_color_adjustment 신규번역 (#23396)
eun-hak Oct 27, 2024
9aa87e9
[ko] htmlinputelement selectionend 신규 번역 (#23382)
givvemee Oct 27, 2024
512d448
fix: 브라우저 호환성 오타 수정 (#24262)
hochan222 Oct 27, 2024
16c3802
[ko] api/Navigator/clipboard 신규 번역 (#23599)
rosaceaee Oct 27, 2024
cc69a82
Bump mdast-util-from-markdown from 2.0.1 to 2.0.2 (#24266)
dependabot[bot] Oct 28, 2024
b53751d
根据建议进行了修改
ikenk Oct 29, 2024
5bfc332
Merge branch 'mdn:main' into update-Create_genre_form-doc
ikenk Oct 29, 2024
7e00d97
Merge branch 'main' into update-Create_genre_form-doc
yin1999 Nov 1, 2024
3c59c3b
minor fixes
yin1999 Nov 1, 2024
94cc447
Update index.md
yin1999 Nov 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,187 +1,188 @@
---
title: 创建种类表单
title: 创建类型表单
slug: Learn/Server-side/Express_Nodejs/forms/Create_genre_form
l10n:
sourceCommit: 8d5440dbd259fd6eea32b4f4a200f25257d1bf41
---

本章节演示如何定义我们的页面,创建`Genre` 物件(这是一个很好的起点,因为类型只有一个字段,它的名称`name`,没有依赖项)。像任何其他页面一样,我们需要设置路由,控制器和视图。
{{LearnSidebar}}

## 引入验证与无害化方法
本章节演示如何定义页面来创建 `Genre` 对象(这是一个很好的起点,因为 `Genre` 只有一个字段,即它的名称 `name`,并且没有依赖项)。与任何其他页面一样,我们需要设置路由、控制器和视图。

在我们的控制器中使用 _express-validator_ 验证器,我們必須导入我们想要从 **'express-validator/check**' 和 **'express-validator/filter**' 模块中使用的函数。
## 导入验证与清理方法

打开**/controllers/genreController.js**,并在文件顶部添加以下行:
要控制器中使用 _express-validator_,我们必须从 `'express-validator'` 模块中 _require_ 我们想使用的函数。

打开 **/controllers/genreController.js**,在文件顶部、路由处理器之前添加下方代码:

```js
const { body, validationResult } = require("express-validator/check");
const { sanitizeBody } = require("express-validator/filter");
const { body, validationResult } = require("express-validator");
```

## 控制器—get 路由
> [!NOTE]
> 此语法允许我们使用 `body` 和 `validationResult` 作为关联的中间件函数,正如你将在下面的 post 路由部分中看到的那样。它相当于:
>
> ```js
> const validator = require("express-validator");
> const body = validator.body;
> const validationResult = validator.validationResult;
> ```

## 控制器——get 路由

找到导出的`genre_create_get()` 控制器方法,并将其替换为以下代码。这只是渲染**genre_form.pug**视图,传递一个 title 变量
找到导出的 `genre_create_get()` 控制器方法,并将其替换为以下代码。这将渲染 **genre_form.pug** 视图,传递一个标题变量

```js
// Display Genre create form on GET.
exports.genre_create_get = function (req, res, next) {
// 呈现 GET 方法获取的 Genre 表单
exports.genre_create_get = (req, res, next) => {
res.render("genre_form", { title: "Create Genre" });
};
```

## 控制器—post 路由
请注意,这里我们将使用一个“普通”express 路由处理器替换我们在 [Express 教程 4:路由和控制器](/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes)中添加的占位异步处理器。我们不需要为该路由添加 `asyncHandler()` 包装器,因为它不包含任何可能引发异常的代码。

## 控制器——post 路由

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

```js
// Handle Genre create on POST.
// 处理 POST 方法创建的 Genre
exports.genre_create_post = [
// Validate that the name field is not empty.
body("name", "Genre name required").isLength({ min: 1 }).trim(),

// Sanitize (trim and escape) the name field.
sanitizeBody("name").trim().escape(),

// Process request after validation and sanitization.
(req, res, next) => {
// Extract the validation errors from a request.
// 验证及清理名称字段
body("name", "Genre name must contain at least 3 characters")
.trim()
.isLength({ min: 3 })
.escape(),

// 处理验证及清理过后的请求
asyncHandler(async (req, res, next) => {
// 从请求中提取验证时产生的错误信息
const errors = validationResult(req);

// Create a genre object with escaped and trimmed data.
var genre = new Genre({ name: req.body.name });
// 使用经去除空白字符和转义处理的数据创建一个类型对象
const genre = new Genre({ name: req.body.name });

if (!errors.isEmpty()) {
// There are errors. Render the form again with sanitized values/error messages.
// 出现错误。使用清理后的值/错误信息重新渲染表单
res.render("genre_form", {
title: "Create Genre",
genre: genre,
errors: errors.array(),
});
return;
} else {
// Data from form is valid.
// Check if Genre with same name already exists.
Genre.findOne({ name: req.body.name }).exec(function (err, found_genre) {
if (err) {
return next(err);
}

if (found_genre) {
// Genre exists, redirect to its detail page.
res.redirect(found_genre.url);
} else {
genre.save(function (err) {
if (err) {
return next(err);
}
// Genre saved. Redirect to genre detail page.
res.redirect(genre.url);
});
}
});
// 表格中的数据有效
// 检查是否存在同名的 Genre
const genreExists = await Genre.findOne({ name: req.body.name })
.collation({ locale: "en", strength: 2 })
.exec();
if (genreExists) {
// 存在同名的 Genre,则重定向到详情页面
res.redirect(genreExists.url);
} else {
await genre.save();
// 保存新创建的 Genre,然后重定向到类型的详情页面
res.redirect(genre.url);
}
}
},
}),
];
```

首先要注意的是,控制器不是单个中间件函数(带参数(`req, res, next`)),而是指定一组中间件函数。数组传递给路由器函数,并按顺序调用每个方法
首先需要注意的是,控制器不是单个中间件函数(带有参数 `(req, res, next)`),而是指定了中间件函数*数组*。该数组传递给路由器函数并依次执行各个方法

> [!NOTE]
> 这种方法是必需的,因为消毒/验证器是中间件功能。

数组中的第一个方法定义了一个验证器(`body`),来检查 name 字段是否为空(在执行验证之前调用`trim()`,以删除任何尾随/前导空格)。
> 这种方法是必要的,因为验证器是中间件函数。

数组中的第二个方法(`sanitizeBody()`),创建一个清理程序来调用`trim()`修剪名称字段和调用`escape()`转义任何危险的 HTML 字符。
数组中的第一个方法定义了一个 body 验证器(`body()`),用于验证和清理字段。这个方法使用 `trim()` 删除所有的首部/尾部空白,检查 _name_ 字段是否为空,然后使用 `escape()` 删除任何危险的 HTML 字符。

```js
// Validate that the name field is not empty.
body('name', 'Genre name required').isLength({ min: 1 }).trim(),

// Sanitize (trim and escape) the name field.
sanitizeBody('name').trim().escape(),
[
// 检验 name 字段不为空
body("name", "Genre name must contain at least 3 characters")
.trim()
.isLength({ min: 3 })
.escape(),
// …
];
```

> [!NOTE]
> 验证期间运行的清洁器不会修改请求。这就是为什么我们必须在上面的两个步骤中调用`trim()`!

在指定验证器和清理器之后,我们创建了一个中间件函数,来提取任何验证错误。我们使用`isEmpty()` 来检查验证结果中,是否有任何错误。如果有,那么我们再次渲染表单,传入我们的已清理种类对象和错误消息的数组(`errors.array()`)。
指定验证器后,我们创建一个中间件函数来提取任何验证错误。我们使用 `isEmpty()` 来检查验证结果是否有错误。如果有,我们就再次渲染表单,传入经过清理的类型对象和错误消息数组(`errors.array()`)。

```js
// Process request after validation and sanitization.
(req, res, next) => {

// Extract the validation errors from a request.
const errors = validationResult(req);

// Create a genre object with escaped and trimmed data.
var genre = new Genre(
{ name: req.body.name }
);

if (!errors.isEmpty()) {
// There are errors. Render the form again with sanitized values/error messages.
res.render('genre_form', { title: 'Create Genre', genre: genre, errors: errors.array()});
// 处理验证和清理之后的请求
asyncHandler(async (req, res, next) => {
// 从请求中提取验证错误
const errors = validationResult(req);

// 使用经去除空白字符和转义处理的数据创建一个类型对象
const genre = new Genre({ name: req.body.name });

if (!errors.isEmpty()) {
// 出现错误。使用清理后的值/错误信息重新渲染表单
res.render("genre_form", {
title: "Create Genre",
genre: genre,
errors: errors.array(),
});
return;
}
else {
// Data from form is valid.
... <save the result> ...
}
}
} else {
// 表单中的数据有效
// …
}
});
```

如果种类名称数据有效,那么我们检查,是否已存在具有相同名称的种类`Genre`(因为我们不想创建重复项)。
如果类型名称数据有效,那么我们执行不区分大小写的搜索,以查看是否存在具有相同名称的 `Genre`(因为我们不想创建仅字母大小写不同的重复或过于近似的记录,例如“Fantasy”、“fantasy”、“FaNtAsY”等等)。为了在搜索时忽略掉大小写和重音,我们链式调用了 [`collation()`](<https://mongoosejs.com/docs/api/query.html#Query.prototype.collation()>) 方法,指定“en”的区域设置和 2 的强度(更多信息请参阅 MongoDB 的[排序规则](https://www.mongodb.com/docs/manual/reference/collation/)主题)。

如果是,我们会重定向到现有种类的详细信息页面。如果没有,我们保存新种类,并重定向到其详细信息页面
如果匹配名称的 `Genre` 已经存在,我们将重定向到其详情页面。如果不存在,我们则保存新种类并重定向到其详情页面。请注意,这里我们 `await` 数据库的查询结果,遵循与其他路由处理器相同的模式

```js
// Check if Genre with same name already exists.
Genre.findOne({ name: req.body.name }).exec(function (err, found_genre) {
if (err) {
return next(err);
}
if (found_genre) {
// Genre exists, redirect to its detail page.
res.redirect(found_genre.url);
} else {
genre.save(function (err) {
if (err) {
return next(err);
}
// Genre saved. Redirect to genre detail page.
res.redirect(genre.url);
});
}
});
// 检查是否存在同名的 Genre
const genreExists = await Genre.findOne({ name: req.body.name })
.collation({ locale: "en", strength: 2 })
.exec();
if (genreExists) {
// 存在同名的 Genre,则重定向到详情页面
res.redirect(genreExists.url);
} else {
await genre.save();
// 保存新创建的 Genre,然后重定向到详情页面
res.redirect(genre.url);
}
```

在我们所有的 `POST`控制器中,都使用了相同的模式:我们运行验证器,然后运行消毒器,然后检查错误,并使用错误信息重新呈现表单,或保存数据
我们所有的 post 控制器中都使用了相同的模式:运行验证器(带有清理功能),然后检查错误并重新渲染带有错误信息的表单或保存数据

## 视图

当我们创建一个新的种类`Genre`时,在`GET`和`POST`控制器/路由中,都会呈现相同的视图(稍后在我们更新种类`Genre`时也会使用它)。

在`GET`情况下,表单为空,我们只传递一个 title 变量。在`POST`情况下,用户先前输入了无效数据 - 在种类变量`genre`中,我们传回了输入数据的已清理版本,并且在`errors`变量中,我们传回了一组错误消息。
当我们创建新的种类 `Genre` 时,会在 `GET` 和 `POST` 控制器/路由中渲染相同的视图(稍后当我们*更新*种类时也会使用它),在 `GET` 情况下,表单为空,我们只传递一个标题变量。在 `POST` 情况下,用户之前输入了无效数据——对于 `genre` 变量,我们回传经清理后的输入数据,对于错误变量,则回传一组错误消息。下面的代码显示了在两种情况下渲染模板的控制器代码。

```js
// 渲染 GET 路由
res.render("genre_form", { title: "Create Genre" });

// 渲染 POST 路由
res.render("genre_form", {
title: "Create Genre",
genre: genre,
genre,
errors: errors.array(),
});
```

创建 **/views/genre_form.pug**,并复制下面的文本。

```plain
```pug
extends layout

block content

h1 #{title}

form(method='POST' action='')
form(method='POST')
div.form-group
label(for='name') Genre:
input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name))
input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' required value=(undefined===genre ? '' : genre.name) )
button.btn.btn-primary(type='submit') Submit

if errors
Expand All @@ -190,32 +191,32 @@ block content
li!= error.msg
```

从我们之前的教程中,可以很好地理解这个模板的大部分内容。首先,我们扩展 **layout.pug**基本模板,并覆盖名为“**content**”的块`block`。然后我们有一个标题,我们从控制器传入的标题`title`(通过`render()` 方法)。
从我们之前的教程中可以很好地理解这个模板的大部分内容。首先,我们扩展 **layout.pug** 基本模板并覆盖名为“**content**”`block`。然后我们使用从控制器传入 `title`(通过 `render()` 方法)创建了一个标题

接下来,我们有 HTML 表单的 Pug 代码,它使用`POST`方法将数据发送到服务器,并且因为操作`action`是空字符串,所以将数据发送到与页面相同的 URL。
接下来,pug 代码中的 HTML 表单部分则会使用 `method="POST"` 方法将数据发送到服务器,并且由于 `action` 是空字符串,因此会将数据发送到与页面相同的 URL。

表单定义了一个名为“name”的“text”类型的必填字段。字段的默认值,取决于是否定义了种类变量`genre`。如果从`GET`路由调用,它将为空,因为这是一个新表单。如果从`POST`路由调用,它将包含用户最初输入的(无效)值。
该表单定义了一个名为“name”的“text”类型的必填字段。该字段的默认值取决于是否定义了种类 `genre` 变量。如果从 `GET` 路由调用,它将为空,因为这是一个新表单。如果从 `POST` 路由调用,它将包含用户最初输入的(无效)值。

页面的最后一部分是错误代码。如果已定义错误变量,则只会打印错误列表(换句话说,当模板在`GET`路由上呈现时,此部分不会出现)。
该页面的最后一部分是错误代码。如果已定义错误变量,则只会打印错误列表(换句话说,当模板在 `GET` 路由上呈现时,此部分将不会出现)。

> [!NOTE]
> 这只是呈现错误的一种方法。你还可以从错误变量中,获取受影响字段的名称,并使用这些,来控制错误消息的呈现位置,以及是否应用自定义 CSS 等。
> 这只是呈现错误的一种方法。你还可以从错误变量中获取受影响字段的名称,并使用它们来控制错误消息的呈现位置以及是否应用自定义 CSS 等。

## 它看起來像是
## 它看起来像是

运行应用程序,打开浏览器到<http://localhost:3000/>,然后选择 Create new genre 链接。如果一切设置正确,你的网站应该类似于以下屏幕截图。输入值后,应保存该值,你将进入种类详细信息页面
运行应用程序,打开浏览器到 `http://localhost:3000/`,然后选择 _Create new genre_ 链接。如果一切设置正确,你的网站应该类似于下方的屏幕截图。输入值后,应将其保存,并且你将进入种类详情页面

![Genre Create Page - Express Local Library site](locallibary_express_genre_create_empty.png)
![种类创建页面——Express 本地图书馆网站](locallibary_express_genre_create_empty.png)

我们针对服务器端,验证的唯一错误是种类字段不能为空。下面的屏幕截图,显示了如果你没有提供种类(以红色突出显示),错误列表会是什么样子。
我们在服务器端验证的唯一错误是种类字段必须至少包含三个字符。下面的屏幕截图显示了如果你提供仅包含一个或两个字符的类型(以黄色突出显示),错误列表会是什么样子。

![](locallibary_express_genre_create_error.png)
![本地图书馆应用的创建种类部分。左栏有一个垂直导航栏。右侧部分是创建一个新种类,标题为“创建种类”。有一个标有“种类”的输入字段。底部有一个提交按钮。“提交”按钮正下方有一条错误消息,上面写着“需要类型名称”。本文作者强调了该错误消息。表格中没有视觉指示表明类型是必需的,也没有错误消息仅在出现错误时出现。](locallibary_express_genre_create_error.png)

> [!NOTE]
> 我们的验证使用`trim()`来确保不接受空格作为种类名称。我们还可以在表单中 的字段定义中,添加值`required='true'`,来验证客户端字段不为空
> 我们的验证使用 `trim()` 来确保不接受空格作为种类名称。我们还对表单中​​字段定义添加 `required` [布尔属性](/zh-CN/docs/Glossary/Boolean/HTML)以在客户端测验证该字段不为空
>
> ```js
> input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name), required='true' )
> ```pug
> input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' required value=(undefined===genre ? '' : genre.name) )
> ```

## 下一步
Expand Down