更改下载方法名

This commit is contained in:
super
2026-04-06 22:26:44 +08:00
parent d8d5b56c13
commit 1b02160b20
12 changed files with 1411 additions and 716 deletions

View File

@@ -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)