diff --git a/_self/manager_meeting_new.md b/_self/manager_meeting_new.md new file mode 100644 index 0000000..dbb8889 --- /dev/null +++ b/_self/manager_meeting_new.md @@ -0,0 +1,314 @@ +# manager/meetings/new — 端到端技术审查 + +> **审查时间**: 2026-08-18 +> **触发场景**: 用户访问 `/manager/meetings/new?projectId=13` +> **审查范围**: `ry-vue3/src/views/manager/MeetingNew.vue` (新建会议独立页) + 后端 BizMeeting 全链路 + DB 实测 +> **对比原型**: `proto/html/components/new-meeting.html` + +--- + +## 0. 4 跳定位 (铁律: 不按名字猜) + +| 跳 | 文件:行 | 结果 | +|---|---|---| +| ① 角色菜单 | `ry-vue3/src/layout/AdminLayout.vue:99` | `path: '/manager/meetings', title: '会议管理'` ✓ (新建是子菜单/独立路由) | +| ② 路由 | `ry-vue3/src/router/index.js:70` | `{ path: 'meetings/new', name: 'manager-meetings-new', component: () => import('@/views/manager/MeetingNew.vue') }` ✓ | +| ③ 组件 | `ry-vue3/src/views/manager/MeetingNew.vue` | 115 行, 本审查对象 | +| ④ 原型 | `proto/html/manager.html` 间接入口 → `components/new-meeting.html` | 注: 原型标 "角色: 执行方, 父级: executor.html" — **角色归属不一致!** | + +> ⚠️ **原型角色归属错位**:`new-meeting.html` 头部注释 `角色: 执行方 / 父级: executor.html`,但本组件在 manager 路由下。原型本身可能是早期版本,实际 Vue 实现给 manager 用 (合规管理员新建会议)。 + +--- + +## 1. 主要功能 + 可见性 + +### 1.1 主要功能 + +| 功能 | 前端入口 | 后端接口 | 涉及表 | +|---|---|---|---| +| 新建会议表单 | `manager/MeetingNew.vue` 表单 | `POST /business/meeting` → `BizMeetingController.add` → `bizMeetingService.insert` | biz_meeting | +| 预选项目回填 | `onMounted` → `bizGet('project', preselectProjectId)` | `GET /business/project/{projectId}` | biz_project | +| 取消 | `onCancel` | (无) | — | + +> ⚠️ **页面无编辑功能**: 即便 URL 是 `/new`,MeetingNew.vue 没 `route.params.id` 分支,**只能新建不能编辑**。如果想编辑会议,得另开 MeetingEdit.vue 或复用本组件加 id 分支。 + +### 1.2 可见性 (三层隔离) + +| 层 | 来源 | 校验字段 | +|---|---|---| +| 前端菜单 | `AdminLayout.vue:99` | `manager` 角色菜单 | +| 路由守卫 | `router/index.js:70` | `name: 'manager-meetings-new'`, 路由 meta.role='manager' (来自父路由) | +| 后端 | `BizMeetingController.list` → `selectList` **无 user_id 过滤**(manager 看所有会议) | 无 | + +> ⚠️ **manager 是全局视图角色**,跟 manager/Projects 一致,不过滤 — 设计正确 + +--- + +## 2. 筛选项 (filter-form) + +**本页面无筛选区** — 是纯表单页,直接进入。 + +--- + +## 3. 工具栏按钮 (toolbar) + +| 按钮 | 功能 | 接口 | 涉及表 | 原型对照 | +|---|---|---|---|---| +| 保存 | `onSave` | `POST /business/meeting` `{...form}` | biz_meeting | ✅ 原型 #6 | +| 取消 | `onCancel` | `router.back()` | — | ✅ 原型 #7 | + +--- + +## 4. 表格 (el-table) + +**本页面无表格** — 是纯表单页。 + +--- + +## 5. 行内操作按钮 + +**本页面无行内操作** — 是纯表单页。 + +--- + +## 6. Java Entity ↔ 数据库表 一致性 + +### 6.1 biz_meeting 主表 DDL (实测) + +```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=8 +``` + +### 6.2 关联表 DDL (引用) + +- `biz_project` (11 行, 7 索引 — 见 `manager_projects.md` §7.1) +- `biz_project_assign` (11 行, 5 索引 — 见 `manager_projects.md` §7.2) + +### 6.3 Entity ↔ 表字段对照表 + +| 实体字段 | 中文列名 (DB COMMENT) | 类型 | 表字段 | 类型 | 一致? | +|---|---|---|---|---|---| +| meetingId | 会议ID | **String** | meeting_id | **bigint** | ⚠️ 类型不一致 (Java String vs SQL bigint), 应改 Long | +| businessId | 业务会议ID 5643145673 | String | business_id | varchar(50) NOT NULL | ✅ (但 NOT NULL, 前端必填, 当前 form 没校验!) | +| 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 | ✅ | +| totalPeriods | 总期数 | Long | total_periods | int | ✅ | +| projectForm | 项目形式 | String | project_form | varchar(20) | ✅ | +| startTime | 会议开始时间 | Date | start_time | datetime | ✅ | +| 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** | 监察时间 | **String** | supervision_time | **datetime** | ⚠️ Java String vs SQL datetime — Java 实体应当用 Date | +| invitationUrl | 邀请函URL | String | invitation_url | varchar(500) | ✅ | +| scheduleUrl | 日程海报URL | String | schedule_url | varchar(500) | ✅ | +| laborSigned | 签署劳务 0未签 1已签 | String | labor_signed | char(1) | ✅ | +| createBy/createTime/updateBy/updateTime | 创建人/时间 等 | | (BaseEntity) | | ✅ | + +### 修订标记 + +> ⚠️ **meetingId 同 §6.3 之前项目** (类似 BizProject.projectId): Java 用 String, SQL 用 bigint, 跟同表其他 bigint 列 (projectId) 风格不一致。**biz_meeting 当前 7 行实测全有数据** (`meeting_id` 1-7, `business_id` 5643145673-77), 说明数据通过别的方式 (seed 脚本) 写入, 不是走当前 mapper.insert — 见 §10 #1。 +> +> ⚠️ **supervisionTime**: 同 §6.3 之前 BizProject.startTime/endTime 模式, Java String vs SQL datetime, 未修复。 +> +> ⚠️ **businessId NOT NULL**: DDL 是 NOT NULL, 但前端 form 没有 `businessId` 字段, 提交后 INSERT 会失败 (DDL 约束) — 见 §10 #2。 + +--- + +## 7. 索引检查 + +### 7.1 biz_meeting 索引 + +| Key | 列 | 用途覆盖 | +|---|---|---| +| PRIMARY | meeting_id | ✅ 主键, ORDER BY | +| uk_business_id | business_id (UNIQUE) | ✅ 业务会议ID 精确匹配 (业务上应唯一,合理) | +| idx_meeting_project | project_id | ✅ `WHERE project_id = ?` JOIN 用 | +| idx_meeting_stage | current_stage | ✅ 按阶段过滤 | +| ❌ **缺** | project_no | ⚠️ `MeetingNew.vue:103` 走 `form.projectNo` 反查 (但实际是 form 字段, 后端无 project_no 过滤查询), 可接受 | +| ❌ **缺** | start_time / end_time | ⚠️ 时间范围查询 (如 `/manager/meetings` 列表筛选), 当前 manager/Meetings.vue 是按 ID 排序, 可接受 | + +> **结论**: 索引设计够用, 4 个索引覆盖主键 + 2 个常见 WHERE 列 + 1 个 UNIQUE 业务约束。 + +--- + +## 8. 与原型差异 (实现偏离) + +### 8.1 实现新增 (原型没有) + +| 项 | 位置 | 说明 | +|---|---|---| +| 项目形式下拉 | `MeetingNew.vue:16-23` | 原型无, 实装 el-select | +| 当前阶段下拉 (4 选项) | `MeetingNew.vue:36-43` | 原型无, 实装 (DB current_stage 注释是 7 个状态, 但 el-select 只给 4 个, 缺 "待监管/监管通过/待整改/待结算/已结算" 5 个) | +| 总期数 input-number | `MeetingNew.vue:30-31` | 原型只在右列有"期数", 实装拆成"总期数 + 期数"两个 | + +### 8.2 原型有但实现缺失 + +| 原型项 | 实现状态 | +|---|---| +| 6 个只读值 (左列) | 🐛 **完全缺失** — 原型左列显示项目编号/项目名称/总场次/支持单位/创建时间/创建人员 6 个只读快照, 实装**全部不显示**, 只用 page-sub 提示"已选择项目: 13" | +| 面包屑 "项目列表 > 新建会议" | 🐛 实装用"首页 / 会议管理 / 新建会议", 跟原型"项目列表 > 新建会议"不一致 | +| 红字提示 "*会议名称默认为项目名称,不可修改" | 🐛 **完全缺失** | +| 会议名称只读 | 🐛 实装让用户手填会议名称, 跟原型"默认为项目名称,不可修改"相反 | +| 期数文本框 | ✅ (实装 input-number, 比原型 input text 更好) | +| 会议开始/结束时间 | ✅ | +| 备注 | ✅ | + +### 8.3 文字 / 标签差异 + +| 原型 | 实现 | 差异 | +|---|---|---| +| "新建会议" h1 | "首页 / 会议管理 / 新建会议" breadcrumb | 实现无 h1 标题, 只用 breadcrumb (轻微) | +| "项目编号" | "项目编号" | ✅ | +| "会议名称" | "会议名称" | ✅ | +| "会议ID" | — | 原型无, **实现新增** (P3 — 该删) | +| "项目形式" | "项目形式" | ✅ | +| "会议开始时间" | "会议开始时间" | ✅ | +| "会议结束时间" | "会议结束时间" | ✅ | +| "总期数" | "总期数" | ✅ | +| "期数" | "期数" | ✅ | +| "当前阶段" | "当前阶段" | ✅ | +| "备注" | "备注" | ✅ | + +### 8.4 总结 + +- **整体方向**: 实现**严重偏离原型**, 丢了 6 个项目只读快照 + 会议名称自动填充 + 红字提示, 改成全手填表单。 +- **业务偏离**: 原型"会议名称默认为项目名称,不可修改" — 设计意图是减少用户输入, 实装让用户自己填, 既不符合原型, 也增加出错概率 (用户可能填错/漏填)。 +- **设计偏离**: 实装多了"会议ID"输入框让用户填 PK, 完全错误 (meeting_id 是 AUTO_INCREMENT)。 + +--- + +## 9. 表字段冗余 / 设计问题 + +### 9.1 实体 vs 表不一致 (详见 §6.3) + +- ⚠️ meetingId String → Long (跟 §10 #1 关联) +- ⚠️ supervisionTime String → Date (跟 BizProject.startTime/endTime 同模式, 未修复) +- ✅ 其他字段一致 + +### 9.2 Mapper INSERT 严重残缺 🐛 + +`BizMeetingMapper.xml:34-42`: +```xml + + insert into biz_meeting + + meeting_id, + + + #{meetingId}, + + +``` + +**🛑 只插入 `meeting_id` 一列**, 其他所有字段 (business_id, project_no, meeting_name, start_time, end_time, current_stage 等) **完全不写**。 + +实测验证: 当前 7 行数据齐全 (`business_id` 5643145673-77), 但 `meeting_id` 是 1-7 顺序自增, `create_time` 全是 2026-08-16 19:18:32, `create_by='admin'`。明显是 seed 脚本直插, **不是从当前 mapper.insert 走的**。 + +如果用户从本组件提交: +- INSERT SQL 实际是 `INSERT INTO biz_meeting (meeting_id) VALUES (1234567890123456789)` — SnowflakeId 生成的 Long +- DB 默认值: business_id NOT NULL **违反约束 → INSERT 报错** +- 其他字段全部 NULL, 数据无效 + +### 9.3 business_id NOT NULL 约束缺失前端校验 + +DDL: `business_id varchar(50) NOT NULL`, 但前端 form 没有 businessId 输入框 (line 7-15 也没列)。`rules` 里也没 `businessId` 必填校验。即便 mapper 修好 INSERT, 提交也会因缺 business_id 报错。 + +### 9.4 project_id 缺失 + +form 只提交 `projectNo`, 不提交 `projectId`。即使 mapper INSERT 修好, 写库的 meeting 也只有 `project_no`, 没有 `project_id` — 跟 `biz_project` 的 JOIN 查询会漏掉这条记录。 + +### 9.5 JOIN 字段 vs 持久化字段混淆 + +`projectName` / `orgName` 是冗余字段 (DB COMMENT 写"冗余"), 但前端 form 没填这两个字段, mapper INSERT 也不会写。后续 JOIN 派生需要靠 service 层回查。 + +--- + +## 10. 待修复列表 (按优先级) + +| # | 问题 | 文件:行 | 修复建议 | 严重度 | +|---|---|---|---|---| +| 1 | **mapper INSERT 只插 meeting_id, 其他字段全 NULL** 🐛 | `BizMeetingMapper.xml:34-42` | 跟 updateByPrimaryKey 一样, 给所有非空字段加 `x,` | **P0** | +| 2 | **business_id NOT NULL, 前端 form 无字段无校验** 🐛 | `MeetingNew.vue` (整个表单) + `rules` | 加 businessId 输入框 + 必填校验, 或 mapper 给 businessId 兜底生成 | **P0** | +| 3 | **form 没 projectId, 写库 meeting 只关联 projectNo 不关联 projectId** | `MeetingNew.vue:67-77` + `:99-105 onMounted` | onMounted 查项目时同时回填 `form.projectId = r.data.projectId` (后端已 Long) | **P0** | +| 4 | **meetingId 输入框让用户填 PK** 🐛 | `MeetingNew.vue:13-15` | 删 (meeting_id 是 AUTO_INCREMENT), 删 rules.meetingId | **P1** | +| 5 | **6 个项目只读快照全缺失** (原型 §#4) | `MeetingNew.vue:6-51` (表单整体) | 在 form 上方加 el-descriptions 区域, 显示 projectNo/projectName/totalSessions/sponsorOrgName/createTime/createBy | **P1** | +| 6 | **会议名称应默认 = 项目名称, 不可修改** (原型 §#3 红字提示 + §#5 只读) | `MeetingNew.vue:10-12` + `:80 rules` | onMounted 查项目后, `form.meetingName = r.data.projectName`, 字段加 `:disabled="true"`, rules.meetingName 改 trigger 或保留必填 | **P1** | +| 7 | **面包屑跟原型不一致** | `MeetingNew.vue:3` | 改 "项目列表 > 新建会议" 或 "首页 > 会议管理 > 新建会议" 加一个返回项目列表链接 | **P2** | +| 8 | **page-sub 显示原始 ID "13", 不是项目名** | `MeetingNew.vue:4` | onMounted 查项目后, page-sub 改成 "已选择项目: {projectName} ({projectNo})" | **P2** | +| 9 | **当前阶段 el-select 只 4 选项, DB 注释 7 个状态** | `MeetingNew.vue:37-42` | 加 "待监管/监管通过/待整改/待结算/已结算" 5 个 option | **P2** | +| 10 | **meetingId String → Long** (跟 BizProject.projectId 同模式) | `BizMeeting.java:13` + getter/setter + `BizMeetingMapper.java:10` + `BizMeetingServiceImpl.java:17,23` + `BizMeetingController.java:31` | 跟之前 9 文件同模式改 | **P1** | +| 11 | **supervisionTime String → Date** (跟 BizProject.startTime/endTime 同模式) | `BizMeeting.java:68` | 加 `@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")` | **P1** | +| 12 | **preselectProjectId 只在 setup 读一次, 路由变化不响应** | `MeetingNew.vue:63-64` | 用 `computed(() => route.query.projectId)` 让其响应式 (但本场景路由不变, 影响小) | **P3** | +| 13 | **supervisionOpinion/By/Time 表单缺失** (DB 有, 当前 stage 是 "未执行" 后续阶段需填) | (后续 MeetingEdit 阶段) | 不在本页范围, 在 MeetingEdit.vue 加 | **P3** | + +--- + +## 11. 引用清单 + +### 前端 +- `ry-vue3/src/views/manager/MeetingNew.vue` — 新建会议独立页 (115 行) +- `ry-vue3/src/views/manager/Meetings.vue` — 会议管理列表 (保存后跳转目标) +- `ry-vue3/src/views/manager/Projects.vue:97` — 操作列 dropdown "建会" 跳转入口 `router.push('/manager/meetings/new?projectId=${row.projectId}')` +- `ry-vue3/src/layout/AdminLayout.vue:99` — 菜单项 "会议管理" +- `ry-vue3/src/router/index.js:70` — `/manager/meetings/new` 路由 +- `ry-vue3/src/utils/request.js:15-30` — 响应拦截器 (code=200 直接放行 data) + +### 后端 (Java) +- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizMeetingController.java` — 会议主 controller (54 行, 5 个 endpoint) +- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizMeeting.java` — 实体 (103 行, 字段含 2 个类型不一致: meetingId String vs meeting_id bigint + supervisionTime String vs supervision_time datetime) +- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizMeetingServiceImpl.java` — service 实现 (34 行, insert 调 SnowflakeId.injectIfEmpty) +- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizMeetingMapper.java` — mapper 接口 (16 行, 全部 String) + +### Mapper XML +- `ry-api/ruoyi-business/src/main/resources/mapper/business/BizMeetingMapper.xml` — 会议 mapper (76 行, **🛑 INSERT 严重残缺: 只插 meeting_id**) + +### 关联文件 +- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizProjectController.java:139-143` — `GET /business/project/{projectId}` (依赖此接口回填项目名, Long @PathVariable 已修) +- `ry-vue3/src/api/public.js:90` — `bizGet(entity, id)` → `GET /business/${entity}/${id}` + +### 配置 / 工具 +- `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:23 调 injectIfEmpty) + +### 原型 +- `proto/html/components/new-meeting.html` — 新建会议原型 (248 行, 标"角色: 执行方" 实装给 manager 用, 角色归属错位) +- `proto/html/components/project-manage.html:529` — 建会入口 `navigateTo('new-meeting.html?project_code=' + code)` (实装改用 projectId 参数) +- `proto/html/manager.html` (间接) — manager 端菜单入口 + +### 数据库 (实测) +- `biz_meeting` — 7 行, 4 索引 (含 1 唯一) +- `biz_project` — 11 行, 7 索引 (见 `manager_projects.md` §7.1) +- `biz_project_assign` — 11 行, 5 索引 (见 `manager_projects.md` §7.2)