diff --git a/docs/zh-Hans/Customizing-Application-Modules-Guide.md b/docs/zh-Hans/Customizing-Application-Modules-Guide.md index 7e799c885d..bf3404ddb7 100644 --- a/docs/zh-Hans/Customizing-Application-Modules-Guide.md +++ b/docs/zh-Hans/Customizing-Application-Modules-Guide.md @@ -17,7 +17,7 @@ ABP框架提供的设计旨在支持构建完全[模块化的应用程序](Modul 这种方法具有以下优点: * 你的解决方案会非常**干净**,只包含你**自己的应用程序代码**. -* 你可以**很简单的**升级模块到最新的可用模板. `abp update` [CLI](CLI.md) 命令会使更新变的更加简单. 通过这种方式, 你可以获得**最新功能和Bus修复**. +* 你可以**很简单的**升级模块到最新的可用模板. `abp update` [CLI](CLI.md) 命令会使更新变的更加简单. 通过这种方式, 你可以获得**最新功能和Bug修复**. 然而有一个缺点: diff --git a/docs/zh-Hans/Entity-Framework-Core-Migrations.md b/docs/zh-Hans/Entity-Framework-Core-Migrations.md index caf5778753..77f5ac5cd7 100644 --- a/docs/zh-Hans/Entity-Framework-Core-Migrations.md +++ b/docs/zh-Hans/Entity-Framework-Core-Migrations.md @@ -550,6 +550,8 @@ public class IdentityRoleExtendingService : ITransientDependency 你需要做的就是如上所诉使用 `ObjectExtensionManager` 定义额外属性, 然后你就可以使得 `GetProperty` 和 `SetProperty` 方法对实体的属性进行get/set,但是这时它存储在数据库表的单独字段中. +参阅[实体扩展系统](Customizing-Application-Modules-Extending-Entities.md)了解更多. + ###### 创建新表 你可以创建**自己的表**来存储属性,而不是创建新实体并映射到同一表. 你通常复制原始实体的一些值. 例如可以将 `Name` 字段添加到你自己的表中,它是原表中 `Name` 字段的副本. diff --git a/docs/zh-Hans/Getting-Started.md b/docs/zh-Hans/Getting-Started.md index b60fccfaa2..1b156b070d 100644 --- a/docs/zh-Hans/Getting-Started.md +++ b/docs/zh-Hans/Getting-Started.md @@ -111,6 +111,7 @@ abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mon {{end}} {{ else if UI == "NG" }} + 在创建的解决方案中有三个文件夹: ![](images/solution-files-non-mvc.png) @@ -120,6 +121,7 @@ abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mon * `react-native` 文件夹包含React Native UI 应用程序. 打开 `aspnet-core` 文件夹下的 `.sln`(`Visual Studio`解决方案)文件: + ![vs-angular-app-backend-solution-structure](images/vs-spa-app-backend-structure{{if DB == "Mongo"}}-mongodb{{end}}.png) {{ end }} diff --git a/docs/zh-Hans/Tutorials/Angular/Part-I.md b/docs/zh-Hans/Tutorials/Angular/Part-I.md index 8e8c74ec59..e981259db8 100644 --- a/docs/zh-Hans/Tutorials/Angular/Part-I.md +++ b/docs/zh-Hans/Tutorials/Angular/Part-I.md @@ -1,3 +1,8 @@ -## Angular 教程 - 第一章 +# 教程 -TODO... \ No newline at end of file +## 应用程序开发 + +* [ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) +* [Angular UI](../Part-1?UI=NG) + + \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md index 7c2db90997..e981259db8 100644 --- a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md +++ b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-I.md @@ -1,478 +1,8 @@ -## ASP.NET Core MVC 介绍 - 第一章 +# 教程 -### 关于本教程 +## 应用程序开发 -在本系列教程中, 你将构建一个用于管理书籍及其作者列表的应用程序. **Entity Framework Core**(EF Core)将用作ORM提供者,因为它是默认数据库提供者. +* [ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) +* [Angular UI](../Part-1?UI=NG) -这是本教程所有章节中的第一章,下面是所有的章节: - -- **Part I: 创建项目和书籍列表页面(本章)** -- [Part II: 创建,编辑,删除书籍](Part-II.md) -- [Part III: 集成测试](Part-III.md) - -你可以从[GitHub存储库](https://github.com/abpframework/abp-samples/tree/master/BookStore)访问应用程序的**源代码**. - -> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). - -### 创建项目 - -创建一个名为`Acme.BookStore`的新项目, 创建数据库并按照[入门文档](../../../Getting-Started-AspNetCore-MVC-Template.md)运行应用程序. - -### 解决方案的结构 - -下面的图片展示了从启动模板创建的项目是如何分层的. - -![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution-v3.png) - -> 你可以查看[应用程序模板文档](../startup-templates/application#solution-structure)以详细了解解决方案结构.但是,你将通过本教程了解基础知识. - -### 创建Book实体 - -启动模板中的域层分为两个项目: - - - `Acme.BookStore.Domain`包含你的[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities), [领域服务](https://docs.abp.io/zh-Hans/abp/latest/Domain-Services)和其他核心域对象. - - `Acme.BookStore.Domain.Shared`包含可与客户共享的常量,枚举或其他域相关对象. - -在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities). 该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个名为`Book`的类,如下所示: - -````C# -using System; -using Volo.Abp.Domain.Entities.Auditing; - -namespace Acme.BookStore -{ - public class Book : AuditedAggregateRoot - { - public string Name { get; set; } - - public BookType Type { get; set; } - - public DateTime PublishDate { get; set; } - - public float Price { get; set; } - - protected Book() - { - } - public Book(Guid id, string name, BookType type, DateTime publishDate, float price) - :base(id) - { - Name = name; - Type = type; - PublishDate = publishDate; - Price = price; - } - } -} -```` - -* ABP为实体提供了两个基本的基类: `AggregateRoot`和`Entity`. **Aggregate Root**是**域驱动设计(DDD)** 概念之一. 有关详细信息和最佳做法,请参阅[实体文档](https://docs.abp.io/zh-Hans/abp/latest/Entities). -* `Book`实体继承了`AuditedAggregateRoot`,`AuditedAggregateRoot`类在`AggregateRoot`类的基础上添加了一些审计属性(`CreationTime`, `CreatorId`, `LastModificationTime` 等). -* `Guid`是`Book`实体的主键类型. -* 使用 **数据注解** 为EF Core添加映射.或者你也可以使用 EF Core 自带的[fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling). - -#### BookType枚举 - -上面所用到的`BookType`枚举定义如下: - -````C# -namespace Acme.BookStore -{ - public enum BookType - { - Undefined, - Adventure, - Biography, - Dystopia, - Fantastic, - Horror, - Science, - ScienceFiction, - Poetry - } -} -```` - -#### 将Book实体添加到DbContext中 - -EF Core需要你将实体和DbContext建立关联.最简单的做法是在`Acme.BookStore.EntityFrameworkCore`项目的`BookStoreDbContext`类中添加`DbSet`属性.如下所示: - -````C# -public class BookStoreDbContext : AbpDbContext -{ - public DbSet Books { get; set; } - ... -} -```` - -#### 配置你的Book实体 - -在`Acme.BookStore.EntityFrameworkCore`项目中打开`BookStoreDbContextModelCreatingExtensions.cs`文件,并将以下代码添加到`ConfigureBookStore`方法的末尾以配置Book实体: - -````C# -builder.Entity(b => -{ - b.ToTable(BookStoreConsts.DbTablePrefix + "Books", BookStoreConsts.DbSchema); - b.ConfigureByConvention(); //auto configure for the base class props - b.Property(x => x.Name).IsRequired().HasMaxLength(128); -}); -```` - -#### 添加新的Migration并更新数据库 - -这个启动模板使用了[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)来创建并维护数据库结构.打开 **程序包管理器控制台(Package Manager Console) (PMC)** (工具/Nuget包管理器菜单),选择 `Acme.BookStore.EntityFrameworkCore.DbMigrations`作为默认的项目然后执行下面的命令: - -![bookstore-pmc-add-book-migration](images/bookstore-pmc-add-book-migration-v2.png) - -这样就会在`Migrations`文件夹中创建一个新的migration类.然后执行`Update-Database`命令更新数据库结构. - -```` -PM> Update-Database -```` - -#### 添加示例数据 - -`Update-Database`命令在数据库中创建了`AppBooks`表. 打开数据库并输入几个示例行,以便在页面上显示它们: - -![bookstore-books-table](images/bookstore-books-table.png) - -### 创建应用服务 - -下一步是创建[应用服务](../../../Application-Services.md)来管理(创建,列出,更新,删除)书籍. 启动模板中的应用程序层分为两个项目: - -* `Acme.BookStore.Application.Contracts`主要包含你的DTO和应用程序服务接口. -* `Acme.BookStore.Application`包含应用程序服务的实现. - -#### BookDto - -在`Acme.BookStore.Application.Contracts`项目中创建一个名为`BookDto`的DTO类: - -````C# -using System; -using Volo.Abp.Application.Dtos; - -namespace Acme.BookStore -{ - public class BookDto : AuditedEntityDto - { - public string Name { get; set; } - - public BookType Type { get; set; } - - public DateTime PublishDate { get; set; } - - public float Price { get; set; } - } -} -```` - -* **DTO**类被用来在 **表示层** 和 **应用层** **传递数据**.查看[DTO文档](https://docs.abp.io/zh-Hans/abp/latest/Data-Transfer-Objects)查看更多信息. -* 为了在页面上展示书籍信息,`BookDto`被用来将书籍数据传递到表示层. -* `BookDto`继承自 `AuditedEntityDto`.跟上面定义的`Book`类一样具有一些审计属性. - -在将书籍返回到表示层时,需要将`Book`实体转换为`BookDto`对象. [AutoMapper](https://automapper.org)库可以在定义了正确的映射时自动执行此转换. 启动模板配置了AutoMapper,因此你只需在`Acme.BookStore.Application`项目的`BookStoreApplicationAutoMapperProfile`类中定义映射: - -````csharp -using AutoMapper; - -namespace Acme.BookStore -{ - public class BookStoreApplicationAutoMapperProfile : Profile - { - public BookStoreApplicationAutoMapperProfile() - { - CreateMap(); - } - } -} -```` - -#### CreateUpdateBookDto - -在`Acme.BookStore.Application.Contracts`项目中创建一个名为`CreateUpdateBookDto`的DTO类: - -````c# -using System; -using System.ComponentModel.DataAnnotations; -using Volo.Abp.AutoMapper; - -namespace Acme.BookStore -{ - public class CreateUpdateBookDto - { - [Required] - [StringLength(128)] - public string Name { get; set; } - - [Required] - public BookType Type { get; set; } = BookType.Undefined; - - [Required] - public DateTime PublishDate { get; set; } - - [Required] - public float Price { get; set; } - } -} -```` - -* 这个DTO类被用于在创建或更新书籍的时候从用户界面获取图书信息. -* 它定义了数据注释属性(如`[Required]`)来定义属性的验证. DTO由ABP框架[自动验证](https://docs.abp.io/zh-Hans/abp/latest/Validation). - -就像上面的`BookDto`一样,创建一个从`CreateUpdateBookDto`对象到`Book`实体的映射: - -````csharp -CreateMap(); -```` - -#### IBookAppService - -在`Acme.BookStore.Application.Contracts`项目中定义一个名为`IBookAppService`的接口: - -````C# -using System; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; - -namespace Acme.BookStore -{ - public interface IBookAppService : - ICrudAppService< //定义了CRUD方法 - BookDto, //用来展示书籍 - Guid, //Book实体的主键 - PagedAndSortedResultRequestDto, //获取书籍的时候用于分页和排序 - CreateUpdateBookDto, //用于创建书籍 - CreateUpdateBookDto> //用于更新书籍 - { - - } -} -```` - -* 框架定义应用程序服务的接口不是必需的. 但是,它被建议作为最佳实践. -* `ICrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 你可以从空的`IApplicationService`接口继承并手动定义自己的方法. -* `ICrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定. - - -#### BookAppService - -在`Acme.BookStore.Application`项目中实现名为`BookAppService`的`IBookAppService`: - -````C# -using System; -using Volo.Abp.Application.Dtos; -using Volo.Abp.Application.Services; -using Volo.Abp.Domain.Repositories; - -namespace Acme.BookStore -{ - public class BookAppService : - CrudAppService, - IBookAppService - { - public BookAppService(IRepository repository) - : base(repository) - { - - } - } -} -```` - -* `BookAppService`继承了`CrudAppService<...>`.它实现了上面定义的CRUD方法. -* `BookAppService`注入`IRepository `,这是`Book`实体的默认仓储. ABP自动为每个聚合根(或实体)创建默认仓储. 请参阅[仓储文档](https://docs.abp.io/zh-Hans/abp/latest/Repositories) -* `BookAppService`使用`IObjectMapper`将`Book`对象转换为`BookDto`对象, 将`CreateUpdateBookDto`对象转换为`Book`对象. 启动模板使用[AutoMapper](http://automapper.org/)库作为对象映射提供程序. 你之前定义了映射, 因此它将按预期工作. - -### 自动生成API Controllers - -你通常创建**Controller**以将应用程序服务公开为**HTTP API**端点. 因此允许浏览器或第三方客户端通过AJAX调用它们. ABP可以[**自动**](https://docs.abp.io/zh-Hans/abp/latest/API/Auto-API-Controllers)按照惯例将你的应用程序服务配置为MVC API控制器. - -#### Swagger UI - -启动模板配置为使用[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)运行[swagger UI](https://swagger.io/tools/swagger-ui/). 运行应用程序并在浏览器中输入`https://localhost:XXXX/swagger/`(用你自己的端口替换XXXX)作为URL. - -你会看到一些内置的接口和`Book`的接口,它们都是REST风格的: - -![bookstore-swagger](images/bookstore-swagger.png) - -Swagger有一个很好的UI来测试API. 你可以尝试执行`[GET] /api/app/book` API来获取书籍列表. - -### 动态JavaScript代理 - -在Javascript端通过AJAX的方式调用HTTP API接口是很常见的,你可以使用`$.ajax`或者其他的工具来调用接口.当然,ABP中提供了更好的方式. - -ABP **自动** 为所有的API接口创建了JavaScript **代理**.因此,你可以像调用 **JavaScript function**一样调用任何接口. - -#### 在浏览器的开发者控制台中测试接口 - -你可以使用你钟爱的浏览器的 **开发者控制台** 中轻松测试JavaScript代理.运行程序,并打开浏览器的 **开发者工具**(快捷键:F12),切换到 **Console** 标签,输入下面的代码并回车: - -````js -acme.bookStore.book.getList({}).done(function (result) { console.log(result); }); -```` - -* `acme.bookStore`是`BookAppService`的命名空间,转换成了[驼峰命名](https://en.wikipedia.org/wiki/Camel_case). -* `book`是`BookAppService`转换后的名字(去除了AppService后缀并转成了驼峰命名). -* `getList`是定义在`AsyncCrudAppService`基类中的`GetListAsync`方法转换后的名字(去除了Async后缀并转成了驼峰命名). -* `{}`参数用于将空对象发送到`GetListAsync`方法,该方法通常需要一个类型为`PagedAndSortedResultRequestDto`的对象,用于向服务器发送分页和排序选项(所有属性都是可选的,所以你可以发送一个空对象). -* `getList`方法返回了一个`promise`.因此,你可以传递一个回调函数到`done`(或者`then`)方法中来获取服务返回的结果. - -运行这段代码会产生下面的输出: - -![bookstore-test-js-proxy-getlist](images/bookstore-test-js-proxy-getlist.png) - -你可以看到服务器返回的 **book list**.你还可以切换到开发者工具的 **network** 查看客户端到服务器端的通讯信息: - -![bookstore-test-js-proxy-getlist-network](images/bookstore-test-js-proxy-getlist-network.png) - -我们使用`create`方法 **创建一本新书**: - -````js -acme.bookStore.book.create({ name: 'Foundation', type: 7, publishDate: '1951-05-24', price: 21.5 }).done(function (result) { console.log('successfully created the book with id: ' + result.id); }); -```` - -你会看到控制台会显示类似这样的输出: - -```` -successfully created the book with id: f3f03580-c1aa-d6a9-072d-39e75c69f5c7 -```` - -检查数据库中的`Books`表以查看新书. 你可以自己尝试`get`,`update`和`delete`功能. - -### 创建书籍页面 - -现在我们来创建一些可见和可用的东西,取代经典的MVC,我们使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start). - - -在 `Acme.BookStore.Web`项目的`Pages`文件夹下创建一个新的文件夹叫`Books`并添加一个名为`Index.cshtml`的Razor Page. - -![bookstore-add-index-page](images/bookstore-add-index-page-v2.png) - -打开`Index.cshtml`并把内容修改成下面这样: - -````html -@page -@using Acme.BookStore.Web.Pages.Books -@inherits Acme.BookStore.Web.Pages.BookStorePage -@model IndexModel - -

Books

-```` - -* 此代码更改了Razor View Page Model的默认继承,因此它从`BookStorePage`类(而不是`PageModel`)继承.启动模板附带的`BookStorePage`类,提供所有页面使用的一些共享属性/方法. -* 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Web.Pages.Books`命名空间,或者在`Index.cshtml`中更新它. - -#### 将Books页面添加到主菜单 - -打开`Menus`文件夹中的 `BookStoreMenuContributor` 类,在`ConfigureMainMenuAsync`方法的底部添加如下代码: - -````c# -context.Menu.AddItem( - new ApplicationMenuItem("BooksStore", l["Menu:BookStore"]) - .AddItem(new ApplicationMenuItem("BooksStore.Books", l["Menu:Books"], url: "/Books")) -); -```` - -#### 本地化菜单 - -本地化文本位于`Acme.BookStore.Domain.Shared`项目的`Localization/BookStore`文件夹下: - -![bookstore-localization-files](images/bookstore-localization-files-v2.png) - -打开`en.json`文件,将`Menu:BookStore`和`Menu:Books`键的本地化文本添加到文件末尾: - -````json -{ - "culture": "en", - "texts": { - "Menu:BookStore": "Book Store", - "Menu:Books": "Books" - } -} -```` - -* ABP的本地化功能建立在[ASP.NET Core's standard localization]((https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization))之上并增加了一些扩展.查看[本地化文档](https://docs.abp.io/zh-Hans/abp/latest/Localization). -* 本地化key是任意的. 你可以设置任何名称. 我们更喜欢为菜单项添加`Menu:`前缀以区别于其他文本. 如果未在本地化文件中定义文本,则它将**返回**到本地化的key(ASP.NET Core的标准行为). - -运行该应用程序,看到新菜单项已添加到顶部栏: - -![bookstore-menu-items](images/bookstore-menu-items.png) - -点击Books菜单项就会跳转到新增的书籍页面. - -#### 书籍列表 - -我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示页面上的表格列表. 数据表可以完全通过AJAX工作,速度快,并提供良好的用户体验. Datatables插件在启动模板中配置,因此你可以直接在任何页面中使用它,而需要在页面中引用样式和脚本文件. - -##### Index.cshtml - -将`Pages/Books/Index.cshtml`改成下面的样子: - -````html -@page -@inherits Acme.BookStore.Web.Pages.BookStorePage -@model Acme.BookStore.Web.Pages.Books.IndexModel -@section scripts -{ - -} - - -

@L["Books"]

-
- - - - - @L["Name"] - @L["Type"] - @L["PublishDate"] - @L["Price"] - @L["CreationTime"] - - - - -
-```` - -* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)用于将外部的 **脚本** 添加到页面中.它比标准的`script`标签多了很多额外的功能.它可以处理 **最小化**和 **版本**.查看[捆绑 & 压缩文档](https://docs.abp.io/zh-Hans/abp/latest/UI/AspNetCore/Bundling-Minification)获取更多信息. -* `abp-card` 和 `abp-table` 是为Twitter Bootstrap的[card component](http://getbootstrap.com/docs/4.1/components/card/)封装的 **tag helpers**.ABP中有很多tag helpers,可以很方便的使用大多数[bootstrap](https://getbootstrap.com/)组件.你也可以使用原生的HTML标签代替tag helpers.使用tag helper可以通过智能提示和编译时类型检查减少HTML代码并防止错误.查看[tag helpers 文档](https://docs.abp.io/zh-Hans/abp/latest/UI/AspNetCore/Tag-Helpers/Index). -* 你可以像上面本地化菜单一样 **本地化** 列名. - -#### 添加脚本文件 - -在`Pages/Books/`文件夹中创建 `index.js`文件 - -![bookstore-index-js-file](images/bookstore-index-js-file-v2.png) - -`index.js`的内容如下: - -````js -$(function () { - var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ - ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), - columnDefs: [ - { data: "name" }, - { data: "type" }, - { data: "publishDate" }, - { data: "price" }, - { data: "creationTime" } - ] - })); -}); -```` - -* `abp.libs.datatables.createAjax`是帮助ABP的动态JavaScript API代理跟Datatable的格式相适应的辅助方法. -* `abp.libs.datatables.normalizeConfiguration`是另一个辅助方法.不是必须的, 但是它通过为缺少的选项提供常规值来简化数据表配置. -* `acme.bookStore.book.getList`是获取书籍列表的方法(上面已经介绍过了) -* 查看 [Datatable文档](https://datatables.net/manual/) 了解更多配置项. - -最终的页面如下: - -![bookstore-book-list](images/bookstore-book-list-2.png) - -### 下一章 - -点击查看 [下一章](Part-II.md) 的介绍. + \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md index e072e5a1a7..e981259db8 100644 --- a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md +++ b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/Part-III.md @@ -1,165 +1,8 @@ -## ASP.NET Core MVC 教程 - 第三章 +# 教程 -### 关于本教程 +## 应用程序开发 -这是ASP.NET Core MVC教程系列的第三章. 查看其它章节 +* [ASP.NET Core MVC / Razor Pages UI](../Part-1?UI=MVC) +* [Angular UI](../Part-1?UI=NG) -- [Part I: 创建项目和书籍列表页面](Part-I.md) -- [Part II: 创建,编辑,删除书籍](Part-II.md) -- **Part III: 集成测试(本章)** - -你可以从[GitHub存储库](https://github.com/volosoft/abp/tree/master/samples/BookStore)访问应用程序的**源代码**. - -> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). - -### 解决方案中的测试项目 - -解决方案中有多个测试项目: - -![bookstore-test-projects-v2](images/bookstore-test-projects-v2.png) - -每个项目用于测试相关的应用程序项目.测试项目使用以下库进行测试: - -* [xunit](https://xunit.github.io/) 作为主测试框架. -* [Shoudly](http://shouldly.readthedocs.io/en/latest/) 作为断言库. -* [NSubstitute](http://nsubstitute.github.io/) 作为模拟库. - -### 添加测试用数据 - -启动模板包含`Acme.BookStore.TestBase`项目中的`BookStoreTestDataSeedContributor`类,它创建一些数据来运行测试. -更改`BookStoreTestDataSeedContributor`类如下所示: - -````C# -using System; -using System.Threading.Tasks; -using Volo.Abp.Data; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Domain.Repositories; -using Volo.Abp.Guids; - -namespace Acme.BookStore -{ - public class BookStoreTestDataSeedContributor - : IDataSeedContributor, ITransientDependency - { - private readonly IRepository _bookRepository; - private readonly IGuidGenerator _guidGenerator; - - public BookStoreTestDataSeedContributor( - IRepository bookRepository, - IGuidGenerator guidGenerator) - { - _bookRepository = bookRepository; - _guidGenerator = guidGenerator; - } - - public async Task SeedAsync(DataSeedContext context) - { - await _bookRepository.InsertAsync( - new Book(_guidGenerator.Create(), "Test book 1", - BookType.Fantastic, new DateTime(2015, 05, 24), 21)); - - await _bookRepository.InsertAsync( - new Book(_guidGenerator.Create(), "Test book 2", - BookType.Science, new DateTime(2014, 02, 11), 15)); - } - } -} -```` - -* 注入`IRepository`并在`SeedAsync`中使用它来创建两个书实体作为测试数据. -* 使用`IGuidGenerator`服务创建GUID. 虽然`Guid.NewGuid()`非常适合测试,但`IGuidGenerator`在使用真实数据库时还有其他特别重要的功能(参见[Guid生成文档](../../../Guid-Generation.md)了解更多信息). - -### 测试 BookAppService - -在 `Acme.BookStore.Application.Tests` 项目中创建一个名叫 `BookAppService_Tests` 的测试类: - -````C# -using System.Threading.Tasks; -using Shouldly; -using Volo.Abp.Application.Dtos; -using Xunit; - -namespace Acme.BookStore -{ - public class BookAppService_Tests : BookStoreApplicationTestBase - { - private readonly IBookAppService _bookAppService; - - public BookAppService_Tests() - { - _bookAppService = GetRequiredService(); - } - - [Fact] - public async Task Should_Get_List_Of_Books() - { - //Act - var result = await _bookAppService.GetListAsync( - new PagedAndSortedResultRequestDto() - ); - - //Assert - result.TotalCount.ShouldBeGreaterThan(0); - result.Items.ShouldContain(b => b.Name == "Test book 1"); - } - } -} -```` - -* 测试方法 `Should_Get_List_Of_Books` 直接使用 `BookAppService.GetListAsync` 方法来获取用户列表,并执行检查. - -新增测试方法,用以测试创建一个合法book实体的场景: - -````C# -[Fact] -public async Task Should_Create_A_Valid_Book() -{ - //Act - var result = await _bookAppService.CreateAsync( - new CreateUpdateBookDto - { - Name = "New test book 42", - Price = 10, - PublishDate = DateTime.Now, - Type = BookType.ScienceFiction - } - ); - - //Assert - result.Id.ShouldNotBe(Guid.Empty); - result.Name.ShouldBe("New test book 42"); -} -```` - -新增测试方法,用以测试创建一个非法book实体失败的场景: - -````C# -[Fact] -public async Task Should_Not_Create_A_Book_Without_Name() -{ - var exception = await Assert.ThrowsAsync(async () => - { - await _bookAppService.CreateAsync( - new CreateUpdateBookDto - { - Name = "", - Price = 10, - PublishDate = DateTime.Now, - Type = BookType.ScienceFiction - } - ); - }); - - exception.ValidationErrors - .ShouldContain(err => err.MemberNames.Any(mem => mem == "Name")); -} -```` - -* 由于 `Name` 是空值, ABP 抛出一个 `AbpValidationException` 异常. - -打开**测试资源管理器**(测试 -> Windows -> 测试资源管理器)并**执行**所有测试: - -![bookstore-appservice-tests](images/bookstore-appservice-tests.png) - -恭喜, 绿色图标表示测试已成功通过! + \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/Part-1.md b/docs/zh-Hans/Tutorials/Part-1.md index 43590faf2b..9589c430b6 100644 --- a/docs/zh-Hans/Tutorials/Part-1.md +++ b/docs/zh-Hans/Tutorials/Part-1.md @@ -30,7 +30,7 @@ ASP.NET Core {{UI_Value}} 系列教程包括三个3个部分: - [Part-2: 创建,编辑,删除书籍](Part-2.md) - [Part-3: 集成测试](Part-3.md) -> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). +> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-booktore-application). ### 创建新项目 @@ -44,19 +44,19 @@ ASP.NET Core {{UI_Value}} 系列教程包括三个3个部分: abp new Acme.BookStore --template app --database-provider {{DB}} --ui {{UI_Text}} --mobile none ``` -![Creating project](./images/bookstore-create-project-{{UI_Text}}.png) +![Creating project](./images/booktore-create-project-{{UI_Text}}.png) ### 应用迁移 项目创建后,需要应用初始化迁移创建数据库. 运行 `Acme.BookStore.DbMigrator` 应用程序. 它会应用所有迁移,完成流程后你会看到以下结果,数据库已经准备好了! -![Migrations applied](./images/bookstore-migrations-applied-{{UI_Text}}.png) +![Migrations applied](./images/booktore-migrations-applied-{{UI_Text}}.png) > 另外你也可以在 Visual Studio 包管理控制台运行 `Update-Database` 命令应用迁移. #### 初始化数据库表 -![Initial database tables](./images/bookstore-database-tables-{{DB}}.png) +![Initial database tables](./images/booktore-database-tables-{{DB}}.png) ### 运行应用程序 @@ -64,7 +64,7 @@ abp new Acme.BookStore --template app --database-provider {{DB}} --ui {{UI_Text} 更多信息,参阅[入门教程](../../Getting-Started?UI={{UI}})的运行应用程序部分. -![Set as startup project](./images/bookstore-start-project-{{UI_Text}}.png) +![Set as startup project](./images/booktore-start-project-{{UI_Text}}.png) {{if UI == "NG"}} @@ -105,7 +105,7 @@ http://localhost:4200/ 下面的图片展示了从启动模板创建的项目是如何分层的. -![bookstore-visual-studio-solution](./images/bookstore-solution-structure-{{UI_Text}}.png) +![booktore-visual-studio-solution](./images/booktore-solution-structure-{{UI_Text}}.png) > 你可以查看[应用程序模板文档](../startup-templates/application#solution-structure)以详细了解解决方案结构. @@ -185,7 +185,7 @@ EF Core需要你将实体和 `DbContext` 建立关联.最简单的做法是在`A ````C# public class BookStoreDbContext : AbpDbContext { - public DbSet Books { get; set; } + public DbSet Book { get; set; } ... } ```` @@ -194,13 +194,13 @@ public class BookStoreDbContext : AbpDbContext {{if DB == "mongodb"}} -添加 `IMongoCollection Books` 属性到 `Acme.BookStore.MongoDB` 项目的 `BookStoreMongoDbContext` 中. +添加 `IMongoCollection Book` 属性到 `Acme.BookStore.MongoDB` 项目的 `BookStoreMongoDbContext` 中. ```csharp public class BookStoreMongoDbContext : AbpMongoDbContext { public IMongoCollection Users => Collection(); - public IMongoCollection Books => Collection();//<--added this line--> + public IMongoCollection Book => Collection();//<--added this line--> //... } ``` @@ -216,7 +216,7 @@ public class BookStoreMongoDbContext : AbpMongoDbContext ````csharp builder.Entity(b => { - b.ToTable(BookStoreConsts.DbTablePrefix + "Books", BookStoreConsts.DbSchema); + b.ToTable(BookStoreConsts.DbTablePrefix + "Book", BookStoreConsts.DbSchema); b.ConfigureByConvention(); //auto configure for the base class props b.Property(x => x.Name).IsRequired().HasMaxLength(128); }); @@ -295,7 +295,7 @@ namespace Acme.BookStore 这个启动模板使用了[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)来创建并维护数据库结构.打开 **程序包管理器控制台(Package Manager Console) (PMC)** (工具/Nuget包管理器菜单) -![Open Package Manager Console](./images/bookstore-open-package-manager-console.png) +![Open Package Manager Console](./images/booktore-open-package-manager-console.png) 选择 `Acme.BookStore.EntityFrameworkCore.DbMigrations`作为默认的项目然后执行下面的命令: @@ -303,7 +303,7 @@ namespace Acme.BookStore Add-Migration "Created_Book_Entity" ``` -![bookstore-pmc-add-book-migration](./images/bookstore-pmc-add-book-migration-v2.png) +![booktore-pmc-add-book-migration](./images/booktore-pmc-add-book-migration-v2.png) 这样就会在 `Migrations` 文件夹中创建一个新的migration类.然后执行 `Update-Database` 命令更新数据库结构: @@ -311,24 +311,24 @@ Add-Migration "Created_Book_Entity" Update-Database ```` -![bookstore-update-database-after-book-entity](./images/bookstore-update-database-after-book-entity.png) +![booktore-update-database-after-book-entity](./images/booktore-update-database-after-book-entity.png) #### 添加示例数据 -`Update-Database`命令在数据库中创建了`AppBooks`表. 打开数据库并输入几个示例行,以便在页面上显示它们: +`Update-Database`命令在数据库中创建了`AppBook`表. 打开数据库并输入几个示例行,以便在页面上显示它们: ```mssql -INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES +INSERT INTO AppBook (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES ('f3c04764-6bfd-49e2-859e-3f9bfda6183e', '2018-07-01', '1984',3,'1949-06-08','19.84') -INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES +INSERT INTO AppBook (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES ('13024066-35c9-473c-997b-83cd8d3e29dc', '2018-07-01', 'The Hitchhiker`s Guide to the Galaxy',7,'1995-09-27','42') -INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES +INSERT INTO AppBook (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES ('4fa024a1-95ac-49c6-a709-6af9e4d54b54', '2018-07-02', 'Pet Sematary',5,'1983-11-14','23.7') ``` -![bookstore-books-table](./images/bookstore-books-table.png) +![booktore-book-table](./images/booktore-book-table.png) {{end}} @@ -501,7 +501,7 @@ namespace Acme.BookStore 你会看到一些内置的接口和`Book`的接口,它们都是REST风格的: -![bookstore-swagger](images/bookstore-swagger.png) +![booktore-swagger](images/booktore-swagger.png) Swagger有一个很好的UI来测试API. 你可以尝试执行`[GET] /api/app/book` API来获取书籍列表. @@ -529,11 +529,11 @@ acme.bookStore.book.getList({}).done(function (result) { console.log(result); }) 运行这段代码会产生下面的输出: -![bookstore-test-js-proxy-getlist](./images/bookstore-test-js-proxy-getlist.png) +![booktore-test-js-proxy-getlist](./images/booktore-test-js-proxy-getlist.png) 你可以看到服务器返回的 **book list**.你还可以切换到开发者工具的 **network** 查看客户端到服务器端的通讯信息: -![bookstore-test-js-proxy-getlist-network](./images/bookstore-test-js-proxy-getlist-network.png) +![booktore-test-js-proxy-getlist-network](./images/booktore-test-js-proxy-getlist-network.png) 我们使用`create`方法 **创建一本新书**: @@ -547,15 +547,15 @@ acme.bookStore.book.create({ name: 'Foundation', type: 7, publishDate: '1951-05- successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246 ```` -检查数据库中的`Books`表以查看新书. 你可以自己尝试`get`,`update`和`delete`功能. +检查数据库中的`Book`表以查看新书. 你可以自己尝试`get`,`update`和`delete`功能. ### 创建书籍页面 现在我们来创建一些可见和可用的东西,取代经典的MVC,我们使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start). -在 `Acme.BookStore.Web`项目的`Pages`文件夹下创建一个新的文件夹叫`Books`并添加一个名为`Index.cshtml`的Razor Page. +在 `Acme.BookStore.Web`项目的`Pages`文件夹下创建一个新的文件夹叫`Book`并添加一个名为`Index.cshtml`的Razor Page. -![bookstore-add-index-page](./images/bookstore-add-index-page-v2.png) +![booktore-add-index-page](./images/booktore-add-index-page-v2.png) 打开`Index.cshtml`并把内容修改成下面这样: @@ -563,22 +563,22 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246 ````html @page -@using Acme.BookStore.Web.Pages.Books +@using Acme.BookStore.Web.Pages.Book @inherits Acme.BookStore.Web.Pages.BookStorePage @model IndexModel -

Books

+

Book

```` * 此代码更改了Razor View Page Model的默认继承,因此它从`BookStorePage`类(而不是`PageModel`)继承.启动模板附带的`BookStorePage`类,提供所有页面使用的一些共享属性/方法. -* 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Web.Pages.Books`命名空间,或者在`Index.cshtml`中更新它. +* 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Web.Pages.Book`命名空间,或者在`Index.cshtml`中更新它. **Index.cshtml.cs:** ```csharp using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Acme.BookStore.Web.Pages.Books +namespace Acme.BookStore.Web.Pages.Book { public class IndexModel : PageModel { @@ -590,7 +590,7 @@ namespace Acme.BookStore.Web.Pages.Books } ``` -#### 将Books页面添加到主菜单 +#### 将Book页面添加到主菜单 打开`Menus`文件夹中的 `BookStoreMenuContributor` 类,在`ConfigureMainMenuAsync`方法的底部添加如下代码: @@ -604,9 +604,9 @@ namespace Acme.BookStore.Web.Menus { //<-- added the below code context.Menu.AddItem( - new ApplicationMenuItem("BooksStore", l["Menu:BookStore"]) + new ApplicationMenuItem("BookStore", l["Menu:BookStore"]) .AddItem( - new ApplicationMenuItem("BooksStore.Books", l["Menu:Books"], url: "/Books") + new ApplicationMenuItem("BookStore.Book", l["Menu:Book"], url: "/Book") ) ); //--> @@ -621,9 +621,9 @@ namespace Acme.BookStore.Web.Menus 本地化文本位于`Acme.BookStore.Domain.Shared`项目的`Localization/BookStore`文件夹下: -![bookstore-localization-files](./images/bookstore-localization-files-v2.png) +![booktore-localization-files](./images/booktore-localization-files-v2.png) -打开`en.json`文件,将`Menu:BookStore`和`Menu:Books`键的本地化文本添加到文件末尾: +打开`en.json`文件,将`Menu:BookStore`和`Menu:Book`键的本地化文本添加到文件末尾: ````json { @@ -634,7 +634,7 @@ namespace Acme.BookStore.Web.Menus "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.", "Menu:BookStore": "Book Store", - "Menu:Books": "Books", + "Menu:Book": "Book", "Actions": "Actions", "Edit": "Edit", "PublishDate": "Publish date", @@ -653,9 +653,9 @@ namespace Acme.BookStore.Web.Menus 运行该应用程序,看到新菜单项已添加到顶部栏: -![bookstore-menu-items](./images/bookstore-new-menu-item.png) +![booktore-menu-items](./images/booktore-new-menu-item.png) -点击BookStore下Books子菜单项就会跳转到新增的书籍页面. +点击BookStore下Book子菜单项就会跳转到新增的书籍页面. #### 书籍列表 @@ -663,22 +663,22 @@ namespace Acme.BookStore.Web.Menus ##### Index.cshtml -将`Pages/Books/Index.cshtml`改成下面的样子: +将`Pages/Book/Index.cshtml`改成下面的样子: ````html @page @inherits Acme.BookStore.Web.Pages.BookStorePage -@model Acme.BookStore.Web.Pages.Books.IndexModel +@model Acme.BookStore.Web.Pages.Book.IndexModel @section scripts { - + } -

@L["Books"]

+

@L["Book"]

- + @L["Name"] @@ -699,15 +699,15 @@ namespace Acme.BookStore.Web.Menus #### 添加脚本文件 -在`Pages/Books/`文件夹中创建 `index.js`文件 +在`Pages/Book/`文件夹中创建 `index.js`文件 -![bookstore-index-js-file](./images/bookstore-index-js-file-v2.png) +![booktore-index-js-file](./images/booktore-index-js-file-v2.png) `index.js`的内容如下: ````js $(function () { - var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ + var dataTable = $('#BookTable').DataTable(abp.libs.datatables.normalizeConfiguration({ ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), columnDefs: [ { data: "name" }, @@ -727,7 +727,7 @@ $(function () { 最终的页面如下: -![Book list](./images/bookstore-book-list-2.png) +![Book list](./images/booktore-book-list-2.png) {{end}} @@ -735,7 +735,7 @@ $(function () { ### Angular 开发 -#### 创建books页面 +#### 创建book页面 是时候创建可见和可用的东西了!开发ABP Angular前端应用程序时,需要使用一些工具: @@ -752,31 +752,31 @@ $(function () { yarn ``` -#### BooksModule +#### BookModule -运行以下命令创建一个名为 `BooksModule` 的新模块: +运行以下命令创建一个名为 `BookModule` 的新模块: ```bash -yarn ng generate module books --route books --module app.module +yarn ng generate module book --routing true ``` -![Generating books module](./images/bookstore-creating-books-module-terminal.png) +![Generating book module](./images/booktore-creating-book-module-terminal.png) #### 路由 -打开位于 `src\app` 目录下的 `app-routing.module.ts` 文件. 添加新的 `import` 和替换 `books` 路径: +打开位于 `src\app` 目录下的 `app-routing.module.ts` 文件. 添加新的 `import` 和路由: ```js import { ApplicationLayoutComponent } from '@abp/ng.theme.basic'; //==> added this line to imports <== -//...replaced original books path with the below +//...added book path with the below to the routes array { - path: 'books', + path: 'book', component: ApplicationLayoutComponent, - loadChildren: () => import('./books/books.module').then(m => m.BooksModule), + loadChildren: () => import('./book/book.module').then(m => m.BookModule), data: { routes: { - name: '::Menu:Books', + name: '::Menu:Book', iconClass: 'fas fa-book' } as ABP.Route }, @@ -785,71 +785,50 @@ import { ApplicationLayoutComponent } from '@abp/ng.theme.basic'; //==> added th * `ApplicationLayoutComponent` 配置将应用程序布局设置为新页面, 我们添加了 `data` 对象. `name` 是菜单项的名称,`iconClass` 是菜单项的图标. -运行 `yarn start` 等待Angular为应用程序启动服务: - -```bash -yarn start -``` - -打开浏览器导航到 http://localhost:4200/books. 你会看到一个带有 "*books works!*" 的空白页. - -![initial-books-page](./images/bookstore-initial-books-page-with-layout.png) - #### Book 列表组件 -用以下内容替换 `books.component.html`: - -```html - -``` - 在命令行运行以下命令,生成名为 book-list 的新组件: ```bash -yarn ng generate component books/book-list +yarn ng generate component book/book-list ``` -![Creating books list](./images/bookstore-creating-book-list-terminal.png) +![Creating book list](./images/booktore-creating-book-list-terminal.png) -打开 `app\books` 目录下的 `books.module.ts` 文件,使用以下内容替换它: +打开 `app\book` 目录下的 `book.module.ts` 文件,使用以下内容替换它: ```js import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; - -import { BooksRoutingModule } from './books-routing.module'; -import { BooksComponent } from './books.component'; +import { BookRoutingModule } from './book-routing.module'; import { BookListComponent } from './book-list/book-list.component'; import { SharedModule } from '../shared/shared.module'; //<== added this line ==> @NgModule({ - declarations: [BooksComponent, BookListComponent], + declarations: [BookListComponent], imports: [ CommonModule, - BooksRoutingModule, + BookRoutingModule, SharedModule, //<== added this line ==> - ] + ], }) -export class BooksModule { } +export class BookModule {} ``` * 我们导入了 `SharedModule` 并添加到 `imports` 数组. -打开 `app\books` 目录下的 `books-routing.module.ts` 文件用以下内容替换它: +打开 `app\book` 目录下的 `book-routing.module.ts` 文件用以下内容替换它: ```js import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; +import { BookListComponent } from './book-list/book-list.component'; // <== added this line ==> -import { BooksComponent } from './books.component'; -import { BookListComponent } from './book-list/book-list.component'; //<== added this line ==> - -//<== replaced routes ==> +// <== replaced routes ==> const routes: Routes = [ { path: '', - component: BooksComponent, - children: [{ path: '', component: BookListComponent }], + component: BookListComponent, }, ]; @@ -857,36 +836,42 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) -export class BooksRoutingModule { } +export class BookRoutingModule { } ``` * 我们导入了 `BookListComponent` 并替换 `routes` 常量. -我们将看到books页面的 **book-list works!**: +运行 `yarn start`,等待Angular启动服务: + +```bash +yarn start +``` + +我们将看到book页面的 **book-list works!**: -![Initial book list page](./images/bookstore-initial-book-list-page.png) +![Initial book list page](./images/booktore-initial-book-list-page.png) -#### 创建 BooksState +#### 创建 BookState -运行以下命令创建名为 `BooksState` 的新state: +运行以下命令创建名为 `BookState` 的新state: ```bash -npx @ngxs/cli --name books --directory src/app/books +npx @ngxs/cli --name book --directory src/app/book ``` -* 此命令在 `src/app/books/state` 文件夹下创建了 `books.state.ts` 和 `books.actions.ts` 文件. 参阅 [NGXS CLI文档](https://www.ngxs.io/plugins/cli)了解更多. +* 此命令在 `src/app/book/state` 文件夹下创建了 `book.state.ts` 和 `book.actions.ts` 文件. 参阅 [NGXS CLI文档](https://www.ngxs.io/plugins/cli)了解更多. -将 `BooksState` 导入到 `src/app` 文件夹中的 `app.module.ts` 中. 然后添加 `BooksState` 到 `NgxsModule` 的 `forRoot` 静态方法,作为该方法的第一个参数的数组元素. +将 `BookState` 导入到 `src/app` 文件夹中的 `app.module.ts` 中. 然后添加 `BookState` 到 `NgxsModule` 的 `forRoot` 静态方法,作为该方法的第一个参数的数组元素. ```js // ... -import { BooksState } from './books/state/books.state'; //<== imported BooksState ==> +import { BookState } from './book/state/book.state'; //<== imported BookState ==> @NgModule({ imports: [ // other imports - NgxsModule.forRoot([BooksState]), //<== added BooksState ==> + NgxsModule.forRoot([BookState]), //<== added BookState ==> //other imports ], @@ -911,50 +896,50 @@ abp generate-proxy --module app ![Generated files](./images/generated-proxies.png) -#### GetBooks 动作 +#### GetBook 动作 动作可以被认为是一个命令,它应该触发某些事情发生,或者是已经发生的事情的结果事件.[See NGXS Actions文档](https://www.ngxs.io/concepts/actions). -打开 `app/books/state` 目录下的 `books.actions.ts` 文件用以下内容替换它: +打开 `app/book/state` 目录下的 `book.actions.ts` 文件用以下内容替换它: ```js -export class GetBooks { - static readonly type = '[Books] Get'; +export class GetBook { + static readonly type = '[Book] Get'; } ``` -#### 实现 BooksState +#### 实现 BookState -打开 `app/books/state` 目录下的 `books.state.ts` 文件用以下内容替换它: +打开 `app/book/state` 目录下的 `book.state.ts` 文件用以下内容替换它: ```js import { PagedResultDto } from '@abp/ng.core'; import { State, Action, StateContext, Selector } from '@ngxs/store'; -import { GetBooks } from './books.actions'; -import { BookService } from '../../app/shared/services'; +import { GetBooks } from './book.actions'; +import { BookService } from '../services'; import { tap } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { BookDto } from '../../app/shared/models'; +import { BookDto } from '../models'; -export class BooksStateModel { +export class BookStateModel { public book: PagedResultDto; } -@State({ - name: 'BooksState', - defaults: { book: {} } as BooksStateModel, +@State({ + name: 'BookState', + defaults: { book: {} } as BookStateModel, }) @Injectable() -export class BooksState { +export class BookState { @Selector() - static getBooks(state: BooksStateModel) { + static getBooks(state: BookStateModel) { return state.book.items || []; } constructor(private bookService: BookService) {} @Action(GetBooks) - get(ctx: StateContext) { + get(ctx: StateContext) { return this.bookService.getListByInput().pipe( tap((booksResponse) => { ctx.patchState({ @@ -966,22 +951,22 @@ export class BooksState { } ``` -* 我们添加了book属性到BooksStateModel模态框. -* 我们添加了 `GetBooks` 动作. 它通过 ABP CLI生成的 `BooksService` 检索图书数据. +* 我们添加了book属性到BookStateModel模态框. +* 我们添加了 `GetBook` 动作. 它通过 ABP CLI生成的 `BookService` 检索图书数据. * `NGXS` 需要在不订阅get函数的情况下返回被观察对象. #### BookListComponent -打开 `app\books\book-list` 目录下的 `book-list.component.ts` 用以下内容替换它: +打开 `app\book\book-list` 目录下的 `book-list.component.ts` 用以下内容替换它: ```js import { Component, OnInit } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; import { finalize } from 'rxjs/operators'; -import { BookDto, BookType } from '../../app/shared/models'; -import { GetBooks } from '../state/books.actions'; -import { BooksState } from '../state/books.state'; +import { BookDto, BookType } from '../models'; +import { GetBooks } from '../state/book.actions'; +import { BookState } from '../state/book.state'; @Component({ selector: 'app-book-list', @@ -989,7 +974,7 @@ import { BooksState } from '../state/books.state'; styleUrls: ['./book-list.component.scss'], }) export class BookListComponent implements OnInit { - @Select(BooksState.getBooks) + @Select(BookState.getBooks) books$: Observable; booksType = BookType; @@ -1012,10 +997,10 @@ export class BookListComponent implements OnInit { } ``` -* 我们添加了 `get` 函数获取books更新store. +* 我们添加了 `get` 函数获取book更新store. * 有关 `NGXS` 特性的更多信息请参见NGXS文档中的[Dispatching actions](https://ngxs.gitbook.io/ngxs/concepts/store#dispatching-actions)和[Select](https://ngxs.gitbook.io/ngxs/concepts/select). -打开 `app\books\book-list` 目录下的 `book-list.component.html` 用以下内容替换它: +打开 `app\book\book-list` 目录下的 `book-list.component.html` 用以下内容替换它: ```html
@@ -1023,7 +1008,7 @@ export class BookListComponent implements OnInit {
- {%{{{ "::Menu:Books" | abpLocalization }}}%} + {%{{{ "::Menu:Book" | abpLocalization }}}%}
@@ -1031,7 +1016,7 @@ export class BookListComponent implements OnInit {
{%{{{ data.name }}}%} - {%{{{ booksType[data.type] }}}%} + {%{{{ bookType[data.type] }}}%} {%{{{ data.publishDate | date }}}%} {%{{{ data.price }}}%} @@ -1063,11 +1048,11 @@ export class BookListComponent implements OnInit { 现在你可以在浏览器看到最终结果: -![Book list final result](./images/bookstore-book-list.png) +![Book list final result](./images/booktore-book-list.png) 项目的文件系统结构: -![Book list final result](./images/bookstore-angular-file-tree.png) +![Book list final result](./images/booktore-angular-file-tree.png) 在本教程中我们遵循了官方的[Angular风格指南](https://angular.io/guide/styleguide#file-tree). diff --git a/docs/zh-Hans/Tutorials/Part-2.md b/docs/zh-Hans/Tutorials/Part-2.md index 56dadcbbcd..5e73c753e6 100644 --- a/docs/zh-Hans/Tutorials/Part-2.md +++ b/docs/zh-Hans/Tutorials/Part-2.md @@ -29,7 +29,7 @@ end - **Part 2: 创建,编辑,删除书籍(本章)** - [Part-3: 集成测试](Part-3.md) -> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). +> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-booktore-application). {{if UI == "MVC"}} @@ -37,13 +37,13 @@ end 通过本节, 你将会了解如何创建一个 modal form 来实现新增书籍的功能. 最终成果如下图所示: -![bookstore-create-dialog](./images/bookstore-create-dialog-2.png) +![booktore-create-dialog](./images/booktore-create-dialog-2.png) #### 新建 modal form 在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个 `CreateModal.cshtml` Razor页面: -![bookstore-add-create-dialog](./images/bookstore-add-create-dialog-v2.png) +![booktore-add-create-dialog](./images/booktore-add-create-dialog-v2.png) ##### CreateModal.cshtml.cs @@ -130,9 +130,9 @@ namespace Acme.BookStore.Web.Pages.Books 如下图所示,只是在表格 **右上方** 添加了 **New book** 按钮: -![bookstore-new-book-button](./images/bookstore-new-book-button.png) +![booktore-new-book-button](./images/booktore-new-book-button.png) -打开 `Pages/books/index.js` 在 `datatable` 配置代码后面添加如下代码: +打开 `Pages/book/index.js` 在 `datatable` 配置代码后面添加如下代码: ````js var createModal = new abp.ModalManager(abp.appPath + 'Books/CreateModal'); @@ -155,7 +155,7 @@ $('#NewBookButton').click(function (e) { 在 `Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个名叫 `EditModal.cshtml` 的Razor页面: -![bookstore-add-edit-dialog](./images/bookstore-add-edit-dialog.png) +![booktore-add-edit-dialog](./images/booktore-add-edit-dialog.png) #### EditModal.cshtml.cs @@ -258,7 +258,7 @@ namespace Acme.BookStore.Web 我们将为表格每行添加下拉按钮 ("Actions") . 最终效果如下: -![bookstore-books-table-actions](images/bookstore-books-table-actions.png) +![booktore-book-table-actions](images/booktore-book-table-actions.png) 打开 `Pages/Books/Index.cshtml` 页面,并按下方所示修改表格部分的代码: @@ -279,7 +279,7 @@ namespace Acme.BookStore.Web * 只是为"Actions"增加了一个 `th` 标签. -打开 `Pages/books/index.js` 并用以下内容进行替换: +打开 `Pages/book/index.js` 并用以下内容进行替换: ````js $(function () { @@ -346,7 +346,7 @@ $(function () { ### 删除一个已有的Book实体 -打开 `Pages/books/index.js` 文件,在 `rowAction` `items` 下新增一项: +打开 `Pages/book/index.js` 文件,在 `rowAction` `items` 下新增一项: ````js { @@ -456,54 +456,54 @@ $(function () { #### 状态定义 -在 `books\state` 文件夹下打开 `books.action.ts` 文件,使用以下内容替换它: +在 `app\book\state` 文件夹下打开 `book.action.ts` 文件,使用以下内容替换它: ```js -import { CreateUpdateBookDto } from '../../app/shared/models'; //<== added this line ==> +import { CreateUpdateBookDto } from '../models'; //<== added this line ==> export class GetBooks { - static readonly type = '[Books] Get'; + static readonly type = '[Book] Get'; } // added CreateUpdateBook class export class CreateUpdateBook { - static readonly type = '[Books] Create Update Book'; + static readonly type = '[Book] Create Update Book'; constructor(public payload: CreateUpdateBookDto) { } } ``` * 我们导入了 `CreateUpdateBookDto` 模型并且创建了 `CreateUpdateBook` 动作. -打开 `books\state` 文件夹下的 `books.state.ts` 文件,使用以下内容替换它: +打开 `app\book\state` 文件夹下的 `book.state.ts` 文件,使用以下内容替换它: ```js import { PagedResultDto } from '@abp/ng.core'; import { State, Action, StateContext, Selector } from '@ngxs/store'; -import { GetBooks, CreateUpdateBook } from './books.actions'; // <== added CreateUpdateBook==> -import { BookService } from '../../app/shared/services'; +import { GetBooks, CreateUpdateBook } from './book.actions'; // <== added CreateUpdateBook==> +import { BookService } from '../services'; import { tap } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { BookDto } from '../../app/shared/models'; +import { BookDto } from '../models'; -export class BooksStateModel { +export class BookStateModel { public book: PagedResultDto; } -@State({ - name: 'BooksState', - defaults: { book: {} } as BooksStateModel, +@State({ + name: 'BookState', + defaults: { book: {} } as BookStateModel, }) @Injectable() -export class BooksState { +export class BookState { @Selector() - static getBooks(state: BooksStateModel) { + static getBooks(state: BookStateModel) { return state.book.items || []; } constructor(private bookService: BookService) {} @Action(GetBooks) - get(ctx: StateContext) { + get(ctx: StateContext) { return this.bookService.getListByInput().pipe( tap((bookResponse) => { ctx.patchState({ @@ -515,7 +515,7 @@ export class BooksState { // added CreateUpdateBook action listener @Action(CreateUpdateBook) - save(ctx: StateContext, action: CreateUpdateBook) { + save(ctx: StateContext, action: CreateUpdateBook) { return this.bookService.createByInput(action.payload); } } @@ -527,7 +527,7 @@ export class BooksState { #### 添加模态到 BookListComponent -打开 `books\book-list` 文件夹内的 `book-list.component.html` 文件,使用以下内容替换它: +打开 `app\book\book-list` 文件夹内的 `book-list.component.html` 文件,使用以下内容替换它: ```html
@@ -556,7 +556,7 @@ export class BooksState {
{%{{{ data.name }}}%} - {%{{{ booksType[data.type] }}}%} + {%{{{ bookType[data.type] }}}%} {%{{{ data.publishDate | date }}}%} {%{{{ data.price }}}%} @@ -603,16 +603,16 @@ export class BooksState { * `abp-modal` 是显示模态框的预构建组件. 你也可以使用其它方法显示模态框,但 `abp-modal` 提供了一些附加的好处. * 我们添加了 `New book` 按钮到 `AbpContentToolbar`. -打开 `books\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: +打开 `app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: ```js import { Component, OnInit } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; import { finalize } from 'rxjs/operators'; -import { BookDto, BookType } from '../../app/shared/models'; -import { GetBooks } from '../state/books.actions'; -import { BooksState } from '../state/books.state'; +import { BookDto, BookType } from '../models'; +import { GetBooks } from '../state/book.actions'; +import { BookState } from '../state/book.state'; @Component({ selector: 'app-book-list', @@ -620,7 +620,7 @@ import { BooksState } from '../state/books.state'; styleUrls: ['./book-list.component.scss'], }) export class BookListComponent implements OnInit { - @Select(BooksState.getBooks) + @Select(BookState.getBooks) books$: Observable; booksType = BookType; @@ -654,22 +654,22 @@ export class BookListComponent implements OnInit { 你可以打开浏览器,点击**New book**按钮看到模态框. -![Empty modal for new book](./images/bookstore-empty-new-book-modal.png) +![Empty modal for new book](./images/booktore-empty-new-book-modal.png) #### 添加响应式表单 [响应式表单](https://angular.io/guide/reactive-forms) 提供一种模型驱动的方法来处理其值随时间变化的表单输入. -打开 `app\books\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: +打开 `app\app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: ```js import { Component, OnInit } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; import { finalize } from 'rxjs/operators'; -import { BookDto, BookType } from '../../app/shared/models'; -import { GetBooks } from '../state/books.actions'; -import { BooksState } from '../state/books.state'; +import { BookDto, BookType } from '../models'; +import { GetBooks } from '../state/book.actions'; +import { BookState } from '../state/book.state'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== added this line ==> @Component({ @@ -678,7 +678,7 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== adde styleUrls: ['./book-list.component.scss'], }) export class BookListComponent implements OnInit { - @Select(BooksState.getBooks) + @Select(BookState.getBooks) books$: Observable; booksType = BookType; @@ -729,7 +729,7 @@ export class BookListComponent implements OnInit { #### 创建表单的DOM元素 -打开 `app\books\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 ` `: +打开 `app\app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 ` `: ```html @@ -748,7 +748,7 @@ export class BookListComponent implements OnInit { *
@@ -772,14 +772,14 @@ export class BookListComponent implements OnInit { #### Datepicker 要求 -打开 `app\books` 文件夹下的 `books.module.ts` 文件,使用以下内容替换它: +打开 `app\book` 文件夹下的 `book.module.ts` 文件,使用以下内容替换它: ```js import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { BooksRoutingModule } from './books-routing.module'; -import { BooksComponent } from './books.component'; +import { BooksRoutingModule } from './book-routing.module'; +import { BooksComponent } from './book.component'; import { BookListComponent } from './book-list/book-list.component'; import { SharedModule } from '../shared/shared.module'; import { NgbDatepickerModule } from '@ng-bootstrap/ng-bootstrap'; //<== added this line ==> @@ -798,16 +798,16 @@ export class BooksModule { } * 我们导入了 `NgbDatepickerModule` 来使用日期选择器. -打开 `app\books\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: +打开 `app\app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: ```js import { Component, OnInit } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; import { finalize } from 'rxjs/operators'; -import { BookDto, BookType } from '../../app/shared/models'; -import { GetBooks } from '../state/books.actions'; -import { BooksState } from '../state/books.state'; +import { BookDto, BookType } from '../models'; +import { GetBooks } from '../state/book.actions'; +import { BookState } from '../state/book.state'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; // <== added this line ==> @@ -818,7 +818,7 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], // <== added this line ==> }) export class BookListComponent implements OnInit { - @Select(BooksState.getBooks) + @Select(BookState.getBooks) books$: Observable; booksType = BookType; @@ -878,20 +878,20 @@ export class BookListComponent implements OnInit { 现在你可以打开浏览器看到以下变化: -![New book modal](./images/bookstore-new-book-form.png) +![New book modal](./images/booktore-new-book-form.png) #### 保存图书 -打开 `app\books\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: +打开 `app\app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,使用以下内容替换它: ```js import { Component, OnInit } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; import { finalize } from 'rxjs/operators'; -import { BookDto, BookType } from '../../app/shared/models'; -import { GetBooks, CreateUpdateBook } from '../state/books.actions'; // <== added CreateUpdateBook ==> -import { BooksState } from '../state/books.state'; +import { BookDto, BookType } from '../models'; +import { GetBooks, CreateUpdateBook } from '../state/book.actions'; // <== added CreateUpdateBook ==> +import { BookState } from '../state/book.state'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; @@ -902,12 +902,11 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], }) export class BookListComponent implements OnInit { - @Select(BooksState.getBooks) + @Select(BookState.getBooks) books$: Observable; booksType = BookType; - //added bookTypeArr array bookTypeArr = Object.keys(BookType).filter( (bookType) => typeof this.booksType[bookType] === 'number' ); @@ -946,7 +945,7 @@ export class BookListComponent implements OnInit { }); } - //<== added save ==> + // <== added save ==> save() { if (this.form.invalid) { return; @@ -964,7 +963,7 @@ export class BookListComponent implements OnInit { * 我们导入了 `CreateUpdateBook`. * 我们添加了 `save` 方法. -打开 `app\books\book-list` 文件夹下的 `app\books\book-list`文件, 添加 `abp-button` 保存图书. +打开 `app\app\book\book-list` 文件夹下的 `app\app\book\book-list`文件, 添加 `abp-button` 保存图书. ```html @@ -991,34 +990,34 @@ export class BookListComponent implements OnInit { 模态框最终看起来像这样: -![Save button to the modal](./images/bookstore-new-book-form-v2.png) +![Save button to the modal](./images/booktore-new-book-form-v2.png) ### 更新图书 #### CreateUpdateBook 动作 -打开 `books\state` 文件夹下的 `books.actions.ts` 文件,使用以下内容替换它: +打开 `app\book\state` 文件夹下的 `book.actions.ts` 文件,使用以下内容替换它: ```js -import { CreateUpdateBookDto } from '../../app/shared/models'; +import { CreateUpdateBookDto } from '../models'; export class GetBooks { - static readonly type = '[Books] Get'; + static readonly type = '[Book] Get'; } export class CreateUpdateBook { - static readonly type = '[Books] Create Update Book'; - constructor(public payload: CreateUpdateBookDto, public id?: string) { } // <== added id parameter ==> + static readonly type = '[Book] Create Update Book'; + constructor(public payload: CreateUpdateBookDto, public id?: string) {} // <== added id parameter ==> } ``` * 我们在 `CreateUpdateBook` 动作的构造函数添加了 `id` 参数. -打开 `books\state` 文件夹下的 `books.state.ts` 文件,使用以下内容替换 `save` 方法: +打开 `app\book\state` 文件夹下的 `book.state.ts` 文件,使用以下内容替换 `save` 方法: ```js @Action(CreateUpdateBook) -save(ctx: StateContext, action: CreateUpdateBook) { +save(ctx: StateContext, action: CreateUpdateBook) { if (action.id) { return this.bookService.updateByIdAndInput(action.payload, action.id); } else { @@ -1029,19 +1028,19 @@ save(ctx: StateContext, action: CreateUpdateBook) { #### BookListComponent -打开 `app\books\book-list` 文件夹下的 `book-list.component.ts` 文件,在构造函数注入 `BookService` 服务,并添加 名为 `selectedBook` 的变量. +打开 `app\app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,在构造函数注入 `BookService` 服务,并添加 名为 `selectedBook` 的变量. ```js import { Component, OnInit } from '@angular/core'; import { Select, Store } from '@ngxs/store'; import { Observable } from 'rxjs'; import { finalize } from 'rxjs/operators'; -import { BookDto, BookType } from '../../app/shared/models'; -import { GetBooks, CreateUpdateBook } from '../state/books.actions'; -import { BooksState } from '../state/books.state'; +import { BookDto, BookType } from '../models'; +import { GetBooks, CreateUpdateBook } from '../state/book.actions'; +import { BookState } from '../state/book.state'; import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; -import { BookService } from '../../app/shared/services'; // <== imported BookService ==> +import { BookService } from '../services'; // <== imported BookService ==> @Component({ selector: 'app-book-list', @@ -1050,13 +1049,13 @@ import { BookService } from '../../app/shared/services'; // <== imported BookSer providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], }) export class BookListComponent implements OnInit { - @Select(BooksState.getBooks) - books$: Observable; + @Select(BookState.getBooks) + book$: Observable; - booksType = BookType; + bookType = BookType; bookTypeArr = Object.keys(BookType).filter( - (bookType) => typeof this.booksType[bookType] === 'number' + (bookType) => typeof this.bookType[bookType] === 'number' ); loading = false; @@ -1137,12 +1136,12 @@ export class BookListComponent implements OnInit { #### 添加 "Actions" 下拉框到表格 -打开 `app\books\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `
` 标签: +打开 `app\app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `
` 标签: ```html
{%{{{ data.name }}}%} - {%{{{ booksType[data.type] }}}%} + {%{{{ bookType[data.type] }}}%} {%{{{ data.publishDate | date }}}%} {%{{{ data.price }}}%} @@ -1193,9 +1192,9 @@ export class BookListComponent implements OnInit { UI最终看起来像这样: -![Action buttons](./images/bookstore-actions-buttons.png) +![Action buttons](./images/booktore-actions-buttons.png) -打开 `app\books\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `` 标签: +打开 `app\app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `` 标签: ```html @@ -1209,45 +1208,45 @@ UI最终看起来像这样: #### DeleteBook 动作 -打开 `books\state` 文件夹下的 `books.actions.ts` 文件添加名为 `DeleteBook` 的动作. +打开 `app\book\state` 文件夹下的 `book.actions.ts` 文件添加名为 `DeleteBook` 的动作. ```js export class DeleteBook { - static readonly type = '[Books] Delete'; + static readonly type = '[Book] Delete'; constructor(public id: string) {} } ``` -打开 `books\state` 文件夹下的 `books.state.ts` 文件,使用以下内容替换它: +打开 `app\book\state` 文件夹下的 `book.state.ts` 文件,使用以下内容替换它: ```js import { PagedResultDto } from '@abp/ng.core'; import { State, Action, StateContext, Selector } from '@ngxs/store'; -import { GetBooks, CreateUpdateBook, DeleteBook } from './books.actions'; // <== added DeleteBook==> -import { BookService } from '../../app/shared/services'; +import { GetBooks, CreateUpdateBook, DeleteBook } from './book.actions'; // <== added DeleteBook==> +import { BookService } from '../services'; import { tap } from 'rxjs/operators'; import { Injectable } from '@angular/core'; -import { BookDto } from '../../app/shared/models'; +import { BookDto } from '../models'; -export class BooksStateModel { +export class BookStateModel { public book: PagedResultDto; } -@State({ - name: 'BooksState', - defaults: { book: {} } as BooksStateModel, +@State({ + name: 'BookState', + defaults: { book: {} } as BookStateModel, }) @Injectable() -export class BooksState { +export class BookState { @Selector() - static getBooks(state: BooksStateModel) { + static getBooks(state: BookStateModel) { return state.book.items || []; } constructor(private bookService: BookService) {} @Action(GetBooks) - get(ctx: StateContext) { + get(ctx: StateContext) { return this.bookService.getListByInput().pipe( tap((booksResponse) => { ctx.patchState({ @@ -1258,7 +1257,7 @@ export class BooksState { } @Action(CreateUpdateBook) - save(ctx: StateContext, action: CreateUpdateBook) { + save(ctx: StateContext, action: CreateUpdateBook) { if (action.id) { return this.bookService.updateByIdAndInput(action.payload, action.id); } else { @@ -1268,7 +1267,7 @@ export class BooksState { // <== added DeleteBook action listener ==> @Action(DeleteBook) - delete(ctx: StateContext, action: DeleteBook) { + delete(ctx: StateContext, action: DeleteBook) { return this.bookService.deleteById(action.id); } } @@ -1280,7 +1279,7 @@ export class BooksState { #### 删除确认弹层 -打开 `app\books\book-list` 文件夹下的 `book-list.component.ts` 文件,注入 `ConfirmationService`. +打开 `app\app\book\book-list` 文件夹下的 `book-list.component.ts` 文件,注入 `ConfirmationService`. 替换构造函数: @@ -1304,7 +1303,7 @@ constructor( 在 `book-list.component.ts` 中添加删除方法: ```js -import { GetBooks, CreateUpdateBook, DeleteBook } from '../state/books.actions' ;// <== imported DeleteBook ==> +import { GetBooks, CreateUpdateBook, DeleteBook } from '../state/book.actions' ;// <== imported DeleteBook ==> import { ConfirmationService, Confirmation } from '@abp/ng.theme.shared'; //<== imported Confirmation ==> @@ -1323,11 +1322,11 @@ delete(id: string) { `delete` 方法会显示一个确认弹层并订阅用户响应. 只在用户点击 `Yes` 按钮时分派动作. 确认弹层看起来如下: -![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png) +![booktore-confirmation-popup](./images/booktore-confirmation-popup.png) #### 添加删除按钮 -打开 `app\books\book-list` 文件夹下的 `app\books\book-list` 文件,修改 `ngbDropdownMenu` 添加删除按钮: +打开 `app\app\book\book-list` 文件夹下的 `app\app\book\book-list` 文件,修改 `ngbDropdownMenu` 添加删除按钮: ```html
@@ -1340,7 +1339,7 @@ delete(id: string) { 最终操作下拉框UI看起来如下: -![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png) +![booktore-final-actions-dropdown](./images/booktore-final-actions-dropdown.png) {{end}} diff --git a/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png b/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png index a3197b6457..ffa8dcd7e2 100644 Binary files a/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png and b/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-list.png b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png index 9e6cc9e010..d402895c9b 100644 Binary files a/docs/zh-Hans/Tutorials/images/bookstore-book-list.png and b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png b/docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png index 6f19dcc7bf..13829db60f 100644 Binary files a/docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png and b/docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-creating-book-module-terminal.png b/docs/zh-Hans/Tutorials/images/bookstore-creating-book-module-terminal.png new file mode 100644 index 0000000000..c935a6f130 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-creating-book-module-terminal.png differ diff --git a/docs/zh-Hans/Tutorials/images/generated-proxies.png b/docs/zh-Hans/Tutorials/images/generated-proxies.png index 9e466e7d55..1d322c0765 100644 Binary files a/docs/zh-Hans/Tutorials/images/generated-proxies.png and b/docs/zh-Hans/Tutorials/images/generated-proxies.png differ diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index c70284637d..9f7d69a784 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -30,8 +30,16 @@ "text": "应用开发", "items": [ { - "text": "使用 ASP.NET Core MVC", - "path": "Tutorials/AspNetCore-Mvc/Part-I.md" + "text": "第一章: 创建新解决方案和列表页", + "path": "Tutorials/Part-1.md" + }, + { + "text": "第一章: 增删改查操作", + "path": "Tutorials/Part-2.md" + }, + { + "text": "第三章: 集成测试", + "path": "Tutorials/Part-3.md" } ] }