diff --git a/apps/web/src/pages/products/hooks/product-actions/useBatchDelete.ts b/apps/web/src/pages/products/hooks/product-actions/useBatchDelete.ts new file mode 100755 index 000000000..6505e1e72 --- /dev/null +++ b/apps/web/src/pages/products/hooks/product-actions/useBatchDelete.ts @@ -0,0 +1,30 @@ +import { useQueryClient } from "@tanstack/react-query" +import { message } from "antd" +import { deleteProduct } from "@/api/products" + +interface UseBatchDeleteOptions { + selectedIds: Set + clearSelection: () => void +} + +export function useBatchDelete({ selectedIds, clearSelection }: UseBatchDeleteOptions) { + const queryClient = useQueryClient() + + const handleBatchDelete = async () => { + const ids = Array.from(selectedIds) + let successCount = 0 + for (const id of ids) { + try { + await deleteProduct(id) + successCount++ + } catch { + // 忽略单个失败 + } + } + queryClient.invalidateQueries({ queryKey: ["products"] }) + clearSelection() + message.success(`已批量删除 ${successCount}/${ids.length} 个视频`) + } + + return { handleBatchDelete } +} diff --git a/apps/web/src/pages/products/hooks/product-actions/useBatchDownload.ts b/apps/web/src/pages/products/hooks/product-actions/useBatchDownload.ts new file mode 100755 index 000000000..1ab0081cc --- /dev/null +++ b/apps/web/src/pages/products/hooks/product-actions/useBatchDownload.ts @@ -0,0 +1,53 @@ +import { useState, useCallback } from "react" +import { message } from "antd" +import { batchDownload, getBatchDownloadStatus } from "@/api/products" + +interface UseBatchDownloadOptions { + selectedIds: Set + clearSelection: () => void +} + +export function useBatchDownload({ selectedIds, clearSelection }: UseBatchDownloadOptions) { + const [batchDownloading, setBatchDownloading] = useState(false) + + const handleBatchDownload = useCallback(async () => { + const ids = Array.from(selectedIds) + if (ids.length === 0) return + setBatchDownloading(true) + try { + const { job_id } = await batchDownload(ids) + message.info(`批量下载任务已创建,正在打包 ${ids.length} 个视频...`) + + let attempts = 0 + const maxAttempts = 60 + const poll = async (): Promise => { + if (attempts >= maxAttempts) { + message.warning("打包超时,请稍后在消息中心查看") + return + } + attempts++ + await new Promise((r) => setTimeout(r, 2000)) + const status = await getBatchDownloadStatus(job_id) + if (status.status === "completed" && status.download_url) { + const a = document.createElement("a") + a.href = status.download_url + a.download = "" + a.click() + message.success(`已打包下载 ${ids.length} 个视频`) + clearSelection() + } else if (status.status === "failed") { + message.error("批量下载失败,请重试") + } else { + await poll() + } + } + await poll() + } catch { + message.error("发起批量下载失败") + } finally { + setBatchDownloading(false) + } + }, [selectedIds, clearSelection]) + + return { batchDownloading, handleBatchDownload } +} diff --git a/apps/web/src/pages/products/hooks/product-actions/useProductMutations.ts b/apps/web/src/pages/products/hooks/product-actions/useProductMutations.ts new file mode 100755 index 000000000..5dfe70733 --- /dev/null +++ b/apps/web/src/pages/products/hooks/product-actions/useProductMutations.ts @@ -0,0 +1,37 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { message } from "antd" +import { deleteProduct, updateReviewStatus, type ReviewStatus } from "@/api/products" + +export function useProductMutations() { + const queryClient = useQueryClient() + + const deleteMutation = useMutation({ + mutationFn: deleteProduct, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["products"] }) + message.success("已删除") + }, + onError: () => { + message.error("删除失败") + }, + }) + + const reviewMutation = useMutation({ + mutationFn: ({ id, status }: { id: string; status: ReviewStatus }) => + updateReviewStatus(id, status), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["products"] }) + message.success("复核状态已更新") + }, + onError: () => { + message.error("更新复核状态失败") + }, + }) + + return { + deleteMutation, + reviewMutation, + isDeleting: deleteMutation.isPending, + isUpdatingReview: reviewMutation.isPending, + } +} diff --git a/apps/web/src/pages/products/hooks/useProductActions.ts b/apps/web/src/pages/products/hooks/useProductActions.ts old mode 100644 new mode 100755 index eefd6e073..84a77a58b --- a/apps/web/src/pages/products/hooks/useProductActions.ts +++ b/apps/web/src/pages/products/hooks/useProductActions.ts @@ -1,17 +1,11 @@ -import { useState } from "react" -import { useMutation, useQueryClient } from "@tanstack/react-query" import { useNavigate } from "react-router-dom" import { message } from "antd" -import { - deleteProduct, - getProductDownloadUrl, - updateReviewStatus, - batchDownload, - getBatchDownloadStatus, - type ReviewStatus, -} from "@/api/products" +import { getProductDownloadUrl } from "@/api/products" import type { ProductItem } from "../types" import { getNextReviewStatus } from "../utils" +import { useBatchDownload } from "./product-actions/useBatchDownload" +import { useBatchDelete } from "./product-actions/useBatchDelete" +import { useProductMutations } from "./product-actions/useProductMutations" interface UseProductActionsOptions { selectedIds: Set @@ -26,35 +20,17 @@ export const useProductActions = ({ products, setPlayingProduct, }: UseProductActionsOptions) => { - const queryClient = useQueryClient() const navigate = useNavigate() - /* ── 删除 mutation ── */ - const deleteMutation = useMutation({ - mutationFn: deleteProduct, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["products"] }) - message.success("已删除") - }, - onError: () => { - message.error("删除失败") - }, + const { deleteMutation, reviewMutation, isDeleting, isUpdatingReview } = useProductMutations() + + const { batchDownloading, handleBatchDownload } = useBatchDownload({ + selectedIds, + clearSelection, }) - /* ── 复核状态 mutation ── */ - const reviewMutation = useMutation({ - mutationFn: ({ id, status }: { id: string; status: ReviewStatus }) => - updateReviewStatus(id, status), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["products"] }) - message.success("复核状态已更新") - }, - onError: () => { - message.error("更新复核状态失败") - }, - }) + const { handleBatchDelete } = useBatchDelete({ selectedIds, clearSelection }) - /* 下载 — 调用真实 API 获取下载链接 */ const handleDownload = async (product: ProductItem) => { if (product.status !== "completed") return try { @@ -69,7 +45,6 @@ export const useProductActions = ({ } } - /* 分享 */ const handleShare = (product: ProductItem) => { if (product.status !== "completed") return const link = `${window.location.origin}/share/${product.id}` @@ -79,109 +54,42 @@ export const useProductActions = ({ ) } - /* 查看详情 — 跳转到产品详情页 */ const handleViewDetail = (product: ProductItem) => { setPlayingProduct(null) navigate(`/app/products/${product.id}`) } - /* 删除 */ const handleDelete = (id: string) => { deleteMutation.mutate(id) } - /* 发布 — TODO: 后端发布 API 待实现 */ const handlePublish = (_product: ProductItem) => { message.info("发布功能待后端 API 补齐") } - /* 切换复核状态 */ const handleReviewStatusChange = (id: string) => { const current = products.find((p) => p.id === id)?.reviewStatus const nextStatus = getNextReviewStatus(current) reviewMutation.mutate({ id, status: nextStatus }) } - /* 批量下载 */ - const [batchDownloading, setBatchDownloading] = useState(false) - - const handleBatchDownload = async () => { - const ids = Array.from(selectedIds) - if (ids.length === 0) return - setBatchDownloading(true) - try { - const { job_id } = await batchDownload(ids) - message.info(`批量下载任务已创建,正在打包 ${ids.length} 个视频...`) - - let attempts = 0 - const maxAttempts = 60 - const poll = async (): Promise => { - if (attempts >= maxAttempts) { - message.warning("打包超时,请稍后在消息中心查看") - return - } - attempts++ - await new Promise((r) => setTimeout(r, 2000)) - const status = await getBatchDownloadStatus(job_id) - if (status.status === "completed" && status.download_url) { - const a = document.createElement("a") - a.href = status.download_url - a.download = "" - a.click() - message.success(`已打包下载 ${ids.length} 个视频`) - clearSelection() - } else if (status.status === "failed") { - message.error("批量下载失败,请重试") - } else { - await poll() - } - } - await poll() - } catch { - message.error("发起批量下载失败") - } finally { - setBatchDownloading(false) - } - } - - /* 批量删除 */ - const handleBatchDelete = async () => { - const ids = Array.from(selectedIds) - let successCount = 0 - for (const id of ids) { - try { - await deleteProduct(id) - successCount++ - } catch { - // 忽略单个失败 - } - } - queryClient.invalidateQueries({ queryKey: ["products"] }) - clearSelection() - message.success(`已批量删除 ${successCount}/${ids.length} 个视频`) - } - - /* 批量发布 */ const handleBatchPublish = () => { message.info("批量发布功能待后端 API 补齐") clearSelection() } return { - // 单个操作 handleDownload, handleShare, handleViewDetail, handleDelete, handlePublish, handleReviewStatusChange, - // 批量操作 handleBatchDownload, handleBatchDelete, handleBatchPublish, batchDownloading, - // mutation 状态 - isDeleting: deleteMutation.isPending, - isUpdatingReview: reviewMutation.isPending, + isDeleting, + isUpdatingReview, } }