提交接口进度完善
This commit is contained in:
3
app/.env
3
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
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -7,7 +7,7 @@
|
||||
<script type="module" crossorigin src="/assets/delete-brand.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/pywebview-Dee3nQjE.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/pywebview-MkjZQlBu.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/delete-brand-D9tUv360.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/delete-brand-_jGFWTAn.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
BIN
backend-java/aiimage-backend.log.2026-04-02.0.gz
Normal file
BIN
backend-java/aiimage-backend.log.2026-04-02.0.gz
Normal file
Binary file not shown.
@@ -17,4 +17,7 @@ public class DeleteBrandTaskDetailVo {
|
||||
|
||||
@Schema(description = "任务结果项快照")
|
||||
private List<DeleteBrandResultItemVo> items = new ArrayList<>();
|
||||
|
||||
@Schema(description = "文件级进度快照")
|
||||
private List<DeleteBrandTaskFileProgressVo> fileProgress = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<DeleteBrandResultItemVo> 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<DeleteBrandTaskFileProgressVo> buildFileProgress(FileTaskEntity task,
|
||||
List<DeleteBrandResultItemVo> 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<DeleteBrandTaskFileProgressVo> 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;
|
||||
|
||||
@@ -115,13 +115,13 @@
|
||||
<div v-if="shouldShowProgress(item)" class="delete-brand-progress-block">
|
||||
<div class="delete-brand-progress-header">
|
||||
<span>任务进度</span>
|
||||
<span>{{ formatProgressPercent(item.taskId!) }}%</span>
|
||||
<span>{{ formatProgressPercent(item) }}%</span>
|
||||
</div>
|
||||
<div class="delete-brand-progress-bar">
|
||||
<div class="delete-brand-progress-bar-fill"
|
||||
:style="{ width: `${formatProgressPercent(item.taskId!)}%` }"></div>
|
||||
:style="{ width: `${formatProgressPercent(item)}%` }"></div>
|
||||
</div>
|
||||
<div class="files delete-brand-progress">{{ formatProgress(item.taskId!) }}</div>
|
||||
<div class="files delete-brand-progress">{{ formatProgress(item) }}</div>
|
||||
</div>
|
||||
<div v-if="item.previewRows?.length" class="files split-entry-list delete-brand-preview">
|
||||
{{ formatPreview(item.previewRows) }}
|
||||
@@ -167,13 +167,13 @@
|
||||
<div v-if="shouldShowProgress(item)" class="delete-brand-progress-block">
|
||||
<div class="delete-brand-progress-header">
|
||||
<span>任务进度</span>
|
||||
<span>{{ formatProgressPercent(item.taskId!) }}%</span>
|
||||
<span>{{ formatProgressPercent(item) }}%</span>
|
||||
</div>
|
||||
<div class="delete-brand-progress-bar">
|
||||
<div class="delete-brand-progress-bar-fill"
|
||||
:style="{ width: `${formatProgressPercent(item.taskId!)}%` }"></div>
|
||||
:style="{ width: `${formatProgressPercent(item)}%` }"></div>
|
||||
</div>
|
||||
<div class="files delete-brand-progress">{{ formatProgress(item.taskId!) }}</div>
|
||||
<div class="files delete-brand-progress">{{ formatProgress(item) }}</div>
|
||||
</div>
|
||||
<div v-if="item.previewRows?.length" class="files split-entry-list delete-brand-preview">
|
||||
{{ 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 '本文件解析完毕'
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user