这里先说下上节作业,将博客按创建时间倒序显示。
修改posts.service.ts的formatPosts方法,在最后添加排序:
posts.sort((a, b) => {
if (a.createTime > b.createTime) {
return -1;
}
return 1;
});
这时在页面上查看效果:
本节将处理博客的修改页面和删除操作。
修改页面
edit.ejs
新建views/posts/edit.ejs,内容如下:
<%- include('../header') %>
<div class="ui grid">
<div class="four wide column">
<a class="avatar" href="/posts?userId=<%= user.id %>" data-title="<%= user.name %> | <%= ({m: '男', f: '女', x: '保密'})[user.gender] %>" data-content="<%= user.bio %>">
<img class="avatar" src="/static/img/<%= user.avatar %>">
</a>
</div>
<div class="eight wide column">
<form class="ui form segment" method="post" action="/posts/<%= post.id %>/edit">
<div class="field required">
<label>标题</label>
<input type="text" name="title" value="<%= post.title %>" maxlength="100" required>
</div>
<div class="field required">
<label>内容</label>
<textarea name="content" rows="15" maxlength="1000" required><%= post.content %></textarea>
</div>
<input type="submit" class="ui button" value="发布">
</form>
</div>
</div>
<%- include('../footer') %>
post-content.ejs
修改views/components/post-content.ejs,在<span>浏览(<%= post.pv %>)</span>之后,增加编辑和删除两个按钮:
<% if (user && post.author.id && user.id===post.author.id) { %>
<div class="ui inline dropdown">
<div class="text"></div>
<i class="dropdown icon"></i>
<div class="menu">
<div class="item"><a href="/posts/<%= post.id %>/edit">编辑</a></div>
<div class="item"><a href="/posts/<%= post.id %>/remove">删除</a></div>
</div>
</div>
<% } %>
刷新页面,在浏览的右下角有个按钮,点开会弹出窗口:
post.dto.ts
修改src/posts/posts.dto.ts,添加一个更新的dto:
export class UpdatePostDto {
@IsString()
@IsOptional()
@MaxLength(100)
title?: string;
@IsString()
@IsOptional()
@MaxLength(1000)
content?: string;
}
posts.service.ts
src/posts/posts.service.ts,增加一个update方法:
update(id: string, params: UpdatePostDto) {
return this.model.findByIdAndUpdate(id, params);
}
posts.controller.ts
src/posts/posts.controller.ts增加渲染GET方法和更新的POST方法:
@UseGuards(SSOGuard)
@Get("/:id/edit")
async editPage(@Params("id") id: string, @Render() render: Render) {
const post = await this.postsService.findById(id, {
isWithUserInfo: true,
});
return render("posts/edit", { post });
}
@Post("/:id/edit")
@UseGuards(SSOGuard)
async updatePost(
@Params("id") id: string,
@Form() params: UpdatePostDto,
@Res() res: Response,
@Flash() flash: Flash,
) {
await this.postsService.update(id, params);
flash("success", "更新成功");
// 编辑成功后跳转到文章页面
res.redirect("/posts/" + id);
}
这里更新操作为什么没有选择使用PUT,而是用的POST呢?
原因是我们在ejs中直接用Form表单提交,它目前只支持GET和POST。
事实上,实际业务中很少使用DELETE和PUT,虽然他们更有语义,但因为某些web软件认为二者有安全漏洞,默认会禁止相应操作(zf部门的网关层更是常见),要使用的话又需要额外的处理,增加了运维的复杂性。
验证
点击编辑按钮,修改标题或内容,提交后刷新页面看是否数据已经变化了。
删除接口
删除接口要容易的多。
src/posts/posts.service.ts,增加一条:
deleteById(id: string) {
return this.model.findByIdAndDelete(id);
}
修改src/posts/posts.controller.ts,新增一个GET方法:
@UseGuards(SSOGuard)
@Get("/:id/remove")
async remove(
@Params("id") id: string,
@UserParam() user: UserInfo,
@Res() res: Response,
@Flash() flash: Flash,
) {
const post = await this.postsService.findById(id);
if (!post) {
throw new NotFoundException(`未找到id为${id}的博客`);
}
if (post.userId !== user.id) {
throw new ForbiddenException(`您没有权限删除该博客`);
}
await this.postsService.deleteById(id);
flash("success", "删除成功");
// 删除成功后跳转到主页
res.redirect("/posts");
}
注意:前文说过,不推荐实际中使用GET进行操作。
请自行验证。
作业
不知道你有没有注意,我们的views/posts/edit.ejs与create.ejs有太多的相似之处,你可以试着把它们提取一个子组件,提高代码复用性。