From 14a723ab1c84a85d9766a20146e6856dff94f8b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BB=84=E8=87=AA=E8=BE=BE?= <980324341@qq.com> Date: Sun, 13 Sep 2026 23:32:27 +0800 Subject: [PATCH] =?UTF-8?q?refactor(=E5=93=81=E7=89=8C=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E9=A1=B5):=20=E5=8E=86=E5=8F=B2=E6=89=B9=E9=87=8F=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=8A=BD=E4=B8=BA=20runBatchDelete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 16 个品牌页逐字相同的 batchDeleteHistory 循环(逐条删除 + 容忍个别失败计数) 收敛为 shared/utils/batch-delete.ts 的 runBatchDelete;各页仅保留自己的删除调用 与提示文案。补单测 4 例(空列表 / 全成功 / 部分失败继续 / 非 Error 异常)。 净减约 120 行;vue-tsc 构建与 699 个前端单测通过。 --- .../components/BrandAppearancePatentTab.vue | 14 ++----- .../pages/brand/components/BrandBrandTab.vue | 14 ++----- .../brand/components/BrandCollectDataTab.vue | 14 ++----- .../brand/components/BrandConvertTab.vue | 14 ++----- .../pages/brand/components/BrandDedupeTab.vue | 14 ++----- .../brand/components/BrandDeleteBrandTab.vue | 14 ++----- .../brand/components/BrandPatrolDeleteTab.vue | 14 ++----- .../brand/components/BrandPriceTrackTab.vue | 14 ++----- .../brand/components/BrandProductRiskTab.vue | 14 ++----- .../brand/components/BrandPublishTab.vue | 14 ++----- .../brand/components/BrandQueryAsinTab.vue | 14 ++----- .../components/BrandShopDataCrawlTab.vue | 14 ++----- .../brand/components/BrandShopMatchTab.vue | 14 ++----- .../brand/components/BrandSimilarAsinTab.vue | 14 ++----- .../pages/brand/components/BrandSplitTab.vue | 14 ++----- .../brand/components/BrandWithdrawTab.vue | 14 ++----- frontend-vue/src/shared/utils/batch-delete.ts | 26 ++++++++++++ frontend-vue/tests/batch-delete.test.ts | 42 +++++++++++++++++++ 18 files changed, 132 insertions(+), 160 deletions(-) create mode 100644 frontend-vue/src/shared/utils/batch-delete.ts create mode 100644 frontend-vue/tests/batch-delete.test.ts diff --git a/frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue b/frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue index b6c95cf9..329ed491 100644 --- a/frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandAppearancePatentTab.vue @@ -112,6 +112,7 @@ import { import { passGuard } from '@/shared/dispatch-guard-ui' import { formatDateTime } from '@/shared/utils/datetime' import { uploadPathsToJava } from '@/shared/utils/upload-to-java' +import { runBatchDelete } from '@/shared/utils/batch-delete' const selectedFileNames = ref([]) const uploadedFiles = ref([]) @@ -915,19 +916,12 @@ onUnmounted(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteTaskRecord(itemSource(view)) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteTaskRecord(itemSource(view))) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandBrandTab.vue b/frontend-vue/src/pages/brand/components/BrandBrandTab.vue index 7743d611..df725f9e 100644 --- a/frontend-vue/src/pages/brand/components/BrandBrandTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandBrandTab.vue @@ -127,6 +127,7 @@ import type { UploadFileVo } from '@/shared/api/upload.ts' import type { UploadedFileRef } from '@/shared/api/types/upload.ts' import { formatDateTime } from '@/shared/utils/datetime' import { uploadPathsToJava } from '@/shared/utils/upload-to-java' +import { runBatchDelete } from '@/shared/utils/batch-delete' const uploadedFiles = ref([]) const strategy = ref<'Terms' | 'Simple'>('Simple') @@ -532,19 +533,12 @@ onBeforeUnmount(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteTask(itemSource(view)) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteTask(itemSource(view))) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandCollectDataTab.vue b/frontend-vue/src/pages/brand/components/BrandCollectDataTab.vue index 9cb1785e..5cea8a8a 100644 --- a/frontend-vue/src/pages/brand/components/BrandCollectDataTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandCollectDataTab.vue @@ -181,6 +181,7 @@ import { } from '@/shared/api/java-modules' import { formatDateTime } from '@/shared/utils/datetime' import { uploadPathsToJava } from '@/shared/utils/upload-to-java' +import { runBatchDelete } from '@/shared/utils/batch-delete' const COUNTRY_OPTIONS = [ { code: 'DE', label: '德国' }, @@ -722,19 +723,12 @@ onBeforeUnmount(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteTaskRecord(itemSource(view)) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteTaskRecord(itemSource(view))) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandConvertTab.vue b/frontend-vue/src/pages/brand/components/BrandConvertTab.vue index 2c4423b6..59f1789e 100644 --- a/frontend-vue/src/pages/brand/components/BrandConvertTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandConvertTab.vue @@ -170,6 +170,7 @@ import { EXCEL_EXTENSIONS, checkSelectedFiles, guardBlocked } from '@/shared/dis import { passGuard } from '@/shared/dispatch-guard-ui' import { formatDateTime } from '@/shared/utils/datetime' import { uploadPathsToJava } from '@/shared/utils/upload-to-java' +import { runBatchDelete } from '@/shared/utils/batch-delete' const convertSelectedPaths = ref([]) const convertArchiveName = ref('') @@ -411,19 +412,12 @@ onMounted(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteConvertHistoryRecord(itemSource(view).resultId!) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteConvertHistoryRecord(itemSource(view).resultId!)) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandDedupeTab.vue b/frontend-vue/src/pages/brand/components/BrandDedupeTab.vue index b131013f..c9c192cf 100644 --- a/frontend-vue/src/pages/brand/components/BrandDedupeTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandDedupeTab.vue @@ -128,6 +128,7 @@ import { EXCEL_EXTENSIONS, checkSelectedFiles, guardBlocked } from '@/shared/dis import { passGuard } from '@/shared/dispatch-guard-ui' import { formatDateTime } from '@/shared/utils/datetime' import { uploadPathsToJava } from '@/shared/utils/upload-to-java' +import { runBatchDelete } from '@/shared/utils/batch-delete' const cleanAvailableColumns = ref([]) const cleanSelectedColumns = ref([]) @@ -443,19 +444,12 @@ onMounted(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteCleanHistoryRecord(itemSource(view).resultId!) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteCleanHistoryRecord(itemSource(view).resultId!)) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue b/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue index 0c1b301b..0d614c8c 100644 --- a/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandDeleteBrandTab.vue @@ -141,6 +141,7 @@ import { passGuard } from '@/shared/dispatch-guard-ui' import { useZiniaoVersion } from '@/shared/utils/ziniao-version' import { formatDateTime } from '@/shared/utils/datetime' import { uploadPathsToJava } from '@/shared/utils/upload-to-java' +import { runBatchDelete } from '@/shared/utils/batch-delete' interface SessionDeleteBrandItem extends DeleteBrandResultItem { _pushed?: boolean @@ -1499,19 +1500,12 @@ onUnmounted(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteHistoryRecord(itemSource(view).resultId!) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteHistoryRecord(itemSource(view).resultId!)) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandPatrolDeleteTab.vue b/frontend-vue/src/pages/brand/components/BrandPatrolDeleteTab.vue index 57b7e9f9..93df0424 100644 --- a/frontend-vue/src/pages/brand/components/BrandPatrolDeleteTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandPatrolDeleteTab.vue @@ -274,6 +274,7 @@ import { } from "@/shared/country-options"; import { formatDateTime } from '@/shared/utils/datetime' import { useHistoryPolling } from '@/shared/composables/useHistoryPolling' +import { runBatchDelete } from '@/shared/utils/batch-delete' const MAX_TRANSIENT_ERRORS = 30; /** 任务终态后等待结果文件(Java 侧异步生成)的最大轮次,12 × 10s ≈ 2 分钟 */ @@ -1255,19 +1256,12 @@ onUnmounted(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteTaskRecord(itemSource(view)) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteTaskRecord(itemSource(view))) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandPriceTrackTab.vue b/frontend-vue/src/pages/brand/components/BrandPriceTrackTab.vue index 18003fda..5d4fae60 100644 --- a/frontend-vue/src/pages/brand/components/BrandPriceTrackTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandPriceTrackTab.vue @@ -259,6 +259,7 @@ import { } from '@/shared/api/java-modules' import { formatDateTime } from '@/shared/utils/datetime' import { uploadPathsToJava } from '@/shared/utils/upload-to-java' +import { runBatchDelete } from '@/shared/utils/batch-delete' const ziniaoVersion = useZiniaoVersion() @@ -1660,19 +1661,12 @@ onUnmounted(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteTaskRecord(itemSource(view)) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteTaskRecord(itemSource(view))) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandProductRiskTab.vue b/frontend-vue/src/pages/brand/components/BrandProductRiskTab.vue index 425b56af..7860736e 100644 --- a/frontend-vue/src/pages/brand/components/BrandProductRiskTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandProductRiskTab.vue @@ -175,6 +175,7 @@ import { passGuard } from '@/shared/dispatch-guard-ui' import ZiniaoVersionSetting from '@/shared/components/ZiniaoVersionSetting.vue' import { useZiniaoVersion } from '@/shared/utils/ziniao-version' import { formatDateTime } from '@/shared/utils/datetime' +import { runBatchDelete } from '@/shared/utils/batch-delete' const shopInput = ref('') const candidates = ref([]) @@ -1256,19 +1257,12 @@ onUnmounted(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteTaskRecord(itemSource(view)) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteTaskRecord(itemSource(view))) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandPublishTab.vue b/frontend-vue/src/pages/brand/components/BrandPublishTab.vue index 84687a9f..858baf6c 100644 --- a/frontend-vue/src/pages/brand/components/BrandPublishTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandPublishTab.vue @@ -220,6 +220,7 @@ import { import { passGuard } from '@/shared/dispatch-guard-ui' import { useZiniaoVersion, type ZiniaoVersion } from '@/shared/utils/ziniao-version' import { uploadPathsToJava } from '@/shared/utils/upload-to-java' +import { runBatchDelete } from '@/shared/utils/batch-delete' const COUNTRY_OPTIONS = [ { code: 'DE', label: '德国' }, @@ -1178,19 +1179,12 @@ onBeforeUnmount(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteTaskRecord(itemSource(view)) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteTaskRecord(itemSource(view))) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandQueryAsinTab.vue b/frontend-vue/src/pages/brand/components/BrandQueryAsinTab.vue index d8985333..2571c981 100644 --- a/frontend-vue/src/pages/brand/components/BrandQueryAsinTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandQueryAsinTab.vue @@ -216,6 +216,7 @@ import ZiniaoVersionSetting from "@/shared/components/ZiniaoVersionSetting.vue"; import { useZiniaoVersion } from "@/shared/utils/ziniao-version"; import { formatDateTime } from '@/shared/utils/datetime' import { useHistoryPolling } from '@/shared/composables/useHistoryPolling' +import { runBatchDelete } from '@/shared/utils/batch-delete' const MAX_TRANSIENT_ERRORS = 30; const ziniaoVersion = useZiniaoVersion(); @@ -1082,19 +1083,12 @@ onUnmounted(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteTaskRecord(itemSource(view)) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteTaskRecord(itemSource(view))) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandShopDataCrawlTab.vue b/frontend-vue/src/pages/brand/components/BrandShopDataCrawlTab.vue index 5f3fb21c..4c3b612d 100644 --- a/frontend-vue/src/pages/brand/components/BrandShopDataCrawlTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandShopDataCrawlTab.vue @@ -152,6 +152,7 @@ import { type ShopDataCrawlShopItem, type ShopDataCrawlTaskDetailVo, } from '@/shared/api/java-modules' +import { runBatchDelete } from '@/shared/utils/batch-delete' const COUNTRY_OPTIONS = [ { code: 'UK', label: '英国' }, @@ -583,19 +584,12 @@ onUnmounted(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteTaskRecord(itemSource(view)) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteTaskRecord(itemSource(view))) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandShopMatchTab.vue b/frontend-vue/src/pages/brand/components/BrandShopMatchTab.vue index 5185ef52..c56ed9cd 100644 --- a/frontend-vue/src/pages/brand/components/BrandShopMatchTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandShopMatchTab.vue @@ -121,6 +121,7 @@ import { getTaskPollIntervalMs } from '@/shared/task-progress-config' import { createCategorizedTimers } from '@/shared/utils/categorized-timers' import { saveUrlWithProgress } from '@/shared/utils/download-progress' import { useZiniaoVersion } from '@/shared/utils/ziniao-version' +import { runBatchDelete } from '@/shared/utils/batch-delete' const COUNTRY_OPTIONS = [{ code: 'DE', label: '德国' }, { code: 'UK', label: '英国' }, { code: 'FR', label: '法国' }, { code: 'IT', label: '意大利' }, { code: 'ES', label: '西班牙' }] as const const shopInput = ref('') @@ -553,19 +554,12 @@ onUnmounted(() => { disposed = true; stopPolling(); stopScheduleHeartbeat(); cle * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteTaskRecord(itemSource(view)) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteTaskRecord(itemSource(view))) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandSimilarAsinTab.vue b/frontend-vue/src/pages/brand/components/BrandSimilarAsinTab.vue index 57722199..406644f0 100644 --- a/frontend-vue/src/pages/brand/components/BrandSimilarAsinTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandSimilarAsinTab.vue @@ -152,6 +152,7 @@ import { import { passGuard } from '@/shared/dispatch-guard-ui' import { formatDateTime } from '@/shared/utils/datetime' import { uploadPathsToJava } from '@/shared/utils/upload-to-java' +import { runBatchDelete } from '@/shared/utils/batch-delete' const selectedFileNames = ref([]) const uploadedFiles = ref([]) @@ -1069,19 +1070,12 @@ onUnmounted(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteTaskRecord(itemSource(view)) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteTaskRecord(itemSource(view))) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandSplitTab.vue b/frontend-vue/src/pages/brand/components/BrandSplitTab.vue index 428dd620..d9269ecd 100644 --- a/frontend-vue/src/pages/brand/components/BrandSplitTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandSplitTab.vue @@ -149,6 +149,7 @@ import { EXCEL_EXTENSIONS, checkSelectedFiles, guardBlocked } from '@/shared/dis import { passGuard } from '@/shared/dispatch-guard-ui' import { formatDateTime } from '@/shared/utils/datetime' import { uploadPathsToJava } from '@/shared/utils/upload-to-java' +import { runBatchDelete } from '@/shared/utils/batch-delete' const splitSelectedPaths = ref([]) const splitArchiveName = ref('') @@ -399,19 +400,12 @@ onMounted(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteSplitHistoryRecord(itemSource(view).resultId!) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteSplitHistoryRecord(itemSource(view).resultId!)) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/pages/brand/components/BrandWithdrawTab.vue b/frontend-vue/src/pages/brand/components/BrandWithdrawTab.vue index 1df364dc..c2c0bd7e 100644 --- a/frontend-vue/src/pages/brand/components/BrandWithdrawTab.vue +++ b/frontend-vue/src/pages/brand/components/BrandWithdrawTab.vue @@ -256,6 +256,7 @@ import ZiniaoVersionSetting from "@/shared/components/ZiniaoVersionSetting.vue"; import { useZiniaoVersion } from "@/shared/utils/ziniao-version"; import { formatDateTime } from '@/shared/utils/datetime' import { useHistoryPolling } from '@/shared/composables/useHistoryPolling' +import { runBatchDelete } from '@/shared/utils/batch-delete' const MAX_TRANSIENT_ERRORS = 30; const ziniaoVersion = useZiniaoVersion(); @@ -1215,19 +1216,12 @@ onUnmounted(() => { * 历史记录批量删除:复用单条删除逻辑(删除后原函数各自刷新列表) */ async function batchDeleteHistory(views: TaskItemView[]) { - if (!views.length) return - let failed = 0 - for (const view of views) { - try { - await deleteTaskRecord(itemSource(view)) - } catch { - failed += 1 - } - } + const { total, failed } = await runBatchDelete(views, (view) => deleteTaskRecord(itemSource(view))) + if (!total) return if (failed > 0) { ElMessage.warning(`删除完成,${failed} 条失败`) } else { - ElMessage.success(`已删除 ${views.length} 条历史记录`) + ElMessage.success(`已删除 ${total} 条历史记录`) } } diff --git a/frontend-vue/src/shared/utils/batch-delete.ts b/frontend-vue/src/shared/utils/batch-delete.ts new file mode 100644 index 00000000..93dc70e7 --- /dev/null +++ b/frontend-vue/src/shared/utils/batch-delete.ts @@ -0,0 +1,26 @@ +export interface BatchDeleteResult { + /** 尝试删除的条数 */ + total: number + /** 删除失败的条数(单条失败不中断整批) */ + failed: number +} + +/** + * 历史任务批量删除:逐条删除,容忍个别失败并计数,供各工具页统一提示。 + * + * 单条失败继续删剩下的(不中断整批),返回成功的数量给调用方决定文案。 + */ +export async function runBatchDelete( + items: readonly T[], + deleteOne: (item: T) => Promise, +): Promise { + let failed = 0 + for (const item of items) { + try { + await deleteOne(item) + } catch { + failed += 1 + } + } + return { total: items.length, failed } +} diff --git a/frontend-vue/tests/batch-delete.test.ts b/frontend-vue/tests/batch-delete.test.ts new file mode 100644 index 00000000..97708727 --- /dev/null +++ b/frontend-vue/tests/batch-delete.test.ts @@ -0,0 +1,42 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { runBatchDelete } from '../src/shared/utils/batch-delete.ts' + +test('空列表不调用删除并返回 0', async () => { + let calls = 0 + const result = await runBatchDelete([], async () => { + calls += 1 + }) + + assert.deepEqual(result, { total: 0, failed: 0 }) + assert.equal(calls, 0) +}) + +test('全部成功时 failed 为 0', async () => { + const seen: number[] = [] + const result = await runBatchDelete([1, 2, 3], async (n) => { + seen.push(n) + }) + + assert.deepEqual(result, { total: 3, failed: 0 }) + assert.deepEqual(seen, [1, 2, 3]) +}) + +test('个别失败继续删其余并计数', async () => { + const seen: number[] = [] + const result = await runBatchDelete([1, 2, 3, 4], async (n) => { + seen.push(n) + if (n === 2 || n === 4) throw new Error('删除失败') + }) + + assert.deepEqual(result, { total: 4, failed: 2 }) + assert.deepEqual(seen, [1, 2, 3, 4], '失败后应继续处理后续条目') +}) + +test('抛出非 Error 也计入失败', async () => { + const result = await runBatchDelete(['a'], async () => { + throw 'boom' + }) + + assert.deepEqual(result, { total: 1, failed: 1 }) +})