修复记录
| 日期 |
章节 |
修复内容 |
| 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) |
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) |
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):
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)
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):
理由: 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 原型