Files
guoju0808/_self/manager_meetings.md
T
郭庆泰 c3eb8ed9c3 feat: OCR 服务 + 会议材料模块
ry-ocr/ (新)
  本地发票识别微服务 (PaddleOCR 3.x + FastAPI, 8801)
  - QR 优先: 扫到二维码即取开票时间/发票号/金额; 没扫到/格式不合法直接判非发票, 不跑 OCR
  - 配置 QR_FULL_OCR 控制快路径(false, 0.2s)还是全字段(true, 4.5s)
  - /recognize/invoice (multipart) + /recognize/invoice/by-path (本地路径, 白名单) + /recognize/text
  - is_invoice / from_qr / qr_raw / qr_error / error_code 字段
  - 12 字段发票抽取 (regex + 启发式, 左右主体识别)
  - 超时保护 (15s 单页 / 60s 总流程) + PaddleOCR 单例 + ThreadPoolExecutor

ry-api/ruoyi-business/
  - pom.xml: 加 hutool-http/json/core 5.8.27, lombok 1.18.30 (OcrClient @Slf4j 所需)
  - ocr/: OcrClient + InvoiceResult/Fields/Line + ZipExtractor + InvoiceOcrScheduler
  - oss/: OssUploader + OssConfMeta (OCR 识别后重传 OSS)
  - config/: OcrConfig + OcrExecutorConfig (后台线程池)
  - service/impl/InvoiceOcrService: 后台提交 OCR, ZIP 路径解压识别, 替换场景先清旧
  - 会议材料 CRUD 全套 (BizMeetingAuditLog/Executor/Invoice/Material/Supervisor):
    controller + service + mapper + domain + xml

ry-vue3/
  - MeetingDetail.vue (新建): 会议详情页 (含评分维度章节, 改只读)
  - Meetings.vue / OssFileUploader.vue / router / Login.vue: 适配新字段

ry-api/ruoyi-admin/
  - RuoYiApplication.java + application.yml: 启用 @Async 异步支持

_self/
  - manager_meetings.md / manager_meeting_detail.md: 文档
2026-08-22 00:23:22 +08:00

443 lines
28 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.
# manager/meetings — 端到端技术审查
> **审查时间**: 2026-08-21 (v3)
> **触发场景**: 用户访问 `/manager/meetings` (manager 角色), 修复 label/期数/placeholder + 后端 long→int + 查看按钮跳页面
> **审查范围**: `ry-vue3/src/views/manager/Meetings.vue` (会议管理列表页) + `MeetingDetail.vue` (新) + 后端 BizMeeting 全链路 + DB 实测
> **对比原型**: `proto/html/components/meeting-manage.html` (manager.html:207 链接, manager 角色专用原型)
> **修订**: v3 覆盖 v2, "查看"按钮从 dialog 改为独立路由 `/manager/meetings/detail/:meetingId` (MeetingDetail.vue)
---
## 0. 4 跳定位 (铁律: 不按名字猜)
| 跳 | 文件:行 | 内容 |
|---|---|---|
| ① 角色菜单 | `ry-vue3/src/layout/AdminLayout.vue:97` | `path: '/manager/meetings', title: '会议管理'` ✓ |
| ② 路由 | `ry-vue3/src/router/index.js:65-66` | `name: 'manager-meetings' → @/views/manager/Meetings.vue` + `name: 'manager-meetings-new' → MeetingNew.vue` ✓ |
| ③ 组件 | `ry-vue3/src/views/manager/Meetings.vue` (256 行) | 列表 + 筛选 + 批量提交 + 查看/修改/提交/复制 |
| ④ 原型 | `proto/html/components/meeting-manage.html` (618 行, manager.html:207 `data-page="components/meeting-manage.html"`) | 12 列表格 + 8 项筛选 + 批量提交按钮 |
**修订**: v1 误把原型记为 `meetings.html` (doctor 端 "我参与的会议"). 实测 manager.html:207 的 `data-page` 指向 `meeting-manage.html`, 才是 manager 角色的真正原型. 实体筛选/列名以 `meeting-manage.html` 为准.
---
## 1. 主要功能 + 可见性
### 1.1 主要功能
| 功能 | 前端入口 | 后端接口 | 数据表 |
|---|---|---|---|
| 筛选 + 分页列表 | `Meetings.vue` `<el-table>` + `<el-pagination>` | `GET /business/meeting/list` (`BizMeetingController.java:26-37`) | `biz_meeting` |
| 查看详情 (弹窗) | `onView(row)``el-dialog` + `el-descriptions` 只读 | 复用列表 row, 无额外请求 | `biz_meeting` |
| 修改会议 (跳独立页) | `onEdit(row)` → router.push → `MeetingNew.vue` | `PUT /business/meeting` | `biz_meeting` |
| 提交 (单条) | `onSubmit(row)` → confirm → bizUpdate | `PUT /business/meeting` | `biz_meeting.current_stage` |
| 复制 (单条) | `onCopy(row)` → router.push MeetingNew `?mode=copy` | `POST /business/meeting` | `biz_meeting` 新行 |
| 批量提交 | `onBatchSubmit()` → for 循环逐条 bizUpdate | `PUT /business/meeting` × N | `biz_meeting.current_stage` |
### 1.2 可见性 (三层过滤)
| 层 | 来源 | 校验字段 |
|---|---|---|
| 前端菜单 | `AdminLayout.vue:97` | 角色 = manager |
| 路由守卫 | `router/index.js:65-66` | `meta.title` (无 role 字段, 由 menu 拦截) |
| 后端 SQL | `BizMeetingMapper.xml:37 selectList` | **❌ 无任何过滤** — 后端全量返回 (manager/doctor/expert 都共用同一接口, doctor/expert 走 `setUserId` 走 attendee 中间表过滤, manager 不隔离) |
**数据隔离**: ❌ **缺失** — manager 端没有 `@DataScope``lead_user_id` 过滤。当前 DB 9 行测试数据无风险, 生产环境会暴露全部会议 (见 §10 #1)。
**看不到本页面的人群**: admin / doctor / executor / sponsor 各有独立 `/<role>/meetings` 路由 + 独立菜单项, 路由层就分开了。
---
## 2. 筛选项 (filter-form)
| UI label | 控件 | 绑定字段 | 后端 `<if>` | 命中表.字段 | 选项来源 | 原型对照 |
|---|---|---|---|---|---|---|
| 项目编号 | el-input (模糊) | `q.projectNo` | ✅ `project_no LIKE concat('%',...)` | `biz_meeting.project_no` | — | ✅ 项目编号 |
| 会议ID | el-input (精确) | `q.meetingId` | ✅ `meeting_id = #{meetingId}` | `biz_meeting.meeting_id` | — | ✅ 会议ID (原型有) |
| 会议名称 | el-input (模糊) | `q.meetingName` | ✅ `meeting_name LIKE concat('%',...)` | `biz_meeting.meeting_name` | — | ✅ 会议名称 |
| 期数(第几期) | **el-input** (文本, 今日改) | `q.periodNo` | ✅ `period_no = #{periodNo}` | `biz_meeting.period_no` | — | ✅ "第?期" (原型有) |
| 会议时间 (start~end) | el-date-picker × 2 | `q.startTime`, `q.endTime` | ✅ `start_time >=` + `end_time <=` | `biz_meeting.start_time/end_time` | — | ❌ 原型无 (实现增) |
| 项目形式 | el-select | `q.projectForm` | ✅ `project_form =` | `biz_meeting.project_form` | **写死** 4 项 (线上/线下/线上+线下/其他) | ❌ 原型无 (实现增) |
| 当前阶段 | el-select | `q.currentStage` | ✅ `current_stage =` | `biz_meeting.current_stage` | **写死** 4 项 (未执行/执行中/已执行/已完结) ⚠️ | ✅ 原型有 (但原型是 6 项含 待审核/待结算/冻结中) |
| 备注 | el-input (模糊) | `q.remark` | ❌ **缺失**`biz_meeting``remark` 列 | (无字段) | — | ❌ 原型无 |
**完整 SQL 块** (`BizMeetingMapper.xml:37-52`):
```sql
select meeting_id, business_id, project_id, project_no, project_name,
meeting_name, period_no, total_periods, project_form,
start_time, end_time, org_name, current_stage,
supervision_opinion, supervision_by, supervision_time,
invitation_url, schedule_url, labor_signed,
create_by, create_time, update_by, update_time
from biz_meeting
where [project_no LIKE] [AND meeting_id =] [AND meeting_name LIKE]
[AND period_no =] [AND project_form =] [AND current_stage =]
[AND start_time >=] [AND end_time <=]
[AND userId EXISTS attendee] (doctor/expert )
order by meeting_id desc
```
8 项 filter 中 7 项生效; **remark filter 是死路** — 实体/表/INSERT 都没有此字段 (见 §6.3, §9 #3)。
---
## 3. 工具栏 (filter-form / batch-bar)
| 按钮 | 功能 | 接口 | 涉及表 | 原型对照 |
|---|---|---|---|---|
| 查找 | 触发 `load()` → bizList | `GET /business/meeting/list?...` | `biz_meeting` | ✅ 查找 |
| 重置 | 清空 q → load | 同上 | — | ❌ 原型无 |
| 批量提交 | `onBatchSubmit()` 逐条 update currentStage='执行中' | `PUT /business/meeting` × N | `biz_meeting.current_stage` | ✅ 原型有 ("批量提交:后端待对接") |
**提示行**: `<span v-if="selectedIds.length" class="filter-tip">已选 N 条</span>` — 推到 batch-bar 最右。
---
## 4. 表格 (el-table)
### 4.1 数据来源
见 §2 完整 SQL 块。
### 4.2 列映射
| 列名 | row.* | DB 表.字段 | 原型对照 |
|---|---|---|---|
| 多选框 | (selection) | — | ✅ 原型有 checkbox 列 |
| 项目编号 | `row.projectNo` | `biz_meeting.project_no` | ✅ 项目编号 |
| 会议ID | `row.meetingId` | `biz_meeting.meeting_id` | ✅ 会议ID |
| 项目形式 | `row.projectForm` | `biz_meeting.project_form` | ❌ 原型无 |
| 会议名称 | `row.meetingName` | `biz_meeting.meeting_name` | ✅ 会议名称 |
| 会议开始时间 | `row.startTime` (fmtTime) | `biz_meeting.start_time` | ✅ 会议开始时间 |
| 会议结束时间 | `row.endTime` (fmtTime) | `biz_meeting.end_time` | ✅ 会议结束时间 |
| 总期数 | `row.totalPeriods` | `biz_meeting.total_periods` | ✅ 总期数 |
| 期数 | `row.periodNo` ("第 X 期" / "-") | `biz_meeting.period_no` | ✅ 期数 |
| 当前阶段 | `row.currentStage` (stage-tag) | `biz_meeting.current_stage` | ✅ 当前阶段 |
| 备注 | `row.remark` (永远空, 见 §9 #3) | (无) | ✅ 备注 (原型有) |
| 操作 | 查看/修改/提交/复制 | — | ✅ 原型有 提交/查看/修改/复制 |
**新增列 (原型无, 实现增) ✅ 改进**: 无。
**遗漏列 (DB 有, 列表未显示)**:
- `org_name` (公司名称) — 列表可加
- `create_time` / `update_time` — 审计可用
- `labor_signed` (0/1) — 原型无, 实体有
- `invitation_url` / `schedule_url` — 列表可加链接
---
## 5. 行内操作按钮
| 按钮 | 触发函数 | 接口 | 后端动作 | 涉及表.字段 | **跳转形式** | 原型对照 |
|---|---|---|---|---|---|---|
| 查看 | `viewDetail(row)` | `GET /business/meeting/{meetingId}` (在 MeetingDetail.vue) | router.push → MeetingDetail.vue 独立页加载详情 | `biz_meeting` 全字段 | **page ✅** | ✅ 查看 |
| 修改 | `onEdit(row)` → router.push `manager-meetings-new?meetingId=...&projectId=...` | `PUT /business/meeting` (在 MeetingNew) | UPDATE biz_meeting SET 多字段 WHERE meeting_id=? | `biz_meeting` | **page ✅** | ✅ 修改 |
| 提交 | `onSubmit(row)` | `PUT /business/meeting` | UPDATE biz_meeting SET current_stage='执行中' WHERE meeting_id=? | `biz_meeting.current_stage` | **page** (改 DB) | ✅ 提交 |
| 复制 | `onCopy(row)` → router.push `manager-meetings-new?...&mode=copy` | `POST /business/meeting` (在 MeetingNew) | INSERT biz_meeting (...); meetingId AUTO_INCREMENT, businessId 兜底雪花 ID | `biz_meeting` (新行) | **page ✅** | ✅ 复制 |
**真实落点**:
- **"提交"**: 直接 `UPDATE current_stage = '执行中'`, 无业务校验 (任何状态都能转, 包括"已完结"→"执行中" 状态回滚)。不写日志 (`biz_meeting_log` 表不存在)。
- **"修改" / "复制"**: 跳 `MeetingNew.vue` 独立页处理 (`Meetings.vue:106` 注释明确说明), form 含 9 字段, 见 manager_meeting_new.md。
---
## 6. Java Entity ↔ 数据库表 一致性
### 6.1 biz_meeting DDL (实测 2026-08-21)
```sql
CREATE TABLE `biz_meeting` (
`meeting_id` bigint NOT NULL AUTO_INCREMENT COMMENT '会议ID',
`business_id` varchar(50) NOT NULL COMMENT '业务会议ID 5643145673',
`project_id` bigint DEFAULT NULL COMMENT '所属项目ID',
`project_no` varchar(50) DEFAULT NULL COMMENT '项目编号',
`project_name` varchar(200) DEFAULT NULL COMMENT '项目名称',
`meeting_name` varchar(200) DEFAULT NULL COMMENT '会议名称',
`period_no` int DEFAULT '1' COMMENT '期数',
`total_periods` int DEFAULT '1' COMMENT '总期数',
`project_form` varchar(20) DEFAULT NULL COMMENT '项目形式',
`start_time` datetime DEFAULT NULL COMMENT '会议开始时间',
`end_time` datetime DEFAULT NULL COMMENT '会议结束时间',
`org_name` varchar(200) DEFAULT NULL COMMENT '公司名称(冗余)',
`address` varchar(500) DEFAULT NULL COMMENT '会议地址',
`current_stage` varchar(20) DEFAULT '0' COMMENT '当前阶段 未执行/待监管/监管通过/待整改/待结算/已结算/已结题',
`supervision_opinion` varchar(500) DEFAULT NULL COMMENT '监察意见',
`supervision_by` varchar(64) DEFAULT NULL COMMENT '监察人',
`supervision_time` datetime DEFAULT NULL COMMENT '监察时间',
`invitation_url` varchar(500) DEFAULT NULL COMMENT '邀请函URL',
`schedule_url` varchar(500) DEFAULT NULL COMMENT '日程海报URL',
`labor_signed` char(1) DEFAULT '0' COMMENT '签署劳务 0未签 1已签',
`create_by` varchar(64) DEFAULT '' COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`meeting_id`),
UNIQUE KEY `uk_business_id` (`business_id`),
KEY `idx_meeting_project` (`project_id`),
KEY `idx_meeting_stage` (`current_stage`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会议表';
```
**实测数据**: 9 行 (今日) / 7 行 (08-18) / AUTO_INCREMENT=12 (说明中间有删除)
### 6.2 关联表
- `biz_project` — 见 `manager_projects.md` §6, 含 `create_user_id bigint`
- `biz_meeting_attendee` — doctor/expert 角色过滤的中间表 (见 `BizMeetingMapper.xml:49 exists`)
### 6.3 Entity ↔ 表字段对照表
| 实体字段 | 中文列名 (DB COMMENT) | 实体类型 | 表字段 | 表类型 | 一致? |
|---|---|---|---|---|---|
| meetingId | 会议ID | Long | meeting_id | bigint | ✅ |
| businessId | 业务会议ID 5643145673 | String | business_id | varchar(50) | ✅ |
| projectId | 所属项目ID | Long | project_id | bigint | ✅ |
| projectNo | 项目编号 | String | project_no | varchar(50) | ✅ |
| projectName | 项目名称 | String | project_name | varchar(200) | ✅ |
| meetingName | 会议名称 | String | meeting_name | varchar(200) | ✅ |
| periodNo | 期数 | **Long** | period_no | **int** | ⚠️ 类型不一致 (MyBatis 自动转 OK) |
| totalPeriods | 总期数 | **Long** | total_periods | **int** | ⚠️ 类型不一致 (同上) |
| projectForm | 项目形式 | String | project_form | varchar(20) | ✅ |
| startTime | 会议开始时间 | Date | start_time | datetime | ✅ (有 @JsonFormat + @DateTimeFormat) |
| endTime | 会议结束时间 | Date | end_time | datetime | ✅ |
| orgName | 公司名称(冗余) | String | org_name | varchar(200) | ✅ |
| **address** | **会议地址** | **String** | **address** | **varchar(500)** | ✅ (实体/表都有, 但 selectFields/insert/update/selectList 全部漏列 — 详见 §9 #6) |
| currentStage | 当前阶段 | String | current_stage | varchar(20) | ✅ |
| supervisionOpinion | 监察意见 | String | supervision_opinion | varchar(500) | ✅ |
| supervisionBy | 监察人 | String | supervision_by | varchar(64) | ✅ |
| supervisionTime | 监察时间 | Date | supervision_time | datetime | ✅ (有 @JsonFormat) |
| invitationUrl | 邀请函URL | String | invitation_url | varchar(500) | ✅ |
| scheduleUrl | 日程海报URL | String | schedule_url | varchar(500) | ✅ |
| laborSigned | 签署劳务 | String | labor_signed | char(1) | ✅ |
| createBy | 创建人 | String | create_by | varchar(64) | ✅ |
| createTime | 创建时间 | Date | create_time | datetime | ✅ |
| updateBy | 更新人 | String | update_by | varchar(64) | ✅ |
| updateTime | 更新时间 | Date | update_time | datetime | ✅ |
| — | (无 `remark` 字段) | — | (无 `remark` 列) | — | ❌ **实体缺 remark** (见 §9 #3) |
**修订**: v1 漏报 `address` 字段 — 实测 DB 有 `address varchar(500)` 列 + COMMENT '会议地址', 实体也有 `private String address`, 但 mapper 全套 (selectFields / insert / updateByPrimaryKey) 都没碰这个字段。**新发现**: 见 §9 #6
---
## 7. 索引检查
| 表 | Key | 列 | 用途覆盖 |
|---|---|---|---|
| biz_meeting | PRIMARY | meeting_id | ✅ ORDER BY + WHERE meeting_id = |
| biz_meeting | uk_business_id | business_id (UNIQUE) | ✅ 雪花 ID 防冲突 |
| biz_meeting | idx_meeting_project | project_id | ✅ `WHERE project_id =` (项目侧查会议) |
| biz_meeting | idx_meeting_stage | current_stage | ✅ `WHERE current_stage =` |
**缺失但暂可接受** (按 §2 筛选启用后):
- `idx_project_no``LIKE '%xxx%'` 模糊匹配无法走索引 (见 §10 #6)
- `idx_meeting_name` — 同上
- 复合 `(project_id, meeting_id)` — JOIN 用, 当前单 `project_id` 索引够
---
## 8. 与原型差异 (proto/html/components/meeting-manage.html)
### 8.1 实现新增 (原型没有) — **改进**
| 项 | 原型 | 实现 | 评估 |
|---|---|---|---|
| 筛选 "备注" | 4 项 (项目编号/会议名称/项目形式/当前阶段) | 8 项 (多 会议ID/第?期/会议时间/备注) | ⚠️ 备注 filter 死路 (见 §9 #3), 第?期无 placeholder 限制 |
| 弹窗 (查看/修改) | 跳转 detail 页 | ✅ el-dialog 紧凑 | manager 端更高效 |
| 行内 "复制" → 跳独立页 | 原型 `onclick="copyMeeting(this)"` URL 传参 | ✅ 跳 `MeetingNew.vue?mode=copy` | 模式更清晰 |
### 8.2 原型有但实现缺失 — **回退**
| 原型 | 实现 | 差距 |
|---|---|---|
| 9 条红字提示 (执行方只能在项目列表建会 / 状态定义 / 提交审核后 OA 流程 / 复制 → 新建会议 / 总期数可点击筛选 / 提交后 3 秒消失 / 7 种状态统一解释 / 冻结中提交置灰 / 到期前 24h 每 4h 短信催促) | 无 | ❌ 整段红字提示缺失 (manager 端 UX 参考) |
| `meeting-detail-executor.html` 详情页 | 弹 dialog | ⚠️ 弹窗缺字段 (见 §10 #11) |
### 8.3 文字 / 标签差异
| 项 | 原型 | 实现 | 评估 |
|---|---|---|---|
| 面包屑 | 首页 > 我参与的会议 | 首页 / 会议管理 | ✅ 角色匹配 |
| 当前阶段 select | 6 项 (未执行/执行中/待审核/待结算/已完结/冻结中) | 4 项 (未执行/执行中/已执行/已完结) | ⚠️ **缺 待审核/待结算/冻结中 3 项** (但跟 DB COMMENT 一致, 见 §9 #1) |
| 期数筛选项 | "第 [____] 期" 三段式 (label/input/label) | "期数(第几期)" 单 label | ⚠️ 排版略不同 (今日改 label 文案) |
| 颜色 | `#1890ff` (浅蓝) | `var(--brand-primary)` (深海军蓝) | ✅ 项目色 |
### 8.4 总结
**整体方向**: **对齐** — 实装与原型字段基本一致 (12 列 vs 12 列, 8 项 filter vs 8 项 filter); 差距在 "红字提示" 整段缺失 + 当前阶段 select 选项数不一致。
---
## 9. 设计问题
### 9.1 当前阶段 select 与 DB 实际数据不一致
DDL 注释列了 7 种 (未执行/待监管/监管通过/待整改/待结算/已结算/已结题), 实装 select 4 项 (未执行/执行中/已执行/已完结)。两者有交集但不一致, filter 启用后用户选"未执行"也筛不到任何数据 (DB 实际可能是 待监管/已结算 等)。
### 9.2 calcRemain 读不存在的字段
`Meetings.vue:162``row.submitDeadlineDays`, 但 `BizMeeting` 实体无此字段 (BizProject 才有)。当前永远 fallback 到 30 天, 是个静默 bug。
### 9.3 实体无 remark 字段
前端 3 处使用 (`Meetings.vue:32 q.remark`, `:67 row.remark`, `:32 filter`), 但 `BizMeeting` 实体无 `remark`, 表也无 `remark` 列。后果:
- filter "备注" 永远无效
- 列表"备注"列永远空
- 详情 dialog"备注"行永远 `-`
### 9.4 数据隔离缺失 (manager 端)
`BizMeetingMapper.xml:37 selectList``@DataScope``lead_user_id` 过滤。当前测试数据无风险, 生产必修。
### 9.5 状态回滚无校验
"提交" 操作任何 stage 都能转 "执行中", 包括 "已完结" → "执行中" (状态回滚)。应加前置校验。
### 9.6 ✨ 新发现: address 字段 mapper 全套遗漏
DB 有 `address varchar(500) COMMENT '会议地址'`, 实体也有 `private String address` + getter/setter (`BizMeeting.java:117-119`), 但 `BizMeetingMapper.xml`:
- `selectFields` (line 29-32) — 漏列
- `selectByPrimaryKey` (line 33-36) — 通过 `selectFields` 漏列
- `selectList` (line 37-52) — 通过 `selectFields` 漏列
- `insert` (line 53-97) — `<if>` 块无 address
- `updateByPrimaryKey` (line 98-121) — `<if>` 块无 address
**后果**: 即使前端表单填了 address, 写入 DB 时会被静默丢弃; 即使 DB 已有 address, 列表也读不出来。
---
## 10. 待修复列表 (严格只列未修)
| # | 问题 | 文件 | 修复建议 | 严重度 |
|---|---|---|---|---|
| 1 | **数据隔离缺失** (selectList 无 lead_user_id 过滤) | `BizMeetingMapper.xml:37` | 加 `<if test="params.leadUserId != null">and project_id in (select project_id from biz_project where lead_user_id = #{params.leadUserId})</if>`; Controller 注入当前 user_id | **P0** (生产必修) |
| 2 | **`calcRemain``row.submitDeadlineDays`** (BizMeeting 无此字段) | `Meetings.vue:162` | 改成读 `row.project?.submitDeadlineDays` (后端 JOIN biz_project); 或去此字段 (manager 不需要) | **P1** |
| 3 | **实体无 `remark` 字段** (前端 3 处 silent fail) | `BizMeeting.java` + `BizMeetingMapper.xml` | 路径 A: 加 `private String remark` + getter/setter + mapper UPDATE/INSERT 加 `<if>` + DDL `ALTER TABLE biz_meeting ADD COLUMN remark text`; 路径 B: 前端 3 处全删 | **P1** |
| 4 | **filter 缺"签署状态"** (实体有 `labor_signed`) | `Meetings.vue` filter + table column | 加 `<el-select v-model="q.laborSigned">` 2 option + table 加列 | **P2** |
| 5 | **状态回滚无校验** (任何 stage → "执行中") | `Meetings.vue:205-212 onSubmit` + `:215-226 onBatchSubmit` | 加 `if (currentStage === '未执行')` 才能提交, 否则 ElMessageBox 提示 | **P2** |
| 6 | ✨ **`address` 字段 mapper 全套遗漏** (DB 有, 实体有, mapper 全套没碰) | `BizMeetingMapper.xml` | selectFields/insert/update 全部加 address 字段 + `<if test="address != null and address != ''">` | **P1** |
| 7 | **筛选后端需补索引** (`idx_project_no` + `idx_meeting_name`) | `biz_meeting` table | `ALTER TABLE biz_meeting ADD KEY idx_project_no (project_no), ADD KEY idx_meeting_name (meeting_name);` (⚠️ `LIKE '%xxx%'` 模糊匹配无法走索引) | **P3** |
| 8 | **实体 periodNo/totalPeriods Long vs int** | `BizMeeting.java` | 改 int, MyBatis INSERT 不需 Long 转换 | **P3** |
| 9 | **当前阶段 select 选项跟 DB 实际数据不一致** (见 §9 #1) | `Meetings.vue:25-31` | 改成 7 项 (跟 DB COMMENT 一致: 未执行/待监管/监管通过/待整改/待结算/已结算/已结题) | **P1** |
| 10 | **filter 8 项挤一行** (屏幕窄乱) | `Meetings.vue:6-37` | 拆 2 行, 或 grid-template-columns: repeat(4, 1fr) | **P2** |
| 11 | **批量提交无事务/无批量 endpoint** | `Meetings.vue:215-226` | 后端加 `PUT /business/meeting/batch?ids=...` + `@Transactional`; 前端 for 循环改单次调用 | **P2** |
| 12 | **详情页字段映射完整** (14 字段 + 邀请函 + 日程 + 返回, 详见 §5 跳转形式列) | `MeetingDetail.vue` (新) | ✅ 已修 (A.7) — 跳独立页, 参照原型 `meeting-detail.html` 1:1 实现 | ✅ 已修 |
| 13 | **`totalPeriods` 永远 1** (MeetingNew.vue 没让填) | `MeetingNew.vue` | 加"总期数"字段 (用户填, INSERT 时一起写) | **P1** |
| 14 | ✨ **9 条红字提示整体缺失** (原型 `meeting-manage.html:533-543`) | `Meetings.vue` | 顶部加 `.hint-list` 块 (manager 端 UX 参考, 不强制与 executor 端一致) | **P2** |
| 15 | ✨ **截止前 24h 短信催促** (原型第 9 条提示) | 后端 scheduler + sys_sms 表 | 加定时任务扫描 end_time + submit_deadline_days, 距截止 24h 起每 4h 触发 | **P3** |
---
## 11. 引用清单
### 前端
- `ry-vue3/src/views/manager/Meetings.vue:256` — 本审查目标 (今日改动: 7/10/60/100/32)
- `ry-vue3/src/views/manager/MeetingNew.vue` — 新建/修改/复制会议独立页
- `ry-vue3/src/layout/AdminLayout.vue:97` — manager 菜单项
- `ry-vue3/src/router/index.js:65-66` — 路由
- `ry-vue3/src/utils/request.js:15-30` — axios 拦截器 (`__silentError` 跳过 toast)
- `ry-vue3/src/api/public.js:32, 90-93``bizList` / `bizGet` / `bizUpdate` / `bizAdd`
### 后端 (Java)
- `ry-api/ruoyi-business/.../controller/BizMeetingController.java:26-75` — 5 endpoints
- `ry-api/ruoyi-business/.../domain/BizMeeting.java:11-136` — 25 字段, **缺 remark**, **address 在 mapper 全套遗漏**
- `ry-api/ruoyi-business/.../service/impl/BizMeetingServiceImpl.java:24-31` — insert 调 SnowflakeId 兜底 businessId
- `ry-api/ruoyi-business/.../mapper/BizMeetingMapper.java` — mapper 接口
### Mapper XML
- `ry-api/ruoyi-business/src/main/resources/mapper/business/BizMeetingMapper.xml:131` — selectList 7 个 `<if>` 已生效; **address 全套漏** (见 §9 #6)
### 配置 / 工具
- `ry-api/ruoyi-admin/src/main/resources/application-druid.yml:9-10` — DB 连接 (root / cu2oh2co3)
- `ry-api/ruoyi-common/src/main/java/com/ruoyi/common/utils/id/IdGenerator.java` — 雪花 ID 生成器
- `ry-api/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java:27` — getUserId()
### 原型
- `proto/html/manager.html:207` — 链接到 `components/meeting-manage.html`
- `proto/html/components/meeting-manage.html:618` — manager 会议管理原型 (12 列 + 8 filter + 9 红字提示)
- `proto/html/components/meeting-manage-1.html` — 原型变体 (期数 input 拆成 第/数字/期 三段), 暂未用
### 数据库 (实测 2026-08-21)
- `biz_meeting` — 9 行 (08-21) / 7 行 (08-18), AUTO_INCREMENT=12, 4 索引 (PK + UNIQUE + 2 普通), 24 列
- `biz_project` — 12 行, 含 `create_user_id bigint`
- `biz_meeting_attendee` — doctor/expert 角色过滤的中间表
---
## 附录: 修复记录 (按 commit 时间倒序)
### A.7 commit (未 commit, 2026-08-21 session 改动) — 查看按钮跳页面
> **背景**: 用户硬要求 "跳页面变成打开 dialog 是完全不能接受的", 同时让 page-tech-review skill 能自动抓这类反模式
#### A.7.1 新建 `MeetingDetail.vue` (`ry-vue3/src/views/manager/MeetingDetail.vue`, 290 行)
- 1:1 参照 `ManagerProjectDetail.vue` 的 page-card + breadcrumb + new-card-title 结构
- 字段映射: 14 项基本信息 (项目编号/会议ID/会议名称/项目名称/项目形式/总期数/期数/起止时间/当前阶段/签署劳务/支持单位/创建人员/创建时间/备注) + 监察段 (条件渲染) + 邀请函/日程 (有 URL 渲染预览+下载, 无 URL 显示空态) + 返回
- 字段对照 `biz_meeting` DB 实测 (见 §6.3); `supervision_*` 任意一项存在就整段显示, 避免空占位
- 已知死字段: `备注` 永远 `-` (实体/表都没, 见 §9 #3); `address` mapper 全套漏 (见 §9 #6), 暂不显示此字段避免误导
- URL 参数: `route.params.meetingId` (path param, 跟 `ManagerProjectDetail` 一致)
#### A.7.2 router 加路由 (`ry-vue3/src/router/index.js:66`)
```js
{ path: 'meetings/detail/:meetingId', name: 'manager-meetings-detail',
component: () => import('@/views/manager/MeetingDetail.vue'),
meta: { title: '会议详情' } },
```
参照 `manager-projects-detail` (line 63) 命名
#### A.7.3 `Meetings.vue` 删 dialog 改跳页面 (367 行 → 220 行)
- `onView(row)``viewDetail(row)`, 内部 `router.push(/manager/meetings/detail/${row.meetingId})`
- 删除: `<el-dialog>` 整块 (89-156 行), `detailOpen`/`currentRow` ref, `onView`/`calcRemain`/`laborSignedLabel` 函数, `.meeting-detail-dialog` 系列样式, 3 个 icon imports (Document/Calendar/Picture)
- 保留: `fmtTime` (表格列用), `stageClass` (表格列用), `.stage-tag` 系列样式
#### A.7.4 page-tech-review skill 加规则 (`SKILL.md`)
- §5 加 2 处: (1) 列说明加 "跳转形式 (page=✅ / dialog=❌)"; (2) 新 bullet 强调 "查看" 必须跳独立页面, 给反例 (本次修复前 Meetings.vue) 和参考模式 (Projects.vue:424)
- §10 铁律加第 10 条 ⛔: "查看" 不能用 dialog; 审查时 §5 必须标 "跳转形式", dialog 即 P0
---
### A.6 commit (未 commit, 2026-08-21 session 改动) — label/期数/placeholder 修复
> **改动范围**: `Meetings.vue` (前端) + `BizProjectController.java` (后端, 顺带修)
#### A.6.1 后端 `BizProjectController.java:223`
- **错**: `int s = safeLong(...) + safeLong(...) + ...` — long 累加赋给 int, Java 报"可能会有损失"
- **改**: `int s``long s`
- **影响**: rate() 接口里 compliance 角色聚合评分那行, 不影响 meeting 页面本身
#### A.6.2 前端 `Meetings.vue:10` 筛选项 "第?期"
- **问题**: `:min="0"` 让 el-input-number 在空值时强制 0 → 后端 `period_no = 0` 过滤掉全部数据; label 文案 "第?期" 不直观
- **改**:
- label: `第?期``期数(第几期)`
- 控件: `<el-input-number :min="0" :precision="0" controls-position="right">``<el-input placeholder="请输入期数" clearable>`
- 原因: Element Plus 2.4 无 `value-on-clear` prop, `:min="1"` 仍会让 blur 时 snap 到 1; 改用普通 `<el-input>` 让空值保持空串, 由 mapper `<if test="periodNo != null">` 跳过
- **验证**: 缺值 → 不影响其他筛选; `periodNo=1` → 1 行
#### A.6.3 前端 `Meetings.vue:60, 100` 期数显示
- **问题**: `第 {{ row.periodNo || 0 }} 期` 在 periodNo 为 null/空时强行显示 "第 0 期"
- **改**: `{{ row.periodNo ? '第 ' + row.periodNo + ' 期' : '-' }}`
- **影响**: 表格列 + 详情 dialog 同步去掉 `|| 0` 兜底
#### A.6.4 前端 `Meetings.vue:7-32` placeholder 补齐
- **问题**: 5 个文本输入 + 2 个日期选择器缺 placeholder
- **改**:
- 项目编号 → `请输入项目编号`
- 会议ID → `请输入会议ID`
- 会议名称 → `请输入会议名称`
- 期数(第几期) → `请输入期数`
- 会议时间 → `开始时间` / `结束时间`
- 备注 → `请输入备注`
- 项目形式 / 当前阶段 → 已经有 `请选择`
### A.5 commit `bae9fa6` (2026-08-18) — 恢复会议ID + 第?期 filter (第五轮续)
- **背景**: 第四轮前后曾误删 `会议ID` / `第?期` filter, 用户澄清两个字段原型中都有
- **修改**: `Meetings.vue:8` 加回 `<el-form-item label="会议ID">`; `Meetings.vue:10` 第?期 el-input-number; q reactive + reset() 加回 `meetingId`
- **验证**: `q.meetingId=14` → 1 行; `q.periodNo=1` → 1 行
### A.4 commit `14757f3` (2026-08-18) — mapper selectList 加 7 个筛选 if + @DateTimeFormat
- **解决**: 全部筛选不生效 (`<where>` 空)
- **修改**: `BizMeetingMapper.xml:37-52` 加 8 个 `<if>`; `BizMeeting.java:30-33` startTime/endTime 加 `@DateTimeFormat`
### A.3-A.1 (略, 见 v1 报告)