- manager_projects_assign.md: 新增 11 章端到端审查报告 (4 跳定位 + Entity 一致性 + 索引 + 原型对比 + 待修复 16 条) - manager_projects.md: 修复记录表加 2026-08-20 P1/P2/P3 修复条目 (与上面 commit 一致) Co-Authored-By: Claude <noreply@anthropic.com>
495 lines
36 KiB
Markdown
495 lines
36 KiB
Markdown
## 修复记录
|
||
|
||
| 日期 | 章节 | 修复内容 |
|
||
|---|---|---|
|
||
| 2026-08-18 | §6.3 / §9.1 | **BizProject.projectId** String → Long (9 文件: domain / IBizProjectService / BizProjectServiceImpl / BizProjectMapper.java+xml / BizProjectController / BizExecutionIntentController / BizPublicityIntentController / BizPublicController)。SnowflakeId.injectIfEmpty 反射依赖 setter(String), Long 后会抛 NoSuchMethodException 被吞掉 (SnowflakeId.java:30-31), 安全 — 但显式删掉更干净 (biz_project 用 AUTO_INCREMENT)。 |
|
||
| 2026-08-18 | §6.3 / §9.1 | **BizProject.startTime / endTime** String → Date, 加 `@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")` (跟 BizMeeting 一致)。Mapper XML 8 处 `<if test="... != null and ... != ''">` 改为 `<if test="... != null">` (Date 无 isEmpty, OGNL 会报错)。前端 el-date-picker value-format "YYYY-MM-DD HH:mm:ss" 双向兼容, 零前端改动。 |
|
||
| 2026-08-18 | §10 #7 | **`batchScoreRows` 死代码删除** (Projects.vue:501)。注释说"保留兼容旧引用", 但 grep 全文 0 个引用, 纯死变量。 |
|
||
| 2026-08-18 | §10 #5 | **生产代码残留 console.info/error 4 处删除** (Projects.vue:299, 302, 306, 874)。`load()` 进/出/失败各一处 + 路由 watch reload 一处, 共 4 行, 全部删除。 |
|
||
| 2026-08-18 | §10 #9 | **日期选择器 value-format 对齐 type** (Projects.vue:17, 19)。原 `type="date"` + `value-format="YYYY-MM-DD HH:mm:ss"` 矛盾 (date picker 给不出 HH:mm:ss), 改 value-format 为 `YYYY-MM-DD`, MySQL 对 datetime 列隐式转换。 |
|
||
| 2026-08-18 | §7.1 / §10 #12 | **DB: biz_project 加索引 `idx_sponsor_admin_user_id`**。ALTER TABLE biz_project ADD INDEX idx_sponsor_admin_user_id (sponsor_admin_user_id)。EXPLAIN 验证: type=ref, Extra=Using index。 |
|
||
| 2026-08-18 | §7.1 / §10 #13 | **DB: biz_project 加复合索引 `idx_start_end_time (start_time, end_time)`**。比 2 个单列索引更适合 `WHERE start_time >= ? AND end_time <= ?` 的范围查询。EXPLAIN 验证: type=range, Extra=Using where; Using index。 |
|
||
| 2026-08-18 | §7.2 / §10 #14 | **DB: biz_project_assign 加 UNIQUE `uk_project_unit (project_id, execution_unit_id)`**。先查 11 行 0 重复, 安全。防重复分配。注意: `biz_project_assign` 是 **项目→executor (执行方)** 分配表, 不是 sponsor; sponsor 走独立的 `biz_project_sponsor_assign`。 |
|
||
| 2026-08-20 | §2 / §10 #3 | **P0 修复: selectList 补 managerScore 过滤** (88df386)。前端评价得分筛选透传到后端但 mapper 没读, 形同虚设。加 `<if test="managerScore != null">and manager_score = #{managerScore}</if>`, 跟 INSERT/UPDATE 同款 BigDecimal null check。EXPLAIN 验证 12 行数据走 PRIMARY Backward scan, 命中 1 行 manager_score=5。 |
|
||
| 2026-08-20 | §2 / §8.1 | **支持单位筛选改多选 remote el-select** (1b35161 + c85370d + 9d2ed60)。前后端 3 commit: 后端 `selectSponsorOrgOptions` 加 LIMIT 5 + mapper `sponsor_admin_user_id IN <foreach>` + 前端 multi-select + 200ms debounce 远程搜索 + 已选 userId 重拉回保证 label 存在。 |
|
||
| 2026-08-20 | §2 / §8.1 | **服务机构筛选改多选 remote el-select** (8d50450 + e462eea + b4acd72)。镜像支持单位, 区别: 服务机构走 EXISTS + biz_project_assign.exec_user_id IN (1 项目 = N 服务方, 关联表), 支持单位走单列 IN。`selectExecutorOrgOptions` 也加 LIMIT 5。 |
|
||
| 2026-08-20 | §6.3 | **彻底删除 BizProject.execOrgName** (2b69ef1)。`execOrgName` 旧 LIKE 查询条件字段已废弃, 新查询走 `execAdminUserIds`, 字段保留无意义, 直接删。`execOrgNames` (复数, GROUP_CONCAT 派生) 保留。 |
|
||
| 2026-08-20 | §9.5 | **DB: biz_project_assign 加复合索引 `idx_assign_exec_user_project (exec_user_id, project_id)`** (be1c973)。新加的 EXISTS+IN 查询从 `idx_assign_exec_user` 单列 + 回表 → 复合 covering, EXPLAIN 验证 `Using index` + `LooseScan`/`FirstMatch`, 无需回表。 |
|
||
| 2026-08-20 | doc | **§10 #1 / #2 删除 + 7 处相关误标清理** (25cec1e)。原 §10 P0 #1 (单条开通误改 is_finished='0') 和 #2 (confirmDeleteAnn 引用不存在字段) 经核实属业务正确, 删除 doc 误标, 不补 "业务正确" 注释。 |
|
||
|
||
---
|
||
|
||
## 0. 4 跳定位 (铁律: 不按名字猜)
|
||
|
||
| 跳 | 文件:行 | 结果 |
|
||
|---|---|---|
|
||
| ① 角色菜单 | `ry-vue3/src/layout/AdminLayout.vue:96` | `path: '/manager/projects', title: '项目管理'` ✓ |
|
||
| ② 路由 | `ry-vue3/src/router/index.js:60` | `component: () => import('@/views/manager/Projects.vue')` ✓ |
|
||
| ③ 组件 | `ry-vue3/src/views/manager/Projects.vue` | 920 行,本审查对象 |
|
||
| ④ 原型 | `proto/html/components/project-manage.html` + `project-assign.html` | 项目管理 + 项目分配 dialog ✓ |
|
||
|
||
> **⚠️ 重要**: AdminLayout L96 (`manager.projects`) 与 AdminLayout L86 (`admin.projects` 一级菜单的子项, 待确认) **不冲突**, 但路由表 L28 `admin-projects` 也指向同一 `manager/Projects.vue` 组件 (`router/index.js:28`)。说明 admin 角色的"项目"也复用本页, 走相同的 `selectList` 接口。
|
||
|
||
---
|
||
|
||
## 1. 主要功能 + 可见性
|
||
|
||
### 1.1 主要功能
|
||
|
||
| 功能 | 前端入口 | 后端接口 | 涉及表 |
|
||
|---|---|---|---|
|
||
| 项目列表(分页+筛选) | `manager/Projects.vue` 表头筛选 + `load()` | `GET /business/project/list` → `BizProjectController.list` → `BizProjectService.selectList` | biz_project |
|
||
| 单条/批量结题 | `openClose` / `openBatch('close')` | `PUT /business/project` → `bizProjectService.updateByPrimaryKey` (只改 `is_finished='1'`) | biz_project.is_finished |
|
||
| 单条/批量开通 | `openActivate` / `openBatch('activate')` | `PUT /business/project` `{projectId, isFinished: '0'}` | biz_project.is_finished='0' |
|
||
| 删除公告(单条) | `confirmDeleteAnn` | `PUT /business/project` (清空 publishUrl/invitationUrl/supportLetterUrl) | biz_project |
|
||
| 单条/批量执行单位评分 | `openSingleScore` / `openBatch('score')` | `POST /business/project/rate` (4 维度) + `PUT /business/project` (写聚合分) | biz_project_rating + biz_project.manager_score |
|
||
| 项目分配(单条+批量) | `doAssign` / `openAssign` | `PUT /business/project` (改 sponsor) + `POST /business/project/{id}/assigns` (先删后插) | biz_project + biz_project_assign |
|
||
| 导出报名专家 | `exportExperts` | `POST /business/executionIntent/export?projectNo=...` | biz_execution_intent |
|
||
| 新建/编辑/查看 | 跳独立页 `/manager/projects/new`、`/edit/:id`、`/detail/:id` | 同 | biz_project |
|
||
|
||
> **⚠️ 原型顶部 "导出" / "项目评价导出" 按钮在 Vue 是 stub** (`Projects.vue:361-362`, `exportProjects` / `exportEval` 仅 `ElMessage.info('开发中')`)。仅 "已报名专家信息导出" 实装, 但挪到了行操作 dropdown。
|
||
|
||
### 1.2 哪些人能看到本页面
|
||
|
||
三层过滤:
|
||
|
||
| 层 | 来源 | 校验字段 |
|
||
|---|---|---|
|
||
| 前端菜单 | `AdminLayout.vue:96` `MENU.manager` | 无 `requireMain`, 主账号/子账号均可见 (manager 角色没子账号概念) |
|
||
| 路由守卫 | `router/index.js:60` `name: 'manager-projects'` | `meta.role` 无显式声明, 走全局 `permission.js` 校验登录态 |
|
||
| 后端 SQL | `BizProjectController.list` 注入 `@Autowired BizProjectService` → `selectList` | **无 role 过滤**, 返回全部项目, 跟前端的"manager 全局可见" 语义一致 |
|
||
|
||
**其他角色映射**:
|
||
- sponsor → 走 `/business/project/sponsorList` (按 `sponsor_admin_user_id = uid` 过滤), 独立端 `sponsor/Projects.vue`, 不进本页
|
||
- executor → 走 `/business/project/executorList` (按 `biz_project_assign` 关联过滤), 独立端 `executor/Projects.vue`, 不进本页
|
||
- doctor → 走 `/business/executionIntent` (跟项目关系是报名, 不是项目主), 独立端 `doctor/Projects.vue`
|
||
|
||
**数据隔离**: manager 是无界角色, 看到全量项目, 不存在"自己的项目"概念。
|
||
|
||
---
|
||
|
||
## 2. 筛选项 (filter-form)
|
||
|
||
| UI label | 控件 | v-model | 后端 SQL | 命中表.字段 | 选项来源 | 原型对照 |
|
||
|---|---|---|---|---|---|---|
|
||
| 支持单位 | `el-select` (multiple+remote) | `q.sponsorAdminUserIds` (List<Long>) | `selectList` line 167-172 `sponsor_admin_user_id IN <foreach>` | biz_project.sponsor_admin_user_id | `/business/org/sponsorOptions` (LIMIT 5) | ✅ 原型有 |
|
||
| 服务机构 | `el-select` (multiple+remote) | `q.execAdminUserIds` (List<Long>) | `selectList` line 173-181 `EXISTS (biz_project_assign WHERE exec_user_id IN <foreach>)` | biz_project_assign.exec_user_id (MAIN user_id) | `/business/org/executorOptions` (LIMIT 5) | ✅ 原型有 |
|
||
| 项目编号 | `el-input` | `q.projectNo` | line 164 `project_no LIKE concat('%',?, '%')` | biz_project.project_no | — | ✅ 原型有 |
|
||
| 项目名称 | `el-input` | `q.projectName` | line 165 `project_name LIKE concat('%',?, '%')` | biz_project.project_name | — | ✅ 原型有 |
|
||
| 项目形式 | `el-select` | `q.projectForm` | line 166 `project_form = ?` | biz_project.project_form | **写死**: 线上/线下/线上+线下/其他 | ✅ 原型有 |
|
||
| 项目时间(起/止) | `el-date-picker` type="date" + `value-format="YYYY-MM-DD"` | `q.startTime/endTime` | line 154-155 `start_time >= ? AND end_time <= ?` (sparentList); line 96-97 同 (sponsorList); line 142-143 同 (executorList) | biz_project.start_time/end_time | — | ✅ 原型有 |
|
||
| 是否结题 | `el-select` | `q.isFinished` | line 185 `is_finished = ?` | biz_project.is_finished | **写死**: 已结题→'1' / 未结题→'0' | ✅ 原型有 |
|
||
| 是否结算 | `el-select` | `q.isSettled` | line 186 `is_settled = ?` | biz_project.is_settled | **写死**: 已结算→'Y' / 未结算→'N' | ⚠️ 原型**无** (实现新增) |
|
||
| 评价得分 | `el-input` | `q.managerScore` | line 188 `manager_score = ?` (BigDecimal, 跟 INSERT/UPDATE 同款 `!= null` 检查) | biz_project.manager_score | — | ✅ 原型有 |
|
||
| 按钮 | 查找/重置 | — | — | — | — | ✅ 原型有 |
|
||
|
||
> **其他角色访问**:
|
||
> - sponsor → `GET /business/project/sponsorList` (按 `sponsor_admin_user_id = uid` 过滤)
|
||
> - executor → `GET /business/project/executorList` (按 `biz_project_assign` 关联过滤)
|
||
> - 这两个**不会进** manager/Projects.vue,但共用 controller
|
||
|
||
---
|
||
|
||
## 3. 工具栏按钮 (toolbar)
|
||
|
||
| 按钮 | 功能 | 接口 | 涉及表 | 原型对照 |
|
||
|---|---|---|---|---|
|
||
| 新建项目 | 跳转独立页 | (无, 仅 router.push) | — | ✅ 原型有 |
|
||
| 项目分配 | 打开分配 dialog (单选/批量) | `PUT /business/project` + `POST /business/project/{id}/assigns` | biz_project + biz_project_assign | ✅ 原型有 |
|
||
| 批量评分 | 打开批量评分 dialog (共享 4 维度) | `POST /business/project/rate` × N + `PUT /business/project` × N | biz_project_rating + biz_project.manager_score | ✅ 原型有 |
|
||
| 批量结题 | 打开批量结题 dialog (纯确认) | `PUT /business/project` {isFinished: '1'} × N | biz_project.is_finished | ✅ 原型有 |
|
||
| 批量开通 | 打开批量开通 dialog (含截止时间 picker) | `PUT /business/project` {endTime} × N | biz_project.end_time | ✅ 原型有 |
|
||
| ~~导出~~ | stub `exportProjects` | (无) | — | ⚠️ 原型有, **实现未做** |
|
||
| ~~项目评价导出~~ | stub `exportEval` | (无) | — | ⚠️ 原型有, **实现未做** |
|
||
|
||
---
|
||
|
||
## 4. 表格 (el-table)
|
||
|
||
### 4.1 数据来源
|
||
|
||
`selectList` SQL (`BizProjectMapper.xml:149-189`):
|
||
|
||
```xml
|
||
SELECT p.project_id, p.project_no, p.project_name, p.total_sessions, p.done_sessions, p.todo_sessions,
|
||
p.total_amount, p.available_amount, p.paid_labor_amount, p.paid_meeting_amount,
|
||
p.manager_score, p.sponsor_score, p.sponsor_admin_user_name, p.project_form, p.is_finished, p.is_settled,
|
||
p.sponsor_admin_user_id, p.lead_user_id, p.is_bid_project,
|
||
p.create_by, p.create_user_id, p.create_time, p.update_by, p.update_time, p.manage_fee,
|
||
p.start_time, p.end_time, p.submit_deadline_days,
|
||
p.support_contract_url, p.execute_contract_url, p.invitation_url, p.support_letter_url, p.publish_url,
|
||
o.org_name as sponsor_org_name, -- LEFT JOIN biz_org (sponsor)
|
||
lu.user_name as lead_user_name, -- LEFT JOIN sys_user (project manager)
|
||
bp.name as create_user_name, -- LEFT JOIN biz_person (creator)
|
||
(SELECT GROUP_CONCAT(DISTINCT o2.org_name SEPARATOR ',')
|
||
FROM biz_project_assign bpa
|
||
JOIN biz_org o2 ON o2.org_id = bpa.execution_unit_id AND o2.org_type = 'executor'
|
||
WHERE bpa.project_id = p.project_id) AS exec_org_names
|
||
FROM biz_project p
|
||
LEFT JOIN biz_org o ON o.user_id = p.sponsor_admin_user_id AND o.org_type = 'sponsor'
|
||
LEFT JOIN sys_user lu ON lu.user_id = p.lead_user_id
|
||
LEFT JOIN biz_person bp ON bp.user_id = p.create_user_id
|
||
|
||
WHERE
|
||
[projectIds IN (...) if params.projectIds] -- 预留, manager 端没传
|
||
AND [projectNos IN (...) if params.projectNos] -- 预留, manager 端没传
|
||
AND [project_no LIKE ? if q.projectNo]
|
||
AND [project_name LIKE ? if q.projectName]
|
||
AND [project_form = ? if q.projectForm]
|
||
AND [sponsor_admin_user_id IN (...) if q.sponsorAdminUserIds] -- 多选 IN
|
||
AND [EXISTS (bpa WHERE exec_user_id IN (...)) if q.execAdminUserIds] -- 多选 EXISTS+IN
|
||
AND [is_finished = ? if q.isFinished]
|
||
AND [is_settled = ? if q.isSettled]
|
||
AND [manager_score = ? if q.managerScore] -- 88df386 新加
|
||
ORDER BY project_id DESC
|
||
```
|
||
|
||
### 4.2 列映射
|
||
|
||
| 列名 | `row.*` | 表.字段 | 原型对照 |
|
||
|---|---|---|---|
|
||
| 选择 | (el-table-column type="selection") | — | ✅ |
|
||
| 项目编号 | `row.projectNo` | biz_project.project_no | ✅ |
|
||
| 项目名称 | `row.projectName` | biz_project.project_name | ✅ |
|
||
| 总场次/总期数 | `row.totalSessions` | biz_project.total_sessions | ✅ |
|
||
| 已执行 | `row.doneSessions` | biz_project.done_sessions | ✅ |
|
||
| 未执行 | `row.todoSessions` | biz_project.todo_sessions | ✅ |
|
||
| 总金额 | `row.totalAmount` | biz_project.total_amount | ✅ |
|
||
| 可用金额 | `row.availableAmount` | biz_project.available_amount | ✅ |
|
||
| 已支付劳务费 | `row.paidLaborAmount` | biz_project.paid_labor_amount | ✅ |
|
||
| 已支付会务费 | `row.paidMeetingAmount` | biz_project.paid_meeting_amount | ✅ |
|
||
| 执行单位评分(合规) | `row.managerScore` | biz_project.manager_score | ✅ |
|
||
| 执行单位评分(支持) | `row.sponsorScore` | biz_project.sponsor_score | ✅ |
|
||
| 支持单位 | `row.sponsorOrgName` | biz_org.org_name (JOIN) | ✅ |
|
||
| 服务机构 | `row.execOrgNames` | GROUP_CONCAT of biz_org.org_name | ✅ |
|
||
| 项目形式 | `row.projectForm` | biz_project.project_form | ✅ |
|
||
| 是否结题 | `row.isFinished` (tag 化) | biz_project.is_finished | ✅ |
|
||
| 项目开始时间 | `row.startTime` | biz_project.start_time | ✅ |
|
||
| 项目结束时间 | `row.endTime` | biz_project.end_time | ✅ |
|
||
| 操作 | (dropdown 菜单) | — | ✅ |
|
||
|
||
---
|
||
|
||
## 5. 行内操作按钮 (op-cell)
|
||
|
||
| 按钮 | 触发函数 | 接口 | 后端动作 | 涉及表.字段 | 原型对照 |
|
||
|---|---|---|---|---|---|
|
||
| 查看 | `viewDetail` | (router.push) | — | — | ✅ |
|
||
| 编辑 | `doEdit` | (router.push) | — | — | ✅ |
|
||
| **更多** dropdown | `onAction` | — | — | — | (原型无, 折叠菜单扩展) |
|
||
| └ 建会 | `doCreateMeeting` | (router.push) | — | — | ✅ |
|
||
| └ 结题 | `openClose` + `confirmClose` | `PUT /business/project` `{projectId, isFinished: '1'}` | `bizProjectService.updateByPrimaryKey` | biz_project.is_finished='1' | ✅ |
|
||
| └ 项目分配 | `doAssign` | 同 toolbar `openAssign` | — | — | ✅ (原型叫"分配", Vue 改名) |
|
||
| └ 执行单位评分 | `openSingleScore` + `confirmSingleScore` | `PUT /business/project` `{managerScore}` + `POST /business/project/rate` | `bizProjectRatingService.upsertRating` | biz_project.manager_score + biz_project_rating (新行或更新) | ✅ |
|
||
| └ 导出报名专家 (divided) | `exportExperts` | `POST /business/executionIntent/export?projectNo=...` | (单独执行方意图表) | biz_execution_intent | ✅ (原型是顶部按钮, Vue 挪到行内) |
|
||
| └ 开通 (divided, 条件) | `openActivate` + `confirmActivate` | `PUT /business/project` `{projectId, isFinished: '0'}` | 同上 | biz_project.is_finished='0' | ✅ |
|
||
| └ 删除公告 (divided, 条件) | `openDeleteAnnouncement` + `confirmDeleteAnn` | `PUT /business/project` 清空 3 个 URL | 同上 | biz_project.{publish_url,invitation_url,support_letter_url}='\0' | ✅ |
|
||
|
||
**条件可见**:
|
||
- `开通` / `删除公告`: `v-if="row.isPublished === '1' || row.publishUrl"` (L132-133)
|
||
- `结题`: `v-if="row.isFinished !== '1' && row.isFinished !== 1"` (L128)
|
||
|
||
> **下拉菜单 vs 全部展开**: 原型 8 个按钮一字排开 (`.col-op width: 420px`), Vue 折叠成 "查看 / 编辑" + "更多" dropdown, 列宽 180px。屏幕利用率更好, 但每行多一次点击展开。
|
||
|
||
---
|
||
|
||
## 6. Java Entity ↔ 数据库表 一致性
|
||
|
||
### 6.1 biz_project DDL (实测 2026-08-20)
|
||
|
||
```sql
|
||
CREATE TABLE biz_project (
|
||
project_id bigint NOT NULL AUTO_INCREMENT COMMENT '项目ID',
|
||
project_no varchar(50) NOT NULL COMMENT '项目编号 ZH-2026-658',
|
||
project_name varchar(200) NOT NULL COMMENT '项目名称',
|
||
project_form varchar(20) DEFAULT NULL COMMENT '项目形式 线上/线下/线上+线下/其他',
|
||
total_sessions int DEFAULT '0' COMMENT '总场次/总期数',
|
||
done_sessions int DEFAULT '0' COMMENT '已执行',
|
||
todo_sessions int DEFAULT '0' COMMENT '未执行',
|
||
total_amount decimal(15,2) DEFAULT '0.00' COMMENT '总金额',
|
||
available_amount decimal(15,2) DEFAULT '0.00' COMMENT '可用金额',
|
||
paid_labor_amount decimal(15,2) DEFAULT '0.00' COMMENT '已支付劳务费',
|
||
paid_meeting_amount decimal(15,2) DEFAULT '0.00' COMMENT '已支付会务费',
|
||
manage_fee decimal(15,2) DEFAULT '0.00' COMMENT '管理费及税金',
|
||
is_finished char(1) DEFAULT '0' COMMENT '是否结题 0否 1是',
|
||
is_settled char(1) DEFAULT 'N' COMMENT '是否结算 N否 Y是',
|
||
manager_score decimal(3,1) DEFAULT NULL COMMENT '合规管理员评均分',
|
||
sponsor_admin_user_id bigint DEFAULT NULL COMMENT '赞助方负责人用户ID (sys_user.user_id, role_type=sponsor)',
|
||
sponsor_admin_user_name varchar(200) DEFAULT NULL COMMENT '赞助方负责人用户名(冗余)',
|
||
lead_user_id bigint DEFAULT NULL COMMENT '项目负责人 user_id (FK sys_user.user_id, role_type=manager)',
|
||
is_bid_project char(1) DEFAULT 'N' COMMENT '是否招标项目 Y/N (默认 N)',
|
||
start_time datetime DEFAULT NULL COMMENT '项目开始时间',
|
||
end_time datetime DEFAULT NULL COMMENT '项目结束时间',
|
||
submit_deadline_days int DEFAULT '0' COMMENT '提交材料截止天数',
|
||
support_contract_url varchar(500) DEFAULT NULL COMMENT '支持合同文件URL',
|
||
execute_contract_url varchar(500) DEFAULT NULL COMMENT '执行合同文件URL',
|
||
invitation_url varchar(500) DEFAULT NULL COMMENT '邀请函文件URL',
|
||
support_letter_url varchar(500) DEFAULT NULL COMMENT '支持函文件URL',
|
||
publish_url varchar(500) DEFAULT NULL COMMENT '已发布公告URL',
|
||
create_by varchar(64) DEFAULT '' COMMENT '创建者',
|
||
create_user_id bigint DEFAULT NULL COMMENT '创建人user_id (JOIN biz_person 取 name)',
|
||
create_time datetime DEFAULT NULL COMMENT '创建时间',
|
||
update_by varchar(64) DEFAULT '' COMMENT '更新者',
|
||
update_time datetime DEFAULT NULL COMMENT '更新时间',
|
||
sponsor_score decimal(3,1) DEFAULT NULL COMMENT '支持方评均分',
|
||
PRIMARY KEY (project_id),
|
||
UNIQUE KEY uk_project_no (project_no),
|
||
KEY idx_project_form (project_form),
|
||
KEY idx_is_finished (is_finished),
|
||
KEY idx_is_settled (is_settled),
|
||
KEY idx_sponsor_admin_user_id (sponsor_admin_user_id),
|
||
KEY idx_start_end_time (start_time,end_time)
|
||
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='项目表';
|
||
```
|
||
|
||
### 6.2 关联表 DDL
|
||
|
||
- `biz_org` (16 列, 主表): `PRIMARY(org_id) + uk_org_user_type(user_id,org_type) + idx_org_type_status + idx_org_type_name + idx_org_user_type_name`
|
||
- `biz_project_assign` (12 列, 关联表): `PRIMARY(assign_id) + uk_project_unit(project_id,execution_unit_id) + idx_assign_project + idx_assign_unit + idx_assign_exec_user + idx_assign_exec_user_project(be1c973)`
|
||
- `sys_user`: `PRIMARY(user_id) + idx_parent_user_id`
|
||
- `biz_person` (15 列, 创建人姓名来源): `PRIMARY(person_id) + uk_person_user_id(user_id,unit_type)`
|
||
- `biz_project_rating` (15 列, 4 维度评分明细表): `PRIMARY(rating_id) + uk_rating(project_id,rater_id,rater_role)`
|
||
|
||
### 6.3 Entity ↔ DB 字段对照
|
||
|
||
| 实体字段 | 中文列名 (DDL COMMENT) | 类型 | 表字段 | 类型 | 一致? |
|
||
|---|---|---|---|---|---|
|
||
| projectId | 项目ID | Long | project_id | bigint | ✅ |
|
||
| projectNo | 项目编号 | String | project_no | varchar(50) | ✅ |
|
||
| projectName | 项目名称 | String | project_name | varchar(200) | ✅ |
|
||
| projectForm | 项目形式 | String | project_form | varchar(20) | ✅ |
|
||
| totalSessions | 总场次/总期数 | Long | total_sessions | int | ✅ (Java Long 兼容 SQL int) |
|
||
| assignedSessions | (派生) | Long | — | — | ⚠️ **派生字段**,只在 executor 端 SQL 用 |
|
||
| assignedAmount | (派生) | BigDecimal | — | — | ⚠️ 同上 |
|
||
| doneSessions | 已执行 | Long | done_sessions | int | ✅ |
|
||
| todoSessions | 未执行 | Long | todo_sessions | int | ✅ |
|
||
| totalAmount | 总金额 | BigDecimal | total_amount | decimal(15,2) | ✅ |
|
||
| availableAmount | 可用金额 | BigDecimal | available_amount | decimal(15,2) | ✅ |
|
||
| paidLaborAmount | 已支付劳务费 | BigDecimal | paid_labor_amount | decimal(15,2) | ✅ |
|
||
| paidMeetingAmount | 已支付会务费 | BigDecimal | paid_meeting_amount | decimal(15,2) | ✅ |
|
||
| managerScore | 合规管理员评均分 | BigDecimal | manager_score | decimal(3,1) | ✅ |
|
||
| sponsorScore | 支持方评均分 | BigDecimal | sponsor_score | decimal(3,1) | ✅ |
|
||
| sponsorAdminUserName | 赞助方负责人用户名(冗余) | String | sponsor_admin_user_name | varchar(200) | ✅ |
|
||
| sponsorOrgName | (派生) | String | — | — | ⚠️ JOIN biz_org 派生 |
|
||
| sponsorAdminUserIds | (查询参数) | List\<Long\> | — | — | ✅ 多选 select 透传, 走 IN 精确查询 |
|
||
| execOrgNames | (派生) | String | — | — | ⚠️ GROUP_CONCAT 派生, 仅展示 |
|
||
| execAdminUserIds | (查询参数) | List\<Long\> | — | — | ✅ 多选 select 透传, 走 EXISTS + IN 精确查询 |
|
||
| leadUserName | (派生) | String | — | — | ⚠️ JOIN sys_user 派生 |
|
||
| leadUserId | 项目负责人 | Long | lead_user_id | bigint | ✅ |
|
||
| sponsorAdminUserId | 赞助方负责人 | Long | sponsor_admin_user_id | bigint | ✅ |
|
||
| isFinished | 是否结题 | String | is_finished | char(1) | ✅ |
|
||
| isSettled | 是否结算 | String | is_settled | char(1) | ✅ |
|
||
| isBidProject | 是否招标项目 | String | is_bid_project | char(1) | ✅ |
|
||
| manageFee | 管理费及税金 | BigDecimal | manage_fee | decimal(15,2) | ✅ |
|
||
| startTime | 项目开始时间 | Date | start_time | datetime | ✅ |
|
||
| endTime | 项目结束时间 | Date | end_time | datetime | ✅ |
|
||
| submitDeadlineDays | 提交材料截止天数 | Integer | submit_deadline_days | int | ✅ |
|
||
| supportContractUrl | 支持合同文件URL | String | support_contract_url | varchar(500) | ✅ |
|
||
| executeContractUrl | 执行合同文件URL | String | execute_contract_url | varchar(500) | ✅ |
|
||
| invitationUrl | 邀请函文件URL | String | invitation_url | varchar(500) | ✅ |
|
||
| supportLetterUrl | 支持函文件URL | String | support_letter_url | varchar(500) | ✅ |
|
||
| publishUrl | 已发布公告URL | String | publish_url | varchar(500) | ✅ |
|
||
| createBy / updateBy | 创建者/更新者 | String | create_by / update_by | varchar(64) | ✅ |
|
||
| createUserId | 创建人 user_id | Long | create_user_id | bigint | ✅ |
|
||
| createUserName | (派生) | String | — | — | ⚠️ JOIN biz_person.name 派生 |
|
||
| createTime / updateTime | 创建时间/更新时间 | Date | create_time / update_time | datetime | ✅ |
|
||
| sponsorRating, sponsorQ1-4, sponsorRemark | (DDL 中不存在) | | ❌ **DDL 中不存在** | — | 🐛 残留支持方评分字段,未使用 |
|
||
|
||
### 修订标记
|
||
|
||
> 较前一版已删除: `execOrgName` (已废弃 LIKE 查询, 2b69ef1 清理)、`noticeUrl/scheduleUrl/isPublished/publishTime/announcementType` (经核实属业务正确, 25cec1e 删除相关误标 doc)
|
||
> 仍保留: `sponsorRating/sponsorQ1-4/sponsorRemark` 6 个字段 — 实体有但 DDL 无, mapper 不读, 纯死字段 (独立 issue, 未修)
|
||
|
||
---
|
||
|
||
## 7. 索引检查
|
||
|
||
### 7.1 biz_project (主表)
|
||
|
||
| Key | 列 | 用途覆盖 |
|
||
|---|---|---|
|
||
| PRIMARY | `project_id` | ✅ SELECT/WHERE/ORDER BY/JOIN |
|
||
| uk_project_no | `project_no` | ✅ 唯一约束 + LIKE 模糊 + uk 索引本身可走 |
|
||
| idx_project_form | `project_form` | ✅ line 166 `project_form = ?` |
|
||
| idx_is_finished | `is_finished` | ✅ line 185 `is_finished = ?` |
|
||
| idx_is_settled | `is_settled` | ✅ line 186 `is_settled = ?` |
|
||
| idx_sponsor_admin_user_id | `sponsor_admin_user_id` | ✅ line 167-172 `IN <foreach>` (range + Using index) |
|
||
| idx_start_end_time | `(start_time, end_time)` | ✅ line 154-155 `start_time >= ? AND end_time <= ?` (range) |
|
||
|
||
### 7.2 biz_org (机构表)
|
||
|
||
| Key | 列 | 用途覆盖 |
|
||
|---|---|---|
|
||
| PRIMARY | `org_id` | ✅ JOIN + ORDER BY |
|
||
| uk_org_user_type | `(user_id, org_type)` | ✅ LEFT JOIN `o.user_id = p.sponsor_admin_user_id AND o.org_type = 'sponsor'` (eq_ref) |
|
||
| idx_org_type_status | `(org_type, status)` | ✅ options 查询命中 (小数据量下优化器优先选小索引) |
|
||
| idx_org_type_name | `(org_type, org_name)` | ✅ options `org_name LIKE` 兜底 |
|
||
| idx_org_user_type_name | `(user_id, org_type, org_name)` | ✅ 注释说走这个, EXPLAIN 实际走 idx_org_type_status (见 §9.5) |
|
||
|
||
### 7.3 biz_project_assign (项目-执行方关联表)
|
||
|
||
| Key | 列 | 用途覆盖 |
|
||
|---|---|---|
|
||
| PRIMARY | `assign_id` | ✅ |
|
||
| uk_project_unit | `(project_id, execution_unit_id)` | ✅ 唯一约束 + GROUP_CONCAT 子查询 `bpa.project_id = p.project_id` (ref) |
|
||
| idx_assign_project | `project_id` | ✅ |
|
||
| idx_assign_unit | `execution_unit_id` | ✅ |
|
||
| idx_assign_exec_user | `exec_user_id` | ✅ 单列回表 |
|
||
| **idx_assign_exec_user_project** (2026-08-20) | `(exec_user_id, project_id)` | ✅ EXISTS+IN 复合 covering (be1c973,见 §9.5) |
|
||
|
||
### 7.4 sys_user / biz_person / biz_project_rating
|
||
|
||
- `sys_user`: PRIMARY(user_id) + idx_parent_user_id — 用于 LEFT JOIN eq_ref
|
||
- `biz_person`: PRIMARY(person_id) + uk_person_user_id(user_id,unit_type) — 用于 LEFT JOIN eq_ref
|
||
- `biz_project_rating`: PRIMARY(rating_id) + uk_rating(project_id,rater_id,rater_role) — 评分明细去重
|
||
|
||
---
|
||
|
||
## 8. 与原型差异 (实现偏离)
|
||
|
||
### 8.1 实现新增 (原型没有)
|
||
|
||
| 项 | 说明 |
|
||
|---|---|
|
||
| 服务机构列 | 原型列叫"服务机构", 但 Vue 实际展示的是 `biz_project_assign` 多服务方 GROUP_CONCAT 字符串, 比原型 1 个单元格更复杂 |
|
||
| 项目分配 dialog | 原型 `.col-op` 一字排开"分配"按钮, Vue 提到 toolbar 按钮 + 弹 dialog (支持单选/批量 + 总场次实时校验) |
|
||
| 批量开通截止时间 picker | 原型批量开通是直接按钮, Vue 加截止时间 datetime picker (一致于批量评分设计) |
|
||
| 评价得分过滤后端生效 | 原型前端有筛选框, Vue 加 selectList 后端过滤 (88df386) |
|
||
|
||
### 8.2 原型有但实现缺失
|
||
|
||
| 项 | 原型位置 | 实现 | 严重度 |
|
||
|---|---|---|---|
|
||
| 顶部"导出"按钮 | `project-manage.html:135` | `Projects.vue:361` 仅 `ElMessage.info('开发中')` | **P2** stub |
|
||
| 顶部"项目评价导出"按钮 | `project-manage.html:136` | `Projects.vue:362` 仅 `ElMessage.info('开发中')` | **P2** stub |
|
||
| 顶部"已报名专家信息导出"按钮 | `project-manage.html:137` | 已挪到行操作 dropdown (`exportExperts`), ✅ 实装 | — |
|
||
|
||
### 8.3 文字 / 标签差异
|
||
|
||
| 原型 | 实现 | 差异 |
|
||
|---|---|---|
|
||
| 分配 | 项目分配 | Vue 改名加 "项目" 前缀, 更明确 (操作对象是项目, 不是 sponsor) |
|
||
| 评分单元格 | 仅数字显示 | Vue 数字显示 + 链接点击打开单条评分 (更省操作) |
|
||
| 状态 tag | `.status-pending` / `.status-done` / `.status-activated` | Vue 用 el-tag success/warning, 等价 |
|
||
|
||
### 8.4 总结
|
||
|
||
整体方向:**扩展** > **缺失**。多选筛选、项目分配 dialog、批量开通 picker 都是增强, 仅 2 个导出按钮 stub 待实装。
|
||
|
||
---
|
||
|
||
## 9. 表字段冗余 / 设计问题
|
||
|
||
### 9.1 派生 vs 持久化字段
|
||
|
||
- `sponsorOrgName` / `execOrgNames` / `leadUserName` / `createUserName` / `assignedSessions` / `assignedAmount` 全部是**派生字段**,不入库,只在 SQL JOIN 时填充,前端可读但不应让前端 PUT 回去
|
||
- mapper 的 `updateByPrimaryKey` 已经用 `<if test="...">` 防御,不会回写 — OK
|
||
|
||
### 9.2 唯一约束语义检查
|
||
|
||
- ✅ biz_project: uk_project_no — 项目编号唯一,合理
|
||
- ✅ biz_project_rating: uk_rating (project_id, rater_id, rater_role) — 一人评一次,合理
|
||
- ✅ biz_project_assign: uk_project_unit (project_id, execution_unit_id) — 同一项目同一执行方只能分配一次
|
||
|
||
### 9.3 业务闭环检查
|
||
|
||
- 项目分配 dialog: `assignForm.execRows` 行内 el-select 的 `value=MAIN user_id`,后端 `BizProjectAssignServiceImpl` 应该从 exec_user_id 反查 biz_org 写 execution_unit_id (mapper 注释有写,需确认 service 实现)
|
||
- 项目分配 → 评分 → 公示 → 通知: 整条业务链已铺,但 manager_score 聚合分写的是 manager 评分的均值,逻辑合理
|
||
|
||
### 9.5 索引分析与 EXPLAIN (2026-08-20)
|
||
|
||
数据规模: biz_project 12 行 / biz_org 33 行 / biz_project_assign 12 行 / biz_project_rating 22 行 / sys_user 47 行 / biz_person 29 行 (测试数据)
|
||
|
||
| # | 查询 | 命中索引 | 备注 |
|
||
|---|---|---|---|
|
||
| Q1 | `selectSponsorOrgOptions` | `idx_org_type_status` (2 列) | 优化器在小数据量倾向小索引, `org_name LIKE` 走 Using where 二次过滤, `ORDER BY org_id DESC` 走 filesort |
|
||
| Q2 | `selectExecutorOrgOptions` | `idx_org_type_status` + `PRIMARY` sys_user | 同上, `parent_user_id IS NULL` 走 Using where |
|
||
| Q3 | `selectList` 主项目 + `sponsor_admin_user_id IN` | `idx_sponsor_admin_user_id` range + Using index | ✅ 完美 covering, 无需回表 |
|
||
| Q4 | `selectList` 主项目 + `EXISTS(exec_user_id IN)` | `idx_assign_exec_user_project` + LooseScan | ✅ 完美 covering (新加索引, 见下) |
|
||
| Q5 | `selectList` 主项目 + sponsor IN + 多 LEFT JOIN + GROUP_CONCAT 子查询 | `idx_project_form` Backward scan + 3 个 LEFT JOIN 全 eq_ref/Using index + `uk_project_unit` GROUP_CONCAT | ✅ 全部走索引 |
|
||
| Q6 | `selectList` 主项目 + 单独 exec EXISTS (无其他过滤) | `idx_assign_exec_user_project` range + Start temporary | ⚠️ 优化器会 material 子查询, 数据量小可接受 |
|
||
| Q7 | selectList 同时 sponsor IN + exec EXISTS | `idx_project_form` 主表 + `idx_assign_exec_user_project` FirstMatch 子查询 | ✅ 组合最优, semi-join |
|
||
| Q8 | selectList 仅 projectForm + isFinished | `index_merge(intersect(idx_project_form, idx_is_finished))` | ✅ MySQL 8 自动交集 |
|
||
|
||
**已加索引** (2026-08-20):
|
||
|
||
```sql
|
||
ALTER TABLE biz_project_assign ADD INDEX idx_assign_exec_user_project (exec_user_id, project_id);
|
||
```
|
||
|
||
理由: Q4/Q6/Q7 (服务机机构 EXISTS+IN) 原命中 `idx_assign_exec_user(exec_user_id)` 单列索引,
|
||
需回表读 project_id; 新加复合索引直接 covering, EXPLAIN 显示 `Using index` + `LooseScan`/`range`, 无需回表。
|
||
|
||
**评估后未加的索引** (记录备查):
|
||
|
||
- `biz_org (org_type, org_id, org_name)` — 用于消除 options 查询的 filesort。
|
||
评估: 强制 hint 后确实变 `Backward index scan` 无 filesort,
|
||
但优化器在 33 行数据下仍选 `idx_org_type_status` (2 列更小, 估算成本更低)。
|
||
决策: 数据量小不需要, **DROP**, 等生产数据增长到 1k+ 行再 EVAL。
|
||
|
||
**过时注释修正** (2026-08-20):
|
||
|
||
- `BizOrgMapper.xml` 旧注释写 "走 `idx_org_user_type_name` 索引", EXPLAIN 实际命中 `idx_org_type_status`, 已修正为实测索引 + Using where 描述
|
||
|
||
---
|
||
|
||
## 10. 待修复列表 (按优先级)
|
||
|
||
| # | 问题 | 文件:行 | 修复建议 | 严重度 |
|
||
|---|---|---|---|---|
|
||
| 1 | ~~`managerScore` 筛选前端有,后端 selectList 不读~~ | ~~`BizProjectMapper.xml:141-183` (selectList)~~ | ✅ 已修 (88df386,见 §修复记录) | — |
|
||
| 2 | ~~biz_project 缺索引 `sponsor_admin_user_id`~~ | ~~`DB`~~ | ✅ 已修 (见 §修复记录) | — |
|
||
| 3 | ~~biz_project 缺索引 `start_time`, `end_time` (范围查询)~~ | ~~`DB`~~ | ✅ 已修 (见 §修复记录) | — |
|
||
| 4 | ~~biz_project_assign 缺 UNIQUE (project_id, execution_unit_id)~~ | ~~`DB`~~ | ✅ 已修 (见 §修复记录) | — |
|
||
| 5 | ~~生产代码残留 console.info/error (4 处)~~ | ~~`Projects.vue:299, 302, 306, 875`~~ | ✅ 已修 (见 §修复记录) | — |
|
||
| 6 | ~~日期选择器 `value-format="YYYY-MM-DD HH:mm:ss"` 跟 `type="date"` 不匹配~~ | ~~`Projects.vue:17, 19`~~ | ✅ 已修 (见 §修复记录) | — |
|
||
| 7 | ~~`batchScoreRows` 死代码~~ | ~~`Projects.vue:501`~~ | ✅ 已修 (见 §修复记录) | — |
|
||
| 8 | 下拉菜单 `divided` 冗余 (activate/delAnn 双分隔线) | `Projects.vue:132-133` | 删 `activate` 的 `divided` | **P1** |
|
||
| 9 | 评分默认值不一致 | `Projects.vue:512 vs 524` | 统一为 0 或 5 (批量=5, 单行=0) | **P2** |
|
||
| 10 | `isSettled` 筛选但表格无列显示 | `Projects.vue:58-62` vs §4.2 表 | 加列 或 删筛选 | **P2** |
|
||
| 11 | 重复 score table HTML (批量评分 + 单条评分) | `Projects.vue:217-229, 274-285` | 抽公共组件 | **P2** |
|
||
| 12 | 2 个导出按钮是 stub (`exportProjects` / `exportEval`) | `Projects.vue:361-362` | 实装 或 删按钮 (原型有) | **P2** |
|
||
| 13 | `is_finished` 和 `is_settled` 状态值约定不一致 (0/1 vs Y/N) | `BizProject.java` + mapper + 前端 select | 统一约定 (全 0/1 或全 Y/N) | **P3** |
|
||
| 14 | selectList / selectExecutorList 有 4 个相关子查询 | `BizProjectMapper.xml:50-53, 118-121` | 数据小可接受, 记录为 P3 待优化 | **P3** |
|
||
| 15 | `.score-row` CSS 死样式 | `Projects.vue:965-967` | 删 | **P3** |
|
||
| 16 | `sponsorRating/sponsorQ1-4/sponsorRemark` 6 个字段实体有 DDL 无 | `BizProject.java:131-136` + 字段映射 | 删实体字段 或 补 DDL (业务走 sponsor_rating 表) | **P3** |
|
||
|
||
---
|
||
|
||
## 11. 引用清单
|
||
|
||
### 前端
|
||
- `ry-vue3/src/layout/AdminLayout.vue:96` — manager 菜单定义
|
||
- `ry-vue3/src/router/index.js:60` — 路由 `manager-projects`
|
||
- `ry-vue3/src/views/manager/Projects.vue` — 本审查对象 (920 行)
|
||
- `ry-vue3/src/views/manager/ProjectsNew.vue` — 新建/编辑项目独立页
|
||
- `ry-vue3/src/api/business/project.js` — `bizList/bizAdd/bizUpdate/bizDelete`
|
||
- `ry-vue3/src/api/system.js` — `listSponsorOrgs/listExecutorOrgs`
|
||
- `ry-vue3/src/utils/request.js` — axios 拦截器 (TableDataInfo 归一化)
|
||
|
||
### 后端
|
||
- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizProjectController.java:33` — `@RequestMapping("/business/project")` (16 endpoints)
|
||
- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/BizProjectService.java` — 接口
|
||
- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizProjectServiceImpl.java` — 实现
|
||
- `ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java` — 实体 (含 8 个 `@Excel` 导出字段)
|
||
- `ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectMapper.xml` — 4 个查询: selectByPrimaryKey, selectSponsorList, selectExecutorList, selectList (主)
|
||
- `ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectAssignMapper.xml` — 分配子查询
|
||
- `ry-api/ruoyi-business/src/main/resources/mapper/business/BizOrgMapper.xml` — sponsor/executor options
|
||
- `ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectRatingMapper.xml` — 4 维度评分
|
||
|
||
### DB
|
||
- `biz_project` (主表, 36 列)
|
||
- `biz_org` (机构表, 16 列, sponsor/executor 共用)
|
||
- `biz_project_assign` (项目-执行方关联表, 12 列)
|
||
- `sys_user` (用户表, 关联 sponsor_admin_user_id/lead_user_id/create_user_id)
|
||
- `biz_person` (创建人姓名来源, LEFT JOIN 取 `name`)
|
||
- `biz_project_rating` (评分明细表, 4 维度 × project_id × rater)
|
||
|
||
### 配置
|
||
- `ry-api/ruoyi-admin/src/main/resources/application.yml` — Spring 主配置
|
||
- `ry-api/ruoyi-admin/src/main/resources/application-druid.yml` — DB 配置 (127.0.0.1:3306/guoju0808/root)
|
||
|
||
### 原型
|
||
- `proto/html/manager.html:206` — 项目管理菜单
|
||
- `proto/html/components/project-manage.html` — 项目管理主页 (15 行 × 19 列 + 8 行内按钮)
|
||
- `proto/html/components/project-assign.html` — 项目分配 dialog 原型 |