完成品牌删除模块

This commit is contained in:
super
2026-03-29 00:22:45 +08:00
parent 9c6232bc85
commit 9e57fa5762
22 changed files with 91002 additions and 70 deletions

View File

@@ -240,6 +240,7 @@ import {
deleteDeleteBrandHistory,
getDeleteBrandHistory,
getDeleteBrandTaskDetail,
getDeleteBrandTaskDetails,
getDeleteBrandTaskDownloadUrl,
runDeleteBrand,
type DeleteBrandPreviewRow,
@@ -260,6 +261,7 @@ const queuePushResult = ref('')
const queuePayloadText = ref('')
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 historySectionItems = computed(() =>
@@ -276,7 +278,7 @@ const hasVisibleItems = computed(() => currentSectionItems.value.length > 0 || h
function normalizeDeleteBrandItems(items: DeleteBrandResultItem[]) {
return items.map((item) => {
if (!item.matched) {
if (item.matched === false) {
return {
...item,
success: false,
@@ -351,6 +353,22 @@ function isTaskRunning(taskId: number) {
return status === 'RUNNING'
}
function isTaskTerminal(taskId: number) {
const status = taskDetails.value[taskId]?.task?.status
return status === 'SUCCESS' || status === 'FAILED'
}
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)),
),
)
}
function canDownloadTaskResult(item: DeleteBrandResultItem) {
const taskId = item.taskId
if (!taskId || !item.matched) return false
@@ -358,32 +376,61 @@ function canDownloadTaskResult(item: DeleteBrandResultItem) {
return status === 'SUCCESS'
}
async function refreshTaskDetails() {
const taskIds = Array.from(new Set(resultItems.value.map(getItemTaskId).filter((id) => id > 0)))
for (const id of taskIds) {
try {
const detail = await getDeleteBrandTaskDetail(id)
taskDetails.value[id] = detail
} catch {
// ignore polling failures
async function refreshTaskDetails(taskIds?: number[]) {
const ids = taskIds || getPollingTaskIds()
if (!ids.length) return
try {
const batch = await getDeleteBrandTaskDetails(ids)
for (const detail of batch.items || []) {
const id = detail?.task?.id
if (typeof id === 'number' && id > 0) {
taskDetails.value[id] = detail
}
}
} catch {
// ignore polling failures
}
}
function getPollIntervalMs() {
return document.visibilityState === 'visible' ? 4500 : 10000
}
function scheduleNextPoll() {
if (pollTimer.value) return
pollTimer.value = window.setTimeout(async () => {
pollTimer.value = null
if (pollingInFlight.value) {
scheduleNextPoll()
return
}
const taskIds = getPollingTaskIds()
if (!taskIds.length) return
pollingInFlight.value = true
try {
await refreshTaskDetails(taskIds)
} finally {
pollingInFlight.value = false
}
const stillRunning = taskIds.some((id) => isTaskRunning(id))
if (stillRunning) {
scheduleNextPoll()
}
}, getPollIntervalMs())
}
function ensurePolling() {
if (pollTimer.value) return
pollTimer.value = window.setInterval(async () => {
await refreshTaskDetails()
const stillRunning = Object.keys(taskDetails.value).some((id) => isTaskRunning(Number(id)))
if (!stillRunning) {
stopPolling()
}
}, 1500)
scheduleNextPoll()
}
function stopPolling() {
if (pollTimer.value) {
window.clearInterval(pollTimer.value)
window.clearTimeout(pollTimer.value)
pollTimer.value = null
}
}
@@ -464,7 +511,7 @@ async function submitRun() {
currentRunItems.value = normalizedItems
syncResultState()
await refreshTaskDetails()
await refreshTaskDetails(getPollingTaskIds())
ensurePolling()
const api = getPywebviewApi()
@@ -537,13 +584,39 @@ async function loadHistory() {
}
}
function removeRecordFromView(resultId: number) {
const removedFromCurrent = currentRunItems.value.find((item) => item.resultId === resultId)
const removedTaskId = removedFromCurrent?.taskId
currentRunItems.value = currentRunItems.value.filter((item) => item.resultId !== resultId)
historyItems.value = historyItems.value.filter((item) => item.resultId !== resultId)
if (removedTaskId && !currentRunItems.value.some((item) => item.taskId === removedTaskId)) {
delete taskDetails.value[removedTaskId]
}
syncResultState()
if (getPollingTaskIds().length === 0) {
stopPolling()
}
}
async function deleteHistoryRecord(resultId: number) {
try {
await deleteDeleteBrandHistory(resultId)
removeRecordFromView(resultId)
await loadHistory()
ElMessage.success('已删除')
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '删除失败')
const message = error instanceof Error ? error.message : '删除失败'
// 后端可能已被清理/重复点击导致「记录不存在」,前端也应立即移除并停止轮询
if (typeof message === 'string' && message.includes('记录不存在')) {
removeRecordFromView(resultId)
ElMessage.success('已删除')
return
}
ElMessage.error(message)
}
}

View File

@@ -205,12 +205,17 @@ export interface DeleteBrandTaskDetailVo {
line_progress: DeleteBrandLineProgress
}
export interface DeleteBrandTaskBatchVo {
items: DeleteBrandTaskDetailVo[]
missingTaskIds: number[]
}
export interface DeleteBrandResultItem {
resultId?: number
fileKey?: string
sourceFilename: string
shopName?: string
matched: boolean
matched?: boolean
shopId?: string
platform?: string
openStoreUrl?: string
@@ -365,6 +370,10 @@ export function getDeleteBrandTaskDetail(taskId: number) {
return unwrapJavaResponse(get<JavaApiResponse<DeleteBrandTaskDetailVo>>(`${JAVA_API_PREFIX}/delete-brand/tasks/${taskId}`))
}
export function getDeleteBrandTaskDetails(taskIds: number[]) {
return unwrapJavaResponse(post<JavaApiResponse<DeleteBrandTaskBatchVo>, { taskIds: number[] }>(`${JAVA_API_PREFIX}/delete-brand/tasks/batch`, { taskIds }))
}
export function getDeleteBrandTaskDownloadUrl(taskId: number) {
return getJavaDownloadUrl(`/delete-brand/tasks/${taskId}/download`)
}