Files
guoju0808/_self/manager_projects_detail.md
T
郭庆泰andClaude 99b9807270 fix(detail): 评分 4 维度改走 /business/project/rate + manager_score 同步回写
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>
2026-08-20 23:59:20 +08:00

39 KiB
Raw Blame History

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:424 viewDetail(row)router.push('/manager/projects/detail/${row.projectId}') (列表行 dropdown view 命令或 toolbar "详情" 按钮触发)。 同级路由 (router/index.js:61-63):

  • projects/newProjectsNew.vue (新建)
  • projects/edit/:projectIdProjectsNew.vue (编辑, 复用组件)
  • projects/detail/:projectIdManagerProjectDetail.vue (详情) ✓

1. 主要功能 + 可见性

1.1 主要功能

功能 前端入口 后端接口 涉及表
加载项目基本信息 loadProject (ManagerProjectDetail.vue:234-263) GET /business/project/{id}BizProjectController.getInfobizProjectService.getByIdBizProjectMapper.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=XBizPublicityController.list (L30: entity.setStatus(null) 全查) → bizPublicityMapper.selectByProjectId biz_publicity
发布公告 submitAnnouncement (L318-338) + bizAdd('publicity', {...}) POST /business/publicityBizPublicityController.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=sponsorBizPersonController.list sys_user (role=supervisor) + biz_person
项目评分 (4 维度 + 总分) submitScore (L354-374) + bizUpdate('project', {scoreQuality, scoreSpeed, scoreCoordination, scoreCompliance, managerScore}) PUT /business/projectBizProjectController.editbizProjectMapper.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) openUploadsubmitAnnouncement (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=XBizPublicityController.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.ratingTime Java 字段暴露, 但字段名误导: 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, 设计 OK
  • biz_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 / monitors JSON 列, 但这俩列从未建过

9.4 路由安全性

  • manager-projects-detail 路由 meta.role 无显式, 走全局登录态校验, 任何已登录角色都可访问 — 与 admin-projects 同款 (router/index.js:28 admin-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 全文 selectFieldspublish_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 selectFieldsLEFT 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_timepublish_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 / detail
  • ry-vue3/src/views/manager/ManagerProjectDetail.vue — 本审查对象 (459 行)
  • ry-vue3/src/views/manager/Projects.vue:424viewDetail (列表行入口)
  • ry-vue3/src/components/Preview.vue — 公告预览组件
  • ry-vue3/src/api/public.js:90-98bizGet/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/remove
  • ry-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 + BizProjectServiceImplgetById/updateByPrimaryKey
  • ry-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 + Impl
  • ry-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 + updateByPrimaryKey
  • ry-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 — DB 127.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 列, 未实现)