feat(form): 发布表单字段与各模块「新建发布」页完全对齐
build / build-push (push) Successful in 54s

原来助手表单只渲染"缺失的必填项",且控件只有文本/单选/时间 —— 与各模块
自己的发布页相比缺了接收对象多选、附件、定时发送、置顶、紧急程度、回执单据、
发布方式等字段,用户没法"填得一样"。

补齐一层共享的表单能力,再把三个发布工具按模块页逐字段重写:

共享能力
- ToolParam: 新增 join(分隔符)/multiple/allOption(哨兵)/scopes(推送范围),
  array 类型有选项来源时自动用多选控件;数组 schema 补 items
- ToolDef: 新增 fullForm() —— 表单渲染全部字段(不再只渲染缺失必填)
- ToolExecutor: POST_JSON 前按 joinSeparator 把数组拼成模块要的逗号串;
  allToken 与具体项互斥(只选具体项→拼串,否则发哨兵)
- AgentOrchestrator: file 控件补 uploadPath(/<模块>/api/base/FileUpload)
- 模板/参数文件同步下发 joinSeparator/uploadPath/multiple/allToken/scopeType

三个发布工具
- notice.publish: 补重要程度/接收班级(pushScopes 对象数组)/置顶/短信/阅读回执/
  附件/定时发送/发布方式;新增 notice.stuTree(组织树)
- dorm.publish: 补紧急程度/生效时间/回执截止/回执单据/接收宿舍(多选,ALL)/
  宿管配合/置顶/短信/附件/定时发送/存草稿
- activity.publish: 补报名截止/回执方式三档/名额上限/家长随行/活动费用/
  接收班级(多选,ALL)/短信/附件/定时发送/存草稿
- leave.submit/register: 连续请假改为可填参数,补连续天数与佐证材料附件

测试:发布工具契约 + 表单字段与模块页一致性断言;90 tests 全绿
This commit is contained in:
lijing
2026-10-12 22:05:09 +08:00
parent b6a8a63b0e
commit 322d589b3e
13 changed files with 456 additions and 64 deletions
@@ -648,8 +648,25 @@ public class AgentOrchestrator {
List<FormField> fields = new ArrayList<>();
List<String> missingLabels = new ArrayList<>();
for (ToolParam p : missing) {
// ★ 表单包含哪些字段:发布类工具(fullForm)渲染**全部字段**,与模块自己的「新建/发布」页一致
// (置顶 / 短信 / 定时发送 / 附件 / 回执方式等可选项也摆出来,否则用户设不了、发出去的数据与模块页不同);
// 其余工具仍只补「缺失的必填项」。
List<ToolParam> shown = new ArrayList<>();
if (def.isFormAll()) {
for (ToolParam p : def.getParams()) {
if (!p.isHidden()) {
shown.add(p);
}
}
} else {
shown.addAll(missing);
}
for (ToolParam p : shown) {
FormField f = p.toFormField();
// 附件控件:补上传地址(模块的附件上传只认浏览器 CAS 会话,必须前端带登录态 POST)
if ("file".equals(f.getWidget())) {
f.setUploadPath("/" + def.getModule() + "/api/base/FileUpload");
}
// 字典类字段(如请假类型、班级、学生):优先交给前端用**用户自己的登录态**代取。
//
// ★ 为什么不在这里直接执行字典工具:业务模块的用户态接口只认浏览器 CAS 会话,
@@ -660,7 +677,10 @@ public class AgentOrchestrator {
BrowserToolRequest.BrowserCall optionCall = buildOptionCall(p, caller);
if (optionCall != null) {
f.setOptionCall(optionCall);
f.setWidget("select");
// 多选字段保持 multiselect(接收班级/宿舍/审批人),其余走单选下拉
if (!"multiselect".equals(f.getWidget())) {
f.setWidget("select");
}
// 有内置兜底的(如请假类型 病假/事假)先摆上,前端取到学校实时字典后覆盖
f.setOptionsFallback(p.isUsingFallbackOptions());
} else if (StringUtils.isNotBlank(p.getOptionsFrom())) {
@@ -673,10 +693,13 @@ public class AgentOrchestrator {
}
}
fields.add(f);
}
for (ToolParam p : missing) {
missingLabels.add(p.labelOf());
}
form.setFields(fields);
form.setMissing(missingLabels);
form.setAllFields(def.isFormAll());
// 已从对话里确定的参数 → 表单里只读回显(值翻译成中文,用户能一眼核对)
Map<String, Object> preset = new LinkedHashMap<>();