diff --git a/app/.env b/app/.env index e40b987..856e41a 100644 --- a/app/.env +++ b/app/.env @@ -12,6 +12,7 @@ zn_username=%E8%87%AA%E5%8A%A8%E5%8C%96_Robot client_name=ShuFuAI -java_api_base=http://8.136.19.173:18080 +java_api_base=http://127.0.0.1:18080 +# java_api_base=http://8.136.19.173:18080 diff --git a/app/amazon/__pycache__/main.cpython-312.pyc b/app/amazon/__pycache__/main.cpython-312.pyc index 702fd9b..e5f9bda 100644 Binary files a/app/amazon/__pycache__/main.cpython-312.pyc and b/app/amazon/__pycache__/main.cpython-312.pyc differ diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandTaskDetailVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandTaskDetailVo.java index 9bf9829..b60ca27 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandTaskDetailVo.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandTaskDetailVo.java @@ -17,4 +17,7 @@ public class DeleteBrandTaskDetailVo { @Schema(description = "任务结果项快照") private List items = new ArrayList<>(); + + @Schema(description = "文件级进度快照") + private List fileProgress = new ArrayList<>(); } diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandTaskFileProgressVo.java b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandTaskFileProgressVo.java new file mode 100644 index 0000000..0c0a024 --- /dev/null +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/model/vo/DeleteBrandTaskFileProgressVo.java @@ -0,0 +1,27 @@ +package com.nanri.aiimage.modules.deletebrand.model.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "删除品牌任务中的文件级进度") +public class DeleteBrandTaskFileProgressVo { + + @Schema(description = "文件标识(优先 fileKey)") + private String fileKey; + + @Schema(description = "源文件名") + private String sourceFilename; + + @Schema(description = "已处理行数") + private Integer processedRows; + + @Schema(description = "总行数") + private Integer totalRows; + + @Schema(description = "百分比(0-100)") + private Integer percent; + + @Schema(description = "文件状态:PENDING/RUNNING/COMPLETED/FAILED") + private String status; +} diff --git a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java index 75fee5d..9152476 100644 --- a/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java +++ b/backend-java/src/main/java/com/nanri/aiimage/modules/deletebrand/service/DeleteBrandRunService.java @@ -25,6 +25,7 @@ import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandResultItemVo; import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandRunVo; import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDeletionStatusVo; import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskDetailVo; +import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskFileProgressVo; import com.nanri.aiimage.modules.deletebrand.model.vo.DeleteBrandTaskItemVo; import com.nanri.aiimage.modules.file.service.LocalFileStorageService; import com.nanri.aiimage.modules.file.service.oss.OssStorageService; @@ -569,9 +570,11 @@ public class DeleteBrandRunService { DeleteBrandLineProgressVo progressVo = buildLineProgress(task.getId(), progress); DeleteBrandTaskDetailVo detail = new DeleteBrandTaskDetailVo(); + List taskItems = resolveTaskResultItems(task); detail.setTask(taskVo); detail.setLine_progress(progressVo); - detail.setItems(resolveTaskResultItems(task)); + detail.setItems(taskItems); + detail.setFileProgress(buildFileProgress(task, taskItems, progressVo)); return detail; } @@ -602,6 +605,73 @@ public class DeleteBrandRunService { } } + private List buildFileProgress(FileTaskEntity task, + List taskItems, + DeleteBrandLineProgressVo progressVo) { + if (taskItems == null || taskItems.isEmpty()) { + return List.of(); + } + + String taskStatus = task == null ? null : task.getStatus(); + DeleteBrandLineProgressInfoVo info = progressVo == null ? null : progressVo.getInfo(); + Integer finishedFiles = info == null ? null : info.getFinished_files(); + String runningFileName = info == null ? null : info.getFile_name(); + Integer currentLine = info == null ? null : info.getCurrent_line(); + Integer totalLines = info == null ? null : info.getTotal_lines(); + + List fileProgress = new ArrayList<>(); + int completedAssigned = 0; + int normalizedFinished = finishedFiles == null ? 0 : Math.max(0, finishedFiles); + + for (DeleteBrandResultItemVo item : taskItems) { + if (item == null) { + continue; + } + + DeleteBrandTaskFileProgressVo fp = new DeleteBrandTaskFileProgressVo(); + fp.setFileKey(item.getFileKey()); + fp.setSourceFilename(item.getSourceFilename()); + + Integer itemTotalRows = item.getTotalRows() == null ? 0 : Math.max(item.getTotalRows(), 0); + fp.setTotalRows(itemTotalRows); + + String status = "PENDING"; + int processedRows = 0; + int percent = 0; + + if ("SUCCESS".equals(taskStatus)) { + status = "COMPLETED"; + processedRows = itemTotalRows; + percent = 100; + } else if ("FAILED".equals(taskStatus)) { + status = "FAILED"; + processedRows = Math.min(itemTotalRows, Math.max(0, currentLine == null ? 0 : currentLine)); + percent = itemTotalRows > 0 ? Math.min(100, (int) Math.round(processedRows * 100.0 / itemTotalRows)) : 0; + } else { + if (completedAssigned < normalizedFinished) { + status = "COMPLETED"; + processedRows = itemTotalRows; + percent = 100; + completedAssigned++; + } else if (runningFileName != null && runningFileName.equals(item.getSourceFilename())) { + status = "RUNNING"; + processedRows = Math.min(itemTotalRows, Math.max(0, currentLine == null ? 0 : currentLine)); + if (totalLines != null && totalLines > 0) { + percent = Math.min(100, Math.max(0, (int) Math.round(processedRows * 100.0 / totalLines))); + } else if (itemTotalRows > 0) { + percent = Math.min(100, Math.max(0, (int) Math.round(processedRows * 100.0 / itemTotalRows))); + } + } + } + + fp.setProcessedRows(processedRows); + fp.setPercent(percent); + fp.setStatus(status); + fileProgress.add(fp); + } + return fileProgress; + } + private FileTaskEntity refreshIndexedMatchesIfNeeded(FileTaskEntity task) { if (task == null || task.getResultJson() == null || task.getResultJson().isBlank()) { return task; diff --git a/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue b/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue index 631f37a..07f3cb5 100644 --- a/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue @@ -115,13 +115,13 @@
任务进度 - {{ formatProgressPercent(item.taskId!) }}% + {{ formatProgressPercent(item) }}%
+ :style="{ width: `${formatProgressPercent(item)}%` }">
-
{{ formatProgress(item.taskId!) }}
+
{{ formatProgress(item) }}
{{ formatPreview(item.previewRows) }} @@ -167,13 +167,13 @@
任务进度 - {{ formatProgressPercent(item.taskId!) }}% + {{ formatProgressPercent(item) }}%
+ :style="{ width: `${formatProgressPercent(item)}%` }">
-
{{ formatProgress(item.taskId!) }}
+
{{ formatProgress(item) }}
{{ formatPreview(item.previewRows) }} @@ -421,25 +421,30 @@ function shouldShowProgress(item: DeleteBrandResultItem) { if (!taskId) return false const progress = taskDetails.value[taskId]?.line_progress + const status = getTaskStatus(taskId) const multiFileTask = isMultiFileTask(taskId) const qs = getQueueStatus(item) - // 已完成/终态后隐藏进度条。 - if (qs === '本文件解析完毕' || qs === '已被全局判定为终态') { + if (multiFileTask) { + const sessionItem = findSessionItem(taskId, item) + const isActiveItem = getItemKey(item) === activeItemKey.value + const isProgressTarget = Boolean(progress?.has_progress && progress?.info?.file_name === item.sourceFilename) + + // 多文件任务下,展示规则更宽松: + // 1) 当前活跃文件;2) 后端进度指向的文件;3) 已推送文件;4) 已完成文件(任务未终态时保留)。 + if (isActiveItem || isProgressTarget || sessionItem?._pushed) { + return true + } + if ((status === 'RUNNING' || status === 'PENDING' || !status) && sessionItem?._completed) { + return true + } + return false } - if (multiFileTask) { - const sessionItem = findSessionItem(taskId, item) - // 多文件任务:未入队的文件不展示同一个 task 的公共进度,避免“两个文件同时在跑”的错觉。 - if (!sessionItem?._pushed) { - return false - } - - if (progress?.has_progress && progress?.info?.file_name) { - return progress.info.file_name === item.sourceFilename - } - return true + // 单文件任务:完成/终态后隐藏。 + if (qs === '本文件解析完毕' || qs === '已被全局判定为终态') { + return false } if (progress?.has_progress || progress?.info) { @@ -507,10 +512,40 @@ function updateSummary(items: DeleteBrandResultItem[]) { } } -function formatProgress(taskId: number) { +function getFileProgress(item: DeleteBrandResultItem) { + const taskId = item.taskId + if (!taskId) return null + const detail = taskDetails.value[taskId] + const fileProgress = detail?.fileProgress || [] + return fileProgress.find((fp) => { + if (fp.fileKey && item.fileKey) return fp.fileKey === item.fileKey + if (fp.sourceFilename && item.sourceFilename) return fp.sourceFilename === item.sourceFilename + return false + }) || null +} + +function formatProgress(item: DeleteBrandResultItem) { + const taskId = item.taskId if (!taskId) return '' + const detail = taskDetails.value[taskId] const info = detail?.line_progress?.info + const fp = getFileProgress(item) + + if (fp) { + const parts: string[] = [] + const processedRows = fp.processedRows ?? 0 + const totalRows = fp.totalRows ?? 0 + if (fp.status) parts.push(`文件状态:${fp.status}`) + parts.push(`文件进度:${processedRows}/${totalRows}`) + if (info?.finished_files != null) { + const total = info.file_total && info.file_total > 0 ? `/${info.file_total}` : '' + parts.push(`已完成文件:${info.finished_files}${total}`) + } + if (info?.phase) parts.push(`阶段:${info.phase}`) + return parts.join('|') + } + if (!detail?.line_progress?.has_progress || !info) { const status = detail?.task?.status return status ? `任务状态:${status}` : '' @@ -532,8 +567,15 @@ function formatProgress(taskId: number) { return parts.join('|') } -function formatProgressPercent(taskId: number) { +function formatProgressPercent(item: DeleteBrandResultItem) { + const taskId = item.taskId if (!taskId) return 0 + + const fp = getFileProgress(item) + if (fp) { + return Math.max(0, Math.min(100, fp.percent ?? 0)) + } + const info = taskDetails.value[taskId]?.line_progress?.info if (!info) return 0 @@ -578,20 +620,21 @@ function getQueueStatus(item: DeleteBrandResultItem) { return '已被全局判定为终态' } - // 多文件:未推送的文件必须保持“未入队”,避免被同 task 的公共 RUNNING 状态误判成“处理中”。 - if (multiFileTask && !sessionItem?._pushed && !sessionItem?._completed) { - return '未入队' - } - // 先看前端会话态:一旦该文件已判定完成,优先展示完成,避免被 RUNNING 覆盖导致链式推进卡住。 if (sessionItem?._completed) { return '本文件解析完毕' } + // 多文件场景下,如果后端进度明确指向当前文件,即使本地 _pushed 丢失也应判定为处理中(例如刷新页面后恢复场景)。 if (status === 'RUNNING' && info?.has_progress && info?.info?.file_name === item.sourceFilename) { return '处理中' } + // 多文件:未推送且未完成,才视为未入队。 + if (multiFileTask && !sessionItem?._pushed && !sessionItem?._completed) { + return '未入队' + } + if (multiFileTask && status === 'SUCCESS') { return '本文件解析完毕' } diff --git a/frontend-vue/src/shared/api/java-modules.ts b/frontend-vue/src/shared/api/java-modules.ts index 3f93ae2..73124d4 100644 --- a/frontend-vue/src/shared/api/java-modules.ts +++ b/frontend-vue/src/shared/api/java-modules.ts @@ -200,10 +200,20 @@ export interface DeleteBrandTaskItem { downloadFilename?: string } +export interface DeleteBrandTaskFileProgress { + fileKey?: string + sourceFilename?: string + processedRows?: number + totalRows?: number + percent?: number + status?: 'PENDING' | 'RUNNING' | 'COMPLETED' | 'FAILED' +} + export interface DeleteBrandTaskDetailVo { task: DeleteBrandTaskItem line_progress: DeleteBrandLineProgress items?: DeleteBrandResultItem[] + fileProgress?: DeleteBrandTaskFileProgress[] } export interface DeleteBrandTaskBatchVo {