更改下载方法名
This commit is contained in:
@@ -286,22 +286,52 @@ function loadSessionTasksFromStorage() {
|
||||
}
|
||||
|
||||
function saveSessionTask(taskId: number, items: DeleteBrandResultItem[], pushResult?: string, payloadText?: string) {
|
||||
const validItems = items.filter(i => i.matchStatus === 'MATCHED' && i.success !== false)
|
||||
if (validItems.length === 0) return
|
||||
if (!items?.length) return
|
||||
|
||||
const existingIndex = sessionTasks.value.findIndex(t => t.taskId === taskId)
|
||||
const existingTask = existingIndex >= 0 ? sessionTasks.value[existingIndex] : null
|
||||
|
||||
// 全量保留任务项(不在存储层按 matched/success 过滤),避免单任务多店铺时出现“未处理项丢失”。
|
||||
// 同时尽量继承已有的 _pushed/_completed 会话态。
|
||||
const mergedItems = items.map((item) => {
|
||||
const prev = existingTask?.items.find((i) => isSameDeleteBrandItem(i, item))
|
||||
|
||||
// 保护大字段:当新值为空数组时,保留本地已有的非空数据,避免被轮询详情的精简 payload 覆盖。
|
||||
const nextCountries = Array.isArray(item.countries) ? item.countries : undefined
|
||||
const prevCountries = Array.isArray(prev?.countries) ? prev?.countries : undefined
|
||||
const countries = (nextCountries && nextCountries.length > 0)
|
||||
? nextCountries
|
||||
: (prevCountries && prevCountries.length > 0 ? prevCountries : (nextCountries ?? prevCountries))
|
||||
|
||||
const nextPreviewRows = Array.isArray(item.previewRows) ? item.previewRows : undefined
|
||||
const prevPreviewRows = Array.isArray(prev?.previewRows) ? prev?.previewRows : undefined
|
||||
const previewRows = (nextPreviewRows && nextPreviewRows.length > 0)
|
||||
? nextPreviewRows
|
||||
: (prevPreviewRows && prevPreviewRows.length > 0 ? prevPreviewRows : (nextPreviewRows ?? prevPreviewRows))
|
||||
|
||||
return {
|
||||
...item,
|
||||
countries,
|
||||
previewRows,
|
||||
_pushed: prev?._pushed ?? (item as SessionDeleteBrandItem)._pushed ?? false,
|
||||
_completed: prev?._completed ?? (item as SessionDeleteBrandItem)._completed ?? false,
|
||||
} as SessionDeleteBrandItem
|
||||
})
|
||||
|
||||
const newTask: StoredCurrentTask = {
|
||||
taskId,
|
||||
items: validItems,
|
||||
createdAt: Date.now(),
|
||||
queuePushResult: pushResult,
|
||||
queuePayloadText: payloadText
|
||||
items: mergedItems,
|
||||
createdAt: existingTask?.createdAt || Date.now(),
|
||||
queuePushResult: pushResult ?? existingTask?.queuePushResult,
|
||||
queuePayloadText: payloadText ?? existingTask?.queuePayloadText,
|
||||
}
|
||||
const existingIndex = sessionTasks.value.findIndex(t => t.taskId === taskId)
|
||||
|
||||
if (existingIndex >= 0) {
|
||||
sessionTasks.value[existingIndex] = newTask
|
||||
} else {
|
||||
sessionTasks.value.push(newTask)
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.localStorage.setItem(getStorageKey(), JSON.stringify(sessionTasks.value))
|
||||
}
|
||||
@@ -318,7 +348,7 @@ function removeSessionTaskFromStorage(taskId: number) {
|
||||
|
||||
const historySectionItems = computed(() =>
|
||||
historyItems.value.filter(
|
||||
(item) => !currentSectionItems.value.some(c => c.taskId === item.taskId || (c.resultId && c.resultId === item.resultId))
|
||||
(item) => !currentSectionItems.value.some(c => isSameDeleteBrandItem(c, item))
|
||||
),
|
||||
);
|
||||
const hasVisibleItems = computed(() => currentSectionItems.value.length > 0 || historySectionItems.value.length > 0)
|
||||
@@ -366,30 +396,28 @@ function updateCompletedItemsFromProgress(taskId: number | undefined) {
|
||||
|
||||
let changed = false
|
||||
const info = taskDetails.value[taskId]?.line_progress?.info
|
||||
const multiFileTask = isMultiFileTask(taskId)
|
||||
|
||||
// 多文件任务优先依据后端 finished_files 同步已完成文件数量。
|
||||
// 规则:按 sessionTask.items 的顺序,标记「已推送」中的前 N 个为 completed。
|
||||
if (isMultiFileTask(taskId) && info?.finished_files != null && info.finished_files >= 0) {
|
||||
const pushedItems = sessionTask.items.filter(i => i._pushed)
|
||||
const shouldCompleted = Math.max(0, Math.min(info.finished_files, pushedItems.length))
|
||||
|
||||
for (let i = 0; i < shouldCompleted; i += 1) {
|
||||
if (!pushedItems[i]._completed) {
|
||||
pushedItems[i]._completed = true
|
||||
// 关键修复:多文件任务下不再用 finished_files 按顺序回填 completed。
|
||||
// 否则当第 1 个文件完成后,推入第 2 个文件时会因为 finished_files=1 被误判为“本文件已完成(100%)”。
|
||||
// 改为仅当后端进度明确指向该文件且 current_line >= total_lines 时,才标记该文件 completed。
|
||||
if (info?.file_name && info.current_line != null && info.total_lines != null && info.total_lines > 0) {
|
||||
if (info.current_line >= info.total_lines) {
|
||||
const sessionItem = sessionTask.items.find(i => i.sourceFilename === info.file_name)
|
||||
if (sessionItem && !sessionItem._completed) {
|
||||
sessionItem._completed = true
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容单文件/兜底:当当前文件行进度到达末行时,标记活动文件完成。
|
||||
if (info?.file_name && info.current_line != null && info.total_lines != null && info.total_lines > 0) {
|
||||
if (info.current_line >= info.total_lines) {
|
||||
const activeItem = currentSectionItems.value.find(item => getItemKey(item) === activeItemKey.value)
|
||||
const sessionItem = activeItem && activeItem.taskId === taskId
|
||||
? findSessionItem(taskId, activeItem)
|
||||
: sessionTask.items.find(i => i.sourceFilename === info.file_name)
|
||||
if (sessionItem && !sessionItem._completed) {
|
||||
sessionItem._completed = true
|
||||
// 单文件任务兜底:任务终态时标记完成,避免偶发进度丢包导致状态卡住。
|
||||
if (!multiFileTask) {
|
||||
const status = taskDetails.value[taskId]?.task?.status
|
||||
if ((status === 'SUCCESS' || status === 'FAILED') && sessionTask.items.length === 1) {
|
||||
const onlyItem = sessionTask.items[0]
|
||||
if (onlyItem && !onlyItem._completed) {
|
||||
onlyItem._completed = true
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
@@ -531,6 +559,48 @@ function formatProgress(item: DeleteBrandResultItem) {
|
||||
const detail = taskDetails.value[taskId]
|
||||
const info = detail?.line_progress?.info
|
||||
const fp = getFileProgress(item)
|
||||
const multiFileTask = isMultiFileTask(taskId)
|
||||
|
||||
// 多文件任务优先按“当前运行文件 + 本地会话态”展示,避免后端 finished_files 按顺序分配导致某个文件被误判 COMPLETED。
|
||||
if (multiFileTask) {
|
||||
const sessionItem = findSessionItem(taskId, item)
|
||||
const status = detail?.task?.status
|
||||
const isRunningTarget = Boolean(info?.file_name && info.file_name === item.sourceFilename)
|
||||
|
||||
let displayStatus = sessionItem?._completed ? 'COMPLETED' : (sessionItem?._pushed ? 'PENDING' : 'PENDING')
|
||||
let processedRows = 0
|
||||
let totalRows = Math.max(0, item.totalRows ?? fp?.totalRows ?? 0)
|
||||
|
||||
if (status === 'SUCCESS') {
|
||||
displayStatus = 'COMPLETED'
|
||||
processedRows = totalRows
|
||||
} else if (status === 'FAILED' && sessionItem?._completed) {
|
||||
displayStatus = 'COMPLETED'
|
||||
processedRows = totalRows
|
||||
} else if (isRunningTarget) {
|
||||
displayStatus = 'RUNNING'
|
||||
processedRows = Math.max(0, Math.min(totalRows || Number.MAX_SAFE_INTEGER, info?.current_line ?? 0))
|
||||
if (!totalRows && info?.total_lines && info.total_lines > 0) {
|
||||
totalRows = info.total_lines
|
||||
}
|
||||
} else if (sessionItem?._completed) {
|
||||
displayStatus = 'COMPLETED'
|
||||
processedRows = totalRows
|
||||
} else if (sessionItem?._pushed) {
|
||||
displayStatus = 'PENDING'
|
||||
processedRows = 0
|
||||
}
|
||||
|
||||
const parts: string[] = []
|
||||
parts.push(`文件状态:${displayStatus}`)
|
||||
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 (fp) {
|
||||
const parts: string[] = []
|
||||
@@ -571,12 +641,32 @@ function formatProgressPercent(item: DeleteBrandResultItem) {
|
||||
const taskId = item.taskId
|
||||
if (!taskId) return 0
|
||||
|
||||
const info = taskDetails.value[taskId]?.line_progress?.info
|
||||
const multiFileTask = isMultiFileTask(taskId)
|
||||
|
||||
if (multiFileTask) {
|
||||
const sessionItem = findSessionItem(taskId, item)
|
||||
const taskStatus = taskDetails.value[taskId]?.task?.status
|
||||
if (taskStatus === 'SUCCESS' || sessionItem?._completed) return 100
|
||||
|
||||
const totalRows = Math.max(0, item.totalRows ?? getFileProgress(item)?.totalRows ?? 0)
|
||||
const isRunningTarget = Boolean(info?.file_name && info.file_name === item.sourceFilename)
|
||||
if (isRunningTarget) {
|
||||
if (info?.current_line != null && info?.total_lines && info.total_lines > 0) {
|
||||
return Math.max(0, Math.min(100, Math.round((info.current_line / info.total_lines) * 100)))
|
||||
}
|
||||
if (info?.current_line != null && totalRows > 0) {
|
||||
return Math.max(0, Math.min(100, Math.round((info.current_line / totalRows) * 100)))
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
if (info.current_line != null && info.total_lines && info.total_lines > 0) {
|
||||
@@ -667,30 +757,58 @@ function canDownloadTaskResult(item: DeleteBrandResultItem) {
|
||||
|
||||
function mergeTaskDetailItemsIntoSession(detail: DeleteBrandTaskDetailVo) {
|
||||
const taskId = detail?.task?.id
|
||||
if (!taskId || !detail?.items?.length) return false
|
||||
if (!taskId) return false
|
||||
const sessionTask = sessionTasks.value.find(t => t.taskId === taskId)
|
||||
if (!sessionTask) return false
|
||||
|
||||
const detailItems = detail?.items || []
|
||||
let changed = false
|
||||
|
||||
// 以本地会话项为主做增量合并,保留 _pushed/_completed;
|
||||
// 对后端新增返回的项追加到末尾,避免单任务多店铺/后续补齐时丢项。
|
||||
const mergedItems = sessionTask.items.map((item) => {
|
||||
const latest = detail.items?.find((candidate) => isSameDeleteBrandItem(candidate, item))
|
||||
const latest = detailItems.find((candidate) => isSameDeleteBrandItem(candidate, item))
|
||||
if (!latest) return item
|
||||
|
||||
const latestCountries = Array.isArray(latest.countries) ? latest.countries : undefined
|
||||
const itemCountries = Array.isArray(item.countries) ? item.countries : undefined
|
||||
const countries = (latestCountries && latestCountries.length > 0)
|
||||
? latestCountries
|
||||
: (itemCountries && itemCountries.length > 0 ? itemCountries : (latestCountries ?? itemCountries))
|
||||
|
||||
const latestPreviewRows = Array.isArray(latest.previewRows) ? latest.previewRows : undefined
|
||||
const itemPreviewRows = Array.isArray(item.previewRows) ? item.previewRows : undefined
|
||||
const previewRows = (latestPreviewRows && latestPreviewRows.length > 0)
|
||||
? latestPreviewRows
|
||||
: (itemPreviewRows && itemPreviewRows.length > 0 ? itemPreviewRows : (latestPreviewRows ?? itemPreviewRows))
|
||||
|
||||
const merged = {
|
||||
...item,
|
||||
...latest,
|
||||
success: item.success ?? latest.success,
|
||||
countries,
|
||||
previewRows,
|
||||
_pushed: item._pushed,
|
||||
_completed: item._completed,
|
||||
}
|
||||
} as SessionDeleteBrandItem
|
||||
if (JSON.stringify(merged) !== JSON.stringify(item)) {
|
||||
changed = true
|
||||
}
|
||||
return merged
|
||||
})
|
||||
|
||||
const validItems = mergedItems.filter(i => i.matchStatus === 'MATCHED' && i.success !== false)
|
||||
if (JSON.stringify(validItems) !== JSON.stringify(sessionTask.items)) {
|
||||
sessionTask.items = validItems
|
||||
for (const latest of detailItems) {
|
||||
if (!mergedItems.some((item) => isSameDeleteBrandItem(item, latest))) {
|
||||
mergedItems.push({
|
||||
...latest,
|
||||
_pushed: false,
|
||||
_completed: false,
|
||||
} as SessionDeleteBrandItem)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
if (JSON.stringify(mergedItems) !== JSON.stringify(sessionTask.items)) {
|
||||
sessionTask.items = mergedItems
|
||||
changed = true
|
||||
}
|
||||
|
||||
@@ -750,21 +868,26 @@ function scheduleNextPoll(immediate = false) {
|
||||
|
||||
pollingInFlight.value = true
|
||||
let stateChanged = false
|
||||
let shouldRefreshHistory = false
|
||||
try {
|
||||
await refreshTaskDetails(taskIds)
|
||||
await maybeAutoAdvance()
|
||||
for (const taskId of taskIds) {
|
||||
if (isTaskTerminal(taskId)) {
|
||||
// 以后端任务终态为准:进入 SUCCESS/FAILED 后立即清理本地当前任务并刷新 history,
|
||||
// 避免 _completed 会话态偶发不同步导致持续轮询 batch。
|
||||
removeSessionTaskFromStorage(taskId)
|
||||
stateChanged = true
|
||||
shouldRefreshHistory = true
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
pollingInFlight.value = false
|
||||
}
|
||||
|
||||
if (stateChanged) {
|
||||
loadHistory()
|
||||
if (shouldRefreshHistory) {
|
||||
await loadHistory()
|
||||
} else if (stateChanged) {
|
||||
syncResultState()
|
||||
}
|
||||
|
||||
@@ -923,8 +1046,8 @@ async function downloadTaskResult(item: DeleteBrandResultItem) {
|
||||
item.sourceFilename ||
|
||||
`delete-brand_${taskId}.xlsx`
|
||||
|
||||
if (api?.save_file_from_url) {
|
||||
const result = await api.save_file_from_url(url, filename)
|
||||
if (api?.save_file_from_url_new) {
|
||||
const result = await api.save_file_from_url_new(url, filename)
|
||||
if (result.success) {
|
||||
ElMessage.success(`已保存:${result.path || filename}`)
|
||||
} else if (result.error && result.error !== '用户取消') {
|
||||
@@ -1174,15 +1297,20 @@ onMounted(() => {
|
||||
loadSessionTasksFromStorage()
|
||||
const taskIds = getPollingTaskIds()
|
||||
if (taskIds.length > 0) {
|
||||
refreshTaskDetails(taskIds).then(() => {
|
||||
refreshTaskDetails(taskIds).then(async () => {
|
||||
let stateChanged = false
|
||||
let shouldRefreshHistory = false
|
||||
for (const taskId of taskIds) {
|
||||
if (isTaskTerminal(taskId)) {
|
||||
// 首屏恢复也以后端终态为准,避免依赖 _completed 造成卡轮询。
|
||||
removeSessionTaskFromStorage(taskId)
|
||||
stateChanged = true
|
||||
shouldRefreshHistory = true
|
||||
}
|
||||
}
|
||||
if (stateChanged) {
|
||||
if (shouldRefreshHistory) {
|
||||
await loadHistory()
|
||||
} else if (stateChanged) {
|
||||
syncResultState()
|
||||
}
|
||||
ensurePolling(true)
|
||||
|
||||
Reference in New Issue
Block a user