P0 #1 修复: 评分之前写到 biz_project (错表), 4 维度 (scoreQuality/Speed/Coordination/Compliance) 实体无字段, mapper 无 <if>, Spring Boot Jackson 默认丢弃未知字段 — 全部失效。 改动: - 后端 BizProjectController.rate: 同步聚合该项目所有 compliance 评分的 4 维度总分之平均, 写回 biz_project.manager_score (1 位小数, HALF_UP)。复用现有 selectList, 不动 mapper xml。 - 前端 ManagerProjectDetail.submitScore: 改调 POST /business/project/rate, body 用后端字段名 (qualityScore/responseScore/cooperationScore/complianceScore), raterRole='compliance' 跟原型"角色:合规"对齐。加 totalScore<4 校验。 - 附: _self/manager_projects_detail.md 11 章审查报告 (含本修复) Co-Authored-By: Claude <noreply@anthropic.com>
39 KiB
0. 4 跳定位 (铁律: 不按名字猜)
| 跳 | 文件:行 | 结果 |
|---|---|---|
| ① 角色菜单 | ry-vue3/src/layout/AdminLayout.vue:96 |
path: '/manager/projects', title: '项目管理' (父菜单, 无独立菜单) |
| ② 路由 | ry-vue3/src/router/index.js:63 |
name: 'manager-projects-detail', component = ManagerProjectDetail.vue ✓ |
| ③ 组件 | ry-vue3/src/views/manager/ManagerProjectDetail.vue |
459 行 (本审查对象, 已 style-aligned) |
| ④ 原型 | proto/html/components/project-detail-compliance.html |
合规管理员视角的项目详情独立页原型 (236 行, 4 个 .card 卡片 + 9 字段项目信息) — 修正: 之前误判为"无原型", 实际是 components/ 根目录, 不在 manager/ 子目录 |
调用路径:
manager/Projects.vue:424viewDetail(row)→router.push('/manager/projects/detail/${row.projectId}')(列表行 dropdownview命令或 toolbar "详情" 按钮触发)。 同级路由 (router/index.js:61-63):
projects/new→ProjectsNew.vue(新建)projects/edit/:projectId→ProjectsNew.vue(编辑, 复用组件)projects/detail/:projectId→ManagerProjectDetail.vue(详情) ✓
1. 主要功能 + 可见性
1.1 主要功能
| 功能 | 前端入口 | 后端接口 | 涉及表 |
|---|---|---|---|
| 加载项目基本信息 | loadProject (ManagerProjectDetail.vue:234-263) |
GET /business/project/{id} → BizProjectController.getInfo → bizProjectService.getById → BizProjectMapper.selectByPrimaryKey |
biz_project (+ JOIN biz_org/sponsor / sys_user/lead / biz_person/createUserName / sub-select exec_org_names) |
| 加载角色劳务 (JSON 字段) | 同上 L249-251 | 同上 (期望 biz_project.role_labor 列) | biz_project.role_labor — 字段不存在 ⚠️ |
| 加载监督员 (JSON 字段) | 同上 L253-256 | 同上 (期望 biz_project.monitors 列) | biz_project.monitors — 字段不存在 ⚠️ |
| 加载评分 4 维度 | scoreForm 初始化 L243-246 |
同上 (期望 biz_project.score_quality/score_speed/score_coordination/score_compliance 列) | biz_project — 4 列全不存在 ⚠️ |
| 加载公告列表 | loadAnnouncements (L265-274) |
GET /business/publicity/list?projectId=X → BizPublicityController.list (L30: entity.setStatus(null) 全查) → bizPublicityMapper.selectByProjectId |
biz_publicity |
| 发布公告 | submitAnnouncement (L318-338) + bizAdd('publicity', {...}) |
POST /business/publicity → BizPublicityController.add (L42-50, status 默认 '1') |
biz_publicity |
| 删除公告 | deleteAnnouncement (L339-348) + bizDelete('publicity', id) |
DELETE /business/publicity/{id} |
biz_publicity |
| 预览公告附件 | openPreview (L381-386) + <Preview> 组件 |
URL 直接走 OSS / 内网文件服务 | (前端, 不入库) |
| 增加监督员 (本地) | confirmAddMonitor (L296-302) |
无保存接口 ⚠️ | (本地数组) |
| 删除监督员 (本地) | removeMonitorAt (L303-306) |
无保存接口 ⚠️ | (本地数组) |
| 增加角色行 (本地) | addRoleRow (L277-280) |
无保存接口 ⚠️ | (本地数组) |
| 删除角色行 (本地) | removeRoleRowAt (L281-284) |
无保存接口 ⚠️ | (本地数组) |
| 加载监督员候选人 | openAddMonitor (L287-295) + bizList('person', {role:'supervisor', unitType:'sponsor'}) |
GET /business/person/list?role=supervisor&unitType=sponsor → BizPersonController.list |
sys_user (role=supervisor) + biz_person |
| 项目评分 (4 维度 + 总分) | submitScore (L354-374) + bizUpdate('project', {scoreQuality, scoreSpeed, scoreCoordination, scoreCompliance, managerScore}) |
PUT /business/project → BizProjectController.edit → bizProjectMapper.updateByPrimaryKey |
biz_project — 4 维度字段不存在, 仅 managerScore 入库 (详见 §10 #1) ⚠️ |
1.2 哪些人能看到本页面
| 层 | 来源 | 校验字段 |
|---|---|---|
| 前端菜单 | AdminLayout.vue:96 (/manager/projects) |
无 requireMain, 主/子账号均可见 (manager 角色没有子账号业务) |
| 路由守卫 | router/index.js:63 |
meta.role 无显式, 走全局 permission.js 校验登录态 |
| 后端 SQL | BizProjectController.getInfo @Autowired service |
无 role 过滤, 任何登录角色都可 hit 接口 — 跟前端 "manager 全局可见" 语义一致 |
其他角色对应功能:
- sponsor 走
sponsorList(BizProjectController.sponsorList) 按 sponsorAdminUserId 过滤, 自己的项目可看 - executor 走
executorList按 execUserId/executionUnitId 过滤, 自己被分配的项目可看 - doctor 走
myProjects按 executionIntent 过滤, 自己意向的项目可看 - admin 走通用
list, 全部可见
数据隔离: manager 是无界角色, 可看任何项目; 但评分接口 /rate (BizProjectController:197-210) 走 biz_project_rating (中间表, 有 rater_id 隔离), 设计 OK。⚠️ 本页面评分实际写到 biz_project 而非 biz_project_rating (详见 §10 #1)。
2. 筛选项 (filter-form)
本页是详情页, 无 filter-form。下面对应区段内的 el-form-item:
| UI label | 控件 | 绑定 | 后端 SQL | 命中表.字段 | 备注 |
|---|---|---|---|---|---|
| 项目编号 | <span> (el-form-item 静态显示) |
row.projectNo |
selectByPrimaryKey (BizProjectMapper.xml:82-85) selectFields |
biz_project.project_no | ✅ |
| 项目名称 | <span> |
row.projectName |
同上 | biz_project.project_name | ✅ |
| 总场次/总期数 | <span> |
row.totalSessions |
同上 | biz_project.total_sessions | ✅ |
| 总金额 | <span class="money"> |
row.totalAmount |
同上 | biz_project.total_amount | ✅ |
| 管理费及税金 | <span class="money"> |
row.manageFee |
同上 | biz_project.manage_fee | ✅ |
| 项目形式 | <span> |
row.projectForm |
同上 | biz_project.project_form | ✅ |
| 项目开始时间 | <span> |
row.startTime |
同上 | biz_project.start_time | ✅ |
| 项目结束时间 | <span> |
row.endTime |
同上 | biz_project.end_time | ✅ |
| 是否招标项目 | <span> |
row.isBidProject (Y/N) |
同上 | biz_project.is_bid_project | ✅ |
| 项目负责人 | <span> |
`row.leadUserName | row.sponsorAdminUserName` | selectFields L57 LEFT JOIN sys_user lu ON lu.user_id = p.lead_user_id | |
| 支持公司 | <span> |
row.sponsorAdminUserName |
selectFields L55 LEFT JOIN biz_org o ON o.user_id = p.sponsor_admin_user_id AND o.org_type='sponsor' | sys_user.user_name (JOIN) — 字段实际存 biz_project.sponsor_admin_user_name (冗余) | ✅ |
| 项目评价 | <a class="op-link"> |
row.managerScore |
同上 | biz_project.manager_score | ✅ 链接到评分 dialog |
注意: 详情页 11 字段全部 <span> 只读, label-width 120px, 字段已 align style (max-width 1200px, 居左)。
3. 工具栏按钮 (toolbar)
本页无传统工具栏, 工具栏按钮分散在各章节尾部, 走 el-button link 风格:
| 按钮 | 功能 | 接口 | 涉及表 | 备注 |
|---|---|---|---|---|
| + 增加角色 (L64) | addRoleRow (L277-280) |
无保存 | — | P1 隐患: 修改不落库 |
| 删除 (角色行, L58) | removeRoleRowAt (L281-284) |
无保存 | — | P1 隐患 |
| + 增加监督员 (L79) | openAddMonitor (L287-295) → confirmAddMonitor (L296-302) |
无保存 | — | P1 隐患 |
| 删除 (监督员, L74) | removeMonitorAt (L303-306) |
无保存 | — | P1 隐患 |
| 发布公告 (L85) | openUpload → submitAnnouncement (L318-338) |
POST /business/publicity |
biz_publicity | ✅ 走完整 CRUD |
| 刷新 (L86) | loadAnnouncements (L265-274) |
GET /business/publicity/list?projectId=X |
biz_publicity | ✅ |
| 查看 (公告行, L94) | openPreview (L381-386) |
URL 直连 | — | ✅ Preview 组件 |
| 删除 (公告行, L95) | deleteAnnouncement (L339-348) |
DELETE /business/publicity/{id} |
biz_publicity | ✅ ElMessageBox 二次确认 |
| 保存评分 (L134) | submitScore (L354-374) |
PUT /business/project ⚠️ |
biz_project (错表) | P0 隐患: 4 维度被静默丢弃 |
4. 表格 (el-table)
本页只有 1 个 el-table (公告列表), 数据源见 §4.1; 角色劳务走原生
<table class="info-table">, 不算 el-table。
4.1 已发布公告 el-table 数据来源
GET /business/publicity/list?projectId=X → BizPublicityController.list (L26-33) → bizPublicityService.selectList(entity) → BizPublicityMapper.selectList (BizPublicityMapper.xml:32-40) 或 selectByProjectId (L42-46) (前端走 selectList 透传 projectId)。
SELECT id, project_id, project_no, project_name, announce_type, title, file_url, remark, status, rating_time, create_by, create_time, update_by, update_time
FROM biz_publicity
WHERE project_id = #{projectId} [AND announce_type = ?] [AND title LIKE ?]
ORDER BY create_time DESC
⚠️
publish_time列在 resultMap / selectFields 中均无映射 (实测 mapper L5-20), 但表里有此列 (DDL COMMENT '发布时间') — 实际业务里所有数据publish_time = NULL(实测 4 行)。 ✅ 已映射的rating_time列 (DDL COMMENT '发布时间' 也指这个) 通过BizPublicity.ratingTimeJava 字段暴露, 但字段名误导: rating 让人以为是评分时间, 实际是发布时间。
EXPLAIN 实测: key=idx_pub_project (project_id), ref=const, rows=1 ✅ 走索引。
4.2 列映射
| 列名 | row.* |
表.字段 | 备注 |
|---|---|---|---|
| # | (type=index) | — | ✅ |
| 公告标题 | row.title |
biz_publicity.title | ✅ |
| 发布时间 | row.createTime (formatter=fmtDate) |
biz_publicity.create_time | ⚠️ 字段错位: DDL 有 publish_time 列但未映射, 实际显示 create_time (创建时间, 通常早于发布时间) |
| 发布人 | row.creatorName |
⚠️ 字段名错 | 前端模板 (L91) 用 creatorName, 但 mapper / entity 暴露的是 createBy (varchar 64, 存 username 而非中文名) — 显示会是 admin/manager 等英文账号名, 不是中文名 |
| 操作: 查看/删除 | — | — | ✅ |
4.3 角色劳务 table (<table class="info-table role-labor-table">, L40-62)
| 列名 | 数据源 | 表.字段 | 备注 |
|---|---|---|---|
| 角色 | r.role |
(本地数组 form.roleRows) | ⚠️ 不落库 — form.roleRows 仅内存, 期望 biz_project.role_labor JSON 列, 但该列不存在 |
| 劳务金额 | r.amount |
同上 | 同上 |
| 操作 | — | — | 删除按钮 (L57-59) |
5. 行内操作按钮 (op-cell)
| 按钮 | 触发函数 | 接口 | 后端动作 | 涉及表.字段 |
|---|---|---|---|---|
| 角色行 删除 (L58) | removeRoleRowAt(idx) |
— | (本地 splice) | (期望 biz_project.role_labor JSON) |
| 监督员 删除 (L74) | removeMonitorAt(idx) |
— | (本地 splice) | (期望 biz_project.monitors JSON) |
| 公告 查看 (L94) | openPreview(row.fileUrl, row.title) |
URL 直连 | — | — |
| 公告 删除 (L95) | deleteAnnouncement(row) |
DELETE /business/publicity/{id} |
BizPublicityController.remove → BizPublicityMapper.deleteByPrimaryKey | biz_publicity.id |
真实落点: 角色劳务 / 监督员所有改动完全不入库 — 后端 BizProject 实体无 roleLabor / monitors 字段 (§6.4), mapper updateByPrimaryKey 也没有对应 <if>, 即使前端发到后端也会被 Jackson 静默丢弃 (Spring Boot 默认 spring.jackson.deserialization.fail-on-unknown-properties = false)。
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_score decimal(3,1) DEFAULT NULL COMMENT '支持方评均分',
sponsor_admin_user_id bigint DEFAULT NULL COMMENT '赞助方负责人用户ID',
sponsor_admin_user_name varchar(200) DEFAULT NULL COMMENT '赞助方负责人用户名(冗余)',
lead_user_id bigint DEFAULT NULL COMMENT '项目负责人 user_id',
is_bid_project char(1) DEFAULT 'N' COMMENT '是否招标项目 Y/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,
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_user_id bigint DEFAULT NULL,
create_time datetime DEFAULT NULL,
update_by varchar(64) DEFAULT '',
update_time datetime 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),
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='项目';
数据规模: 12 行 (实测)。
6.2 biz_publicity DDL (实测)
CREATE TABLE biz_publicity (
id bigint NOT NULL AUTO_INCREMENT COMMENT 'PK',
project_id bigint NOT NULL COMMENT '项目ID',
project_no varchar(50) DEFAULT NULL COMMENT '项目编号 (冗余)',
project_name varchar(200) DEFAULT NULL COMMENT '项目名称 (冗余)',
announce_type varchar(20) DEFAULT NULL COMMENT '公告类型 (invitation/support/notice/agenda)',
title varchar(200) DEFAULT NULL COMMENT '公告标题',
file_url varchar(500) DEFAULT NULL COMMENT '公告文件URL',
publish_time datetime DEFAULT NULL COMMENT '发布时间',
remark varchar(500) DEFAULT NULL,
status char(1) DEFAULT '1' COMMENT '状态 (0保存 1发布)',
rating_time datetime DEFAULT NULL COMMENT '发布时间', -- ⚠️ 命名误导: COMMENT 说"发布时间", 但列名 rating_time
create_by varchar(64) DEFAULT NULL,
create_time datetime DEFAULT NULL,
update_by varchar(64) DEFAULT NULL,
update_time datetime DEFAULT NULL,
PRIMARY KEY (id),
KEY idx_pub_project (project_id),
KEY idx_pub_create_time (create_time),
KEY idx_pub_project_no (project_no),
KEY idx_pub_announce_type (announce_type),
KEY idx_pub_title (title)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目公告';
数据规模: 4 行 (实测)。
6.3 biz_project_rating DDL (实测, 与评分相关)
CREATE TABLE biz_project_rating (
rating_id bigint NOT NULL AUTO_INCREMENT COMMENT '雪花ID',
project_id bigint NOT NULL COMMENT '项目ID',
project_no varchar(50) DEFAULT NULL,
project_name varchar(200) DEFAULT NULL,
rater_id bigint DEFAULT NULL COMMENT '评分人ID',
rater_name varchar(50) DEFAULT NULL,
rater_role varchar(20) DEFAULT NULL COMMENT '评分人角色 compliance/executor/sponsor',
quality_score int DEFAULT '0' COMMENT '履约质量 1-5',
response_score int DEFAULT '0' COMMENT '时效响应 1-5',
cooperation_score int DEFAULT '0' COMMENT '配合度 1-5',
compliance_score int DEFAULT '0' COMMENT '合规安全 1-5',
remark varchar(500) DEFAULT NULL,
rating_time datetime DEFAULT NULL,
create_by varchar(64) DEFAULT NULL,
create_time datetime DEFAULT NULL,
update_by varchar(64) DEFAULT NULL,
update_time datetime DEFAULT NULL,
PRIMARY KEY (rating_id),
UNIQUE KEY uk_rating (project_id, rater_id, rater_role),
KEY idx_pr_project (project_id),
KEY idx_pr_rater (rater_id),
KEY idx_pr_rating_time (rating_time)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目评分表';
数据规模: 30 行 (实测)。
6.4 Entity ↔ DB 字段对照
BizProject (ManagerProjectDetail 涉及的 12 字段)
| 实体字段 | 中文列名 (DDL COMMENT) | 类型 | 表字段 | 类型 | 一致? |
|---|---|---|---|---|---|
| projectId | 项目ID | Long | project_id | bigint | ✅ |
| projectNo | 项目编号 | String | project_no | varchar(50) | ✅ |
| projectName | 项目名称 | String | project_name | varchar(200) | ✅ |
| totalSessions | 总场次/总期数 | Long | total_sessions | int | ✅ (Long 兼容 int) |
| totalAmount | 总金额 | BigDecimal | total_amount | decimal(15,2) | ✅ |
| manageFee | 管理费及税金 | BigDecimal | manage_fee | 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) | ✅ |
| sponsorAdminUserId | 赞助方负责人用户ID | Long | sponsor_admin_user_id | bigint | ✅ |
| leadUserId | 项目负责人 user_id | Long | lead_user_id | bigint | ✅ |
| isBidProject | 是否招标项目 | String | is_bid_project | char(1) | ✅ |
| 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) | ✅ |
| createTime | 创建时间 | Date | create_time | datetime | ✅ |
| updateTime | 更新时间 | Date | update_time | datetime | ✅ |
⚠️ 前端使用但实体/DB 完全不存在的字段
| 前端字段 | 用途 | 实体 | Mapper <if> |
DB 列 | 状态 |
|---|---|---|---|---|---|
row.scoreQuality (L243) |
评分 4 维度 - 履约质量 | ❌ | ❌ | ❌ | 完全不存在 |
row.scoreSpeed (L244) |
评分 4 维度 - 时效响应 | ❌ | ❌ | ❌ | 完全不存在 |
row.scoreCoordination (L245) |
评分 4 维度 - 配合度 | ❌ | ❌ | ❌ | 完全不存在 |
row.scoreCompliance (L246) |
评分 4 维度 - 合规安全 | ❌ | ❌ | ❌ | 完全不存在 |
row.roleLabor (L249) |
角色劳务 JSON | ❌ | ❌ | ❌ | 完全不存在 |
row.monitors (L254) |
监督员 JSON | ❌ | ❌ | ❌ | 完全不存在 |
结论: 上面 6 个字段前端读不到 (始终
undefined→ JSON.parse 抛错 → catch 兜底用默认值), 后端写不入 (Jackson 静默丢弃)。
BizPublicity (ManagerProjectDetail 涉及的 7 字段)
| 实体字段 | 中文列名 (DDL COMMENT) | 类型 | 表字段 | 类型 | 一致? |
|---|---|---|---|---|---|
| id | PK | Long | id | bigint | ✅ |
| projectId | 项目ID | Long | project_id | bigint | ✅ |
| projectNo | 项目编号 | String | project_no | varchar(50) | ✅ |
| projectName | 项目名称 | String | project_name | varchar(200) | ✅ |
| announceType | 公告类型 | String | announce_type | varchar(20) | ✅ |
| title | 公告标题 | String | title | varchar(200) | ✅ |
| fileUrl | 公告文件URL | String | file_url | varchar(500) | ✅ |
| remark | 备注 | String | remark | varchar(500) | ✅ |
| status | 状态 | String | status | char(1) | ✅ |
| ratingTime | 发布时间 (COMMENT 误导) | Date | rating_time | datetime | ✅ |
| createBy | 创建者 | String | create_by | varchar(64) | ✅ |
| createTime | 创建时间 | Date | create_time | datetime | ✅ |
| updateBy | 更新者 | String | update_by | varchar(64) | ✅ |
| updateTime | 更新时间 | Date | update_time | datetime | ✅ |
⚠️ BizPublicity 表里有但 mapper 不映射的字段
| DB 列 | DDL COMMENT | 实体字段 | 备注 |
|---|---|---|---|
publish_time |
发布时间 | ❌ | mapper selectFields / resultMap / insert / update 全部未引用, 实测 4 行数据此列全为 NULL |
6.5 修订
v1 误判:
biz_publicity.rating_time看似是"评分时间" — 实际 DDL COMMENT 明确写 "发布时间", 与biz_project_rating.rating_time(评分时间) 同列名但不同语义。应统一改名为publish_time(跟同表publish_time列对齐, 删掉冗余rating_time)。
7. 索引检查
7.1 biz_project
| Key | 列 | 用途覆盖 |
|---|---|---|
| PRIMARY | project_id |
✅ |
| uk_project_no | project_no (UNIQUE) |
✅ selectByPrimaryKey / sponsorList / selectList / manager 端精确查 |
| idx_project_form | project_form |
✅ selectList WHERE project_form = ? |
| idx_is_finished | is_finished |
✅ selectList WHERE is_finished = ? |
| idx_is_settled | is_settled |
✅ selectList WHERE is_settled = ? |
| idx_sponsor_admin_user_id | sponsor_admin_user_id |
✅ sponsorList WHERE sponsor_admin_user_id = ? |
| idx_start_end_time | (start_time, end_time) |
✅ selectList WHERE start_time >= ? AND end_time <= ? |
冗余检查: 无明显冗余索引。
7.2 biz_publicity
| Key | 列 | 用途覆盖 |
|---|---|---|
| PRIMARY | id |
✅ |
| idx_pub_project | project_id |
✅ selectList WHERE project_id = ? (本页面主查询) — EXPLAIN 实测命中 |
| idx_pub_create_time | create_time |
✅ ORDER BY create_time desc |
| idx_pub_project_no | project_no |
⚠️ 当前页面未用, 待清理或保留 |
| idx_pub_announce_type | announce_type |
✅ selectList WHERE announce_type = ? |
| idx_pub_title | title |
✅ selectList WHERE title LIKE ? |
冗余检查: 5 个单列索引, 数据规模 4 行, 优化器实测选 idx_pub_project (本页面主查询), 其它索引暂保留无害。
7.3 biz_project_rating (评分备用表)
| Key | 列 | 用途覆盖 |
|---|---|---|
| PRIMARY | rating_id |
✅ |
| uk_rating | (project_id, rater_id, rater_role) UNIQUE |
✅ upsert 一人评一项目一次 |
| idx_pr_project | project_id |
⚠️ 前缀被 uk_rating 覆盖, EXPLAIN 应优先 uk |
| idx_pr_rater | rater_id |
✅ 按评分人查 |
| idx_pr_rating_time | rating_time |
✅ 按时间排序 |
7.4 EXPLAIN 实测 (2026-08-20)
| # | 查询 | 命中索引 | 备注 |
|---|---|---|---|
| Q1 | SELECT_BY_PRIMARY_KEY biz_publicity WHERE project_id=2 |
idx_pub_project (ref const) |
✅ |
| Q2 | SELECT_BY_PRIMARY_KEY biz_project WHERE project_id=2 |
PRIMARY (const) |
✅ |
8. 与原型差异 (实现偏离)
原型:
proto/html/components/project-detail-compliance.html(236 行, "角色:合规 父级:manager.html 原型:原型/项目详情.html" 注释确认) 结构: 4 个独立.card+h1.page-title标题 + 单<main class="content">包裹 + 底部返回按钮 + 全页红字*项目详情可修改,只有创建人可修改
8.1 实现新增 (原型没有)
| 项 | 说明 |
|---|---|
| 跳转子页面 (而非 dialog) | 详情页是独立路由 ManagerProjectDetail.vue, 走 router.push (列表行 dropdown view 命令触发) |
| 项目信息扩展 5 字段 | 原型 9 字段 → Vue 11 字段, 新增 项目开始时间 / 项目结束时间 / 是否招标项目 / 项目负责人 / 支持公司 (映射 biz_project.start_time / end_time / is_bid_project / lead_user_name (JOIN) / sponsor_admin_user_name) |
| 项目评价 dialog | 原型只显示 4.5 一个数字 + alert 弹窗, Vue 改成 el-dialog 4 维度 el-rate × 5 + 总分20 实时计算 |
| 增加监督员 dialog | el-select 远程搜索候选人 (按 role='supervisor' + unitType='sponsor'), 原型用 prompt() 弹输入框 |
| 角色劳务增/删按钮 | 原型只显示静态 2 行 (主席5000 / 主持3000), Vue 加 + 增加角色 按钮 + 行内 删除 按钮 — 但无保存接口, 改完刷新即丢 (§10 #2) |
| 公告预览组件 | <Preview> 组件走 URL 直连 + OSS, 原型是 alert('预览文件') |
8.2 原型有但实现缺失
| 项 | 原型位置 | 实现 | 严重度 |
|---|---|---|---|
| h1 page-title "项目详情" | project-detail-compliance.html:89 <h1 class="page-title" id="pageTitle">项目详情</h1> |
⚠️ Vue 仅面包屑 <span class="current">项目详情</span>, 无大标题 |
P3 |
| 4 个独立 .card | 原型 L92/109/132/144 (4 个 <div class="card">) |
⚠️ Vue 用 1 个 page-card + 内部 .new-card-title 章节标题 — 视觉简化, 业务一致 | P3 |
| 项目信息 — 服务机构字段 | 原型 L101 <span class="info-label">服务机构:</span><span class="info-value" id="vService">北京XXXX公司</span> |
⚠️ Vue 完全没显示 — 但 DB 有 exec_org_names 派生字段 (mapper L50-53) 可用 |
P2 |
| 邀请函 / 支持函 文件区 | 原型 L147-169 3 子标题 (邀请函 / 已发布公告 / 支持函), 每段 查看+下载 | ⚠️ Vue 仅做"已发布公告" el-table, 邀请函和支持函完全未实现 — DB 已有 biz_project.invitation_url / support_letter_url 列 (selectFields L46-69 已读), 但页面没用 |
P2 |
| 角色劳务 — 无增/删按钮 | 原型 L131-141 仅展示 2 行静态数据 | ✅ Vue 加了增/删按钮 (但无保存, 配合 §10 #2 形成新 bug) | — |
| 红字提示 *项目详情可修改,只有创建人可修改 | 原型 L172 <p class="hint-red">*项目详情可修改,只有创建人可修改</p> |
⚠️ Vue 没加, 业务规则没人管 (谁可以编辑项目?) | P3 |
| 底部"返回"按钮 | 原型 L174-176 <div class="form-actions"><button onclick="goBack()">返回</button></div> |
⚠️ Vue 无底部"返回"按钮 (只能靠浏览器后退或面包屑) | P3 |
| 评分 4 维度持久化 | 原型未细说, 但应有评分持久化逻辑 | ⚠️ Vue 评分 dialog 提交写到 错表 (biz_project 而非 biz_project_rating), 4 维度全部丢失 | P0 |
| 角色劳务持久化 | 原型未细说, 但应有持久化 | ⚠️ Vue form.roleRows 仅内存, 改完刷新即丢 |
P1 |
| 监督员持久化 | 原型未细说, 但应有持久化 | ⚠️ Vue monitors 数组仅内存, 改完刷新即丢 |
P1 |
8.3 文字 / 标签差异
| 原型 | 实现 | 差异 |
|---|---|---|
| 项目管理 > 项目详情 (面包屑) | ✅ Vue 1:1 (L3-6) | — |
| 项目详情 (h1 page-title) | 仅面包屑, 无 h1 | ⚠️ 缺 |
| 项目信息 (9 字段) | 项目信息 (11 字段, 缺"服务机构", 多 5 字段) | ⚠️ 字段不齐 |
| 支持单位监督员 | ✅ 同 | — |
| 角色劳务设置 | ✅ 同 (但 Vue 加了增/删按钮) | ⚠️ 增/删按钮但无保存, 形成新 bug |
| 公告文件 | 已发布公告 (Vue 改 el-table) | ⚠️ 邀请函/支持函 缺, 实现偏离 |
| 红字 *项目详情可修改,只有创建人可修改 | (未加) | ⚠️ 缺业务规则 |
| *增加只显示已点支持意向的 | ✅ Vue 加 (hint-text 灰字) | 颜色从红字 → 灰字 (跟 ProjectsNew 对齐) |
| *点击后可查看具体评价维度,评价标准与供应商系统保持一致 | ⚠️ Vue 缺 | 缺 |
8.4 总结
整体方向: 业务核心对齐 + 多处扩展偏离:
严重问题 (按优先级):
- P0 #1 评分写到错表 (biz_project 而非 biz_project_rating), 4 维度全部丢失 — 即使原型没说, 但已有
biz_project_rating表 +/business/project/rate接口 - P1 #2 角色劳务 / 监督员 UI 有但无保存 (原型未要求增/删按钮, Vue 自加反而暴露了无保存 bug)
- P2 #4 邀请函 / 支持函 完全未实现 (DB 有列, mapper 已 select, 页面不用)
- P2 #5 "服务机构" 字段缺 (mapper 有派生
exec_org_names, 页面没用)
风格问题 (P3):
- 缺 h1 大标题 / 底部返回按钮 / "项目详情可修改" 红字业务规则
- 监督员提示从红字改灰字 (已 style-align, 跟 ProjectsNew 一致)
建议优先级: P0 #1 → P1 #2 → P2 #4+#5 → P3 其它。
9. 表字段冗余 / 设计问题
9.1 派生 vs 持久化字段
biz_project.sponsor_admin_user_name: 持久化 (冗余), 用于列表展示避免 JOIN biz_org, 设计 OKbiz_project.sponsor_admin_user_id: 持久化, 用于 sponsorList 过滤, 必需BizProject.sponsorOrgName / leadUserName / createUserName / execOrgNames: 派生字段 (Mapper sub-select / LEFT JOIN), 不入库, 仅展示, 设计 OK
9.2 唯一约束语义检查
- ✅ biz_project:
uk_project_no (project_no)— 业务上项目编号唯一, 合理 - ✅ biz_publicity: 无 uk, 同一项目可发多条公告 (合理)
- ✅ biz_project_rating:
uk_rating (project_id, rater_id, rater_role)— 一人评一项目一次, upsert 防重, 合理
9.3 业务闭环检查
- ✅ 项目评分已有专门表
biz_project_rating(有 rater_id 隔离, uk 防重, 走 upsert) - ⚠️ 本页面评分实际写到
biz_project而非biz_project_rating(§10 #1) — 既没走专用接口, 4 维度也丢 - ⚠️ 角色劳务 / 监督员无持久化 (§10 #2) — 期望落
biz_project.role_labor / monitorsJSON 列, 但这俩列从未建过
9.4 路由安全性
manager-projects-detail路由meta.role无显式, 走全局登录态校验, 任何已登录角色都可访问 — 与 admin-projects 同款 (router/index.js:28admin-projects 也指向 manager/Projects.vue), 业务上 OK 但没显式meta: { role: 'manager' }是隐性约定
9.5 命名误导
biz_publicity.rating_time列 (COMMENT "发布时间") — 跟biz_project_rating.rating_time(评分时间) 同列名但不同语义。应统一改名为publish_time(跟同表publish_time列对齐)
9.6 mapper 不映射的列
biz_publicity.publish_time列存在但 mapper 全链路 (resultMap / selectFields / insert / update) 不引用 — 业务上需要这个字段时无法存储
10. 待修复列表 (按优先级)
| # | 问题 | 文件:行 | 修复建议 | 严重度 |
|---|---|---|---|---|
| 1 | 评分 4 维度写到 biz_project (错表), 4 维度 (scoreQuality/Speed/Coordination/Compliance) 实体无字段, mapper 无 <if>, Spring Boot Jackson 默认丢弃未知字段 — 全部失效 |
ManagerProjectDetail.vue:354-374 submitScore |
改走 POST /business/project/rate (BizProjectController L197-210, 已有专用接口写 biz_project_rating 表), body 含 {projectId, raterRole:'compliance', qualityScore/scoreSpeed/cooperationScore/complianceScore, remark}, 不再写 managerScore (managerScore 应在 SELECT 时从 biz_project_rating 派生 AVG) |
P0 |
| 2 | 角色劳务无保存按钮, UI 可改可加可删但 form.roleRows 仅内存, 刷新后丢失 |
ManagerProjectDetail.vue:37-65, 277-284 |
DB 加 biz_project.role_labor JSON 列 + 实体加 roleLabor 字段 + mapper updateByPrimaryKey 加 <if test="roleLabor != null">role_labor = #{roleLabor},</if>; 前端加 "保存角色劳务" 按钮 + 提交时 JSON.stringify(form.roleRows) 走 bizUpdate('project', {projectId, roleLabor: JSON.stringify(form.roleRows)}) |
P1 |
| 3 | 监督员无保存按钮, 同 #2, monitors 数组仅内存 |
ManagerProjectDetail.vue:67-80, 287-306 |
同 #2: DB 加 biz_project.monitors JSON 列 + 实体 + mapper; 前端加 "保存监督员" 按钮 |
P1 |
| 4 | biz_publicity.publish_time 列存在但 mapper 全链路不映射, 实测 4 行数据此列全 NULL | BizPublicityMapper.xml 全文 |
selectFields 加 publish_time, resultMap 加 <result property="publishTime" column="publish_time" />, BizPublicity 实体加 private Date publishTime, BizPublicityController.add 在 setStatus('1') 时同步 entity.setPublishTime(new Date()) |
P2 |
| 5 | biz_publicity.announceTypes 写死 ('invitation/support/notice/agenda') 跟前端 enum 一致, 但 DDL COMMENT 与 entity @Excel 注解未提供枚举说明, 后端无校验 |
BizPublicityController:42-50 |
加 enum 校验或 enum 字典表 | P3 |
| 6 | 公告 creatorName 字段名错 (前端 L91), 实际 mapper 映射 createBy (英文 username), 显示会是 "admin" 而非 "张三" |
ManagerProjectDetail.vue:91 |
mapper 改造: BizPublicityMapper.xml selectFields 加 LEFT JOIN sys_user u ON u.user_name = p.create_by, 派生 creator_name; 或前端接受 username 显示 |
P3 |
| 7 | biz_publicity.rating_time 命名误导 (COMMENT 是"发布时间", 但跟 biz_project_rating.rating_time 同名冲突) | biz_publicity (DDL) |
改列名 rating_time → publish_time (跟同表 publish_time 列对齐, 删一个冗余) + 同步 entity 字段 + mapper |
P3 |
| 8 | 项目负责人 字段兜底显示 sponsorAdminUserName, 业务语义偏差 |
ManagerProjectDetail.vue:29 |
仅显示 row.leadUserName, 无值时显示 "-" 不兜底 sponsorAdminUserName |
P3 |
| 9 | 页面无 loading 态 (el-form 上没用 v-loading), 加载时只读区闪烁空白 |
ManagerProjectDetail.vue:10 |
<el-form ... v-loading="loading"> (loading ref 已定义 L173 但未使用) |
P3 |
| 10 | previewOpen / previewUrl / previewTitle 跟业务耦合 — 4 处共用 Preview 组件, 但当前只有公告预览用 |
ManagerProjectDetail.vue:209-211, 381-386 |
OK, 但若增加"邀请函预览"等需复用同一组件, 注意 title 不要硬编码 | P3 |
| 11 | biz_publicity.create_by 字段宽度仅 64 (DDL), 后续 username 含特殊字符可能截断 | DDL | 改 varchar(64) → varchar(128) | P3 |
| 12 | manager-projects-detail 路由 meta.role 无显式, 走全局登录态 |
router/index.js:63 |
加 meta: { role: 'manager' } 跟其它 manager 路由一致 |
P3 |
| 13 | biz_publicity 5 个单列索引, 数据规模 4 行, EXPLAIN 优化器选 idx_pub_project, 其它 4 个暂无命中场景 | biz_publicity (DB) |
数据量增长后再 EVAL DROP | P3 |
| 14 | 评分 dialog 提交后未校验 totalScore 是否在 4~20 范围 (允许 0 分但语义无意义) | submitScore L354 |
加 if (totalScore < 4) return ElMessage.warning('请至少给每个维度 1 分') |
P3 |
11. 引用清单
前端
ry-vue3/src/layout/AdminLayout.vue:96— manager 菜单 (项目管理入口)ry-vue3/src/router/index.js:61-63— 路由manager-projects-new / edit / detailry-vue3/src/views/manager/ManagerProjectDetail.vue— 本审查对象 (459 行)ry-vue3/src/views/manager/Projects.vue:424—viewDetail(列表行入口)ry-vue3/src/components/Preview.vue— 公告预览组件ry-vue3/src/api/public.js:90-98—bizGet/bizAdd/bizUpdate/bizDelete(CRUD 包装)
后端
ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizProjectController.java:139-210— 项目 getInfo / rate (评分专用接口, 本页面未走此接口, 见 §10 #1)ry-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPublicityController.java:26-65— 公告 list/getInfo/add/edit/removery-api/ruoyi-business/src/main/java/com/ruoyi/business/controller/BizPersonController.java:36-44, 65-79— 业务角色白名单 (executor/sponsor/doctor/manager), 监督员候选人按role='supervisor'+unitType='sponsor'过滤ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizProjectService.java+BizProjectServiceImpl—getById/updateByPrimaryKeyry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizProjectRatingService.java+ Impl —upsertRating(评分专用, 本页面未调用)ry-api/ruoyi-business/src/main/java/com/ruoyi/business/service/IBizPublicityService.java+ Implry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProject.java— 项目实体 (无 roleLabor/monitors/scoreQuality 等字段, 见 §6.4)ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizPublicity.java— 公告实体 (无 publishTime 字段, 见 §6.4)ry-api/ruoyi-business/src/main/java/com/ruoyi/business/domain/BizProjectRating.java— 评分实体 (走quality_score/response_score/cooperation_score/compliance_score)ry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizProjectMapper.java+ xml — 4 个 select + updateByPrimaryKeyry-api/ruoyi-business/src/main/java/com/ruoyi/business/mapper/BizPublicityMapper.java+ xml — selectByProjectId / selectList / insert / updateByPrimaryKey (publish_time 不映射, 见 §6.4)
DB (实测 2026-08-20)
biz_project(12 行, 34 列): 含 sponsor_admin_user_id / sponsor_admin_user_name / total_sessions / total_amount / manage_fee / submit_deadline_days / manager_score / sponsor_score / lead_user_id / is_bid_project / start_time / end_time / 5 URL 列 + 索引 6 个biz_publicity(4 行, 15 列): id / project_id / project_no / project_name / announce_type / title / file_url / publish_time (未映射) / remark / status / rating_time (命名误导) / 4 audit 列 + 索引 5 个biz_project_rating(30 行, 16 列): rating_id / project_id / rater_id / rater_name / rater_role / 4 维度分 (quality/response/cooperation/compliance) / remark / rating_time + 4 audit 列 + 索引 4 个 (含 uk_rating)
配置
ry-api/ruoyi-admin/src/main/resources/application-druid.yml— DB127.0.0.1:3306/guoju0808/root/cu2oh2co3
原型
proto/html/manager.html— manager 菜单入口 (iframe 路由)proto/html/components/project-manage.html— 项目管理列表原型 (对应manager/Projects.vue, 详情页无独立原型)
相关 (非本审查, 引用以辅助)
_self/manager_projects.md— 上游列表页审查报告 (本审查的调用方)_self/manager_projects_assign.md— 兄弟页/manager/projects/assign审查报告 (分配执行方)biz_project_sponsor_assign(15 列) — 与本页面无关: 存"赞助方分配监察员"中间表, 跟本页面"项目详情监督员"是不同业务 (本页面监督员期望落 biz_project.monitors JSON 列, 未实现)