Web应用程序开发教程 - 第九章: 作者: 用户页面
关于本教程
在本系列教程中, 你将构建一个名为 Acme.BookStore
的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的:
- Entity Framework Core 做为ORM提供程序.
- Angular 做为UI框架.
本教程分为以下部分:
- Part 1: 创建服务端
- Part 2: 图书列表页面
- Part 3: 创建,更新和删除图书
- Part 4: 集成测试
- Part 5: 授权
- Part 6: 作者: 领域层
- Part 7: 作者: 数据库集成
- Part 8: 作者: 应用服务层
- Part 9: 作者: 用户页面 (本章)
- Part 10: 图书到作者的关系
下载源码
本教程根据你的UI 和 数据库偏好有多个版本,我们准备了几种可供下载的源码组合:
如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 在Windows 10中启用长路径.
如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
git config --system core.longpaths true
简介
这章阐述如何为前一章介绍的 作者
实体创建CRUD页面.
作者管理页面
运行下面的命令行在angular应用程序的根目录创建一个名为 AuthorModule
新模块:
yarn ng generate module author --module app --routing --route authors
这上命令应该产生以下输出:
> yarn ng generate module author --module app --routing --route authors
yarn run v1.19.1
$ ng generate module author --module app --routing --route authors
CREATE src/app/author/author-routing.module.ts (344 bytes)
CREATE src/app/author/author.module.ts (349 bytes)
CREATE src/app/author/author.component.html (21 bytes)
CREATE src/app/author/author.component.spec.ts (628 bytes)
CREATE src/app/author/author.component.ts (276 bytes)
CREATE src/app/author/author.component.scss (0 bytes)
UPDATE src/app/app-routing.module.ts (1396 bytes)
Done in 2.22s.
AuthorModule
打开 /src/app/author/author.module.ts,
替换成以下内容:
import { NgModule } from '@angular/core';
import { SharedModule } from '../shared/shared.module';
import { AuthorRoutingModule } from './author-routing.module';
import { AuthorComponent } from './author.component';
import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap';
@NgModule({
declarations: [AuthorComponent],
imports: [SharedModule, AuthorRoutingModule, NgbDatepickerModule],
})
export class AuthorModule {}
- 添加
SharedModule
.SharedModule
导出一些创建用户页面需要的通用模块. SharedModule
已经导出CommonModule
, 所以移除CommonModule
.- 添加
NgbDatepickerModule
, 后面用于作者创建和编辑表单.
菜单定义
打开 src/app/route.provider.ts
文件, 加入以下的菜单定义:
{
path: '/authors',
name: '::Menu:Authors',
parentName: '::Menu:BookStore',
layout: eLayoutType.application,
requiredPolicy: 'BookStore.Authors',
}
最终的 configureRoutes
函数声明应该如下:
function configureRoutes(routes: RoutesService) {
return () => {
routes.add([
{
path: '/',
name: '::Menu:Home',
iconClass: 'fas fa-home',
order: 1,
layout: eLayoutType.application,
},
{
path: '/book-store',
name: '::Menu:BookStore',
iconClass: 'fas fa-book',
order: 2,
layout: eLayoutType.application,
},
{
path: '/books',
name: '::Menu:Books',
parentName: '::Menu:BookStore',
layout: eLayoutType.application,
requiredPolicy: 'BookStore.Books',
},
{
path: '/authors',
name: '::Menu:Authors',
parentName: '::Menu:BookStore',
layout: eLayoutType.application,
requiredPolicy: 'BookStore.Authors',
},
]);
};
}
生成服务代理
ABP CLI 提供 generate-proxy
命令为HTTP APIs生成客户端代理, 使得使用HTTP APIs更容易. generate-proxy
命令运行前, 应用程序必须启动运行.
在 angular
文件夹运行以下命令:
abp generate-proxy -t ng
这个命令为作者服务和相关模型(DTO)类生成服务代理:
AuthorComponent
打开 /src/app/author/author.component.ts
文件, 替换成以下内容:
import { Component, OnInit } from '@angular/core';
import { ListService, PagedResultDto } from '@abp/ng.core';
import { AuthorService, AuthorDto } from '@proxy/authors';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared';
@Component({
selector: 'app-author',
templateUrl: './author.component.html',
styleUrls: ['./author.component.scss'],
providers: [ListService, { provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
})
export class AuthorComponent implements OnInit {
author = { items: [], totalCount: 0 } as PagedResultDto<AuthorDto>;
isModalOpen = false;
form: FormGroup;
selectedAuthor = {} as AuthorDto;
constructor(
public readonly list: ListService,
private authorService: AuthorService,
private fb: FormBuilder,
private confirmation: ConfirmationService
) {}
ngOnInit(): void {
const authorStreamCreator = (query) => this.authorService.getList(query);
this.list.hookToQuery(authorStreamCreator).subscribe((response) => {
this.author = response;
});
}
createAuthor() {
this.selectedAuthor = {} as AuthorDto;
this.buildForm();
this.isModalOpen = true;
}
editAuthor(id: string) {
this.authorService.get(id).subscribe((author) => {
this.selectedAuthor = author;
this.buildForm();
this.isModalOpen = true;
});
}
buildForm() {
this.form = this.fb.group({
name: [this.selectedAuthor.name || '', Validators.required],
birthDate: [
this.selectedAuthor.birthDate ? new Date(this.selectedAuthor.birthDate) : null,
Validators.required,
],
});
}
save() {
if (this.form.invalid) {
return;
}
if (this.selectedAuthor.id) {
this.authorService
.update(this.selectedAuthor.id, this.form.value)
.subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
} else {
this.authorService.create(this.form.value).subscribe(() => {
this.isModalOpen = false;
this.form.reset();
this.list.get();
});
}
}
delete(id: string) {
this.confirmation.warn('::AreYouSureToDelete', '::AreYouSure')
.subscribe((status) => {
if (status === Confirmation.Status.confirm) {
this.authorService.delete(id).subscribe(() => this.list.get());
}
});
}
}
打开 /src/app/author/author.component.html
文件, 替换成以下内容:
<div class="card">
<div class="card-header">
<div class="row">
<div class="col col-md-6">
<h5 class="card-title">
{{ '::Menu:Authors' | abpLocalization }}
</h5>
</div>
<div class="text-right col col-md-6">
<div class="text-lg-right pt-2">
<button *abpPermission="'BookStore.Authors.Create'" id="create" class="btn btn-primary" type="button" (click)="createAuthor()">
<i class="fa fa-plus mr-1"></i>
<span>{{ '::NewAuthor' | abpLocalization }}</span>
</button>
</div>
</div>
</div>
</div>
<div class="card-body">
<ngx-datatable [rows]="author.items" [count]="author.totalCount" [list]="list" default>
<ngx-datatable-column
[name]="'::Actions' | abpLocalization"
[maxWidth]="150"
[sortable]="false"
>
<ng-template let-row="row" ngx-datatable-cell-template>
<div ngbDropdown container="body" class="d-inline-block">
<button
class="btn btn-primary btn-sm dropdown-toggle"
data-toggle="dropdown"
aria-haspopup="true"
ngbDropdownToggle
>
<i class="fa fa-cog mr-1"></i>{{ '::Actions' | abpLocalization }}
</button>
<div ngbDropdownMenu>
<button *abpPermission="'BookStore.Authors.Edit'" ngbDropdownItem (click)="editAuthor(row.id)">
{{ '::Edit' | abpLocalization }}
</button>
<button *abpPermission="'BookStore.Authors.Delete'" ngbDropdownItem (click)="delete(row.id)">
{{ '::Delete' | abpLocalization }}
</button>
</div>
</div>
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [name]="'::Name' | abpLocalization" prop="name"></ngx-datatable-column>
<ngx-datatable-column [name]="'::BirthDate' | abpLocalization">
<ng-template let-row="row" ngx-datatable-cell-template>
{{ row.birthDate | date }}
</ng-template>
</ngx-datatable-column>
</ngx-datatable>
</div>
</div>
<abp-modal [(visible)]="isModalOpen">
<ng-template #abpHeader>
<h3>{{ (selectedAuthor.id ? '::Edit' : '::NewAuthor') | abpLocalization }}</h3>
</ng-template>
<ng-template #abpBody>
<form [formGroup]="form" (ngSubmit)="save()">
<div class="form-group">
<label for="author-name">Name</label><span> * </span>
<input type="text" id="author-name" class="form-control" formControlName="name" autofocus />
</div>
<div class="form-group">
<label>Birth date</label><span> * </span>
<input
#datepicker="ngbDatepicker"
class="form-control"
name="datepicker"
formControlName="birthDate"
ngbDatepicker
(click)="datepicker.toggle()"
/>
</div>
</form>
</ng-template>
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" abpClose>
{{ '::Close' | abpLocalization }}
</button>
<button class="btn btn-primary" (click)="save()" [disabled]="form.invalid">
<i class="fa fa-check mr-1"></i>
{{ '::Save' | abpLocalization }}
</button>
</ng-template>
</abp-modal>
本地化
这个页面使用了一些需要声明的本地化键. 打开 Acme.BookStore.Domain.Shared
项目中 Localization/BookStore
文件夹下的 en.json
文件, 加入以下条目:
"Menu:Authors": "Authors",
"Authors": "Authors",
"AuthorDeletionConfirmationMessage": "Are you sure to delete the author '{0}'?",
"BirthDate": "Birth date",
"NewAuthor": "New author"
简体中文翻译请打开
zh-Hans.json
文件 ,并将"Texts"对象中对应的值替换为中文.
运行应用程序
运行并登录应用程序. 因为你还没有权限, 所以不能看见菜单项. 转到 Identity/Roles
页面, 点击 操作 按钮并选择管理员角色的权限操作:
如你所见, 管理员角色还没有作者管理权限. 单击复选框并保存, 赋予权限. 刷新页面后, 你会在主菜单中的图书商店下看到作者菜单项:
这就是全部了! 这是一个完整的, 可以工作的页面. 你可以新建, 编辑和删除作者.
提示: 如果你在定义一个新权限后运行
.DbMigrator
控制台程序, 它会自动将这些权限赋予管理员角色, 你不需要手工赋予权限.
下一章
查看本教程的下一章.