Browse Source

Merge branch 'dev' into dev

pull/3754/head
Khaled 6 years ago
committed by GitHub
parent
commit
453fb47c3b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 24
      abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
  2. 6
      common.DotSettings
  3. 8
      docs/en/CLI.md
  4. 4
      docs/en/Getting-Started.md
  5. 52
      docs/en/Localization.md
  6. 8
      docs/en/Object-Extensions.md
  7. 6
      docs/en/Tutorials/Part-1.md
  8. 54
      docs/en/Tutorials/Part-2.md
  9. 4
      docs/en/Tutorials/Part-3.md
  10. BIN
      docs/en/Tutorials/images/bookstore-angular-file-tree.png
  11. 12
      docs/en/UI/Angular/Config-State.md
  12. 22
      docs/en/UI/Angular/Confirmation-Service.md
  13. 199
      docs/en/UI/Angular/Modifying-the-Menu.md
  14. BIN
      docs/en/UI/Angular/images/navigation-menu-after-patching.png
  15. BIN
      docs/en/UI/Angular/images/navigation-menu-search-input.png
  16. BIN
      docs/en/UI/Angular/images/navigation-menu-via-app-routing.png
  17. BIN
      docs/en/UI/Angular/images/navigation-menu-via-config-state.png
  18. 22
      docs/en/UI/Common/Utils/Linked-List.md
  19. 6
      docs/en/docs-nav.json
  20. 6
      docs/zh-Hans/CLI.md
  21. 9
      docs/zh-Hans/Getting-Started-Angular-Template.md
  22. 2
      docs/zh-Hans/Getting-Started-AspNetCore-Application.md
  23. 104
      docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md
  24. 8
      docs/zh-Hans/Getting-Started-With-Startup-Templates.md
  25. 411
      docs/zh-Hans/Getting-Started.md
  26. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png
  27. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png
  28. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png
  29. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png
  30. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png
  31. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png
  32. BIN
      docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png
  33. 1078
      docs/zh-Hans/Tutorials/Part-1.md
  34. 1349
      docs/zh-Hans/Tutorials/Part-2.md
  35. 198
      docs/zh-Hans/Tutorials/Part-3.md
  36. BIN
      docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png
  37. 0
      docs/zh-Hans/Tutorials/images/bookstore-add-create-dialog-v2.png
  38. 0
      docs/zh-Hans/Tutorials/images/bookstore-add-edit-dialog.png
  39. 0
      docs/zh-Hans/Tutorials/images/bookstore-add-index-page-v2.png
  40. BIN
      docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png
  41. 0
      docs/zh-Hans/Tutorials/images/bookstore-appservice-tests.png
  42. BIN
      docs/zh-Hans/Tutorials/images/bookstore-book-list-2.png
  43. BIN
      docs/zh-Hans/Tutorials/images/bookstore-book-list.png
  44. 0
      docs/zh-Hans/Tutorials/images/bookstore-books-table-actions.png
  45. 0
      docs/zh-Hans/Tutorials/images/bookstore-books-table.png
  46. BIN
      docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png
  47. 0
      docs/zh-Hans/Tutorials/images/bookstore-create-dialog-2.png
  48. 0
      docs/zh-Hans/Tutorials/images/bookstore-create-dialog.png
  49. BIN
      docs/zh-Hans/Tutorials/images/bookstore-create-project-angular.png
  50. BIN
      docs/zh-Hans/Tutorials/images/bookstore-create-project-mvc.png
  51. BIN
      docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png
  52. BIN
      docs/zh-Hans/Tutorials/images/bookstore-creating-books-module-terminal.png
  53. BIN
      docs/zh-Hans/Tutorials/images/bookstore-database-tables-ef.png
  54. BIN
      docs/zh-Hans/Tutorials/images/bookstore-database-tables-mongodb.png
  55. BIN
      docs/zh-Hans/Tutorials/images/bookstore-edit-button.png
  56. BIN
      docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png
  57. BIN
      docs/zh-Hans/Tutorials/images/bookstore-final-actions-dropdown.png
  58. BIN
      docs/zh-Hans/Tutorials/images/bookstore-generate-state-books.png
  59. 0
      docs/zh-Hans/Tutorials/images/bookstore-homepage.png
  60. 0
      docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v2.png
  61. BIN
      docs/zh-Hans/Tutorials/images/bookstore-initial-book-list-page.png
  62. BIN
      docs/zh-Hans/Tutorials/images/bookstore-initial-books-page-with-layout.png
  63. BIN
      docs/zh-Hans/Tutorials/images/bookstore-localization-files-v2.png
  64. 0
      docs/zh-Hans/Tutorials/images/bookstore-menu-items.png
  65. BIN
      docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-angular.png
  66. BIN
      docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-mvc.png
  67. BIN
      docs/zh-Hans/Tutorials/images/bookstore-new-book-button.png
  68. BIN
      docs/zh-Hans/Tutorials/images/bookstore-new-book-form-v2.png
  69. BIN
      docs/zh-Hans/Tutorials/images/bookstore-new-book-form.png
  70. BIN
      docs/zh-Hans/Tutorials/images/bookstore-new-menu-item.png
  71. BIN
      docs/zh-Hans/Tutorials/images/bookstore-open-package-manager-console.png
  72. BIN
      docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration-v2.png
  73. 0
      docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration.png
  74. BIN
      docs/zh-Hans/Tutorials/images/bookstore-service-terminal-output.png
  75. BIN
      docs/zh-Hans/Tutorials/images/bookstore-solution-structure-angular.png
  76. 0
      docs/zh-Hans/Tutorials/images/bookstore-solution-structure-mvc.png
  77. BIN
      docs/zh-Hans/Tutorials/images/bookstore-start-project-angular.png
  78. BIN
      docs/zh-Hans/Tutorials/images/bookstore-start-project-mvc.png
  79. BIN
      docs/zh-Hans/Tutorials/images/bookstore-swagger-book-dto-properties.png
  80. BIN
      docs/zh-Hans/Tutorials/images/bookstore-swagger.png
  81. 0
      docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist-network.png
  82. 0
      docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist.png
  83. BIN
      docs/zh-Hans/Tutorials/images/bookstore-test-projects-angular.png
  84. 0
      docs/zh-Hans/Tutorials/images/bookstore-test-projects-mvc.png
  85. BIN
      docs/zh-Hans/Tutorials/images/bookstore-test-projects-v2.png
  86. BIN
      docs/zh-Hans/Tutorials/images/bookstore-update-database-after-book-entity.png
  87. 0
      docs/zh-Hans/Tutorials/images/bookstore-user-management.png
  88. BIN
      docs/zh-Hans/Tutorials/images/bookstore-visual-studio-solution-v3.png
  89. BIN
      docs/zh-Hans/Tutorials/images/generate-proxy-command.png
  90. BIN
      docs/zh-Hans/Tutorials/images/generated-proxies.png
  91. BIN
      docs/zh-Hans/Tutorials/images/mozilla-self-signed-cert-error.png
  92. 9
      docs/zh-Hans/UI/Angular/Config-State.md
  93. 24
      docs/zh-Hans/UI/Angular/Confirmation-Service.md
  94. 197
      docs/zh-Hans/UI/Angular/Modifying-the-Menu.md
  95. BIN
      docs/zh-Hans/UI/Angular/images/navigation-menu-after-patching.png
  96. BIN
      docs/zh-Hans/UI/Angular/images/navigation-menu-search-input.png
  97. BIN
      docs/zh-Hans/UI/Angular/images/navigation-menu-via-app-routing.png
  98. BIN
      docs/zh-Hans/UI/Angular/images/navigation-menu-via-config-state.png
  99. 22
      docs/zh-Hans/UI/Common/Utils/Linked-List.md
  100. 12
      docs/zh-Hans/docs-nav.json

24
abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json

@ -13,10 +13,11 @@
"Permission:Edit": "Edit", "Permission:Edit": "Edit",
"Permission:Delete": "Delete", "Permission:Delete": "Delete",
"Permission:Create": "Create", "Permission:Create": "Create",
"Permission:Accounting": "Accounting", "Permission:Accounting": "Accounting",
"Permission:Accounting:Quotation": "Quotation", "Permission:Accounting:Quotation": "Quotation",
"Permission:Accounting:Invoice": "Invoice",
"Menu:Organizations": "Organizations", "Menu:Organizations": "Organizations",
"Menu:Accounting": "Accounting", "Menu:Accounting": "Accounting",
"Menu:Packages": "Packages", "Menu:Packages": "Packages",
"NpmPackageDeletionWarningMessage": "This NPM Package will be deleted. Do you confirm that?", "NpmPackageDeletionWarningMessage": "This NPM Package will be deleted. Do you confirm that?",
"NugetPackageDeletionWarningMessage": "This Nuget Package will be deleted. Do you confirm that?", "NugetPackageDeletionWarningMessage": "This Nuget Package will be deleted. Do you confirm that?",
@ -103,8 +104,21 @@
"Price": "Price", "Price": "Price",
"DiscountText": "Discount text", "DiscountText": "Discount text",
"DiscountQuantity": "Discount quantity", "DiscountQuantity": "Discount quantity",
"DiscountPrice": "Discount price", "DiscountPrice": "Discount price",
"Quotation": "Quotation", "Quotation": "Quotation",
"Generate": "Generate" "ExtraText": "Extra Text",
"ExtraAmount": "Extra Amount",
"DownloadQuotation": "Download Quotation",
"Invoice": "Invoice",
"TaxNumber": "Tax Number",
"InvoiceNumber": "Invoice Number",
"InvoiceDate": "Invoice Date",
"Quantity": "Quantity",
"AddProduct": "Add Product",
"AddProductWarning": "You need to add product!",
"TotalPrice": "Total Price",
"Generate": "Generate",
"MissingQuantityField": "The quantity field is required!",
"MissingPriceField": "The Price field is required!"
} }
} }

6
common.DotSettings

@ -20,5 +20,11 @@
<s:String x:Key="/Default/CodeStyle/Generate/=Overrides/Options/=Async/@EntryIndexedValue">False</s:String> <s:String x:Key="/Default/CodeStyle/Generate/=Overrides/Options/=Async/@EntryIndexedValue">False</s:String>
<s:String x:Key="/Default/CodeStyle/Generate/=Overrides/Options/=Mutable/@EntryIndexedValue">False</s:String> <s:String x:Key="/Default/CodeStyle/Generate/=Overrides/Options/=Mutable/@EntryIndexedValue">False</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SQL/@EntryIndexedValue">SQL</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=SQL/@EntryIndexedValue">SQL</s:String>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/HideTypeNameHintsWhenTypeNameIsEvidentFromVariableName/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/ShowMethodReturnTypeNameHints/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/ShowTypeNameHintsForImplicitlyTypedVariables/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/ShowTypeNameHintsForLambdaExpressionParameters/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/ShowTypeNameHintsForLinqQueryRangeVariables/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Environment/TypeNameHintsOptions/ShowTypeNameHintsForPatternMatchingExpressions/@EntryValue">False</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Volo/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Volo/@EntryIndexedValue">True</s:Boolean>
</wpf:ResourceDictionary> </wpf:ResourceDictionary>

8
docs/en/CLI.md

@ -135,6 +135,8 @@ abp update [options]
* `--include-previews` or `-p`: Includes preview, beta and rc packages while checking the latest versions. * `--include-previews` or `-p`: Includes preview, beta and rc packages while checking the latest versions.
* `--npm`: Only updates NPM packages. * `--npm`: Only updates NPM packages.
* `--nuget`: Only updates NuGet packages. * `--nuget`: Only updates NuGet packages.
* `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default
* `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default.
### switch-to-preview ### switch-to-preview
@ -170,7 +172,11 @@ Some features of the CLI requires to be logged in to abp.io platform. To login w
abp login <username> abp login <username>
``` ```
Notice that, a new login with an already active session, will kill the previous session and creates a new one. ```bash
abp login <username> -p <password>
```
Notice that, a new login with an already active session, overwrites the previous session.
### logout ### logout

4
docs/en/Getting-Started.md

@ -400,8 +400,8 @@ Enter **admin** as the username and **1q2w3E*** as the password to login to the
The application is up and running. You can continue to develop your application based on this startup template. The application is up and running. You can continue to develop your application based on this startup template.
> The [application startup template](startup-templates/application/index.md) includes the TenantManagement and Identity modules. > The [application startup template](Startup-Templates/Application.md) includes the TenantManagement and Identity modules.
## What's next? ## What's next?
[Application development tutorial](tutorials/book-store/part-1.md) [Application development tutorial](Tutorials/Part-1.md)

52
docs/en/Localization.md

@ -87,6 +87,21 @@ A JSON localization file content is shown below:
* Every localization file should define the `culture` code for the file (like "en" or "en-US"). * Every localization file should define the `culture` code for the file (like "en" or "en-US").
* `texts` section just contains key-value collection of the localization strings (keys may have spaces too). * `texts` section just contains key-value collection of the localization strings (keys may have spaces too).
### Default Resource
`AbpLocalizationOptions.DefaultResourceType` can be set to a resource type, so it is used when the localization resource was not specified:
````csharp
Configure<AbpLocalizationOptions>(options =>
{
options.DefaultResourceType = typeof(TestResource);
});
````
> The [application startup template](Startup-Templates/Application.md) sets `DefaultResourceType` to the localization resource of the application.
See the *Client Side* section below for a use case.
### Short Localization Resource Name ### Short Localization Resource Name
Localization resources are also available in the client (JavaScript) side. So, setting a short name for the localization resource makes it easy to use localization texts. Example: Localization resources are also available in the client (JavaScript) side. So, setting a short name for the localization resource makes it easy to use localization texts. Example:
@ -166,6 +181,10 @@ public class MyService
} }
```` ````
##### Format Arguments
Format arguments can be passed after the localization key. If your message is `Hello {0}, welcome!`, then you can pass the `{0}` argument to the localizer like `_localizer["HelloMessage", "John"]`
#### Simplest Usage In A Razor View/Page #### Simplest Usage In A Razor View/Page
````c# ````c#
@ -180,18 +199,47 @@ Refer to the [Microsoft's localization documentation](https://docs.microsoft.com
ABP provides JavaScript services to use the same localized texts in the client side. ABP provides JavaScript services to use the same localized texts in the client side.
Get a localization resource: #### getResource
`abp.localization.getResource` function is used to get a localization resource:
````js ````js
var testResource = abp.localization.getResource('Test'); var testResource = abp.localization.getResource('Test');
```` ````
Localize a string: Then you can localize a string based on this resource:
````js ````js
var str = testResource('HelloWorld'); var str = testResource('HelloWorld');
```` ````
#### localize
`abp.localization.localize` function is a shortcut where you can both specify the text name and the resource name:
````js
var str = abp.localization.localize('HelloWorld', 'Test');
````
`HelloWorld` is the text to localize, where `Test` is the localization resource name here.
If you don't specify the localization resource name, it uses the default localization resource defined on the `AbpLocalizationOptions` (see the *Default Resource* section above). Example:
````js
var str = abp.localization.localize('HelloWorld'); //uses the default resource
````
##### Format Arguments
If your localized string contains arguments, like `Hello {0}, welcome!`, you can pass arguments to the localization methods. Examples:
````js
var str1 = abp.localization.getResource('Test')('HelloWelcomeMessage', 'John');
var str2 = abp.localization.localize('HelloWorld', 'Test', 'John');
````
Both of the samples above produce the output `Hello John, welcome!`.
## See Also ## See Also
* [Localization in Angular UI](UI/Angular/Localization.md) * [Localization in Angular UI](UI/Angular/Localization.md)

8
docs/en/Object-Extensions.md

@ -197,8 +197,8 @@ ObjectExtensionManager.Instance
"SocialSecurityNumber", "SocialSecurityNumber",
options => options =>
{ {
options.ValidationAttributes.Add(new RequiredAttribute()); options.Attributes.Add(new RequiredAttribute());
options.ValidationAttributes.Add( options.Attributes.Add(
new StringLengthAttribute(32) { new StringLengthAttribute(32) {
MinimumLength = 6 MinimumLength = 6
} }
@ -248,12 +248,12 @@ ObjectExtensionManager.Instance
objConfig.AddOrUpdateProperty<string>("Password", propertyConfig => objConfig.AddOrUpdateProperty<string>("Password", propertyConfig =>
{ {
propertyConfig.ValidationAttributes.Add(new RequiredAttribute()); propertyConfig.Attributes.Add(new RequiredAttribute());
}); });
objConfig.AddOrUpdateProperty<string>("PasswordRepeat", propertyConfig => objConfig.AddOrUpdateProperty<string>("PasswordRepeat", propertyConfig =>
{ {
propertyConfig.ValidationAttributes.Add(new RequiredAttribute()); propertyConfig.Attributes.Add(new RequiredAttribute());
}); });
//Write a common validation logic works on multiple properties //Write a common validation logic works on multiple properties

6
docs/en/Tutorials/Part-1.md

@ -61,7 +61,7 @@ After creating the project, you need to apply the initial migrations and create
To run the project, right click to the {{if UI == "MVC"}} `Acme.BookStore.Web`{{end}} {{if UI == "NG"}} `Acme.BookStore.HttpApi.Host` {{end}} project and click **Set As StartUp Project**. And run the web project by pressing **CTRL+F5** (*without debugging and fast*) or press **F5** (*with debugging and slow*). {{if UI == "NG"}}You will see the Swagger UI for BookStore API.{{end}} To run the project, right click to the {{if UI == "MVC"}} `Acme.BookStore.Web`{{end}} {{if UI == "NG"}} `Acme.BookStore.HttpApi.Host` {{end}} project and click **Set As StartUp Project**. And run the web project by pressing **CTRL+F5** (*without debugging and fast*) or press **F5** (*with debugging and slow*). {{if UI == "NG"}}You will see the Swagger UI for BookStore API.{{end}}
Further information, see the [running the application section](../../Getting-Started-{{if UI == "NG"}}Angular{{else}}AspNetCore-MVC{{end}}-Template#running-the-application).Getting-Started-AspNetCore-MVC-Template#running-the-application Further information, see the [running the application section](../Getting-Started?UI={{UI}}#run-the-application).
![Set as startup project](./images/bookstore-start-project-{{UI_Text}}.png) ![Set as startup project](./images/bookstore-start-project-{{UI_Text}}.png)
@ -335,7 +335,7 @@ INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES
### Create the application service ### Create the application service
The next step is to create an [application service](../../Application-Services.md) to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects: The next step is to create an [application service](../Application-Services.md) to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects:
* `Acme.BookStore.Application.Contracts` mainly contains your `DTO`s and application service interfaces. * `Acme.BookStore.Application.Contracts` mainly contains your `DTO`s and application service interfaces.
* `Acme.BookStore.Application` contains the implementations of your application services. * `Acme.BookStore.Application` contains the implementations of your application services.
@ -874,8 +874,6 @@ We'll see **book-list works!** text on the books page:
Run the following command in the terminal to create a new state, named `BooksState`: Run the following command in the terminal to create a new state, named `BooksState`:
![Initial book list page](./images/bookstore-generate-state-books.png)
```bash ```bash
npx @ngxs/cli --name books --directory src/app/books npx @ngxs/cli --name books --directory src/app/books
``` ```

54
docs/en/Tutorials/Part-2.md

@ -525,7 +525,7 @@ export class BooksState {
* We imported `CreateUpdateBook` action and defined the `save` method that will listen to a `CreateUpdateBook` action to create a book. * We imported `CreateUpdateBook` action and defined the `save` method that will listen to a `CreateUpdateBook` action to create a book.
When the `SaveBook` action dispatched, the save method is being executed. It calls `create` method of the `BookService`. When the `SaveBook` action dispatched, the save method is being executed. It calls `createByInput` method of the `BookService`.
#### Add a modal to BookListComponent #### Add a modal to BookListComponent
@ -885,32 +885,6 @@ Now, you can open your browser to see the changes:
#### Saving the book #### Saving the book
Open `book-list.component.html` in `app\books\book-list` folder and add the following `abp-button` to save the new book.
```html
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
{%{{{ 'AbpAccount::Close' | abpLocalization }}}%}
</button>
<!--added save button-->
<button class="btn btn-primary" (click)="save()" [disabled]="form.invalid">
<i class="fa fa-check mr-1"></i>
{%{{{ 'AbpAccount::Save' | abpLocalization }}}%}
</button>
</ng-template>
```
Find the `<form [formGroup]="form">` tag and replace below content:
```html
<form [formGroup]="form" (ngSubmit)="save()"> <!-- added the ngSubmit -->
```
* We added the `(ngSubmit)="save()"` to `<form>` element to save a new book by pressing the enter.
* We added `abp-button` to the bottom area of the modal to save a new book.
Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below: Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below:
```js ```js
@ -993,6 +967,32 @@ export class BookListComponent implements OnInit {
* We imported `CreateUpdateBook`. * We imported `CreateUpdateBook`.
* We added `save` method * We added `save` method
Open `book-list.component.html` in `app\books\book-list` folder and add the following `abp-button` to save the new book.
```html
<ng-template #abpFooter>
<button type="button" class="btn btn-secondary" #abpClose>
{%{{{ 'AbpAccount::Close' | abpLocalization }}}%}
</button>
<!--added save button-->
<button class="btn btn-primary" (click)="save()" [disabled]="form.invalid">
<i class="fa fa-check mr-1"></i>
{%{{{ 'AbpAccount::Save' | abpLocalization }}}%}
</button>
</ng-template>
```
Find the `<form [formGroup]="form">` tag and replace below content:
```html
<form [formGroup]="form" (ngSubmit)="save()"> <!-- added the ngSubmit -->
```
* We added the `(ngSubmit)="save()"` to `<form>` element to save a new book by pressing the enter.
* We added `abp-button` to the bottom area of the modal to save a new book.
The final modal UI looks like below: The final modal UI looks like below:
![Save button to the modal](./images/bookstore-new-book-form-v2.png) ![Save button to the modal](./images/bookstore-new-book-form-v2.png)

4
docs/en/Tutorials/Part-3.md

@ -96,9 +96,9 @@ namespace Acme.BookStore
```` ````
* `IRepository<Book, Guid>` is injected and used it in the `SeedAsync` to create two book entities as the test data. * `IRepository<Book, Guid>` is injected and used it in the `SeedAsync` to create two book entities as the test data.
* `IGuidGenerator` is injected to create GUIDs. While `Guid.NewGuid()` would perfectly work for testing, `IGuidGenerator` has additional features especially important while using real databases. Further information, see the [Guid generation document](../Guid-Generation.md).
### Testing the application service BookAppService ### Testing the application service BookAppService
* `IGuidGenerator` is injected to create GUIDs. While `Guid.NewGuid()` would perfectly work for testing, `IGuidGenerator` has additional features especially important while using real databases. Further information, see the [Guid generation document](https://docs.abp.io/{{Document_Language_Code}}/abp/{{Document_Version}}/Guid-Generation).
Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project: Create a test class named `BookAppService_Tests` in the `Acme.BookStore.Application.Tests` project:

BIN
docs/en/Tutorials/images/bookstore-angular-file-tree.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

After

Width:  |  Height:  |  Size: 91 KiB

12
docs/en/UI/Angular/Config-State.md

@ -236,7 +236,7 @@ const newRoute: ABP.Route = {
path: "page", path: "page",
invisible: false, invisible: false,
order: 2, order: 2,
requiredPolicy: "MyProjectName::MyNewPage" requiredPolicy: "MyProjectName.MyNewPage"
}; };
this.config.dispatchAddRoute(newRoute); this.config.dispatchAddRoute(newRoute);
@ -248,23 +248,25 @@ The `newRoute` will be placed as at root level, i.e. without any parent routes a
If you want **to add a child route, you can do this:** If you want **to add a child route, you can do this:**
```js ```js
import { eIdentityRouteNames } from '@abp/ng.identity';
// this.config is instance of ConfigStateService // this.config is instance of ConfigStateService
const newRoute: ABP.Route = { const newRoute: ABP.Route = {
parentName: "AbpAccount::Login", parentName: eIdentityRouteNames.IdentityManagement,
name: "My New Page", name: "My New Page",
iconClass: "fa fa-dashboard", iconClass: "fa fa-dashboard",
path: "page", path: "page",
invisible: false, invisible: false,
order: 2, order: 2,
requiredPolicy: "MyProjectName::MyNewPage" requiredPolicy: "MyProjectName.MyNewPage"
}; };
this.config.dispatchAddRoute(newRoute); this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete // returns a state stream which emits after dispatch action is complete
``` ```
The `newRoute` will then be placed as a child of the parent route named `'AbpAccount::Login'` and its url will be set as `'/account/login/page'`. The `newRoute` will then be placed as a child of the parent route named `eIdentityRouteNames.IdentityManagement` and its url will be set as `'/identity/page'`.
#### Route Configuration Properties #### Route Configuration Properties
@ -291,4 +293,4 @@ Please refer to `Config.Environment` type for all the properties you can pass to
## What's Next? ## What's Next?
* [Component Replacement](./Component-Replacement.md) - [Modifying the Menu](./Modifying-the-Menu.md)

22
docs/en/UI/Angular/Confirmation-Service.md

@ -94,6 +94,28 @@ With the options above, the confirmation popup looks like this:
![confirmation](./images/confirmation.png) ![confirmation](./images/confirmation.png)
You are able to pass in an HTML string as title, message, or button texts. Here is an example:
```js
const options: Partial<Confirmation.Options> = {
yesText: '<i class="fa fa-trash mr-1"></i>Yes, delete it',
};
this.confirmation.warn(
`
<strong>Role Demo</strong> will be <strong>deleted</strong>
<br>
Do you confirm that?
`,
'<span class="my-custom-title">Are you sure?</span>',
options,
);
```
Since the values are HTML now, localization should be handled manually. Check out the [LocalizationService](./Localization#using-the-localization-service) to see how you can accomplish that.
> Please note that all strings will be sanitized by Angular and not every HTML string will work. Only values that are considered as "safe" by Angular will be displayed.
### How to Remove a Confirmation Popup ### How to Remove a Confirmation Popup
The open confirmation popup can be removed manually via the `clear` method: The open confirmation popup can be removed manually via the `clear` method:

199
docs/en/UI/Angular/Modifying-the-Menu.md

@ -0,0 +1,199 @@
# Modifying the Menu
The menu is inside the `ApplicationLayoutComponent` in the @abp/ng.theme.basic package. There are several methods for modifying the menu elements. This document covers these methods. If you would like to replace the menu completely, please refer to [Component Replacement documentation](./Component-Replacement.md) and learn how to replace a layout.
<!-- TODO: Replace layout replacement document with component replacement. Layout replacement document will be created.-->
## How to Add a Logo
The `logoUrl` property in the environment variables is the url of the logo.
You can add your logo to `src/assets` folder and set the `logoUrl` as shown below:
```js
export const environment = {
// other configurations
application: {
name: 'MyProjectName',
logoUrl: 'assets/logo.png',
},
// other configurations
};
```
## How to Add a Navigation Element
### Via `routes` Property in `AppRoutingModule`
You can define your routes by adding `routes` as a child property to `data` property of a route configuration in the `app-routing.module`. The `@abp/ng.core` package organizes your routes and stores them in the `ConfigState`. `ApplicationLayoutComponent` gets routes from store and displays them on the menu.
You can add the `routes` property like below:
```js
{
path: 'your-path',
data: {
routes: {
name: 'Your navigation',
order: 3,
iconClass: 'fas fa-question-circle',
requiredPolicy: 'permission key here',
children: [
{
path: 'child',
name: 'Your child navigation',
order: 1,
requiredPolicy: 'permission key here',
},
],
} as ABP.Route, // can be imported from @abp/ng.core
}
}
```
- `name` is the label of the navigation element. A localization key or a localization object can be passed.
- `order` is the order of the navigation element.
- `iconClass` is the class of the `i` tag, which is placed to the left of the navigation label.
- `requiredPolicy` is the permission key to access the page. See the [Permission Management document](./Permission-Management.md)
- `children` is an array and is used for declaring child navigation elements. The child navigation element will be placed as a child route which will be available at `'/your-path/child'` based on the given `path` property.
After adding the `routes` property as described above, the navigation menu looks like this:
![navigation-menu-via-app-routing](./images/navigation-menu-via-app-routing.png)
## Via ConfigState
The `dispatchAddRoute` method of `ConfigStateService` adds a new navigation element to the menu.
```js
// this.config is instance of ConfigStateService
const newRoute: ABP.Route = {
name: 'My New Page',
iconClass: 'fa fa-dashboard',
path: 'page',
invisible: false,
order: 2,
requiredPolicy: 'MyProjectName.MyNewPage',
} as Omit<ABP.Route, 'children'>;
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
The `newRoute` will be placed as at root level, i.e. without any parent routes, and its url will be stored as `'/path'`.
If you want **to add a child route, you can do this:**
```js
// this.config is instance of ConfigStateService
// eIdentityRouteNames enum can be imported from @abp/ng.identity
const newRoute: ABP.Route = {
parentName: eIdentityRouteNames.IdentityManagement,
name: 'My New Page',
iconClass: 'fa fa-dashboard',
path: 'page',
invisible: false,
order: 3,
requiredPolicy: 'MyProjectName.MyNewPage'
} as Omit<ABP.Route, 'children'>;
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
The `newRoute` will then be placed as a child of the parent route named `eIdentityRouteNames.IdentityManagement` and its url will be set as `'/identity/page'`.
The new route will be added like below:
![navigation-menu-via-config-state](./images/navigation-menu-via-config-state.png)
## How to Patch a Navigation Element
The `dispatchPatchRouteByName` method finds a route by its name and replaces its configuration in the store with the new configuration passed as the second parameter.
```js
// this.config is instance of ConfigStateService
// eIdentityRouteNames enum can be imported from @abp/ng.identity
const newRouteConfig: Partial<ABP.Route> = {
iconClass: 'fas fa-home',
parentName: eIdentityRouteNames.Administration,
order: 0,
children: [
{
name: 'Dashboard',
path: 'dashboard',
},
],
};
this.config.dispatchPatchRouteByName('::Menu:Home', newRouteConfig);
// returns a state stream which emits after dispatch action is complete
```
* Moved the _Home_ navigation under the _Administration_ dropdown based on given `parentName`.
* Added an icon.
* Specified the order.
* Added a child route named _Dashboard_.
After the patch above, navigation elements looks like below:
![navigation-menu-after-patching](./images/navigation-menu-after-patching.png)
## How to Add an Element to Right Part of the Menu
The right part elements are stored in the `LayoutState` that is in the @abp/ng.theme.basic package.
The `dispatchAddNavigationElement` method of the `LayoutStateService` adds an element to the right part of the menu.
You can insert an element by adding your template to `app.component` and calling the `dispatchAddNavigationElement` method:
```js
import { Layout, LayoutStateService } from '@abp/ng.theme.basic'; // added this line
@Component({
selector: 'app-root',
template: `
<!-- Added below content -->
<ng-template #search
><input type="search" placeholder="Search" class="bg-transparent border-0"
/></ng-template>
`,
})
export class AppComponent {
// Added ViewChild
@ViewChild('search', { static: false, read: TemplateRef }) searchElementRef: TemplateRef<any>;
constructor(private layout: LayoutStateService) {} // injected LayoutStateService
// Added ngAfterViewInit
ngAfterViewInit() {
const newElement = {
name: 'Search',
element: this.searchElementRef,
order: 1,
} as Layout.NavigationElement;
this.layout.dispatchAddNavigationElement(newElement);
}
}
```
This inserts a search input to the menu. The final UI looks like below:
![navigation-menu-search-input](./images/navigation-menu-search-input.png)
## How to Remove an Element From Right Part of the Menu
TODO
## What's Next
* [Component Replacement](./Component-Replacement.md)

BIN
docs/en/UI/Angular/images/navigation-menu-after-patching.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/en/UI/Angular/images/navigation-menu-search-input.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/en/UI/Angular/images/navigation-menu-via-app-routing.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/en/UI/Angular/images/navigation-menu-via-config-state.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

22
docs/en/UI/Common/Utils/Linked-List.md

@ -1270,7 +1270,7 @@ find(predicate: ListIteratorFn<T>): ListNode<T> | undefined
Finds the first node from the list that matches the given predicate: Finds the first node from the list that matches the given predicate:
```js ```js
list.addTailMany(['a', 'b', 'b', 'c']); list.addManyTail(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c" // "a" <-> "b" <-> "b" <-> "c"
@ -1294,7 +1294,7 @@ findIndex(predicate: ListIteratorFn<T>): number
Finds the position of the first node from the list that matches the given predicate: Finds the position of the first node from the list that matches the given predicate:
```js ```js
list.addTailMany(['a', 'b', 'b', 'c']); list.addManyTail(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c" // "a" <-> "b" <-> "b" <-> "c"
@ -1322,7 +1322,7 @@ get(position: number): ListNode<T> | undefined
Finds and returns the node with specific position in the list: Finds and returns the node with specific position in the list:
```js ```js
list.addTailMany(['a', 'b', 'c']); list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
@ -1346,7 +1346,7 @@ indexOf(value: T, compareFn?: ListComparisonFn<T>): number
Finds the position of the first node from the list that has the given value: Finds the position of the first node from the list that has the given value:
```js ```js
list.addTailMany(['a', 'b', 'b', 'c']); list.addManyTail(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c" // "a" <-> "b" <-> "b" <-> "c"
@ -1368,7 +1368,7 @@ i3 === -1
You may pass a custom compare function to detect the searched value: You may pass a custom compare function to detect the searched value:
```js ```js
list.addTailMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]); list.addManyTail([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]);
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3} // {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
@ -1408,7 +1408,7 @@ forEach(iteratorFn: ListIteratorFn<T>): void
Runs a function on all nodes in a linked list from head to tail: Runs a function on all nodes in a linked list from head to tail:
```js ```js
list.addTailMany(['a', 'b', 'c']); list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
@ -1425,7 +1425,7 @@ list.forEach((node, index) => console.log(node.value + index));
A linked list is iterable. In other words, you may use methods like `for...of` on it. A linked list is iterable. In other words, you may use methods like `for...of` on it.
```js ```js
list.addTailMany(['a', 'b', 'c']); list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
@ -1449,7 +1449,7 @@ toArray(): T[]
Converts a linked list to an array of values: Converts a linked list to an array of values:
```js ```js
list.addTailMany(['a', 'b', 'c']); list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
@ -1471,7 +1471,7 @@ toNodeArray(): ListNode<T>[]
Converts a linked list to an array of nodes: Converts a linked list to an array of nodes:
```js ```js
list.addTailMany(['a', 'b', 'c']); list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
@ -1495,7 +1495,7 @@ toString(mapperFn: ListMapperFn<T> = JSON.stringify): string
Converts a linked list to a string representation of nodes and their relations: Converts a linked list to a string representation of nodes and their relations:
```js ```js
list.addTailMany(['a', 2, 'c', { k: 4, v: 'd' }]); list.addManyTail(['a', 2, 'c', { k: 4, v: 'd' }]);
// "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"} // "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}
@ -1562,7 +1562,7 @@ export class ListNode<T = any> {
- `previous` refers to the previous node in the list. - `previous` refers to the previous node in the list.
```js ```js
list.addTailMany([ 0, 1, 2 ]); list.addManyTail([ 0, 1, 2 ]);
console.log( console.log(
list.head.value, // 0 list.head.value, // 0

6
docs/en/docs-nav.json

@ -352,6 +352,10 @@
"text": "Config State", "text": "Config State",
"path": "UI/Angular/Config-State.md" "path": "UI/Angular/Config-State.md"
}, },
{
"text": "Modifying the Menu",
"path": "UI/Angular/Modifying-the-Menu.md"
},
{ {
"text": "Component Replacement", "text": "Component Replacement",
"path": "UI/Angular/Component-Replacement.md" "path": "UI/Angular/Component-Replacement.md"
@ -398,7 +402,7 @@
"text": "Data Access", "text": "Data Access",
"items": [ "items": [
{ {
"text": "Overall", "text": "Overall",
"path": "Data-Access.md" "path": "Data-Access.md"
}, },
{ {

6
docs/zh-Hans/CLI.md

@ -135,6 +135,8 @@ abp update [options]
* `--include-previews``-p`: 将预览版, 测试版本 和 rc 包 同时更新到最新版本. * `--include-previews``-p`: 将预览版, 测试版本 和 rc 包 同时更新到最新版本.
* `--npm`: 仅更新NPM包 * `--npm`: 仅更新NPM包
* `--nuget`: 仅更新的NuGet包 * `--nuget`: 仅更新的NuGet包
* `--solution-path``-sp`: 指定解决方案路径/目录. 默认使用当前目录
* `--solution-name``-sn`: 指定解决方案名称. 默认在目录中搜索`*.sln`文件.
### 切换到每晚构建(预览)包 ### 切换到每晚构建(预览)包
@ -164,6 +166,10 @@ CLI的一些功能需要登录到abp.io平台. 使用你的用户名登录
abp login <username> abp login <username>
``` ```
```bash
abp login <username> -p <password>
```
请注意,新的登录将终止先前的会话并创建一个新的会话. 请注意,新的登录将终止先前的会话并创建一个新的会话.
### logout ### logout

9
docs/zh-Hans/Getting-Started-Angular-Template.md

@ -1,3 +1,8 @@
## Getting Started With the Angular Application Template # 启动模板入门
TODO... 请参阅以下教程,了解如何使用预构建的应用程序启动模板开始使用ABP框架:
* [ASP.NET Core MVC / Razor Pages UI 入门](Getting-Started?UI=MVC&DB=EF&Tiered=No)
* [Angular UI 入门](Getting-Started?UI=NG&DB=EF&Tiered=No)
<!-- TODO: this document has been moved, it should be deleted in the future. -->

2
docs/zh-Hans/Getting-Started-AspNetCore-Application.md

@ -2,7 +2,7 @@
本教程将介绍如何开始以最少的依赖关系开始使用ABP开发. 本教程将介绍如何开始以最少的依赖关系开始使用ABP开发.
通常情况下你需要下载一个 ***[启动模板](https://abp.io/Templates)*** 通常情况下你需要下载一个 ***[启动模板](Getting-Started-AspNetCore-MVC-Template.md)***
### 创建一个新项目 ### 创建一个新项目

104
docs/zh-Hans/Getting-Started-AspNetCore-MVC-Template.md

@ -1,102 +1,8 @@
## ASP.NET Core MVC 模板入门 # 启动模板入门
### 创建新项目 请参阅以下教程,了解如何使用预构建的应用程序启动模板开始使用ABP框架:
本教程使用 **ABP CLI** 创建一个新项目. 更多选项, 请参阅[入门](https://abp.io/get-started)页面. * [ASP.NET Core MVC / Razor Pages UI 入门](Getting-Started?UI=MVC&DB=EF&Tiered=No)
* [Angular UI 入门](Getting-Started?UI=NG&DB=EF&Tiered=No)
如果你之前未安装,请使用命令行安装ABP CLI: <!-- TODO: this document has been moved, it should be deleted in the future. -->
````bash
dotnet tool install -g Volo.Abp.Cli
````
在空文件夹中使用 `abp new` 命令来创建项目:
````bash
abp new Acme.BookStore
````
> 你可以使用不同级别的命名空间; 例如BookStore, Acme.BookStore或Acme.Retail.BookStore.
`new` 命令创建**分层MVC应用程序**, **Entity Framework Core**作为数据库提供程序. 但是,它还有其他选择. 有关所有可用选项,请参见[CLI文档](CLI.md)
#### 预先要求
创建项目的要求:
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/)
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
### 解决方案结构
在**Visual Studio**中打开解决方案:
![bookstore-visual-studio-solution](images/bookstore-visual-studio-solution-v3.png)
该解决方案具有分层结构(基于[Domain Driven Design](Domain-Driven-Design.md)), 并包含配置好的的单元&集成测试项目,可与**EF Core**和**SQLite**数据库内存一起使用.
> 请参阅[应用程序模板文档](Startup-Templates/Application.md)以详细了解解决方案结构.
### 数据库连接字符串
查看`.Web`项目下`appsettings.json`文件中的 **连接字符串**:
````json
{
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
}
````
解决方案使用 **Entity Framework Core****MS SQL Server**. EF Core支持[各种](https://docs.microsoft.com/zh-cn/ef/core/providers/)数据库提供程序,因此你可以根据实际需要使用其他DBMS. 如果需要,请更改连接字符串.
### 创建数据库并应用数据库迁移
你有两个选项来创建数据库.
#### 使用DbMigrator应用程序
该解决方案包含一个控制台应用程序(在此示例中名为`Acme.BookStore.DbMigrator`),可以创建数据库,应用迁移和初始化数据. 它对开发和生产环境都很有用.
> `.DbMigrator`项目有自己的`appsettings.json`. 因此,如果你更改了上面的连接字符串,则还应更改此字符串.
右键单击`.DbMigrator`项目并选择 **设置为启动项目**:
![set-as-startup-project](images/set-as-startup-project.png)
按F5(或Ctrl + F5)运行应用程序. 它将具有如下所示的输出:
![set-as-startup-project](images/db-migrator-app.png)
#### 使用EF Core Update-Database命令
Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂起的迁移. 右键单击`.Web`项目并选择**设置为启动项目**:
![set-as-startup-project](images/set-as-startup-project.png)
打开**包管理器控制台(Package Manager Console)**, 选择`.EntityFrameworkCore.DbMigrations`项目作为**默认项目**并运行`Update-Database`命令:
![pcm-update-database](images/pcm-update-database-v2.png)
这将基于配置的连接字符串创建新数据库.
> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序.
### 运行应用程序
你现在可以运行应用程序,它将会打开**home**页面:
![bookstore-homepage](images/bookstore-homepage.png)
单击 **登录** 按钮, 输入用户名`admin`, 密码`1q2w3E*`, 登录应用程序.
启动模板包括**身份管理**和**租户管理**模块. 登录后,将显示"管理"菜单, 你可以在其中管理**租户**,**角色**,**用户**和**权限**. 用户管理页面如下所示:
![bookstore-user-management](images/bookstore-user-management-v2.png)
### 下一步是什么?
* [应用程序开发教程](Tutorials/AspNetCore-Mvc/Part-I.md)

8
docs/zh-Hans/Getting-Started-With-Startup-Templates.md

@ -1,6 +1,8 @@
# 启动模板入门 # 启动模板入门
参阅下面的教程来学习如何开始使用的ABP框架预构建的应用程序启动模板: 请参阅以下教程,了解如何使用预构建的应用程序启动模板开始使用ABP框架:
* [ASP.NET Core MVC/Razor页面模板入门](Getting-Started-AspNetCore-MVC-Template.md) * [ASP.NET Core MVC / Razor Pages UI 入门](Getting-Started?UI=MVC&DB=EF&Tiered=No)
* [Angular UI模板入门](Getting-Started-Angular-Template.md) * [Angular UI 入门](Getting-Started?UI=NG&DB=EF&Tiered=No)
<!-- TODO: this document has been moved, it should be deleted in the future. -->

411
docs/zh-Hans/Getting-Started.md

@ -0,0 +1,411 @@
## 入门
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"DB": ["EF", "Mongo"],
"Tiered": ["Yes", "No"]
}
````
本教程介绍了如何创建一个新的{{if UI == "MVC"}} ASP.NET Core MVC web {{else if UI == "NG"}} Angular {{end}}. 配置并运行它.
## 设置你的开发环境
创建第一个项目之前,需要正确的设置你的开发环境.
### 预先要求
你需要安装以下工具:
* [Visual Studio 2019 (v16.4+)](https://visualstudio.microsoft.com/vs/) for Windows / [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
* [.NET Core 3.0+](https://www.microsoft.com/net/download/dotnet-core/)
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
> 你可以也使用其他支持.NET Core 和 ASP.NET Core的编辑器.
### 安装ABP CLI
[ABP CLI](./CLI.md)是一个命令行页面,用于为基于ABP的应用程序验证和自动化一些任务.
> ABP CLI是[ABP框架](https://abp.io/)一个免费开源的工具.
你需要使用以下命令安排ABP CLI:
````shell
dotnet tool install -g Volo.Abp.Cli
````
如果你已经安装,你可以使用以下命令更新到最新版本:
````shell
dotnet tool update -g Volo.Abp.Cli
````
## 创建新项目
> 本文假设你使用 **{{ UI_Value }}** 做为UI框架 **{{ DB_Value }}** 做为数据库提供程序,对于其它选项,你可以更改文档顶部的首选项.
### 使用ABP CLI创建一个新项目
使用ABP CLI的 `new` 命令创建新项目:
````shell
abp new Acme.BookStore -t app{{if UI == "NG"}} -u angular {{end}}{{if DB == "Mongo"}} -d mongodb{{end}}{{if Tiered == "Yes" && UI != "NG"}} --tiered {{else if Tiered == "Yes" && UI == "NG"}}--separate-identity-server{{end}}
````
* `-t` 参数指定 [启动模板](Startup-Templates/Application.md) 名称. `app` 是一个启动模板名称,包含了预安装并且配置好的[ABP模块](Modules/Index.md).
{{ if UI == "NG" }}
* `-u` 指定UI框架, 本例中是 `angular`.
{{ if Tiered == "Yes" }}
* `--separate-identity-server` 参数用于将Identity服务器应用程序与API主机应用程序分隔开. 如果未指定,你将只有一个端点.
{{ end }}
{{ end }}
{{ if DB == "Mongo" }}
* `-d` 指定数据库提供程序, 本例中是 `mongodb`.
{{ end }}
{{ if Tiered == "Yes" && UI != "NG" }}
* `--tiered` 参数用于创建n层解决方案,其中身份验证服务器层,UI层和API层在物理上是分离的.
{{ end }}
> 你可以使用不同级别的命令空间; 例如. BookStore, Acme.BookStore or Acme.Retail.BookStore.
## 解决方案结构
{{ if UI == "MVC" }}
创建项目后你会有以下解决方案目录和文件:
![](images/solution-files-mvc.png)
在Visual Studio中打开 `.sln` 文件时,将看到以下解决方案结构:
{{if DB == "Mongo"}}
![vs-default-app-solution-structure](images/vs-app-solution-structure-mongodb.png)
{{else}}
![vs-default-app-solution-structure](images/vs-app-solution-structure{{if Tiered == "Yes"}}-tiered{{end}}.png)
{{end}}
{{ else if UI == "NG" }}
在创建的解决方案中有三个文件夹:
![](images/solution-files-non-mvc.png)
* `angular` 文件夹包含Angular UI应用程序.
* `aspnet-core` 文件夹包含后端应用程序.
* `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 }}
> ###### 关于解决方案中的项目
>
> 根据你的**UI**,**数据库**和其他选项,你的解决方案的结构可能略有不同.
该解决方案具有分层结构(基于[Domain Driven Design](Domain-Driven-Design.md)), 并包含配置好的的单元&集成测试项目.
{{ if DB == "EF" }}
集成测试项目已配置为可与 **EF Core** & **SQLite 内存** database同时使用.
{{ else if DB == "Mongo" }}
集成测试项目已配置为每个测试创建的内存中的**MongoDB**数据库(使用的[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)库).
{{ end }}
> 请参阅[应用程序模板文档](Startup-Templates/Application.md)详细了解解决方案结构.
## 创建数据库
### 数据库连接字符串
检查 {{if UI == "MVC"}}{{if Tiered == "Yes"}}`.IdentityServer` 和 `.HttpApi.Host` 项目{{else}}`.Web` 项目{{end}}{{else if UI == "NG" }}`.HttpApi.Host` 项目{{end}}下 `appsettings.json` 文件中的 **链接字符串**:
{{ if DB == "EF" }}
````json
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
````
该解决方案配置为**Entity Framework Core**与**MS SQL Server**一起使用. EF Core支持[各种](https://docs.microsoft.com/en-us/ef/core/providers/)数据库提供程序,因此你可以使用任何受支持的DBMS. 请参阅[Entity Framework集成文档](https://docs.abp.io/en/abp/latest/Entity-Framework-Core)了解如何切换到另一个DBMS.
### 数据库连接字符串
查看`.Web`项目下`appsettings.json`文件中的 **连接字符串**:
````json
{
"ConnectionStrings": {
"Default": "Server=localhost;Database=BookStore;Trusted_Connection=True"
}
}
````
解决方案使用 **Entity Framework Core****MS SQL Server**. EF Core支持[各种](https://docs.microsoft.com/zh-cn/ef/core/providers/)数据库提供程序,因此你可以根据实际需要使用其他DBMS. 如果需要,请更改连接字符串.
### 应用迁移
该解决方案使用[Entity Framework Core Code First 迁移](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli). 你需要应用迁移来创建数据库,有两种方法迁移数据库.
#### 使用DbMigrator应用程序应用迁移
该解决方案包含一个控制台应用程序(在此示例中名为`Acme.BookStore.DbMigrator`),可以创建数据库,应用迁移和初始化数据. 它对开发和生产环境都很有用.
> `.DbMigrator`项目有自己的`appsettings.json`. 因此,如果你更改了上面的连接字符串,则还应更改此字符串.
右键单击`.DbMigrator`项目并选择 **设置为启动项目**:
![set-as-startup-project](images/set-as-startup-project.png)
按F5(或Ctrl + F5)运行应用程序. 它将具有如下所示的输出:
![set-as-startup-project](images/db-migrator-app.png)
#### 使用EF Core Update-Database命令
Ef Core具有`Update-Database`命令, 可根据需要创建数据库并应用挂起的迁移. 右键单击`.Web`项目并选择**设置为启动项目**:
{{ if UI == "MVC" }}
右键单击{{if Tiered == "Yes"}}`.IdentityServer`{{else}}`.Web`{{end}}项目并选择**设置为启动项目**:
{{ else if UI != "MVC" }}
右键单击`.HttpApi.Host`项目并选择**设置为启动项目**:
{{ end }}
![set-as-startup-project](images/set-as-startup-project.png)
打开**包管理器控制台(Package Manager Console)**, 选择`.EntityFrameworkCore.DbMigrations`项目作为**默认项目**并运行`Update-Database`命令:
![package-manager-console-update-database](images/package-manager-console-update-database.png)
这将基于配置的连接字符串创建新数据库.
> 使用`.Migrator`工具是建议的方法, 因为它还能初始化初始数据能够正确运行Web应用程序.
{{ else if DB == "Mongo" }}
````json
"ConnectionStrings": {
"Default": "mongodb://localhost:27017/BookStore"
}
````
该解决方案被配置为在你的本地计算机中使用 **MongoDB**,因此你需要启动并运行一个MongoDB服务器实例或者将连接字符串更改为另一个MongoDB服务器.
### 初始化种子数据
该解决方案附带一个 `.DbMigrator` 控制台应用程序,该应用程序为初始数据提供了种子. 它对于开发以及生产环境都很有用.
> `.DbMigrator` 项目有自己的 `appsettings.json`.如果你更改了其他项目的 `appsettings.json`,也应该更改这个.
右键点击 `.DbMigrator` 并选择 **设置为启动项目**.
![set-as-startup-project](images/set-as-startup-project.png)
按F5(或Ctrl+F5)启动应用程序,你会看到以下输出:
![db-migrator-output](images/db-migrator-output.png)
> 数据库创建后会初始化种子数据, 其中包含用于登录的 `admin` 用户. 所以你至少使用 `.DbMigrator` 一次.
{{ end }}
### 运行应用程序
{{ if UI == "MVC" }}
{{ if Tiered == "Yes" }}
确保 `.IdentityServer` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面.
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间.
你可以登录,但是不能在这里进入主应用程序,它仅是验证服务器.
确保 `.HttpApi.Host` 是启动项目,运行应用程序后会在你的浏览器打开一个 **Swagger UI** 页面.
![swagger-ui](images/swagger-ui.png)
这里是Web应用程序使用的API应用程序.
最后确保 `.Web` 是启动项目,运行应用程序后会在你的浏览器打开一个 **welcome** 页面.
![mvc-tiered-app-home](images/bookstore-home.png)
点击 **login** 按钮重定向到 `Identity Server` 来登录应用程序.
![bookstore-login](images/bookstore-login.png)
{{ else }}
最后确保 `.Web` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面.
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间.
![bookstore-login](images/bookstore-login.png)
{{ end }}
{{ else if UI != "MVC" }}
#### 运行HTTP API Host (服务器端)
{{ if Tiered == "Yes" }}
确保 `.IdentityServer` 是启动项目,运行应用程序后会在你的浏览器打开一个 **login** 页面.
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间.
你可以登录,但是不能在这里进入主应用程序,它仅是验证服务器.
{{ end }}
确保 `.HttpApi.Host` 是启动项目,运行应用程序后会在你的浏览器打开一个 **Swagger UI** 页面.
{{ if Tiered == "No" }}
> 在Visual Studio中使用Ctrl+F5(而不是F5)运行应用,如果你不用于调试,这会减少启动时间.
{{ end }}
![swagger-ui](images/swagger-ui.png)
你可以看到应用程序的API并进行测试. 更多信息,请参阅[Swagger UI](https://swagger.io/tools/swagger-ui/).
> ##### Swagger UI 授权
>
> 大多数的HTTP API都需要身份验证和授权. 如果你要测试授权API, 请手动进入 `/Account/Login` 页面, 输入用户名: `admin` 和密码: `1q2w3E*` 登录到应用程序. 然后你可以访问授权API.
{{ end }}
{{ if UI == "NG" }}
#### 运行 Angular 应用程序 (客户端)
`angular` 下打开命令行终端, 输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作).
```bash
yarn
```
等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令:
```bash
yarn start
```
等待 `Angular CLI` 使用 `BrowserSync` 启动 `Webpack` dev-server.
它会负责编译你的 `TypeScript`代码, 并自动重新加载浏览器.
完成后 `Angular Live Development Server` 会监听 localhost:4200.
打开你的浏览器并导航到[localhost:4200](http://localhost:4200/).
![bookstore-login](images/bookstore-login.png)
{{ end }}
输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序.
![bookstore-home](images/bookstore-home.png)
应用程序已经启动并执行,你可以基于该启动模板开发应用程序.
#### 移动开发
ABP平台提供了[React Native](https://reactnative.dev/)模板用于开发移动应用程序.
> 该解决方案默认 `react-native` 包含了React Native应用程序,如果你不计划使用React Native开发移动应用程序,你可以忽略并删除 `react-native` 文件夹.
运行在Android模拟器或真机上的React Native应用程序无法连接到 `localhost` 上的后.要修复此问题,需要在本地IP上运行后端.
{{ if Tiered == "No"}}
![React Native host project local IP entry](images/rn-host-local-ip.png)
* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl``Authority` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址.
{{ else if Tiered == "Yes" }}
![React Native tiered project local IP entry](images/rn-tiered-local-ip.png)
* 打开 `.IdentityServer` 文件夹下的 `appsettings.json` 文件. 将 `SelfUrl` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.IdentityServer/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.HttpApi.Host` 文件夹下的 `appsettings.json` 文件. 将 `Authority` 属性的 `localhost` 替换为你本地的IP地址.
* 打开 `.HttpApi.Host/Properties` 文件夹下的 `launchSettings.json` 文件. 将 `applicationUrl` 属性的 `localhost` 替换为你本地的IP地址.
{{ end }}
按照**运行HTTP API Host (服务端口)**那样运行后端.
> React Native应用程序不信任自动生成的.NET HTTPS证书,你可以在开发期间使用HTTP.
`react-native` 文件夹打开命令行终端,输入 `yarn` 命令(我们推荐使用[yarn](https://yarnpkg.com/)包管理, `npm install` 在大多数情况下也可以工作).
```bash
yarn
```
* 打开 `react-nativer` 文件夹下的 `Environment.js` 文件. 将 `apiUrl``issuer` 属性的 `localhost` 替换为你本地的IP地址:
![react native environment local IP](images/rn-environment-local-ip.png)
{{ if Tiered == "Yes" }}
> 确保 `issuer` 与正在运行的 `.IdentityServer` 项目匹配, `apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配.
{{else}}
> 确保 `issuer``apiUrl` 与正在运行的 `.HttpApi.Host` 项目匹配
{{ end }}
等到所有node模块加载成功, 执行 `yarn start` (或 `npm start`) 命令:
```bash
yarn start
```
等待Expo CLI启动后Expo CLI在 `http://localhost:19002/` 地址要开管理页面.
![expo-interface](images/rn-expo-interface.png)
在上面的管理界面中,可以通过使用[Expo Client](https://expo.io/tools#client)扫描二维码,使用Android模拟器,iOS模拟器或真机来启动应用程序.
> 请参阅[expo.io](https://docs.expo.io/versions/v36.0.0/workflow/ios-simulator/)上的[Android Studio模拟器](https://docs.expo.io/versions/v36.0.0/workflow/android-studio-emulator/)和[iOS模拟器文档](https://docs.expo.io/versions/v36.0.0/workflow/android-studio-emulator/).
![React Native login screen on iPhone 11](images/rn-login-iphone.png)
输入用户名 **admin**,密码 **1q2w3E*** 登录到应用程序.
应用程序已经启动并执行,你可以基于该启动模板开发应用程序.
> [应用程序启动模板](Startup-Templates/Application.md) 包含租户管理和Identity模块.
## 下一步是什么?
[应用程序开发教程](Tutorials/Part-1.md)

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

BIN
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

1078
docs/zh-Hans/Tutorials/Part-1.md

File diff suppressed because it is too large

1349
docs/zh-Hans/Tutorials/Part-2.md

File diff suppressed because it is too large

198
docs/zh-Hans/Tutorials/Part-3.md

@ -0,0 +1,198 @@
## ASP.NET Core {{UI_Value}} 教程 - 第三章
````json
//[doc-params]
{
"UI": ["MVC","NG"]
}
````
{{
if UI == "MVC"
DB="ef"
DB_Text="Entity Framework Core"
UI_Text="mvc"
else if UI == "NG"
DB="mongodb"
DB_Text="MongoDB"
UI_Text="angular"
else
DB ="?"
UI_Text="?"
end
}}
### 关于本教程
这是ASP.NET Core{{UI_Value}}系列教程的第二章. 共有三章:
- [Part-1: 创建项目和书籍列表页面](Part-1.md)
- [Part 2: 创建,编辑,删除书籍](Part-2.md)
- **Part-3: 集成测试(本章)**
> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application).
### 解决方案中的测试项目
解决方案中有多个测试项目:
![bookstore-test-projects-v2](./images/bookstore-test-projects-{{UI_Text}}.png)
每个项目用于测试相关的应用程序项目.测试项目使用以下库进行测试:
* [xunit](https://xunit.github.io/) 作为主测试框架.
* [Shoudly](http://shouldly.readthedocs.io/en/latest/) 作为断言库.
* [NSubstitute](http://nsubstitute.github.io/) 作为模拟库.
### 添加测试数据
启动模板包含`Acme.BookStore.TestBase`项目中的`BookStoreTestDataSeedContributor`类,它创建一些数据来运行测试.
更改`BookStoreTestDataSeedContributor`类如下所示:
````csharp
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<Book, Guid> _bookRepository;
private readonly IGuidGenerator _guidGenerator;
public BookStoreTestDataSeedContributor(
IRepository<Book, Guid> bookRepository,
IGuidGenerator guidGenerator)
{
_bookRepository = bookRepository;
_guidGenerator = guidGenerator;
}
public async Task SeedAsync(DataSeedContext context)
{
await _bookRepository.InsertAsync(
new Book(id: _guidGenerator.Create(),
name: "Test book 1",
type: BookType.Fantastic,
publishDate: new DateTime(2015, 05, 24),
price: 21
)
);
await _bookRepository.InsertAsync(
new Book(id: _guidGenerator.Create(),
name: "Test book 2",
type: BookType.Science,
publishDate: new DateTime(2014, 02, 11),
price: 15
)
);
}
}
}
````
* 注入`IRepository<Book,Guid>`并在`SeedAsync`中使用它来创建两个书实体作为测试数据.
* 使用`IGuidGenerator`服务创建GUID. 虽然`Guid.NewGuid()`非常适合测试,但`IGuidGenerator`在使用真实数据库时还有其他特别重要的功能(参见[Guid生成文档](../Guid-Generation.md)了解更多信息).
### 测试 BookAppService
`Acme.BookStore.Application.Tests` 项目中创建一个名叫 `BookAppService_Tests` 的测试类:
````csharp
using System;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Shouldly;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Validation;
using Microsoft.EntityFrameworkCore.Internal;
namespace Acme.BookStore
{
public class BookAppService_Tests : BookStoreApplicationTestBase
{
private readonly IBookAppService _bookAppService;
public BookAppService_Tests()
{
_bookAppService = GetRequiredService<IBookAppService>();
}
[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实体失败的场景:
````csharp
[Fact]
public async Task Should_Not_Create_A_Book_Without_Name()
{
var exception = await Assert.ThrowsAsync<Volo.Abp.Validation.AbpValidationException>(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)
恭喜, 绿色图标表示测试已成功通过!

BIN
docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-create-dialog-v2.png → docs/zh-Hans/Tutorials/images/bookstore-add-create-dialog-v2.png

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-edit-dialog.png → docs/zh-Hans/Tutorials/images/bookstore-add-edit-dialog.png

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-index-page-v2.png → docs/zh-Hans/Tutorials/images/bookstore-add-index-page-v2.png

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-appservice-tests.png → docs/zh-Hans/Tutorials/images/bookstore-appservice-tests.png

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-book-list-2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-book-list.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table-actions.png → docs/zh-Hans/Tutorials/images/bookstore-books-table-actions.png

Before

Width:  |  Height:  |  Size: 9.1 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table.png → docs/zh-Hans/Tutorials/images/bookstore-books-table.png

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog-2.png → docs/zh-Hans/Tutorials/images/bookstore-create-dialog-2.png

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog.png → docs/zh-Hans/Tutorials/images/bookstore-create-dialog.png

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-create-project-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-create-project-mvc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-creating-books-module-terminal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-database-tables-ef.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-database-tables-mongodb.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-edit-button.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-final-actions-dropdown.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-generate-state-books.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-homepage.png → docs/zh-Hans/Tutorials/images/bookstore-homepage.png

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-index-js-file-v2.png → docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v2.png

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-initial-book-list-page.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-initial-books-page-with-layout.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-localization-files-v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-menu-items.png → docs/zh-Hans/Tutorials/images/bookstore-menu-items.png

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-mvc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-new-book-button.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-new-book-form-v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-new-book-form.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-new-menu-item.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-open-package-manager-console.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration-v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration.png → docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration.png

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-service-terminal-output.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-solution-structure-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-visual-studio-solution-v3.png → docs/zh-Hans/Tutorials/images/bookstore-solution-structure-mvc.png

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-start-project-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-start-project-mvc.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-swagger-book-dto-properties.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-swagger.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist-network.png → docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist-network.png

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist.png → docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist.png

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-test-projects-angular.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects-v2.png → docs/zh-Hans/Tutorials/images/bookstore-test-projects-mvc.png

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-test-projects-v2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-update-database-after-book-entity.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

0
docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-user-management.png → docs/zh-Hans/Tutorials/images/bookstore-user-management.png

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

BIN
docs/zh-Hans/Tutorials/images/bookstore-visual-studio-solution-v3.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
docs/zh-Hans/Tutorials/images/generate-proxy-command.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
docs/zh-Hans/Tutorials/images/generated-proxies.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

BIN
docs/zh-Hans/Tutorials/images/mozilla-self-signed-cert-error.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

9
docs/zh-Hans/UI/Angular/Config-State.md

@ -236,7 +236,7 @@ const newRoute: ABP.Route = {
path: "page", path: "page",
invisible: false, invisible: false,
order: 2, order: 2,
requiredPolicy: "MyProjectName::MyNewPage" requiredPolicy: "MyProjectName.MyNewPage"
}; };
this.config.dispatchAddRoute(newRoute); this.config.dispatchAddRoute(newRoute);
@ -248,16 +248,17 @@ this.config.dispatchAddRoute(newRoute);
如果你想要**添加一个子路由,你可以这样做:** 如果你想要**添加一个子路由,你可以这样做:**
```js ```js
import { eIdentityRouteNames } from '@abp/ng.identity';
// this.config is instance of ConfigStateService // this.config is instance of ConfigStateService
const newRoute: ABP.Route = { const newRoute: ABP.Route = {
parentName: "AbpAccount::Login", parentName: eIdentityRouteNames.IdentityManagement,
name: "My New Page", name: "My New Page",
iconClass: "fa fa-dashboard", iconClass: "fa fa-dashboard",
path: "page", path: "page",
invisible: false, invisible: false,
order: 2, order: 2,
requiredPolicy: "MyProjectName::MyNewPage" requiredPolicy: "MyProjectName.MyNewPage"
}; };
this.config.dispatchAddRoute(newRoute); this.config.dispatchAddRoute(newRoute);
@ -291,4 +292,4 @@ this.config.dispatchSetEnvironment({
## 下一步是什么? ## 下一步是什么?
* [组件替换](./Component-Replacement.md) - [修改菜单](./Modifying-the-Menu.md)

24
docs/zh-Hans/UI/Angular/Confirmation-Service.md

@ -86,10 +86,32 @@ this.confirmation.warn(
- `messageLocalizationParams`是用于消息本地化的插值参数. - `messageLocalizationParams`是用于消息本地化的插值参数.
- `titleLocalizationParams` 是标题本地化的插值参数. - `titleLocalizationParams` 是标题本地化的插值参数.
With the options above, the confirmation popup looks like this: 使用以上选项确认弹层窗口如下所示:
![confirmation](./images/confirmation.png) ![confirmation](./images/confirmation.png)
你可以传递HTML字符串作为标题,消息或按钮文本. 例如:
```js
const options: Partial<Confirmation.Options> = {
yesText: '<i class="fa fa-trash mr-1"></i>Yes, delete it',
};
this.confirmation.warn(
`
<strong>Role Demo</strong> will be <strong>deleted</strong>
<br>
Do you confirm that?
`,
'<span class="my-custom-title">Are you sure?</span>',
options,
);
```
由于这些值现在是HTML,因此应该手动处理本地化. 参阅[LocalizationService](./Localization#using-the-localization-service)了解如何实现.
> 注意,Angular会清除所有字符串,并且并非每个HTML字符串都可以使用. 仅显示被Angular视为"安全"的值.
### 如何删除一个确认弹层 ### 如何删除一个确认弹层
打开的确认弹出窗口可以通过 `clear` 方法手动删除: 打开的确认弹出窗口可以通过 `clear` 方法手动删除:

197
docs/zh-Hans/UI/Angular/Modifying-the-Menu.md

@ -0,0 +1,197 @@
# 修改菜单
菜单在 @abp/ng.theme.basic包 `ApplicationLayoutComponent` 内部. 有几种修改菜单的方法,本文档介绍了这些方法. 如果你想完全替换菜单,请参考[组件替换文档]了解如何替换布局.
<!-- TODO: Replace layout replacement document with component replacement. Layout replacement document will be created.-->
## 如何添加Logo
环境变量中的 `logoUrl` 是logo的url.
你可以在 `src/assets` 文件夹下添加logo并设置 `logoUrl`:
```js
export const environment = {
// other configurations
application: {
name: 'MyProjectName',
logoUrl: 'assets/logo.png',
},
// other configurations
};
```
## 如何添加导航元素
### 通过 AppRoutingModule 中的 `routes` 属性
你可以通过在 `app-routing.module` 中将路由作为子属性添加到路由配置的 `data` 属性来定义路由. `@abp/ng.core` 包组织路由并将其存储在 `ConfigState` 中.`ApplicationLayoutComponent` 从存储中获取路由显示在菜单上.
你可以像以下一样添加 `routes` 属性:
```js
{
path: 'your-path',
data: {
routes: {
name: 'Your navigation',
order: 3,
iconClass: 'fas fa-question-circle',
requiredPolicy: 'permission key here',
children: [
{
path: 'child',
name: 'Your child navigation',
order: 1,
requiredPolicy: 'permission key here',
},
],
} as ABP.Route, // can be imported from @abp/ng.core
}
}
```
- `name` 是导航元素的标签,可以传递本地化密钥或本地化对象.
- `order` 排序导航元素.
- `iconClass``i` 标签的类,在导航标签的左侧.
- `requiredPolicy` 是访问页面所需的权限key. 参阅 [权限管理文档](./Permission-Management.md)
- `children` is an array and is used for declaring child navigation elements. The child navigation element will be placed as a child route which will be available at `'/your-path/child'` based on the given `path` property.
- `children` 是一个数组,用于声明子菜单,它基于给定的 `path` 属性,路径是在`/your-path/child`.
添加了上面描述的route属性后,导航菜单如下图所示:
![navigation-menu-via-app-routing](./images/navigation-menu-via-app-routing.png)
## 通过 ConfigState
`ConfigStateService``dispatchAddRoute` 方法可以向菜单添加一个新的导航元素.
```js
// this.config is instance of ConfigStateService
const newRoute: ABP.Route = {
name: 'My New Page',
iconClass: 'fa fa-dashboard',
path: 'page',
invisible: false,
order: 2,
requiredPolicy: 'MyProjectName.MyNewPage',
} as Omit<ABP.Route, 'children'>;
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
`newRoute` 放在根级别,没有任何父路由,url将为`/path`.
如果你想 **添加子路由, 你可以这样做:**
```js
// this.config is instance of ConfigStateService
// eIdentityRouteNames enum can be imported from @abp/ng.identity
const newRoute: ABP.Route = {
parentName: eIdentityRouteNames.IdentityManagement,
name: 'My New Page',
iconClass: 'fa fa-dashboard',
path: 'page',
invisible: false,
order: 3,
requiredPolicy: 'MyProjectName.MyNewPage'
} as Omit<ABP.Route, 'children'>;
this.config.dispatchAddRoute(newRoute);
// returns a state stream which emits after dispatch action is complete
```
`newRoute` 做为 `eIdentityRouteNames.IdentityManagement` 的子路由添加, url 设置为 `'/identity/page'`.
新路由看起来像这样:
![navigation-menu-via-config-state](./images/navigation-menu-via-config-state.png)
## 如何修改一个导航元素
`DispatchPatchRouteByName` 方法通过名称查找路由,并使用二个参数传递的新配置替换存储中的配置.
```js
// this.config is instance of ConfigStateService
// eIdentityRouteNames enum can be imported from @abp/ng.identity
const newRouteConfig: Partial<ABP.Route> = {
iconClass: 'fas fa-home',
parentName: eIdentityRouteNames.Administration,
order: 0,
children: [
{
name: 'Dashboard',
path: 'dashboard',
},
],
};
this.config.dispatchPatchRouteByName('::Menu:Home', newRouteConfig);
// returns a state stream which emits after dispatch action is complete
```
* 根据给定的 `parentName`_Home_ 导航移动到 _Administration_ 下拉框下.
* 添加了 icon.
* 指定了顺序.
* 添加了名为 _Dashboard_ 的子路由.
修改后,导航元素看起来像这样:
![navigation-menu-after-patching](./images/navigation-menu-after-patching.png)
## 如何在菜单的右侧添加元素
右侧的元素存储在 @abp/ng.theme.basic 包的 `LayoutState` 中.
`LayoutStateService``dispatchAddNavigationElement` 方法添加元素到右侧的菜单.
你可以通过将模板添加到 `app.component` 调用 `dispatchAddNavigationElement` 方法来插入元素:
```js
import { Layout, LayoutStateService } from '@abp/ng.theme.basic'; // added this line
@Component({
selector: 'app-root',
template: `
<!-- Added below content -->
<ng-template #search
><input type="search" placeholder="Search" class="bg-transparent border-0"
/></ng-template>
`,
})
export class AppComponent {
// Added ViewChild
@ViewChild('search', { static: false, read: TemplateRef }) searchElementRef: TemplateRef<any>;
constructor(private layout: LayoutStateService) {} // injected LayoutStateService
// Added ngAfterViewInit
ngAfterViewInit() {
const newElement = {
name: 'Search',
element: this.searchElementRef,
order: 1,
} as Layout.NavigationElement;
this.layout.dispatchAddNavigationElement(newElement);
}
}
```
上面我们在菜单添加了一个搜索输入,最终UI如下:s
![navigation-menu-search-input](./images/navigation-menu-search-input.png)
## 如何删除右侧菜单元素
TODO
## 下一步是什么?
* [组件替换](./Component-Replacement.md)

BIN
docs/zh-Hans/UI/Angular/images/navigation-menu-after-patching.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
docs/zh-Hans/UI/Angular/images/navigation-menu-search-input.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
docs/zh-Hans/UI/Angular/images/navigation-menu-via-app-routing.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/zh-Hans/UI/Angular/images/navigation-menu-via-config-state.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

22
docs/zh-Hans/UI/Common/Utils/Linked-List.md

@ -1229,7 +1229,7 @@ find(predicate: ListIteratorFunction<T>): ListNode<T> | undefined
从链表中找到与给定谓词匹配的第一个节点: 从链表中找到与给定谓词匹配的第一个节点:
```js ```js
list.addTailMany(['a', 'b', 'b', 'c']); list.addManyTail(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c" // "a" <-> "b" <-> "b" <-> "c"
@ -1251,7 +1251,7 @@ findIndex(predicate: ListIteratorFunction<T>): number
从链表中找到与给定谓词匹配的第一个节点的位置: 从链表中找到与给定谓词匹配的第一个节点的位置:
```js ```js
list.addTailMany(['a', 'b', 'b', 'c']); list.addManyTail(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c" // "a" <-> "b" <-> "b" <-> "c"
@ -1279,7 +1279,7 @@ get(position: number): ListNode<T> | undefined
查找并返回链表中特定位置的节点: 查找并返回链表中特定位置的节点:
```js ```js
list.addTailMany(['a', 'b', 'c']); list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
@ -1303,7 +1303,7 @@ indexOf(value: T, compareFn?: ListComparisonFn<T>): number
在链表中找到匹配给定值的第一个节点位置: 在链表中找到匹配给定值的第一个节点位置:
```js ```js
list.addTailMany(['a', 'b', 'b', 'c']); list.addManyTail(['a', 'b', 'b', 'c']);
// "a" <-> "b" <-> "b" <-> "c" // "a" <-> "b" <-> "b" <-> "c"
@ -1325,7 +1325,7 @@ i3 === -1
你可以自定义比较器 你可以自定义比较器
```js ```js
list.addTailMany([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]); list.addManyTail([{ x: 1 }, { x: 0 }, { x: 2 }, { x: 0 }, { x: 3 }]);
// {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3} // {"x":1} <-> {"x":0} <-> {"x":2} <-> {"x":0} <-> {"x":3}
@ -1365,7 +1365,7 @@ forEach(iteratorFn: ListIteratorFn<T>): void
从头到尾在链表中的所有节点上运行回调函数: 从头到尾在链表中的所有节点上运行回调函数:
```js ```js
list.addTailMany(['a', 'b', 'c']); list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
@ -1381,7 +1381,7 @@ list.forEach((node, index) => console.log(node.value + index));
链表是可迭代的. 换句话说你可以使用诸如`for ... of`之类的方法. 链表是可迭代的. 换句话说你可以使用诸如`for ... of`之类的方法.
```js ```js
list.addTailMany(['a', 'b', 'c']); list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
@ -1405,7 +1405,7 @@ toArray(): T[]
转换链表值为数组: 转换链表值为数组:
```js ```js
list.addTailMany(['a', 'b', 'c']); list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
@ -1427,7 +1427,7 @@ toNodeArray(): T[]
转换链表节点为数组: 转换链表节点为数组:
```js ```js
list.addTailMany(['a', 'b', 'c']); list.addManyTail(['a', 'b', 'c']);
// "a" <-> "b" <-> "c" // "a" <-> "b" <-> "c"
@ -1449,7 +1449,7 @@ toString(mapperFn: ListMapperFn<T> = JSON.stringify): string
将链表转换为节点及其关系的字符串表示形式: 将链表转换为节点及其关系的字符串表示形式:
```js ```js
list.addTailMany(['a', 2, 'c', { k: 4, v: 'd' }]); list.addManyTail(['a', 2, 'c', { k: 4, v: 'd' }]);
// "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"} // "a" <-> 2 <-> "c" <-> {"k":4,"v":"d"}
@ -1507,7 +1507,7 @@ export class ListNode<T = any> {
- `previous`引用列表中的上一个节点. - `previous`引用列表中的上一个节点.
```js ```js
list.addTailMany([ 0, 1, 2 ]); list.addManyTail([ 0, 1, 2 ]);
console.log( console.log(
list.head.value, // 0 list.head.value, // 0

12
docs/zh-Hans/docs-nav.json

@ -5,13 +5,7 @@
"items": [ "items": [
{ {
"text": "从启动模板开始", "text": "从启动模板开始",
"path": "Getting-Started-With-Startup-Templates.md", "path": "Getting-Started.md"
"items": [
{
"text": "ASP.NET Core MVC 模板",
"path": "Getting-Started-AspNetCore-MVC-Template.md"
}
]
}, },
{ {
"text": "从空项目开始", "text": "从空项目开始",
@ -339,6 +333,10 @@
"text": "配置状态", "text": "配置状态",
"path": "UI/Angular/Config-State.md" "path": "UI/Angular/Config-State.md"
}, },
{
"text": "修改菜单",
"path": "UI/Angular/Modifying-the-Menu.md"
},
{ {
"text": "替换组件", "text": "替换组件",
"path": "UI/Angular/Component-Replacement.md" "path": "UI/Angular/Component-Replacement.md"

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save