在系列教程中,我们会构建一个基于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 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
并做如下调整:
- 添加了
SharedModule
,SharedModule
导出了一些创建用户界面所需的通用模块
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.application
,eLayoutType.account
或 eLayoutType.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]()
到这里我们就完成了前端启动和开发
下一节
教程下一节