完善删除品牌asin
This commit is contained in:
@@ -128,8 +128,8 @@
|
||||
</div>
|
||||
|
||||
<div class="task-right split-result-actions">
|
||||
<span class="status" :class="item.success ? 'success' : 'failed'">
|
||||
{{ item.success ? '已完成' : '失败' }}
|
||||
<span class="status" :class="getTaskStatusInfo(item).className">
|
||||
{{ getTaskStatusInfo(item).text }}
|
||||
</span>
|
||||
<button
|
||||
v-if="item.openStoreUrl"
|
||||
@@ -192,8 +192,8 @@
|
||||
</div>
|
||||
|
||||
<div class="task-right split-result-actions">
|
||||
<span class="status" :class="item.success ? 'success' : 'failed'">
|
||||
{{ item.success ? '已完成' : '失败' }}
|
||||
<span class="status" :class="getTaskStatusInfo(item).className">
|
||||
{{ getTaskStatusInfo(item).text }}
|
||||
</span>
|
||||
<button
|
||||
v-if="item.openStoreUrl"
|
||||
@@ -262,17 +262,41 @@ const taskDetails = ref<Record<number, DeleteBrandTaskDetailVo>>({})
|
||||
const pollTimer = ref<number | null>(null)
|
||||
const pollingInFlight = ref(false)
|
||||
const displayPaths = computed(() => selectedPaths.value.slice(0, 10))
|
||||
const currentSectionItems = computed(() => currentRunItems.value)
|
||||
const runningTaskIdsInHistory = computed(() => {
|
||||
return historyItems.value
|
||||
.filter(item => {
|
||||
const status = item.taskId ? taskDetails.value[item.taskId]?.task?.status : null;
|
||||
// 优先认后端的实时状态
|
||||
if (status) return status === 'RUNNING';
|
||||
// 如果还没拿到底层详情,看 history 接口带回来的全局任务状态
|
||||
if (item.taskStatus) return item.taskStatus === 'RUNNING';
|
||||
// 最后降级到根据 success/error 猜测(向下兼容)
|
||||
return (item.taskId || 0) > 0 && item.success === false && !item.error;
|
||||
})
|
||||
.map(item => item.taskId);
|
||||
});
|
||||
|
||||
const currentSectionItems = computed(() => {
|
||||
// 当前运行的任务 = 用户手动触发的 + 历史记录里还在跑的
|
||||
const allCurrent = [...currentRunItems.value];
|
||||
|
||||
historyItems.value.forEach(hItem => {
|
||||
if (hItem.taskId && runningTaskIdsInHistory.value.includes(hItem.taskId)) {
|
||||
// 避免重复
|
||||
if (!allCurrent.some(c => c.taskId === hItem.taskId)) {
|
||||
allCurrent.push(hItem);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return allCurrent;
|
||||
});
|
||||
|
||||
const historySectionItems = computed(() =>
|
||||
historyItems.value.filter(
|
||||
(item) =>
|
||||
!currentRunItems.value.some(
|
||||
(current) =>
|
||||
(current.resultId || current.taskId || current.fileKey || current.sourceFilename) ===
|
||||
(item.resultId || item.taskId || item.fileKey || item.sourceFilename),
|
||||
),
|
||||
(item) => !currentSectionItems.value.some(c => c.taskId === item.taskId || (c.resultId && c.resultId === item.resultId))
|
||||
),
|
||||
)
|
||||
);
|
||||
const hasVisibleItems = computed(() => currentSectionItems.value.length > 0 || historySectionItems.value.length > 0)
|
||||
|
||||
function getTaskStatus(taskId?: number) {
|
||||
@@ -289,8 +313,9 @@ function getDisplayError(item: DeleteBrandResultItem) {
|
||||
|
||||
function shouldShowProgress(item: DeleteBrandResultItem) {
|
||||
const taskId = item.taskId
|
||||
if (!taskId || item.matched !== true) return false
|
||||
if (!taskId) return false
|
||||
const status = getTaskStatus(taskId)
|
||||
// 只要状态是运行中就显示进度条,不管 matched 了(matched 不对的状态应该在后端就标记为 FAILED)
|
||||
return status === 'RUNNING'
|
||||
}
|
||||
|
||||
@@ -301,7 +326,6 @@ function normalizeDeleteBrandItems(items: DeleteBrandResultItem[]) {
|
||||
...item,
|
||||
success: false,
|
||||
error: item.error || '未匹配到紫鸟店铺',
|
||||
taskId: undefined,
|
||||
openStoreUrl: undefined,
|
||||
}
|
||||
}
|
||||
@@ -344,10 +368,10 @@ function formatProgress(taskId: number) {
|
||||
if (info.phase) parts.push(`阶段:${info.phase}`)
|
||||
if (info.file_index && info.file_total) parts.push(`文件:${info.file_index}/${info.file_total}`)
|
||||
if (info.file_name) parts.push(`当前文件:${info.file_name}`)
|
||||
if (info.current_line !== undefined && info.total_lines !== undefined) parts.push(`进度:${info.current_line}/${info.total_lines}`)
|
||||
if (info.current_line != null && info.total_lines != null) parts.push(`进度:${info.current_line}/${info.total_lines}`)
|
||||
if (info.current_country) parts.push(`国家:${info.current_country}`)
|
||||
if (info.current_asin) parts.push(`ASIN:${info.current_asin}`)
|
||||
if (info.finished_files !== undefined) parts.push(`已完成文件:${info.finished_files}`)
|
||||
if (info.finished_files != null) parts.push(`已完成文件:${info.finished_files}`)
|
||||
return parts.join('|')
|
||||
}
|
||||
|
||||
@@ -356,10 +380,10 @@ function formatProgressPercent(taskId: number) {
|
||||
const info = taskDetails.value[taskId]?.line_progress?.info
|
||||
if (!info) return 0
|
||||
|
||||
if (info.current_line !== undefined && info.total_lines && info.total_lines > 0) {
|
||||
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.finished_files !== undefined && info.file_total && info.file_total > 0) {
|
||||
if (info.finished_files != null && info.file_total && info.file_total > 0) {
|
||||
return Math.max(0, Math.min(100, Math.round((info.finished_files / info.file_total) * 100)))
|
||||
}
|
||||
const status = taskDetails.value[taskId]?.task?.status
|
||||
@@ -377,14 +401,34 @@ function isTaskTerminal(taskId: number) {
|
||||
}
|
||||
|
||||
function getPollingTaskIds() {
|
||||
return Array.from(
|
||||
new Set(
|
||||
currentRunItems.value
|
||||
.filter((item) => item.matched && (item.taskId || 0) > 0)
|
||||
.map(getItemTaskId)
|
||||
.filter((id) => id > 0 && !isTaskTerminal(id)),
|
||||
),
|
||||
)
|
||||
const ids = new Set<number>()
|
||||
|
||||
// 1. 检查本会话触发的任务
|
||||
currentRunItems.value.forEach((item) => {
|
||||
if (item.taskId && !isTaskTerminal(item.taskId)) {
|
||||
ids.add(item.taskId)
|
||||
}
|
||||
})
|
||||
|
||||
// 2. 检查历史加载的任务
|
||||
historyItems.value.forEach((item) => {
|
||||
if (item.taskId) {
|
||||
if (taskDetails.value[item.taskId]) {
|
||||
// 如果已经拿到了详情,以后端详情为准
|
||||
if (!isTaskTerminal(item.taskId)) {
|
||||
ids.add(item.taskId)
|
||||
}
|
||||
} else {
|
||||
// 还没拿到详情,看 history 接口中的 taskStatus 或 success 标记
|
||||
const isRunning = item.taskStatus === 'RUNNING' || (!item.taskStatus && !item.success && !item.error);
|
||||
if (isRunning) {
|
||||
ids.add(item.taskId)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return Array.from(ids)
|
||||
}
|
||||
|
||||
function canDownloadTaskResult(item: DeleteBrandResultItem) {
|
||||
@@ -415,9 +459,14 @@ function getPollIntervalMs() {
|
||||
return document.visibilityState === 'visible' ? 4500 : 10000
|
||||
}
|
||||
|
||||
function scheduleNextPoll() {
|
||||
if (pollTimer.value) return
|
||||
pollTimer.value = window.setTimeout(async () => {
|
||||
function scheduleNextPoll(immediate = false) {
|
||||
if (pollTimer.value) {
|
||||
if (!immediate) return
|
||||
clearTimeout(pollTimer.value)
|
||||
pollTimer.value = null
|
||||
}
|
||||
|
||||
const executePoll = async () => {
|
||||
pollTimer.value = null
|
||||
if (pollingInFlight.value) {
|
||||
scheduleNextPoll()
|
||||
@@ -438,12 +487,35 @@ function scheduleNextPoll() {
|
||||
if (stillRunning) {
|
||||
scheduleNextPoll()
|
||||
}
|
||||
}, getPollIntervalMs())
|
||||
}
|
||||
|
||||
if (immediate) {
|
||||
executePoll()
|
||||
} else {
|
||||
pollTimer.value = window.setTimeout(executePoll, getPollIntervalMs())
|
||||
}
|
||||
}
|
||||
|
||||
function ensurePolling() {
|
||||
if (pollTimer.value) return
|
||||
scheduleNextPoll()
|
||||
function ensurePolling(immediate = false) {
|
||||
if (pollTimer.value && !immediate) return
|
||||
scheduleNextPoll(immediate)
|
||||
}
|
||||
|
||||
function getTaskStatusInfo(item: DeleteBrandResultItem) {
|
||||
// 1. 优先查实时详情中的状态
|
||||
const status = item.taskId ? taskDetails.value[item.taskId]?.task?.status : null
|
||||
if (status === 'SUCCESS') return { className: 'success', text: '已完成' }
|
||||
if (status === 'FAILED') return { className: 'failed', text: '失败' }
|
||||
if (status === 'RUNNING') return { className: 'running', text: '执行中' }
|
||||
|
||||
// 2. 其次看 history 接口带回来的后端任务状态
|
||||
if (item.taskStatus === 'SUCCESS') return { className: 'success', text: '已完成' }
|
||||
if (item.taskStatus === 'FAILED') return { className: 'failed', text: '失败' }
|
||||
if (item.taskStatus === 'RUNNING') return { className: 'running', text: '执行中' }
|
||||
|
||||
// 3. 最后根据 item 本身的 success 标识兜底
|
||||
if (item.success) return { className: 'success', text: '已完成' }
|
||||
return { className: 'failed', text: '失败' }
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
@@ -597,8 +669,12 @@ async function loadHistory() {
|
||||
const response = await getDeleteBrandHistory()
|
||||
historyItems.value = normalizeDeleteBrandItems(response.items || [])
|
||||
syncResultState()
|
||||
} catch {
|
||||
// ignore history load errors
|
||||
|
||||
// 关键:页面加载历史后,立刻拉取详情触发状态机
|
||||
await refreshTaskDetails()
|
||||
ensurePolling(true)
|
||||
} catch (err) {
|
||||
console.error('loadHistory error:', err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -712,6 +788,7 @@ onUnmounted(() => {
|
||||
.status { padding: 4px 10px; border-radius: 6px; font-size: 12px; white-space: nowrap; }
|
||||
.status.success { background: rgba(46, 204, 113, 0.18); color: #2ecc71; }
|
||||
.status.failed { background: rgba(231, 76, 60, 0.18); color: #ff6b6b; }
|
||||
.status.running { background: rgba(52, 152, 219, 0.18); color: #3498db; }
|
||||
.download { padding: 6px 10px; border-radius: 6px; font-size: 12px; background: rgba(52, 152, 219, 0.18); color: #69b6ff; }
|
||||
.download:hover { background: rgba(52, 152, 219, 0.28); }
|
||||
.btn-delete { padding: 6px 10px; border-radius: 6px; font-size: 12px; background: rgba(231, 76, 60, 0.12); color: #ff8f8f; }
|
||||
|
||||
@@ -229,6 +229,7 @@ export interface DeleteBrandResultItem {
|
||||
previewRows?: DeleteBrandPreviewRow[]
|
||||
success: boolean
|
||||
error?: string
|
||||
taskStatus?: string
|
||||
}
|
||||
|
||||
export interface DeleteBrandRunVo {
|
||||
|
||||
Reference in New Issue
Block a user