Update document

pull/4159/head
liangshiwei 5 years ago
parent 44b5413cf4
commit 30d28bc7d3

@ -224,4 +224,14 @@ ABP实现 `SignalR` 的 `IUserIdProvider` 接口,从ABP框架的 `ICurrentUser`
参阅 [SignalR集成Demo](https://github.com/abpframework/abp-samples/tree/master/SignalRDemo),它有一个简单的聊天页面,可以在(经过身份验证的)用户之间发送消息.
![signalr-demo-chat](images/signalr-demo-chat.png)
![signalr-demo-chat](images/signalr-demo-chat.png)
## 备注
ABP框架不会更改SignalR. 就像在其他ASP.NET Core应用程序中一样,它也可以在基于ABP框架的应用程序中工作.
参考[微软文档](https://docs.microsoft.com/zh-cn/aspnet/core/signalr/scale)托管和扩展您的应用程序,集成[Azure](https://docs.microsoft.com/zh-cn/aspnet/core/signalr/publish-to-azure-web-app)或[Redis底版](https://docs.microsoft.com/zh-cn/aspnet/core/signalr/redis-backplane)...等.
## 另请参阅
* [微软SignalR文档](https://docs.microsoft.com/zh-cn/aspnet/core/signalr/introduction)

@ -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-booktore-application).
> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-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/booktore-create-project-{{UI_Text}}.png)
![Creating project](./images/bookstore-create-project-{{UI_Text}}.png)
### 应用迁移
项目创建后,需要应用初始化迁移创建数据库. 运行 `Acme.BookStore.DbMigrator` 应用程序. 它会应用所有迁移,完成流程后你会看到以下结果,数据库已经准备好了!
![Migrations applied](./images/booktore-migrations-applied-{{UI_Text}}.png)
![Migrations applied](./images/bookstore-migrations-applied-{{UI_Text}}.png)
> 另外你也可以在 Visual Studio 包管理控制台运行 `Update-Database` 命令应用迁移.
#### 初始化数据库表
![Initial database tables](./images/booktore-database-tables-{{DB}}.png)
![Initial database tables](./images/bookstore-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/booktore-start-project-{{UI_Text}}.png)
![Set as startup project](./images/bookstore-start-project-{{UI_Text}}.png)
{{if UI == "NG"}}
@ -105,7 +105,7 @@ http://localhost:4200/
下面的图片展示了从启动模板创建的项目是如何分层的.
![booktore-visual-studio-solution](./images/booktore-solution-structure-{{UI_Text}}.png)
![bookstore-visual-studio-solution](./images/bookstore-solution-structure-{{UI_Text}}.png)
> 你可以查看[应用程序模板文档](../startup-templates/application#solution-structure)以详细了解解决方案结构.
@ -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/booktore-open-package-manager-console.png)
![Open Package Manager Console](./images/bookstore-open-package-manager-console.png)
选择 `Acme.BookStore.EntityFrameworkCore.DbMigrations`作为默认的项目然后执行下面的命令:
@ -303,7 +303,7 @@ namespace Acme.BookStore
Add-Migration "Created_Book_Entity"
```
![booktore-pmc-add-book-migration](./images/booktore-pmc-add-book-migration-v2.png)
![bookstore-pmc-add-book-migration](./images/bookstore-pmc-add-book-migration-v2.png)
这样就会在 `Migrations` 文件夹中创建一个新的migration类.然后执行 `Update-Database` 命令更新数据库结构:
@ -311,7 +311,7 @@ Add-Migration "Created_Book_Entity"
Update-Database
````
![booktore-update-database-after-book-entity](./images/booktore-update-database-after-book-entity.png)
![bookstore-update-database-after-book-entity](./images/bookstore-update-database-after-book-entity.png)
#### 添加示例数据
@ -328,7 +328,7 @@ 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')
```
![booktore-book-table](./images/booktore-book-table.png)
![bookstore-book-table](./images/bookstore-book-table.png)
{{end}}
@ -501,7 +501,7 @@ namespace Acme.BookStore
你会看到一些内置的接口和`Book`的接口,它们都是REST风格的:
![booktore-swagger](images/booktore-swagger.png)
![bookstore-swagger](images/bookstore-swagger.png)
Swagger有一个很好的UI来测试API. 你可以尝试执行`[GET] /api/app/book` API来获取书籍列表.
@ -529,11 +529,11 @@ acme.bookStore.book.getList({}).done(function (result) { console.log(result); })
运行这段代码会产生下面的输出:
![booktore-test-js-proxy-getlist](./images/booktore-test-js-proxy-getlist.png)
![bookstore-test-js-proxy-getlist](./images/bookstore-test-js-proxy-getlist.png)
你可以看到服务器返回的 **book list**.你还可以切换到开发者工具的 **network** 查看客户端到服务器端的通讯信息:
![booktore-test-js-proxy-getlist-network](./images/booktore-test-js-proxy-getlist-network.png)
![bookstore-test-js-proxy-getlist-network](./images/bookstore-test-js-proxy-getlist-network.png)
我们使用`create`方法 **创建一本新书**:
@ -555,7 +555,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246
`Acme.BookStore.Web`项目的`Pages`文件夹下创建一个新的文件夹叫`Book`并添加一个名为`Index.cshtml`的Razor Page.
![booktore-add-index-page](./images/booktore-add-index-page-v2.png)
![bookstore-add-index-page](./images/bookstore-add-index-page-v2.png)
打开`Index.cshtml`并把内容修改成下面这样:
@ -621,7 +621,7 @@ namespace Acme.BookStore.Web.Menus
本地化文本位于`Acme.BookStore.Domain.Shared`项目的`Localization/BookStore`文件夹下:
![booktore-localization-files](./images/booktore-localization-files-v2.png)
![bookstore-localization-files](./images/bookstore-localization-files-v2.png)
打开`en.json`文件,将`Menu:BookStore`和`Menu:Book`键的本地化文本添加到文件末尾:
@ -653,7 +653,7 @@ namespace Acme.BookStore.Web.Menus
运行该应用程序,看到新菜单项已添加到顶部栏:
![booktore-menu-items](./images/booktore-new-menu-item.png)
![bookstore-menu-items](./images/bookstore-new-menu-item.png)
点击BookStore下Book子菜单项就会跳转到新增的书籍页面.
@ -701,7 +701,7 @@ namespace Acme.BookStore.Web.Menus
在`Pages/Book/`文件夹中创建 `index.js`文件
![booktore-index-js-file](./images/booktore-index-js-file-v2.png)
![bookstore-index-js-file](./images/bookstore-index-js-file-v2.png)
`index.js`的内容如下:
@ -727,7 +727,7 @@ $(function () {
最终的页面如下:
![Book list](./images/booktore-book-list-2.png)
![Book list](./images/bookstore-book-list-2.png)
{{end}}
@ -760,7 +760,7 @@ yarn
yarn ng generate module book --routing true
```
![Generating book module](./images/booktore-creating-book-module-terminal.png)
![Generating book module](./images/bookstore-creating-book-module-terminal.png)
#### 路由
@ -793,7 +793,7 @@ import { ApplicationLayoutComponent } from '@abp/ng.theme.basic'; //==> added th
yarn ng generate component book/book-list
```
![Creating book list](./images/booktore-creating-book-list-terminal.png)
![Creating book list](./images/bookstore-creating-book-list-terminal.png)
打开 `app\book` 目录下的 `book.module.ts` 文件,使用以下内容替换它:
@ -849,7 +849,7 @@ yarn start
我们将看到book页面的 **book-list works!**:
![Initial book list page](./images/booktore-initial-book-list-page.png)
![Initial book list page](./images/bookstore-initial-book-list-page.png)
#### 创建 BookState
@ -1048,11 +1048,11 @@ export class BookListComponent implements OnInit {
现在你可以在浏览器看到最终结果:
![Book list final result](./images/booktore-book-list.png)
![Book list final result](./images/bookstore-book-list.png)
项目的文件系统结构:
![Book list final result](./images/booktore-angular-file-tree.png)
![Book list final result](./images/bookstore-angular-file-tree.png)
在本教程中我们遵循了官方的[Angular风格指南](https://angular.io/guide/styleguide#file-tree).

@ -29,7 +29,7 @@ end
- **Part 2: 创建,编辑,删除书籍(本章)**
- [Part-3: 集成测试](Part-3.md)
> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-booktore-application).
> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application).
{{if UI == "MVC"}}
@ -37,13 +37,13 @@ end
通过本节, 你将会了解如何创建一个 modal form 来实现新增书籍的功能. 最终成果如下图所示:
![booktore-create-dialog](./images/booktore-create-dialog-2.png)
![bookstore-create-dialog](./images/bookstore-create-dialog-2.png)
#### 新建 modal form
`Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个 `CreateModal.cshtml` Razor页面:
![booktore-add-create-dialog](./images/booktore-add-create-dialog-v2.png)
![bookstore-add-create-dialog](./images/bookstore-add-create-dialog-v2.png)
##### CreateModal.cshtml.cs
@ -130,7 +130,7 @@ namespace Acme.BookStore.Web.Pages.Books
如下图所示,只是在表格 **右上方** 添加了 **New book** 按钮:
![booktore-new-book-button](./images/booktore-new-book-button.png)
![bookstore-new-book-button](./images/bookstore-new-book-button.png)
打开 `Pages/book/index.js``datatable` 配置代码后面添加如下代码:
@ -155,7 +155,7 @@ $('#NewBookButton').click(function (e) {
`Acme.BookStore.Web` 项目的 `Pages/Books` 目录下新建一个名叫 `EditModal.cshtml` 的Razor页面:
![booktore-add-edit-dialog](./images/booktore-add-edit-dialog.png)
![bookstore-add-edit-dialog](./images/bookstore-add-edit-dialog.png)
#### EditModal.cshtml.cs
@ -258,7 +258,7 @@ namespace Acme.BookStore.Web
我们将为表格每行添加下拉按钮 ("Actions") . 最终效果如下:
![booktore-book-table-actions](images/booktore-book-table-actions.png)
![bookstore-book-table-actions](images/bookstore-book-table-actions.png)
打开 `Pages/Books/Index.cshtml` 页面,并按下方所示修改表格部分的代码:
@ -450,6 +450,8 @@ $(function () {
{{end}}
{{if UI == "NG"}}
### 新增 Book 实体
下面的章节中,你将学习到如何创建一个新的模态对话框来新增Book实体.
@ -654,7 +656,7 @@ export class BookListComponent implements OnInit {
你可以打开浏览器,点击**New book**按钮看到模态框.
![Empty modal for new book](./images/booktore-empty-new-book-modal.png)
![Empty modal for new book](./images/bookstore-empty-new-book-modal.png)
#### 添加响应式表单
@ -878,7 +880,7 @@ export class BookListComponent implements OnInit {
现在你可以打开浏览器看到以下变化:
![New book modal](./images/booktore-new-book-form.png)
![New book modal](./images/bookstore-new-book-form.png)
#### 保存图书
@ -990,7 +992,7 @@ export class BookListComponent implements OnInit {
模态框最终看起来像这样:
![Save button to the modal](./images/booktore-new-book-form-v2.png)
![Save button to the modal](./images/bookstore-new-book-form-v2.png)
### 更新图书
@ -1192,7 +1194,7 @@ export class BookListComponent implements OnInit {
UI最终看起来像这样:
![Action buttons](./images/booktore-actions-buttons.png)
![Action buttons](./images/bookstore-actions-buttons.png)
打开 `app\app\book\book-list` 文件夹下的 `book-list.component.html` 文件,使用以下内容替换 `<ng-template #abpHeader>` 标签:
@ -1322,7 +1324,7 @@ delete(id: string) {
`delete` 方法会显示一个确认弹层并订阅用户响应. 只在用户点击 `Yes` 按钮时分派动作. 确认弹层看起来如下:
![booktore-confirmation-popup](./images/booktore-confirmation-popup.png)
![bookstore-confirmation-popup](./images/bookstore-confirmation-popup.png)
#### 添加删除按钮
@ -1339,7 +1341,7 @@ delete(id: string) {
最终操作下拉框UI看起来如下:
![booktore-final-actions-dropdown](./images/booktore-final-actions-dropdown.png)
![bookstore-final-actions-dropdown](./images/bookstore-final-actions-dropdown.png)
{{end}}

@ -15,16 +15,20 @@ import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddRepl
import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
import { Store } from '@ngxs/store'; // imported Store
//...
export class AppComponent {
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: YourNewRoleComponent,
key: eIdentityComponents.Roles,
}),
);
//...
}
}
@ -54,7 +58,7 @@ yarn ng generate component shared/my-application-layout --export --entryComponen
<router-outlet></router-outlet>
```
打开 `app.component.ts` 添加以下内容:
打开 `src/app` 文件夹下的 `app.component.ts` 文件添加以下内容:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
@ -62,11 +66,13 @@ import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeB
import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
import { Store } from '@ngxs/store'; // imported Store
//...
export class AppComponent {
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
// added below content
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: MyApplicationLayoutComponent,
@ -79,6 +85,464 @@ export class AppComponent {
}
```
### 布局组件
![Layout Components](./images/layout-components.png)
#### 如何替换LogoComponent
![LogoComponent](./images/logo-component.png)
`angular` 目录下运行以下命令创建新的组件 `LogoComponent`:
```bash
yarn ng generate component logo --inlineTemplate --inlineStyle --entryComponent
# You don't need the --entryComponent option in Angular 9
```
打开 `src/app/logo` 目录下生成的 `logo.component.ts` 并使用以下内容替换它:
```js
import { Component } from '@angular/core';
@Component({
selector: 'app-logo',
template: `
<a class="navbar-brand" routerLink="/">
<!-- Change the img src -->
<img
src="https://via.placeholder.com/100x50/343a40/FF0000?text=MyLogo"
alt="logo"
width="100%"
height="auto"
/>
</a>
`,
})
export class LogoComponent {}
```
打开 `src/app` 目录下的 `app.component.ts` 做以下修改:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { LogoComponent } from './logo/logo.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: LogoComponent,
key: eThemeBasicComponents.Logo,
}),
);
}
}
```
最终UI如下:
![New logo](./images/replaced-logo-component.png)
#### 如何替换RoutesComponent
![RoutesComponent](./images/routes-component.png)
`angular` 目录下运行以下命令创建新的组件 `RoutesComponent`:
```bash
yarn ng generate component routes --entryComponent
# You don't need the --entryComponent option in Angular 9
```
打开 `src/app/routes` 目录下生成的 `routes.component.ts` 并使用以下内容替换它:
```js
import { ABP, ReplaceableComponents } from '@abp/ng.core';
import {
Component,
HostBinding,
Inject,
Renderer2,
TrackByFunction,
AfterViewInit,
} from '@angular/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
@Component({
selector: 'app-routes',
templateUrl: 'routes.component.html',
})
export class RoutesComponent implements AfterViewInit {
@HostBinding('class.mx-auto')
marginAuto = true;
smallScreen = window.innerWidth < 992;
constructor(private renderer: Renderer2) {}
ngAfterViewInit() {
fromEvent(window, 'resize')
.pipe(debounceTime(150))
.subscribe(() => {
this.smallScreen = window.innerWidth < 992;
});
}
}
```
打开 `src/app/routes` 目录下生成的 `routes.component.html` 并使用以下内容替换它:
```html
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" routerLink="/"
><i class="fas fa-home"></i> {%{{{ '::Menu:Home' | abpLocalization }}}%}</a
>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/my-page"><i class="fas fa-newspaper mr-1"></i>My Page</a>
</li>
<li
#navbarRootDropdown
[abpVisibility]="routeContainer"
class="nav-item dropdown"
display="static"
(click)="
navbarRootDropdown.expand
? (navbarRootDropdown.expand = false)
: (navbarRootDropdown.expand = true)
"
>
<a class="nav-link dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">
<i class="fas fa-wrench"></i>
{%{{{ 'AbpUiNavigation::Menu:Administration' | abpLocalization }}}%}
</a>
<div
#routeContainer
class="dropdown-menu border-0 shadow-sm"
(click)="$event.preventDefault(); $event.stopPropagation()"
[class.d-block]="smallScreen && navbarRootDropdown.expand"
>
<div
class="dropdown-submenu"
ngbDropdown
#dropdownSubmenu="ngbDropdown"
placement="right-top"
[autoClose]="true"
*abpPermission="'AbpIdentity.Roles || AbpIdentity.Users'"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false">
<a
abpEllipsis="210px"
[abpEllipsisEnabled]="!smallScreen"
role="button"
class="btn d-block text-left dropdown-toggle"
>
<i class="fa fa-id-card-o"></i>
{%{{{ 'AbpIdentity::Menu:IdentityManagement' | abpLocalization }}}%}
</a>
</div>
<div
#childrenContainer
class="dropdown-menu border-0 shadow-sm"
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
>
<div class="dropdown-submenu" *abpPermission="'AbpIdentity.Roles'">
<a class="dropdown-item" routerLink="/identity/roles">
{%{{{ 'AbpIdentity::Roles' | abpLocalization }}}%}</a
>
</div>
<div class="dropdown-submenu" *abpPermission="'AbpIdentity.Users'">
<a class="dropdown-item" routerLink="/identity/users">
{%{{{ 'AbpIdentity::Users' | abpLocalization }}}%}</a
>
</div>
</div>
</div>
<div
class="dropdown-submenu"
ngbDropdown
#dropdownSubmenu="ngbDropdown"
placement="right-top"
[autoClose]="true"
*abpPermission="'AbpTenantManagement.Tenants'"
>
<div ngbDropdownToggle [class.dropdown-toggle]="false">
<a
abpEllipsis="210px"
[abpEllipsisEnabled]="!smallScreen"
role="button"
class="btn d-block text-left dropdown-toggle"
>
<i class="fa fa-users"></i>
{%{{{ 'AbpTenantManagement::Menu:TenantManagement' | abpLocalization }}}%}
</a>
</div>
<div
#childrenContainer
class="dropdown-menu border-0 shadow-sm"
[class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
>
<div class="dropdown-submenu" *abpPermission="'AbpTenantManagement.Tenants'">
<a class="dropdown-item" routerLink="/tenant-management/tenants">
{%{{{ 'AbpTenantManagement::Tenants' | abpLocalization }}}%}</a
>
</div>
</div>
</div>
</div>
</li>
</ul>
```
打开 `src/app` 目录下的 `app.component.ts` 做以下修改:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { RoutesComponent } from './routes/routes.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: RoutesComponent,
key: eThemeBasicComponents.Routes,
}),
);
}
}
```
最终UI如下:
![New routes](./images/replaced-routes-component.png)
#### 如何替换NavItemsComponent
![NavItemsComponent](./images/nav-items-component.png)
`angular` 目录下运行以下命令创建新的组件 `NavItemsComponent`:
```bash
yarn ng generate component nav-items --entryComponent
# You don't need the --entryComponent option in Angular 9
```
打开 `src/app/nav-items` 目录下生成的 `nav-items.component.ts` 并使用以下内容替换它:
```js
import {
ApplicationConfiguration,
AuthService,
ConfigState,
SessionState,
SetLanguage,
} from '@abp/ng.core';
import { Component, AfterViewInit } from '@angular/core';
import { Navigate, RouterState } from '@ngxs/router-plugin';
import { Select, Store } from '@ngxs/store';
import { Observable, fromEvent } from 'rxjs';
import { map, debounceTime } from 'rxjs/operators';
import snq from 'snq';
@Component({
selector: 'app-nav-items',
templateUrl: 'nav-items.component.html',
})
export class NavItemsComponent implements AfterViewInit {
@Select(ConfigState.getOne('currentUser'))
currentUser$: Observable<ApplicationConfiguration.CurrentUser>;
@Select(ConfigState.getDeep('localization.languages'))
languages$: Observable<ApplicationConfiguration.Language[]>;
smallScreen = window.innerWidth < 992;
get defaultLanguage$(): Observable<string> {
return this.languages$.pipe(
map(
languages =>
snq(
() => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName,
),
'',
),
);
}
get dropdownLanguages$(): Observable<ApplicationConfiguration.Language[]> {
return this.languages$.pipe(
map(
languages =>
snq(() => languages.filter(lang => lang.cultureName !== this.selectedLangCulture)),
[],
),
);
}
get selectedLangCulture(): string {
return this.store.selectSnapshot(SessionState.getLanguage);
}
constructor(private store: Store, private authService: AuthService) {}
ngAfterViewInit() {
fromEvent(window, 'resize')
.pipe(debounceTime(150))
.subscribe(() => {
this.smallScreen = window.innerWidth < 992;
});
}
onChangeLang(cultureName: string) {
this.store.dispatch(new SetLanguage(cultureName));
}
logout() {
this.authService.logout().subscribe(() => {
this.store.dispatch(
new Navigate(['/'], null, {
state: { redirectUrl: this.store.selectSnapshot(RouterState).state.url },
}),
);
});
}
}
```
打开 `src/app/nav-items` 目录下生成的 `nav-items.component.html` 并使用以下内容替换它:
```html
<ul class="navbar-nav">
<input type="search" placeholder="Search" class="bg-transparent border-0 text-white" />
<li *ngIf="(dropdownLanguages$ | async)?.length > 0" class="nav-item">
<div class="dropdown" ngbDropdown #languageDropdown="ngbDropdown" display="static">
<a
ngbDropdownToggle
class="nav-link"
href="javascript:void(0)"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{%{{{ defaultLanguage$ | async }}}%}
</a>
<div
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && languageDropdown.isOpen()"
>
<a
*ngFor="let lang of dropdownLanguages$ | async"
href="javascript:void(0)"
class="dropdown-item"
(click)="onChangeLang(lang.cultureName)"
>{%{{{ lang?.displayName }}}%}</a
>
</div>
</div>
</li>
<li class="nav-item">
<ng-template #loginBtn>
<a role="button" class="nav-link" routerLink="/account/login">{%{{{
'AbpAccount::Login' | abpLocalization
}}}%}</a>
</ng-template>
<div
*ngIf="(currentUser$ | async)?.isAuthenticated; else loginBtn"
ngbDropdown
class="dropdown"
#currentUserDropdown="ngbDropdown"
display="static"
>
<a
ngbDropdownToggle
class="nav-link"
href="javascript:void(0)"
role="button"
id="dropdownMenuLink"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
{%{{{ (currentUser$ | async)?.userName }}}%}
</a>
<div
class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
aria-labelledby="dropdownMenuLink"
[class.d-block]="smallScreen && currentUserDropdown.isOpen()"
>
<a class="dropdown-item" routerLink="/account/manage-profile"
><i class="fa fa-cog mr-1"></i>{%{{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}}%}</a
>
<a class="dropdown-item" href="javascript:void(0)" (click)="logout()"
><i class="fa fa-power-off mr-1"></i>{%{{{ 'AbpUi::Logout' | abpLocalization }}}%}</a
>
</div>
</div>
</li>
</ul>
```
打开 `src/app` 目录下的 `app.component.ts` 做以下修改:
```js
import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
import { Store } from '@ngxs/store'; // imported Store
import { NavItemsComponent } from './nav-items/nav-items.component'; // imported NavItemsComponent
import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
//...
@Component(/* component metadata */)
export class AppComponent implements OnInit {
constructor(..., private store: Store) {} // injected Store
ngOnInit() {
//...
// added dispatch
this.store.dispatch(
new AddReplaceableComponent({
component: NavItemsComponent,
key: eThemeBasicComponents.NavItems,
}),
);
}
}
```
最终UI如下:
![New nav-items](./images/replaced-nav-items-component.png)
## 下一步是什么?
- [自定义设置页面](./Custom-Setting-Page.md)
- [自定义设置页面](./Custom-Setting-Page.md)

@ -454,6 +454,15 @@
}
]
},
{
"text": "实时",
"items": [
{
"text": "SignalR集成",
"path": "SignalR-Integration.md"
}
]
},
{
"text": "后台服务",
"items": [

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Loading…
Cancel
Save