manager/projects — 端到端技术审查
审查时间: 2026-08-18
审查范围: ry-vue3/src/views/manager/Projects.vue (主列表) + 后端 BizProject 全链路 + DB 实测
对比原型: proto/html/components/project-manage.html
修复记录
| 日期 |
章节 |
修复内容 |
| 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。 |
0. 4 跳定位 (铁律: 不按名字猜)
| 跳 |
文件:行 |
结果 |
| ① 角色菜单 |
ry-vue3/src/layout/AdminLayout.vue:98 |
path: '/manager/projects', title: '项目管理' ✓ |
| ② 路由 |
ry-vue3/src/router/index.js:65 |
component: () => import('@/views/manager/Projects.vue') ✓ |
| ③ 组件 |
ry-vue3/src/views/manager/Projects.vue |
881 行,本审查对象 |
| ④ 原型 |
proto/html/manager.html:206 |
<a data-page="components/project-manage.html"> ✓ |
1. 主要功能 + 可见性
1.1 主要功能
| 功能 |
前端入口 |
后端接口 |
涉及表 |
| 项目列表(分页+筛选) |
manager/Projects.vue 表头筛选 + load() |
GET /business/project/list → BizProjectController.list → BizProjectService.selectList |
biz_project |
| 单条/批量结题 |
onToggleStatus (批量走 openBatch('close')) |
PUT /business/project → bizProjectService.updateByPrimaryKey (只改 is_finished='1') |
biz_project.is_finished |
| 单条/批量开通 |
openActivate / openBatch('activate') |
PUT /business/project (改 end_time) |
biz_project.end_time |
| 单条/批量执行单位评分 |
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 |
| 删除公告(单条) |
confirmDeleteAnn |
PUT /business/project (清空 publishUrl/invitationUrl/supportLetterUrl/noticeUrl/scheduleUrl + is_published='0') |
biz_project |
| 导出报名专家 |
exportExperts |
POST /business/executionIntent/export?projectNo=... |
biz_execution_intent |
| 新建/编辑/查看 |
跳独立页 /manager/projects/new、/edit/:id、/detail/:id |
同 |
biz_project |
1.2 可见性(三层隔离)
| 层 |
来源 |
校验字段 |
| 前端菜单 |
AdminLayout.vue:98 |
只在 manager 角色菜单出现 |
| 路由守卫 |
router/index.js:61 meta: { role: 'manager' } |
路由 meta.role |
| 后端 |
BizProjectController.list → selectList 无 user_id 过滤(manager 可看所有项目) |
无 |
⚠️ manager 是全局视图角色(合规管理员), 看所有项目不过滤 — 设计正确
其他角色访问:
- sponsor →
GET /business/project/sponsorList (按 sponsor_admin_user_id = uid 过滤)
- executor →
GET /business/project/executorList (按 biz_project_assign 关联过滤)
- 这两个不会进 manager/Projects.vue,但共用 controller
2. 筛选项 (filter-form)
| UI label |
控件 |
v-model |
后端 SQL |
命中表.字段 |
选项来源 |
原型对照 |
| 支持单位 |
el-input |
q.sponsorOrgName |
selectList line 159-166 EXISTS (biz_org WHERE o.user_id=sponsor_admin_user_id AND o.org_type='sponsor' AND o.org_name LIKE ?) |
biz_org.org_name |
— |
✅ 原型有 |
| 服务机构 |
el-input |
q.execOrgName |
line 167-178 EXISTS (biz_project_assign JOIN biz_org + sys_user WHERE org_name/user_name/nick_name LIKE ?) |
biz_org.org_name + sys_user.user_name/nick_name |
— |
✅ 原型有 |
| 项目编号 |
el-input |
q.projectNo |
line 156 project_no LIKE concat('%',?, '%') |
biz_project.project_no |
— |
✅ 原型有 |
| 项目名称 |
el-input |
q.projectName |
line 157 project_name LIKE concat('%',?, '%') |
biz_project.project_name |
— |
✅ 原型有 |
| 项目形式 |
el-select |
q.projectForm |
line 158 project_form = ? |
biz_project.project_form |
写死: 线上/线下/线上+线下/其他 |
✅ 原型有 |
| 项目时间(起/止) |
el-date-picker type="date" + value-format="YYYY-MM-DD HH:mm:ss" |
q.startTime/endTime |
line 93-94 start_time >= ? AND end_time <= ? (sponsorList); line 134-135 同 |
biz_project.start_time/end_time |
— |
✅ 原型有 |
| 是否结题 |
el-select |
q.isFinished |
line 179 is_finished = ? |
biz_project.is_finished |
写死: 已结题→'1' / 未结题→'0' |
✅ 原型有 |
| 是否结算 |
el-select |
q.isSettled |
line 180 is_settled = ? |
biz_project.is_settled |
写死: 已结算→'Y' / 未结算→'N' |
⚠️ 原型无 (实现新增) |
| 评价得分 |
el-input |
q.managerScore |
❌ 未生效 — 后端 selectList 无 managerScore 过滤 |
— |
— |
✅ 原型有 |
⚠️ 重要 bug:managerScore 筛选项前端展示 + 前端 reset 也保留字段,但后端 selectList 完全没读这个参数,筛选无效
另见 selectSponsorList 和 selectExecutorList 也都没有 managerScore 过滤
3. 工具栏按钮 (toolbar)
| 按钮 |
功能 |
接口 |
涉及表 |
原型对照 |
| 新建项目 |
跳转独立页 |
router.push('/manager/projects/new') → ProjectsNew.vue |
biz_project |
✅ 原型有 |
| 项目分配 |
弹 dialog (单条或批量,按勾选数) |
PUT /business/project (改 sponsor_admin_user_id) + POST /business/project/{id}/assigns |
biz_project + biz_project_assign |
✅ 原型有 |
| 批量评分 |
弹 dialog,共用 4 维度评分 |
PUT /business/project (managerScore) + POST /business/project/rate × N |
biz_project + biz_project_rating |
✅ 原型有 |
| 批量结题 |
弹 dialog,确认后批量改 is_finished='1' |
PUT /business/project × N |
biz_project.is_finished |
✅ 原型有 |
| 批量开通 |
弹 dialog,选截止时间,批量改 end_time |
PUT /business/project × N |
biz_project.end_time |
✅ 原型有 |
| 导出 (3 个按钮) |
项目/评价/已报名专家导出 |
exportProjects / exportEval / exportExperts |
— |
⚠️ 前两个是 stub,仅 exportExperts 实装 |
⚠️ stub 函数:exportProjects (line 347) / exportEval (line 348) 仅弹 ElMessage.info('开发中')
⚠️ 遗漏:原型有 3 个导出按钮 (导出/项目评价导出/已报名专家信息导出), 实装只有 1 个
⚠️ 额外按钮 (实现新增): 删除公告(原型的 #7), 不是"扩展"而是"实现"
4. 表格 (el-table)
4.1 数据来源 SQL(manager 通用列表 selectList)
4.2 列映射
| 列 |
row.* |
DB 表.字段 |
原型对照 |
| 复选框 |
— |
— |
✅ |
| 项目编号 |
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 (fmtMoney) |
biz_project.total_amount |
✅ |
| 可用金额 |
row.availableAmount (fmtMoney) |
biz_project.available_amount |
✅ |
| 已支付劳务费 |
row.paidLaborAmount (fmtMoney) |
biz_project.paid_labor_amount |
✅ |
| 已支付会务费 |
row.paidMeetingAmount (fmtMoney) |
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 (fmtDate) |
biz_project.start_time |
✅ |
| 项目结束时间 |
row.endTime (fmtDate) |
biz_project.end_time |
✅ |
| 操作 |
6 个 link + dropdown |
— |
✅ 8 项操作齐全(建会/分配/结题/查看/编辑/开通/删除公告/执行单位评分) |
5. 行内操作按钮
| 按钮 |
函数 |
接口 |
后端动作 |
涉及表.字段 |
原型对照 |
| 查看 |
viewDetail |
router.push('/manager/projects/detail/${row.projectId}') |
(独立页) |
— |
✅ |
| 编辑 |
doEdit |
router.push('/manager/projects/edit/${row.projectId}') |
(独立页 ProjectsNew.vue) |
biz_project (全字段) |
✅ |
| 建会 |
doCreateMeeting |
router.push('/manager/meetings/new?projectId=${row.projectId}') |
(独立页 MeetingNew.vue) |
— |
✅ |
| 项目分配 |
doAssign |
同 openAssign |
— |
— |
✅ |
| 结题 (单条) |
openClose + confirmClose |
PUT /business/project {projectId, isFinished: '1'} |
bizProjectService.updateByPrimaryKey |
biz_project.is_finished='1' |
✅ |
| 开通 (单条) |
openActivate + confirmActivate |
PUT /business/project {projectId, isFinished: '0'} ⚠️ 字段写错了 |
同上 |
biz_project.is_finished='0' |
✅ 但实现不对 |
| 删除公告 |
openDeleteAnnouncement + confirmDeleteAnn |
PUT /business/project 清空 5 个 URL + isPublished: '0' |
同上 |
biz_project.{publish_url,invitation_url,support_letter_url,notice_url,schedule_url}='\0' + is_published='0' |
✅ |
| 执行单位评分 (单条) |
openSingleScore + confirmSingleScore |
PUT /business/project {managerScore} + POST /business/project/rate |
bizProjectRatingService.upsertRating |
biz_project.manager_score + biz_project_rating (新行或更新) |
✅ |
| 导出报名专家 |
exportExperts |
POST /business/executionIntent/export?projectNo=... |
(单独执行方意图表) |
biz_execution_intent |
✅ |
| 批量评分 |
openBatch('score') + confirmBatch |
PUT + POST /rate × N |
同单条,按勾选遍历 |
biz_project + biz_project_rating |
✅ |
| 批量结题 |
openBatch('close') + confirmBatch |
PUT /business/project {isFinished: '1'} × N |
同单条 |
biz_project.is_finished |
✅ |
| 批量开通 |
openBatch('activate') + confirmBatch |
PUT /business/project {endTime: activateDeadline} × N |
同单条 |
biz_project.end_time |
✅ |
⚠️ 🐛 Bug: 单条"开通"(confirmActivate line 397-401) 改了 is_finished='0' 而不是 end_time — 应该跟批量开通一致改 end_time。改 is_finished='0' 等同于"取消结题",跟"开通"语义不符
🟢 正确性: 评分走 biz_project_rating 中间表 + upsertRating SQL 是 (project_id, rater_id, rater_role) 复合唯一,符合"一人评一次"的语义
6. Java Entity ↔ 数据库表 一致性
6.1 biz_project 主表 DDL (实测)
CREATE TABLE `biz_project` (
`project_id` bigint NOT NULL AUTO_INCREMENT,
`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',
`done_sessions` int DEFAULT '0',
`todo_sessions` int DEFAULT '0',
`total_amount` decimal(15,2) DEFAULT '0.00',
`available_amount` decimal(15,2) DEFAULT '0.00',
`paid_labor_amount` decimal(15,2) DEFAULT '0.00',
`paid_meeting_amount` decimal(15,2) DEFAULT '0.00',
`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,
`sponsor_admin_user_id` bigint DEFAULT NULL,
`sponsor_admin_user_name` varchar(200) DEFAULT NULL,
`lead_user_id` bigint DEFAULT NULL,
`is_bid_project` char(1) DEFAULT 'N' COMMENT '是否招标项目 Y/N',
`start_time` datetime DEFAULT NULL,
`end_time` datetime DEFAULT NULL,
`submit_deadline_days` int DEFAULT '0',
`support_contract_url` varchar(500) DEFAULT NULL,
`execute_contract_url` varchar(500) DEFAULT NULL,
`invitation_url` varchar(500) DEFAULT NULL,
`support_letter_url` varchar(500) DEFAULT NULL,
`publish_url` varchar(500) DEFAULT NULL,
`create_by` varchar(64) DEFAULT '',
`create_time` datetime DEFAULT NULL,
`update_by` varchar(64) DEFAULT '',
`update_time` datetime DEFAULT NULL,
`sponsor_score` decimal(3,1) DEFAULT NULL,
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`)
) ENGINE=InnoDB
6.2 biz_project_assign / biz_project_rating DDL (实测)
biz_project_assign 索引:PRIMARY(assign_id) + idx_assign_project / idx_assign_unit / idx_assign_exec_user
biz_project_rating 索引:
- PRIMARY (rating_id)
- UNIQUE
uk_rating (project_id, rater_id, rater_role) ✅ 一人评一次
- idx_pr_project, idx_pr_rater, idx_pr_rating_time
6.3 Entity ↔ 表字段对照 (BizProject.java)
| 实体字段 |
类型 |
表字段 |
类型 |
一致? |
| projectId |
Long |
project_id |
bigint |
✅ 已修 (2026-08-18, 见 §修复记录) |
| projectNo |
String |
project_no |
varchar(50) |
✅ |
| projectName |
String |
project_name |
varchar(200) |
✅ |
| totalSessions |
Long |
total_sessions |
int |
✅ |
| 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) |
✅ |
| projectForm |
String |
project_form |
varchar(20) |
✅ |
| isFinished |
String |
is_finished |
char(1) |
✅ |
| isSettled |
String |
is_settled |
char(1) |
✅ |
| sponsorAdminUserId |
Long |
sponsor_admin_user_id |
bigint |
✅ |
| leadUserId |
Long |
lead_user_id |
bigint |
✅ |
| isBidProject |
String |
is_bid_project |
char(1) |
✅ |
| createBy/createTime |
|
(BaseEntity) |
|
✅ |
| manageFee |
BigDecimal |
manage_fee |
decimal(15,2) |
✅ |
| startTime |
Date |
start_time |
datetime |
✅ 已修 (2026-08-18, 加 @JsonFormat, 见 §修复记录) |
| endTime |
Date |
end_time |
datetime |
✅ 已修 (2026-08-18, 同上) |
| submitDeadlineDays |
Integer |
submit_deadline_days |
int |
✅ |
| supportContractUrl / executeContractUrl / invitationUrl / supportLetterUrl / publishUrl |
String |
(同名 varchar(500)) |
|
✅ |
| assignedSessions |
Long |
❌ 无对应字段 |
— |
⚠️ 派生字段,只在 executor 端 SQL 用 |
| assignedAmount |
BigDecimal |
❌ 无对应字段 |
— |
⚠️ 同上 |
| sponsorOrgName |
String |
❌ 无对应字段 |
— |
⚠️ JOIN biz_org 派生 |
| execOrgName |
String |
❌ 无对应字段 |
— |
⚠️ 查询参数,不入库 |
| execOrgNames |
String |
❌ 无对应字段 |
— |
⚠️ GROUP_CONCAT 派生 |
| leadUserName |
String |
❌ 无对应字段 |
— |
⚠️ JOIN sys_user 派生 |
| noticeUrl |
String |
❌ DDL 中不存在 |
— |
🐛 实体有但表无 |
| scheduleUrl |
String |
❌ DDL 中不存在 |
— |
🐛 同上 |
| isPublished |
String |
❌ DDL 中不存在 |
— |
🐛 同上,被 confirmDeleteAnn 当字段更新 |
| publishTime |
Date |
❌ DDL 中不存在 |
— |
🐛 同上 |
| announcementType |
String |
❌ DDL 中不存在 |
— |
🐛 同上 |
| sponsorRating, sponsorQ1-4, sponsorRemark |
|
❌ DDL 中不存在 |
— |
🐛 残留支持方评分字段,未使用 |
修订标记
修订:is_published / notice_url / schedule_url / publish_time / announcement_type 这些字段只在 BizProject.java 实体里有,DDL 实测不存在。
后果:confirmDeleteAnn 调 bizUpdate('project', { publishUrl, invitationUrl, supportLetterUrl, noticeUrl, scheduleUrl, isPublished: '0' }),后端 mapper 的 updateByPrimaryKey 的 <if test="..."> 会因为没传值而不更新 notice_url/schedule_url/is_published,只清空前 3 个。功能部分失效(第 5 章结论需修正)。
7. 索引检查
7.1 biz_project 索引
| Key |
列 |
用途覆盖 |
| PRIMARY |
project_id |
✅ ORDER BY, 主键 |
| uk_project_no |
project_no (UNIQUE) |
✅ 编号精确/模糊查询 |
| idx_project_form |
project_form |
✅ 项目形式过滤 |
| idx_is_finished |
is_finished |
✅ 是否结题过滤 |
| idx_is_settled |
is_settled |
✅ 是否结算过滤 |
| idx_sponsor_admin_user_id |
sponsor_admin_user_id |
✅ selectSponsorList WHERE sponsor_admin_user_id = ? |
| idx_start_end_time |
(start_time, end_time) |
✅ 范围查询 start_time >= ? AND end_time <= ?,EXPLAIN 验证 type=range |
| ❌ 缺 |
lead_user_id |
⚠️ 主表没用,但 JOIN sys_user 用 — 可接受,小数据量 |
7.2 biz_project_assign 索引
| Key |
列 |
用途覆盖 |
| PRIMARY |
assign_id |
✅ |
| idx_assign_project |
project_id |
✅ SELECT WHERE project_id = ? |
| idx_assign_unit |
execution_unit_id |
✅ |
| idx_assign_exec_user |
exec_user_id |
✅ |
| uk_project_unit |
(project_id, execution_unit_id) UNIQUE |
✅ 防重复分配 (实测 11 行 0 重复, 安全) |
7.3 biz_project_rating 索引
| Key |
列 |
用途覆盖 |
| PRIMARY |
rating_id |
✅ |
| uk_rating |
(project_id, rater_id, rater_role) UNIQUE |
✅ upsertRating 用 ON DUPLICATE KEY,完美 |
| idx_pr_project |
project_id |
✅ SELECT WHERE project_id = ? |
| idx_pr_rater |
rater_id |
✅ |
| idx_pr_rating_time |
rating_time |
✅ 按时间排序 |
结论:rating 表索引设计优秀(uk_rating 三元组唯一约束)。project 表索引已补齐 (7 个, 涵盖所有 WHERE 列)。assign 表唯一约束到位 (uk_project_unit 防重复分配)。
8. 与原型差异
8.1 实现新增 (原型没有)
| 项 |
位置 |
说明 |
| 项目分配 dialog |
assignOpen (line 119) |
原型 project-assign.html 是独立页,实现合并到本 dialog,功能更全 (支持方下拉 + 总场次/金额校验) |
| 评分明细反显 |
openSingleScore (line 424-449) |
原型无,实装调用 /business/project/ratings 反显当前管理员历史评分 |
| 评分默认值 |
batchScoreForm default=5, singleScoreForm default=0 |
原型无 |
| 批量开通截止时间字段 |
activateDeadline |
原型批量开通仅"确认"无时间字段,实装加了截止时间 |
| isSettled 筛选 |
line 26-30 |
原型无"是否结算"筛选项 |
8.2 原型有但实现缺失
| 原型项 |
实现状态 |
| 导出 (项目) |
🐛 stub — exportProjects 仅 ElMessage.info('开发中') |
| 项目评价导出 |
🐛 stub — exportEval 仅 ElMessage.info('开发中') |
| 已报名专家信息导出 |
✅ 实装 — exportExperts POST executionIntent/export |
managerScore 评价得分筛选 |
🐛 后端未生效 — 前端有字段,后端 selectList 无此过滤 |
8.3 文案/标签差异
| 原型 |
实现 |
差异 |
| 评分标准输入框 placeholder "1~5" |
el-input-number :min=0 :max=5 :step=1 |
实现用数字输入控件(更好) |
| "您确定要开通吗?" 弹框 |
仅"确定/取消",无截止时间字段 (单条版) |
原型单条版也无,批量版实现加了 |
表格 min-width=2900px 横滚 |
el-table 自动布局 |
实现用 fixed="right" 操作列,列宽合理 |
| 状态颜色: 蓝色 status-activated (已开通) |
❌ 实现无 "已开通" 状态列,只有"已结题/未结题"列 |
实现走 end_time 而非 is_finished,前端没标"已开通" tag |
8.4 总结
- 整体方向: 实现比原型更完整 (评分明细反显、批量开通截止时间、支持方/执行方下拉),但丢了 3 个导出按钮 (2 个 stub,1 个已报名专家实装)
- 设计偏离: 把
is_finished 复用为"开通/结题"两种语义 (改值而非新增字段),导致前端状态显示混乱 (已结题是 tag,已开通没 tag)
9. 表字段冗余 / 设计问题
9.1 实体 vs 表不一致 (详见 §6.3)
- ✅ 已修: projectId String → Long (见 §修复记录)
- ✅ 已修: startTime/endTime String → Date (见 §修复记录)
- 🔴 严重冗余 (未修): noticeUrl / scheduleUrl / isPublished / publishTime / announcementType 这 5 个字段只在实体里,DDL 实测不存在,但
confirmDeleteAnn 仍按它们构造 payload,后端 mapper 的 <if test="..."> 静默跳过,公告删除功能只删了 publishUrl/invitation/supportLetter 3 个
9.2 JOIN 字段 vs 持久化字段混淆
sponsorOrgName / execOrgNames / leadUserName / assignedSessions / assignedAmount 全部是派生字段,不入库,只在 SQL JOIN 时填充,前端可读但不应让前端 PUT 回去
- 当前 mapper 的
updateByPrimaryKey 已经用 <if test="..."> 防御,不会回写 — OK
9.3 唯一约束语义检查
- ✅ biz_project: uk_project_no — 项目编号唯一,合理
- ✅ biz_project_rating: uk_rating (project_id, rater_id, rater_role) — 一人评一次,合理
- ❌ biz_project_assign: 缺唯一约束 (project_id, execution_unit_id) — 可能重复分配同一执行方
9.4 业务闭环检查
- 项目分配 dialog:
assignForm.execRows 行内 el-select 的 value=MAIN user_id,后端 BizProjectAssignServiceImpl 应该从 exec_user_id 反查 biz_org 写 execution_unit_id (mapper 注释有写,需确认 service 实现)
- 项目分配 → 评分 → 公示 → 通知: 整条业务链已铺,但 manager_score 聚合分写的是 manager 评分的均值,逻辑合理
- 批量开通: 改
end_time 而不是新建 is_activated 字段 — 用现有字段表达多语义,容易跟"结题"冲突,设计可商榷
10. 待修复列表 (按优先级)
| # |
问题 |
文件:行 |
修复建议 |
严重度 |
| 1 |
单条"开通"误改 is_finished='0' 而非 end_time |
Projects.vue:398 |
改 endTime 而非 isFinished |
P0 |
| 2 |
confirmDeleteAnn 引用了不存在的字段 noticeUrl/scheduleUrl/isPublished |
Projects.vue:411-419 + BizProject.java:113-122 |
要么补 DDL,要么删实体字段;mapper 不更新这些字段导致功能部分失效 |
P0 |
| 3 |
managerScore 筛选前端有,后端 selectList 不读 |
BizProjectMapper.xml:141-183 (selectList) |
加 <if test="managerScore != null"> 过滤 |
P0 |
| 4 |
下拉菜单 divided 冗余 (activate/delAnn 双分隔线) |
Projects.vue:100-101 |
删 activate 的 divided |
P1 |
| 5 |
生产代码残留 console.info/error (4 处) |
Projects.vue:299, 302, 306, 875 |
✅ 已修 (见 §修复记录) |
— |
| 6 |
评分默认值不一致 |
Projects.vue:501 vs 513 |
统一为 0 或 5 |
P2 |
| 7 |
batchScoreRows 死代码 |
Projects.vue:501 |
✅ 已修 (见 §修复记录) |
— |
| 8 |
isSettled 筛选但表格无列显示 |
Projects.vue:26-30 vs §4.2 表 |
加列 或 删筛选 |
P2 |
| 9 |
日期选择器 value-format="YYYY-MM-DD HH:mm:ss" 跟 type="date" 不匹配 |
Projects.vue:17, 19 |
✅ 已修 (见 §修复记录) |
— |
| 10 |
重复 score table HTML (批量评分 + 单条评分) |
Projects.vue:201-213, 257-269 |
抽公共组件 |
P2 |
| 11 |
2 个导出按钮是 stub (exportProjects / exportEval) |
Projects.vue:347-348 |
实装 或 删按钮 |
P2 |
| 12 |
biz_project 缺索引 sponsor_admin_user_id |
DB |
✅ 已修 (见 §修复记录) |
— |
| 13 |
biz_project 缺索引 start_time, end_time (范围查询) |
DB |
✅ 已修 (见 §修复记录) |
— |
| 14 |
biz_project_assign 缺 UNIQUE (project_id, execution_unit_id) |
DB |
✅ 已修 (见 §修复记录) |
— |
| 15 |
is_finished 和 is_settled 状态值约定不一致 (0/1 vs Y/N) |
BizProject.java:65-68 + mapper |
统一约定 (要么全 0/1 要么全 Y/N) |
P3 |
| 16 |
selectList / selectExecutorList 有 4 个相关子查询 |
BizProjectMapper.xml:46-50, 110-124 |
数据小可接受,但应记录为 P3 待优化 |
P3 |
| 17 |
.score-row CSS 死样式 |
Projects.vue:926-928 |
删 |
P3 |
11. 引用清单
前端
ry-vue3/src/views/manager/Projects.vue — 主列表(881 行)
ry-vue3/src/views/manager/ProjectsNew.vue — 新建/编辑独立页
ry-vue3/src/views/manager/ManagerProjectDetail.vue — 详情独立页
ry-vue3/src/layout/AdminLayout.vue:98 — 菜单项定义
ry-vue3/src/router/index.js:65-68 — 路由 (含 /new, /edit/:id, /detail/:id)
ry-vue3/src/api/public.js — bizList/bizAdd/bizUpdate/bizDelete 通用 CRUD
ry-vue3/src/api/system.js — listExecutorOrgs/listSponsorOrgs 项目分配用
ry-vue3/src/components/Preview.vue — 附件预览 (import 但未使用?)
后端 (Java)
ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizProjectController.java — 项目主 controller (291 行, 14 个 endpoint)
ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java — 实体 (230 行, 字段定义含 5 个 DDL 不存在的字段)
ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectAssign.java — 分配实体
ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectRating.java — 评分实体
ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizProjectRatingController.java — 评分 CRUD (单独 controller)
ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizProjectAssignServiceImpl.java — 分配 service (分配时反查 execution_unit_id)
ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/impl/BizProjectRatingServiceImpl.java — 评分 upsert service
Mapper XML
ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectMapper.xml — 项目主 mapper (301 行, 4 个 select + insert + update + 2 个 delete)
ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectAssignMapper.xml — 分配 mapper (106 行)
ry-api/ruoyi-business/src/main/resources/mapper/business/BizProjectRatingMapper.xml — 评分 mapper (含 upsertRating 三元组 ON DUPLICATE KEY)
配置 / 工具
ry-api/ruoyi-admin/src/main/resources/application-druid.yml:9-10 — DB 连接 (root / cu2oh2co3)
原型
proto/html/manager.html:206 — 菜单入口
proto/html/components/project-manage.html — 项目管理原型 (676 行,含 5 个 modal)
proto/html/components/project-assign.html — 项目分配原型 (未深读,但 dialog 实装与之有偏差)
数据库 (实测)
biz_project — 11 行,5 索引 (含 1 唯一),缺 3 个索引
biz_project_assign — 11 行,4 索引,缺唯一约束
biz_project_rating — 21 行,5 索引 (含 1 唯一),设计优秀
biz_org — 33 行 (待查索引,本审查未深入)
sys_user — 40 行 (待查 role_type 索引)