build / build (push) Successful in 9m1s
家长端回执弹窗的专属区动态规则此前只写 required + trigger、没给 type。
async-validator 的 getValidationMethod 仅在规则「除掉 message 后只剩 required」
时才走 required 校验器;带上 trigger 后会回退到默认 string 校验器,而 DatePicker
的 v-model 给出的是 Date 对象,被判类型不符。真正的报错又在 cb() 里被自定义
message 顶掉,界面上只剩「请填写XX」——表现为字段明明填了却不让提交。
- ParentNotices.vue: 新增 ruleTypeOf() 统一映射规则类型
(number -> type:"number",date/datetime -> type:"date",其余保持默认 string)。
注意 async-validator 没有 datetime 类型,写上去会导致 validate 抛异常。
- ParentNotices.vue: 日期字段回填统一转 Date(本地时间语义,避免 new Date("yyyy-MM-dd")
的 UTC 偏移),提交时序列化成 yyyy-MM-dd / yyyy-MM-dd HH:mm。
这同时修掉一处数据污染:此前 String(Date) 会把
"Tue Sep 22 2026 10:33:00 GMT+0800 (中国标准时间)" 这类 locale 脏串写进 fillData。
- NewNotice.vue: 定时发布时间弹窗的规则存在同类问题,补 type:"date"。
- 覆盖四类回执单:stay / visit / adjust 的日期字段曾坏,general 及公共区字段本就正常。
.gitignore 追加 .workbuddy/(本地 AI 工作目录,不入库)。
278 lines
24 KiB
Markdown
278 lines
24 KiB
Markdown
# 宿舍通知模块 · 功能与接口说明 v1
|
||
|
||
> 模板来源:`D:\项目管理\FamilyActivity`(家校活动信息发布)
|
||
> 本模块:`D:\项目管理\DormNotice`(家校宿舍通知)
|
||
> 技术栈一致:Vue3 + Vite + view-ui-plus + alova + vite-plugin-mock,`npm run dev:mock` 纯前端可跑。
|
||
|
||
---
|
||
|
||
## 一、为什么不能直接复用「活动」模块
|
||
|
||
活动与宿舍通知同为「发布 → 家长接收 → 回执」的家校闭环,但**收件人粒度**和**回执形态**两处根本差异,决定了它是独立模块而不是活动的换皮:
|
||
|
||
| 维度 | 活动信息发布 | 宿舍通知 |
|
||
| --- | --- | --- |
|
||
| 收件人粒度 | **班级**(`receiveType=class`,`receiveList` 为班级 id 串) | **宿舍房间**(`receiveType=room`,`receiveList` 为房间 id 串) |
|
||
| 应收人数口径 | 班级学生数 | 覆盖房间的**实际入住床位数** |
|
||
| 回执形态 | 单一:报名(填参加人数 + 随行 + 费用) | 三档:仅通知 / 需确认(打开即确认) / 需填报(统一回执单:公共区固定 + 专属区随单据类型) |
|
||
| 回执内容 | 固定字段(joinCount、guardianName、remark) | **动态字段**:由发布方声明(留宿起止、探视时段、搬迁日期…) |
|
||
| 紧急程度 | 无 | 普通 / 重要 / 紧急(家长端置顶标红) |
|
||
| 宿管协同 | 无 | `needSupervisor`:需宿管到场配合执行 |
|
||
| 覆盖统计 | 按班 | 按**楼栋**覆盖房间数 / 床位数 |
|
||
|
||
**关键业务点:** 武校是寄宿制,同一班级的学生分住不同楼栋房间。若沿用「按班级下发」,一条「1号楼 3 层明日停水」会推送给全部家长(大量无关触达),而真正受影响的房间只占少数。因此本模块把发布对象下沉到**房间**一级,应收人数取房间实际入住床位,回执率才具备管理意义。
|
||
|
||
---
|
||
|
||
## 二、功能地图(7 个页面)
|
||
|
||
### 发布端(教师 / 宿管 / 德育处)
|
||
|
||
| # | 页面 | 路由 | modId | 说明 |
|
||
| --- | --- | --- | --- | --- |
|
||
| 1 | 我发布的 | `/list` | 2 | 本人发布的通知列表,含草稿;行内可催办、置顶、撤回、删除、复用再发 |
|
||
| 2 | 全部通知 | `/noticelist` | 3 | 全校通知(去掉 `scope=user`),用于跨部门查询与统计核对 |
|
||
| 3 | 发布通知 | `/list/new` | 22(`parentId=2`) | 新建 / 编辑(`?serid=`)/ 复用(`?fid=`)三态同一组件 |
|
||
| 4 | 通知详情 | `/list/detail`、`/noticelist/detail` | 23 / 24 | 管理视角(`type=0` 不标读)与查看视角(`type=1` 标读) |
|
||
| 5 | 回执情况 | `/receipts` | 25 | 按「楼栋·房间」分组的回执/填报名单,支持代确认、取消、退回、催办、导出 CSV |
|
||
| 6 | 通知统计 | `/stats` | 4 | 总量/回执率/覆盖人次、类型分布、月度趋势、楼栋覆盖、发布人分布 |
|
||
| 7 | 宿舍设置 | `/setting` | 21 | 通知类型字典 + 宿舍楼栋房间台账 + 自定义组(宿管/生活老师) |
|
||
|
||
### 家长端(家长 / 学生账号)
|
||
|
||
| # | 页面 | 路由 | modId | 说明 |
|
||
| --- | --- | --- | --- | --- |
|
||
| 8 | 宿舍通知箱 | `/par/notices` | 31 | 左侧分桶列表(全部/待回执/已回执/已结束)+ 右侧通知详情与回执操作 |
|
||
| 9 | 我的回执 | `/par/mine` | 32 | 同一组件,`meta.initTab=done` 默认落「已回执」 |
|
||
|
||
### 端分离机制(沿用模板)
|
||
`App.vue` 按路由前缀判定视角(`/par` → parent,其余 → teacher),先调 `POST /api/base/SetRole` 切 mock 当前用户,再拉 `GetUserInfo` / `GetUserMod`。这样同一份代码在家长端与发布端各拿一份身份与菜单,不需要两套工程。
|
||
|
||
---
|
||
|
||
## 三、发布通知页(模块的复杂度中心)
|
||
|
||
### 3.1 表单字段
|
||
|
||
| 字段 | 控件 | 必填 | 说明 |
|
||
| --- | --- | --- | --- |
|
||
| 通知标题 | Input(≤50 字) | ✅ | 家长端列表主标题 |
|
||
| 通知类型 | Select | ✅ | 取自「宿舍设置 → 通知类型」字典(停用项不下发) |
|
||
| 发布部门 | Select | ✅ | 校办 / 德育处 / 生活管理科 / 教务处 / 后勤保障部 / 校医务室 / 武术训练部 / 安全管理办公室 |
|
||
| 紧急程度 | Radio | — | 普通 / 重要 / **紧急**(家长端置顶标红) |
|
||
| 生效时间 | DatePicker(datetime) | — | 留空 = 立即生效;用于「明早起执行新作息」提前下发 |
|
||
| 回执截止 | DatePicker(datetime) | — | 留空 = 不设截止;早于生效时间会被前端拦下 |
|
||
| 回执方式 | Radio | — | `none` / `confirm` / `fill`,见 3.3 |
|
||
| 回执单据 | Select(单据类型) | fill 时 ✅ | 只选类型,字段由系统预置模板带出,**不可增删改**;下方为家长端表单只读预览 |
|
||
| 接收宿舍 | 楼栋→楼层→房间勾选弹窗 | ✅ | 或勾「全部宿舍」(`receiveList=ALL`) |
|
||
| 宿管配合 | Switch | — | 需宿管到场配合执行时开启 |
|
||
| 置顶展示 | Switch | — | 家长端列表置顶并标「顶」 |
|
||
| 短信提醒 | Switch | — | 需学校已开通短信 |
|
||
| 附件 | AttachmentUpload | — | 图片/文档,走 `/api/base/FileUpload` |
|
||
| 通知内容 | wangEditor 富文本 | — | 图片走 `/api/file/UploadImage` |
|
||
|
||
### 3.2 接收宿舍选择器(本模块独有)
|
||
弹窗左侧楼栋列表(名称 / 房间数 / 入住 N/M),右侧按楼层分组的房间勾选网格,每间显示 `房号(已住/总床位)`;支持「本层全选」「本楼全选」「清空」。底部实时汇总 **N 间 · M 名住宿生**。
|
||
表单区回显为可读摘要:`6 间宿舍 · 42 名住宿生|1号楼 305、1号楼 306、2号楼 …`。
|
||
|
||
> 校验要点:`receiveList` 挂 `type: "array", min: 1`,「全部宿舍」勾选时清空手工选择并以 `ALL` 提交,保证校验能通过(模板里「全部班级」的同一套处理方式)。
|
||
|
||
### 3.3 三档回执方式(家校闭环的核心)
|
||
|
||
| 值 | 家长端行为 | 适用通知类型 |
|
||
| --- | --- | --- |
|
||
| `none` 仅通知 | 阅读即知悉,**不产生回执记录**,只统计已读 | 作息调整、停水停电、卫生检查、违规通报、假期安排 |
|
||
| `confirm` 需确认 | **打开通知详情即自动确认**,无需点击;仅在自动确认失败时给手动兜底按钮 | 安全排查、夜间查寝、宿舍调整(知悉类) |
|
||
| `fill` 需填报 | 家长按**统一回执单**提交(公共区固定 + 专属区随单据类型),截止前可修改,也可取消 | 留宿申请、家长探视、物品清退、宿舍调整(需回执搬迁日期) |
|
||
|
||
**为什么 `confirm` 要「打开即确认」**:让家长为「已阅」多点一次按钮,实际结果是没人点、管理端拿不到真实触达数据。打开即确认把「已读」升级为「已确认知悉」,且顺手省掉一次点击。实现上在 `open()` 内触发一次 `autoConfirmIfNeeded()`,提交后**只刷新左侧列表与分桶计数**、不重新拉详情,避免自我循环。
|
||
|
||
### 3.4 统一回执单(公共区 + 专属区)
|
||
|
||
「需填报」不是自由表单,而是**一张统一回执单**,分两个区块:
|
||
|
||
| 区块 | 内容 | 是否随单据变化 |
|
||
| --- | --- | --- |
|
||
| **公共区** | 学生(花名册带出) / 家长姓名 / 联系电话 / **回执结论**(同意 / 不同意 / 需沟通) | ❌ 所有单据完全一致 |
|
||
| **专属区** | 单据登记项(留宿起止、到访时段、搬迁日期等) | ✅ 随单据类型 `formType` |
|
||
|
||
四类预置单据:
|
||
|
||
| 单据 | 专属区字段 | 适用 |
|
||
| --- | --- | --- |
|
||
| `stay` 留宿申请单 | 留宿开始日期* / 留宿结束日期* / 留宿人数*(人) / 留宿事由*(家远不便往返、赛前集训、伤病康复观察、学业补差、其他) | 周末 / 假期留宿申请 |
|
||
| `visit` 探视预约单 | 到访日期* / 到访时段*(上午 / 下午 / 晚间) / 到访人数*(人) | 家长到校探视预约 |
|
||
| `adjust` 调宿申请单 | 可配合搬迁日期* / 情况说明 | 床位重排、宿舍调换确认 |
|
||
| `general` 通用回执单 | 无(仅公共区) | 只需家长给结论的场景 |
|
||
|
||
**发布人只能选单据类型,不能增删字段**:字段清单由后端按 `formType` 生成(`buildFormFields()`),发布页表单只上传 `formType`,不传字段定义。发布页下方为只读预览(公共区 chips + 专属区 chips),单据类型由 `GET /api/notice/FormTemplates` 下发(结构见 `ReceiptFormTemplate`)。
|
||
|
||
> **为什么不做自由表单**:自由拼字段会让每条通知的字段名、字段数、表头各不相同 —— 回执落成 `fillData` 的 JSON 后**无法跨通知合并统计**,导出的 CSV 表头也对不上。而本模块的回执只有 `confirmed / filled / canceled` 三态、**没有审批下游**,字段结构化的意义只剩「汇总」;既然只为汇总,口径就必须固定。
|
||
|
||
字段模型:
|
||
`FillField = { key, label, type, required, placeholder?, options?, unit?, group }`,其中 `group ∈ common|specific`,`type ∈ text|number|date|datetime|select|textarea`。
|
||
家长端按 `group` **分两段渲染**(先公共区、后专属区),按 `type` 渲染控件,`select` 回显时把 `value` 反查为 `label`。
|
||
提交时按后端口径统一转成字符串 Map(`Map<String,String>`),避免数字/日期序列化口径不一致。
|
||
|
||
### 3.5 通知流程状态
|
||
`draft` 草稿 → `published` 已发布 → `closed` 已截止 → `finished` 已结束。
|
||
草稿不产生回执记录、家长端不可见;撤回(`UserPull`)把状态置为 `closed` 并标 `isPulled=1`,家长端不再展示,但**已提交的回执记录保留**以便追溯。
|
||
|
||
---
|
||
|
||
## 四、回执情况页
|
||
|
||
- 顶部:通知选择(仅列出 `receiptMode != none` 且非草稿的本部门通知)+ 状态过滤(全部/已确认/已填报/已取消/未回执)+ 催办 + 导出。
|
||
- 汇总条:应收(住宿生)/ 已回执 / 已确认 / 已填报 / 已取消 / 未回执 / 回执率 / **覆盖宿舍数** / **覆盖床位数**。
|
||
- 名单:按 `楼栋名 · 房间号` 分组,组内每名学生一行 —— 学生 / 床位 / 家长 / 电话 / 关系 / **回执结论**(公共区,独立成 Tag 列)/ **单据登记项**(专属区字段展开)/ 状态 / 回执时间 / 备注。
|
||
- 行操作:
|
||
- **回执详情** —— 弹窗展示家长信息 + 按「公共区 / 专属区」两段展开的填报内容 + 已读时间;
|
||
- **代为确认** —— 家长电话口头确认等系统外已告知的情形,管理员代录(需填报类会同时写入结论,避免产生「已填报但内容为空」的空单);
|
||
- **取消/撤回** —— 家长申请不参与(不留宿、不探视)时置为已取消并留痕;
|
||
- **退回未回执** —— 置回 `none`,家长端可重新提交。
|
||
- 导出 CSV:列顺序固定为 `房间/床位/学生/班级/单据类型/家长/关系/电话` + **公共区(回执结论)** + **专属区字段** + `状态/时间/备注`,带 BOM 供 Excel 直接打开;同类单据导出的列完全相同,可纵向合并。
|
||
|
||
---
|
||
|
||
## 五、宿舍设置页(3 个 Tab)
|
||
|
||
1. **通知类型** —— `TypeSetBox` 卡片 + `vuedraggable` 拖动排序;启停状态影响发布下拉,删除不影响历史通知。
|
||
2. **宿舍楼栋房间** —— 左侧楼栋台账(房间数、入住率、宿管),右侧该楼房间表格(`房号 / 楼层 / 入住 N/M 床(满)/ 宿舍长 / 宿管`)。
|
||
- 新增楼栋按「楼层数 × 每层房间数 × 每间床位数」批量铺房间,新楼栋按**空楼**创建(`usedBeds=0`)——真实流程是先建房间、再由床位安排流程写入住宿生;
|
||
- 新增房间从该层已有房号往后追加,避免重号;
|
||
- 改床位数收紧时,装不下的学生**退回待安排池**(不再凭空裁掉);
|
||
- 删除房间/楼栋均有保护:房间内仍有学生、楼栋下仍有房间时后端拒绝并给出可读原因。
|
||
3. **自定义组** —— 宿管 / 生活老师联系组,成员从基础平台教师账号中选取(`UserCheckModal`)。
|
||
|
||
### 5.1 房间人员(住宿生)与宿舍长
|
||
|
||
「人员怎么进房间」在本模块只有一条链路,**全部收在房间行的「住宿生/床位」入口里**:
|
||
|
||
| 动作 | 效果 | 约束 |
|
||
| --- | --- | --- |
|
||
| **安排入住** | 从「待安排」名单(学籍在册未分配 / 退宿退回 / 转学插班)勾选学生调入本房 | 不得超过空床位数;按空床位顺序落位;**性别与楼栋校验**(女生楼不收男生,反之亦然) |
|
||
| **调整宿舍** | 房内学生转到其他房间(可跨楼栋) | 目标房需有空床位,满房选项置灰;原房为宿舍长时,身份顺延给同寝第一位 |
|
||
| **退宿** | 学生移出房间并**退回待安排池**(来源标注「退宿待重新安排」) | 退宿后床位号自动顺延补齐,不留空洞床位 |
|
||
|
||
**关键口径:`usedBeds` 恒等于花名册长度**(单一数据源)。早期实现是反过来的 —— 按 `usedBeds` 反向凭空生成学生,导致新建房间/调大床位会「自动变出不存在的人」。现已收口:房间人数只能由床位安排接口驱动。
|
||
|
||
**宿舍长**(`isMonitor`)显式落在花名册数据上,不再靠「数组第一位」隐式约定:
|
||
|
||
- 房间表格「宿舍长」列显示当前宿舍长,未指定显示灰字「未指定」;
|
||
- 每间房**至多 1 人**,在住宿生名单里点「设为宿舍长 / 取消宿舍长」改派(`SetMonitor`,取消传 `studentId=0`);
|
||
- 宿舍长退宿或调出时,身份自动顺延给同寝第一位学生。
|
||
|
||
**待安排学生池**(`PendingStudents`)是安排入住的数据源:初始 46 人(姓名按性别分池,避免「周雨萱(男)」这类错配),配合「来源」列区分新生未分配 / 退宿待重新安排 / 楼栋调整待安置 / 转学插班待安排。
|
||
|
||
---
|
||
|
||
## 六、接口清单
|
||
|
||
统一响应 `{ code, msg, data }`,`code==0` 时 alova 拦截器解包为 `data`。GET 走 query,POST 分两种(`postJSON` 对齐 `@RequestBody`,`postForm` 对齐 `@RequestParam`)。
|
||
|
||
### 基础 / 身份
|
||
| 方法 | 路径 | 说明 |
|
||
| --- | --- | --- |
|
||
| GET | `/api/base/GetUserInfo` | 当前登录用户(教师 / 家长) |
|
||
| GET | `/api/base/GetUserMod` | 已授权菜单(`modId` + `page`) |
|
||
| POST | `/api/base/SetRole` | 端分离切视角(`as=teacher/parent`) |
|
||
| GET | `/api/base/GetSchoolAll` | 教师组织树(联系组选人) |
|
||
| GET | `/api/base/GetDormTree` | 楼栋→楼层→房间树(叶子带 `bedCount` / `usedBeds`) |
|
||
|
||
### 宿舍台账
|
||
`GET /api/dorm/BuildingList`、`GET /api/dorm/RoomList?buildingId=&keyword=`、`GET /api/dorm/RoomStudents?roomId=`(含 `isMonitor`)、
|
||
`POST /api/dorm/AddBuilding|EditBuilding|DelBuilding`、`POST /api/dorm/AddRoom|EditRoom|DelRoom`
|
||
|
||
### 床位安排 / 宿舍长
|
||
| 方法 | 路径 | 说明 |
|
||
| --- | --- | --- |
|
||
| GET | `/api/dorm/PendingStudents?keyword=&gender=` | 待安排学生池(安排入住的数据源) |
|
||
| POST | `/api/dorm/AssignStudent` | 安排入住(`roomId` + `studentIds` 逗号串),先整体校验性别/空床位再落位,避免半截数据 |
|
||
| POST | `/api/dorm/RemoveStudent` | 退宿(`roomId` + `studentId`),学生退回待安排池,床位号顺延 |
|
||
| POST | `/api/dorm/TransferStudent` | 调整宿舍(`studentId` + `fromRoomId` + `toRoomId`),返回 `{ wasMonitor, bedNo }` |
|
||
| POST | `/api/dorm/SetMonitor` | 设 / 取消宿舍长(`roomId` + `studentId`,取消传 `0`) |
|
||
|
||
### 通知
|
||
| 方法 | 路径 | 说明 |
|
||
| --- | --- | --- |
|
||
| GET | `/api/notice/SendList` | 我发布的(`scope=user`)/ 全部通知;支持 keyword、noticeType、level、buildingId、noticeStatus、时间区间、orderByTime |
|
||
| GET | `/api/notice/List` | 家长端收件箱(仅已发布、非未来定时、面向本生房间),额外返回 `unreadCount` |
|
||
| GET | `/api/notice/Detail` | 详情(`type=1` 标读并同步标记该房间学生的已读;`type=0` 管理视角不标读) |
|
||
| GET | `/api/notice/GetDetail` | 编辑回显(不标读) |
|
||
| GET | `/api/notice/FormTemplates` | 单据模板列表(统一回执单选型:公共区 + 各类单据的专属区字段,发布人只能选不能改) |
|
||
| GET | `/api/notice/GetNextPrev` | 上一篇 / 下一篇 |
|
||
| GET | `/api/notice/ReadDetail` | 阅读情况(按楼栋分组,叶子为房间名 + 人数) |
|
||
| POST | `/api/notice/Create` | 新建(JSON body,返回新 id) |
|
||
| POST | `/api/notice/Edit` | 编辑(JSON body,含 `sourceId`) |
|
||
| POST | `/api/notice/Delete` | 删除(JSON 数组 id,级联删回执) |
|
||
| POST | `/api/notice/UserPull` | 撤回 |
|
||
| POST | `/api/notice/SetTopmost` | 置顶 / 取消置顶 |
|
||
| POST | `/api/notice/Expedite` | 催办未读 |
|
||
| POST | `/api/notice/ReceiveRooms` | 追加接收房间(补发) |
|
||
| POST | `/api/notice/InitializeRed` | 一键全部已读 |
|
||
|
||
### 回执 / 填报
|
||
| 方法 | 路径 | 说明 |
|
||
| --- | --- | --- |
|
||
| GET | `/api/notice/Receipts?serid=&status=` | 回执名单 + 汇总(按 `楼栋 · 房间` 分组),含 `formType` / `formTypeDesc` / `formFields` |
|
||
| GET | `/api/notice/ReceiptDetail?receiptId=` | 单条回执详情(含 `fillData`、`formType` 与 `formFields`) |
|
||
| POST | `/api/notice/Review` | 处理回执(JSON):`action=confirm/cancel/reset`;需填报类代确认时会写入公共区结论 |
|
||
| POST | `/api/notice/Remind` | 向未回执家长发送提醒,返回 `{ reminded }` |
|
||
| GET | `/api/notice/ExportReceipts` | 导出 CSV 文本(固定列序:固定列 → 公共区 → 专属区 → 回执信息) |
|
||
| GET | `/api/notice/MyNotices` | 家长端通知箱(含 `buckets: {todo, done, finished, all}`) |
|
||
| GET | `/api/notice/MyNotice?serid=` | 家长端某通知详情(通知信息 + 我的回执 + `formType` + `formFields`),打开即标读 |
|
||
| POST | `/api/notice/SubmitReceipt` | 提交回执 / 填报(JSON:`serid, fillData, guardianName, guardianPhone, relation, remark`;`fillData` 为字段键 → 值) |
|
||
| POST | `/api/notice/CancelReceipt` | 取消回执 |
|
||
|
||
### 统计 / 字典 / 联系组 / 文件
|
||
`GET /api/notice/Statis`、`GET /api/dict/List?type=dept|noticeType`、`GET /api/dict/Sync`、
|
||
`GET /api/dict/ManageList`、`POST /api/dict/Add|Update|Delete|Sort`、
|
||
`GET /api/contacts/GetGroup|GetContactsTea|GetGroupTree`、`POST /api/contacts/AddGroup|EditGroup|DelGroup|SetContactsTea`、
|
||
`POST /api/base/FileUpload`、`POST /api/file/UploadImage`、`GET /api/file/Preview|Down`、`GET /api/notice/AllFileDown`
|
||
|
||
---
|
||
|
||
## 七、mock 数据说明
|
||
|
||
`src/assets/mock/dormapi.ts`(约 2200 行)覆盖上表**全部**接口,要点:
|
||
|
||
- **确定性伪随机**:`seededRand(sin(seed))`,同一房间/通知的入住学生、回执率刷新不跳变,适合演示与截图。
|
||
- **楼栋与房间**:4 栋楼(1/2 号男生 6+5 层×8 间×8 床,3 号女生 4 层×6 间×6 床,4 号武术特长班 3 层×6 间×4 床)。
|
||
`roomId = buildingId*10000 + floor*100 + index`(如 `10305` = 1 号楼 3 层 05 房),房间号即 `305`。
|
||
- **住宿生花名册**:1 床 1 生,含姓名(武校风格,**男/女名字分池**)、班级(文化班 + 套路表演班/散打竞技班/拳击班混合)、床位号、家长姓名/关系/电话、**是否宿舍长 `isMonitor`**(默认首位,可改派)。
|
||
- **待安排学生池**:46 人(`studentId` 段 `900001+`,与房内学生 `roomId*100+i` 不冲突),带来源标注;退宿 / 床位缩减的学生回流到此池。
|
||
- **床位一致性**:`usedBeds` 由花名册长度派生,`rebuildRoomIndex()` 不再按床位数反向生成学生。
|
||
- **通知数据集**:覆盖 13 类通知 × 4 种流程状态 × 3 档回执方式 × 3 种紧急程度,含草稿、已撤回、定时未到、已截止等边界样本。
|
||
- **回执生成**:按通知状态给不同回执率(已结束 ~95%、已发布 ~60%、已截止 ~85%),已结束类回执时间落在截止日前;填报内容按 `formFields` 逐字段造合理值(留宿起止、探视时段、搬迁日期等),并派生 `progressOf()` 汇总(应收/已确认/已填报/已取消/未回执/回执率/房间数/床位数)。
|
||
- **家长端演示身份**:`ME_ROOM = "10305"`(1 号楼 305 室第一位学生),家长端只看该房间命中的通知;`?serid=` 打开即标读。
|
||
- **写操作真实生效**:Create/Edit/Delete/Review/SubmitReceipt/CancelReceipt/ReceiveRooms 以及宿舍台账的 Add/Edit/Del、床位安排的 Assign/Remove/Transfer/SetMonitor 都会改内存数据并同步重建回执,页面刷新后行为一致(重启 dev server 复位)。
|
||
|
||
---
|
||
|
||
## 八、沿用模板的既有约定(踩坑记录)
|
||
|
||
这些是模板项目已经踩过并写进注释的坑,本模块原样继承:
|
||
|
||
1. **`<FormItem required>` 必须配 `prop`** —— 否则字段名被解析成 `undefined`,model 取到 `undefined`,家长**填了值也照样报错**、表单永远提交不了。
|
||
2. **非 string 取值的控件,规则里必须显式写 `type`** —— async-validator 只在规则「除掉 message 后只剩 required」时走 required 校验器;一旦带 `trigger`,会回退到 `default validators[type || 'string']`,再拿 `typeof value !== 'string'` 判类型不符,而**真实报错会被自定义 `message` 覆盖**,于是「明明填了值,红字仍显示『请填写XX』、表单交不上去」,极具误导性。
|
||
- `InputNumber` → `type: "number"`;
|
||
- `DatePicker` → **`type: "date"`**(v-model 给的是 `Date` 对象;async-validator **没有 `datetime` 这个类型**,写上去 `getType()` 直接抛 `Unknown rule type datetime`,整个 validate 崩掉,被 `.catch(() => false)` 吞成「提交失败」);
|
||
- `Select`/`Input`/`Textarea` → string,默认即可。
|
||
- 配套:日期字段在模型里统一存 `Date`、提交时再格式化成 `yyyy-MM-dd` / `yyyy-MM-dd HH:mm`(本地时间手工拼,别 `new Date("2026-09-23")` —— 按 UTC 解析会跑日期),否则 `String(Date)` 会提交成 `"Tue Sep 22 2026 10:33:00 GMT+0800 (中国标准时间)"`,回执名单与导出 CSV 全废。
|
||
- 30 秒定位法:在 node 里用项目自己的 `node_modules/async-validator` 复刻 `FormItem.validate()`(`descriptor[prop] = rules`、`model[prop] = fieldValue`、`{ firstFields: true }`),逐个类型跑一遍,比读代码猜快得多。
|
||
3. **表单模型是稳定的 `reactive` 对象,不能写成 `computed(() => ({...}))`** —— 每次依赖变化生成新对象,而 `FormItem` 校验读的是 `Form.model`,props 要等父组件重渲染才更新,同步 change 校验拿到的还是旧对象(「选了却红字不消失」)。
|
||
4. **`rules` 写成普通常量/`computed` 要分清场景** —— Form 内有 `watch: { rules () { this.validate() } }`,引用一变就全量重校验。
|
||
5. **存草稿只校验标题**,并用 `clearFieldState()` 清掉其它字段的残留红字。
|
||
6. **`postForm` 同时把参数拼进 URL query** —— 后端 `@RequestParam` 收得到,且 `vite-plugin-mock` 不解析 urlencoded body,靠 query 才能拿参。
|
||
7. **面包屑/菜单高亮靠 `meta.parentId`**,发布通知/详情不在菜单树里也能正确高亮父菜单「我发布的」。
|
||
8. **`menuRoute.ts` 做模块配置兜底解析**(平台模块 path 格式不可控),`NAME_ALIASES` 已按宿舍口径改写,并兼容旧 `/notice`、`/dorm`、`/activity` 前缀。
|
||
|
||
---
|
||
|
||
## 九、后续接入后端的改造点
|
||
|
||
1. `vite.config.ts` 的 `/api` 代理 target 换成本模块后端上下文(现仍指向模板的 `familyactivity`)。
|
||
2. `src/assets/config/menu.ts` 是本地菜单兜底,接入后端 `GetUserMod` 后以接口返回为准(`modId` 契约见路由表)。
|
||
3. `src/assets/mock/index.ts` 是 `vite build` 的 prod mock 入口,正式构建不启用 mock(`localEnabled: mode === "mock"`)。
|
||
4. `GetDormTree` 已按 ztree 结构返回,若后续要用 ztree 选房间(而非当前的自绘楼栋+楼层勾选),可直接接入 `src/components/MyTree*.vue`。
|