# /doctor/home 页面端到端审查 (v3) **审查日期**: 2026-08-19 (v3 含修复) **审查范围**: 前端 → 后端 → DB → 原型 **审查者**: Claude Code (page-tech-review skill) > **修订 (v1 → v2)**: > - ✅ P1 数据隔离: `biz_meeting_attendee` 中间表 + mapper EXISTS + controller 注入 (doctor/expert 角色) > - ✅ 欢迎栏显示真实姓名 (`biz_expert.name` → fallback 到 nickName/userName) > > **修订 (v2 → v3)**: > - ✅ "待签署协议" 数据源从 `biz_project_plan` 改到 `biz_meeting_attendee` (按 user_id + 任一未签) > - ✅ `biz_meeting_attendee` 表加 2 字段: `handsign longtext` (Base64 手写签名) + `labor_protocol varchar(500)` (劳务协议 URL) > - ✅ 新建后端 controller + service + mapper + 前端 API > - ❌ 签署 dialog + 手写板 UI **未做** (v3 待修) > - ❌ 新建会议自动写 attendee 中间表 (BizMeetingController.add) **未做** --- ## 0. 组件定位 (四跳链) | 跳 | 命中 | |---|---| | ① 角色菜单 | `AdminLayout.vue:90` `MENU.doctor` → `{ path: '/doctor/home', title: '首页' }` | | ② 路由 | `router/index.js:90` → `name: 'doctor-home', component: () => import('@/views/doctor/Home.vue')` | | ③ 组件 | `ry-vue3/src/views/doctor/Home.vue` | | ④ 原型 | `proto/html/doctor.html:204` `data-page="components/home.html"` → `proto/html/components/home.html` | --- ## 1. 主要功能 + 可见性 ### 1.1 主要功能 页面是 **dashboard 工作台**,4 块内容: | 功能 | 前端入口 | 后端接口 | 数据表 | |---|---|---|---| | 欢迎栏 (实时时钟 + 专家姓名) | `Home.vue:6-15` | `GET /business/expert/profile` | `biz_expert` | | 待参加的会议 | `Home.vue:114` `bizList('meeting', {pageSize: 5})` | `GET /business/meeting/list` | `biz_meeting` + `biz_meeting_attendee` ✅ | | **待签署的协议 (v3 改)** | `Home.vue:126` `listUnsignedMeetingProtocols()` | `GET /business/meetingAttendee/unsigned` | `biz_meeting_attendee` (handsign 或 labor_protocol 为空) ✅ | | 通知消息 | `Home.vue` `listMyMessages({limit: 5})` | `GET /business/message/my` | `biz_message` | 页面**无 CRUD 操作**,纯展示 (v3 计划加签署 dialog 但未做)。 ### 1.2 可见性 (三层过滤) | 层 | 来源 | 校验字段 | |---|---|---| | 前端菜单 | `AdminLayout.vue:90` `MENU.doctor` | 仅 `doctor` 角色可见 | | 路由守卫 | `router/index.js:88` `meta: { role: 'doctor' }` + `permission.js` | token 角色 | | 后端 | 各 controller 注入 + mapper `` 过滤 | 详见 §1.1 | **数据隔离现状** (v3): | 数据块 | 隔离机制 | |---|---| | 欢迎栏 | `biz_expert` 按 token userId | | 待参加会议 | `biz_meeting_attendee` (EXISTS) — controller 注入 `userId` for doctor/expert | | **待签署协议 (v3)** | `biz_meeting_attendee.user_id = current user` (controller `SecurityUtils.getUserId()`) | | 通知消息 | `biz_message.user_id = current user` | --- ## 2. 筛选项 (filter-form) **本页面无 filter-form**,跳过。 --- ## 3. 工具栏按钮 (toolbar) **本页面无 toolbar**,只有 3 个"更多"链接 (`Home.vue:22, 37, 57`)。 --- ## 4. 表格 (el-table) **本页面无表格**,使用 `ul/li` 自定义列表。 --- ## 5. UI 元素映射 ### 5.1 欢迎栏 (`Home.vue:6-15`) | UI | 绑定 | 来源 | |---|---|---| | 用户名 (左) | `displayName` (computed) | 优先级: `biz_expert.name` → `nickName` → `userName` → '专家' | | 当前时间 | `nowTime` (setInterval) | 前端 Date | | 当前日期 | `nowDate` (setInterval) | 前端 Date | ### 5.2 待参加会议列表 (`Home.vue:24-32`) | UI label | 绑定字段 | 后端 SQL | 命中表.字段 | 原型对照 | |---|---|---|---|---| | 会议名 | `m.meetingName` | selectList + `EXISTS biz_meeting_attendee` | `biz_meeting.meeting_name` | ✅ | | 状态/时间 | `formatTime(m.startTime)` | selectList | `biz_meeting.start_time` | ✅ | ### 5.3 待签署协议列表 (`Home.vue:39-49`) — v3 重大改动 | UI label | 绑定字段 | 后端 SQL | 命中表.字段 | 原型对照 | |---|---|---|---|---| | 协议名 | `s.meetingName` | `selectUnsignedByUserId` JOIN `biz_meeting` | `biz_meeting.meeting_name` (via JOIN) | ⚠️ 原型写"协议/意见书",实际显示会议名 | | 状态 | 固定 "待签署" | — | — | ⚠️ 实现只显示"待签署",原型有"已完成" | **v2 → v3 数据流变化**: - **v2 (错误)**: `GET /business/projectPlan/list` → 按 `submitter_id` 过滤 (这是"投稿方案"语义, 不是劳务协议) - **v3 (正确)**: `GET /business/meetingAttendee/unsigned` → 当前用户 + 任一未签 → JOIN biz_meeting 取会议名 **API** (`ry-vue3/src/api/business/meetingAttendee.js`): ```js export function listUnsignedMeetingProtocols() { return request({ url: '/business/meetingAttendee/unsigned', method: 'get' }) } export function updateHandsign(id, handsign) { ... } export function updateLaborProtocol(id, laborProtocol) { ... } ``` **后端 mapper SQL** (核心): ```sql select a.id, a.meeting_id, a.user_id, a.handsign, a.labor_protocol, m.meeting_name as meetingName, m.start_time as startTime, m.end_time as endTime, m.project_name as projectName, m.project_no as projectNo from biz_meeting_attendee a inner join biz_meeting m on m.meeting_id = a.meeting_id where a.user_id = #{userId} and (a.handsign is null or a.handsign = '' or a.labor_protocol is null or a.labor_protocol = '') order by m.start_time asc ``` **判定逻辑 (决策 A1)**: 任一未签即"待签署"。两条都有值就排除。 ### 5.4 通知消息列表 (`Home.vue:59-69`) 略,同 v2 报告。 --- ## 6. Java Entity ↔ 数据库表 一致性 ### 6.1 biz_meeting_attendee (v3 实测, 2026-08-19) ```sql CREATE TABLE biz_meeting_attendee ( id bigint NOT NULL AUTO_INCREMENT COMMENT '主键', meeting_id bigint NOT NULL COMMENT '会议ID (FK biz_meeting.meeting_id)', user_id bigint NOT NULL COMMENT '参会人 user_id (FK sys_user.user_id)', handsign longtext COMMENT '手写签名 Base64', labor_protocol varchar(500) DEFAULT NULL COMMENT '劳务协议 URL (OSS)', create_by varchar(64) DEFAULT '' COMMENT '添加人', create_time datetime DEFAULT NULL COMMENT '添加时间', PRIMARY KEY (id), UNIQUE KEY uk_meeting_user (meeting_id, user_id), KEY idx_user_id (user_id) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='会议参会人'; ``` ### 6.2 BizMeetingAttendee Entity ↔ DB 字段对照 | 实体字段 | 中文列名 | Java 类型 | 表字段 | DB 类型 | 一致? | |---|---|---|---|---|---| | `id` | 主键 | `Long` | `id` | bigint AUTO_INCREMENT | ✅ | | `meetingId` | 会议ID | `Long` | `meeting_id` | bigint | ✅ | | `userId` | 参会人 user_id | `Long` | `user_id` | bigint | ✅ | | **`handsign`** | 手写签名 Base64 | `String` | `handsign` | longtext | ✅ (v3 新增) | | **`laborProtocol`** | 劳务协议 URL (OSS) | `String` | `labor_protocol` | varchar(500) | ✅ (v3 新增) | | `createBy` (BaseEntity) | 添加人 | `String` | `create_by` | varchar(64) | ✅ | | `createTime` | 添加时间 | `Date` | `create_time` | datetime | ✅ | | `meetingName` (transient) | — | `String` | JOIN `m.meeting_name` | — | ✅ (联表字段) | | `startTime` (transient) | — | `Date` | JOIN `m.start_time` | — | ✅ | | `endTime` (transient) | — | `Date` | JOIN `m.end_time` | — | ✅ | | `projectName` (transient) | — | `String` | JOIN `m.project_name` | — | ✅ | | `projectNo` (transient) | — | `String` | JOIN `m.project_no` | — | ✅ | **5 个 transient 联表字段** 接收 `selectUnsignedByUserId` 的 JOIN 输出, 给前端用。 --- ## 7. 索引检查 ### 7.1 biz_meeting_attendee | Key | 列 | 用途覆盖 | |---|---|---| | PRIMARY | `id` | ✅ | | uk_meeting_user | `meeting_id`, `user_id` (UNIQUE) | ✅ 防重复 + 精确查 | | idx_user_id | `user_id` | ✅ `selectUnsignedByUserId WHERE user_id = ?` 走这个索引 | **评价**: 复合查询 (`WHERE user_id = X AND (handsign IS NULL OR labor_protocol IS NULL)`) 走 idx_user_id,然后行过滤。10k 行内完全 OK。 ### 7.2 缺什么? - ❌ **缺 `idx_user_status`**: 如果会议量大且空记录占比小, 想加速"待签署"查询, 可以加 `(user_id, handsign IS NULL, labor_protocol IS NULL)` 函数索引。但目前数据少, 暂不需要。 - ❌ **缺 `idx_meeting_status`**: `WHERE meeting_id = X AND handsign IS NULL` 场景目前不存在 (管理端"看哪些医生没签"的视图未做), 暂不需要。 --- ## 8. 与原型差异 ### 8.1 实现新增 (超出原型) | 项 | 实现 | 原型 | |---|---|---| | 实时时钟 | ✅ | ✅ 静态 | | 通知标记已读 | ✅ | ❌ | | "更多"链接跳 Vue 路由 | ✅ | ❌ 静态 HTML | | 空状态文案 | ✅ | ❌ | | 欢迎栏显示真实姓名 | ✅ (v2) | ❌ 静态写死 | | **数据源按 user 隔离** | ✅ (v2 加中间表) | ❌ | | **劳务协议按会议关联** | ✅ (v3 改 biz_meeting_attendee) | ❌ | ### 8.2 原型有但实现缺失 | 项 | 原型 | 实现 | 严重度 | |---|---|---|---| | item-icon 图标 (📅 📄) | ✅ | ❌ 无 | P2 | | section-title 左侧蓝条 | ✅ | ❌ 无 | P2 | | 欢迎栏渐变背景 | ✅ | ⚠️ 单色 | P3 | | **"已完成" 状态显示** (signed status) | ✅ `item-status.done` | ❌ 实现永远显示"待签署" | **P1** | | **签署交互** (点列表 → 弹 dialog → 上传 handsign + labor_protocol) | ✅ 原型点击 alert | ❌ 无 dialog, 点列表没反应 | **P1** | ### 8.3 文字 / 标签差异 | 项 | 原型 | 实现 | |---|---|---| | 协议状态文案 | "待签署 / 已完成" | "待签署" (永远) | | 协议名显示 | "《方案名》专家意见书" | "会议名" (会议关联, 语义不同) | ### 8.4 总结 **v3 整体方向**: 数据语义修正 (劳务协议按会议关联 ✅), 视觉 + 交互仍待补。 --- ## 9. 字段冗余 / 设计问题 ### 9.1 biz_meeting_attendee 设计合理 (v3 验证) - `handsign longtext` 存 Base64 是合理的: 不依赖 OSS, 数据完整性内嵌 DB, 简单 - `labor_protocol varchar(500)` 存 OSS URL 是合理的: 大文件走 OSS, DB 只存引用 ### 9.2 缺管理端点 ⚠️ P1 `BizMeetingAttendeeController` 目前只有 doctor 用的 3 接口。**新建会议时, 没有 API 自动把参会人写入中间表**。 - 影响: 现有数据需要手动 INSERT (像之前那样用 SQL), 业务没法在线给会议加参会人 - 修复: `BizMeetingController.add` 接受 `attendeeUserIds: Long[]`, 批量 insert 中间表 ### 9.3 缺管理列表 (P3) 没有"看某会议的所有参会人 + 签署状态"的后台接口 (manager 端要用)。 - 影响小 (低优先级), 当前 P1 先修 ### 9.4 biz_project_plan 完全废弃? ⚠️ v3 把"待签署协议"从 biz_project_plan 改走中间表, 但 `biz_project_plan` 表和 `BizProjectPlan` entity 还在。 - 投稿方案业务 (manager 审稿) 仍存在, 这是独立的 "项目策划方案投稿" 业务 - 与劳务协议是两回事, 不应混淆 ### 9.5 biz_meeting.status 缺失 (沿 v2 待修) 业务希望"待签署协议"按 `meeting.status` 过滤 (会议取消/已结束的不再显示待签), 但 `biz_meeting` 无 status 字段, 需要 ALTER。 --- ## 10. 待修复列表 (v3) | # | 问题 | 文件 | 修复建议 | 严重度 | 状态 | |---|---|---|---|---|---| | ~~1~~ | ~~`biz_meeting` 无 user 隔离~~ | ~~Controller + mapper~~ | ~~中间表 EXISTS~~ | ~~P1~~ | ✅ v2 修 | | ~~2~~ | ~~欢迎栏显示数字 userName~~ | ~~Home.vue~~ | ~~displayName computed 优先级链~~ | ~~P2~~ | ✅ v2 修 | | ~~3~~ | ~~劳务协议数据源错 (biz_project_plan)~~ | ~~Home.vue + BizProjectPlanController~~ | ~~改走 biz_meeting_attendee~~ | ~~P0~~ | ✅ **v3 修** | | ~~4~~ | ~~表无 handsign/labor_protocol~~ | ~~biz_meeting_attendee~~ | ~~ALTER 加 2 列 + 后端全套接口~~ | ~~P0~~ | ✅ **v3 修** | | 6 | **签署 dialog 未做**: 点"待签署"列表项无反应 | `Home.vue` | 弹 dialog: handsign 用 textarea 输入 Base64 (临时), labor_protocol 用 OssImageUploader, 提交分别 PUT | **P1** | 待修 | | 7 | **手写板 canvas 组件未做**: handsign 暂用 textarea 输入 Base64, 用户体验差 | 新建 `SignaturePad.vue` | vue-signature-pad 库 或 自写 canvas + toDataURL() | P2 | 待修 | | 8 | item-icon 图标缺失 (📅 📄) | `Home.vue` | 加 `.item-icon` 样式 + 模板 | P2 | 待修 (v1 提的) | | 9 | section-title 左侧蓝条缺失 | `Home.vue:151` | border-left + padding-left | P2 | 待修 (v1 提的) | | 10 | 欢迎栏渐变背景降级为单色 | `Home.vue:145` | 还原 gradient | P3 | 待修 (v1 提的) | | 11 | `biz_project_plan.status` 注释缺失 | DB | ALTER COMMENT | P3 | 待修 | | 12 | `biz_meeting.status` 不存在 (无法过滤"已结束"会议) | DB + mapper | ALTER 加 status 字段 | P3 | 待修 | | 13 | `Home.vue:131` `read: a.read \|\| i >= 2` 强制第3条后标已读 | `Home.vue` | 改为纯 `read: a.read` | P3 | 待修 | | 14 | **新建会议自动写 attendee 中间表**: BizMeetingController.add 不支持传 attendeeUserIds | `BizMeetingController.java` + service | add 接受 `attendeeUserIds: Long[]`, 批量 insert 中间表 | **P1** | 待修 | | 15 | 缺管理端"看参会人列表 + 签署状态"接口 | 新建 `BizMeetingAttendeeController` 管理端点 | `GET /business/meetingAttendee/byMeeting/{meetingId}` | P3 | 待修 | --- ## 11. 引用清单 ### v3 新增/改的文件 | 文件 | 行号 | 说明 | |---|---|---| | **DB** `biz_meeting_attendee` | — | ALTER 加 `handsign longtext` + `labor_protocol varchar(500)` | | `ry-api/.../controller/BizMeetingAttendeeController.java` | 全文 | **v3 新建** | | `ry-api/.../service/IBizMeetingAttendeeService.java` | 全文 | **v3 新建** | | `ry-api/.../service/impl/BizMeetingAttendeeServiceImpl.java` | 全文 | **v3 新建** | | `ry-api/.../domain/BizMeetingAttendee.java` | 全文 | **v3 加 handsign + laborProtocol + 5 个 transient 联表字段** | | `ry-api/.../mapper/BizMeetingAttendeeMapper.java` | 全文 | **v3 加 updateHandsign + updateLaborProtocol + selectUnsignedByUserId** | | `ry-api/.../mapper/business/BizMeetingAttendeeMapper.xml` | 全文 | **v3 加 resultMap 字段 + 2 update + selectUnsignedByUserId 联表 SQL** | | `ry-vue3/src/api/business/meetingAttendee.js` | 全文 | **v3 新建**: 3 API helpers | | `ry-vue3/src/views/doctor/Home.vue` | 39-49, 124-134 | **v3 改 "待签署协议" 数据源** | ### 未改的文件 (沿 v2) | 文件 | 行号 | 说明 | |---|---|---| | `ry-vue3/src/layout/AdminLayout.vue` | 90 | `MENU.doctor` 第 1 项 | | `ry-vue3/src/router/index.js` | 88-99 | `/doctor` 父路由 | | `proto/html/doctor.html` | 204 | 原型菜单 | | `proto/html/components/home.html` | 全文 | 原型基准 | | `ry-vue3/src/api/public.js` | — | listMyMessages / bizList | | `ry-vue3/src/api/business/expert.js` | 全文 | v2 新建: getMyExpertProfile | | `ry-api/.../controller/BizMeetingController.java` | 23-33 | v2 改: 加 userId 注入 | | `ry-api/.../controller/BizProjectPlanController.java` | — | 不再被"待签署协议"调用, 投稿方案独立业务 | | `ry-api/.../controller/BizExpertController.java` | 46-50 | 欢迎栏取名 | | MySQL `guoju0808.biz_meeting` | — | v2 无改动 | | MySQL `guoju0808.biz_meeting_attendee` | — | **v3 加 2 列** | | MySQL `guoju0808.biz_message` | — | 通知 | | MySQL `guoju0808.biz_expert` (user_id=142) | — | name='dct07' | --- ## v3 总结 **修复 2 项 (跨 v2 → v3)**: 1. ✅ **数据语义修正**: "待签署协议" 从 `biz_project_plan` 改到 `biz_meeting_attendee` (语义正确: 劳务协议按会议关联, 不按投稿方案) 2. ✅ **新字段 + 后端**: `handsign longtext` + `labor_protocol varchar(500)` + 完整后端 CRUD 接口 + 前端 API **还剩 11 项待修复**,按严重度: - **P1 (2 项)**: 签署 dialog 缺失 (点列表没反应); 新建会议没自动写 attendee - **P2 (3 项)**: 手写板 UI; item-icon; section-title 边框 - **P3 (6 项)**: 字段注释 + status 字段 + 索引 + 前端兜底 + 管理端点 **SQL 实测验证** (user_id=142 当前状态): ``` 待签署会议: 3 个 (id=1,2,3 全部 handsign=NULL labor_protocol=NULL) 会议名: 整合医学学会项目评审会 / 基层医疗改革试点中期评估会 / 数字化医疗转型方案评审会 按 start_time 升序 ✓ ```