refactor(biz/project): 修实体类型不一致 projectId String→Long + startTime/endTime String→Date
- BizProject.projectId String→Long (9 文件: domain / IBizProjectService / BizProjectServiceImpl / BizProjectMapper.java+xml / BizProjectController / BizExecutionIntentController / BizPublicityIntentController / BizPublicController). 删 SnowflakeId.injectIfEmpty (Long setter 不匹配反射 NoSuchMethodException, biz_project 用 AUTO_INCREMENT 安全) - BizProject.startTime/endTime String→Date, 加 @JsonFormat yyyy-MM-dd HH:mm:ss (跟 BizMeeting 一致) - Mapper XML 8 处 <if test='x != null and x != ""'> → <if test='x != null'> (Date 无 isEmpty) - _self/manager_projects.md: §6.3 + §9.1 标已修, 加修复记录章节
This commit is contained in:
@@ -0,0 +1,442 @@
|
||||
# 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" 双向兼容, 零前端改动。 |
|
||||
|
||||
---
|
||||
|
||||
## 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`)
|
||||
|
||||
```sql
|
||||
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_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,
|
||||
lu.user_name as lead_user_name,
|
||||
(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
|
||||
[WHERE 动态过滤...]
|
||||
```
|
||||
|
||||
### 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 (实测)
|
||||
|
||||
```sql
|
||||
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 | ✅ 是否结算过滤 |
|
||||
| ❌ **缺** | sponsor_admin_user_id | ⚠️ `selectSponsorList` WHERE `sponsor_admin_user_id = ?` |
|
||||
| ❌ **缺** | lead_user_id | ⚠️ 主表没用,但 JOIN sys_user 用 |
|
||||
| ❌ **缺** | start_time / end_time | ⚠️ 范围查询 `>= ? AND <= ?` |
|
||||
|
||||
### 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 | ✅ |
|
||||
| ❌ **缺** | UNIQUE (project_id, execution_unit_id) | ⚠️ **业务上** "一个项目 + 一个执行方" 应唯一,否则可能重复分配 |
|
||||
|
||||
### 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 表索引够用但**不够优化**(4 个 WHERE 列无索引),assign 表缺唯一约束
|
||||
|
||||
---
|
||||
|
||||
## 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` | 全删 | **P1** |
|
||||
| 6 | 评分默认值不一致 | `Projects.vue:502 vs 514` | 统一为 0 或 5 | **P2** |
|
||||
| 7 | `batchScoreRows` 死代码 | `Projects.vue:501` | 删 | **P2** |
|
||||
| 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` | 改 `type="datetime"` 或 value-format 用 `YYYY-MM-DD` | **P1** |
|
||||
| 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` | 加 `idx_sponsor_admin_user_id` | **P1** |
|
||||
| 13 | biz_project 缺索引 `start_time`, `end_time` (范围查询) | `DB` | 加 `idx_start_time`, `idx_end_time` 或组合索引 | **P2** |
|
||||
| 14 | biz_project_assign 缺 UNIQUE (project_id, execution_unit_id) | `DB` | 加唯一约束防重复分配 | **P1** |
|
||||
| 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 索引)
|
||||
Reference in New Issue
Block a user