From b96161ecf4c0f5d808d9b8b6fdee09fc83103c12 Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Mon, 30 Mar 2026 15:29:38 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=E9=A3=8E=E9=99=A9=E6=80=BB?= =?UTF-8?q?=E8=A7=88=E4=BA=BA=E5=91=98=E5=AF=BC=E5=87=BA=E5=89=8D=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E5=AE=9E=E6=96=BD=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...sk-people-export-backend-implementation.md | 355 ++++++++++++++++++ ...k-people-export-frontend-implementation.md | 185 +++++++++ 2 files changed, 540 insertions(+) create mode 100644 docs/plans/backend/2026-03-30-project-detail-risk-people-export-backend-implementation.md create mode 100644 docs/plans/frontend/2026-03-30-project-detail-risk-people-export-frontend-implementation.md diff --git a/docs/plans/backend/2026-03-30-project-detail-risk-people-export-backend-implementation.md b/docs/plans/backend/2026-03-30-project-detail-risk-people-export-backend-implementation.md new file mode 100644 index 00000000..43a5b3f0 --- /dev/null +++ b/docs/plans/backend/2026-03-30-project-detail-risk-people-export-backend-implementation.md @@ -0,0 +1,355 @@ +# Project Detail Risk People Export Backend Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. +> +> **Repo note:** 本仓库 `AGENTS.md` 明确禁止开启 subagent,执行本计划时请在当前会话使用 `superpowers:executing-plans`。 + +**Goal:** 为项目详情“结果总览 -> 风险总览”人员列表补齐后端 Excel 导出能力,导出当前项目全部风险人员,并保证导出口径与页面表格字段一致。 + +**Architecture:** 后端继续沿用 `CcdiProjectOverviewController -> ICcdiProjectOverviewService -> CcdiProjectOverviewServiceImpl -> CcdiProjectOverviewMapper.xml` 这条结果总览链路,不新增平行模块或补丁接口。分页查询与导出查询共用同一组风险人员列映射与排序口径,服务层继续复用 `buildRiskPeopleItem(...)` 统一组装页面字段,再映射为导出 Excel 对象,避免页面与导出出现两套业务口径。 + +**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, MyBatis XML, ExcelUtil, JUnit 5, Mockito, Maven, Swagger/OpenAPI + +--- + +### Task 1: 建立风险人员导出契约 + +**Files:** +- Create: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiProjectRiskPeopleOverviewExcel.java` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java` +- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java` + +- [ ] **Step 1: 先写失败测试,锁定导出接口路径、Service 新签名和 Excel 对象列** + +```java +Method method = CcdiProjectOverviewController.class.getMethod( + "exportRiskPeople", + HttpServletResponse.class, + Long.class +); +assertEquals("/risk-people/export", method.getAnnotation(PostMapping.class).value()[0]); +``` + +```java +assertNotNull(ICcdiProjectOverviewService.class.getMethod("exportRiskPeopleOverview", Long.class)); +``` + +```java +assertNotNull(CcdiProjectRiskPeopleOverviewExcel.class.getDeclaredField("name")); +assertNotNull(CcdiProjectRiskPeopleOverviewExcel.class.getDeclaredField("riskPoint")); +``` + +- [ ] **Step 2: 运行控制器结构测试,确认当前仓库还没有风险人员导出入口** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectOverviewControllerTest test +``` + +Expected: + +- FAIL,提示 `exportRiskPeople` 或 `exportRiskPeopleOverview` 尚不存在 + +- [ ] **Step 3: 写最小导出契约** + +```java +@PostMapping("/risk-people/export") +@Operation(summary = "导出风险人员总览") +@PreAuthorize("@ss.hasPermi('ccdi:project:query')") +public void exportRiskPeople(HttpServletResponse response, Long projectId) { + List rows = overviewService.exportRiskPeopleOverview(projectId); + ExcelUtil util = + new ExcelUtil<>(CcdiProjectRiskPeopleOverviewExcel.class); + util.exportExcel(response, rows, "风险人员总览"); +} +``` + +```java +default List exportRiskPeopleOverview(Long projectId) { + return List.of(); +} +``` + +```java +@Excel(name = "姓名") +private String name; +``` + +- [ ] **Step 4: 回跑控制器结构测试,确认导出外壳建立完成** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectOverviewControllerTest test +``` + +Expected: + +- PASS +- 控制器测试能断言 `/risk-people/export` +- 权限仍为 `ccdi:project:query` + +- [ ] **Step 5: 提交本任务** + +```bash +git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/excel/CcdiProjectRiskPeopleOverviewExcel.java \ + ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectOverviewService.java \ + ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewController.java \ + ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java +git commit -m "补充风险总览人员导出接口契约" +``` + +### Task 2: 让分页查询与导出查询共用同一套风险人员口径 + +**Files:** +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java` +- Modify: `ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml` +- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapperSqlTest.java` + +- [ ] **Step 1: 先写失败测试,锁定导出查询、排序和共用 SQL 片段** + +```java +String xml = readMapperXml(); +assertTrue(xml.contains(""), xml); +assertTrue(xml.contains(""), xml); +assertTrue(xml.contains("selectRiskPeopleOverviewPage"), xml); +assertTrue(xml.contains("selectRiskPeopleOverviewList"), xml); +assertTrue(xml.contains("order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc"), xml); +``` + +- [ ] **Step 2: 运行 SQL 测试,确认当前 Mapper 还没有导出列表查询** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectOverviewMapperSqlTest test +``` + +Expected: + +- FAIL,提示缺少 `selectRiskPeopleOverviewList` +- FAIL,提示未抽出共用 SQL 片段 + +- [ ] **Step 3: 写最小共用查询结构** + +```java +List selectRiskPeopleOverviewList(@Param("projectId") Long projectId); +``` + +```xml + + result.staff_id_card, + result.staff_name, + result.dept_id, + result.dept_name, + result.rule_count, + result.model_count, + result.hit_count, + null as top_rule_code, + null as top_rule_name, + result.risk_point, + result.risk_level_code, + case + when result.risk_level_code = 'HIGH' then '高风险' + when result.risk_level_code = 'MEDIUM' then '中风险' + else '低风险' + end as risk_level_name, + case + when result.risk_level_code = 'HIGH' then 1 + when result.risk_level_code = 'MEDIUM' then 2 + else 3 + end as risk_level_sort + +``` + +```xml + + order by risk_level_sort asc, result.model_count desc, result.rule_count desc, result.staff_id_card asc + +``` + +- [ ] **Step 4: 回跑 SQL 测试,确认分页与导出将使用同一套排序和列映射** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectOverviewMapperSqlTest test +``` + +Expected: + +- PASS +- `selectRiskPeopleOverviewPage` 与 `selectRiskPeopleOverviewList` 共用列与排序片段 + +- [ ] **Step 5: 提交本任务** + +```bash +git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapper.java \ + ccdi-project/src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml \ + ccdi-project/src/test/java/com/ruoyi/ccdi/project/mapper/CcdiProjectOverviewMapperSqlTest.java +git commit -m "统一风险总览人员查询与导出口径" +``` + +### Task 3: 实现服务层导出映射并复用现有字段组装逻辑 + +**Files:** +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java` +- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java` + +- [ ] **Step 1: 先写失败测试,锁定“导出全部人员”和字段映射** + +```java +when(projectMapper.selectById(40L)).thenReturn(project); +when(overviewMapper.selectRiskPeopleOverviewList(40L)).thenReturn(List.of(aggregate)); +when(overviewMapper.selectRiskHitTagsByScope(40L, "330000000000000001", null)).thenReturn(List.of( + buildHitTag("LARGE_TRANSACTION", "大额交易模型", "RULE_A", "大额单笔收入", "HIGH"), + buildHitTag("PART_TIME", "兼职取酬模型", "RULE_B", "疑似兼职", "MEDIUM") +)); + +List rows = service.exportRiskPeopleOverview(40L); + +assertEquals(1, rows.size()); +assertEquals("李四", rows.getFirst().getName()); +assertEquals("高风险", rows.getFirst().getRiskLevel()); +assertEquals("大额单笔收入、疑似兼职", rows.getFirst().getRiskPoint()); +verify(overviewMapper).selectRiskPeopleOverviewList(40L); +``` + +- [ ] **Step 2: 运行 Service 测试,确认当前实现还没有导出映射** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest test +``` + +Expected: + +- FAIL,提示 `exportRiskPeopleOverview` 尚未实现 + +- [ ] **Step 3: 写最小服务实现** + +```java +@Override +public List exportRiskPeopleOverview(Long projectId) { + ensureProjectExists(projectId); + + return defaultList(overviewMapper.selectRiskPeopleOverviewList(projectId)).stream() + .map(aggregate -> buildRiskPeopleItem(projectId, aggregate)) + .map(this::buildRiskPeopleExcelRow) + .toList(); +} +``` + +```java +private CcdiProjectRiskPeopleOverviewExcel buildRiskPeopleExcelRow(CcdiProjectRiskPeopleOverviewItemVO item) { + CcdiProjectRiskPeopleOverviewExcel row = new CcdiProjectRiskPeopleOverviewExcel(); + row.setName(item.getName()); + row.setIdNo(item.getIdNo()); + row.setDepartment(item.getDepartment()); + row.setRiskCount(item.getRiskCount()); + row.setRiskLevel(item.getRiskLevel()); + row.setModelCount(item.getModelCount()); + row.setRiskPoint(item.getRiskPoint()); + return row; +} +``` + +- [ ] **Step 4: 回跑 Service 测试,确认导出复用了页面口径** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectOverviewServiceImplTest test +``` + +Expected: + +- PASS +- 测试明确断言导出走 `selectRiskPeopleOverviewList` +- 导出字段与页面表格字段一致 +- 项目不存在时继续抛 `ServiceException` + +- [ ] **Step 5: 提交本任务** + +```bash +git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImpl.java \ + ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectOverviewServiceImplTest.java +git commit -m "实现风险总览人员列表后端导出" +``` + +### Task 4: 补齐导出控制器回归与文档留痕 + +**Files:** +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java` +- Create: `docs/reports/implementation/2026-03-30-project-detail-risk-people-export-backend-record.md` +- Create: `docs/tests/records/2026-03-30-project-detail-risk-people-export-backend-verification.md` +- Verify: `docs/design/2026-03-30-project-detail-risk-people-export-design.md` + +- [ ] **Step 1: 补一条控制器导出回归测试** + +```java +MockHttpServletResponse response = new MockHttpServletResponse(); +when(overviewService.exportRiskPeopleOverview(40L)).thenReturn(List.of(new CcdiProjectRiskPeopleOverviewExcel())); + +controller.exportRiskPeople(response, 40L); + +verify(overviewService).exportRiskPeopleOverview(40L); +``` + +- [ ] **Step 2: 跑本次后端最小回归集** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectOverviewControllerTest,CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceImplTest test +``` + +Expected: + +- PASS + +- [ ] **Step 3: 记录测试结果** + +在 `docs/tests/records/2026-03-30-project-detail-risk-people-export-backend-verification.md` 记录: + +- 执行命令 +- 通过情况 +- 导出接口路径 +- 字段口径与页面一致的核对结论 + +- [ ] **Step 4: 记录实施内容** + +在 `docs/reports/implementation/2026-03-30-project-detail-risk-people-export-backend-record.md` 记录: + +- 新增导出接口与 Excel 对象 +- 复用 `buildRiskPeopleItem(...)` 的实现方式 +- 分页查询与导出查询共用排序/字段 SQL 片段 +- 未引入新的筛选、菜单或权限 + +- [ ] **Step 5: 提交本任务** + +```bash +git add ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectOverviewControllerTest.java \ + docs/reports/implementation/2026-03-30-project-detail-risk-people-export-backend-record.md \ + docs/tests/records/2026-03-30-project-detail-risk-people-export-backend-verification.md +git commit -m "补充风险总览人员导出后端记录" +``` + +### 最终验证 + +- [ ] 运行后端回归: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectOverviewControllerTest,CcdiProjectOverviewMapperSqlTest,CcdiProjectOverviewServiceImplTest test +``` + +- [ ] 确认以下结果: + - `POST /ccdi/project/overview/risk-people/export` 已接通 + - 导出范围为当前项目全部风险人员 + - 导出列仅包含页面展示列,不包含操作列 + - 导出字段顺序与设计文档一致 + +- [ ] 准备执行时,只暂存本次任务相关后端文件后再提交 diff --git a/docs/plans/frontend/2026-03-30-project-detail-risk-people-export-frontend-implementation.md b/docs/plans/frontend/2026-03-30-project-detail-risk-people-export-frontend-implementation.md new file mode 100644 index 00000000..4e581c62 --- /dev/null +++ b/docs/plans/frontend/2026-03-30-project-detail-risk-people-export-frontend-implementation.md @@ -0,0 +1,185 @@ +# Project Detail Risk People Export Frontend Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. +> +> **Repo note:** 本仓库 `AGENTS.md` 明确禁止开启 subagent,执行本计划时请在当前会话使用 `superpowers:executing-plans`。 + +**Goal:** 为项目详情“结果总览 -> 风险总览”人员列表的“导出”按钮接通真实下载动作,让前端按当前项目导出全部风险人员,同时不影响现有分页和“查看项目”行为。 + +**Architecture:** 前端继续使用 `PreliminaryCheck.vue -> RiskPeopleSection.vue` 现有结构,不新增导出弹窗、导出状态管理或 API 封装层。实现采用仓库现成的 `this.download(...)` 模式,直接在 `RiskPeopleSection.vue` 中根据 `projectId` 触发 `/ccdi/project/overview/risk-people/export` 下载,保持改动集中在结果总览人员组件和静态结构回归测试中。 + +**Tech Stack:** Vue 2, Element UI, SCSS, 若依前端 `download` 插件, Node `assert` 结构测试 + +--- + +### Task 1: 锁定风险人员导出按钮的前端契约 + +**Files:** +- Modify: `ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js` +- Create: `ruoyi-ui/tests/unit/preliminary-check-risk-people-export.test.js` + +- [ ] **Step 1: 先写失败测试,锁定导出方法、下载路径和文件名** + +```javascript +[ + "handleRiskPeopleExport", + "this.download(", + "ccdi/project/overview/risk-people/export", + "projectId: this.projectId", + "风险人员总览_", +].forEach((token) => assert(source.includes(token), token)); +``` + +```javascript +assert(source.includes("@click=\"handleRiskPeopleExport\""), "导出按钮必须绑定导出事件"); +``` + +- [ ] **Step 2: 运行前端结构测试,确认当前按钮还只是静态文案** + +Run: + +```bash +node ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js +node ruoyi-ui/tests/unit/preliminary-check-risk-people-export.test.js +``` + +Expected: + +- FAIL,提示缺少 `handleRiskPeopleExport` +- FAIL,提示“导出”按钮尚未绑定 `@click` + +- [ ] **Step 3: 提交当前失败测试基线到工作区,不提交 Git** + +工作区保持红灯,继续下一任务实现,不要在此步提前提交。 + +### Task 2: 在 `RiskPeopleSection.vue` 中接通最短路径导出 + +**Files:** +- Modify: `ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue` +- Modify: `ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js` +- Modify: `ruoyi-ui/tests/unit/preliminary-check-risk-people-export.test.js` + +- [ ] **Step 1: 写最小实现,只在组件内接通下载** + +```vue +导出 +``` + +```javascript +handleRiskPeopleExport() { + if (!this.projectId) { + return; + } + this.download( + "ccdi/project/overview/risk-people/export", + { + projectId: this.projectId, + }, + `风险人员总览_${this.projectId}_${new Date().getTime()}.xlsx` + ); +}, +``` + +- [ ] **Step 2: 保持现有分页与查看项目逻辑不变** + +重点确认实现时不要改动: + +- `loadRiskPeoplePage` +- `handlePageChange` +- `handleViewProject` +- `sectionData -> localRows` 的 watch 逻辑 + +- [ ] **Step 3: 跑前端结构回归测试** + +Run: + +```bash +node ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js +node ruoyi-ui/tests/unit/preliminary-check-risk-people-export.test.js +node ruoyi-ui/tests/unit/preliminary-check-risk-people-pagination.test.js +node ruoyi-ui/tests/unit/preliminary-check-project-analysis-entry.test.js +``` + +Expected: + +- PASS +- 导出按钮已接通 +- 分页与“查看项目”相关断言不受影响 + +- [ ] **Step 4: 提交本任务** + +```bash +git add ruoyi-ui/src/views/ccdiProject/components/detail/RiskPeopleSection.vue \ + ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js \ + ruoyi-ui/tests/unit/preliminary-check-risk-people-export.test.js \ + ruoyi-ui/tests/unit/preliminary-check-risk-people-pagination.test.js \ + ruoyi-ui/tests/unit/preliminary-check-project-analysis-entry.test.js +git commit -m "接通风险总览人员列表前端导出" +``` + +### Task 3: 补齐前端验证记录与实施留痕 + +**Files:** +- Create: `docs/reports/implementation/2026-03-30-project-detail-risk-people-export-frontend-record.md` +- Create: `docs/tests/records/2026-03-30-project-detail-risk-people-export-frontend-verification.md` +- Verify: `docs/design/2026-03-30-project-detail-risk-people-export-design.md` + +- [ ] **Step 1: 跑本次前端最小回归集** + +Run: + +```bash +node ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js +node ruoyi-ui/tests/unit/preliminary-check-risk-people-export.test.js +node ruoyi-ui/tests/unit/preliminary-check-risk-people-pagination.test.js +node ruoyi-ui/tests/unit/preliminary-check-project-analysis-entry.test.js +``` + +Expected: + +- PASS + +- [ ] **Step 2: 记录测试结果** + +在 `docs/tests/records/2026-03-30-project-detail-risk-people-export-frontend-verification.md` 记录: + +- 执行命令 +- 通过情况 +- 按钮下载路径 +- 分页与查看项目未受影响的结论 + +- [ ] **Step 3: 记录实施内容** + +在 `docs/reports/implementation/2026-03-30-project-detail-risk-people-export-frontend-record.md` 记录: + +- 为什么直接使用 `this.download(...)` +- 为什么不额外新增 `projectOverview.js` 导出封装 +- 本次只修改 `RiskPeopleSection.vue` +- 文件名和下载参数口径 + +- [ ] **Step 4: 提交本任务** + +```bash +git add docs/reports/implementation/2026-03-30-project-detail-risk-people-export-frontend-record.md \ + docs/tests/records/2026-03-30-project-detail-risk-people-export-frontend-verification.md +git commit -m "补充风险总览人员导出前端记录" +``` + +### 最终验证 + +- [ ] 运行前端回归: + +```bash +node ruoyi-ui/tests/unit/preliminary-check-risk-people-binding.test.js +node ruoyi-ui/tests/unit/preliminary-check-risk-people-export.test.js +node ruoyi-ui/tests/unit/preliminary-check-risk-people-pagination.test.js +node ruoyi-ui/tests/unit/preliminary-check-project-analysis-entry.test.js +``` + +- [ ] 确认以下结果: + - `RiskPeopleSection.vue` 的“导出”按钮已接通真实下载动作 + - 下载参数仅包含 `projectId` + - 当前分页不会影响导出范围 + - “查看项目”和分页逻辑仍正常 + +- [ ] 准备执行时,只暂存本次任务相关前端文件后再提交