修复lsfx删除文件接口logIds解析异常
This commit is contained in:
@@ -0,0 +1,410 @@
|
||||
# Project Upload File Delete Backend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 为项目上传文件列表新增后端删除能力,支持删除已解析成功的文件、清理本地流水,并把上传记录状态更新为 `deleted`。
|
||||
|
||||
**Architecture:** 在 `CcdiFileUploadController` 新增按记录 ID 删除接口,由 Controller 获取当前登录用户 ID 并传给 `ICcdiFileUploadService`。Service 负责查询记录、校验状态、调用 `LsfxAnalysisClient.deleteFiles`、删除 `ccdi_bank_statement` 中对应 `logId` 的流水,并更新 `ccdi_file_upload_record.file_status` 为 `deleted`;统计 VO 同步扩展 `deleted` 状态。
|
||||
|
||||
**Tech Stack:** Java 21, Spring Boot 3, MyBatis Plus, JUnit 5, Mockito, Maven
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 补齐删除接口控制器契约
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiFileUploadService.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
在 `CcdiFileUploadControllerTest` 中新增测试,验证删除接口会读取当前登录用户 ID 并调用 Service:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void deleteFile_shouldUseCurrentLoginUserId() {
|
||||
try (MockedStatic<SecurityUtils> mocked = mockStatic(SecurityUtils.class)) {
|
||||
mocked.when(SecurityUtils::getUserId).thenReturn(9527L);
|
||||
when(fileUploadService.deleteFileUploadRecord(123L, 9527L))
|
||||
.thenReturn("删除成功");
|
||||
|
||||
AjaxResult result = controller.deleteFile(123L);
|
||||
|
||||
assertEquals(200, result.get("code"));
|
||||
assertEquals("删除成功", result.get("msg"));
|
||||
verify(fileUploadService).deleteFileUploadRecord(123L, 9527L);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadControllerTest#deleteFile_shouldUseCurrentLoginUserId
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是 `CcdiFileUploadController` 中还没有 `deleteFile` 方法或 `ICcdiFileUploadService` 中还没有 `deleteFileUploadRecord` 方法
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在接口和控制器中补最小实现:
|
||||
|
||||
```java
|
||||
String deleteFileUploadRecord(Long id, Long operatorUserId);
|
||||
```
|
||||
|
||||
```java
|
||||
@DeleteMapping("/{id}")
|
||||
@Operation(summary = "删除上传文件", description = "按上传记录ID删除文件并清理流水")
|
||||
public AjaxResult deleteFile(@PathVariable Long id) {
|
||||
Long userId = SecurityUtils.getUserId();
|
||||
String message = fileUploadService.deleteFileUploadRecord(id, userId);
|
||||
return AjaxResult.success(message);
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadControllerTest#deleteFile_shouldUseCurrentLoginUserId
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/ICcdiFileUploadService.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java
|
||||
git commit -m "test: 补充上传文件删除接口控制器契约"
|
||||
```
|
||||
|
||||
### Task 2: 实现删除成功主链路
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
在 `CcdiFileUploadServiceImplTest` 中新增成功链路测试:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void deleteFileUploadRecord_shouldDeletePlatformFileBankStatementsAndMarkDeleted() {
|
||||
CcdiFileUploadRecord record = buildRecord();
|
||||
record.setProjectId(PROJECT_ID);
|
||||
record.setLsfxProjectId(LSFX_PROJECT_ID);
|
||||
record.setLogId(LOG_ID);
|
||||
record.setFileStatus("parsed_success");
|
||||
|
||||
when(recordMapper.selectById(RECORD_ID)).thenReturn(record);
|
||||
when(lsfxClient.deleteFiles(any())).thenReturn(buildDeleteFilesResponse());
|
||||
|
||||
String result = service.deleteFileUploadRecord(RECORD_ID, 9527L);
|
||||
|
||||
assertEquals("删除成功", result);
|
||||
verify(lsfxClient).deleteFiles(argThat(request ->
|
||||
request.getGroupId().equals(LSFX_PROJECT_ID)
|
||||
&& request.getUserId().equals(9527)
|
||||
&& request.getLogIds().length == 1
|
||||
&& request.getLogIds()[0].equals(LOG_ID)
|
||||
));
|
||||
verify(bankStatementMapper).deleteByProjectIdAndBatchId(PROJECT_ID, LOG_ID);
|
||||
verify(recordMapper).updateById(argThat(item ->
|
||||
RECORD_ID.equals(item.getId()) && "deleted".equals(item.getFileStatus())
|
||||
));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#deleteFileUploadRecord_shouldDeletePlatformFileBankStatementsAndMarkDeleted
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是 `CcdiFileUploadServiceImpl` 中还没有 `deleteFileUploadRecord` 实现
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在 `CcdiFileUploadServiceImpl` 中实现删除主链路:
|
||||
|
||||
```java
|
||||
@Override
|
||||
@Transactional
|
||||
public String deleteFileUploadRecord(Long id, Long operatorUserId) {
|
||||
CcdiFileUploadRecord record = recordMapper.selectById(id);
|
||||
validateDeleteRecord(record);
|
||||
|
||||
DeleteFilesRequest request = new DeleteFilesRequest();
|
||||
request.setGroupId(record.getLsfxProjectId());
|
||||
request.setLogIds(new Integer[]{record.getLogId()});
|
||||
request.setUserId(toUploadUserId(operatorUserId));
|
||||
|
||||
DeleteFilesResponse response = lsfxClient.deleteFiles(request);
|
||||
if (response == null || Boolean.FALSE.equals(response.getSuccessResponse())) {
|
||||
throw new RuntimeException("流水分析平台删除文件失败");
|
||||
}
|
||||
|
||||
bankStatementMapper.deleteByProjectIdAndBatchId(record.getProjectId(), record.getLogId());
|
||||
|
||||
CcdiFileUploadRecord update = new CcdiFileUploadRecord();
|
||||
update.setId(record.getId());
|
||||
update.setFileStatus("deleted");
|
||||
recordMapper.updateById(update);
|
||||
return "删除成功";
|
||||
}
|
||||
```
|
||||
|
||||
同时补一个私有校验方法,仅先满足成功路径所需字段。
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#deleteFileUploadRecord_shouldDeletePlatformFileBankStatementsAndMarkDeleted
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "feat: 打通上传文件删除成功主链路"
|
||||
```
|
||||
|
||||
### Task 3: 补齐删除前置校验与失败保护
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
为关键边界新增失败测试,至少覆盖“状态不允许删除”和“平台删除失败时不应更新本地状态”:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void deleteFileUploadRecord_shouldRejectNonParsedSuccessStatus() {
|
||||
CcdiFileUploadRecord record = buildRecord();
|
||||
record.setFileStatus("parsed_failed");
|
||||
when(recordMapper.selectById(RECORD_ID)).thenReturn(record);
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.deleteFileUploadRecord(RECORD_ID, 9527L));
|
||||
|
||||
assertTrue(exception.getMessage().contains("仅支持删除解析成功文件"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteFileUploadRecord_shouldStopWhenLsfxDeleteFails() {
|
||||
CcdiFileUploadRecord record = buildRecord();
|
||||
record.setFileStatus("parsed_success");
|
||||
record.setLogId(LOG_ID);
|
||||
record.setLsfxProjectId(LSFX_PROJECT_ID);
|
||||
when(recordMapper.selectById(RECORD_ID)).thenReturn(record);
|
||||
when(lsfxClient.deleteFiles(any())).thenThrow(new RuntimeException("lsfx delete failed"));
|
||||
|
||||
assertThrows(RuntimeException.class, () -> service.deleteFileUploadRecord(RECORD_ID, 9527L));
|
||||
|
||||
verify(bankStatementMapper, org.mockito.Mockito.never()).deleteByProjectIdAndBatchId(any(), any());
|
||||
verify(recordMapper, org.mockito.Mockito.never()).updateById(argThat(item ->
|
||||
"deleted".equals(item.getFileStatus())
|
||||
));
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#deleteFileUploadRecord_shouldRejectNonParsedSuccessStatus,CcdiFileUploadServiceImplTest#deleteFileUploadRecord_shouldStopWhenLsfxDeleteFails
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是现有实现还没有完整的前置校验和失败保护
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
补全 Service 校验和异常处理:
|
||||
|
||||
```java
|
||||
private void validateDeleteRecord(CcdiFileUploadRecord record) {
|
||||
if (record == null) {
|
||||
throw new RuntimeException("上传记录不存在");
|
||||
}
|
||||
if (!"parsed_success".equals(record.getFileStatus())) {
|
||||
if ("deleted".equals(record.getFileStatus())) {
|
||||
throw new RuntimeException("文件已删除,请勿重复操作");
|
||||
}
|
||||
throw new RuntimeException("仅支持删除解析成功文件");
|
||||
}
|
||||
if (record.getLsfxProjectId() == null) {
|
||||
throw new RuntimeException("缺少流水分析项目ID");
|
||||
}
|
||||
if (record.getLogId() == null) {
|
||||
throw new RuntimeException("缺少文件logId");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
不要吞掉平台删除异常,让事务直接回滚。
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#deleteFileUploadRecord_shouldRejectNonParsedSuccessStatus,CcdiFileUploadServiceImplTest#deleteFileUploadRecord_shouldStopWhenLsfxDeleteFails
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "test: 补齐上传文件删除校验与失败保护"
|
||||
```
|
||||
|
||||
### Task 4: 扩展统计口径支持 `deleted`
|
||||
|
||||
**Files:**
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiFileUploadStatisticsVO.java`
|
||||
- Modify: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Test: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新增统计测试,验证 `deleted` 状态会被正确映射到 VO:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void countByStatus_shouldIncludeDeletedCount() {
|
||||
when(recordMapper.countByStatus(PROJECT_ID)).thenReturn(List.of(
|
||||
Map.of("status", "uploading", "count", 1),
|
||||
Map.of("status", "deleted", "count", 2)
|
||||
));
|
||||
|
||||
CcdiFileUploadStatisticsVO result = service.countByStatus(PROJECT_ID);
|
||||
|
||||
assertEquals(1L, result.getUploading());
|
||||
assertEquals(2L, result.getDeleted());
|
||||
assertEquals(3L, result.getTotal());
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#countByStatus_shouldIncludeDeletedCount
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是 `CcdiFileUploadStatisticsVO` 还没有 `deleted` 字段,或 `countByStatus` 还没有映射该状态
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在 VO 和 Service 中补 `deleted` 字段及映射:
|
||||
|
||||
```java
|
||||
private Long deleted;
|
||||
```
|
||||
|
||||
```java
|
||||
vo.setDeleted(0L);
|
||||
case "deleted" -> vo.setDeleted(count);
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadServiceImplTest#countByStatus_shouldIncludeDeletedCount
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiFileUploadStatisticsVO.java ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java
|
||||
git commit -m "feat: 扩展上传文件统计支持已删除状态"
|
||||
```
|
||||
|
||||
### Task 5: 跑后端回归验证
|
||||
|
||||
**Files:**
|
||||
- Verify only: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadController.java`
|
||||
- Verify only: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`
|
||||
- Verify only: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/controller/CcdiFileUploadControllerTest.java`
|
||||
- Verify only: `ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImplTest.java`
|
||||
|
||||
**Step 1: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn test -pl ccdi-project -am -Dtest=CcdiFileUploadControllerTest,CcdiFileUploadServiceImplTest
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 全部 `PASS`
|
||||
|
||||
**Step 2: Run module compile**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn clean compile -pl ccdi-project -am
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `BUILD SUCCESS`
|
||||
|
||||
**Step 3: Record manual API smoke checklist**
|
||||
|
||||
手工检查以下接口行为:
|
||||
|
||||
- `DELETE /ccdi/file-upload/{id}` 删除 `parsed_success` 记录返回成功
|
||||
- 删除 `parsed_failed` 记录返回错误
|
||||
- 重复删除 `deleted` 记录返回错误
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "test: 完成上传文件删除后端回归验证"
|
||||
```
|
||||
@@ -0,0 +1,362 @@
|
||||
# Project Upload File Delete Frontend Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 在项目详情-上传数据-上传文件列表中新增操作列,实现“查看错误原因”和“删除”交互,并让删除后的记录显示为“已删除”。
|
||||
|
||||
**Architecture:** 先把状态到“操作/标签”的判断抽取到一个轻量 helper,通过 Node 可执行脚本做最小自动化校验;然后在 `ccdiProjectUpload.js` 新增删除接口,在 `UploadData.vue` 中接入操作列、删除确认框、删除后刷新列表与统计。保留现有轮询机制,不新增前端全局状态管理。
|
||||
|
||||
**Tech Stack:** Vue 2, Element UI, Axios request wrapper, Node script verification, npm build
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 抽取状态与操作规则并先写失败测试
|
||||
|
||||
**Files:**
|
||||
- Create: `ruoyi-ui/src/views/ccdiProject/components/detail/uploadFileActionRules.mjs`
|
||||
- Create: `tests/frontend/upload-file-action-rules.test.mjs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
新建一个轻量 Node 规则测试脚本,先定义期望行为:
|
||||
|
||||
```javascript
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
getUploadFileAction,
|
||||
getUploadFileStatusText,
|
||||
getUploadFileStatusType
|
||||
} from "../../ruoyi-ui/src/views/ccdiProject/components/detail/uploadFileActionRules.mjs";
|
||||
|
||||
assert.deepEqual(getUploadFileAction("parsed_failed"), { key: "viewError", text: "查看错误原因" });
|
||||
assert.deepEqual(getUploadFileAction("parsed_success"), { key: "delete", text: "删除" });
|
||||
assert.equal(getUploadFileAction("deleted"), null);
|
||||
assert.equal(getUploadFileStatusText("deleted"), "已删除");
|
||||
assert.equal(getUploadFileStatusType("deleted"), "info");
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node tests/frontend/upload-file-action-rules.test.mjs
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
- 原因是 helper 文件还不存在
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
创建 helper 文件:
|
||||
|
||||
```javascript
|
||||
export function getUploadFileAction(status) {
|
||||
const actionMap = {
|
||||
parsed_failed: { key: "viewError", text: "查看错误原因" },
|
||||
parsed_success: { key: "delete", text: "删除" }
|
||||
};
|
||||
return actionMap[status] || null;
|
||||
}
|
||||
|
||||
export function getUploadFileStatusText(status) {
|
||||
const map = {
|
||||
uploading: "上传中",
|
||||
parsing: "解析中",
|
||||
parsed_success: "解析成功",
|
||||
parsed_failed: "解析失败",
|
||||
deleted: "已删除"
|
||||
};
|
||||
return map[status] || status;
|
||||
}
|
||||
|
||||
export function getUploadFileStatusType(status) {
|
||||
const map = {
|
||||
uploading: "primary",
|
||||
parsing: "warning",
|
||||
parsed_success: "success",
|
||||
parsed_failed: "danger",
|
||||
deleted: "info"
|
||||
};
|
||||
return map[status] || "info";
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node tests/frontend/upload-file-action-rules.test.mjs
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/detail/uploadFileActionRules.mjs tests/frontend/upload-file-action-rules.test.mjs
|
||||
git commit -m "test: 补充上传文件操作规则测试"
|
||||
```
|
||||
|
||||
### Task 2: 新增删除 API 契约
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/api/ccdiProjectUpload.js`
|
||||
|
||||
**Step 1: Write the failing usage**
|
||||
|
||||
先在计划中约定组件调用的新 API 形式,后续组件实现前必须存在:
|
||||
|
||||
```javascript
|
||||
export function deleteFileUploadRecord(id) {
|
||||
return request({
|
||||
url: `/ccdi/file-upload/${id}`,
|
||||
method: "delete"
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run a quick import smoke check**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node -e "const fs=require('fs'); const text=fs.readFileSync('ruoyi-ui/src/api/ccdiProjectUpload.js','utf8'); if(!text.includes('deleteFileUploadRecord')) process.exit(1);"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
把旧的按 `projectId + uploadType` 删除接口保留不动,新加按记录 ID 删除的方法:
|
||||
|
||||
```javascript
|
||||
export function deleteFileUploadRecord(id) {
|
||||
return request({
|
||||
url: `/ccdi/file-upload/${id}`,
|
||||
method: "delete"
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Run smoke check to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node -e "const fs=require('fs'); const text=fs.readFileSync('ruoyi-ui/src/api/ccdiProjectUpload.js','utf8'); if(!text.includes('deleteFileUploadRecord')) process.exit(1);"
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 退出码 `0`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/api/ccdiProjectUpload.js
|
||||
git commit -m "feat: 新增上传文件按记录删除接口"
|
||||
```
|
||||
|
||||
### Task 3: 在列表中渲染操作列和错误原因弹窗
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue`
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/detail/uploadFileActionRules.mjs`
|
||||
- Test: `tests/frontend/upload-file-action-rules.test.mjs`
|
||||
|
||||
**Step 1: Write the failing test**
|
||||
|
||||
先扩充规则测试,确保无操作状态不会返回按钮:
|
||||
|
||||
```javascript
|
||||
assert.equal(getUploadFileAction("uploading"), null);
|
||||
assert.equal(getUploadFileAction("parsing"), null);
|
||||
```
|
||||
|
||||
**Step 2: Run test to verify it fails**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node tests/frontend/upload-file-action-rules.test.mjs
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 如果 helper 暂未覆盖全部状态,则 `FAIL`
|
||||
|
||||
**Step 3: Write minimal implementation**
|
||||
|
||||
在 `UploadData.vue` 中:
|
||||
|
||||
- 引入 helper
|
||||
- 给表格新增“操作”列
|
||||
- 根据 `getUploadFileAction(scope.row.fileStatus)` 渲染按钮
|
||||
- 增加 `handleViewError(row)`,直接读取 `row.errorMessage || "未知错误"` 并调用:
|
||||
|
||||
```javascript
|
||||
this.$alert(row.errorMessage || "未知错误", "错误信息", {
|
||||
confirmButtonText: "确定",
|
||||
type: "error"
|
||||
});
|
||||
```
|
||||
|
||||
表格模板示例:
|
||||
|
||||
```vue
|
||||
<el-table-column label="操作" width="160" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
v-if="getRowAction(scope.row)"
|
||||
type="text"
|
||||
@click="handleRowAction(scope.row)"
|
||||
>
|
||||
{{ getRowAction(scope.row).text }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
```
|
||||
|
||||
**Step 4: Run test to verify it passes**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node tests/frontend/upload-file-action-rules.test.mjs
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue ruoyi-ui/src/views/ccdiProject/components/detail/uploadFileActionRules.mjs tests/frontend/upload-file-action-rules.test.mjs
|
||||
git commit -m "feat: 新增上传文件列表操作列与错误原因查看"
|
||||
```
|
||||
|
||||
### Task 4: 接入删除确认与刷新逻辑
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue`
|
||||
- Modify: `ruoyi-ui/src/api/ccdiProjectUpload.js`
|
||||
|
||||
**Step 1: Write the failing manual checklist**
|
||||
|
||||
先记录需要被满足的行为:
|
||||
|
||||
- `parsed_success` 行点击“删除”必须弹出二次确认
|
||||
- 确认后调用 `deleteFileUploadRecord(row.id)`
|
||||
- 成功后提示“删除成功”
|
||||
- 成功后刷新 `loadStatistics()` 与 `loadFileList()`
|
||||
- 若仍存在 `uploading/parsing` 记录,则继续轮询;否则不重复启动轮询
|
||||
|
||||
**Step 2: Implement the minimal component code**
|
||||
|
||||
在 `UploadData.vue` 新增删除逻辑:
|
||||
|
||||
```javascript
|
||||
async handleDeleteFile(row) {
|
||||
await this.$confirm(
|
||||
"删除后将同步删除流水分析平台中的文件,并清除本系统中该文件对应的所有银行流水数据,是否继续?",
|
||||
"提示",
|
||||
{
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}
|
||||
);
|
||||
|
||||
await deleteFileUploadRecord(row.id);
|
||||
this.$message.success("删除成功");
|
||||
await Promise.all([this.loadStatistics(), this.loadFileList()]);
|
||||
|
||||
if (this.statistics.uploading > 0 || this.statistics.parsing > 0) {
|
||||
this.startPolling();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
并在统一入口 `handleRowAction(row)` 中根据 action key 分流到:
|
||||
|
||||
- `viewError`
|
||||
- `delete`
|
||||
|
||||
**Step 3: Run build to verify no syntax errors**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui
|
||||
npm run build:prod
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `BUILD SUCCESS` 或等价的前端打包成功输出
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue ruoyi-ui/src/api/ccdiProjectUpload.js
|
||||
git commit -m "feat: 接入上传文件删除确认与刷新逻辑"
|
||||
```
|
||||
|
||||
### Task 5: 完成前端手工回归验证
|
||||
|
||||
**Files:**
|
||||
- Verify only: `ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue`
|
||||
- Verify only: `ruoyi-ui/src/api/ccdiProjectUpload.js`
|
||||
- Verify only: `tests/frontend/upload-file-action-rules.test.mjs`
|
||||
|
||||
**Step 1: Run rules test**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node tests/frontend/upload-file-action-rules.test.mjs
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- `PASS`
|
||||
|
||||
**Step 2: Run production build**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui
|
||||
npm run build:prod
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- 打包成功
|
||||
|
||||
**Step 3: Perform manual UI verification**
|
||||
|
||||
手工验证清单:
|
||||
|
||||
- `parsed_failed` 行显示“查看错误原因”
|
||||
- 点击后能弹出错误信息
|
||||
- `parsed_success` 行显示“删除”
|
||||
- 点击删除会出现确认框
|
||||
- 删除成功后状态显示为“已删除”
|
||||
- `deleted` 行不再显示任何操作按钮
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "test: 完成上传文件删除前端回归验证"
|
||||
```
|
||||
Reference in New Issue
Block a user