package com.yunhai.agent.tool.defs; import com.yunhai.agent.tool.ToolDef; import com.yunhai.agent.tool.ToolParam; import java.util.ArrayList; import java.util.List; /** * 通知公告模块工具 —— 后端 NoticeFront,线上部署前缀 /notice,接口前缀 /api/notice、/api/dict、/api/base。 * *

★ 与同名的「校园公告」({@link CampusNoticeTools},模块 key={@code campusnotice},前缀 /campusnotice) * 不是一个应用:校园公告按「班级」下发、带审批流与草稿箱;本模块(通知公告)按「用户」下发, * 页面只有 收件箱 / 已发送 / 全部通知 / 发通知 / 详情 / 应用设置,没有审批、草稿、推送范围。 * 两者的发布字段截然不同,改这里时不要照搬 CampusNoticeTools。 * *

发布表单逐项对齐 {@code NoticeFront/src/views/notice/NewNotice.vue#doAdd}: * {@code title / content / receiveList / deptId / fileIds / smsFlag / timingSendTime}。 * 其中模块页的 {@code smsFlag} 恒为 0(模板里没有对应控件),故这里不放出该字段。 * *

注意:本模块的用户态接口只认浏览器 CAS 会话,服务端直调会拿到 {@code code=302}, * 凡是需要「取字典给下拉」的字段都用 {@code optionsFrom + optionsKeys} 声明,由前端带用户登录态代取 * (见 AgentOrchestrator#buildForm)。 */ public final class NoticeTools { public static final String MODULE = "notice"; /** 发文部门字典的 type 值({@code /api/dict/List?type=1},与模块页 {@code noticeApi.getSysDic()} 一致) */ private static final String DICT_TYPE_DEPT = "1"; /** 附件上传地址 —— 模块页 NoticeUpload.vue 用的是 {@code /api/base/FileUpload}; * 这里只写**模块内相对路径**,前缀由 AgentOrchestrator.resolveUploadPath 统一拼。 */ private static final String UPLOAD_PATH = "/api/base/FileUpload"; private NoticeTools() { } public static List defs() { List list = new ArrayList<>(); addInbox(list); addPublishLoop(list); addDictionaries(list); return list; } /* ==================== 收件箱(所有用户) ==================== */ private static void addInbox(List list) { list.add(ToolDef.of("notify.received", MODULE, "我收到的通知", "分页查询当前用户收到的通知(返回值含未读条数)。" + "用户问“最近有什么通知”“有没有未读通知”“我还有几条没看”时用它。", "GET", "/api/notice/List") .p(ToolParam.optional("pageIndex", "integer", "页码,从 1 开始").dft("1"), ToolParam.optional("pageSize", "integer", "每页条数,默认 10").dft("10"), ToolParam.optional("keyword", "string", "标题关键词"), ToolParam.optional("readType", "integer", "阅读状态:0 全部 / 1 未读 / 2 已读") .dft("0"), ToolParam.optional("orderByTime", "string", "排序:1 时间倒序 / 2 时间正序") .dft("1")) .readAt("/")); list.add(ToolDef.of("notify.detail", MODULE, "通知详情", "查看一条通知的正文、附件与阅读进度(应读 / 已读人数)。", "GET", "/api/notice/Detail") .p(ToolParam.required("serid", "integer", "通知 id") .label("通知"), ToolParam.optional("type", "integer", "1 查看并标记已读(默认) / 0 仅查看不标读") .dft("1")) .readAt("/")); list.add(ToolDef.of("notify.initializeRed", MODULE, "一键已读全部通知", "把我收到的通知一次性全部标记为已读(模块页「已收到」列表上的一键已读)。", "POST_FORM", "/api/notice/InitializeRed") .write("/")); } /* ==================== 发布闭环(教职工) ==================== */ private static void addPublishLoop(List list) { list.add(ToolDef.of("notify.publish", MODULE, "发布通知", "发布一条通知(可定时发送)。字段与通知公告模块的「发通知」页**完全一致**:" + "通知标题、来源部门、通知对象、附件、正文、定时发送。" + "调用前必须确认:标题 title、来源部门 deptId、通知对象 receiveList;" + "缺失就直接调用本工具,系统会弹出**完整表单**让用户填写,不要用文字反复追问;" + "来源部门必须来自字典,不要编造。" + "通知对象 receiveList 默认「全部用户」;要只发给部分人时由表单多选(用户 id 逗号串)。" + "timingSendTime 非空 = 定时发送。", "POST_JSON", "/api/notice/Create") .p(noticePublishParams()) .fullForm() .write("/list/new")); list.add(ToolDef.of("notify.editNotice", MODULE, "编辑通知", "修改一条已发送的通知(标题、正文、来源部门、通知对象、附件、定时发送)。" + "调用前须确认 sourceId(要改的那条通知 id,可先用 notify.received / notify.sent 查到)。", "POST_JSON", "/api/notice/Edit") .p(ToolParam.required("sourceId", "integer", "要编辑的通知 id") .label("要编辑的通知"), ToolParam.required("title", "string", "通知标题(50 字以内)") .label("通知标题"), ToolParam.optional("content", "string", "通知正文(纯文本)") .label("通知正文").as("textarea"), ToolParam.optional("deptId", "string", "来源部门 id,来自 notify.deptDics 的 k 字段") .label("来源部门") .optionsFrom("notify.deptDics").optionsKeys("k", "v"), ToolParam.optional("receiveList", "array", "通知对象:留空或含全部 = 全部用户") .label("通知对象") .optionsFrom("notify.schoolAll").optionsKeys("id", "name") .join(",").allOption("all", "全部用户"), ToolParam.optional("fileIds", "array", "附件文件 id 列表") .label("添加附件").as("file").multiple(true).join(",") .upload(UPLOAD_PATH), ToolParam.optional("timingSendTime", "string", "定时发送时间 yyyy-MM-dd HH:mm:ss(留空=立即发送)") .label("定时发送")) .write("/list")); list.add(ToolDef.of("notify.sent", MODULE, "我发出的通知", "分页查询我发出(或有权限查看)的通知,可按部门 / 时间段 / 关键词筛选。" + "用户问“我发过哪些通知”“那条通知发了没”时用它。", "GET", "/api/notice/SendList") .p(ToolParam.optional("pageIndex", "integer", "页码").dft("1"), ToolParam.optional("pageSize", "integer", "每页条数").dft("10"), ToolParam.optional("scope", "string", "传 user 只查自己发布的"), ToolParam.optional("keyword", "string", "标题关键词"), ToolParam.optional("deptId", "string", "来源部门 id"), ToolParam.optional("startTime", "string", "开始时间 yyyy-MM-dd").eg("2026-10-01"), ToolParam.optional("endTime", "string", "结束时间 yyyy-MM-dd").eg("2026-10-31"), ToolParam.optional("orderByTime", "string", "排序:1 时间倒序 / 2 时间正序").dft("1")) .readAt("/list")); list.add(ToolDef.of("notify.readDetail", MODULE, "通知阅读情况", "查询某条通知的阅读情况(按组织分组列出已读 / 未读人员)。", "GET", "/api/notice/ReadDetail") .p(ToolParam.required("serid", "integer", "通知 id") .label("通知")) .readAt("/list/detail")); } /* ==================== 字典 / 组织树(发布表单下拉的来源) ==================== */ private static void addDictionaries(List list) { list.add(ToolDef.of("notify.deptDics", MODULE, "发文部门字典", "查询本校本模块可选的来源部门(发布通知时填 deptId 用)。" + "仅在用户明确问「有哪些发文部门」时使用;" + "发布通知时**不要**先调它 —— 表单的「来源部门」下拉会自动带出可选值。", "GET", "/api/dict/List") .fix("type", DICT_TYPE_DEPT) .readAt("/list/new")); list.add(ToolDef.of("notify.schoolAll", MODULE, "组织树(选择通知对象)", "查询本校组织树(部门 → 教师),用于发布通知时选择接收用户。" + "仅在用户明确要「按组织范围选人」时使用;" + "发布通知时**不要**先调它 —— 表单的「通知对象」多选会自动代取该树。", "GET", "/api/base/GetSchoolAll") .readAt("/list/new")); } /** * 「发通知」页的完整字段(发布用)。 * *

★ 与模块页 {@code NewNotice.vue#doAdd} 构造的 {@code NoticeRequest} 逐项对齐: * {@code title / content / receiveList / deptId / fileIds / timingSendTime}。 * 模块页不传 {@code deptName}(校园公告那套才传),故这里也不设镜像字段; * {@code smsFlag} 虽在请求体里,但页面没有对应控件、恒为 0,故不作为表单字段放出。 */ private static ToolParam[] noticePublishParams() { List ps = new ArrayList<>(); ps.add(ToolParam.required("title", "string", "通知标题(50 字以内)") .label("通知标题").eg("关于本周宿舍熄灯时间调整的通知")); ps.add(ToolParam.optional("content", "string", "通知正文(纯文本,对应模块页的富文本编辑器)") .label("通知正文").as("textarea").eg("各位老师:经学校研究决定……")); ps.add(ToolParam.required("deptId", "string", "来源部门 id,来自 notify.deptDics 的 k 字段") .label("来源部门") .optionsFrom("notify.deptDics").optionsKeys("k", "v")); // 模块页的 UserCheckInput:v-model 值为 "all" 或用户 id 逗号串(NOTICE_REQ.receiveList 是 string) ps.add(ToolParam.required("receiveList", "array", "通知对象:留空或含全部 = 全部用户;也可多选具体用户") .label("通知对象") .optionsFrom("notify.schoolAll").optionsKeys("id", "name") .join(",").allOption("all", "全部用户")); ps.add(ToolParam.optional("fileIds", "array", "附件文件 id 列表(由表单的附件上传得到)") .label("添加附件").as("file").multiple(true).join(",") .upload(UPLOAD_PATH)); ps.add(ToolParam.optional("timingSendTime", "string", "定时发送时间 yyyy-MM-dd HH:mm:ss(留空=立即发送)") .label("定时发送").eg("2026-10-15 08:00:00")); return ps.toArray(new ToolParam[0]); } }