# 字段客制化
系统支持通过客制化的方式,实现对字段的默认值、校验、联动等行为进行定制化。
# 目标读者
本文档的目标读者为:本系统的开发和实施人员
# 客制化字段默认值
除了支持使用 grails 模型定义的方式 (opens new window)指定字段保存到数据库中时的默认值, 系统还支持通过客制化指定创建表单中的字段的默认值。
# 注入变量
该客制化运行时,系统的注入变量如下表所列:
变量名称 | 变量类型 | 描述 |
---|---|---|
objectType | tech.muyan.DomainClass | 当前操作的对象类型 |
userContext | grails.plugin.springsecurity.userdetails.GrailsUser | 当前操作的用户信息 |
destColumn | java.lang.String | 设定默认值的目标列 |
destColumnType | java.lang.String | 目标列的完整类型名称 |
application | grails.core.GrailsApplication | 当前的 grails 应用上下文 |
log | Closure<?> | 用于打印执行日志的 log 闭包 |
# 返回结果
该客制化运行的结果需要返回一个包含 key 为 "result" 的 Map 结果,如下所示
// result key 对应的 value 即为系统会传递到前台的默认值
// The value corresponding to the result key is the default value passed to the frontend by the system
return [result: xxx]
2
# 客制化字段校验
表单字段可以定义动态的校验逻辑,在前台表单中该字段发生修改时,会通过接口调用后台的校验逻辑进行校验, 如果校验不通过,则会在前台显示错误信息。
# 注入变量
动态字段校验逻辑执行时,系统注入的相关变量如下标所示
变量名称 | 变量类型 | 描述 |
---|---|---|
userContext | grails.plugin.springsecurity.userdetails.GrailsUser | 当前操作的用户信息 |
application | grails.core.GrailsApplication | 当前的 grails 应用上下文 |
domainName | java.lang.String | 当前操作的对象类型名称 |
destColumn | java.lang.String | 校验的目标列名称 |
destColumnValue | java.lang.Object | 目标列的值 |
create | boolean | 是否是创建操作,如为 false 表示更新操作 |
requestData | java.lang.Object | 前台表单所有字段值的 JSON 序列化 |
objectId | java.lang.Long | 校验的对象 id, 对于创建操作,值为 -1 |
log | Closure<?> | 用于打印执行日志的 log 闭包 |
提示
requestData 的示例结构为:
{
"formValues":{}, // 界面上显示的表单字段的值,包括当前在界面上还未保存到数据库中的值
"record":{}, // 如果是更新操作,该值为当前记录保存在数据库中的值,如果是创建操作,该值为所有有默认值的字段的值
"unique":[] // 与当前触发字段组合的唯一性校验的字段列表
}
2
3
4
5
可以使用 requestData.getAt("formValues").getAt("fieldName")
来获取界面上名为 fieldName 的字段的值
# 返回结果
客制化校验返回结果的结果如下所示, valid 表示校验是否通过, message 是校验失败时的提示信息,如果校验成功,该值会被忽略。
// result key 对应的 value 即为系统会传递到前台的默认值
// The value corresponding to the result key is the default value passed to the frontend by the system
return [
valid: true | false,
message: "校验失败的界面提示信息 Field validation failure information"
]
2
3
4
5
# 客制化字段联动
系统支持通过客制化的方式,实现字段之间的联动,可实现的逻辑包括:
- 决定目标字段是否隐藏
- 决定目标字段是否只读
- 决定目标字段是否必填
- 可以直接设定目标字段的值
- 如果目标字段是选择类型,则可以设定其他选择字段的备选项
以下是向系统注入字段联动处理逻辑需要创建的 Dynamic Field Hook
对象的概述:
Object Type: 选择该客制化逻辑适用的对象类型
Logic source: 逻辑来源,可选为 static field 或 dynamic field
Trigger field: 联动的触发字段,该字段的变化会触发联动
Target field: 联动的目标字段,即该客制化逻辑的目标字段
Trigger dynamic field: 联动的触发动态字段,该动态字段的变化会触发联动
Target dynamic field: 联动的目标动态字段,即该客制化逻辑的目标动态字段
Hook Type: 选择 Field Dependencies hook
core Logic: 客制化代码,具体代码中可使用的注入变量和返回值约定参见下述文档
2
3
4
5
6
7
8
提示
Trigger field 和 Trigger dynamic field 同时只能有一个字段设置值 Trigger field 和 Trigger dynamic field 同时只能有一个字段设置值
具体设置哪个字段的值由字段 Logic source 决定
# 注入变量
该客制化代码中,可使用的注入变量如下表所示:
变量名称 | 变量类型 | 描述 |
---|---|---|
application | grails.core.GrailsApplication | 当前的 grails 应用上下文 |
userContext | grails.plugin.springsecurity.userdetails.GrailsUser | 当前操作的用户信息 |
domainName | java.lang.String | 不包含package 部分的,当前操作的对象类型 |
sourceColumn | java.lang.String | 驱动列,该字段的变化触发了该客制化 |
destColumn | java.lang.String | 目标列,客制化的返回结果会作用于该列 |
sourceColumnValue | java.lang.String | 驱动列的值 |
object | org.grails.web.json.JSONObject | 用户正在编辑的对象当前界面上各个字段的值 |
log | Closure<?> | 用于打印执行日志的 log 闭包 |
提示
在客制化代码被调用的时点,注入的 object
对象只是界面上的输入框中,每一列的暂存数据,
还没有保存到后台的数据库中。
# 返回结果
该客制化代码需要返回的结果为一个多层的 Map 数据,具体返回数据及其说明如下:
return [
//指定该字段的显示状态:hide 为隐藏、show 为显示且可编辑、readonly 为只读
//Specify the display status of the field: hide for hidden, show for editable, and readonly for read-only
display: hide | show | readonly
// 指定该字段是否必填, true 表示必填,false 表示非必填
// Specify whether the field is required: true for required, false for non-required
required: true | false
// 指定该字段的值,如果是个多选字段,可以使用 [] 的形式来指定多个值
// Specify the value of the field, if it is a multi-select field, you can use the [] form to specify multiple values
value: [] 或者 xxx,
// 如果该字段是个选择类型的字段,如下的返回值指定其备选项,
// 每个备选项均包括显示给用户看的 Label 属性和实际保存的 value 属性
// If the field is a select type field, the following return value specifies its options,
// each option includes the Label attribute displayed to the user and the value attribute actually saved
options: [
{
"value": "ABSTRACT_DATE",
"label": "Abstract date"
},
{
"value": "ABSTRACT_DATE_TIME",
"label": "Abstract date with time"
}
]
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
提示
如果希望将某个选择字段的备选项清空,需要将 options 设置为空数组传递到前台,而不能传递 undefined 或者 null 到前台
{
options: []
}
2
3
# 字段联动代码示例
如下的相关代码示例片段演示了一些可能在业务中用到的返回形式
- 将客制化的目标字段设置为隐藏或只读
// 声明返回结果的 Map 对象
// Declare a Map object for the return result
Map result = [:]
// 以下的两句代码是互斥的,否则后一句会覆盖前一句
// The following two lines of code are mutually exclusive, otherwise the latter will override the former
// 设置目标字段在界面上隐藏
// Set the target field to be hidden on the interface
result.put("display", "hide")
// 设置目标字段在界面上为只读字段
// Set the target field to be read-only on the interface
result.put("display", "readonly")
// 返回结果
// Return the result
result
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 将客制化字段设置为显示,且必填
// 声明返回结果的 Map 对象
// Declare a Map object for the return result
Map result = [:]
// 设置目标字段在界面上的正常显示
// Set the target field to be displayed normally on the interface
result.put("display", "show")
// 设置目标字段为必填字段
// Set the target field as required
result.put("required", true)
// 返回结果
// Return the result
result
2
3
4
5
6
7
8
9
10
11
12
13
14
- 根据来源字段的值,修改目标字段的选项(这是一个很通用的,省市字段联动的示例)
// 声名返回结果的 Map 对象
// Declare a Map object for the return result
Map result = [:]
// 设置目标字段在界面上的正常显示
// Set the target field to be displayed normally on the interface
result.put("display", "show")
// 设置目标字段为必填字段
// Set the target field as required
result.put("required", true)
// 如果来源字段的值是 "JS"
// If the value of the source field is "JS"
if (sourceColumnValue == "JS") {
// 将目标字段的选项设置为 [NJ, HA],显示分别为 "南京", "淮安"
// Set the options of the target field to [NJ, HA], displayed as "南京", "淮安" respectively
result.put("options", [
[value: "NJ", label: "南京"],
[value: "HA", label: "淮安"],
])
// 将目标字段的值设置为 "NJ"
// Set the value of the target field to "NJ"
result.put("value", "NJ")
}
// 返回结果
// Return the result
result
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 真实系统中的使用示例
本章节列出了两个在正式系统中使用的,使用客制化代码控制表单行为的例子。
# 控制字段显示
创建 DynamicFieldDefinition
时根据 fieldType 控制 optionsJson 字段显示
# Dynamic Field Hook 对象
Target Field: opitonsJson
Logic source: Static field
Object Type: Dynamic Field Definition
Field Name: fieldType
Hook Type: Field dependencies hook
Core logic: 关联的 Dynamic Logic 定义,可参考下述代码片段
2
3
4
5
6
# 客制化代码
// 声明返回的 Map 对象
// Declare a Map object for the return result
def result = [:]
// 如果 fieldType 字段的值等于 OBJECT、FILE、BOOLEAN 或者 IMAGE
// If the value of the fieldType field equals OBJECT, FILE, BOOLEAN or IMAGE
if (sourceColumnValue == tech.muyan.enums.FieldType.OBJECT.name()
|| sourceColumnValue == tech.muyan.enums.FieldType.FILE.name()
|| sourceColumnValue == tech.muyan.enums.FieldType.BOOLEAN.name()
|| sourceColumnValue == tech.muyan.enums.FieldType.IMAGE.name()) {
// 将目标字段 optionsJson 设置为只读
// Set the target field optionsJson to read-only
result.put("display", "readonly")
} else {
// 否则将目标字段 opitonsJson 设置为显示且可编辑
// Otherwise, set the target field optionsJson to be displayed and editable
result.put("display", "show")
}
// 返回 result 对象作为客制化逻辑执行的结果
// Return the result object as the result of custom logic execution
result
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 控制选择字段的备选项
以下的客制化配置和代码,实现了在创建 Object Dynamic Field 对象时,根据用户选择的 dynamicField
字段,
控制 displayComponentType
字段中的备选项。
# Dynamic Logic 对象
Target Field: opitonsJson
Object Type: Dynamic Field Definition
Field Name: fieldType
Logic Type: Field dependencies
Code: 参考下述代码片段
2
3
4
5
# 客制化代码
// 声明返回的 result Map,默认显示该字段
// Declare the result Map to be returned, default to show this field
def result = ["display": "show"]
// 声明返回的 options 数组
// Declare the options array to be returned
def options = []
// 从传递到后台的 sourceColumnValue 字段,在这里是 dynamicFieldDefinition 的 id 字段获取对象
// Get the object from the sourceColumnValue field passed to the backend, which is the id field of dynamicFieldDefinition here
def dynamicField = tech.muyan.DynamicFieldDefinition.get(Long.valueOf(sourceColumnValue))
if (null != dynamicField) {
// 获取对象中保存的 fieldType 信息
// Get the fieldType information saved in the object
def fieldType = dynamicField.fieldType
// 从配置的映射表中获取每一种 fieldType 可以使用的 displayComponentType 列表
// Get the list of displayComponentTypes that can be used for each fieldType from the configured mapping table
def optionConfig = tech.muyan.config.DynamicFieldTypeToComponentMapping.FieldTypeToComponentMapping.get(fieldType)
if (optionConfig != null && optionConfig.size() > 0) {
// 遍历该列表,将枚举值的 name 和 label 分别放入 options 列表
// Iterate through the list, putting the name and label of the enum values into the options list respectively
for (option in optionConfig) {
options.add(Map.of("value", option.name(), "label", option.label))
}
// 将 options 数组放入 result map
// Put the options array into the result map
result.put("options", options)
}
}
// 返回结果
// Return the result
result
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 字段快捷搜索逻辑
系统支持通过客制化的方式,定制某个对象的页面上,引用其他对象的时候,快捷搜索的过滤逻辑,具体
对于客制化搜索逻辑,需要创建如下的Dynamic Field Hook
对象
Target Field: 设置为待搜索的对象字段
Object Type: 选择该客制化逻辑适用的对象类型,这里要设置字段所属于的对象的类型, 而不是待搜索对象的类型
Hook Type: 选择 Field search addon hook
Core Logic: 客制化代码,具体代码中可使用的注入变量和返回值约定参见下述文档
2
3
4
# 注入变量
该客制化代码中,可使用的注入变量如下表所示:
变量名称 | 变量类型 | 描述 |
---|---|---|
ownerClass | java.lang.Class<?> | 该字段所属的对象类型 |
fieldName | java.lang.String | 在对象中,待搜索字段的字段名称 |
fieldClass | java.lang.Class<?> | 待搜索字段的字段类型 |
keyword | java.lang.String | 搜索关键字 |
userContext | grails.plugin.springsecurity.userdetails.GrailsUser | 当前操作的用户信息 |
application | grails.core.GrailsApplication | 当前的 grails 应用上下文 |
log | Closure<?> | 用于打印执行日志的 log 闭包 |
# 返回结果
该客制化代码需要返回的结果为一个 Map 数据类型,该 Map 包含一个 key 为 result, value 为 domain 对象列表的元素,具体返回数据示例如下:
return [
// 包含且仅包含一个 key 为 result 的数组
// Contains and only contains an array with a key of result
'result': [// result 中是一个数组
// 数组中每个元素都是一个 domain 对象
// Each element in the array is a domain object
]
]
2
3
4
5
6
7
# Action 及 Wizard 字段客制化
Action 和 Wizard 中所使用的动态字段也可支持 DynamicFieldHook
客制化,在创建供
Action 和 Wizard 使用的字段客制化 DynamicFieldHook
对象时,需要注意如下几点:
triggerField
应该设置为df_<DynamicFieldDefinition 字段 name 属性>
logicSource
应该设置为DYNAMIC_FIELD
objectType
应设置为空,因为该客制化逻辑不是针对某个对象类型的,而是针对某个 动态字段的。如果通过导入 CSV 方式创建DynamicFieldHook
对象,可以将objectType.shortName
列设置为空。
如下是一个实际项目中的例子,在 用户
这个字段上,定义了名为
// DynamicFieldDefinition.csv
// 定义一个动态字段,其 name 为 "用户"
name(*),fieldType,optionsJson,label,referenceClazz.fullName
用户,OBJECT,,用户,tech.muyan.security.User
// DynamicFieldInstance.csv
// 定义一个动态字段实例,关联到上述 name 为 "用户" 的动态字段定义,
// 其 type 为 "Action parameter field", 关联到 name 为 "BatchAddUserToGroupInUserGroupList" 的 Action
// type,label,displayComponentType,dynamicField.name(*),objectType.shortName(*),action.name,wizardStep.name,editable,display,required,displaySequence
Action parameter field,用户,OBJECT_MULTIPLE_SELECTION,用户,,BatchAddUserToGroupInUserGroupList,,true,true,true,10
// DynamicFieldHook.csv
// 定义一个动态字段客制化,objectType.shortName 为空,其 triggerField 为 "df_用户", logicSource 为 "DYNAMIC_FIELD"
organization.name,objectType.shortName,coreLogic.name,hookType,triggerField,targetField,triggerDynamicField.label,targetDynamicField.label,logicSource,name(*),active,isSystem,description
$ROOT_ORG$,,User search,SEARCH,,df_用户,,,DYNAMIC_FIELD,User search in bulk add user to group action,Y,Y,"批量加用户到用户组 Action 中的用户搜索"
2
3
4
5
6
7
8
9
10
11
12
13
14
15
注意
注意如果多个 Action 或 Wizard 使用了同一个动态字段定义(同一个
DynamicFieldDefinition
),且同时定义了 DynamicFieldHook
,因系统使用
df_<DynamicFieldDefinition Name>
来识别字段客制化,因此系统无法区分不同的
Action 或 Wizard 对应的字段的客制化。
这种情况下,需要创建多个 DynamicFieldDefinition
对象,每个 Action 或 Wizard 的
字段实例请分别基于不同的 DynamicFieldDefinition
定义进行创建。
← 🔑 动态权限控制 🔄 对象生命周期客制化 →