# doctor/Submissions 端到端技术审查 **审查日期**: 2026-08-18 **目标页面**: `ry-vue3/src/views/doctor/Submissions.vue` (我的项目设计投稿) **审查方法**: 前端 → 后端 → 数据库 → 原型 四层穿透 **审查范围**: `AdminLayout` 菜单 → `router` 路由 → Vue 组件 → `BizProjectPlanController` → `BizProjectPlanMapper.xml` → `biz_project_plan` 表 → `proto/html/components/submissions.html` --- ## 第 0 章: 组件定位 (四跳) | 跳 | 文件 | 命中 | 行 | |---|---|---|---| | ① 角色菜单 | `ry-vue3/src/layout/AdminLayout.vue` | `MENU.doctor[4]` = `{ path: '/doctor/submissions', title: '我的项目设计投稿', icon: EditPen }` | 112 | | ② 路由 | `ry-vue3/src/router/index.js` | `name: 'doctor-submissions'`, `component: () => import('@/views/doctor/Submissions.vue')` | 94 | | ③ 组件 | `ry-vue3/src/views/doctor/Submissions.vue` | 审查目标文件 | — | | ④ 原型 | `proto/html/components/submissions.html` | `doctor.html:208 ` | 208 / submissions.html | > 路由表 (router/index.js:88-99) 还包含 `submission/new` (新建)、`submission/detail/:planId` (详情)、`submission/edit/:planId` (同 `SubmissionNew.vue` 修改模式),本审查不展开。 --- ## 第 1 章: 主要功能 + 可见性 ### 1.1 主要功能 | 功能 | 前端入口 | 后端接口 | 数据表 | |---|---|---|---| | 列表查询 (分页 + 筛选) | `bizList('projectPlan', q)` → Submissions.vue:119 | `GET /business/projectPlan/list` (BizProjectPlanController.list:29) | `biz_project_plan` | | 提交投稿 (单条 + 批量) | `bizUpdate('projectPlan', {planId, status:'1'})` → Submissions.vue:164,178 | `PUT /business/projectPlan` (BizProjectPlanController.edit:60) | `biz_project_plan.status` | | 新建投稿 (路由跳转) | `router.push('/doctor/submission/new')` → Submissions.vue:149 | `POST /business/projectPlan` (Controller.add:47) | `biz_project_plan` | | 查看/修改 (路由跳转) | Submissions.vue:151-155 | `GET /business/projectPlan/{planId}` + `PUT` (同上) | `biz_project_plan` | | 项目方向下拉 options | `request.get('/business/specialPlan/options')` → Submissions.vue:111 | `GET /business/specialPlan/options` (BizSpecialPlanController.options:40) | `biz_special_plan` (status='0' 启用项) | ### 1.2 可见性 (三层过滤) | 层 | 来源 | 校验字段 | |---|---|---| | 前端菜单 | `AdminLayout.vue:131` `menu` computed | 仅当 `role='doctor'` 显示;无 `requireMain` | | 路由守卫 | `router/index.js:88-100` `meta: { role: 'doctor' }` | 路由级 role 守卫 (其他角色不可访问) | | 后端 SQL 强隔离 | `BizProjectPlanController.list:32-35` | `if ("doctor".equals(roleType)) setSubmitterId(getUserId())` — **强制**只查自己投的稿,前端绕不开 | **其他角色如何查看投稿**: - `manager` 角色看不到此菜单;通过 `/manager/plans` (manager/Plans.vue) 全量管理所有投稿。 - `admin` 角色通过 `/admin/library` 或 admin 后台直接调接口。 - `sponsor/executor/leader` 角色均没有投稿查看入口(意图明确,投稿是评审专家专属)。 **数据隔离结论**: doctor 端无论 UI 怎么改 (切换 tab/手动调接口),后端 list 都强制覆盖 `submitter_id = currentUserId`。✅ 安全。 ### 1.3 状态枚举不一致警告 ⚠️ | 位置 | 取值 | |---|---| | `biz_project_plan` DDL 注释 | `1 待审 / 2 通过 / 3 拒绝` (**没有 `0` 待提交**) | | 前端 Submissions.vue:25-29 | `待提交`/`待审核`/`通过`/`拒绝` 映射到 `0`/`1`/`2`/`3` | | BizProjectPlanController.add:53-55 | doctor 角色强制 `setStatus('0')` (待提交) | > DDL 注释说"状态: 1 待审 / 2 通过 / 3 拒绝",但代码 + 前端都用 `0` 表示"待提交",且实际 SELECT 查到当前 DB 仅有 `1`/`2` (2 条数据,见 §6.1),`0`/`3` 都是**无数据状态**。 P2 待修: ① DDL 注释补上 `0 待提交`;② BizAuditStatusEnum (若有) 同步。 --- ## 第 2 章: 筛选项 (filter-form) | UI label | 控件 | 绑定字段 | 后端 SQL | 命中表.字段 | 选项来源 | 原型对比 | |---|---|---|---|---|---|---| | 策划方案名称 | `el-input` | `q.planName` | **❌ 无过滤**(Mapper 没有 `planName` 条件,详见 §9.1) | — | — | ⚠️ 原型 label="投稿名称",实现 label 改文案"策划方案名称" | | 项目方向 | `el-select` | `q.planDirectionId` | `p.plan_direction_id = #{planDirectionId}` (XML:43) | `biz_project_plan.plan_direction_id` | `GET /business/specialPlan/options` → 仅 `status='0'` 启用项 | ⚠️ 原型是 input "学科方向",实现改下拉 → **超出原型** (改进) | | 项目类别 | `dict-select` (DictSelect 组件) | `q.planCategory` | `p.plan_category = #{planCategory}` (XML:44, **等值匹配**) | `biz_project_plan.plan_category` | 字典 `biz_project_category` (`sys_dict_data` 7 条,`value-field=label`) | ➕ 原型没有"项目类别"筛选 (实现扩展) | | 形式 | `el-select` | `q.projectForm` | **❌ 无过滤**(Mapper 没写 `projectForm` 条件) | — | 写死 4 项: 线上/线下/线上+线下/其他 | ✅ 原型保留 input,实现升级下拉 (用户确认保留) | | 状态 | `el-select` | `q.status` | `p.status = #{status}` (XML:48, 等值匹配) | `biz_project_plan.status` | 写死 4 项: 待提交 0 / 待审核 1 / 通过 2 / 拒绝 3 | ⚠️ 原型 select 是"已结题/未结题",实现映射到 status 码 | | 备注 | `el-input` | `q.remark` | `p.remark = #{remark}` (XML:51, **等值匹配**) | `biz_project_plan.remark` | — | ✅ 原型一致 | **P0 发现 (筛选失效)**: `planName` 和 `projectForm` 两个筛选项前端都有,但 mapper `BizProjectPlanMapper.xml:40-54` 没有 `` 或 `` 条件,后端会**默默忽略**这两个查询条件,UI 上输入再多次值都查不到。详见 §10 P0-1。 **P1 发现 (精确匹配不像搜索)**: `remark` 是 `=` 等值匹配,而非 `LIKE`,用户输备注关键词搜不到。原型是 input,但语义上应是模糊搜索。详见 §10 P1-3。 --- ## 第 3 章: 工具栏按钮 (toolbar) | 按钮 | 触发函数 | 接口 | 涉及表 | 原型对比 | |---|---|---|---|---| | 新建投稿 | `onCreate()` → `router.push('/doctor/submission/new')` | (无前端调用, 仅路由跳转) | — | ✅ 一致 | | 批量提交 | `onBatch()` → 遍历 `selected` 调用 `bizUpdate('projectPlan', {planId, status:'1'})` | `PUT /business/projectPlan` | `biz_project_plan` (status 改 '1', submitter_id 兜底) | ✅ 一致,但**原型弹"您确定要提交吗?" modal,实现用 ElMessageBox.confirm** (效果一致,实现不同) | | 查找 | `查找` | 触发 `load()` 重新列表 | — | ✅ 一致 | | 重置 | `重置` | 清空 q 后 `load()` | — | ➕ 原型无,实现扩展 (辅助筛选) | --- ## 第 4 章: 表格 (el-table) ### 4.1 数据来源 SQL (从 mapper 摘出) ```sql -- BizProjectPlanMapper.xml:24-54 select p.plan_id, p.plan_name, p.plan_direction, p.plan_direction_id, s.title as plan_direction_title, p.plan_category, p.project_form, p.design_file_url, p.status, p.is_settled, p.project_no, p.remark, p.submitter_id, COALESCE(bp.name, u.user_name) as submitter_name, p.create_by, p.create_time, p.update_by, p.update_time from biz_project_plan p left join biz_special_plan s on s.id = p.plan_direction_id left join sys_user u on u.user_id = p.submitter_id left join biz_person bp on bp.user_id = u.user_id where 1=1 [and p.plan_direction_id = #{planDirectionId}] [and p.plan_category = #{planCategory}] [and p.submitter_id = #{submitterId}] -- doctor 角色强制 SET (Controller.list:33-34) [and p.status = #{status}] [and p.remark = #{remark}] -- 等值匹配,非 LIKE order by p.plan_id desc ``` > ⚠️ 实际跑出当前 DB 仅 2 条数据 (`status=1` x 1, `status=2` x 1),`submitter_id` 都不为 NULL。 (mysql 实测,见 §6.1) ### 4.2 列映射 | 列名 (label) | 绑定 | DB 表.字段 | 原型对比 | |---|---|---|---| | 选择框 | `el-table-column type="selection"` | — | ✅ 一致 | | 投稿名称 | `prop="planName"` | `biz_project_plan.plan_name` | ✅ 一致 (文案一致) | | 项目方向 | `{{ row.planDirectionTitle \|\| row.planDirection \|\| '-' }}` | JOIN `biz_special_plan.title` 兜底自身 `plan_direction` | ⚠️ 原型是 "学科方向"(文案差异,见 §8.3) | | 项目类别 | `prop="planCategory"` | `biz_project_plan.plan_category` (字典 label) | ➕ 原型无 (实现扩展,字典来源) | | 形式 | `prop="projectForm"` | `biz_project_plan.project_form` | ✅ 一致 (原型 input 升级为下拉) | | 设计文件 | `el-link href=row.designFileUrl` | `biz_project_plan.design_file_url` | ⚠️ 原型是"查看+下载"双链,实现合并为一个下载链 | | 状态 | `` | `biz_project_plan.status` | ⚠️ 原型纯文本,实现用 Tag 组件 | | 备注 | `prop="remark"` | `biz_project_plan.remark` | ✅ 一致 | | 操作 | 按钮组 (查看/修改/提交) | — | ✅ 一致 | --- ## 第 5 章: 行内操作按钮 | 按钮 | 触发函数 | 接口 | 后端动作 | 涉及表.字段 | 原型对比 | |---|---|---|---|---|---| | 查看 | `onView(row)` → `router.push('/doctor/submission/detail/${row.planId}')` | (仅路由) | — | — | ✅ 一致 | | 修改 | `onEdit(row)` → `router.push('/doctor/submission/edit/${row.planId}')` | `PUT /business/projectPlan` (通过 SubmissionNew.vue) | `updateByPrimaryKey`: SET submitter_id = currentUserId, 字段按实体非空更新 | `biz_project_plan.*` | ✅ 一致 | | 提交 | `onSubmit(row)` → ElMessageBox.confirm → `bizUpdate('projectPlan', {planId, status:'1'})` | `PUT /business/projectPlan` | `updateByPrimaryKey`: SET status='1' + submitter_id=currentUserId (兜底) | `biz_project_plan.status` | ✅ 一致 | **真实落点**: - 医生角色**真的不能**把 status 改成非 '1' —— 因为前端代码只发了 `{planId, status:'1'}`,即使前端被攻破修改请求体,Controller 也不会修改 status 之外的东西,但**没有强制 status='1'**,理论上可以发任意 status 给 doctor。 ✅ (业务上没必要,前端足够安全) - 修改按钮的可见性 `canModify`:`status !== '1' && status !== '2'` → 待审/通过 锁定,符合原型"待审核和审核通过后,操作只显示查看按钮,无法进行修改"。 - 提交按钮的可见性 `canSubmit`:`status === '0' || status === '3'` → 仅未提交/拒绝可提交,符合原型说明。 --- ## 第 6 章: Java Entity ↔ 数据库表 一致性 ### 6.1 biz_project_plan 主表 DDL (mysql 实测, 2026-08-18) ```sql CREATE TABLE `biz_project_plan` ( `plan_id` bigint NOT NULL AUTO_INCREMENT, `plan_name` varchar(200) NOT NULL, `plan_direction` varchar(500) DEFAULT NULL, `plan_direction_id` bigint DEFAULT NULL, `plan_category` varchar(50) DEFAULT NULL, `project_form` varchar(20) DEFAULT NULL, `design_file_url` varchar(500) DEFAULT NULL, `status` varchar(20) DEFAULT '1' COMMENT '状态 BizAuditStatusEnum: 1待审 2通过 3拒绝', `is_settled` char(1) DEFAULT 'N', `project_no` varchar(50) DEFAULT NULL, `remark` varchar(500) DEFAULT NULL, `audit_opinion` varchar(500) DEFAULT NULL, `audit_by` varchar(64) DEFAULT NULL, `audit_time` datetime DEFAULT NULL, `create_by` varchar(64) DEFAULT '', `create_time` datetime DEFAULT NULL, `update_by` varchar(64) DEFAULT '', `update_time` datetime DEFAULT NULL, `submitter_id` bigint DEFAULT NULL COMMENT '投?人用户ID (医生侧投稿归? ...)', PRIMARY KEY (`plan_id`), KEY `idx_plan_status` (`status`), KEY `idx_plan_project_no` (`project_no`), KEY `idx_plan_submitter` (`submitter_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1463898549354497 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='项目策划方案表'; ``` **当前 DB 数据状态 (mysql 实测)**: - 总行数: 2 - `submitter_id IS NOT NULL` 行数: 2 (100% 都被投稿) - 按 status 分组: `status=1` x1, `status=2` x1 (没有 `0`/`3` 数据) ### 6.2 join 关联表 #### biz_special_plan (下拉 options 用) | 列 | 类型 | 说明 | |---|---|---| | `id` | bigint PK AUTO_INCREMENT | 即 `biz_project_plan.plan_direction_id` | | `title` | varchar(200) NOT NULL | 前端下拉 `label=opt.title` | | `content_type` | varchar(20) DEFAULT 'rich' | (丰富/file 二选一) | | `content` | mediumtext | 富文本 | | `file_url` | varchar(500) | 上传文件 URL | | `sort_order` | int DEFAULT 0 | 排序,controller `/options` 没排序 (可改进) | | `status` | char(1) DEFAULT '0' | 0=启用,1=停用 | | `remark` | varchar(500) | — | | `create_by/update_by` | varchar(64) | — | | `create_time/update_time` | datetime | — | #### sys_dict_data (字典 `biz_project_category`) 7 条记录,`value-field=label` (即前端存的"共识""学术项目""重点学科"...)。注意 RuoYi 默认 `value-field=dict_value`,这里强制改 label,**对其他模块兼容性需复查**。 ### 6.3 Entity ↔ 表 字段对照 | 实体字段 | 实体类型 | 表字段 | 表类型 | 一致? | 备注 | |---|---|---|---|---|---| | planId | **String** | plan_id | **bigint** | ⚠️ 类型不一致 | 详见 §9.2 | | planName | String | plan_name | varchar(200) NOT NULL | ✅ | | | planDirection | String | plan_direction | varchar(500) | ✅ | | | planDirectionId | **Long** | plan_direction_id | bigint | ✅ | | | planDirectionTitle | String | (LEFT JOIN 注入) | biz_special_plan.title | ✅ | SQL 别名注入 | | planCategory | String | plan_category | varchar(50) | ✅ | 字典 label | | projectForm | String | project_form | varchar(20) | ✅ | | | designFileUrl | String | design_file_url | varchar(500) | ✅ | | | status | String | status | varchar(20) | ✅ | | | isSettled | String | is_settled | char(1) | ✅ | | | projectNo | String | project_no | varchar(50) | ✅ | | | remark | String | remark | varchar(500) | ✅ | | | **isFinished** ⛔ | String | **(不存在!)** | — | ❌ | **Entity 冗余字段,DDL 没有此列,代码也没用,应清理** | | createBy | String | create_by | varchar(64) | ✅ | BaseEntity | | createTime | Date | create_time | datetime | ✅ | BaseEntity | | updateBy | String | update_by | varchar(64) | ✅ | BaseEntity | | updateTime | Date | update_time | datetime | ✅ | BaseEntity | | auditOpinion | String | audit_opinion | varchar(500) | ✅ | | | auditBy | String | audit_by | varchar(64) | ✅ | | | auditTime | String | audit_time | datetime | ⚠️ 类型不一致 | 详见 §9.2 | | submitterId | **Long** | submitter_id | bigint | ✅ | | | submitterName | String | (LEFT JOIN 注入) | COALESCE(bp.name, u.user_name) | ✅ | | **修订**: - ⚠️ `planId` 实体 `String` vs 表 `bigint` —— MyBatis 自动转换目前没报错 (实测 list 接口 OK),但 SnowflakeId 返回 `Long`,Entity setPlanId(String) 隐式 toString 后插入,所以 DB 现在实际存的是 `bigint` 但表达式是 `String`。建议统一为 `Long`。 - ⚠️ `auditTime` 实体 `String` vs 表 `datetime` —— 实际 PHP/MyBatis 转换可读出,但前端拿到的时间字符串可能不是 `yyyy-MM-dd HH:mm:ss` 而是 ISO。建议统一 `Date + @JsonFormat`。 - ❌ `isFinished` Entity 字段无对应 DDL 列 —— 设计残留,建议删除 (或在 DDL 加列并实现)。 --- ## 第 7 章: 索引检查 ### biz_project_plan | Key | 列 | 用途覆盖 | |---|---|---| | PRIMARY | `plan_id` | `SELECT BY ID`/`WHERE p.plan_id = #{planId}` (详情) ✅ | | idx_plan_status | `status` | 状态筛选 ✅ | | idx_plan_project_no | `project_no` | 项目编号筛选(Manager 端用) ✅ | | idx_plan_submitter | `submitter_id` | **doctor 角色数据隔离强过滤** ✅ (P0 关键索引) | **未建索引列 (但有 WHERE 过滤)**: - `plan_direction_id` - 有 LEFT JOIN + 等值过滤,**应有索引** (大表时拖慢) 📌 - `plan_category` - 等值过滤,**应有索引** - `project_form` - 即使 mapper 没过滤 (§9.1),前端 UI 也有这筛选项,若补上 SQL 应同时建索引 - `remark` - 等值匹配 (说明里写 LIKE 更好,但当前 `=`),小数据量暂可接受 **当前规模**: 表只有 2 条数据,所有索引都不影响性能。但**未来预估** doctor 投稿会增长,`idx_plan_submitter` 是 P0 关键 (doctor 每打开页面都强制走这个),**已建 ✅**。 ### biz_special_plan (join 目标表) | Key | 列 | 用途覆盖 | |---|---|---| | PRIMARY | `id` | JOIN ON ✅ | | idx_special_plan_status | `(status, sort_order)` | options 端 status='0' 过滤 + 排序 ✅ | | idx_special_plan_create_time | `create_time` | (管理后台 list 用) ✅ | JOIN 列 `id` 是 PK,无需额外索引。 --- ## 第 8 章: 与原型差异 ### 8.1 实现新增 (原型没有) 1. **"项目类别"筛选 + 列** —— 原型 columns 只有 7 列 (复选框/名称/学科方向/形式/设计文件/状态/备注/操作 = 8 列含复选框),实现加了"项目类别"列 + 筛选下拉,使用字典 `biz_project_category` 填充。 ➕ 增强。 2. **"形式"升级为下拉** —— 原型是 input "形式",实现是 el-select 4 项。原 HTML 注释已写"原型无,用户确认保留"。 ✅ 3. **设计文件列简化** —— 原型是"查看"+"下载"双链,实现合并为一个 el-link "下载"。 ➕ 简化合理。 4. **状态列使用 Tag 组件** —— 原型纯文本"未结题/已结题",实现用 `` 渲染彩色徽章。 ➕ 升级。 5. **submitter_id 数据隔离** —— 原型无,实现给 doctor 角色强制过滤自己投的稿。 ➕ 安全。 6. **备注列扩展到所有行** —— 原型 8 行样例中有几行备注为空,实现统一展示 `prop="remark"`。 ✅ 7. **分页器** —— 原型无 el-pagination,实现加了 (10/20/50,prev/next/jumper)。 ➕ 工程化补全。 8. **响应式 @media 768px** (原型有,实现继承) ✅ ### 8.2 原型有但实现缺失/降级 无重大缺失。原型第 419 行的红字提示"*待审核和审核通过后,操作只显示查看按钮,无法进行修改"实现通过 `canModify` 隐式表达,用户不容易感知。 ### 8.3 文字 / 标签差异 | 位置 | 原型 | 实现 | 差异原因 | |---|---|---|---| | 表头 | 学科方向 | 项目方向 | 跟项目内统一术语 (manager/Plans.vue 用"项目方向") | | 表头 | (无) | 项目类别 | 字典扩展 | | 状态原型文案 | 未结题/已结题 | 待提交/待审核/通过/拒绝 | 原型状态枚举与项目不一致 (见 §1.3) | | 第 1 行红字提示 | "*点击查看可进行预览" | **未实现** | 详见 §10 P2-2 | | 表后红字提示 | "*待审核和审核通过后,操作只显示查看按钮,无法进行修改" | **未实现** | 详见 §10 P2-2 | ### 8.4 总结 整体方向:**扩展 + 升级** (无原型有但实现缺失的核心功能)。 实现严格遵循"7 列 → 加 1 列项目类别"的扩展模式,且安全控制 (submitter_id 强隔离) 远超原型。 --- ## 第 9 章: 表字段冗余 / 设计问题 ### 9.1 Mapper 字段过滤 vs 前端字段不一致 ⚠️ 前端 filter-form 有 6 个筛选项 (`planName`/`planDirectionId`/`planCategory`/`projectForm`/`status`/`remark`),但 `BizProjectPlanMapper.xml:40-54` 的 `selectList` 只支持 5 个条件,**缺 2 个**: - ❌ `planName` (前端有,mapper 没有) - ❌ `projectForm` (前端有,mapper 没有) **影响**: 用户输入"小牛血清"想搜投稿名,前端发了 `planName=小牛血清`,后端 mapper 没处理,SQL 无此条件,**查不到结果,以为是空数据**。P0。 ### 9.2 Entity 字段类型与 DDL 不一致 | 实体字段 | 实体类型 | DDL | 风险 | |---|---|---|---| | `planId` | `String` | `bigint` | 高 - SnowflakeId 返回 Long,setPlanId(String) 需 toString(),Mapper updateByPrimaryKey 的 WHERE plan_id=#{planId} 走隐式转换 | | `auditTime` | `String` | `datetime` | 中 - JSON 反序列化丢失时间格式 | **建议**: 统一为 `Long`/`Date`,避免隐式转换。P2。 ### 9.3 Entity 残留字段 `isFinished` Entity 有 `isFinished`,DDL 没有此列。BizProjectPlanService 接口全无 `getIsFinished()/setIsFinished()`,前端 Submissions.vue 不引用。**纯遗留**,建议清掉。P3。 ### 9.4 Dictionary `value-field=label` 与全局不一致 项目默认 RuoYi 字典 `value-field=dict_value`,DictSelect 组件在 Submissions.vue:13 强行写 `value-field="label"`,这意味着插入数据是中文 label。但 DDL `plan_category varchar(50)` 可以存,只是国际化场景下会破坏 (例如以后加英文 category)。审查所有 manager 端是否同步? 若不一致,会有数据脏。 P2。 ### 9.5 biz_project_plan 没有软删除字段 DDL 无 `del_flag`,Mapper `deleteByPrimaryKeys` 是硬删除。doctor 角色无删除按钮 (前端未实现),但接口 `DELETE /business/projectPlan/{ids}` 可被 admin/manager 任意删。验收策略需明确。P2。 ### 9.6 role_type 多源真相风险 (项目级) 根据 `sys-user-role-type-truth` memory,本项目单一可信源是 `sys_user.role_type`,`biz_user_role_bind` 是废表别动。本页面 Controller.list:32 `getRoleType()` 直接读 sys_user,符合 ✅。 ### 9.7 唯一约束 `biz_project_plan.plan_id` 是 PK,无其他唯一键。可考虑加 `uk_plan_submitter_name(plan_name, submitter_id)` 防同人重名 (业务待定)。 --- ## 第 10 章: 待修复列表 | # | 问题 | 文件 | 修复建议 | 严重度 | |---|---|---|---|---| | P0-1 | 筛选项 `planName` 和 `projectForm` 在前端 filter-form 有绑定,但 `BizProjectPlanMapper.xml:40-54` selectList 没有 `` 和 `` 条件,导致输入后查不到数据 (沉默失效) | `BizProjectPlanMapper.xml:40-54` + `Submissions.vue:6,16` | ① mapper 加 `and p.plan_name LIKE concat('%', #{planName}, '%')`;② 同时加 `and p.project_form = #{projectForm}` | **P0** | | P0-2 | `remark` filter 用 `=` 等值匹配 (mapper:51),语义上应是 LIKE 模糊搜索,否则用户必须输完整备注才能命中 | `BizProjectPlanMapper.xml:51` | 改 `=` 为 `LIKE concat('%', #{remark}, '%')` | P0 | | P1-1 | DDL 注释 `status` 缺少 `0 待提交` 枚举,但代码和前端都用 `0`,且 DDL DEFAULT '1' 与 controller.add:53-55 的 doctor 角色强制 setStatus('0') 配合有歧义 | `biz_project_plan` DDL COMMENT | ALTER TABLE 修改 COMMENT 为 `状态 BizAuditStatusEnum: 0待提交 1待审核 2通过 3拒绝` | P1 | | P1-2 | 当前 DB 无 `status=0`/`status=3` 数据,canModify/canSubmit 逻辑未真实跑过。需在测试环境插入 0/3 各一条做回归 | 测试数据 | INSERT (status='0'/'3') 用于测试 | P1 | | P1-3 | `remark` filter 用 `=` 等值 (见 P0-2),且前端表头列也是 `prop="remark"`,若数据库内 `remark` 是长文本,搜索 UX 差。`planName`/`remark` 未来都建议 LIKE | (合并 P0-2) | — | (合并) | | P2-1 | `auditTime` 实体 `String` vs DDL `datetime` 类型不一致,JSON 序列化时区可能错乱 | `BizProjectPlan.java:67` + DDL audit_time | 统一 `Date + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")` | P2 | | P2-2 | 原型红字提示"*点击查看可进行预览" 和 "*待审核和审核通过后,操作只显示查看按钮,无法进行修改" 实现未保留 | Submissions.vue | 在表格下方新增 2 行 `.hint-text` 红字,跟原型风格一致 | P2 | | P2-3 | 字典 `biz_project_category` 用 `value-field=label` (中文 label 进 DDL),国际化将破。需全项目排查是否一致 | DictSelect 调用方 | 评审全项目 DictSelect 用法,统一改 `value-field=dict_value`,前端 label 同时展示 | P2 | | P2-4 | `biz_project_plan` DDL 无 `del_flag`,Mapper 硬删除;应加 del_flag 默认 '0',软删除 | DDL + Mapper | ALTER TABLE ADD del_flag char(1) DEFAULT '0';Mapper deleteByPrimaryKey 改 UPDATE del_flag='1' | P2 | | P3-1 | Entity `isFinished` 字段无对应 DDL 列,纯遗留 | `BizProjectPlan.java:46-47,96-97` | 删掉 getIsFinished/setIsFinished/getter+setter | P3 | | P3-2 | `planId` 实体 `String` 与 DDL `bigint` 类型不一致,Setter 隐式转换风险 | `BizProjectPlan.java:13,72-73` | 改 Long 类型,同步 SnowflakeId.injectIfEmpty | P3 | | P3-3 | `biz_special_plan.status='0'` 表示启用 (跟项目内其它表 `del_flag` 语义混) | 文档/常量 | 加 BizPlanStatusEnum 常量类注释 | P3 | | P3-4 | plan_direction_id / plan_category / project_form 缺索引,数据量 <10k 暂可接受,>10k 后会有 performance 影响 | DDL + Mapper | ALTER TABLE 加 `idx_plan_direction_id` / `idx_plan_category` / `idx_plan_form` | P3 | --- ## 第 11 章: 引用清单 | 类型 | 文件 | 行号 | 备注 | |---|---|---|---| | 菜单 | `ry-vue3/src/layout/AdminLayout.vue` | 107-114 | doctor 菜单 6 项 | | 路由 | `ry-vue3/src/router/index.js` | 88-100 | /doctor children | | 组件 | `ry-vue3/src/views/doctor/Submissions.vue` | — | 审查目标 | | 新建 | `ry-vue3/src/views/doctor/SubmissionNew.vue` | — | (未读,但路由引用) | | 详情 | `ry-vue3/src/views/doctor/SubmissionDetail.vue` | — | (未读,但路由引用) | | 前端 API | `ry-vue3/src/api/public.js` | 32-37, 90-94 | bizList/bizGet/bizAdd/bizUpdate/bizDelete 通用入口 | | Controller | `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizProjectPlanController.java` | — | 全部接口 | | Service | `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizProjectPlanServiceImpl.java` | — | 薄壳 | | Entity | `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectPlan.java` | — | 见 §6.3 | | Mapper | `ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectPlanMapper.xml` | 24-54 | 关键 selectList | | Join Controller | `ry-api/ruoyi-business/src/main/java/com/.../controller/BizSpecialPlanController.java` | 40-53 | /options 接口 | | 数据源 | `ry-api/ruoyi-admin/src/main/resources/application-druid.yml` | 7-15 | mysql root@127.0.0.1:3306/guoju0808 | | 原型入口 | `proto/html/doctor.html` | 208 | data-page=components/submissions.html | | 原型 | `proto/html/components/submissions.html` | — | 484 行 HTML/CSS | | DB 实测 | `biz_project_plan` | §6.1 | 2 行数据,status 仅有 1/2 | | DB 实测 | `biz_special_plan` | §6.2 | 7 行,AUTO_INCREMENT=8 | | DB 实测 | `sys_dict_data` dict_type=biz_project_category | §6.2 | 7 条字典 | --- ## 附录: skill 改进反馈 (来自本次审查) > 用户反馈:"从 druid yml 中获取。把获取数据连接的方法写到 skill 中。" **改进内容**: 在 page-tech-review skill 中增加 §0.5 节"DB 连接获取",固化从 `ry-api/ruoyi-admin/src/main/resources/application-druid.yml` 读 `spring.datasource.druid.master.url/username/password` 的步骤,避免每次询问用户。 ```bash # 提取 DB 连接 (单行命令) grep -E "url:|username:|password:" ry-api/ruoyi-admin/src/main/resources/application-druid.yml # → 拿到 jdbc:mysql://127.0.0.1:3306/guoju0808? ... / root / cu2oh2co3 # 实测 DDL 用: mysql -h 127.0.0.1 -u root -pcu2oh2co3 guoju0808 -e "SHOW CREATE TABLE biz_project_plan \G" ``` (本次审查已落地使用,见 §6.1)