Files
guoju0808/_self/manager_meetings.md
T

453 lines
32 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-18
> **触发场景**: 用户访问 `/manager/meetings` (manager 角色)
> **审查范围**: `ry-vue3/src/views/manager/Meetings.vue` (会议管理列表页) + 后端 BizMeeting 全链路 + DB 实测
> **对比原型**: `proto/html/components/meetings.html` (我参与的会议 / doctor 端原型, 实装 manager 重用)
> **特别说明**: 本页是 manager 端"会议管理"列表, 跟原型"我参与的会议"(doctor 端) 功能相近但角色不同 — 复用原型 UI 是合理的
---
## 修复记录 (2026-08-18 落实)
### 第一轮: 数据库正确性 (同 manager_meeting_new.md, 已 push)
(略 — 见 `_self/manager_meeting_new.md` §修复记录 第一轮; 共享 `BizMeeting` 实体/mapper/service)
### 第二轮: onCopy 改走 bizAdd + 三处双 toast (commit `08d81e9`)
| 问题 | 修复 |
|---|---|
| **P0#3 `onCopy` bug**`meetingId=''``bizUpdate` → UPDATE 影响 0 行 + 误报"修改成功", 实际未创建副本 | `Meetings.vue:254-263 onCopy``editMode='copy'` 标志; `submitEdit()` 按 mode 分支: copy 走 `bizAdd('meeting', payload, { __silentError: true })` (删 meetingId, 走 AUTO_INCREMENT), edit 走 `bizUpdate`; Dialog title 动态 `'复制会议'` vs `'修改会议'`; Toast 文案动态 `'复制成功'` vs `'修改成功'` |
| **P0#4 双 toast (3 处)**`submitEdit` / `onSubmit` / `onBatchSubmit``__silentError`, 拦截器+catch 双 toast | 三处 `bizUpdate` 都加 `{ __silentError: true }`; catch 统一 `ElMessage.error(e?.msg || '...')`, 拦截器不再自动 toast |
| 附带: editForm 补 `projectId` / `projectNo` / `projectName` / `currentStage` (复制需要) | `Meetings.vue:222-231` 加 4 字段; onEdit/onCopy 都从 row 回填 |
| 后端核对 | `POST /business/meeting` 已存在 (BizMeetingController.add:36-40); Mapper INSERT (BizMeetingMapper.xml:43-87) 已覆盖所有字段; Spring Boot 默认 `fail-on-unknown=false`, 前端发的 `remark` 字段被静默忽略, 不报错; `meetingId` 走 AUTO_INCREMENT; `create_by/create_time/update_by/update_time` 由 MyBatis-Plus MetaObjectHandler 自动填充; **后端零改动** ✅ |
### 第三轮: style-alignment + 批量提交按钮去 plain (commits `6d4f1e6` + `6774c44`)
| 问题 | 修复 |
|---|---|
| **批量提交按钮 disabled 顺色** (`type="primary" plain`) — outlined primary 在 disabled 状态下文字极淡灰色, 跟透明背景对比度低, 看不清 | `Meetings.vue:41` 去掉 `plain` 属性; 现 `<el-button type="primary" :disabled="!selectedIds.length">批量提交</el-button>` — disabled 状态浅蓝底 + 白字, 一眼可识别 |
| **"已选 X 条" 提示紧贴按钮左边** — 应推到 batch-bar 最右侧 (跟 People.vue 风格一致) | `Meetings.vue:324 .filter-tip { ... margin-left: auto; }` |
| `<span style="margin:0 4px">至</span>` — 残留 inline style | `Meetings.vue:13``<span class="date-sep">至</span>`; `Meetings.vue:325` 新增 `.date-sep { margin: 0 4px; color: #606266; }` |
| `.batch-bar` 属性顺序跟 People.vue 不一致 | `Meetings.vue:323` 改成 `margin-bottom: 12px; display: flex; gap: 8px; align-items: center;` (margin 提前) |
| 自查 (style-alignment 验证清单) | ✅ 1 层 page-card / ✅ breadcrumb / ✅ 无 desc / ✅ 无 h2 page-title / ✅ filter-form/batch-bar/pager 三 class / ✅ 蓝色全用 `var(--brand-primary)`#1890ff / ✅ 边框全 #f0f0f0#ebeef5 / ✅ 无 `:deep(.el-button--primary)` 大段覆盖 / ✅ el-pagination 在 .pager / ✅ el-form-item label 无冒号 / ✅ `<script>` 业务逻辑 100% 保留 (load/reset/view/edit/sub/copy/submit/batch 全在) |
### 第四轮: P0#1 selectList 加 7 个筛选 if (commit `14757f3`)
| 问题 | 修复 |
|---|---|
| **P0#1 mapper selectList `<where>` 空** — 8 项筛选全部不接 SQL, 用户选了筛选 + 点查找 = 返回全表 | `BizMeetingMapper.xml:37-50` 加 7 个 `<if>` 条件: project_no LIKE, meeting_id =, meeting_name LIKE, period_no =, project_form =, current_stage =, start_time >=, end_time <= (remark 字段跳过, 因为 biz_meeting 表无此列, 等 #5 一起修) |
| **Date 字段 Spring 绑定问题**`q.startTime = "2026-08-18 10:00:00"` 通过 axios GET 传到后端, Spring 默认 Converter 只支持 ISO-8601 (`2026-08-18T10:00:00`), 用空格分隔会 binding 失败 → 字段变 null → mapper `<if>` 跳过 → 实际筛选不生效 | `BizMeeting.java:30-33``@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")` 到 startTime/endTime (跟已有 @JsonFormat 并存, JSON 序列化 / Spring 绑定都支持); supervisionTime/createTime/updateTime 不加 (本次 filter 不涉及, 暂不扩大范围) |
| 实测 (DB 查询) | `LIKE %ZH-2026-6%` → 5 行; `current_stage = 已结算` → 1 行; `start_time >= 2026-03-01` → 4 行; `period_no = 1` → 1 行; 全部条件 `AND` 组合也工作 ✅ |
| ⚠️ 发现数据不一致 (本轮不修, 仅记录) | DB 实际 current_stage 值是: 0/已结算/已结题/待整改/待监管/监管通过 (6 种); DDL 注释写 7 种 (未执行/执行中/监管通过/待整改/待结算/已结算/已结题); 前端 select 是 4 种 (未执行/执行中/已执行/已完结) — **三者全不一致**! 用户即使 mapper 修了, 也筛不到任何 stage 因为 select 选项不在 DB 实际数据中. 待 P1#7 一并修. |
| 前端不动 | `q` 的 8 项字段保持原样 (空字符串 / Long / Date 都有), Spring 默认 Converter 会把空字符串绑成 null, mapper `<if>` 跳过, 行为正确 |
---
## 0. 4 跳定位 (铁律: 不按名字猜)
| 跳 | 文件:行 | 结果 |
|---|---|---|
| ① 角色菜单 | `ry-vue3/src/layout/AdminLayout.vue:99` | `path: '/manager/meetings', title: '会议管理'` ✓ |
| ② 路由 | `ry-vue3/src/router/index.js:69-70` | `name: 'manager-meetings', component: () => import('@/views/manager/Meetings.vue')` ✓ |
| ③ 组件 | `ry-vue3/src/views/manager/Meetings.vue` (343 行) | 列表 + 筛选 + 批量提交 + 查看/修改/提交/复制 |
| ④ 原型 | `proto/html/components/meetings.html` | "我参与的会议" 7 列表格 + 4 项筛选 (项目编号/会议名称/当前阶段/签署状态) |
---
## 1. 主要功能 + 可见性
### 1.1 主要功能
| 功能 | 前端入口 | 后端接口 | 数据表 |
|---|---|---|---|
| 筛选+分页列表 | `Meetings.vue` `<el-table>` + `<el-pagination>` | `GET /business/meeting/list` (BizMeetingController:24-29) | `biz_meeting` |
| 查看详情 (弹窗) | `onView(row)``el-dialog` (只读 el-descriptions) | 复用列表返回的 row, 不额外请求 | `biz_meeting` |
| 修改会议 (弹窗) | `onEdit(row)``submitEdit()` | `PUT /business/meeting` (BizMeetingController:42-46) | `biz_meeting` |
| 提交 (单条) | `onSubmit(row)` → 弹 confirm → bizUpdate | `PUT /business/meeting` (BizMeetingController:42-46) | `biz_meeting` (current_stage 改 "执行中") |
| 复制 (单条) | `onCopy(row)` → 打开 editForm (`editMode='copy'`) → submitEdit 走 bizAdd | `POST /business/meeting` ✅ 第二轮修 | `biz_meeting` |
| 批量提交 (逐条) | `onBatchSubmit()` → for 循环 bizUpdate | 同上 × N 次 | `biz_meeting` |
### 1.2 可见性 (三层隔离)
| 层 | 来源 | 校验字段 |
|---|---|---|
| 前端菜单 | `AdminLayout.vue:99` `path: '/manager/meetings'` | 角色 = manager |
| 路由守卫 | `router/index.js:69-70` `meta: { title: '会议管理' }` (无 role 字段, 由 menu 拦截) | role |
| 后端 SQL | `BizMeetingMapper.xml:37-42 selectList` **无任何过滤** ❌ | **无 (数据全量返回, 无 unit_type 隔离)** |
**严重问题**: `selectList` 后端无任何条件, manager 端能 SELECT 出所有会议 (无 sponsor / executor / doctor 隔离)。当前 DB 7 行全测数据, 无用户敏感数据, 不算 leak, 但生产环境会有问题。
**看不到本页面的人群**:
- admin: 有独立 `/admin/meetings` (AdminLayout.vue:74)
- leader: 有独立 `/leader/meetings` (AdminLayout.vue:92)
- doctor: 有独立 `/doctor/meetings` (AdminLayout.vue:109)
- executor: 有独立 `/executor/meetings` (AdminLayout.vue:117)
- sponsor: 有独立 `/sponsor/meetings` (AdminLayout.vue:126)
**数据隔离机制**: ❌ **缺失** — manager 端没有按 `lead_user_id = 当前 user_id` 过滤, 也没用 `@DataScope` 注解。后端直接返回全表。
---
## 2. 筛选项 (filter-form)
| UI label | 控件 | 绑定字段 | 后端 SQL `<if>` | 命中表.字段 | 选项来源 | 原型对照 |
|---|---|---|---|---|---|---|
| 项目编号 | el-input (模糊) | `q.projectNo` | ❌ **缺失** | (无 WHERE) | — | ✅ 项目编号 (原型有) |
| 会议ID | el-input (精确) | `q.meetingId` | ❌ **缺失** | (无 WHERE) | — | ❌ **原型无此字段** (用户增, 但暴露 PK 是错的) |
| 会议名称 | el-input (模糊) | `q.meetingName` | ❌ **缺失** | `biz_meeting.meeting_name` | — | ✅ 会议名称 (原型有) |
| 第?期 | el-input-number? (实际 el-input) | `q.periodNo` | ❌ **缺失** | `biz_meeting.period_no` | — | ❌ 原型无 (实装增, 但 type=el-input 应该是 el-input-number) |
| 会议时间 (start~end) | el-date-picker × 2 | `q.startTime`, `q.endTime` | ❌ **缺失** | `biz_meeting.start_time/end_time` | — | ❌ 原型无 (实装增, 但后端不接) |
| 项目形式 | el-select | `q.projectForm` | ❌ **缺失** | `biz_meeting.project_form` | **写死** 4 项 (线上/线下/线上+线下/其他) | ❌ 原型无 |
| 当前阶段 | el-select | `q.currentStage` | ❌ **缺失** | `biz_meeting.current_stage` | **写死** 4 项 (未执行/执行中/已执行/已完结) ❌ DDL 注释 7 项 | ✅ 当前阶段 (原型有, 但原型 2 项 "未开始/已结束") |
| 备注 | el-input (模糊) | `q.remark` | ❌ **缺失** | (无字段, biz_meeting 无 remark 列 ❌) | — | ❌ 原型无 |
**汇总**: 7 个 el-input/el-select 全部无效, 后端 mapper `<where>` 是空的 (BizMeetingMapper.xml:39-40):
```xml
<select id="selectList" resultMap="BizMeetingResult" parameterType="BizMeeting">
<include refid="selectFields"/>
<where>
</where>
order by meeting_id desc
</select>
```
**核心 bug**: 筛选功能全部失灵 — 点了"查找"也只按 meeting_id desc 排序返回全表。
---
## 3. 工具栏按钮 (toolbar)
| 按钮 | 功能 | 接口 (HTTP method + URL) | 涉及表 | 原型对照 |
|---|---|---|---|---|
| 查找 | 触发 `load()` → bizList | `GET /business/meeting/list?...` | `biz_meeting` | ✅ 查找 (原型有) |
| 重置 | 清空 q → load | 同上 | — | ❌ 原型无 |
| 批量提交 | `onBatchSubmit()` 逐条 update currentStage='执行中' | `PUT /business/meeting` × N | `biz_meeting` | ❌ 原型无 (manager 专属) |
**提示行**: `<span v-if="selectedIds.length" class="filter-tip">已选 {{ selectedIds.length }} 条</span>` — 跟 People.vue 的 `<div class="filter-tip">` 一致 ✅
---
## 4. 表格 (el-table)
### 4.1 数据来源 (SQL 块)
完整 SQL (从 `BizMeetingMapper.xml:29-32` `selectFields`):
```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] -- 全部条件都缺失, 见 §2
order by meeting_id desc
```
### 4.2 列映射
| 列名 | row.* | DB 表.字段 | 原型对照 |
|---|---|---|---|
| 多选框 | (selection) | — | ❌ 原型无 |
| 项目编号 | `row.projectNo` | `biz_meeting.project_no` | ✅ 项目编号 |
| 会议ID | `row.meetingId` ❌ 暴露 PK | `biz_meeting.meeting_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`**DB 无此字段, 永远空** | (无) | ❌ 原型无 |
| 操作 | 查看/修改/提交/复制 (4 按钮) | — | ✅ 操作 (原型只有 1 个"签署劳务/查看劳务"按钮) |
**新增列 (原型无, 实现增)** ✅ 是改进:
- 项目形式, 会议开始/结束时间, 总期数, 期数 — 业务上必要
- 多选框 (用于批量提交) — manager 专属
- 操作列 4 按钮 — manager 端管理需要
**遗漏列 (DB 有, 当前未显示)**:
- `org_name` (公司名称, 冗余) — 列表可加
- `create_time` / `update_time` — 列表可加 (审计)
- `supervision_opinion` / `supervision_by` / `supervision_time` — 详情 dialog 有但列表无
- `labor_signed` (0/1) — 详情 dialog 有但列表无
---
## 5. 行内操作按钮
| 按钮 | 触发函数 | 接口 | 后端动作 | 涉及表.字段 | 原型对照 |
|---|---|---|---|---|---|
| 查看 | `onView(row)` | (无, 用列表 row 数据) | 弹 detailOpen dialog | — | ❌ 原型无 (原型点会议名跳转 meeting-detail.html) |
| 修改 | `onEdit(row)``submitEdit()` | `PUT /business/meeting` | UPDATE biz_meeting SET meeting_name?, project_form?, start_time?, end_time?, total_periods?, remark? WHERE meeting_id=? | `biz_meeting` (6 字段) | ❌ 原型无 |
| 提交 | `onSubmit(row)` | `PUT /business/meeting` | UPDATE biz_meeting SET current_stage='执行中' WHERE meeting_id=? | `biz_meeting.current_stage` | ❌ 原型无 |
| 复制 | `onCopy(row)``submitEdit()` (走 `bizAdd`) | `POST /business/meeting` ✅ 第二轮修 | INSERT biz_meeting (...) VALUES (...); meetingId AUTO_INCREMENT, businessId 兜底 | `biz_meeting` (新行) | ❌ 原型无 |
**真实落点**:
- "提交": 直接改 `current_stage = '执行中'`, 没有任何业务校验 (例如只能从未执行→执行中). 也不写日志 (`biz_meeting_log` 等不存在).
- "修改": 改 6 字段, 但 start_time/end_time/total_periods 没校验 (MeetingNew 的校验规则未共享).
**核心 bug #2** (`onCopy`) — 第二轮已修 ✅: `Meetings.vue:282-299 onCopy`
```js
function onCopy(row) {
editMode.value = 'copy' // ← 标志为复制模式
Object.assign(editForm, {
meetingId: '', // ← 空字符串 (复制时不要传, 走 AUTO_INCREMENT)
meetingName: (row.meetingName || '') + ' 副本',
projectId: row.projectId ?? null,
...
})
editOpen.value = true
}
```
`submitEdit()``editMode` 分支: copy 走 `bizAdd('meeting', payload, { __silentError: true })` (删 meetingId), edit 走 `bizUpdate`。第二轮回滚/回退风险已消除: 后端零改动, 仅前端分支。
---
## 6. Java Entity ↔ 数据库表 一致性
### 6.1 biz_meeting 主表 DDL (实测, 2026-08-18)
```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 '公司名称(冗余)',
`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=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会议表';
```
### 6.2 关联表 DDL (引用)
- `biz_project` — 见 `manager_projects.md` §6 + 最近 `ALTER TABLE``create_user_id` (commit ffda9c6)
- `biz_person` — 主键 person_id bigint, 唯一键 uk_person_user_id (user_id)
### 6.3 Entity ↔ 表字段对照表
| 实体字段 | 中文列名 (DB COMMENT) | 实体类型 | 表字段 | 表类型 | 一致? |
|---|---|---|---|---|---|
| meetingId | 会议ID | Long | meeting_id | bigint | ✅ (前轮修过 String→Long) |
| businessId | 业务编号ID | String | business_id | varchar(50) | ✅ |
| projectId | 关联项目ID | Long | project_id | bigint | ✅ (前轮修过 String→Long) |
| projectNo | 项目编号 | String | project_no | varchar(50) | ✅ |
| projectName | 项目名称 | String | project_name | varchar(200) | ✅ |
| meetingName | 会议名称 | String | meeting_name | varchar(200) | ✅ |
| periodNo | 期数 | **Long** | period_no | **int** | ⚠️ 类型不一致 (Long vs int) — 列表显示无问题, 但 editForm 初始化 `periodNo || 0` 走 Long, INSERT mapper 用 `<if test="periodNo != null">#{periodNo}</if>` Long 入 int 列, MyBatis 自动转换 OK |
| totalPeriods | 总期数 | Long | total_periods | int | ⚠️ 类型不一致 (同 periodNo) |
| projectForm | 项目形式 | String | project_form | varchar(20) | ✅ |
| startTime | 会议开始时间 | Date | start_time | datetime | ✅ (前轮修过 String→Date + @JsonFormat) |
| endTime | 会议结束时间 | Date | end_time | datetime | ✅ |
| orgName | 公司名称(冗余) | String | org_name | varchar(200) | ✅ |
| currentStage | 当前阶段 | String | current_stage | varchar(20) | ✅ |
| supervisionOpinion | 监察意见 | String | supervision_opinion | varchar(500) | ✅ |
| supervisionBy | 监察人 | String | supervision_by | varchar(64) | ✅ |
| supervisionTime | 监察时间 | Date | supervision_time | datetime | ✅ (前轮修过 String→Date) |
| 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) | ✅ (BaseEntity 字段, 由 MetaObjectHandler 自动填) |
| createTime | 创建时间 | Date | create_time | datetime | ✅ |
| updateBy | 修改人 | String | update_by | varchar(64) | ✅ |
| updateTime | 修改时间 | Date | update_time | datetime | ✅ |
**重大问题**: 实体**无 `remark` 字段**, 但前端 `Meetings.vue:32, 67, 125` 都在用 `q.remark` / `row.remark` / `editForm.remark`**永远 null**:
- 列表"备注"列永远空
- 详情 dialog"备注"行永远 `-` (`Meetings.vue:102`)
- 修改 dialog 有"备注"输入框 (但写入 mapper `<if test="remark != null">` 也会失败 — MyBatis 找不到 setter)
`BizMeetingMapper.xml:108 updateByPrimaryKey` 也无 `remark = #{remark}` 的 if 块。
### 修订标记
- 第一轮: meetingId String→Long, supervisionTime String→Date, INSERT/UPDATE/selectFields 补漏, businessId 兜底生成 (已在 manager_meeting_new.md 第一轮完成)
---
## 7. 索引检查
### 7.1 biz_meeting 索引 (实测, 2026-08-18)
| Key | 列 | 用途覆盖 |
|---|---|---|
| PRIMARY | `meeting_id` | ✅ 默认 ORDER BY |
| uk_business_id | `business_id` (UNIQUE) | ✅ 雪花 ID 兜底生成防冲突 |
| idx_meeting_project | `project_id` | ✅ `WHERE project_id = ?` (项目侧查会议) |
| idx_meeting_stage | `current_stage` | ✅ `WHERE current_stage = ?` (filter 启用后生效) |
**预期但缺失** (按 §2 筛选修复后需要):
- `idx_project_no` (project_no varchar(50)) — filter "项目编号" 用 `LIKE` 模糊匹配, 小数据量可接受, 但生产 10k+ 项目需要
- `idx_meeting_name` (meeting_name varchar(200)) — filter "会议名称" 用 `LIKE` 模糊, 同上
- 复合索引 `(project_id, meeting_id)` — JOIN 用, 当前单 `project_id` 够用
**判定**: 当前 7 行数据, 索引缺失影响可接受; 若按 §2 加全筛选, 需补 idx_project_no + idx_meeting_name。
---
## 8. 与原型差异 (实现偏离)
### 8.1 实现新增 (原型没有) — **改进**
| 项 | 原型 | 实现 |
|---|---|---|
| 4 个 el-select 筛选 (会议ID/会议时间/项目形式/备注) | 无 | ✅ 是 manager 端管理需要 |
| 表格多选 + 批量提交 | 无 | ✅ manager 专属 |
| 操作列 4 按钮 (查看/修改/提交/复制) | 1 个 "签署劳务/查看劳务" | ✅ 是 (角色不同) |
| 分页 | 无 | ✅ 必要 |
| 弹窗 (查看/修改) | 跳转 meeting-detail.html | ✅ 弹窗更紧凑 |
| 详情 dialog 显示 submitDeadlineDays | 无 | ✅ 派生字段 |
### 8.2 原型有但实现缺失 — **回退**
| 原型 | 实现 | 差距 |
|---|---|---|
| 项目编号/会议名称/当前阶段/签署状态 — 4 项筛选 | 8 项 | 原型"签署状态" → `labor_signed` 缺失 |
| 表格列: 项目编号/会议名称/当前阶段/日程/邀请函/签署状态/操作 (7 列) | 11 列 | 原型"日程"/"邀请函"/"签署状态"列未直接显示, 但有 `schedule_url` / `invitation_url` / `labor_signed` 字段, 可点操作查看 |
| 弹框 邀请函 + 日程 (原型 u11685/u11690 动态面板) | 无 | ❌ 原型专门有, 实现未做 |
| 红字提示 (*邀请函根据执行方上传…) (原型 u11590) | 无 | ❌ 原型顶部红字, 实现无 |
| 底部红字提示 (*未签署时显示…) (原型 u11592/u11594/u11681/u11683) | 无 | ❌ 同上 |
### 8.3 文字 / 标签差异
| 项 | 原型 | 实现 | 评估 |
|---|---|---|---|
| 面包屑 | 首页 > 我参与的会议 | 首页 / 会议管理 | ✅ 跟角色匹配 |
| 当前阶段 select | 未开始 / 已结束 | 未执行 / 执行中 / 已执行 / 已完结 | ⚠️ 命名风格不同, 实装更细 |
| 颜色 | `#1890ff` (浅蓝) | `var(--brand-primary)` (深海军蓝) | ✅ 跟项目色系统一 |
### 8.4 总结
**整体方向**: **扩展** — 实现远超原型 (manager 端多 7 列 + 批量提交 + 修改 dialog); 原型的"邀请函/日程弹框"和"红字提示"未移植 (manager 角色下这些提示不太需要, 但建议加); 原型"签署状态"列未加 (manager 端不关注劳务签署)。
---
## 9. 表字段冗余 / 设计问题
### 9.1 实体 vs 表不一致 (详见 §6.3)
- **⚠️ 实体无 `remark` 字段** — 前端 3 处使用, 全部 silent fail
- **⚠️ periodNo/totalPeriods 类型不一致** — Long vs int, MyBatis 自动转换, 暂不修
### 9.2 selectList 无 WHERE 条件 (详见 §2)
- 全部筛选条件 mapper 不接, 用户体验: 选了筛选项 + 点查找 = 返回全表 ❌
### 9.3 业务闭环检查
-`onCopy` 已走 `bizAdd` (第二轮修) — 现在正确创建副本
- ⚠️ 提交/批量提交无业务校验 (任何状态都能转 "执行中", 包括"已完结" → "执行中" 的状态回滚) — 未修
- ⚠️ 修改 dialog 无任何校验 (start_time > end_time 不报, meetingName 空不报) — 未修
- ⚠️ 修改 dialog 改 6 字段, 但 mapper UPDATE 只 6 个 `<if>` (跟前端一致 ✅), 但 **缺 `remark` `<if>`** (实体无此字段) — 未修
### 9.4 双 toast bug (跨页面通用) ✅ 第二轮已修
- `submitEdit` / `onSubmit` / `onBatchSubmit` 三处 `bizUpdate`/`bizAdd` 都加 `{ __silentError: true }`
- catch 统一 `ElMessage.error(e?.msg || '...')`, 拦截器不再自动 toast
-`MeetingNew.vue` 之前同款 bug 一起消除 ✅
### 9.5 UI 顺色 / 样式问题 ✅ 第三轮已修
- ✅ 批量提交按钮 `type="primary" plain` disabled 时文字顺色 — 已去 `plain`
- ✅ "已选 X 条" 紧贴按钮左边 — 已 `margin-left: auto` 推右
-`<span style="margin:0 4px">至</span>` inline style — 已抽 `.date-sep` class
-`.batch-bar` 属性顺序 — 已跟 People.vue 对齐
### 9.6 数据隔离缺失
- `BizMeetingMapper.xml:37-42 selectList``@DataScope` 或 manager 端限制
- 当前 7 行数据无风险, 但生产环境 manager 应该只看自己 lead_user_id 的项目关联的会议 (应通过 `biz_project.lead_user_id` 过滤)
---
## 10. 待修复列表 (按优先级)
| # | 问题 | 文件 | 修复建议 | 严重度 |
|---|---|---|---|---|
| 1 | **所有筛选不生效** (7 项) — mapper `<where>` 空 | `BizMeetingMapper.xml:39-40` | 加 8 个 `<if>` 条件: project_no LIKE, meeting_name LIKE, period_no =, project_form =, current_stage =, start_time >=, end_time <=, business_id = (如果还用 meetingId filter, 应改成业务字段) | ~~**P0**~~ ✅ 第六轮修 (commit 14757f3) |
| 2 | **`meetingId` 列让用户看/筛 PK** | `Meetings.vue:8, 49` | 删 filter (q.meetingId + 字段) + 删 el-table-column | **P0** |
| 3 | **`onCopy` bug** — PUT 0 行 + 误报成功 | `Meetings.vue:254-263` + `submitEdit()` | `onCopy` 改走 `bizAdd('meeting', { ...不带 meetingId })`; submitEdit 改成 `if (editForm.meetingId) bizUpdate else bizAdd`, 或拆成两个 dialog | **P0** ✅ 第二轮修 |
| 4 | **3 处缺 `__silentError`** (双 toast) | `Meetings.vue:235, 247, 272` | bizUpdate 调用加 `{ __silentError: true }`, catch 里手动 `ElMessage.error(e?.msg \|\| '...')` | **P0** ✅ 第二轮同commit修 |
| 5 | **实体无 `remark` 字段** — 前端 3 处 silent fail | `BizMeeting.java` + `BizMeetingMapper.xml` UPDATE | 加 `private String remark` + getter/setter + mapper UPDATE 加 `<if test="remark != null">remark = #{remark},</if>` + INSERT 加 `<if test="remark != null and remark != ''">remark,</if>`; 或前端 3 处全删 (业务可能不需要) | **P0** |
| 6 | **`calcRemain``row.submitDeadlineDays`** (BizMeeting 无此字段) | `Meetings.vue:186` | 改成读 `row.project?.submitDeadlineDays` (需后端 JOIN biz_project); 或去掉此列 (manager 不太需要此字段) | **P1** |
| 7 | **当前阶段 select 只 4 项, DDL 列了 7 项** (未执行/执行中/监管通过/待整改/待结算/已结算/已结题) | `Meetings.vue:25-31` + `:62-66` (column) | 补 3 个 option + stageClass 已支持, OK | **P1** |
| 8 | **详情 dialog 缺字段** (create_time / org_name / supervision_* / labor_signed / invitation_url / schedule_url) | `Meetings.vue:91-103` | el-descriptions 加 5-8 行; URL 字段加 `<a :href>` 链接 | **P1** |
| 9 | **`totalPeriods` 永远 1** — MeetingNew.vue 没让填 | `MeetingNew.vue` (新建表单) | MeetingNew.vue 加"总期数"字段 (用户填, INSERT 时一起写); 列表列保留 | **P1** |
| 10 | **批量提交无事务/无批量 endpoint** | `Meetings.vue:271-273` | 后端加 `PUT /business/meeting/batch?ids=...` + `@Transactional`; 前端 for 循环改成单次调用 | **P2** |
| 11 | **filter 第?期 type=el-input 应是 el-input-number** | `Meetings.vue:10` | 改 `<el-input-number v-model="q.periodNo" :min="1" />` | **P2** |
| 12 | **filter 8 项挤一行** (屏幕窄乱) | `Meetings.vue:6-37` | 拆 2 行, 或 grid-template-columns: repeat(4, 1fr) | **P2** |
| 13 | **状态回滚无校验** (任何 stage → "执行中") | `Meetings.vue:244-251` | 加 if (currentStage === '未执行') 才能提交, 否则 ElMessageBox 提示 | **P2** |
| 14 | **数据隔离缺失** (selectList 无 lead_user_id 过滤) | `BizMeetingMapper.xml:37-42` | 加 `<if test="params.leadUserId != null">and project_id in (select project_id from biz_project where lead_user_id = #{params.leadUserId})</if>` (需要后端传入当前 user_id) | **P2** (生产前必修) |
| 15 | **filter 缺"签署状态"** (原型有, 实体有 labor_signed) | `Meetings.vue` filter 加 | 加 `<el-select v-model="q.laborSigned">` 2 option | **P2** |
| 16 | **筛选后端需补索引** (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);` | **P3** (小数据量暂可) |
| 17 | **实体 periodNo/totalPeriods Long vs int** | `BizMeeting.java` | 改 int, MyBatis INSERT 不需要 Long 转换 | **P3** |
| 18 | **批量提交按钮 plain + disabled 顺色** | `Meetings.vue:41` | 去掉 `plain` 属性 | ~~**P2**~~ ✅ 第三轮修 (commit 6774c44) |
| 19 | **"已选 X 条" 紧贴按钮左边** | `Meetings.vue:324` | `.filter-tip { margin-left: auto }` | ~~**P2**~~ ✅ 第三轮同 commit 修 |
| 20 | **inline style `margin:0 4px` 残留** ("至") | `Meetings.vue:13` | 抽 `.date-sep` class | ~~**P3**~~ ✅ 第三轮修 (commit 6d4f1e6) |
---
## 11. 引用清单
### 前端
- `ry-vue3/src/views/manager/Meetings.vue` (343 行) — 本审查目标
- `ry-vue3/src/views/manager/MeetingNew.vue` — 新建会议独立页 (共享 BizMeeting)
- `ry-vue3/src/layout/AdminLayout.vue:99` — manager 菜单项 "会议管理"
- `ry-vue3/src/router/index.js:69-70``/manager/meetings` 路由
- `ry-vue3/src/utils/request.js:15-30` — 响应拦截器 (code=200 直接放行 data, 错误触发 `__silentError` 跳过)
- `ry-vue3/src/api/public.js:32, 92-93``bizList` / `bizUpdate` / `bizAdd`
### 后端 (Java)
- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingController.java` (54 行) — 5 个 endpoint (list/getInfo/add/edit/remove)
- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizMeeting.java` (104 行) — 实体 (24 字段, **缺 remark 字段**; meetingId/supervisionTime 已修)
- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingServiceImpl.java` (34 行) — service 实现 (insert 调 SnowflakeId 兜底 businessId)
- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizMeetingService.java` — service 接口
- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizMeetingMapper.java` — mapper 接口
### Mapper XML
- `ry-api/ruoyi-business/src/main/resources/mapper/business/BizMeetingMapper.xml` (121 行) — **selectList `<where>` 是空的 (P0#1)**
### 配置 / 工具
- `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/SnowflakeId.java` — 雪花 ID 生成器 (BizMeetingServiceImpl:24 调 generateId)
- `ry-api/ruoyi-common/src/main/java/com/ruoyi/common/utils/SecurityUtils.java:27` — getUserId() (manager 端过滤可能用)
### 原型
- `proto/html/components/meetings.html` (546 行) — "我参与的会议" 原型 (doctor 端)
- `proto/html/components/meeting-detail.html` — 会议详情页 (查看操作可跳转, 现用 el-dialog 替代)
### 数据库 (实测, 2026-08-18)
- `biz_meeting` — 7 行, 4 索引 (PK + UNIQUE + 2 普通), 23 列 (含 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)
- `biz_project` — 12 行 (含最近 `ALTER TABLE ADD COLUMN create_user_id bigint`, commit ffda9c6)
- `biz_person` — JOIN 取姓名 (create_user_id → biz_person.user_id → biz_person.name)
### 历史修复 (跨 session, 本页相关)
- 第一轮 (manager_meeting_new.md commit 66a928a): 13 项 DB + 类型修复 (影响共享 BizMeeting)
- 第二轮 (manager_meeting_new.md commit 4fbfcbf + 8f62f65 + 518da05 + 29a4b02 + 379cf22 + ac31387): MeetingNew.vue 原型 1:1 重写 + 风格统一 + 双 toast 修复
- 第三轮 (commit ffda9c6): biz_project 加 create_user_id (影响跨表)
- 第四轮 (commit a081da8): MeetingNew.vue "期数" 文案改 "请填写第几期"
- 第五轮 — Meetings.vue 第一波修复 (commit 08d81e9): onCopy 改 bizAdd + 三处 __silentError
- 第五轮 — Meetings.vue style-alignment (commit 6d4f1e6): 删 inline margin, batch-bar 顺序对齐, 加 .date-sep
- 第五轮 — Meetings.vue 批量按钮去 plain (commit 6774c44): disabled 顺色修 + 提示 margin-left:auto