package com.yunhai.agent.tool; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import lombok.Data; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * 工具参数定义。一份声明,三处复用: * *
有了它,下游字典接口不通时下拉框仍然是下拉框,而不是退化成让用户填「请假类型编号」的数字框。
* 与 {@link #enumValues} 的区别:enumValues 是权威定义(如审批结论),本字段是「大概是这样,以实时字典为准」。
*/
private List ★ 为什么需要:模块的很多字段在 wire 上是「逗号串」而不是 JSON 数组
* (如宿舍通知 {@code receiveList="1,2,3"}、附件 {@code fileIds="11,12"}),
* 但表单里用户是多选的(天然是数组)。声明 {@code .join(",")} 后,执行器会在发请求前
* 把数组拼成模块要的字符串,避免"页面里选得到、提交后端判非法"。
*/
private String joinSeparator;
/** {@code widget=file} 时是否允许多个附件(缺省 true);对齐模块页 AttachmentUpload 的 multiple */
private Boolean multiple;
/**
* 「全部」哨兵值。
*
* ★ 用途:模块的接收对象多选里都有一个「全部班级 / 全部宿舍」开关,wire 上是 {@code "ALL"}。
* 声明后:① 表单把它作为第一个选项摆进多选;② 提交时若用户既没选具体项、又没选它,
* 视作「全部」(保持"默认发全体"的语义);③ 若同时选了具体项与它,以**具体项**为准(折叠掉它)。
*/
private String allToken;
/** 「全部」选项在表单里显示的中文名,如「全部班级」/「全部宿舍」 */
private String allTokenLabel;
/**
* 「推送范围」控件(widget=scopes)的节点类型。
*
* 校园公告的接收对象 {@code pushScopes} 在 wire 上是
* {@code [{scopeType,scopeId,scopeName}]}(1 全校 / 2 学部 / 3 年级 / 4 班级 / 5 训练队 / 6 指定学员)。
* 声明后前端把多选结果拼成这个对象数组;选「全部」则发出 {@code [{scopeType:1,scopeId:0,scopeName:"全部班级"}]}。
*/
private Integer scopeType;
/** 是否在表单里隐藏(如由系统固定填入的参数,用户无需看见) */
private boolean hidden;
/**
* 该字段的下拉选项来自哪个只读工具(如 {@code leave.types})。
* 服务端在生成表单时会先执行这个工具,把返回的字典项填成下拉选项 ——
* 用户就不用去猜「病假是几号」了。执行失败则回退为普通输入框,不影响主流程。
*/
private String optionsFrom;
/**
* 选项取自字典返回对象的哪个字段(如 {@code classId} / {@code stuId})。
*
* ★ 为什么必须显式声明:模块字典返回的是业务 VO({@code TeachingClassVo{classId,className}}、
* {@code ClassStudentVo{stuId,stuName}}),字段名五花八门,靠「猜常见键名」不可靠。
* 显式声明后,前端在浏览器里取到原始数据也能**确定性地**映射成下拉项。
*/
private String optionValueKey;
/** 选项显示名取自字典返回对象的哪个字段(如 {@code className} / {@code stuName}) */
private String optionLabelKey;
/**
* 本字段的选项依赖哪些其它字段(如「学生」下拉依赖先选好「班级」)。
*
* 作用有两个:① 生成表单时把依赖名带给前端,前端据此清空/重新拉取选项;
* ② 生成「浏览器代取选项」的地址时,把依赖字段位置留成占位符({@code ?classId={{classId}}})。
*/
private List 实时字典取到 → 用实时值;取不到 → 用这份兜底,下拉框不至于消失。
*/
public ToolParam fallbackOptions(String[] values, String[] labels) {
this.fallbackValues = Arrays.asList(values);
this.fallbackLabels = Arrays.asList(labels);
return this;
}
public ToolParam dft(String value) {
this.defaultValue = value;
return this;
}
/** 字段中文标签 */
public ToolParam label(String value) {
this.label = value;
return this;
}
/** 示例值(帮助用户按格式填写) */
public ToolParam eg(String value) {
this.example = value;
return this;
}
/** 指定控件类型 */
public ToolParam as(String value) {
this.widget = value;
return this;
}
public ToolParam hide() {
this.hidden = true;
return this;
}
/** 下拉选项来自某个只读字典工具 */
public ToolParam optionsFrom(String tool) {
this.optionsFrom = tool;
return this;
}
/**
* 字典返回对象里,取哪个字段做「值」、哪个字段做「显示名」。
* 例:{@code optionsKeys("classId","className")} 对应 {@code {classId,className}} 这种业务 VO。
*/
public ToolParam optionsKeys(String valueKey, String labelKey) {
this.optionValueKey = valueKey;
this.optionLabelKey = labelKey;
return this;
}
/** 本字段的选项依赖的先序字段(用于联动下拉,如「学生」依赖「班级」) */
public ToolParam dependsOn(String... fieldNames) {
this.dependsOn = Arrays.asList(fieldNames);
return this;
}
/**
* 数组值序列化分隔符:{@code POST_JSON} 时把多选的数组拼成模块要的「逗号串」。
* 例:{@code optional("fileIds","array",...).join(",")} → {@code "11,12,13"}。
*/
public ToolParam join(String separator) {
this.joinSeparator = separator;
return this;
}
/** 附件是否可多选(仅 {@code widget=file} 有意义) */
public ToolParam multiple(boolean value) {
this.multiple = value;
return this;
}
/**
* 「全部」哨兵值(如 {@code "ALL"}):表单会把它作为多选的第一个选项,
* 提交时与具体项互斥(见 {@link #allToken} 字段说明)。
*/
public ToolParam allOption(String token, String label) {
this.allToken = token;
this.allTokenLabel = label;
return this;
}
/**
* 声明为「推送范围」多选:提交时把选中的节点拼成
* {@code [{scopeType,scopeId,scopeName}]}(校园公告的 {@code pushScopes})。
*
* @param type 节点类型:1 全校 / 2 学部 / 3 年级 / 4 班级 / 5 训练队 / 6 指定学员
*/
public ToolParam scopes(int type) {
this.widget = "scopes";
this.scopeType = type;
return this;
}
/** 表单里显示的名字 */
public String labelOf() {
return StringUtils.isNotBlank(label) ? label : name;
}
/** 当前生效的枚举值:权威枚举优先,其次内置兜底 */
private List 参数名里的 date/time 是强信号(startDate / endDate / leaveTime),
* 文本类词根(remarks / reason / content)则渲染成多行输入。
*/
public String resolveWidget() {
if (StringUtils.isNotBlank(widget)) return widget;
List
*
*/
private String widget;
/**
* 数组值的序列化分隔符(仅 {@code POST_JSON} 生效)。
*
*