diff --git a/docs/plans/backend/2026-03-24-project-archive-backend-implementation.md b/docs/plans/backend/2026-03-24-project-archive-backend-implementation.md new file mode 100644 index 00000000..ce2fbdb0 --- /dev/null +++ b/docs/plans/backend/2026-03-24-project-archive-backend-implementation.md @@ -0,0 +1,311 @@ +# Project Archive 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. + +**Goal:** 为项目管理列表补齐真实归档后端链路,并在服务层封住“已归档项目仍可上传数据或修改参数”的写入口。 + +**Architecture:** 后端采用最短路径实现,在现有 `CcdiProjectController -> ICcdiProjectService -> CcdiProjectServiceImpl` 链路中新增专用归档动作,不复用通用更新接口。归档成功后统一写入 `status=2`、`isArchived=1`,同时将“已归档不可写”收敛到项目服务校验层,再复用到上传与参数保存服务,保证页面限制和接口限制一致。 + +**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, JUnit 5, Mockito, Maven + +## 后端验收清单 + +- `POST /ccdi/project/{projectId}/archive` 可成功归档已完成项目 +- 归档后项目写入 `status = "2"` 且 `isArchived = 1` +- 非 `已完成` 状态项目调用归档接口会被拒绝 +- 已归档项目调用上传或参数保存相关后端入口会被拒绝 + +--- + +### Task 1: 先用测试锁定归档状态流转和写保护规则 + +**Files:** +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java` +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java` +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImplTest.java` + +- [ ] **Step 1: Write the failing service test for archive success** + +在 `CcdiProjectServiceImplTest.java` 中新增归档成功用例,至少锁定以下结果: + +```java +@Test +void shouldArchiveCompletedProject() { + CcdiProject project = new CcdiProject(); + project.setProjectId(40L); + project.setProjectName("专案A"); + project.setStatus("1"); + project.setIsArchived(0); + when(projectMapper.selectById(40L)).thenReturn(project); + + service.archiveProject(40L, "tester"); + + assertEquals("2", project.getStatus()); + assertEquals(1, project.getIsArchived()); +} +``` + +- [ ] **Step 2: Extend the failing test for rejected states** + +继续补两个失败场景: + +```java +assertThrows(ServiceException.class, () -> service.archiveProject(41L, "tester")); +assertThrows(ServiceException.class, () -> service.ensureProjectNotArchived(42L, "已归档项目暂不允许修改参数")); +``` + +覆盖目标: + +- 进行中 / 打标中 / 已归档项目不允许归档 +- 已归档项目会被项目服务层写保护拦截 + +- [ ] **Step 3: Run service tests to verify they fail first** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest,CcdiFileUploadServiceImplTest,CcdiModelParamServiceImplTest test +``` + +Expected: + +- `FAIL` +- 原因包括 `archiveProject` / `ensureProjectNotArchived` 尚未实现,或现有测试断言不满足 + +- [ ] **Step 4: Add minimal upload and param guard expectations** + +在 `CcdiFileUploadServiceImplTest.java` 和 `CcdiModelParamServiceImplTest.java` 中补充 mock 校验,锁定归档保护会被调用,例如: + +```java +verify(projectService).ensureProjectNotArchived(PROJECT_ID, "已归档项目暂不允许上传或拉取数据"); +verify(projectService).ensureProjectNotArchived(40L, "已归档项目暂不允许修改参数"); +``` + +- [ ] **Step 5: Commit** + +```bash +git add ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImplTest.java +git commit -m "补充项目归档后端测试约束" +``` + +### Task 2: 实现项目归档服务与接口 + +**Files:** +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectService.java` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectController.java` + +- [ ] **Step 1: Add the failing controller contract test** + +新建或补充控制器测试,锁定归档接口契约: + +```java +Method method = CcdiProjectController.class.getMethod("archiveProject", Long.class); +PostMapping postMapping = method.getAnnotation(PostMapping.class); +PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class); + +assertEquals("/{projectId}/archive", postMapping.value()[0]); +assertEquals("@ss.hasPermi('ccdi:project:edit')", preAuthorize.value()); +``` + +建议新增文件: + +- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerTest.java` +- `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerContractTest.java` + +- [ ] **Step 2: Run controller tests to verify they fail** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectControllerTest,CcdiProjectControllerContractTest test +``` + +Expected: + +- `FAIL` +- 原因是当前 `CcdiProjectController` 尚无归档方法 + +- [ ] **Step 3: Implement the minimal archive action** + +在 `ICcdiProjectService.java` 增加: + +```java +void archiveProject(Long projectId, String operator); +void ensureProjectNotArchived(Long projectId, String message); +``` + +在 `CcdiProjectServiceImpl.java` 实现: + +```java +@Override +public void archiveProject(Long projectId, String operator) { + CcdiProject project = getRequiredProject(projectId); + if (!CcdiProjectStatusConstants.COMPLETED.equals(project.getStatus())) { + throw new ServiceException("仅已完成项目允许归档"); + } + project.setStatus(CcdiProjectStatusConstants.ARCHIVED); + project.setIsArchived(1); + project.setUpdateBy(operator); + project.setUpdateTime(new Date()); + projectMapper.updateById(project); +} +``` + +并在 `CcdiProjectController.java` 增加: + +```java +@PostMapping("/{projectId}/archive") +@Operation(summary = "归档项目") +@PreAuthorize("@ss.hasPermi('ccdi:project:edit')") +public AjaxResult archiveProject(@PathVariable Long projectId) { + projectService.archiveProject(projectId, SecurityUtils.getUsername()); + return AjaxResult.success("项目归档成功"); +} +``` + +约束: + +- 不新增 DTO +- 不在控制器里直接写数据库更新逻辑 +- 不引入“取消归档”“恢复归档”分支 + +- [ ] **Step 4: Run tests to verify the archive chain passes** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest,CcdiProjectControllerTest,CcdiProjectControllerContractTest test +``` + +Expected: + +- `PASS` +- 能证明归档接口、服务状态流转和权限注解均已到位 + +- [ ] **Step 5: Commit** + +```bash +git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiProjectService.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiProjectController.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiProjectControllerContractTest.java +git commit -m "实现项目归档后端接口" +``` + +### Task 3: 将归档写保护接入上传与参数保存链路 + +**Files:** +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java` +- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java` +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java` +- Modify: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImplTest.java` + +- [ ] **Step 1: Add failing assertions for archive guard calls** + +先在两组测试里锁定新增校验调用顺序: + +```java +verify(projectService).ensureProjectNotArchived(PROJECT_ID, "已归档项目暂不允许上传或拉取数据"); +verify(projectService).ensureProjectWritable(PROJECT_ID, "当前项目正在进行银行流水打标,暂不允许上传或拉取数据"); +``` + +以及: + +```java +verify(projectService).ensureProjectNotArchived(40L, "已归档项目暂不允许修改参数"); +verify(projectService).ensureProjectWritable(40L, "当前项目正在进行银行流水打标,暂不允许修改参数"); +``` + +- [ ] **Step 2: Run the targeted tests to verify failure** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiFileUploadServiceImplTest,CcdiModelParamServiceImplTest test +``` + +Expected: + +- `FAIL` +- 原因是现有实现尚未调用 `ensureProjectNotArchived` + +- [ ] **Step 3: Implement the minimal service guard** + +在 `CcdiFileUploadServiceImpl.java` 和 `CcdiModelParamServiceImpl.java` 中,在现有 `ensureProjectWritable(...)` 前增加: + +```java +projectService.ensureProjectNotArchived(projectId, "已归档项目暂不允许上传或拉取数据"); +projectService.ensureProjectNotArchived(projectId, "已归档项目暂不允许修改参数"); +``` + +约束: + +- 不改现有业务主流程 +- 不改上传、拉取、参数保存的入参与返回结构 +- 只补充状态校验 + +- [ ] **Step 4: Run the full backend regression set** + +Run: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest,CcdiProjectControllerTest,CcdiProjectControllerContractTest,CcdiFileUploadServiceImplTest,CcdiModelParamServiceImplTest test +``` + +Expected: + +- `PASS` +- 能证明归档接口和写保护闭环同时成立 + +- [ ] **Step 5: Commit** + +```bash +git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiModelParamServiceImplTest.java +git commit -m "补充项目归档后端写保护" +``` + +### Task 4: 沉淀后端实施与验证记录 + +**Files:** +- Create: `docs/tests/records/2026-03-24-project-archive-backend-verification.md` +- Create: `docs/reports/implementation/2026-03-24-project-archive-backend-record.md` + +- [ ] **Step 1: Write the verification skeleton** + +在验证记录中至少写明: + +```markdown +# 项目归档后端验证记录 + +## 验证范围 +- 归档接口仅允许已完成项目调用 +- 归档成功后状态和 isArchived 正确落库 +- 已归档项目后端禁止上传或拉取数据 +- 已归档项目后端禁止修改参数 +``` + +- [ ] **Step 2: Record exact commands and results** + +把实际执行命令与结果写入文档: + +```bash +mvn -pl ccdi-project -Dtest=CcdiProjectServiceImplTest,CcdiProjectControllerTest,CcdiProjectControllerContractTest,CcdiFileUploadServiceImplTest,CcdiModelParamServiceImplTest test +``` + +Expected: + +- 记录通过结果,或失败时记录具体原因 + +- [ ] **Step 3: Write the backend implementation record** + +在实施记录中说明: + +- 为什么采用专用归档接口而不是复用更新接口 +- 为什么需要把“已归档不可写”下沉到服务层 +- 本次改动影响了哪些后端入口 +- 已完成哪些测试验证 + +- [ ] **Step 4: Commit** + +```bash +git add docs/tests/records/2026-03-24-project-archive-backend-verification.md docs/reports/implementation/2026-03-24-project-archive-backend-record.md +git commit -m "补充项目归档后端实施记录" +``` diff --git a/docs/plans/frontend/2026-03-24-project-archive-frontend-implementation.md b/docs/plans/frontend/2026-03-24-project-archive-frontend-implementation.md new file mode 100644 index 00000000..5e029547 --- /dev/null +++ b/docs/plans/frontend/2026-03-24-project-archive-frontend-implementation.md @@ -0,0 +1,352 @@ +# Project Archive 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. + +**Goal:** 让项目管理列表中的“归档”按钮真正触发归档,并让已归档项目在详情页中无法点击“上传数据”“参数配置”页签。 + +**Architecture:** 前端复用现有 `ruoyi-ui/src/api/ccdiProject.js` 中已预留的归档接口,在列表页 `index.vue` 接入真实异步提交,并收敛 `ArchiveConfirmDialog.vue` 的超范围文案。详情页 `detail.vue` 负责归档态页签禁用和 URL 直达拦截,`UploadData.vue` 与 `ParamConfig.vue` 再补一层归档态禁用保护,确保页面状态和组件行为一致。 + +**Tech Stack:** Vue 2, Element UI, Axios request wrapper, Node, npm + +## 前端验收清单 + +- 列表页点击“归档”后会调用真实归档接口 +- 归档成功后关闭弹窗并刷新列表 +- 已归档项目在详情页中“上传数据”“参数配置”页签不可点击 +- 直接访问 `?tab=upload` / `?tab=config` 时会自动切到 `overview` +- 归档确认弹窗不再出现删数据、生成 PDF、归档库等超需求文案 + +--- + +### Task 1: 先锁定列表页归档动作与弹窗文案契约 + +**Files:** +- Modify: `ruoyi-ui/tests/unit/project-list-archive-flow.test.js` +- Modify: `ruoyi-ui/src/views/ccdiProject/index.vue` +- Modify: `ruoyi-ui/src/views/ccdiProject/components/ArchiveConfirmDialog.vue` + +- [ ] **Step 1: Write the failing source-based test** + +新增 `ruoyi-ui/tests/unit/project-list-archive-flow.test.js`,至少锁定以下行为: + +```javascript +assert( + pageSource.includes("await archiveProject(data.projectId)"), + "确认归档后应调用真实归档接口" +); + +assert( + pageSource.includes("this.$modal.msgSuccess(\"项目归档成功\")") || + pageSource.includes("this.$modal.msgSuccess('项目归档成功')"), + "归档成功后应提示项目归档成功" +); + +assert( + !dialogSource.includes("自动生成项目报告PDF"), + "归档弹窗不应保留超范围 PDF 文案" +); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/project-list-archive-flow.test.js +``` + +Expected: + +- `FAIL` +- 原因是当前 `handleConfirmArchive` 仍然只有本地提示,弹窗还保留旧文案 + +- [ ] **Step 3: Implement the minimal list action and dialog cleanup** + +在 `ruoyi-ui/src/views/ccdiProject/index.vue` 中: + +1. 从 `@/api/ccdiProject` 引入 `archiveProject` +2. 将 `handleConfirmArchive(data)` 改为真实异步提交: + +```javascript +async handleConfirmArchive(data) { + try { + await archiveProject(data.projectId) + this.$modal.msgSuccess("项目归档成功") + this.archiveDialogVisible = false + this.currentArchiveProject = null + this.getList() + } catch (error) { + const message = error && error.message ? error.message : "项目归档失败,请稍后重试" + this.$modal.msgError(message) + } +} +``` + +在 `ArchiveConfirmDialog.vue` 中: + +- 删除 `deleteData` +- 删除“自动生成项目报告PDF”“归档库”“恢复”“同时删除数据”等文案和交互 +- 仅保留“确认后状态变更为已归档”的说明和 loading 提交态 + +- [ ] **Step 4: Run test to verify it passes** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/project-list-archive-flow.test.js +``` + +Expected: + +- `PASS` + +- [ ] **Step 5: Commit** + +```bash +git add ruoyi-ui/src/views/ccdiProject/index.vue ruoyi-ui/src/views/ccdiProject/components/ArchiveConfirmDialog.vue ruoyi-ui/tests/unit/project-list-archive-flow.test.js +git commit -m "实现项目列表归档前端交互" +``` + +### Task 2: 实现详情页签禁用与 URL 直达拦截 + +**Files:** +- Modify: `ruoyi-ui/src/views/ccdiProject/detail.vue` +- Modify: `ruoyi-ui/tests/unit/project-detail-archive-tab-lock.test.js` + +- [ ] **Step 1: Write the failing detail-page test** + +新增 `ruoyi-ui/tests/unit/project-detail-archive-tab-lock.test.js`,锁定以下关键代码痕迹: + +```javascript +assert( + source.includes('index="upload"') && source.includes(':disabled="isArchiveLockedTab(\'upload\')"'), + "上传数据页签应支持归档态禁用" +); + +assert( + /initActiveTabFromRoute\(\)\s*\{[\s\S]*?this\.resolveAccessibleTab/.test(source), + "详情页应在路由初始化时校正归档态不可访问页签" +); + +assert( + /handleMenuSelect\(index\)\s*\{[\s\S]*?if\s*\(this\.isArchiveLockedTab\(index\)\)\s*return/.test(source), + "点击禁用页签时不应切换" +); +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/project-detail-archive-tab-lock.test.js +``` + +Expected: + +- `FAIL` +- 原因是当前详情页还没有归档态页签禁用逻辑 + +- [ ] **Step 3: Implement the minimal tab lock** + +在 `ruoyi-ui/src/views/ccdiProject/detail.vue` 中增加: + +```javascript +computed: { + isProjectArchived() { + return String(this.projectInfo.projectStatus) === "2"; + } +} +``` + +以及两个辅助方法: + +```javascript +isArchiveLockedTab(tab) { + return this.isProjectArchived && ["upload", "config"].includes(tab); +}, +resolveAccessibleTab(tab) { + if (this.isArchiveLockedTab(tab)) { + return "overview"; + } + return tab; +} +``` + +并修改: + +- `initActiveTabFromRoute()`:先取 tab,再调用 `resolveAccessibleTab(tab)` +- `handleMenuSelect(index)`:若禁用则直接 `return` +- 顶部两个 `el-menu-item`:加 `:disabled="isArchiveLockedTab('upload')"` 和 `:disabled="isArchiveLockedTab('config')"` + +- [ ] **Step 4: Run tests to verify the tab lock passes** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/project-detail-archive-tab-lock.test.js +node tests/unit/project-detail-tagging-polling.test.js +``` + +Expected: + +- 两条测试都 `PASS` +- 说明归档页签禁用未破坏现有打标轮询结构 + +- [ ] **Step 5: Commit** + +```bash +git add ruoyi-ui/src/views/ccdiProject/detail.vue ruoyi-ui/tests/unit/project-detail-archive-tab-lock.test.js +git commit -m "实现项目详情归档页签禁用" +``` + +### Task 3: 给上传页和参数页补一层归档态保护 + +**Files:** +- Modify: `ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue` +- Modify: `ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue` +- Modify: `ruoyi-ui/tests/unit/upload-data-disabled-cards.test.js` +- Create: `ruoyi-ui/tests/unit/project-archive-readonly-guard.test.js` + +- [ ] **Step 1: Extend the failing tests for archive guard** + +在 `upload-data-disabled-cards.test.js` 中补充归档态断言: + +```javascript +assert( + /disabled:\s*this\.isProjectTagging\s*\|\|\s*this\.isProjectArchived/.test(source), + "流水导入卡片应在项目已归档时同步置灰" +); +``` + +再新增 `project-archive-readonly-guard.test.js` 锁定: + +```javascript +assert(uploadSource.includes('return String(this.projectInfo.projectStatus) === "2"')); +assert(paramSource.includes('return String(this.projectInfo.projectStatus) === "2"')); +assert(paramSource.includes("已归档项目暂不可修改参数")); +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/upload-data-disabled-cards.test.js +node tests/unit/project-archive-readonly-guard.test.js +``` + +Expected: + +- `FAIL` +- 原因是当前组件只处理“打标中”禁用 + +- [ ] **Step 3: Implement the minimal archived guard** + +在 `UploadData.vue` 中补充: + +```javascript +isProjectArchived() { + return String(this.projectInfo.projectStatus) === "2"; +} +``` + +并把以下禁用逻辑扩展为归档态也成立: + +- 顶部“拉取本行信息”“征信导入” +- 流水上传卡片 `disabled` +- 相关上传/拉取方法中的前置 `return` + +在 `ParamConfig.vue` 中补充: + +```javascript +isProjectArchived() { + return String(this.projectInfo.projectStatus) === "2"; +} +``` + +并同步修改: + +- 顶部提示文案:归档态显示“已归档项目暂不可修改参数” +- 输入框和保存按钮禁用条件改为 `isProjectTagging || isProjectArchived` +- `handleSaveAll()` 中增加归档态前置拦截 + +- [ ] **Step 4: Run frontend regression tests** + +Run: + +```bash +cd ruoyi-ui +node tests/unit/project-list-archive-flow.test.js +node tests/unit/project-detail-archive-tab-lock.test.js +node tests/unit/upload-data-disabled-cards.test.js +node tests/unit/project-archive-readonly-guard.test.js +``` + +Expected: + +- 全部 `PASS` + +- [ ] **Step 5: Commit** + +```bash +git add ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue ruoyi-ui/src/views/ccdiProject/components/detail/ParamConfig.vue ruoyi-ui/tests/unit/upload-data-disabled-cards.test.js ruoyi-ui/tests/unit/project-archive-readonly-guard.test.js +git commit -m "补充项目归档前端只读保护" +``` + +### Task 4: 沉淀前端实施与验证记录 + +**Files:** +- Create: `docs/tests/records/2026-03-24-project-archive-frontend-verification.md` +- Create: `docs/reports/implementation/2026-03-24-project-archive-frontend-record.md` + +- [ ] **Step 1: Write the verification skeleton** + +验证记录至少包含: + +```markdown +# 项目归档前端验证记录 + +## 验证范围 +- 列表归档按钮接入真实接口 +- 归档确认弹窗文案已收敛 +- 详情页上传数据与参数配置页签不可点击 +- 归档态下上传页与参数页有组件级保护 +``` + +- [ ] **Step 2: Record exact frontend test commands** + +把实际执行命令写入记录: + +```bash +cd ruoyi-ui +node tests/unit/project-list-archive-flow.test.js +node tests/unit/project-detail-archive-tab-lock.test.js +node tests/unit/upload-data-disabled-cards.test.js +node tests/unit/project-archive-readonly-guard.test.js +``` + +Expected: + +- 记录通过结果,或失败原因 + +- [ ] **Step 3: Write the frontend implementation record** + +在实施记录中说明: + +- 为什么弹窗文案需要收敛 +- 为什么页签禁用和 URL 拦截需要同时做 +- 为什么组件级归档保护仍要保留 +- 本次前端改动覆盖了哪些文件与测试 + +- [ ] **Step 4: Commit** + +```bash +git add docs/tests/records/2026-03-24-project-archive-frontend-verification.md docs/reports/implementation/2026-03-24-project-archive-frontend-record.md +git commit -m "补充项目归档前端实施记录" +```