项目

Angular UI 的实体操作扩展

介绍

实体操作扩展系统允许您为实体的操作菜单添加新操作。下图用户管理页面中添加了一个“点击我”操作:

通过编写自定义代码,您可以执行任何操作(打开模态框、进行 HTTP API 调用、重定向到其他页面……等等)。您还可以在代码中访问当前实体。

如何设置

在此示例中,我们将在身份模块的用户管理页面中添加一个“点击我!”操作,并弹出当前行的 userName 提示框。

步骤 1. 创建实体操作贡献者

以下代码准备了一个名为 identityEntityActionContributors 的常量,可供导入并在您的根应用程序配置中使用:

// src/app/entity-action-contributors.ts

import { eIdentityComponents, IdentityEntityActionContributors } from '@abp/ng.identity';
import { IdentityUserDto } from '@abp/ng.identity/proxy';
import { EntityAction, EntityActionList } from '@abp/ng.components/extensible';

const alertUserName = new EntityAction<IdentityUserDto>({
  text: 'Click Me!',
  action: (data) => {
    // 用您的自定义代码替换 alert
    alert(data.record.userName);
  },
  // 有关所有选项,请参阅 API 部分的 EntityActionOptions
});

export function alertUserNameContributor(actionList: EntityActionList<IdentityUserDto>) {
  actionList.addTail(alertUserName);
}

export const identityEntityActionContributors: IdentityEntityActionContributors = {
  // 枚举指示要添加贡献者的页面
  [eIdentityComponents.Users]: [
    alertUserNameContributor,
    // 您可以在此处添加更多贡献者
  ],
};

操作列表,为了方便命名为 actionList,是一个双向链表。这就是我们使用 addTail 方法的原因,该方法将给定值添加到列表的末尾。您可以在此处找到所有可用的方法

步骤 2. 导入并使用实体操作贡献者

在您的路由配置中导入 identityEntityActionContributors,并将其传递给 identity 路由的静态 configureRoutes 方法,如下所示:

// src/app/app.routes.ts

// 其他导入
import { identityEntityActionContributors } from './entity-action-contributors';

export const APP_ROUTES: Routes = [
  // 其他路由

  {
    path: 'identity',
    loadChildren: () =>
      import('@abp/ng.identity').then(c =>
        c.createRoutes({
          entityActionContributors: identityEntityActionContributors,
        })
      ),
  },

  // 其他路由
];

至此,alertUserName 实体操作将作为 identity 包中“用户”页面(UsersComponent)网格下拉菜单的最后一个操作被添加。

如何放置自定义模态框并通过实体操作触发它

让我们采用依赖注入来扩展 identity 包的功能,并为用户实体添加一个快速查看操作。我们将采用惰性加载的方法。

  1. 在此路径下创建一个文件夹:src/app/identity-extended

  2. 添加一个类似于此的实体操作:

    // src/app/identity-extended/entity-action-contributors.ts
    
    import {
      eIdentityComponents,
      IdentityEntityActionContributors,
      IdentityUserDto,
    } from '@abp/ng.identity';
    import { EntityAction, EntityActionList } from '@abp/ng.components/extensible';
    import { IdentityExtendedComponent } from './identity-extended.component';
    
    const quickViewAction = new EntityAction<IdentityUserDto>({
      text: 'Quick View',
      action: data => {
        const component = data.getInjected(IdentityExtendedComponent);
        component.openUserQuickView(data.record);
      },
    });
    
    export function customModalContributor(actionList: EntityActionList<IdentityUserDto>) {
      actionList.addTail(quickViewAction);
    }
    
    export const identityEntityActionContributors: IdentityEntityActionContributors = {
      // 枚举指示要添加贡献者的页面
      [eIdentityComponents.Users]: [
        customModalContributor,
        // 您可以在此处添加更多贡献者
      ],
    };
    
  3. 创建一个身份包的父组件。

    // src/app/identity-extended/identity-extended.component.ts
    
    import { LocalizationPipe } from '@abp/ng.core';
    import { IdentityUserDto } from '@abp/ng.identity/proxy';
    import { ModalCloseDirective, ModalComponent } from '@abp/ng.theme.shared';
    import { CommonModule } from '@angular/common';
    import { Component } from '@angular/core';
    import { RouterOutlet } from '@angular/router';
    
    @Component({
      selector: 'app-identity-extended',
      templateUrl: './identity-extended.component.html',
      imports: [
        CommonModule,
        ModalComponent,
        RouterOutlet,
        LocalizationPipe,
        ModalCloseDirective
      ]
    })
    export class IdentityExtendedComponent {
      isUserQuickViewVisible: boolean;
    
      user: IdentityUserDto;
    
      openUserQuickView(record: IdentityUserDto) {
        this.user = new Proxy(record, {
          get: (target, prop) => target[prop] || '—',
        });
        this.isUserQuickViewVisible = true;
      }
    }
    
  4. 向父组件添加路由出口和模态框。

    <!-- src/app/identity-extended/identity-extended.component.html -->
    
    <router-outlet></router-outlet>
    
    <abp-modal [(visible)]="isUserQuickViewVisible">
      <ng-template #abpHeader>
        <h3>{{ user.userName }}</h3>
      </ng-template>
    
      <ng-template #abpBody>
        <table class="table table-borderless">
          <tbody>
            <tr>
              <th scope="row">{{ 'AbpIdentity::DisplayName:Name' | abpLocalization }}</th>
              <td>{{ user.name }}</td>
            </tr>
            <tr>
              <th scope="row">{{ 'AbpIdentity::DisplayName:Surname' | abpLocalization }}</th>
              <td>{{ user.surname }}</td>
            </tr>
            <tr>
              <th scope="row">{{ 'AbpIdentity::EmailAddress' | abpLocalization }}</th>
              <td>{{ user.email }}</td>
            </tr>
            <tr>
              <th scope="row">{{ 'AbpIdentity::PhoneNumber' | abpLocalization }}</th>
              <td>{{ user.phoneNumber }}</td>
            </tr>
          </tbody>
        </table>
      </ng-template>
    
      <ng-template #abpFooter>
        <button type="button" class="btn btn-secondary" abpClose>
          {{ 'AbpUi::Close' | abpLocalization }}
        </button>
      </ng-template>
    </abp-modal>
    
  5. 为组件添加如下所示的路由配置:

    //  src/app/identity-extended/identity-extended.routes.ts
    
    import { Routes } from '@angular/router';
    import { IdentityExtendedComponent } from './identity-extended.component';
    import { identityEntityActionContributors } from './entity-action-contributors';
    
    export const createExtendedIdentityRoutes = (): Routes => [
      {
        path: '',
        component: IdentityExtendedComponent,
        children: [
          {
            path: '',
            loadChildren: () =>
              import('@abp/ng.identity').then(c =>
                c.createRoutes({
                  entityActionContributors: identityEntityActionContributors,
                }),
              ),
          },
        ],
      },
    ];
    
  6. 在您的根路由配置中使用 createExtendedIdentityRoutes 而不是 createRoutes 函数。 由于路由已在 createExtendedIdentityRoutes 函数中惰性加载,您可以直接使用其子路由数组,以避免不必要的额外惰性加载调用。

    // src/app/app.routes.ts
    
    export const APP_ROUTES: Routes = [
      // 其他路由
    
      {
        path: 'identity',
        children: [
          ...createExtendedIdentityRoutes()
        ],
      },
    
      // 其他路由
    ];
    

完成了。正如您所见,我们通过依赖注入访问了 IdentityExtendedComponent,并在我们的操作中调用了它的一个方法。特定的用户也可以通过 data.record 获得,因此我们能够显示一个摘要视图。

API

ActionData<R = any>

ActionData 是传递给 EntityAction 中所有回调函数或谓词的参数的结构。

它具有以下属性:

  • record 是行数据,即在表格中渲染的当前值。

    {
      text: 'Click Me!',
      action: data => {
        alert(data.record.userName);
      },
    }
    
  • index 是记录所在的表格索引。

  • getInjected 等同于 Injector.get。您可以使用它来获取 GridActionsComponent 的注入依赖项,包括但不限于其父组件。

    {
      text: 'Click Me!',
      action: data => {
        const restService = data.getInjected(RestService);
    
        // 在此处使用 restService 的公共属性和方法
      },
      visible: data => {
        const usersComponent = data.getInjected(UsersComponent);
    
        // 在此处使用 usersComponent 的公共属性和方法
      },
    }
    

ActionCallback<T, R = any>

ActionCallback 是可以作为 action 参数传递给 EntityAction 的回调函数类型。操作回调接收单个参数 ActionData。返回类型可以是任何类型,包括 void。以下是简化表示:

type ActionCallback<T, R = any> = (data?: ActionData<T>) => R;

ActionPredicate<T>

ActionPredicate 是可以作为 visible 参数传递给 EntityAction 的谓词函数类型。操作谓词接收单个参数 ActionData。返回类型必须是 boolean。以下是简化表示:

type ActionPredicate<T> = (data?: ActionData<T>) => boolean;

EntityActionOptions<R = any>

EntityActionOptions 是定义创建实体操作时必须传递的必需和可选属性的类型。

其类型定义如下:

type EntityActionOptions<R = any> = {
  action: ActionCallback<R>,
  text: string,
  icon?: string,
  permission?: string,
  visible?: ActionPredicate<R>,
  btnClass?: string,
  btnStyle?: string,
  showOnlyIcon?: boolean,
  tooltip?: FormPropTooltip;
};

如您所见,传递 actiontext 就足以创建一个实体操作。以下是每个属性的作用:

  • action 是点击网格操作时调用的回调函数。(必填)
  • text 是将被本地化的按钮文本。(必填)
  • icon 是定义将放置在文本之前的图标的类。(默认值: '')
  • permission 是权限上下文,用于决定是否应向用户显示此类网格操作。(默认值: undefined)
  • visible 是一个谓词,用于决定当前记录是否应具有此网格操作。(默认值: () => true)
  • btnClass 是应用于按钮的类。(默认值: 'btn btn-primary text-center')
  • btnStyle 是应用于按钮的样式。(默认值: '')
  • showOnlyIcon 表示是否仅显示图标本身。(默认值: false)
  • tooltip 仅适用于单个实体操作按钮。为按钮添加工具提示。(默认值: undefined)

您可以在下面找到一个完整示例。

EntityAction<R = any>

EntityAction 是定义您的实体操作的类。它接收一个 EntityActionOptions 并为属性设置默认值,创建一个可以传递给实体贡献者的实体操作。

const options: EntityActionOptions<IdentityUserDto> = {
  action: data => {
    const component = data.getInjected(IdentityExtendedComponent);
    component.unlock(data.record.id);
  },
  text: 'AbpIdentity::Unlock',
  icon: 'fa fa-unlock',
  permission: 'AbpIdentity.Users.Update',
  visible: data => data.record.isLockedOut,
  btnClass:'btn btn-warning text-center',
  btnStyle: '', //添加内联样式
  showOnlyIcon: true,
  tooltip: { text: 'AbpIdentity::Edit', placement: 'top' }
};

const action = new EntityAction(options);

它还有两个静态方法来创建其实例:

  • EntityAction.create<R = any>(options: EntityActionOptions<R>) 用于创建 EntityAction 的实例。

    const action = EntityAction.create(options);
    
  • EntityAction.createMany<R = any>(options: EntityActionOptions<R>[]) 用于使用给定的 EntityActionOptions 数组创建多个 EntityAction 实例。

    const actions = EntityAction.createMany(optionsArray);
    

EntityActionList<R = any>

EntityActionList 是作为第一个参数(名为 actionList)传递给每个操作贡献者回调的操作列表。它是一个双向链表。您可以在此处找到所有可用的方法

列表中的项目将按照链表的顺序(即从头到尾)显示。如果您想重新排序它们,您只需要这样做:

export function reorderUserContributors(
  actionList: EntityActionList<IdentityUserDto>,
) {
  // 删除“解锁”按钮
  const unlockActionNode = actionList.dropByValue(
    'AbpIdentity::Unlock',
    (action, text) => action.text === text,
  );

  // 将其添加回列表头部
  actionList.addHead(unlockActionNode.value);
}

EntityActionContributorCallback<R = any>

EntityActionContributorCallback 是您可以作为实体操作贡献者回调传递给包静态 createRoutes 方法的类型。

// lockUserContributor 应具有 EntityActionContributorCallback<IdentityUserDto> 类型

export function lockUserContributor(
  actionList: EntityActionList<IdentityUserDto>,
) {
  // 添加 lockUser 作为第 3 个操作
  actionList.add(lockUser).byIndex(2);
}

export const identityEntityActionContributors = {
  [eIdentityComponents.Users]: [lockUserContributor],
};

另请参阅

在本文档中