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;
/** 「镜像字段」指向的源字段名(隐藏字段,提交时填入源字段选中项的显示名,如 deptId → deptName) */
private String mirrorLabelOf;
/** 「连续请假」展开说明:{@code {count,start,end}} 三个字段名(见 {@link #expandDaily}) */
private Map 服务端在生成表单时会先执行这个工具,把返回的字典项填成下拉选项 ——
* 用户就不用去猜「病假是几号」了。执行失败则回退为普通输入框,不影响主流程。
*/
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;
}
/**
* 声明为「审批流」控件(校园公告的 {@code flow})。
*
* 表单会渲染成:是否走审批 + 多步(每步可选或签/会签)+ 每步多选审批人
* (候选人来自 {@link #optionsFrom} 指向的教师树工具);提交时前端拼成
* {@code [{name:"第1步",vote:"any"|"all",approverTeaIds:[...]}]}。
*/
public ToolParam flow() {
this.widget = "flow";
return this;
}
/**
* 声明本字段为**隐藏的镜像字段**:用户不填,提交时自动带上源字段选中项的显示名。
*
* 用于 {@code deptName} 这类「模块页会随包提交、但界面上没有对应输入框」的字段:
* 三个模块页都是「选部门 → 用字典把 id 反查成名称 → 一起提交」。
*/
public ToolParam mirrorLabelOf(String sourceField) {
this.mirrorLabelOf = sourceField;
return this;
}
/**
* 连续请假展开:本字段取 1 时,前端按 {@code countField} 的天数把
* {@code startField}/{@code endField} 复制成逐天时间段,生成 {@code continuousLeaveTime}。
*
* @param countField 连续天数字段名(如 continuousLeaveNum)
* @param startField 基准开始时间字段名(如 startDate)
* @param endField 基准结束时间字段名(如 endDate)
*/
public ToolParam expandDaily(String countField, String startField, String endField) {
this.dailyExpandSpec = new LinkedHashMap<>();
this.dailyExpandSpec.put("count", countField);
this.dailyExpandSpec.put("start", startField);
this.dailyExpandSpec.put("end", endField);
return this;
}
/** 附件控件提交**对象数组**(作业模块 {@code attachments:[{fileId,fileUrl,fileType,fileName,sort}]}) */
public ToolParam asAttachments() {
this.widget = "file";
this.fileObject = true;
return this;
}
/** 覆盖附件上传地址(缺省由编排层按 {@code /<模块>/api/base/FileUpload} 兜底) */
public ToolParam upload(String path) {
this.uploadPathOverride = path;
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} 生效)。
*
*