项目

Angular UI 的数据表列(或实体属性)扩展

介绍

实体属性扩展系统允许您为实体的数据表添加新列,或更改/移除已存在的列。下方用户管理页面中添加了一个“名称”列:

您将在代码中访问当前实体并显示其值,使列可排序,执行可见性检查等操作。您还可以在表格单元格中渲染自定义 HTML。

如何设置

在本例中,我们将在身份模块的用户管理页面中添加一个“名称”列,并显示 name 字段的值。

步骤 1. 创建实体属性贡献者

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

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

import {
  eIdentityComponents,
  IdentityEntityPropContributors,
  IdentityUserDto,
} from '@abp/ng.identity';
import { EntityProp, EntityPropList, ePropType } from '@abp/ng.components/extensible';

const nameProp = new EntityProp<IdentityUserDto>({
  type: ePropType.String,
  name: 'name',
  displayName: 'AbpIdentity::Name',
  sortable: true,
  columnWidth: 250,
});

export function namePropContributor(propList: EntityPropList<IdentityUserDto>) {
  propList.addAfter(nameProp, 'userName', (value, name) => value.name === name);
}

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

属性列表(方便地命名为 propList)是一个双向链表。这就是我们使用 addAfter 方法的原因,该方法在具有先前值的第一个节点之后添加一个具有给定值的节点。您可以在此处找到所有可用的方法

步骤 2. 导入并使用实体属性贡献者

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

// src/app/app.routes.ts

// 其他导入
import { identityEntityPropContributors } from './entity-prop-contributors';

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

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

  // 其他路由
];

这样就可以了,nameProp 实体属性将被添加,您将在 identity 包的用户页面(UsersComponent)的网格中,在用户名旁边看到“名称”列。

如何在单元格中渲染自定义 HTML

您可以使用 valueResolver 在表格中渲染 HTML 字符串。假设我们希望在未确认的电子邮件和电话旁边显示一个红色的叉号图标(❌),而不是在已确认的电子邮件和电话旁边显示绿色的勾号图标。下面的贡献者将为您完成此操作。

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

import {
  eIdentityComponents,
  IdentityEntityPropContributors,
  IdentityUserDto,
} from '@abp/ng.identity';
import { EntityProp, EntityPropList } from '@abp/ng.components/extensible';
import { of } from 'rxjs';

export function emailPropContributor(propList: EntityPropList<IdentityUserDto>) {
  const index = propList.indexOf('email', (value, name) => value.name === name);
  const droppedNode = propList.dropByIndex(index);
  const emailProp = new EntityProp<IdentityUserDto>({
    ...droppedNode.value,
    valueResolver: data => {
      const { email, emailConfirmed } = data.record;
      const icon = email && !emailConfirmed ? `<i class="fa fa-times text-danger ml-1"></i>` : '';

      return of((email || '') + icon); // 应返回一个 observable
    },
  });

  propList.addByIndex(emailProp, index);
}

export function phonePropContributor(propList: EntityPropList<IdentityUserDto>) {
  const index = propList.indexOf('phoneNumber', (value, name) => value.name === name);
  const droppedNode = propList.dropByIndex(index);
  const phoneProp = new EntityProp<IdentityUserDto>({
    ...droppedNode.value,
    valueResolver: data => {
      const { phoneNumber, phoneNumberConfirmed } = data.record;
      const icon =
        phoneNumber && !phoneNumberConfirmed ? `<i class="fa fa-times text-danger ml-1"></i>` : '';

      return of((phoneNumber || '') + icon); // 应返回一个 observable
    },
  });

  propList.addByIndex(phoneProp, index);
}

export const identityEntityPropContributors: IdentityEntityPropContributors = {
  [eIdentityComponents.Users]: [emailPropContributor, phonePropContributor],
};

valueResolver 方法应返回一个 observable。您可以使用 RxJS 中的 of 包装返回值。

对象扩展

在现有实体上定义的额外属性将根据其配置包含在表格中。这些值也会自动映射到 extraProperties 和从 extraProperties 映射。在定义自定义贡献者时,这些属性是可用的,因此您可以删除、修改或重新排序它们。对于这些属性,isExtra 标识符将设置为 true,并将定义此自动行为。

API

PropData<R = any>

PropData 是传递给 EntityProp 中所有回调或谓词的参数的类型。

它具有以下属性:

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

    {
      type: ePropType.String,
      name: 'name',
      valueResolver: data => {
        const name = data.record.name || '';
        return of(name.toUpperCase());
      },
    }
    
  • index 是记录所在的表格索引。

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

    {
      type: ePropType.String,
      name: 'name',
      valueResolver: data => {
        const restService = data.getInjected(RestService);
        const usersComponent = data.getInjected(UsersComponent);
    
        // 在此处使用 restService 和 usersComponent 的公共属性和方法
      },
    }
    

PropCallback<T, R = any>

PropCallback 是作为 prop 参数传递给 EntityProp 的回调函数的类型。属性回调接收单个参数 PropData。返回类型可以是任何类型,包括 void。以下是简化表示:

type PropCallback<T, R = any> = (data?: PropData<T>) => R;

PropPredicate<T>

PropPredicate 是作为 visible 参数传递给 EntityProp 的谓词函数的类型。属性谓词接收单个参数 PropData。返回类型必须为 boolean。以下是简化表示:

type PropPredicate<T> = (data?: PropData<T>) => boolean;

ColumnPredicate

ColumnPredicate 是作为 columnVisible 参数传递给 EntityProp 的谓词函数的类型。列谓词接收单个参数 GetInjected,您可以使用 GetInjected 参数访问注入的 ServiceComponent。返回类型必须为 boolean。以下是简化表示:

type ColumnPredicate<T> = (getInjected: GetInjected) => boolean;

EntityPropOptions<R = any>

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

其类型定义如下:

type EntityPropOptions<R = any> = {
  type: ePropType;
  name: string;
  displayName?: string;
  valueResolver?: PropCallback<R, Observable<any>>;
  sortable?: boolean;
  columnWidth?: number;
  permission?: string;
  visible?: PropPredicate<R>;
  columnVisible?: ColumnPredicate;
  tooltip?: FormPropTooltip;
};

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

  • type 是属性值的类型。用于表格中的自定义渲染。(必需
  • name 是属性名称(或键),将用于读取属性的值。(必需
  • displayName 是属性的名称,将被本地化并显示为列标题。(默认值: options.name
  • valueResolver 是渲染单元格时调用的回调。它必须返回一个 observable。(默认值: data => of(data.record[options.name])
  • sortable 定义表格是否可基于此实体属性排序。排序图标基于此显示。(默认值: false
  • columnWidth 为列定义最小宽度。适用于水平滚动。(默认值: undefined
  • permission 是权限上下文,将用于决定是否应向用户显示此实体属性的列。(默认值: undefined
  • visible 是一个谓词,将用于根据数据记录决定此实体属性的单元格内容是否应显示在表格上。(默认值: () => true
  • columnVisible 是一个谓词,将用于决定此实体属性的列是否应显示在表格上。(默认值: () => true
  • tooltip 是表格列的工具提示。(默认值: undefined

重要提示:不要在可见性谓词中使用记录。首先,表格标题也会检查它,并且记录将是 undefined。其次,如果一些单元格显示而另一些不显示,表格将会损坏。当您需要隐藏特定单元格时,请使用 valueResolver 并渲染一个空单元格。

visible 谓词只隐藏单元格内容,而不隐藏列。使用 columnVisible 来隐藏整个列。

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

EntityProp<R = any>

EntityProp 是定义实体属性的类。它接收一个 EntityPropOptions 并将默认值设置到属性,创建一个可以传递给实体贡献者的实体属性。

const options: EntityPropOptions<IdentityUserDto> = {
  type: ePropType.String,
  name: 'email',
  displayName: 'AbpIdentity::EmailAddress',
  valueResolver: data => {
    const { email, emailConfirmed } = data.record;

    return of(
      (email || '') + (emailConfirmed ? `<i class="fa fa-check text-success ml-1"></i>` : ''),
    );
  },
  sortable: true,
  columnWidth: 250,
  permission: 'AbpIdentity.Users.ReadSensitiveData', // 假设的
  visible: data => {
    const store = data.getInjected(Store);
    const selectSensitiveDataVisibility = ConfigState.getSetting(
      'Abp.Identity.IsSensitiveDataVisible'  // 假设的
    );
    
    return store.selectSnapshot(selectSensitiveDataVisibility).toLowerCase() === 'true';
  }
  columnVisible: getInjected => {
    const sessionStateService = getInjected(SessionStateService);
    return !sessionStateService.getTenant()?.isAvailable; // 当租户可用时隐藏此列。
  },
  tooltip: { text: 'AbpIdentity::EmailAddress_Tooltip', placement: 'top' }
};

const prop = new EntityProp(options);

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

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

    const prop = EntityProp.create(options);
    
  • EntityProp.createMany<R = any>(options: EntityPropOptions<R>[]) 用于通过给定的 EntityPropOptions 数组创建多个 EntityProp 实例。

    const props = EntityProp.createMany(optionsArray);
    

EntityPropList<R = any>

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

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

export function reorderUserContributors(
  propList: EntityPropList<IdentityUserDto>
) {
  // 删除 email 节点
  const emailPropNode = propList.dropByValue(
    'AbpIdentity::EmailAddress',
    (prop, text) => prop.text === text
  );

  // 在 phoneNumber 之后添加回来
  propList.addAfter(
    emailPropNode.value,
    'phoneNumber',
    (value, name) => value.name === name
  );
}

EntityPropContributorCallback<R = any>

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

export function isLockedOutPropContributor(
  propList: EntityPropList<IdentityUserDto>
) {
  // 将 isLockedOutProp 添加为第 2 列
  propList.add(isLockedOutProp).byIndex(1);
}

export const identityEntityPropContributors = {
  [eIdentityComponents.Users]: [isLockedOutPropContributor],
};

另请参阅

在本文档中