Files
yunhai-agent-service/docs/模块能力盘点与工具补全建议.md
T
lijing c16356db6e
build / build-push (push) Successful in 1m2s
feat(form): 发布表单与各模块「新建发布」页逐字段对齐(含审批流与作业两步发布)
把 5 个模块「新建/发布」页的字段与提交体逐个核对后,补齐此前缺失或形态不符的部分:

- 部门名镜像:公告/宿舍/活动三处在模块页是「字典 id + 反查名称」一起提交。
  新增 mirrorLabelOf 机制 —— 该字段不渲染控件、不进模型 schema,提交时按源字段
  选中项的显示名填充(deptName)
- 公告审批流:新增 flow 控件(widget=flow)+ 教师组织树字典 notice.teaTree,
  提交 flow:[{name,vote,approverTeaIds}];publishType 值域修正为 direct/approve;
  存草稿改用模块自己的独立接口 notice.saveDraft(POST /api/notice/SaveDraft)
- 活动分类:category 提交的是**分类名称**(模块页 :value="String(c.v)"),
  optionsKeys 由 ("k","v") 改为 ("v","v")
- 请假附件:fileIds 在 HomeLeave 是 number[](JSON 数组),去掉逗号串拼接;
  连续请假按 continuousLeaveNum 逐天展开成 continuousLeaveTime[](服务端/浏览器同源)
- 作业发布(整块新增):homework.publish / schedule / published / drafts /
  teachingSubjects / classTree。模块没有一步发布的接口,用 ToolDef.then 声明两步提交
  (Create 落草稿 → Publish | Schedule);附件是对象数组
  attachments:[{fileId,fileUrl,fileType,fileName,sort}],上传走 file 域
  /coursehomework/api/file/Upload
- 执行器:新增 followUp 两步提交(浏览器侧同源)、数组参数给标量时包成单元素数组、
  首步响应 data 取资源 id(对象形态取 .id,避免拼出 /homeworks/{"id":1}/publish)
- 新增 ModuleFormParityTest(13 项)逐条锁住上述契约

回归:mvn clean package → Tests run: 103, Failures: 0, Errors: 0, BUILD SUCCESS
真机:headless Chrome 走「表单 → 浏览器代执行 → 模块接口」,50 项断言全绿
2026-10-13 11:29:03 +08:00

455 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 校园智能助手 · 五模块能力盘点与工具补全建议
> 盘点日期:2026-10-12 范围:AgentService 已对接的 5 个业务模块(请假 / 校园公告 / 宿舍通知 / 家校活动 / 课程作业)
> 方法:逐模块通读前端 `src/api/module/*.ts` + `src/router/routes.ts` + 权限守卫,与 AgentService 现有 `tool/defs/*.java` 做差集。
> 目标:① 说清每个模块的**功能与权限**怎么划;② 指出**哪些写操作可以做成表单**;③ 挖出**模块有、助手还没接**的能力。
---
## 0. 结论速览
| 模块 | 已接工具 | 建议新增 | 一句话结论 |
|---|---:|---:|---|
| `homeleave` 请假 | 12 | **+6** | 最完整的一个,缺的是「删除 / 类型字典维护 / 按班查列表」 |
| `campusnotice` 校园公告 | 9 | **+14** | 发得出,但**收不回**——撤回/置顶/催读/阅读情况全缺 |
| `dormnotice` 宿舍通知 | 9 | **+18** | 家长侧齐了,**管理侧几乎空白**;模块还自带一整套「床位上床台账」完全没接 |
| `familyactivity` 家校活动 | 7 | **+13** | 家长报名齐了,**学校侧(发活动 / 确认报名)全缺** |
| `coursehomework` 课程作业 | 4 | **+20** | 缺口最大:**只接了学生端,教师端一整套能力 0 接入** |
| 合计 | **41** | **+71** | — |
| (`profession` 教师专业发展,教师专属,附带参考) | 9 | +12 | 只缺「编辑/删除记录、评论点赞、统计报表」 |
**一句话总纲**:四个「发布型」模块(公告 / 宿舍 / 活动 / 作业)其实是**同一套骨架**——
`我发布的 → 全部 → 详情 → 新建/编辑 → 回执或提交名单 → 统计 → 应用设置`,
再各自加一个 `/par/*` 家长端或 `/stu/*` 学生端。
助手目前普遍只做了**「读列表 + 我提交」**这一层,
而骨架里价值最高的**「发布 / 撤回 / 盯回执 / 批阅 / 催办」**基本没接。
---
## 1. 先说权限:能力边界的四层判定
### 1.1 助手侧(AgentService)
| 层 | 组件 | 作用 |
|---|---|---|
| 1 | `RoleResolver` | 只读平台 `BoaUser`,**只认「当前身份」`currentRole`**(不做并集)→ 教师 / 学生 / 家长 / 管理员 / 访客 |
| 2 | `ToolRegistry` | 只把**本身份可见**的工具写进模型 schema —— 模型"看不见"就不会选错 |
| 3 | `ToolExecutor` | 调用前再校验一次,越权直接拒绝并打 `[越权拦截]` |
| 4 | 浏览器代执行(A 通道) | 写操作由**前端带用户登录态**执行,服务端不代持身份 |
`RolePolicy` 现状(一张集中表):
- `STAFF_ONLY`(教师+管理员):请假登记/审批/统计/批量通过、公告已发/发文部门/发布/审批、宿舍楼栋房间、活动班级树
- `STUDENT_ONLY`:作业四件套、请假申请、销假
- `STUDENT_OR_PARENT`:活动报名 / 取消报名
- `SHARED`:撤销请假
- **未列入任何名单 = 所有已识别角色都可用**
> ★★ **最大风险点**:新增工具若忘了登记,**默认是"人人可用"**。
> 例如把 `homework.review`(教师打分)漏登记,学生就会在目录里看到"给作业打分"。
> 本报告第 5 节给出建议分桶表,实施时必须同步改 `RolePolicy`。
### 1.2 模块侧(5 个前端怎么分权限)
结论:**五个模块的前端都几乎不做基于 `roleId` 的业务判定**,统一靠三件事:
1. **平台菜单授权 `GET /api/base/GetUserMod`** —— 决定"能不能进这个入口页"(`modId` 是稳定契约);
2. **URL 前缀** —— `/par/*` = 家长端、`/stu/*` = 学生端,其余 = 教职工端(`roleOfPath()`);
3. **后端数据范围** —— 同一接口按身份收敛(学生看自己 / 教师看任教班 / 管理员看全校)。
| 模块 | 端分离方式 | 入口 `modId` 契约 |
|---|---|---|
| homeleave | 纯菜单(无 URL 前缀) | 2 申请 / 3 我的 / 4 审批 / 5 登记 / 6 统计 / 7 设置 |
| campusnotice | 纯菜单 | 1 我的公告 / 2 已发管理 / 3 全部公告 / 4 统计 / 5 审批中心 / 21 设置 |
| dormnotice | `/par/*` | 2 我发的 / 3 全部 / 25 回执情况 / 4 统计 / 21 设置 ‖ **31 通知箱 / 32 我的回执** |
| familyactivity | `/par/*` | 2 我发的 / 3 全部 / 25 报名情况 / 4 统计 / 21 设置 ‖ **31 活动广场 / 32 我的报名** |
| coursehomework | `/stu/*` | 教师端无 `/stu` 前缀(列表/详情/提交情况/批改中心/统计);学生端 今日作业 / 待订正 / 详情 |
> 注意:`/list/new`、`/list/detail`、`/signups`、`/grade` 这类**二级落地页一律不判权限**(只要路由声明了 `meta.parentId`)。
> 所以"有权限发公告"≠"有权限进审批中心",两者是不同 `modId`。
---
## 2. 逐模块盘点
图例:**角色** = 建议归属(学/教/管 = 学生/教师/管理员,家 = 家长);**类型** = 只读 / 单参写 / 表单写。
`⭐` = 高频高价值,建议优先。
### 2.1 `homeleave` 请假 —— 已接 12,建议 +6
**已接**:`leave.types` `myClasses` `classStudents` `myLeaves` `detail` `stats`
`submit`(学生申请) `register`(教师登记) `withdraw` `reportBack` `audit` `batchPass`
**模块能力全景**(`/api/leave/*` + `/api/dict/*` + `/api/leavestats/*`):
| 页面 | 角色 | 能力 |
|---|---|---|
| `/apply` 请假申请 | 学生 | 选班级/类型/时间/事由/附件 → 提交 |
| `/my` 我的请假 | 学生 | 列表+筛选、详情、撤销、申请销假 |
| `/audit` 请假审批 | 教师/管理员 | 列表、批量通过、本班一键通过、通过/拒绝、撤销审批、销假审批、删除 |
| `/register` 请假登记 | 教师/管理员 | 选班级→学生→类型→时间 → 免审登记 |
| `/stats` 请假统计 | 教师/管理员 | 时间/班级/类型筛选 + 分布 |
| `/setting` 应用设置 | 管理员 | **请假类型字典**增删改启停排序 |
**建议新增:**
| 建议工具 | 接口 | 角色 | 类型 | 说明 |
|---|---|---|---|---|
| `leave.delete` ⭐ | `POST /api/leave/Delete?id=` | 教/管 | 单参写 | 只允许删「已撤销/已销假/已拒绝」;补全审批页的删除 |
| `leave.typeList` | `GET /api/dict/LeaveTypeList` | 管 | 只读 | 含**停用**项的管理列表(现有 `leave.types` 只有启用项) |
| `leave.typeAdd` | `POST /api/dict/LeaveTypeAdd?name=` | 管 | 表单写 | "加一个『竞赛假』" |
| `leave.typeUpdate` | `POST /api/dict/LeaveTypeUpdate?serid=&name=&enable=` | 管 | 表单写 | 改名 / 启停 |
| `leave.typeDelete` | `POST /api/dict/LeaveTypeDelete?serid=` | 管 | 单参写 | 被引用时后端会拒绝(要有友好提示) |
| `leave.typeSort` | `POST /api/dict/LeaveTypeSort?ids=` | 管 | 单参写 | 调整下拉顺序 |
| (扩展)`leave.myLeaves` | 同上,**加** `classId/stuId/type` 参数 | 教/管 | 只读 | 现在教师问"三班今天有谁请假"没法筛 |
> 说明:`leave.stats` / `audit` / `batchPass` 已有,是五个模块里做得最完整的一个。
---
### 2.2 `campusnotice` 校园公告 —— 已接 9,建议 +14
**已接**:`notice.received` `readTabCount` `sent` `detail` `deptDics` `publish` `approvalList` `approve` `reject`
**模块能力全景**:
| 页面 | 角色 | 能力 |
|---|---|---|
| `/` 我的公告 | 全员 | 收件箱、未读/已读筛选、一键已读、上/下篇、打印、附件 |
| `/list` 已发管理 | 发布者 | 撤回 / 置顶 / **催读** / 转发 / 编辑重提 / 删除 / **阅读回执** |
| `/list/new` 写公告 | 发布者 | 富文本+附件+置顶+短信+**阅读回执**+定时发送;直接发布 / 存草稿 / 提交审批 |
| `/drafts` 草稿箱 | 发布者 | 继续编辑、提交审批、删除 |
| `/noticelist` 全部公告 | 管理员 | 全量查询 + 删除 |
| `/approval` 审批中心 | 审批人 | 待审/已通过/已驳回/全部 + 通过/驳回 + 审批轨迹 |
| `/setting` 应用设置 | 管理员 | **发文部门字典** + **自定义联系组** |
**建议新增:**
| 建议工具 | 接口 | 角色 | 类型 | 说明 |
|---|---|---|---|---|
| `notice.revoke` ⭐ | `POST /api/notice/UserPull?serid=` | 教/管 | 单参写 | "发错了,撤回" |
| `notice.setTopmost` ⭐ | `POST /api/notice/SetTopmost?serid=&topmost=` | 教/管 | 单参写 | 置顶 / 取消 |
| `notice.expedite` ⭐ | `POST /api/notice/Expedite?serid=` | 教/管 | 单参写 | **催读**,管理刚需 |
| `notice.readDetail` ⭐ | `GET /api/notice/ReadDetail?serid=` | 教/管 | 只读 | **谁读了 / 谁没读**(教师维度分组) |
| `notice.readReceipt` | `GET /api/notice/ReadReceipt?serid=` | 教/管 | 只读 | 年级→班级→学生 + 教师组的回执快照 |
| `notice.readAll` | `POST /api/notice/InitializeRed` | 全员 | 单参写 | 一键全部已读("我还有多少没看"→顺手清) |
| `notice.delete` | `POST /api/notice/Delete` | 教/管 | 单参写 | body 是 id **数组**,支持批量 |
| `notice.drafts` | `GET /api/notice/DraftList` | 教/管 | 只读 | 草稿箱 |
| `notice.saveDraft` | `POST /api/notice/SaveDraft` | 教/管 | 表单写 | 先存着 |
| `notice.edit` | `POST /api/notice/Edit` | 教/管 | 表单写 | 编辑(`flow=[]` 清流程) |
| `notice.submitApproval` ⭐ | `POST /api/notice/Submit` | 教/管 | 表单写 | 提交审批,`steps[].approverTeaIds` 来自**教师树** |
| `notice.appendReceivers` | `POST /api/notice/ReceiveUsers?serid=&users=` | 教/管 | 单参写 | 追加接收人(补发) |
| `notice.stats` | `GET /api/notice/Statis` | 管 | 只读 | 区间统计(页面 `/stats` 目前还是占位页,但接口是活的) |
| `notice.deptManage` | `/api/dict/{ManageList,Add,Update,Delete,Sort}` | 管 | 写 | 发文部门维护 |
> 老师最常问的两句是「**这条通知谁还没看**」和「**催一下没看的**」,对应 `readDetail` + `expedite`,建议 P0。
---
### 2.3 `dormnotice` 宿舍通知 —— 已接 9,建议 +18(缺口第二大)
**已接**:`dorm.myNotices` `myNotice` `sendList` `submitReceipt` `cancelReceipt` `buildings` `rooms` `roomStudents` `dormTree`
**模块能力全景**(三条业务线):
| 页面 | 角色 | 能力 |
|---|---|---|
| `/par/notices` `/par/mine` | 家长 | 通知箱、阅读、提交/修改/取消**回执** |
| `/list` `/noticelist` `/list/new` | 宿管/教师 | **发布通知**(含回执方式/回执单据/接收宿舍/短信/定时)、撤回、置顶、催办、编辑 |
| `/receipts` 回执情况 | 宿管/管理员 | 回执名单(按楼栋·房间)、**代为确认 / 取消 / 退回**、催办、导出 CSV |
| `/setting` 宿舍设置 | 管理员 | 通知类型字典、发布部门字典、**楼栋/房间台账 + 住宿生床位安排**、自定义联系组 |
**建议新增(A. 通知管理侧):**
| 建议工具 | 接口 | 角色 | 类型 | 说明 |
|---|---|---|---|---|
| `dorm.publish` ⭐ | `POST /api/notice/Create` | 教/管 | 表单写 | **宿管发通知**。字段:标题/类型/部门/紧急度/生效时间/回执截止/**回执方式**(none/confirm/fill)/**回执单据**(stay/visit/adjust/general)/**接收宿舍**/附件 |
| `dorm.edit` | `POST /api/notice/Edit` | 教/管 | 表单写 | 编辑(含 `sourceId`) |
| `dorm.revoke` | `POST /api/notice/UserPull?serid=` | 教/管 | 单参写 | 撤回 |
| `dorm.setTopmost` | `POST /api/notice/SetTopmost?serid=&topmost=` | 教/管 | 单参写 | 置顶 |
| `dorm.delete` | `POST /api/notice/Delete` | 教/管 | 单参写 | 批量删除(级联删回执) |
| `dorm.receipts` ⭐ | `GET /api/notice/Receipts?serid=&status=` | 教/管 | 只读 | **回执名单 + 汇总**(应收/已回/已确认/已填报/未回执/回执率) |
| `dorm.receiptDetail` | `GET /api/notice/ReceiptDetail?receiptId=` | 教/管 | 只读 | 单条回执详情 |
| `dorm.reviewReceipt` ⭐ | `POST /api/notice/Review` | 教/管 | 单参写 | `{receiptId, action: confirm/cancel/reset}` —— 代确认 / 取消 / 退回 |
| `dorm.remindReceipt` ⭐ | `POST /api/notice/Remind?serid=` | 教/管 | 单参写 | 催未回执家长 |
| `dorm.remindStudent` | `POST /api/notice/RemindStudent?serid=&studentId=` | 教/管 | 单参写 | 定向催某个学生 |
| `dorm.markRead` | `POST /api/notice/MarkRead?serid=&studentId=` | 教/管 | 单参写 | 代标记已读(线下已通知的情况) |
| `dorm.exportReceipts` | `GET /api/notice/ExportReceipts?serid=` | 教/管 | 只读 | 导出 CSV(返回文本,可回传前端下载) |
| `dorm.stats` | `GET /api/notice/Statis` | 教/管 | 只读 | 通知统计 |
| `dorm.formTemplates` | `GET /api/notice/FormTemplates` | 教/管 | 只读 | 回执单据模板(**动态字段**:key/label/type/required/options) |
| `dorm.noticeTypes` | `GET /api/dict/List?type=noticeType` | 教/管 | 只读 | 通知类型下拉 |
**建议新增(B. 床位台账 —— 模块自带的"没想到的能力"):**
| 建议工具 | 接口 | 角色 | 类型 | 说明 |
|---|---|---|---|---|
| `dorm.pendingStudents` ⭐ | `GET /api/dorm/PendingStudents?keyword=&gender=` | 教/管 | 只读 | **待安排床位学生池** |
| `dorm.assignStudent` ⭐ | `POST /api/dorm/AssignStudent?roomId=&studentIds=` | 教/管 | 表单写 | **安排入住**(房间 + 学生多选) |
| `dorm.removeStudent` | `POST /api/dorm/RemoveStudent?roomId=&studentId=` | 教/管 | 单参写 | 退宿 |
| `dorm.transferStudent` | `POST /api/dorm/TransferStudent` | 教/管 | 表单写 | 调整宿舍(回显是否原宿舍长 + 床位号) |
| `dorm.setMonitor` | `POST /api/dorm/SetMonitor?roomId=&studentId=` | 教/管 | 单参写 | 设/取消宿舍长(取消传 0) |
| `dorm.addBuilding` | `POST /api/dorm/AddBuilding` | 管 | 表单写 | 新增楼栋(可**批量铺房间**:层数×每层房间数×床位数) |
| `dorm.editBuilding` / `dorm.delBuilding` | 同名接口 | 管 | 写 | 改 / 删(删除需先清空房间) |
| `dorm.addRoom` / `editRoom` / `delRoom` | 同名接口 | 管 | 写 | 房间增删改 |
| `dorm.buildingTree` | 组合 `BuildingList` → `RoomList(buildingId)` | 教/管 | 只读 | 楼栋→楼层→房间三级(前端按 `floor` 分组) |
> 这三条联动链是模块里最有价值、也最"没人想到助手能做"的部分:
> **楼栋 → 房间 → 住宿生**、**待安排池 → 安排入住**、**回执名单 → 代确认/催办**。
---
### 2.4 `familyactivity` 家校活动 —— 已接 7,建议 +13
**已接**:`activity.plaza` `list` `detail` `mySignup` `signup` `cancelSignup` `classTree`
**模块能力全景**:闭环 = 发布(草稿/定时/复用)→ 触达(班级定向/短信/置顶)→ 已读追踪 → **报名回执**(仅通知 / 需报名 / 需确认)→ 学校确认 / 催办 / 导出 → 统计 → 字典与联系组。
**建议新增:**
| 建议工具 | 接口 | 角色 | 类型 | 说明 |
|---|---|---|---|---|
| `activity.publish` ⭐ | `POST /api/activity/Create` | 教/管 | 表单写 | **发活动**。字段:标题/分类/主办部门/**接收班级**/时间地点/回执方式/附件/短信/定时 |
| `activity.edit` | `POST /api/activity/Edit` | 教/管 | 表单写 | 编辑 |
| `activity.revoke` | `POST /api/activity/UserPull?serid=` | 教/管 | 单参写 | 撤回(家长端不可见) |
| `activity.setTopmost` | `POST /api/activity/SetTopmost` | 教/管 | 单参写 | 置顶 |
| `activity.delete` | `POST /api/activity/Delete` | 教/管 | 单参写 | 批量删除 |
| `activity.signups` ⭐ | `GET /api/activity/Signups?serid=&signupStatus=` | 教/管 | 只读 | **报名名单**(按班级分组 + 汇总) |
| `activity.signupDetail` | `GET /api/activity/SignupDetail?signupId=` | 教/管 | 只读 | 单条回执详情 |
| `activity.reviewSignup` ⭐ | `POST /api/activity/Review` | 教/管 | 单参写 | `{signupId, action: confirm/cancel/waitlist, remark}` —— 学校确认 / 取消请假 / 待定 |
| `activity.remind` ⭐ | `POST /api/activity/Remind?serid=` | 教/管 | 单参写 | 催报名 |
| `activity.exportSignups` | `GET /api/activity/ExportSignups?serid=` | 教/管 | 只读 | 导出 CSV 文本(带 BOM) |
| `activity.readDetail` | `GET /api/activity/ReadDetail?serid=` | 教/管 | 只读 | 已读/未读(年级→班级) |
| `activity.stats` | `GET /api/activity/Statis` | 教/管 | 只读 | 活动统计 |
| `activity.categories` | `GET /api/dict/List?type=2` | 全员 | 只读 | 活动分类下拉(发布时用) |
| `activity.receiveClasses` | `POST /api/activity/ReceiveClasses?serid=&classes=` | 教/管 | 单参写 | 追加接收班级(补发) |
---
### 2.5 `coursehomework` 课程作业 —— 已接 4,建议 +20(**缺口最大**)
**已接(全是学生端)**:`homework.myList` `detail` `result` `submit`
> ★ 这个模块是**端分离**的:`/stu/*` = 学生,其余 = 教师。
> 助手目前只接了 `/api/student/homeworks*` 四件事,**`/api/teacher/homeworks/*` 一整套 0 接入**。
> 而教师的日常恰恰全在这一侧:布置、催交、批阅、打分、打回。
**建议新增(A. 教师端 V2,`/api/teacher/homeworks`):**
| 建议工具 | 接口 | 角色 | 类型 | 说明 |
|---|---|---|---|---|
| `homework.create` ⭐ | `POST /api/teacher/homeworks` | 教师 | 表单写 | **布置作业**(先落草稿)。字段:标题/学科/**接收班级**/截止时间/提交方式/逾期可补交/截止提醒/附件 |
| `homework.editDraft` | `PUT /api/teacher/homeworks/{id}` | 教师 | 表单写 | 仅草稿可改 |
| `homework.deleteDraft` | `DELETE /api/teacher/homeworks/{id}` | 教师 | 单参写 | 仅草稿可删 |
| `homework.publishNow` ⭐ | `POST .../{id}/publish` | 教师 | 单参写 | 草稿 → 进行中(触发学生通知) |
| `homework.schedule` | `POST .../{id}/schedule` | 教师 | 单参写 | 定时发布 / 取消定时 |
| `homework.drafts` | `GET /api/teacher/homeworks/drafts` | 教师 | 只读 | 草稿箱 |
| `homework.published` | `GET /api/teacher/homeworks/published` | 教师 | 只读 | 已发布列表(可按学科筛) |
| `homework.remind` ⭐ | `POST .../{id}/remind` | 教师 | 单参写 | **催交**:`{type: NOT_READ\|NOT_SUBMITTED, studentIds?}` |
| `homework.extend` | `POST .../{id}/extend` | 教师 | 单参写 | 延长截止(新截止须晚于当前) |
| `homework.copy` | `POST .../{id}/copy` | 教师 | 单参写 | 复制为新草稿 |
| `homework.archive` | `POST .../{id}/archive` | 教师 | 单参写 | 归档(只读,不再提醒) |
| `homework.teacherDetail` | `GET .../{id}` | 教师 | 只读 | 作业详情 + 汇总统计 + 分班进度 |
| `homework.teacherStats` ⭐ | `GET .../{id}/stats` | 教师 | 只读 | "这份作业交了多少" |
| `homework.students` ⭐ | `GET .../{id}/students` | 教师 | 只读 | **学生矩阵**:谁读了/谁交了/谁没交/谁待批 |
| `homework.review` ⭐ | `POST .../{id}/submissions/{studentId}/review` | 教师 | 表单写 | **批阅打分 + 评语**(score/comment 至少一项) |
| `homework.returnSubmission` ⭐ | `POST .../{id}/submissions/{studentId}/return` | 教师 | 单参写 | **打回订正**(comment 必填) |
| `homework.versions` | `GET .../{id}/submissions/{studentId}/versions` | 教师 | 只读 | 历史提交版本 |
| `homework.export` | `GET .../{id}/export` | 教师 | 只读 | 导出学生矩阵 Excel(直链) |
| `homework.subjects` | `GET /api/teacher/homeworks/subjects/teaching` | 教师 | 只读 | 学科下拉 |
| `homework.classList` | `GET /api/teacher/homeworks/classes?subject=` | 教师 | 只读 | **年级→班级树,依赖所选学科** |
**建议新增(B. 学生端补全 + 通知中心):**
| 建议工具 | 接口 | 角色 | 类型 | 说明 |
|---|---|---|---|---|
| `homework.withdrawSubmission` | `PUT .../submissions/{submissionId}` `action=withdraw` | 学生 | 单参写 | 撤回提交(截止前) |
| `homework.resubmit` | `PUT ...` `action=resubmit` | 学生 | 单参写 | **打回后重交**(与 `result` 配对) |
| `homework.read` | `POST /api/student/homeworks/{id}/read` | 学生 | 单参写 | 阅读上报(幂等) |
| `homework.notifications` | `GET /api/notifications` | 学生/教师 | 只读 | **通知中心**(接口已封装但前端无页面,是"有接口无 UI"的隐藏能力) |
| `homework.readAllNotifications` | `POST /api/notifications/read-all` | 同上 | 单参写 | 全部已读 |
> 另外 V1 冻结管理端还有 `GET /api/homework/SendList`(全部人作业)、`POST /api/homework/Delete`、`GET /api/homework/Statis`,
> 供"管理/上级教师"口径使用,可选接。
---
### 2.6 `profession` 教师专业发展(第 6 个模块,教师专属,已接 9,建议 +12)
**已接**:`homeRecords` `list` `moduleRecords` `myRecords` `detail` `moduleTree` `myPoints` `pointsRank` `publish`
| 建议工具 | 接口 | 类型 | 说明 |
|---|---|---|---|
| `profession.editRecord` | `POST /api/profession/Edit` | 表单写 | 编辑自己的记录 |
| `profession.deleteRecord` | `GET /api/profession/Delete?ids=` | 单参写 | 删除 |
| `profession.comments` | `GET /api/profession/DiscussList?sid=` | 只读 | 评论列表 |
| `profession.comment` | `POST /api/profession/DiscussCreate` | 单参写 | 发表评论 |
| `profession.deleteComment` | `GET /api/profession/DiscussDel?id=` | 单参写 | 删评论 |
| `profession.like` | `POST /api/profession/LikeCreate?recordId=` | 单参写 | 点赞 / 取消(toggle) |
| `profession.timeline` | `GET /api/profession/MyTimeline?userid=` | 只读 | 个人时间轴 |
| `profession.spaceOverview` | `GET /api/profession/SpaceOverview` | 只读 | 个人空间聚合(积分/记录/获赞/被访问) |
| `profession.pointsSource` | `GET /api/profession/PointsSourceList` | 只读 | **积分来源明细**("我这 20 分怎么来的") |
| `profession.grades` / `subjects` | `GetGradeList` / `GetSubList` | 只读 | 年级段 / 学科字典(发布记录时用) |
| `profession.stats` | `GET /api/stats/{TeacherStat,ModuleStat,PointsStat}` | 只读 | 校级统计(管理员) |
| `profession.modules` | `GET /api/moudule/MouduleList` | 只读 | 栏目列表(含 `readonly` 只读标记) |
> 注:**该模块没有审批流**(记录发布即生效,`isCheck` 是栏目启停不是记录审核),别按"提交审核"设计。
---
## 3. 跨模块共性缺口 —— "各模块都没想到的能力"
这 7 条是**重复出现在多个模块**、做一次就能复用多次的能力:
| # | 共性能力 | 出现在 | 助手现状 | 价值 |
|---|---|---|---|---|
| 1 | **撤回 / 置顶 / 催办三件套** | 公告、宿舍、活动、作业 | 全缺 | ★★★ 发错了能收回、重要的能置顶、没看的能催 —— 都是**单参数 POST**,成本极低 |
| 2 | **回执 / 提交名单闭环** | 公告、宿舍、活动、作业 | 只做了"我提交",没做"**谁没提交**" | ★★★ 教师最高频诉求 |
| 3 | **统计数据** | 公告、宿舍、活动、作业都有 `Statis` | 只接了 `leave.stats` | ★★ 一句话出数字 |
| 4 | **字典维护**(在"应用设置"里) | 公告(发文部门)、宿舍(通知类型)、活动(主办部门/分类)、请假(类型) | 只做了"读下拉",没做"改" | ★★ 管理员可用自然语言维护 |
| 5 | **附件上传/预览/下载** | 5 模块统一 `/api/file/{Upload,Preview,Down}` | 写操作暂不支持附件 | ★ 侧重点:写时提示"需附件请到页面补传",只读加 `Preview/Down` 直链 |
| 6 | **级联下拉链** | 作业、宿舍、活动、公告 | **基建已有**(`optionCall` + `valueKey/labelKey` + `dependsOn`) | ★★ 直接复用,见第 4 节 |
| 7 | **审批人选择** | 公告(多步审批流 `steps`) | 缺 | ★ `getTeaTree` 教师树 + 多选 |
---
## 4. 落地设计:复用现有基建,**零新增管线**
这一节是"**怎么做**",全部基于已经跑通的机制:
### 4.1 写操作 —— 自动走浏览器代执行
服务端**无法**代持用户登录态调模块 `/api/*`(下游 CAS 过滤器只认浏览器容器会话,必回 `code=302`)。
现有 A 通道(commit `ab87a4b`)已在跑:服务端挂起下发 `browserTool.calls` → 前端用**用户自己的登录态** fetch → 回传结果续跑。
**所以上面所有"写"工具,只需要在 `ToolDef` 里声明 `.write(landingPath)`,不需要任何新的传输管线。**
### 4.2 表单下拉 —— 用现成的 `optionCall` 机制
后端只下发"取数说明"(含 `{{占位符}}`),前端带登录态取,再按后端给的 `valueKey/labelKey` 映射:
```java
ToolParam.required("stuId", "integer", "学生 id")
.label("学生")
.optionsFrom("leave.classStudents").optionsKeys("stuId", "stuName")
.dependsOn("classId"); // ← 选完班级才解锁
```
**需要新写的级联链清单**(照这个模式加):
| 模块 | 联动链 | 依赖关系 |
|---|---|---|
| 课程作业 | 学科 → 年级班级树 | `homework.classList` `dependsOn("subject")` |
| 课程作业 | 已发布作业 → 班级 → 学生 | `stats.byClass` → `students?classId` |
| 宿舍通知 | **楼栋 → 房间 → 住宿生** | `RoomList` `dependsOn("buildingId")`;`RoomStudents` `dependsOn("roomId")` |
| 宿舍通知 | 楼栋 → 房间(接收宿舍多选) | 同上,前端按 `floor` 分组 |
| 家校活动 | 接收班级(年级→班级) | `GetClassTree` 分组下拉,选"全部班级"→ `receiveList=ALL` |
| 校园公告 | 发文部门 / 接收范围 / 审批人 | `dict/List?type=1` / `getStuTree` / `getTeaTree` |
| 请假 | 班级 → 学生(**已完成**) | 参考实现 |
### 4.3 裸意图直达 —— 可扩展
已有 `IntentShortcut`(去标点 + 剥前后缀正好等于标准说法 + ≤12 字 → 跳过 LLM 直接弹表单)。
可照葫芦画瓢加:
| 说法 | 直达工具 |
|---|---|
| 「我要交作业」「看今天作业」 | `homework.myList` / 学生提交表单 |
| 「布置作业」 | `homework.create` |
| 「我要报名」 | `activity.signup`(先弹活动选择) |
| 「查宿舍」 | `dorm.myNotices` |
| 「发通知」 | 视模块 → `notice.publish` / `dorm.publish` / `activity.publish`(**有歧义,需按目录可见性消歧**) |
> ⚠️ 「发通知」「我要报名」这类**跨模块同义**的说法不要做裸意图直达,
> 交给模型按 `catalog` 里可见的工具自己选,否则会猜错模块。
---
## 5. 角色分桶建议(实施时必须同步改 `RolePolicy`)
| 工具族 | 建议桶 |
|---|---|
| 写:发布/编辑/撤回/置顶/催办/删除/审批/统计/字典维护/床位台账/批阅打分/打回/催交 | `STAFF_ONLY` |
| 写:请假申请、交作业、阅读上报、撤回提交、重交 | `STUDENT_ONLY` |
| 写:活动报名 / 取消报名 | `STUDENT_OR_PARENT` |
| 写:一键全部已读、宿舍回执提交/取消 | 不限(所有人) |
| 只读:各类列表/详情/统计/下拉 | 按模块判定(**教师专属模块**如 `profession.*` 用前缀规则) |
当前 `profession.*` 已用模块前缀规则(`STAFF_MODULE_PREFIXES`)整模块归教职工,新增模块可沿用。
**几条容易踩的:**
- `homework.*` 现在被整桶放进 `STUDENT_ONLY`。**新增教师端工具必须改名字或用显式 `.allow(...)`**,否则会被误判成学生工具。
建议改成:`homework.myList/detail/result/submit/read/...`(学生)保留在 `STUDENT_ONLY`,
而 `homework.create/publish/review/...` 显式登记 `STAFF_ONLY`。
- 「回执/名单查询」(`dorm.receipts`、`activity.signups`、`notice.readDetail`)**必须** `STAFF_ONLY` —— 那是全班/全校数据。
- 「一键已读」「提交自己的回执」这类**只动自己的**,不要限制角色。
---
## 6. 建议实施顺序(4 批,风险递增)
| 批次 | 内容 | 风险 | 为什么先做 |
|---|---|---|---|
| **批 1** | 只读补全:统计(4 个模块)、阅读情况、回执/报名名单、草稿箱、`homework.students` 学生矩阵、下拉字典 | 极低 | 纯 GET,不改变任何数据;立刻让"谁没交/谁没看"能问 |
| **批 2** | 单参数写:撤回 / 置顶 / 催读 / 催办 / 催报名 / 删除 / 一键已读 / 代确认回执 / 打回 / 归档 / 发布草稿 | 低 | 单字段 POST,表单只需"确认";覆盖最高频的"发错了/催一下" |
| **批 3** | 表单写:布置作业、发活动、发宿舍通知、公告草稿+编辑+提交审批、请假类型字典维护 | 中 | 正文级表单,需要完整字段 + 级联下拉 |
| **批 4** | 复杂交互:批阅打分(分数+评语+历史版本)、床位安排(楼栋→房间→学生)、多步审批人选人、导出报表 | 中高 | 多步 + 多级联动,建议单独设计交互 |
---
## 附录 A:接口路径速查
```
# 请假 homeleave
GET /api/leave/List|Details|MyClasses|ClassStudents /api/leavestats/Stats
POST /api/leave/Save|Audit|BatchPass|ReportBack /api/leave/Withdraw|Delete (form)
/api/dict/LeaveTypeOptions|List|Add|Update|Delete|Sort
# 校园公告 campusnotice
GET /api/notice/List|SendList|Detail|GetDetail|GetNextPrev|DraftList
/api/notice/ReadDetail|ReadReceipt|ApprovalList|ApprovalCount|Statis
POST /api/notice/Create|Edit|SaveDraft|Submit|Delete|Approve|Reject (JSON)
/api/notice/UserPull|SetTopmost|Expedite|InitializeRed|ReceiveUsers (form)
/api/dict/{List,ManageList,Add,Update,Delete,Sort} /api/contacts/*
# 宿舍通知 dormnotice
GET /api/notice/MyNotices|MyNotice|List|SendList|Detail|ReadDetail
/api/notice/Receipts|ReceiptDetail|ExportReceipts|FormTemplates|Statis
/api/dorm/BuildingList|RoomList|RoomStudents|PendingStudents /api/base/GetDormTree
POST /api/notice/SubmitReceipt|CancelReceipt|Create|Edit|Delete|Review (JSON)
/api/notice/UserPull|SetTopmost|Expedite|Remind|RemindStudent|MarkRead (form)
/api/dorm/AddBuilding|EditBuilding|DelBuilding|AddRoom|EditRoom|DelRoom
/api/dorm/AssignStudent|RemoveStudent|TransferStudent|SetMonitor
# 家校活动 familyactivity
GET /api/activity/MyActivities|List|SendList|Detail|GetDetail|ReadDetail
/api/activity/Signups|SignupDetail|ExportSignups|Statis|MySignup
POST /api/activity/Signup|CancelSignup|Create|Edit|Delete|Review (JSON)
/api/activity/UserPull|SetTopmost|Remind|ReceiveClasses|InitializeRed|Expedite (form)
/api/dict/List?type=1|2 /api/base/GetClassTree
# 课程作业 coursehomework
GET /api/student/homeworks?tab= /api/student/homeworks/{id}|/{id}/result
GET /api/teacher/homeworks/drafts|published|classes?subject=|subjects/teaching|assigned
/api/teacher/homeworks/{id}|/{id}/stats|/{id}/students|/{id}/export
/api/teacher/homeworks/{id}/submissions/{studentId}/versions
GET /api/homework/SendList|Detail|ReadDetail|Statis (V1 管理端)
POST /api/student/homeworks/{id}/read|/{id}/submissions
PUT /api/student/homeworks/{id}/submissions/{submissionId} (withdraw|resubmit)
POST /api/teacher/homeworks | /{id}/publish|schedule|remind|extend|copy|archive
/{id}/submissions/{studentId}/review|return
PUT /api/teacher/homeworks/{id} DELETE /api/teacher/homeworks/{id}
GET /api/notifications POST /api/notifications/{id}/read|read-all
# 专业发展 profession
GET /api/profession/HomeModuleRecords|ProfessionList|ModuleRecordList|MyModuleRecordList
/api/profession/ProfessionDetail|MyTimeline|MyPoints|PointsSourceList|PointsRank
/api/profession/SpaceOverview|DiscussList|VisitList|GetGradeList|GetSubList
POST /api/profession/Create|Edit|DiscussCreate|DiscussEdit|VisitCreate|LikeCreate
GET /api/profession/Delete|DiscussDel /api/moudule/* /api/points/* /api/stats/*
# 全模块通用
GET /api/base/GetUserInfo|GetUserMod|GetSchoolAll|GetClassTree|getStuTree|getTeaTree
POST /api/base/SetRole|FileUpload|Logout
GET /api/file/Preview|Down|List POST /api/file/Upload|UploadImage
```