build / build-push (push) Successful in 2m1s
前端已把 NoticeFront 的 wangEditor 与自绘「通知对象」控件 1:1 搬进助手表单,
后端补上这两个控件所需的契约:
- ToolParam / FormField 新增:
· pickerSources:选人弹窗的**数据源页签**(形如 {name, 模块内相对地址}),
由 AgentOrchestrator 拼上模块前缀后下发(沿用「只写相对路径」的铁律);
· richTextUploadPath / richTextPreviewPath:正文插图的上传与预览地址。
- AgentOrchestrator:把 resolveUploadPath 抽出通用形态 resolveModulePath(幂等、只接受
模块内相对路径);buildForm 为 widget=userpicker 下发 pickerSources;
optionCall 强制改写 widget 的白名单放行 userpicker(否则会被降级成 select)。
- NoticeTools:notify.publish 与 notify.editNotice 的 receiveList 改为 userpicker,
两个数据源为「基础平台」/api/base/GetSchoolAll 与「自定义组」
/api/contacts/GetGroupTree?type=1;content 走 richtext。
测试:NotifyIntentFormTest / IntentDirectFormTest 补 pickerSources 断言
(**只写相对路径**,不带前缀 —— 前缀拼错是本项目反复出过的坑),
ModuleFormParityTest 补 userpicker 取值口径断言(字符串原样透传,不得包成数组)。
mvn -o test:Tests run: 135, Failures: 0, Errors: 0,BUILD SUCCESS。
205 lines
8.8 KiB
Java
205 lines
8.8 KiB
Java
package com.yunhai.agent.tool;
|
||
|
||
import lombok.Data;
|
||
|
||
import java.util.ArrayList;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
|
||
/**
|
||
* 表单里的一个字段:由 {@link ToolParam} 生成,前端据此选择控件(下拉 / 日期时间 / 多行文本…)。
|
||
*/
|
||
@Data
|
||
public class FormField {
|
||
|
||
/** 提交时的参数名(与业务接口一致) */
|
||
private String name;
|
||
|
||
/** 中文标签,如「开始时间」 */
|
||
private String label;
|
||
|
||
/** 控件类型:text / textarea / number / date / datetime / select / multiselect / file / scopes / flow / radio / switch / richtext / userpicker */
|
||
private String widget;
|
||
|
||
/** 数据类型:string / integer / number / boolean / array */
|
||
private String type;
|
||
|
||
/**
|
||
* 数组值的序列化分隔符(仅 POST_JSON 有意义)。
|
||
* <p>前端展示时不需要它(用户看到的是多选),但下发给前端便于「提交前预览」如实呈现最终值。
|
||
*/
|
||
private String joinSeparator;
|
||
|
||
/**
|
||
* {@code widget=file} 时的上传地址(相对站点根),如 {@code /dormnotice/api/base/FileUpload}。
|
||
* <p>模块的附件上传只认浏览器 CAS 会话,必须由前端带用户登录态 POST(multipart),
|
||
* 拿到 {@code data.fileid} 后作为 fileIds 提交。
|
||
*/
|
||
private String uploadPath;
|
||
|
||
/** {@code widget=file} 时是否允许多个附件 */
|
||
private boolean multiple;
|
||
|
||
/**
|
||
* {@code widget=richtext}(富文本正文)时,「插入图片」的上传地址(**已带模块前缀**的绝对相对路径),
|
||
* 如 {@code /notice/api/file/UploadImage}。
|
||
* <p>与附件同理:模块的上传接口只认浏览器 CAS 会话,必须由前端带用户登录态 POST(multipart)。
|
||
*/
|
||
private String richTextUploadPath;
|
||
|
||
/**
|
||
* {@code widget=richtext} 时,正文内图片的预览地址模板(**已带模块前缀**,含 {@code {id}} 占位),
|
||
* 如 {@code /notice/api/file/Preview?id={id}} —— 前端拿到上传返回的文件 id 后替换占位符,
|
||
* 得到可直接放进 {@code <img src>} 的地址。
|
||
*/
|
||
private String richTextPreviewPath;
|
||
|
||
/**
|
||
* {@code widget=userpicker}(通知对象)的**数据源页签**,形如
|
||
* {@code [{name:"基础平台", call:{...}}, {name:"自定义组", call:{...}}]}。
|
||
*
|
||
* <p>搬运自通知公告模块页 {@code components/UserCheckModal.vue} 的两个页签,
|
||
* 每个 {@code call} 都已经是「**带模块前缀**的浏览器代取调用」(同 {@link #optionCall} 的形态),
|
||
* 由前端用用户登录态去取原始**嵌套树**(不做拍平 —— 拍平会丢掉层级,树就画不出来)。
|
||
*/
|
||
private List<PickerSource> pickerSources;
|
||
|
||
/** {@code widget=userpicker} 的一个数据源页签:页签名 + 浏览器代取调用 */
|
||
@lombok.Data
|
||
public static class PickerSource {
|
||
private String name;
|
||
private com.yunhai.agent.model.BrowserToolRequest.BrowserCall call;
|
||
}
|
||
|
||
/**
|
||
* 「全部」哨兵值(如 {@code ALL})与其显示名(如「全部宿舍」)。
|
||
* <p>前端把它作为多选的第一个选项;用户只勾它 → 提交 {@code ["ALL"]};
|
||
* 同时勾了具体项 → 提交时折叠掉它,以具体项为准。
|
||
*/
|
||
private String allToken;
|
||
|
||
/** 「全部」选项的显示名 */
|
||
private String allTokenLabel;
|
||
|
||
/**
|
||
* {@code widget=scopes} 时的节点类型(校园公告 pushScopes):
|
||
* 1 全校 / 2 学部 / 3 年级 / 4 班级 / 5 训练队 / 6 指定学员。
|
||
* <p>前端把多选结果拼成 {@code [{scopeType,scopeId,scopeName}]}。
|
||
*/
|
||
private Integer scopeType;
|
||
|
||
/**
|
||
* 「镜像字段」:本字段不由用户填写({@code hidden}),提交时自动填入同一表单中
|
||
* {@code mirrorOf} 指定字段**选中项的显示名**。
|
||
*
|
||
* <p>★ 用途:公告/宿舍/活动三个模块页在提交时会用字典把部门 id 反查成名称一并发出
|
||
* ({@code deptId} + {@code deptName},后端字典缺失时回退该名称)。助手只有下拉选中的 id,
|
||
* 但前端手里就有选项的 label —— 由它顺手镜像过去,提交体才和模块页一模一样。
|
||
*/
|
||
private String mirrorOf;
|
||
|
||
/**
|
||
* 「连续请假」展开说明:{@code {count: 天数字段, start: 开始时间字段, end: 结束时间字段}}。
|
||
*
|
||
* <p>作用在请假工具上:当本字段({@code continuousLeave})取 1 时,前端按
|
||
* {@code continuousLeaveNum} 天把 {@code startDate}/{@code endDate} 复制成逐天时间段,
|
||
* 生成模块要的 {@code continuousLeaveTime}({@code [{startDate,endDate}]})——
|
||
* 与模块页 {@code LeaveTimeSection.regenerate()} 的口径一致。
|
||
*/
|
||
private Map<String, String> dailyExpand;
|
||
|
||
/**
|
||
* {@code widget=file} 且为 true 时,提交的不是 fileId 数组,而是模块要的**附件对象数组**。
|
||
*
|
||
* <p>作业模块(coursehomework V2)的 {@code attachments} 形如
|
||
* {@code [{fileId,fileUrl,fileType,fileName,sort}]},与其余模块的 {@code fileIds="1,2"} 不同,
|
||
* 故由前端按上传结果组装成对象。
|
||
*/
|
||
private boolean fileAsObject;
|
||
|
||
/**
|
||
* 单选控件({@code widget=radio})在选项就绪后自动选中第一项。
|
||
* <p>对齐模块页:{@code LeaveApply.vue} 的「请假类型」一进页面就已选中第一个类型。
|
||
*/
|
||
private boolean autoPickFirst;
|
||
|
||
/**
|
||
* 选项只有一个时自动填入并隐藏本字段。
|
||
* <p>对齐模块页:{@code LeaveApply.vue} 的「班级」用 {@code v-if="myClasses.length > 1"}`,
|
||
* 单班时不渲染、值直接带上。
|
||
*/
|
||
private boolean hideWhenSingleOption;
|
||
|
||
/**
|
||
* 快捷时段按钮组:{@code {start: 开始字段名, end: 结束字段名,
|
||
* items: [{label, start, end}]}};{@code start/end} 为时刻({@code HH:mm:ss})。
|
||
* <p>对齐模块页 {@code LeaveTimeSection.quickSet()}:沿用已选日期,只换时分秒。
|
||
*/
|
||
private Map<String, Object> quickPeriods;
|
||
|
||
/** {@code widget=switch} 勾选时提交的值 */
|
||
private String switchOn;
|
||
|
||
/** {@code widget=switch} 未勾选时提交的值 */
|
||
private String switchOff;
|
||
|
||
/** 多行文本框显示行数 */
|
||
private Integer rows;
|
||
|
||
/** 文本最大长度(前端显示字数统计) */
|
||
private Integer maxLength;
|
||
|
||
private boolean required;
|
||
|
||
/** 说明(来自工具定义,写清格式与含义) */
|
||
private String description;
|
||
|
||
/** 格式示例,前端用作 placeholder,如 2026-10-11 08:00:00 */
|
||
private String example;
|
||
|
||
/**
|
||
* 显式的占位提示(优先于 {@link #example})。
|
||
* <p>用于对齐业务模块页的原文案,如请假事由的「请输入请假原因.....」——
|
||
* 这里不该塞它的示例值(「发烧就医,需在家休息」),那是给模型看的参考,不是给用户看的提示。
|
||
*/
|
||
private String placeholder;
|
||
|
||
private String defaultValue;
|
||
|
||
/**
|
||
* 下拉选项的动态来源(字典工具名,如 leave.types)。
|
||
* <p>有值但 {@link #options} 为空 ⇒ 字典接口没取到(多半是下游 302/超时),
|
||
* 前端据此提示用户「可先手填编号」,而不是干瞪眼。
|
||
*/
|
||
private String optionsFrom;
|
||
|
||
/**
|
||
* 当前 {@link #options} 是否来自内置兜底(而非学校实时字典)。
|
||
* <p>为 true 时前端提示「用的是标准类型,自定义类型暂时没取到」,而不是静默当成权威值。
|
||
*/
|
||
private boolean optionsFallback;
|
||
|
||
/** 下拉选项 [{value,label}] */
|
||
private List<Map<String, String>> options = new ArrayList<>();
|
||
|
||
/* ==================== 动态选项(浏览器代取 + 联动) ==================== */
|
||
|
||
/**
|
||
* 「浏览器代取选项」的调用描述:业务模块的字典接口(班级、学生名单…)只认浏览器 CAS 会话,
|
||
* 服务端代取必然 302,所以把地址下发前端,由前端用**用户自己的登录态**取回原始字典再渲染下拉。
|
||
*
|
||
* <p>地址串里可能含 {@code {{字段名}}} 占位符(见 {@link #dependsOn}),
|
||
* 前端在依赖字段有值时做替换(值需 URL 编码)后才发起请求。
|
||
*/
|
||
private com.yunhai.agent.model.BrowserToolRequest.BrowserCall optionCall;
|
||
|
||
/** 本字段选项依赖的先序字段名(如「学生」依赖「班级」);依赖未填时前端应清空并提示先选 */
|
||
private List<String> dependsOn = new ArrayList<>();
|
||
|
||
/** 从字典返回对象里取哪个字段作为选项「值」(如 classId / stuId) */
|
||
private String valueKey;
|
||
|
||
/** 从字典返回对象里取哪个字段作为选项「显示名」(如 className / stuName) */
|
||
private String labelKey;
|
||
}
|