UI 约束

UI 约束扩展组件支持通过资源角色中定义的声明性策略来控制 UI 组件的可见性与可访问性。

例如,可以将按钮设为非活动状态,可以隐藏表格,等等。

任何 UI 组件或 操作 都可以由 UI 约束进行管理,即使该组件未绑定数据模型,或者不受实体操作和实体属性 策略 的控制。唯一的条件是组件必须具有 ID。

使用该功能可以不必编写代码分析用户的角色或特定权限,也不用在应用程序视图中管理组件的状态。还提供了 Security → Resource roles 视图,可以在运行时调整角色对 UI 的访问控制。

安装

此扩展组件需要企业版 订阅。如果没有企业版订阅,可以按照 企业版试用 介绍的方法试用。

请按照 扩展组件 章节的介绍通过 Jmix 市场自动安装。

手动安装步骤:

  1. 配置 premium 仓库的访问:

    • 在 build.gradle 添加 premium 仓库:

      repositories {
          // ...
          maven {
              url = 'https://global.repo.jmix.io/repository/premium'
              credentials {
                  username = rootProject['premiumRepoUser']
                  password = rootProject['premiumRepoPass']
              }
          }
      }
    • 将 premium 仓库的凭证记录在 ~/.gradle/gradle.properties 文件中:

      premiumRepoUser=123456123456
      premiumRepoPass=abcdefabcdef

      仓库的凭证可以通过许可秘钥获取:秘钥中短横前的部分为用户名,短横后的部分为密码。例如,如果你的秘钥是 123456123456-abcdefabcdef,则用户名是:123456123456,密码是 abcdefabcdef。

  2. 在 build.gradle 中添加依赖:

    implementation 'io.jmix.uiconstraints:jmix-uiconstraints-starter'

用法

UI 约束扩展组件提供了 UI 组件策略,可以在 资源角色 中配置。既支持开发时通过带注解的 Java 接口配置,也支持运行时通过角色管理视图进行配置。

Java 接口中用到的 @UiComponentPolicy 注解在 策略注解 部分介绍。

运行时的角色编辑器可以通过主菜单 Security → Resource roles 访问。当应用程序中包含 UI 约束扩展组件时,策略的下拉列表中会包含 UI component policy 选项:

resource role policies

UI 组件策略编辑器支持指定目标视图,并从下拉列表中选择已有的组件然后配置策略操作和效果:

ui component policy

与实体和实体属性策略不同,UI 组件策略是“拒绝”策略,即,使用这个策略只能使 UI 组件或操作的可见性或可访问性低于策略使用之前的状态。例如,如果某个实体属性在实体属性策略中配置了可见,则 UI 组件策略可以使该属性对应的 UI 组件不可见(设置 VISIBLE 操作为 DENY 效果)。请参阅 effect。

UI 组件策略在视图打开过程的最后应用,即在 ReadyEvent 发送之后。此时,视图的所有初始化工作都已完成,且已经应用了其他策略。

策略也适用于 懒加载标签页 的内容,会在首次打开标签页时执行策略。对于视图打开后创建的其他组件,请以编程方式应用策略,参见 运行时使用策略。

检查 UI 组件策略

使用 UiComponentPolicyContext 和 AccessManager bean 检查当前用户的策略是否允许显示或编辑某个组件或操作。传入视图 ID 和组件路径,格式与 componentIds 属性相同,例如 emailField、addressFragment.streetField 或 userGrid.edit。

以下示例检查策略是否允许在 Customer.detail 视图中显示和编辑 emailField:

@Autowired
private AccessManager accessManager;
private boolean isEmailFieldEditable() {
    UiComponentPolicyContext context =
            new UiComponentPolicyContext("Customer.detail", "emailField");
    accessManager.applyRegisteredConstraints(context);
    return context.isVisiblePermitted() && context.isEnabledPermitted();
}

调用 applyRegisteredConstraints() 后,使用 isVisiblePermitted() 和 isEnabledPermitted() 读取权限。这两种权限初始均为 true;除非另一个约束拒绝访问,否则未被任何策略提及的组件都是允许的。

该上下文检查的是策略权限,而不是组件当前状态或其实体属性权限。即使组件因其他原因被隐藏、禁用或设为只读,该策略判定允许也不会使其可见或可编辑。

内置的 UiComponentPolicyConstraint 会评估当前用户的有效策略。UiComponentPolicyApplier 在应用策略时使用相同的上下文。应用程序可以注册 AccessConstraint<UiComponentPolicyContext>,通过 setVisibleDenied() 或 setEnabledDenied() 进一步限制访问。拒绝的结果会由 AccessLogger 记录。

对动态内容应用策略

对于视图打开后创建的组件,请注入 UiComponentPolicyApplier,然后在将组件添加到视图后调用 applyUiComponentPolicies(view, content),将视图的策略应用于该组件及其子组件。组件和操作的 ID 必须与其策略路径匹配。

@Autowired
private UiComponentPolicyApplier uiComponentPolicyApplier;
private void addRuntimeContent(Component content) {
    getContent().add(content);
    uiComponentPolicyApplier.applyUiComponentPolicies(this, content);
}

单参数方法 applyUiComponentPolicies(content) 会通过组件树查找组件的所属视图。如果找不到视图,则不会应用任何策略。

对于 tabSheet 标签页中的内容,请使用双参数方法 applyUiComponentPolicies(view, content)。JmixTabSheet 仅在组件树中保留选中标签页的内容,并在客户端响应之前才 attach,因此单参数方法无法可靠地找到所属视图。

标签页选择

默认情况下,扩展组件在标签页被选中时对其内容应用策略。也包括 <tab lazy="true"> 中的组件和操作,其内容仅在首次选中时创建。懒加载标签页无需应用程序代码即可应用策略。

每个内容组件只处理一次。再次选择同一标签页不会对应用程序已做的组件更改重新应用策略,传递给 applyUiComponentPolicies() 的内容也会标记为已处理。

如果某个策略隐藏了选中的标签页,则用户在使用视图时可能会选中另一个标签页。

如需禁用标签页选择时自动应用策略,请将 jmix.uiconstraints.apply-policies-on-tab-selection 设置为 false。此时,视图打开后创建的内容(包括懒加载标签页内容)不会自动应用策略。

UiComponentPolicy 注解

编程时,可以使用 @UiComponentPolicy 注解在 资源角色 接口中定义 UI 约束。

示例:

package com.company.demo.security;

import com.company.demo.view.customer.CustomerDetailView;
import com.company.demo.view.customer.CustomerListView;
import io.jmix.security.role.annotation.ResourceRole;
import io.jmix.uiconstraints.annotation.UiComponentPolicy;
import io.jmix.uiconstraints.annotation.UiComponentPolicyAction;
import io.jmix.uiconstraints.annotation.UiComponentPolicyEffect;

@ResourceRole(name = "EmployeeRole", code = EmployeeRole.CODE, scope = "UI")
public interface EmployeeRole {
    String CODE = "employee-role";

    @UiComponentPolicy(
            viewClass = CustomerListView.class,
            componentIds = {"customersDataGrid.importCustomersAction"},
            action = UiComponentPolicyAction.ENABLED,
            effect = UiComponentPolicyEffect.DENY
    )
    @UiComponentPolicy(
            viewClass = CustomerDetailView.class,
            componentIds = {"commentsPane"},
            action = UiComponentPolicyAction.VISIBLE,
            effect = UiComponentPolicyEffect.DENY
    )
    void customers();
}

@UiComponentPolicy 注解的属性如下:

viewClass 和 viewId

UI 组件策略是为视图中的组件定义。视图可以通过 viewClass 或 viewId 属性指定。

componentIds

每个 @UiComponentPolicy 注解可以为视图中的一个或多个 UI 组件定义策略。组件的 ID 必须在 componentIds 属性中列出。支持为下列 UI 组件类型配置策略:

  • 给定视图中的可视化组件(textField、button、vbox、div 等)。示例:firstNameField、importButton。

  • 给定视图中的操作。示例:save。

  • UI 组件的操作。示例:userGrid.edit。

  • fragment 中的组件或操作。示例:addressFragment.cityField。

操作

UI 组件策略支持两种 action:

  • VISIBLE - 管理组件的可见性。

  • ENABLED - 管理组件的可访问性。如果组件支持只读状态,则会设置为只读状态。否则,设置为禁用状态。

效果

effect 属性可以配置两个值之一:ALLOW 或 DENY。

默认情况下,如果未应用 UI 组件策略,则视图中的所有 UI 组件都具有原本的可见性和可访问性状态。如果存在相应的实体属性策略,则与数据绑定组件是可见和可访问的。

如果在 UI 组件策略中指定了 DENY 效果,则会隐藏或禁用 UI 组件或操作。这是 UI 组件策略的最常见用法。

如果需要覆盖由其他角色对同一组件应用的另一个 UI 组件策略,则可以使用 ALLOW 效果。例如,如果已为用户分配了 A 角色,并且此角色禁止访问某个 UI 组件,则可以配置一个 B 角色,对同一组件配置 ALLOW 效果即可恢复对该组件的访问。

ALLOW 效果无法显示/启用由于初始状态或对应实体或实体属性权限缺失而隐藏/禁用的组件。

Application Properties

jmix.uiconstraints.apply-policies-on-tab-selection

Jmix 3.1+

控制是否在选择标签页时应用 UI 组件策略,也包括懒加载标签页中的内容。参考 标签页选择。

默认值:true。

设置为 false 禁用此处理。当视图打开时,策略仍然会应用,也可以使用 UiComponentPolicyApplier 以编程方式应用策略。