From de9b7a0e9aa1bb1a70f3da907542c2fafa9d5b76 Mon Sep 17 00:00:00 2001 From: xiaoxia Date: Fri, 4 Sep 2026 13:48:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=88=90=E7=89=87=E5=BA=93=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E3=80=8C=E9=87=8D=E6=96=B0=E6=9F=A5=E9=87=8D=E3=80=8D?= =?UTF-8?q?=E6=8C=89=E9=92=AE=EF=BC=8C=E8=B0=83=E7=94=A8=20POST=20/videos/?= =?UTF-8?q?recompute-dedup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - api/products: 新增 recomputeDedup() 函数和 RecomputeDedupResponse 类型 - useRecomputeDedup hook: 封装 mutation,成功后刷新列表 + 提示入队数量 - ProductLibrary 页面头部:批量导出旁增加「重新查重」按钮,带 loading 状态 - ProductLibrary test: mock 补充 ReloadOutlined --- apps/web/src/api/products/index.ts | 3 +++ apps/web/src/api/products/products.ts | 15 +++++++++++ .../web/src/pages/products/ProductLibrary.tsx | 14 +++++++++- .../product-actions/useRecomputeDedup.ts | 27 +++++++++++++++++++ .../src/test/pages/ProductLibrary.test.tsx | 1 + 5 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/pages/products/hooks/product-actions/useRecomputeDedup.ts diff --git a/apps/web/src/api/products/index.ts b/apps/web/src/api/products/index.ts index b4bf23a95..5f1148b57 100644 --- a/apps/web/src/api/products/index.ts +++ b/apps/web/src/api/products/index.ts @@ -13,6 +13,8 @@ export type { VideoItem, } from "./types" +export type { RecomputeDedupResponse } from "./products" + // 工具函数 export { mapVideoToProductItem } from "./utils" @@ -25,4 +27,5 @@ export { updateReviewStatus, batchDownload, getBatchDownloadStatus, + recomputeDedup, } from "./products" diff --git a/apps/web/src/api/products/products.ts b/apps/web/src/api/products/products.ts index 7c0fcb19f..18680df7b 100644 --- a/apps/web/src/api/products/products.ts +++ b/apps/web/src/api/products/products.ts @@ -78,3 +78,18 @@ export const getBatchDownloadStatus = async (jobId: string): Promise => { + const response = await apiClient.post("/videos/recompute-dedup", { + video_ids: videoIds, + }) + return response.data +} diff --git a/apps/web/src/pages/products/ProductLibrary.tsx b/apps/web/src/pages/products/ProductLibrary.tsx index d4d2ea40d..d15cacc9c 100644 --- a/apps/web/src/pages/products/ProductLibrary.tsx +++ b/apps/web/src/pages/products/ProductLibrary.tsx @@ -11,7 +11,7 @@ * 产品卡片 → components/ProductCard(内联视频播放) */ import React from "react" -import { VideoCameraOutlined, DownloadOutlined } from "@ant-design/icons" +import { VideoCameraOutlined, DownloadOutlined, ReloadOutlined } from "@ant-design/icons" import { Button } from "@/components/ui" import { ProductCard } from "./components/ProductCard" import { ProductFilterBar } from "./components/ProductFilterBar" @@ -19,6 +19,7 @@ import { ProductBatchBar } from "./components/ProductBatchBar" import { ProductEmptyState } from "./components/ProductEmptyState" import { useProductList } from "./hooks/useProductList" import { useProductActions } from "./hooks/useProductActions" +import { useRecomputeDedup } from "./hooks/product-actions/useRecomputeDedup" import "./products.css" const ProductLibrary: React.FC = () => { @@ -67,6 +68,8 @@ const ProductLibrary: React.FC = () => { setPlayingProduct: () => {}, // 不再使用弹窗播放 }) + const { recomputeDedup, isRecomputing } = useRecomputeDedup() + // ── Loading 状态 ── if (isLoading) { return @@ -94,6 +97,15 @@ const ProductLibrary: React.FC = () => { + diff --git a/apps/web/src/pages/products/hooks/product-actions/useRecomputeDedup.ts b/apps/web/src/pages/products/hooks/product-actions/useRecomputeDedup.ts new file mode 100644 index 000000000..40341c7c7 --- /dev/null +++ b/apps/web/src/pages/products/hooks/product-actions/useRecomputeDedup.ts @@ -0,0 +1,27 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { message } from "antd" +import { recomputeDedup } from "@/api/products" + +export function useRecomputeDedup() { + const queryClient = useQueryClient() + + const mutation = useMutation({ + mutationFn: () => recomputeDedup(), + onSuccess: (data) => { + queryClient.invalidateQueries({ queryKey: ["products"] }) + if (data.enqueued > 0) { + message.success(`已提交 ${data.enqueued} 个视频的查重任务,后台处理中`) + } else { + message.info("所有视频查重率已是最新,无需重算") + } + }, + onError: () => { + message.error("查重任务提交失败,请稍后重试") + }, + }) + + return { + recomputeDedup: () => mutation.mutate(), + isRecomputing: mutation.isPending, + } +} diff --git a/apps/web/src/test/pages/ProductLibrary.test.tsx b/apps/web/src/test/pages/ProductLibrary.test.tsx index f2c8629e2..d157b1d04 100644 --- a/apps/web/src/test/pages/ProductLibrary.test.tsx +++ b/apps/web/src/test/pages/ProductLibrary.test.tsx @@ -102,6 +102,7 @@ vi.mock("@ant-design/icons", () => ({ SearchOutlined: () => , ShareAltOutlined: () => , VideoCameraOutlined: () => , + ReloadOutlined: () => , })) vi.mock("@/store/authStore", () => ({ -- 2.54.0