Files
guoju0808/_self/manager_projects.md
T
郭庆泰andClaude d1c755971d docs: 加 _self/manager_projects_assign.md + manager_projects 修复记录
- manager_projects_assign.md: 新增 11 章端到端审查报告 (4 跳定位 + Entity 一致性 + 索引 + 原型对比 + 待修复 16 条)
- manager_projects.md: 修复记录表加 2026-08-20 P1/P2/P3 修复条目 (与上面 commit 一致)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-20 23:31:41 +08:00

36 KiB
Raw Blame History

修复记录

日期 章节 修复内容
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/listBizProjectController.listBizProjectService.selectList biz_project
单条/批量结题 openClose / openBatch('close') PUT /business/projectbizProjectService.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 / exportEvalElMessage.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 BizProjectServiceselectList 无 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):

SELECT p.project_id, p.project_no, p.project_name, p.total_sessions, p.done_sessions, p.todo_sessions,
       p.total_amount, p.available_amount, p.paid_labor_amount, p.paid_meeting_amount,
       p.manager_score, p.sponsor_score, p.sponsor_admin_user_name, p.project_form, p.is_finished, p.is_settled,
       p.sponsor_admin_user_id, p.lead_user_id, p.is_bid_project,
       p.create_by, p.create_user_id, p.create_time, p.update_by, p.update_time, p.manage_fee,
       p.start_time, p.end_time, p.submit_deadline_days,
       p.support_contract_url, p.execute_contract_url, p.invitation_url, p.support_letter_url, p.publish_url,
       o.org_name as sponsor_org_name,        -- LEFT JOIN biz_org (sponsor)
       lu.user_name as lead_user_name,        -- LEFT JOIN sys_user (project manager)
       bp.name as create_user_name,           -- LEFT JOIN biz_person (creator)
       (SELECT GROUP_CONCAT(DISTINCT o2.org_name SEPARATOR ',')
        FROM biz_project_assign bpa
        JOIN biz_org o2 ON o2.org_id = bpa.execution_unit_id AND o2.org_type = 'executor'
        WHERE bpa.project_id = p.project_id) AS exec_org_names
FROM biz_project p
LEFT JOIN biz_org o ON o.user_id = p.sponsor_admin_user_id AND o.org_type = 'sponsor'
LEFT JOIN sys_user lu ON lu.user_id = p.lead_user_id
LEFT JOIN biz_person bp ON bp.user_id = p.create_user_id

WHERE
  [projectIds IN (...)        if params.projectIds]      -- 预留, manager 端没传
  AND [projectNos IN (...)    if params.projectNos]      -- 预留, manager 端没传
  AND [project_no LIKE ?      if q.projectNo]
  AND [project_name LIKE ?    if q.projectName]
  AND [project_form = ?       if q.projectForm]
  AND [sponsor_admin_user_id IN (...) if q.sponsorAdminUserIds]   -- 多选 IN
  AND [EXISTS (bpa WHERE exec_user_id IN (...)) if q.execAdminUserIds]  -- 多选 EXISTS+IN
  AND [is_finished = ?        if q.isFinished]
  AND [is_settled = ?         if q.isSettled]
  AND [manager_score = ?      if q.managerScore]          -- 88df386 新加
ORDER BY project_id DESC

4.2 列映射

列名 row.* 表.字段 原型对照
选择 (el-table-column type="selection")
项目编号 row.projectNo biz_project.project_no
项目名称 row.projectName biz_project.project_name
总场次/总期数 row.totalSessions biz_project.total_sessions
已执行 row.doneSessions biz_project.done_sessions
未执行 row.todoSessions biz_project.todo_sessions
总金额 row.totalAmount biz_project.total_amount
可用金额 row.availableAmount biz_project.available_amount
已支付劳务费 row.paidLaborAmount biz_project.paid_labor_amount
已支付会务费 row.paidMeetingAmount biz_project.paid_meeting_amount
执行单位评分(合规) row.managerScore biz_project.manager_score
执行单位评分(支持) row.sponsorScore biz_project.sponsor_score
支持单位 row.sponsorOrgName biz_org.org_name (JOIN)
服务机构 row.execOrgNames GROUP_CONCAT of biz_org.org_name
项目形式 row.projectForm biz_project.project_form
是否结题 row.isFinished (tag 化) biz_project.is_finished
项目开始时间 row.startTime biz_project.start_time
项目结束时间 row.endTime biz_project.end_time
操作 (dropdown 菜单)

5. 行内操作按钮 (op-cell)

按钮 触发函数 接口 后端动作 涉及表.字段 原型对照
查看 viewDetail (router.push)
编辑 doEdit (router.push)
更多 dropdown onAction (原型无, 折叠菜单扩展)
└ 建会 doCreateMeeting (router.push)
└ 结题 openClose + confirmClose PUT /business/project {projectId, isFinished: '1'} bizProjectService.updateByPrimaryKey biz_project.is_finished='1'
└ 项目分配 doAssign 同 toolbar openAssign (原型叫"分配", Vue 改名)
└ 执行单位评分 openSingleScore + confirmSingleScore PUT /business/project {managerScore} + POST /business/project/rate bizProjectRatingService.upsertRating biz_project.manager_score + biz_project_rating (新行或更新)
└ 导出报名专家 (divided) exportExperts POST /business/executionIntent/export?projectNo=... (单独执行方意图表) biz_execution_intent (原型是顶部按钮, Vue 挪到行内)
└ 开通 (divided, 条件) openActivate + confirmActivate PUT /business/project {projectId, isFinished: '0'} 同上 biz_project.is_finished='0'
└ 删除公告 (divided, 条件) openDeleteAnnouncement + confirmDeleteAnn PUT /business/project 清空 3 个 URL 同上 biz_project.{publish_url,invitation_url,support_letter_url}='\0'

条件可见:

  • 开通 / 删除公告: v-if="row.isPublished === '1' || row.publishUrl" (L132-133)
  • 结题: v-if="row.isFinished !== '1' && row.isFinished !== 1" (L128)

下拉菜单 vs 全部展开: 原型 8 个按钮一字排开 (.col-op width: 420px), Vue 折叠成 "查看 / 编辑" + "更多" dropdown, 列宽 180px。屏幕利用率更好, 但每行多一次点击展开。


6. Java Entity ↔ 数据库表 一致性

6.1 biz_project DDL (实测 2026-08-20)

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:361ElMessage.info('开发中') P2 stub
顶部"项目评价导出"按钮 project-manage.html:136 Projects.vue:362ElMessage.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):

ALTER TABLE biz_project_assign ADD INDEX idx_assign_exec_user_project (exec_user_id, project_id);

理由: Q4/Q6/Q7 (服务机机构 EXISTS+IN) 原命中 idx_assign_exec_user(exec_user_id) 单列索引, 需回表读 project_id; 新加复合索引直接 covering, EXPLAIN 显示 Using index + LooseScan/range, 无需回表。

评估后未加的索引 (记录备查):

  • biz_org (org_type, org_id, org_name) — 用于消除 options 查询的 filesort。 评估: 强制 hint 后确实变 Backward index scan 无 filesort, 但优化器在 33 行数据下仍选 idx_org_type_status (2 列更小, 估算成本更低)。 决策: 数据量小不需要, DROP, 等生产数据增长到 1k+ 行再 EVAL。

过时注释修正 (2026-08-20):

  • BizOrgMapper.xml 旧注释写 "走 idx_org_user_type_name 索引", EXPLAIN 实际命中 idx_org_type_status, 已修正为实测索引 + Using where 描述

10. 待修复列表 (按优先级)

# 问题 文件:行 修复建议 严重度
1 managerScore 筛选前端有,后端 selectList 不读 BizProjectMapper.xml:141-183 (selectList) 已修 (88df386,见 §修复记录)
2 biz_project 缺索引 sponsor_admin_user_id DB 已修 (见 §修复记录)
3 biz_project 缺索引 start_time, end_time (范围查询) DB 已修 (见 §修复记录)
4 biz_project_assign 缺 UNIQUE (project_id, execution_unit_id) DB 已修 (见 §修复记录)
5 生产代码残留 console.info/error (4 处) Projects.vue:299, 302, 306, 875 已修 (见 §修复记录)
6 日期选择器 value-format="YYYY-MM-DD HH:mm:ss"type="date" 不匹配 Projects.vue:17, 19 已修 (见 §修复记录)
7 batchScoreRows 死代码 Projects.vue:501 已修 (见 §修复记录)
8 下拉菜单 divided 冗余 (activate/delAnn 双分隔线) Projects.vue:132-133 activatedivided 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_finishedis_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.jsbizList/bizAdd/bizUpdate/bizDelete
  • ry-vue3/src/api/system.jslistSponsorOrgs/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 原型