主要改动: - biz_meeting_attendee 中间表 + doctor/expert 角色数据隔离 - biz_meeting_attendee 加 handsign/labor_protocol 字段 (劳务协议改造) - biz_labor_protocol_template 配置表 + admin 页面 (从 HeguiConstants 迁移) - biz_expert.expertId 改 Long + IdGenerator 生成 (去自增) - Login.vue / PublicityDetail.vue / Home.vue 等多处 bug 修复 + UI 改进 - 新增 page-tech-review 报告 3 篇 (_self/*.md) Co-Authored-By: Claude <noreply@anthropic.com>
16 KiB
/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 <if> 过滤 |
详见 §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):
export function listUnsignedMeetingProtocols() {
return request({ url: '/business/meetingAttendee/unsigned', method: 'get' })
}
export function updateHandsign(id, handsign) { ... }
export function updateLaborProtocol(id, laborProtocol) { ... }
后端 mapper 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)
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)
| # | 问题 | 文件 | 修复建议 | 严重度 | 状态 |
|---|---|---|---|---|---|
biz_meeting 无 user 隔离 |
✅ v2 修 | ||||
| ✅ v2 修 | |||||
| ✅ v3 修 | |||||
| ✅ 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):
- ✅ 数据语义修正: "待签署协议" 从
biz_project_plan改到biz_meeting_attendee(语义正确: 劳务协议按会议关联, 不按投稿方案) - ✅ 新字段 + 后端:
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 升序 ✓