Abp vNext - 应用开发系列之页面

在系列教程中,我们会构建一个基于ABP的Web应用程序,用于管理书籍及其作者列表

使用到的技术:

  • Entity Framework Core
  • Angular

本教程分为以下部分:

本地化

配置本地化多语言,位于.Domain.Shared项目Localization/BookStore目录下:

编辑zh-Hans.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
{
"culture": "zh-Hans",
"texts": {
"Menu:Home": "首页",
"Welcome": "欢迎",
"LongWelcomeMessage": "欢迎使用本应用程序。这是一个基于 ABP 框架的启动项目。更多信息,请访问 abp.io。",
"Menu:BookStore": "书店",
"Menu:Books": "书籍",
"Actions": "操作",
"Close": "关闭",
"Delete": "删除",
"Edit": "编辑",
"PublishDate": "发布日志",
"NewBook": "新的书籍",
"Name": "名称",
"Type": "类型",
"Price": "价格",
"CreationTime": "创建时间",
"AreYouSure": "是否确认?",
"AreYouSureToDelete": "是否确认删除?",
"Enum:BookType.0": "未定义",
"Enum:BookType.1": "冒险",
"Enum:BookType.2": "传记",
"Enum:BookType.3": "乌托邦",
"Enum:BookType.4": "奇幻",
"Enum:BookType.5": "恐怖",
"Enum:BookType.6": "科学",
"Enum:BookType.7": "科幻",
"Enum:BookType.8": "诗歌"
}
}

通过修改DomainSharedModule.cs配置默认语言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

public class BookStoreDomainSharedModule : AbpModule
{

public override void ConfigureServices(ServiceConfigurationContext context)
{


Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Add<BookStoreResource>("zh-Hans")
.AddBaseTypes(typeof(AbpValidationResource))
.AddVirtualJson("/Localization/BookStore");

options.DefaultResourceType = typeof(BookStoreResource);
});
}
}

本地化多语言的关键字Key可以是任意的,但是对于的特定的文本类型,ABP有如下约定:

  • 为按钮项添加Menu:前缀
  • 使用 Enum:<enum-type>:<enum-name><enum-type>.<enum-name><enum-name> 命名约定来本地化枚举成员,ABP会自动将枚举本地化

如果没有在本地化多语言文件中定义文本,将直接显示本地化键Key

安装NPM包

angular 目录下打开命令行窗口,选择 yarn 命令安装NPM包:

1
yarn

使用以下命令启动前端:

1
2
3
4
5
6
➜ yarn start
yarn run v1.22.19
$ ng serve --open
✔ Browser application bundle generation complete.

Initial Chunk Files

创建图书页面

开发ABP Angular前端应用程序时,需要使用一些工具:

运行以下命令在angular应用程序根目录创建一个名为 BookModule 的新模块:

1
yarn ng generate module book --module app --routing --route books

命令输出:

1
2
3
4
5
6
7
8
9
10
11
12
➜ yarn ng generate module book --module app --routing --route books
>
yarn run v1.22.19
$ ng generate module book --module app --routing --route books
CREATE src/app/book/book-routing.module.ts (346 bytes)
CREATE src/app/book/book.module.ts (358 bytes)
CREATE src/app/book/book.component.html (20 bytes)
CREATE src/app/book/book.component.spec.ts (610 bytes)
CREATE src/app/book/book.component.ts (202 bytes)
CREATE src/app/book/book.component.scss (0 bytes)
UPDATE src/app/app-routing.module.ts (1017 bytes)
Done in 1.07s.

image-20240623184420790

BookModule

打开 /src/app/book/book.module.ts 并做如下调整:

  • 添加了 SharedModuleSharedModule 导出了一些创建用户界面所需的通用模块
  • SharedModule 已经导出了 CommonModule,所以删除了 CommonModule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { NgModule } from '@angular/core';
-- import { CommonModule } from '@angular/common';
++ import { SharedModule } from '../shared/shared.module';

import { BookRoutingModule } from './book-routing.module';
import { BookComponent } from './book.component';


@NgModule({
declarations: [
BookComponent
],
imports: [
-- CommonModule,
++ SharedModule
BookRoutingModule
]
})
export class BookModule { }

路由

生成的代码将新的路由定义放在 src/app/app-routing.module.ts 文件中,如下所示:

1
2
3
4
const routes: Routes = [
// other route definitions...
{ path: 'books', loadChildren: () => import('./book/book.module').then(m => m.BookModule) },
];

打开 src/app/route.provider.ts 替换 configureRoutes 函数为以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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,
},
]);
};
}

RoutesService 是ABP框架提供的用于配置主菜单和路由的服务

  • path: 路由的URL
  • name :菜单项的名称
  • iconClass: 菜单项的图标
  • order :菜单项的排序
  • layout :路由的布局(有三个预定义的布局类型: eLayoutType.applicationeLayoutType.accounteLayoutType.empty)

生成服务代理

ABP CLI 提供 generate-proxy 命令为HTTP APIs生成客户端代理类,生成代理后,在客户端使用HTTP APIs会更方便

运行 generate-proxy 命令前,请先运行HostApi

警告: 使用IIS Express时有一个问题;它不允许从另一个进程连接应用程序,如果使用Visual Studio,,在运行按钮的下拉框中选择Acme.BookStore.HttpApi.Host,不要选择IIS Express,如下图:

image-20240623185429925

启动Host后,在angular目录运行命令:

1
abp generate-proxy -t ng

命令输出如下:

1
2
3
4
5
6
7
8
9
➜ abp generate-proxy -t ng
ABP CLI 8.1.4
CREATE src/app/proxy/generate-proxy.json (311016 bytes)
CREATE src/app/proxy/README.md (1000 bytes)
CREATE src/app/proxy/books/book.service.ts (1715 bytes)
CREATE src/app/proxy/books/models.ts (375 bytes)
CREATE src/app/proxy/books/book-type.enum.ts (299 bytes)
CREATE src/app/proxy/books/index.ts (92 bytes)
CREATE src/app/proxy/index.ts (52 bytes)

可以看到,该命令在/src/app/proxy/books文件夹下生成以下文件:

image-20240623185641930

BookComponent

编辑 /src/app/book/book.component.ts

  • 引入并注入了生成的 BookService.
  • 使用 ListService,它是一个工具服务,提供了易用的分页,排序和搜索
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookService, BookDto } from '@proxy/books';

@Component({
selector: 'app-book',
templateUrl: './book.component.html',
styleUrls: ['./book.component.scss'],
providers: [ListService],
})
export class BookComponent implements OnInit {
book = { items: [], totalCount: 0 } as PagedResultDto<BookDto>;

constructor(public readonly list: ListService, private bookService: BookService) {}

ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getList(query);

this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
});
}
}

编辑 /src/app/book/book.component.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<div class="card">
<div class="card-header">
<div class="row">
<div class="col col-md-6">
<h5 class="card-title">
{{ '::Menu:Books' | abpLocalization }}
</h5>
</div>
<div class="text-end col col-md-6"></div>
</div>
</div>
<div class="card-body">
<ngx-datatable [rows]="book.items" [count]="book.totalCount" [list]="list" default>
<ngx-datatable-column [name]="'::Name' | abpLocalization" prop="name"></ngx-datatable-column>
<ngx-datatable-column [name]="'::Type' | abpLocalization" prop="type">
<ng-template let-row="row" ngx-datatable-cell-template>
{{ '::Enum:BookType.' + row.type | abpLocalization }}
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [name]="'::PublishDate' | abpLocalization" prop="publishDate">
<ng-template let-row="row" ngx-datatable-cell-template>
{{ row.publishDate | date }}
</ng-template>
</ngx-datatable-column>
<ngx-datatable-column [name]="'::Price' | abpLocalization" prop="price">
<ng-template let-row="row" ngx-datatable-cell-template>
{{ row.price | currency }}
</ng-template>
</ngx-datatable-column>
</ngx-datatable>
</div>
</div>

查看页面

image-20240623191950019

到这里我们就完成了前端启动和开发

下一节

教程下一节