你可以替换不能自定义默认ABP组件的原因是禁用或更改该组件的一部分可能会导致问题. 所以我们把这些组件称为可替换组件.

创建一个你想要使用的新组件,添加到 中的 declarationsentryComponents 中.

然后打开 app.component.ts 使用 AddReplaceableComponent 将你的组件替换ABP组件. 如下所示:

每个ABP主题模块有3个布局,分别是ApplicationLayoutComponent, AccountLayoutComponent, EmptyLayoutComponent. 这些布局可以用相同的方式替换.

下面的例子解释了如何更换 ApplicationLayoutComponent:

运行以下命令在 angular 文件夹中生成布局:

  1. yarn ng generate component my-application-layout

在你的布局模板(my-application-layout.component.html)中添加以下代码:

  1. <router-outlet></router-outlet>
  1. import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
  2. import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys
  3. import { Store } from '@ngxs/store'; // imported Store
  4. import { MyApplicationLayoutComponent } from './my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
  5. @Component(/* component metadata */)
  6. export class AppComponent {
  7. constructor(
  8. private store: Store, // injected Store
  9. ) {
  10. // dispatched the AddReplaceableComponent action
  11. this.store.dispatch(
  12. new AddReplaceableComponent({
  13. component: MyApplicationLayoutComponent,
  14. key: eThemeBasicComponents.ApplicationLayout,
  15. }),
  16. );
  17. }
  18. }

Layout Components

如何替换LogoComponent

angular 目录下运行以下命令创建新的组件 LogoComponent:

  1. yarn ng generate component logo --inlineTemplate --inlineStyle --entryComponent
  2. # You don't need the --entryComponent option in Angular 9

打开 src/app/logo 目录下生成的 logo.component.ts 并使用以下内容替换它:

打开 src/app 目录下的 app.component.ts 做以下修改:

  1. import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
  2. import { Store } from '@ngxs/store'; // imported Store
  3. import { LogoComponent } from './logo/logo.component'; // imported NavItemsComponent
  4. import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
  5. //...
  6. @Component(/* component metadata */)
  7. export class AppComponent implements OnInit {
  8. constructor(..., private store: Store) {} // injected Store
  9. ngOnInit() {
  10. //...
  11. // added dispatch
  12. this.store.dispatch(
  13. new AddReplaceableComponent({
  14. component: LogoComponent,
  15. key: eThemeBasicComponents.Logo,
  16. }),
  17. );
  18. }
  19. }

最终UI如下:

New logo

如何替换RoutesComponent

angular 目录下运行以下命令创建新的组件 RoutesComponent:

  1. yarn ng generate component routes --entryComponent
  2. # You don't need the --entryComponent option in Angular 9
  1. import { ABP, ReplaceableComponents } from '@abp/ng.core';
  2. import {
  3. Component,
  4. HostBinding,
  5. Inject,
  6. Renderer2,
  7. TrackByFunction,
  8. AfterViewInit,
  9. } from '@angular/core';
  10. import { fromEvent } from 'rxjs';
  11. import { debounceTime } from 'rxjs/operators';
  12. @Component({
  13. selector: 'app-routes',
  14. templateUrl: 'routes.component.html',
  15. })
  16. export class RoutesComponent implements AfterViewInit {
  17. @HostBinding('class.mx-auto')
  18. marginAuto = true;
  19. smallScreen = window.innerWidth < 992;
  20. constructor(private renderer: Renderer2) {}
  21. ngAfterViewInit() {
  22. fromEvent(window, 'resize')
  23. .pipe(debounceTime(150))
  24. .subscribe(() => {
  25. this.smallScreen = window.innerWidth < 992;
  26. });
  27. }
  28. }

打开 src/app/routes 目录下生成的 routes.component.html 并使用以下内容替换它:

  1. <ul class="navbar-nav">
  2. <li class="nav-item">
  3. <a class="nav-link" routerLink="/"
  4. ><i class="fas fa-home"></i> {{ '::Menu:Home' | abpLocalization }}</a
  5. >
  6. </li>
  7. <li class="nav-item">
  8. <a class="nav-link" routerLink="/my-page"><i class="fas fa-newspaper mr-1"></i>My Page</a>
  9. </li>
  10. <li
  11. #navbarRootDropdown
  12. [abpVisibility]="routeContainer"
  13. class="nav-item dropdown"
  14. display="static"
  15. (click)="
  16. navbarRootDropdown.expand
  17. ? (navbarRootDropdown.expand = false)
  18. : (navbarRootDropdown.expand = true)
  19. "
  20. >
  21. <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="javascript:void(0)">
  22. <i class="fas fa-wrench"></i>
  23. {{ 'AbpUiNavigation::Menu:Administration' | abpLocalization }}
  24. <div
  25. #routeContainer
  26. class="dropdown-menu border-0 shadow-sm"
  27. (click)="$event.preventDefault(); $event.stopPropagation()"
  28. [class.d-block]="smallScreen && navbarRootDropdown.expand"
  29. >
  30. <div
  31. class="dropdown-submenu"
  32. #dropdownSubmenu="ngbDropdown"
  33. placement="right-top"
  34. [autoClose]="true"
  35. *abpPermission="'AbpIdentity.Roles || AbpIdentity.Users'"
  36. >
  37. <div ngbDropdownToggle [class.dropdown-toggle]="false">
  38. <a
  39. abpEllipsis="210px"
  40. [abpEllipsisEnabled]="!smallScreen"
  41. role="button"
  42. class="btn d-block text-left dropdown-toggle"
  43. >
  44. <i class="fa fa-id-card-o"></i>
  45. {{ 'AbpIdentity::Menu:IdentityManagement' | abpLocalization }}
  46. </a>
  47. </div>
  48. <div
  49. #childrenContainer
  50. class="dropdown-menu border-0 shadow-sm"
  51. [class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
  52. >
  53. <div class="dropdown-submenu" *abpPermission="'AbpIdentity.Roles'">
  54. <a class="dropdown-item" routerLink="/identity/roles">
  55. {{ 'AbpIdentity::Roles' | abpLocalization }}</a
  56. >
  57. </div>
  58. <div class="dropdown-submenu" *abpPermission="'AbpIdentity.Users'">
  59. <a class="dropdown-item" routerLink="/identity/users">
  60. {{ 'AbpIdentity::Users' | abpLocalization }}</a
  61. >
  62. </div>
  63. </div>
  64. </div>
  65. <div
  66. class="dropdown-submenu"
  67. ngbDropdown
  68. #dropdownSubmenu="ngbDropdown"
  69. placement="right-top"
  70. [autoClose]="true"
  71. *abpPermission="'AbpTenantManagement.Tenants'"
  72. >
  73. <div ngbDropdownToggle [class.dropdown-toggle]="false">
  74. <a
  75. abpEllipsis="210px"
  76. [abpEllipsisEnabled]="!smallScreen"
  77. role="button"
  78. class="btn d-block text-left dropdown-toggle"
  79. >
  80. <i class="fa fa-users"></i>
  81. {{ 'AbpTenantManagement::Menu:TenantManagement' | abpLocalization }}
  82. </a>
  83. </div>
  84. <div
  85. #childrenContainer
  86. class="dropdown-menu border-0 shadow-sm"
  87. [class.d-block]="smallScreen && dropdownSubmenu.isOpen()"
  88. >
  89. <div class="dropdown-submenu" *abpPermission="'AbpTenantManagement.Tenants'">
  90. <a class="dropdown-item" routerLink="/tenant-management/tenants">
  91. {{ 'AbpTenantManagement::Tenants' | abpLocalization }}</a
  92. >
  93. </div>
  94. </div>
  95. </div>
  96. </div>
  97. </li>
  98. </ul>

打开 src/app 目录下的 app.component.ts 做以下修改:

最终UI如下:

New routes

如何替换NavItemsComponent

angular 目录下运行以下命令创建新的组件 NavItemsComponent:

  1. yarn ng generate component nav-items --entryComponent
  2. # You don't need the --entryComponent option in Angular 9

打开 src/app/nav-items 目录下生成的 nav-items.component.ts 并使用以下内容替换它:

  1. import {
  2. ApplicationConfiguration,
  3. AuthService,
  4. ConfigState,
  5. SessionState,
  6. SetLanguage,
  7. } from '@abp/ng.core';
  8. import { Component, AfterViewInit } from '@angular/core';
  9. import { Navigate, RouterState } from '@ngxs/router-plugin';
  10. import { Select, Store } from '@ngxs/store';
  11. import { Observable, fromEvent } from 'rxjs';
  12. import { map, debounceTime } from 'rxjs/operators';
  13. import snq from 'snq';
  14. @Component({
  15. selector: 'app-nav-items',
  16. templateUrl: 'nav-items.component.html',
  17. })
  18. export class NavItemsComponent implements AfterViewInit {
  19. @Select(ConfigState.getOne('currentUser'))
  20. currentUser$: Observable<ApplicationConfiguration.CurrentUser>;
  21. @Select(ConfigState.getDeep('localization.languages'))
  22. languages$: Observable<ApplicationConfiguration.Language[]>;
  23. smallScreen = window.innerWidth < 992;
  24. get defaultLanguage$(): Observable<string> {
  25. return this.languages$.pipe(
  26. map(
  27. languages =>
  28. snq(
  29. () => languages.find(lang => lang.cultureName === this.selectedLangCulture).displayName,
  30. ),
  31. '',
  32. ),
  33. );
  34. }
  35. get dropdownLanguages$(): Observable<ApplicationConfiguration.Language[]> {
  36. return this.languages$.pipe(
  37. map(
  38. languages =>
  39. [],
  40. ),
  41. );
  42. }
  43. get selectedLangCulture(): string {
  44. }
  45. constructor(private store: Store, private authService: AuthService) {}
  46. ngAfterViewInit() {
  47. fromEvent(window, 'resize')
  48. .pipe(debounceTime(150))
  49. .subscribe(() => {
  50. this.smallScreen = window.innerWidth < 992;
  51. });
  52. }
  53. onChangeLang(cultureName: string) {
  54. this.store.dispatch(new SetLanguage(cultureName));
  55. }
  56. logout() {
  57. this.authService.logout().subscribe(() => {
  58. this.store.dispatch(
  59. new Navigate(['/'], null, {
  60. state: { redirectUrl: this.store.selectSnapshot(RouterState).state.url },
  61. }),
  62. );
  63. });
  64. }
  65. }

打开 src/app/nav-items 目录下生成的 nav-items.component.html 并使用以下内容替换它:

  1. <ul class="navbar-nav">
  2. <input type="search" placeholder="Search" class="bg-transparent border-0 text-white" />
  3. <li *ngIf="(dropdownLanguages$ | async)?.length > 0" class="nav-item">
  4. <div class="dropdown" ngbDropdown #languageDropdown="ngbDropdown" display="static">
  5. <a
  6. ngbDropdownToggle
  7. class="nav-link"
  8. href="javascript:void(0)"
  9. role="button"
  10. id="dropdownMenuLink"
  11. data-toggle="dropdown"
  12. aria-haspopup="true"
  13. aria-expanded="false"
  14. >
  15. {{ defaultLanguage$ | async }}
  16. </a>
  17. <div
  18. class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
  19. aria-labelledby="dropdownMenuLink"
  20. [class.d-block]="smallScreen && languageDropdown.isOpen()"
  21. >
  22. <a
  23. *ngFor="let lang of dropdownLanguages$ | async"
  24. href="javascript:void(0)"
  25. class="dropdown-item"
  26. (click)="onChangeLang(lang.cultureName)"
  27. >{{ lang?.displayName }}</a
  28. >
  29. </div>
  30. </div>
  31. </li>
  32. <li class="nav-item">
  33. <ng-template #loginBtn>
  34. <a role="button" class="nav-link" routerLink="/account/login">{{
  35. 'AbpAccount::Login' | abpLocalization
  36. }}</a>
  37. </ng-template>
  38. <div
  39. *ngIf="(currentUser$ | async)?.isAuthenticated; else loginBtn"
  40. ngbDropdown
  41. class="dropdown"
  42. #currentUserDropdown="ngbDropdown"
  43. display="static"
  44. >
  45. <a
  46. ngbDropdownToggle
  47. class="nav-link"
  48. href="javascript:void(0)"
  49. role="button"
  50. id="dropdownMenuLink"
  51. data-toggle="dropdown"
  52. aria-haspopup="true"
  53. aria-expanded="false"
  54. >
  55. {{ (currentUser$ | async)?.userName }}
  56. </a>
  57. <div
  58. class="dropdown-menu dropdown-menu-right border-0 shadow-sm"
  59. aria-labelledby="dropdownMenuLink"
  60. [class.d-block]="smallScreen && currentUserDropdown.isOpen()"
  61. >
  62. <a class="dropdown-item" routerLink="/account/manage-profile"
  63. ><i class="fa fa-cog mr-1"></i>{{ 'AbpAccount::ManageYourProfile' | abpLocalization }}</a
  64. >
  65. <a class="dropdown-item" href="javascript:void(0)" (click)="logout()"
  66. ><i class="fa fa-power-off mr-1"></i>{{ 'AbpUi::Logout' | abpLocalization }}</a
  67. >
  68. </div>
  69. </div>
  70. </li>
  71. </ul>

打开 src/app 目录下的 app.component.ts 做以下修改:

  1. import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
  2. import { Store } from '@ngxs/store'; // imported Store
  3. import { NavItemsComponent } from './nav-items/nav-items.component'; // imported NavItemsComponent
  4. import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents
  5. //...
  6. @Component(/* component metadata */)
  7. export class AppComponent implements OnInit {
  8. constructor(..., private store: Store) {} // injected Store
  9. ngOnInit() {
  10. //...
  11. // added dispatch
  12. this.store.dispatch(
  13. new AddReplaceableComponent({
  14. component: NavItemsComponent,
  15. key: eThemeBasicComponents.NavItems,
  16. }),
  17. );
  18. }

New nav-items