章节 8:字段间联动(Field Interlinkage)
模型之间的关系是任何 Oinone 模块的关键组成部分。对于任何业务场景的建模,它们都是必不可少的。然而,我们可能还希望在给定模型内的字段之间建立联系。有时,一个字段的值是由其他字段的值决定的,而有时我们则希望在数据输入方面为用户提供帮助。
这些情况可以通过关系字段domain属性,Ux相关的 compute、 constructFun 属性来实现。尽管从技术层面来讲,本章的内容并不复杂,但这三个概念的语义非常重要。这也是我们首次编写 JAVA 逻辑代码,在此之前,除了类定义和字段声明之外,我们还没有编写过其他任何代码。
一、关系字段属性:domain
参考:与此主题相关的文档可在 “字段属性” 中找到。
目标:在本节结束时:
在项目信息模型中,选择了伙伴类型以后,项目外部关联方的可选项自动发生变化

在我们的费用管理模块里,已完成项目外部关联方的定义。若新增一种伙伴类型,那么在选择该伙伴类型后,项目外部关联方的可选范围会随之改变。为实现这一功能,我们将运用domain
属性这一概念。简单来说,关联字段所呈现的数据,会依据domain
属性值进行筛选 。
例如,为了在我们的expenses.TestModel
模型添加字段partnerType
,并对字段 partners
设置其 domain
属性:
@Field.Enum
@Field(displayName = "伙伴类型")
@UxForm.FieldWidget(@UxWidget(config = {@Prop(name = "clearFields",value = "partners")}))
private BusinessPartnerTypeEnum partnerType;
@Field(displayName = "合作伙伴列表")
@Field.many2many(relationFields = {"testModelId"},referenceFields = {"partnerId"},throughClass =TestModelRelPartner.class)
@Field.Relation(domain = "partnerType == ${activeRecord.partnerType}")
@UxForm.FieldWidget(@UxWidget(widget = "Select"))
// @Field.Relation(domain = "partnerType == ${rootRecord.partnerType}")
private List<PamirsPartner> partners;
通过Ux的 clearFields
属性配置,当partnerType
字段发生变化时,需要清空的字段列表。
在关系字段定义中,添加 domain=X
选项,其中 X
可以接受一个RSQL的表达式,activeRecord代表视图的当前对象

如果不指定UxForm的widget
属性,在生成默认视图时,在多对多字段使用的是Table组件,要影响弹出框的数据过滤。即在视图嵌套的情况下,子视图需要用到父视图数据,则可以通过则需要用rootRecord
来获取值。

警告:
关系字段的 domain
属性仅在前端组件主动发起数据请求时起作用,展示的方式使用 widget="Checkbox"
属性,因为Oinone系统默认实现Checkbox组件,不会再选中是发起新请求,则domain无效。
练习(Exercise)
按照本节目标中所示,将
partnerType
字段添加到你的expenses.ProjectInfo
模型及其表单视图中。
expenses.ProjectInfo
模型的表单视图已完成自定义设置。基于前面所掌握的知识内容,现在让我们尝试对该表单的自定义视图进行进一步的修改与优化吧!
二、UX的一些新属性
(一)compute
参考:与此主题相关的文档可在 “UX compute属性” 中找到。
目标:在本节结束时:
在项目信息模型中,应根据人均预算和人员投入规模,计算最佳项目预算

在我们的费用管理模块中,已经定义了预算和人员投入规模。如果增加一个人均预算,那么,将预算定义为人员投入规模和人均预算这两个字段的乘积是很自然的。为此,我们将使用计算属性的概念,即给定字段的值将根据其他字段的值来计算。
要设置字段的计算属性,并将其 compute
属性设置为一个前端支持的计算表达式。计算方法应该为 activeRecord
中的每条记录设置计算字段的值。
例如,为了在我们的expenses.TestModel
模型及其表单视图中添加字段computeName
,并设置其 compute
属性或表单视图上配置compute
属性:
@Field(displayName = "计算字段", compute = "activeRecord.name")
private String computeName;
<field data="computeName" label="计算字段" compute="activeRecord.name"/>
提示:
你可能已经注意到,计算属性一般是跟只读属性配合使用。这是合理的,因为用户不应该设置其值。
然而,倘若computeName
字段本身也需要支持用户进行修改操作,也就是说computeName
仅仅是将name
字段的值作为一个默认值来使用的话。
此时,只需将计算逻辑修改为activeRecord.computeName?activeRecord.computeName:activeRecord.name
即可。
练习(Exercise)
按照本节目标中所示,将
budgetPerCapita
字段添加到你的expenses.ProjectInfo
模型及其表单视图中。
字段(Field) | 字段显示名 | 类型(Type) | JAVA类型 |
---|---|---|---|
budgetPerCapita | 人均预算 | FLOAT | BigDecimal |
表单视图中
budgetAmount
字段的compute
配置为activeRecord.staffSize * activeRecord.budgetPerCapita
(二)constructFun
参考:与此主题相关的文档可在 “UX constructFun属性” 中找到。
目标:在本节结束时:
当启切换 “项目可见性” 字段时,将设置人员投入规模的默认值为 1。

在我们的费用管理模块中,我们还希望在数据输入方面为用户提供帮助。当切换了 “项目可见性” 字段时,我们希望为"人员投入规模"设置为默认值。此外,当取消设置 “项目可见性” 字段时,我们希望"人员投入规模"重置为零。在这种情况下,给定字段的值会修改其他字段的值。
constructFun
机制为客户端界面提供了一种方式,使得每当用户填写了一个字段值时,无需将任何内容保存到数据库,就可以更新表单。为了实现这一点,在给定字段上增加了Ux注解并指定constructFun
的值为我们定义的一个方法,其中 入参data
表示表单视图中的记录,并使用 @Function(openLevel ={FunctionOpenEnum.API})
来指定该方法可以由前端发起调用。对 data
所做的任何更改都会反映在表单上:
package pro.shushi.oinone.trutorials.expenses.api.model;
import pro.shushi.pamirs.boot.base.ux.annotation.field.UxWidget;
import pro.shushi.pamirs.boot.base.ux.annotation.view.UxForm;
import pro.shushi.pamirs.meta.annotation.Field;
import pro.shushi.pamirs.meta.annotation.Function;
import pro.shushi.pamirs.meta.annotation.Model;
import pro.shushi.pamirs.meta.annotation.Prop;
import pro.shushi.pamirs.meta.base.IdModel;
import pro.shushi.pamirs.meta.enmu.FunctionOpenEnum;
import pro.shushi.pamirs.user.api.model.PamirsUser;
@Model.model(TestConstructFunModel.MODEL_MODEL)
@Model(displayName = "测试ConstructFun模型")
public class TestConstructFunModel extends IdModel {
public static final String MODEL_MODEL="expenses.TestConstructFunModel";
@Field(displayName = "名称")
private String name;
@Field.String
@Field(displayName = "描述")
private String description;
@Field.many2one
@Field(displayName = "用户")
@UxForm.FieldWidget(@UxWidget(config = {@Prop(name = "constructFun",value = "constructFun4User")}))
private PamirsUser user;
@Function(openLevel ={FunctionOpenEnum.API})
public TestConstructFunModel constructFun4User(TestConstructFunModel data){
data.setName("Document for "+data.getUser().getName());
data.setDescription("Default description for "+data.getUser().getName());
return data;
}
}
在这个例子中,更改合作伙伴也会更改名称和描述的值。之后用户可以自行决定是否进一步修改名称和描述的值。
练习(Exercise)
设置人员投入规模的值。
在
expenses.ProjectInfo
模型中创建一个onProjectVisibilityChange
方法,以便在切换了 “项目可见性” 字段时,将“人员投入规模”设置为 1。当取消设置时,清除“人员投入规模”字段的值。基于此前所学,我们知道 UX 的 Prop 属性皆可在自定义视图中进行设置。现在,就让我们借助这些知识,着手对该表单的自定义视图展开进一步的修改与优化吧!
提示:用于提供有用的建议或指导
compute
和 constructFun
机制常见的陷阱是试图通过添加过多的逻辑来显得 “过于聪明”。这可能会产生与预期相反的结果:最终用户会因所有的自动化操作而感到困惑。
compute
往往更容易调试:这样的字段是由给定的方法设置的,所以很容易跟踪值是何时设置的。另一方面,constructFun
机制可能会让人感到困惑:很难知道 constructFun
方法的影响范围。由于多个 constructFun
方法可能会设置相同的字段,因此很容易难以跟踪某个值的来源。
警告:
但凡,方法需要从用户界面调用,你都应该将其定义为public方法
在下一章中,我们将了解如何在点击按钮时触发一些业务逻辑。