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 有意义)。 *

前端展示时不需要它(用户看到的是多选),但下发给前端便于「提交前预览」如实呈现最终值。 */ private String joinSeparator; /** * {@code widget=file} 时的上传地址(相对站点根),如 {@code /dormnotice/api/base/FileUpload}。 *

模块的附件上传只认浏览器 CAS 会话,必须由前端带用户登录态 POST(multipart), * 拿到 {@code data.fileid} 后作为 fileIds 提交。 */ private String uploadPath; /** {@code widget=file} 时是否允许多个附件 */ private boolean multiple; /** * {@code widget=richtext}(富文本正文)时,「插入图片」的上传地址(**已带模块前缀**的绝对相对路径), * 如 {@code /notice/api/file/UploadImage}。 *

与附件同理:模块的上传接口只认浏览器 CAS 会话,必须由前端带用户登录态 POST(multipart)。 */ private String richTextUploadPath; /** * {@code widget=richtext} 时,正文内图片的预览地址模板(**已带模块前缀**,含 {@code {id}} 占位), * 如 {@code /notice/api/file/Preview?id={id}} —— 前端拿到上传返回的文件 id 后替换占位符, * 得到可直接放进 {@code } 的地址。 */ private String richTextPreviewPath; /** * {@code widget=userpicker}(通知对象)的**数据源页签**,形如 * {@code [{name:"基础平台", call:{...}}, {name:"自定义组", call:{...}}]}。 * *

搬运自通知公告模块页 {@code components/UserCheckModal.vue} 的两个页签, * 每个 {@code call} 都已经是「**带模块前缀**的浏览器代取调用」(同 {@link #optionCall} 的形态), * 由前端用用户登录态去取原始**嵌套树**(不做拍平 —— 拍平会丢掉层级,树就画不出来)。 */ private List pickerSources; /** {@code widget=userpicker} 的一个数据源页签:页签名 + 浏览器代取调用 */ @lombok.Data public static class PickerSource { private String name; private com.yunhai.agent.model.BrowserToolRequest.BrowserCall call; } /** * 「全部」哨兵值(如 {@code ALL})与其显示名(如「全部宿舍」)。 *

前端把它作为多选的第一个选项;用户只勾它 → 提交 {@code ["ALL"]}; * 同时勾了具体项 → 提交时折叠掉它,以具体项为准。 */ private String allToken; /** 「全部」选项的显示名 */ private String allTokenLabel; /** * {@code widget=scopes} 时的节点类型(校园公告 pushScopes): * 1 全校 / 2 学部 / 3 年级 / 4 班级 / 5 训练队 / 6 指定学员。 *

前端把多选结果拼成 {@code [{scopeType,scopeId,scopeName}]}。 */ private Integer scopeType; /** * 「镜像字段」:本字段不由用户填写({@code hidden}),提交时自动填入同一表单中 * {@code mirrorOf} 指定字段**选中项的显示名**。 * *

★ 用途:公告/宿舍/活动三个模块页在提交时会用字典把部门 id 反查成名称一并发出 * ({@code deptId} + {@code deptName},后端字典缺失时回退该名称)。助手只有下拉选中的 id, * 但前端手里就有选项的 label —— 由它顺手镜像过去,提交体才和模块页一模一样。 */ private String mirrorOf; /** * 「连续请假」展开说明:{@code {count: 天数字段, start: 开始时间字段, end: 结束时间字段}}。 * *

作用在请假工具上:当本字段({@code continuousLeave})取 1 时,前端按 * {@code continuousLeaveNum} 天把 {@code startDate}/{@code endDate} 复制成逐天时间段, * 生成模块要的 {@code continuousLeaveTime}({@code [{startDate,endDate}]})—— * 与模块页 {@code LeaveTimeSection.regenerate()} 的口径一致。 */ private Map dailyExpand; /** * {@code widget=file} 且为 true 时,提交的不是 fileId 数组,而是模块要的**附件对象数组**。 * *

作业模块(coursehomework V2)的 {@code attachments} 形如 * {@code [{fileId,fileUrl,fileType,fileName,sort}]},与其余模块的 {@code fileIds="1,2"} 不同, * 故由前端按上传结果组装成对象。 */ private boolean fileAsObject; /** * 单选控件({@code widget=radio})在选项就绪后自动选中第一项。 *

对齐模块页:{@code LeaveApply.vue} 的「请假类型」一进页面就已选中第一个类型。 */ private boolean autoPickFirst; /** * 选项只有一个时自动填入并隐藏本字段。 *

对齐模块页:{@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})。 *

对齐模块页 {@code LeaveTimeSection.quickSet()}:沿用已选日期,只换时分秒。 */ private Map 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})。 *

用于对齐业务模块页的原文案,如请假事由的「请输入请假原因.....」—— * 这里不该塞它的示例值(「发烧就医,需在家休息」),那是给模型看的参考,不是给用户看的提示。 */ private String placeholder; private String defaultValue; /** * 下拉选项的动态来源(字典工具名,如 leave.types)。 *

有值但 {@link #options} 为空 ⇒ 字典接口没取到(多半是下游 302/超时), * 前端据此提示用户「可先手填编号」,而不是干瞪眼。 */ private String optionsFrom; /** * 当前 {@link #options} 是否来自内置兜底(而非学校实时字典)。 *

为 true 时前端提示「用的是标准类型,自定义类型暂时没取到」,而不是静默当成权威值。 */ private boolean optionsFallback; /** 下拉选项 [{value,label}] */ private List> options = new ArrayList<>(); /* ==================== 动态选项(浏览器代取 + 联动) ==================== */ /** * 「浏览器代取选项」的调用描述:业务模块的字典接口(班级、学生名单…)只认浏览器 CAS 会话, * 服务端代取必然 302,所以把地址下发前端,由前端用**用户自己的登录态**取回原始字典再渲染下拉。 * *

地址串里可能含 {@code {{字段名}}} 占位符(见 {@link #dependsOn}), * 前端在依赖字段有值时做替换(值需 URL 编码)后才发起请求。 */ private com.yunhai.agent.model.BrowserToolRequest.BrowserCall optionCall; /** 本字段选项依赖的先序字段名(如「学生」依赖「班级」);依赖未填时前端应清空并提示先选 */ private List dependsOn = new ArrayList<>(); /** 从字典返回对象里取哪个字段作为选项「值」(如 classId / stuId) */ private String valueKey; /** 从字典返回对象里取哪个字段作为选项「显示名」(如 className / stuName) */ private String labelKey; }