mirror of https://github.com/abpframework/abp
				
				
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							547 lines
						
					
					
						
							16 KiB
						
					
					
				
			
		
		
	
	
							547 lines
						
					
					
						
							16 KiB
						
					
					
				| ## 替换组件
 | |
| 
 | |
| 你可以将一些ABP的组件替换为你自己的自定义组件.
 | |
| 
 | |
| 你可以**替换**但**不能自定义**默认ABP组件的原因是禁用或更改该组件的一部分可能会导致问题. 所以我们把这些组件称为可替换组件.
 | |
| 
 | |
| ### 如何替换组件
 | |
| 
 | |
| 创建一个你想要使用的新组件,添加到 `AppModule` 中的 `declarations` 和`entryComponents` 中.
 | |
| 
 | |
| 然后打开 `app.component.ts` 使用 `AddReplaceableComponent` 将你的组件替换ABP组件. 如下所示:
 | |
| 
 | |
| ```js
 | |
| import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action
 | |
| import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum
 | |
| import { Store } from '@ngxs/store'; // imported Store
 | |
| //...
 | |
| 
 | |
| @Component(/* component metadata */)
 | |
| export class AppComponent {
 | |
|   constructor(
 | |
|     private store: Store // injected Store
 | |
|   )
 | |
|   {
 | |
|     // dispatched the AddReplaceableComponent action
 | |
|     this.store.dispatch(
 | |
|       new AddReplaceableComponent({
 | |
|         component: YourNewRoleComponent,
 | |
|         key: eIdentityComponents.Roles,
 | |
|       }),
 | |
|     );
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| 
 | |
| 
 | |
| ### 如何替换布局
 | |
| 
 | |
| 每个ABP主题模块有3个布局,分别是`ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. 这些布局可以用相同的方式替换.
 | |
| 
 | |
| > 一个布局组件模板应该包含 `<router-outlet></router-outlet>` 元素.
 | |
| 
 | |
| 下面的例子解释了如何更换 `ApplicationLayoutComponent`:
 | |
| 
 | |
| 运行以下命令在 `angular` 文件夹中生成布局:
 | |
| 
 | |
| ```bash
 | |
| yarn ng generate component my-application-layout
 | |
| ```
 | |
| 
 | |
| 在你的布局模板(`my-application-layout.component.html`)中添加以下代码:
 | |
| 
 | |
| ```html
 | |
| <router-outlet></router-outlet>
 | |
| ```
 | |
| 
 | |
| 打开 `src/app` 文件夹下的 `app.component.ts` 文件添加以下内容:
 | |
| 
 | |
| ```js
 | |
| import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
 | |
| import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys
 | |
| import { Store } from '@ngxs/store'; // imported Store
 | |
| import { MyApplicationLayoutComponent } from './my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
 | |
| 
 | |
| @Component(/* component metadata */)
 | |
| export class AppComponent {
 | |
|   constructor(
 | |
|     private store: Store, // injected Store
 | |
|   ) {
 | |
|     // dispatched the AddReplaceableComponent action
 | |
|     this.store.dispatch(
 | |
|       new AddReplaceableComponent({
 | |
|         component: MyApplicationLayoutComponent,
 | |
|         key: eThemeBasicComponents.ApplicationLayout,
 | |
|       }),
 | |
|     );
 | |
|   }
 | |
| }
 | |
| ```
 | |
| 
 | |
| ### 布局组件
 | |
| 
 | |
| 
 | |
| 
 | |
| #### 如何替换LogoComponent
 | |
| 
 | |
| 
 | |
| 
 | |
| 在 `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如下:
 | |
| 
 | |
| 
 | |
| 
 | |
| #### 如何替换RoutesComponent
 | |
| 
 | |
| 
 | |
| 
 | |
| 在 `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如下:
 | |
| 
 | |
| 
 | |
| 
 | |
| #### 如何替换NavItemsComponent
 | |
| 
 | |
| 
 | |
| 
 | |
| 在 `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::MyAccount' | 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如下:
 | |
| 
 | |
| 
 | |
| 
 | |
| ## 另请参阅
 | |
| 
 | |
| - [如何替换PermissionManagementComponent](./Permission-Management-Component-Replacement.md)
 | |
| 
 | |
| ## 下一步是什么?
 | |
| 
 | |
| - [自定义设置页面](./Custom-Setting-Page.md)
 |