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 包的功能,并为用户实体添加一个快速查看操作。我们将采用惰性加载的方法。
在此路径下创建一个文件夹:
src/app/identity-extended添加一个类似于此的实体操作:
// 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, // 您可以在此处添加更多贡献者 ], };创建一个身份包的父组件。
// 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; } }向父组件添加路由出口和模态框。
<!-- 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>为组件添加如下所示的路由配置:
// 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, }), ), }, ], }, ];在您的根路由配置中使用
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;
};
如您所见,传递 action 和 text 就足以创建一个实体操作。以下是每个属性的作用:
- 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],
};
抠丁客


