异常记录检测修改

This commit is contained in:
super
2026-05-04 19:04:55 +08:00
parent f46c231323
commit 111f30d150
70 changed files with 7182 additions and 252 deletions
@@ -215,6 +215,7 @@ import { expandBrandFolderRecursive, type BrandExpandFolderItem } from '@/shared
import {
deleteDeleteBrandHistory,
getDeleteBrandHistory,
getDeleteBrandResultDownloadUrl,
getDeleteBrandTaskDetails,
getDeleteBrandTaskProgress,
getDeleteBrandTaskDownloadUrl,
@@ -269,8 +270,9 @@ const timers = createCategorizedTimers('delete-brand')
const displayPaths = computed(() => selectedPaths.value.slice(0, 10))
const currentSectionItems = computed(() => {
const sortedTasks = [...sessionTasks.value].sort((left, right) => (right.createdAt || 0) - (left.createdAt || 0))
const allCurrent: DeleteBrandResultItem[] = [];
sessionTasks.value.forEach(task => {
sortedTasks.forEach(task => {
allCurrent.push(...task.items);
});
return allCurrent;
@@ -521,9 +523,10 @@ function shouldShowProgress(item: DeleteBrandResultItem) {
function normalizeDeleteBrandItems(items: DeleteBrandResultItem[]) {
return items.map((item) => {
if (item.matched || item.matchStatus === 'MATCHED' || item.matchStatus === 'INDEX_STALE') {
if (isUsableMatchedItem(item)) {
return {
...item,
matched: true,
_pushed: false,
_completed: false,
} as SessionDeleteBrandItem
@@ -540,10 +543,10 @@ function normalizeDeleteBrandItems(items: DeleteBrandResultItem[]) {
}
function formatMatchResult(item: DeleteBrandResultItem) {
if (item.matched && item.matchStatus === 'INDEX_STALE') {
return item.matchMessage || `已匹配 ${item.shopId || ''}(索引过期)`.trim()
if (item.matchStatus === 'INDEX_STALE' && isUsableMatchedItem(item)) {
return `已匹配 ${item.shopId || ''}(索引过期,可继续推送`.trim()
}
if (item.matchStatus === 'MATCHED') {
if (item.matchStatus === 'MATCHED' || item.matched) {
return `已匹配 ${item.shopId || ''}`.trim()
}
if (item.matchMessage) {
@@ -749,6 +752,12 @@ function getQueueStatus(item: DeleteBrandResultItem) {
return '已被全局判定为终态'
}
// 后端已经把单文件结果组装出来时,以文件终态为准。
// 多店铺同 taskId 场景下任务本身仍是 RUNNING,不能因此挡住下一个店铺入队。
if (isFileResultReady(item) || isFileResultReady(sessionItem)) {
return '本文件解析完毕'
}
// 先看前端会话态:一旦该文件已判定完成,优先展示完成,避免被 RUNNING 覆盖导致链式推进卡住。
if (sessionItem?._completed) {
return '本文件解析完毕'
@@ -791,6 +800,21 @@ function canDownloadTaskResult(item: DeleteBrandResultItem) {
return false
}
function isFileResultReady(item?: Partial<DeleteBrandResultItem> | null) {
if (!item) return false
const fileStatus = String(item.fileStatus || '').toUpperCase()
return Boolean(
item.fileReady
|| item.downloadUrl
|| fileStatus === 'SUCCESS'
|| fileStatus === 'COMPLETED',
)
}
function preferLatestValue<T>(latestValue: T | null | undefined, currentValue: T | null | undefined) {
return latestValue ?? currentValue
}
function mergeTaskDetailItemsIntoSession(detail: DeleteBrandTaskDetailVo) {
const taskId = detail?.task?.id
if (!taskId) return false
@@ -821,10 +845,21 @@ function mergeTaskDetailItemsIntoSession(detail: DeleteBrandTaskDetailVo) {
const merged = {
...item,
...latest,
// progress/batch 返回的是轻量文件状态,店铺匹配信息和执行 payload 可能为空;
// 不能覆盖本地解析出的完整店铺数据,否则后续文件会失去入队资格。
shopName: preferLatestValue(latest.shopName, item.shopName),
companyName: preferLatestValue(latest.companyName, item.companyName),
shopId: preferLatestValue(latest.shopId, item.shopId),
platform: preferLatestValue(latest.platform, item.platform),
openStoreUrl: preferLatestValue(latest.openStoreUrl, item.openStoreUrl),
matchStatus: preferLatestValue(latest.matchStatus, item.matchStatus),
matchMessage: preferLatestValue(latest.matchMessage, item.matchMessage),
matched: isUsableMatchedItem(item) || isUsableMatchedItem(latest),
countryCount: preferLatestValue(latest.countryCount, item.countryCount),
countries,
previewRows,
_pushed: item._pushed,
_completed: item._completed,
_completed: item._completed || isFileResultReady(latest),
} as SessionDeleteBrandItem
if (JSON.stringify(merged) !== JSON.stringify(item)) {
changed = true
@@ -837,7 +872,7 @@ function mergeTaskDetailItemsIntoSession(detail: DeleteBrandTaskDetailVo) {
mergedItems.push({
...latest,
_pushed: false,
_completed: false,
_completed: isFileResultReady(latest),
} as SessionDeleteBrandItem)
changed = true
}
@@ -956,6 +991,8 @@ function ensurePolling(immediate = false) {
}
function getTaskStatusInfo(item: DeleteBrandResultItem) {
if (item.fileReady || item.downloadUrl) return { className: 'success', text: '已完成' }
// 1. 优先查实时详情中的状态
const status = item.taskId ? taskDetails.value[item.taskId]?.task?.status : null
if (status === 'SUCCESS') return { className: 'success', text: '已完成' }
@@ -1053,13 +1090,9 @@ async function submitRun() {
})),
})
const normalizedItems = normalizeDeleteBrandItems(result.items || [])
const hasRunnableItems = normalizedItems.some((item) =>
item.matchStatus === 'MATCHED' || item.matchStatus === 'INDEX_STALE' || item.matched
)
const hasRunnableItems = normalizedItems.some((item) => isUsableMatchedItem(item))
const hasStaleMatchedItems = normalizedItems.some((item) => item.matchStatus === 'INDEX_STALE' && item.matched)
const hasBlockedItems = normalizedItems.some((item) =>
!item.matched && item.matchStatus !== 'MATCHED' && item.matchStatus !== 'INDEX_STALE'
)
const hasBlockedItems = normalizedItems.some((item) => !isUsableMatchedItem(item))
queuePushResult.value = hasRunnableItems
? '解析完成,可在左侧点击“推送到 Python 队列”开始串行处理'
@@ -1097,7 +1130,7 @@ async function downloadTaskResult(item: DeleteBrandResultItem) {
return
}
const api = getPywebviewApi()
const url = getDeleteBrandTaskDownloadUrl(taskId)
const url = item.resultId ? getDeleteBrandResultDownloadUrl(item.resultId) : getDeleteBrandTaskDownloadUrl(taskId)
const filename =
item.outputFilename ||
item.sourceFilename ||
@@ -1219,8 +1252,24 @@ function getItemKey(item: DeleteBrandResultItem) {
return `task:${item.taskId || 0}:${item.sourceFilename || ''}`
}
function isUsableMatchedItem(item: DeleteBrandResultItem) {
return Boolean(
item.matched
|| item.matchStatus === 'MATCHED'
|| (item.matchStatus === 'INDEX_STALE' && item.shopId),
)
}
function hasRunnableDeleteBrandPayload(item: DeleteBrandResultItem) {
return Boolean(
item.shopName
&& Array.isArray(item.countries)
&& item.countries.length > 0,
)
}
function canPushToQueue(item: DeleteBrandResultItem) {
return Boolean(item.taskId && (item.matchStatus === 'MATCHED' || item.matchStatus === 'INDEX_STALE' || item.matched))
return Boolean(item.taskId && (isUsableMatchedItem(item) || hasRunnableDeleteBrandPayload(item)))
}
function findNextAutoRunnableItem() {