refactor(assets): split useAssetOperations into sub-modules
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 35s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 43s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m19s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m33s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 2m54s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m10s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m48s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 2m48s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 36s
AI Code Review / AI Code Review (pull_request) Successful in 2m24s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m13s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 7m38s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 33s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1243h44m25s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1243h47m14s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1243h47m18s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1243h47m20s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 1243h56m4s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1243h56m7s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1243h56m9s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1243h56m11s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 1243h56m46s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1244h39m6s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1244h39m10s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1244h19m58s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1244h28m47s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1245h11m48s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 35s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 43s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m19s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m33s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 2m54s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m10s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m48s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 2m48s
Preview Deploy / Deploy Preview Environment (pull_request) Failing after 36s
AI Code Review / AI Code Review (pull_request) Successful in 2m24s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m13s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 7m38s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 33s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Failing after 1243h44m25s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 1243h47m14s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 1243h47m18s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 1243h47m20s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 1243h56m4s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 1243h56m7s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 1243h56m9s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 1243h56m11s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 1243h56m46s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 1244h39m6s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 1244h39m10s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 1244h19m58s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 1244h28m47s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 1245h11m48s
- Extract constants (SMART_VIEW_LABELS) to asset-operations/constants.ts - Extract single operations (diagnose + delete) to useSingleOperations.ts - Extract batch helpers (invalidateAssets, showOperationResult) to useSingleOperations.ts - Extract batch operations (delete/tag/classify/mark) to batchOperations.ts - Main hook reduced from 301 to 165 lines (-45%) - Keep import path @/pages/assets/hooks/useAssetOperations unchanged
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
import { useCallback } from "react"
|
||||
import { message } from "antd"
|
||||
import {
|
||||
batchDeleteAssets,
|
||||
batchTagAssets,
|
||||
batchClassifyAssets,
|
||||
batchMarkAssets,
|
||||
type BatchOperationResult,
|
||||
} from "@/api/assets"
|
||||
import type { SmartViewType } from "../../components/BatchMarkModal"
|
||||
import { SMART_VIEW_LABELS } from "./constants"
|
||||
|
||||
interface BatchOperationDeps {
|
||||
selectedIds: Set<string>
|
||||
invalidateAssets: () => void
|
||||
showResult: (result: BatchOperationResult, title: string) => void
|
||||
setLoading: (loading: boolean) => void
|
||||
queryClient: ReturnType<typeof import("@tanstack/react-query").useQueryClient>
|
||||
}
|
||||
|
||||
interface BatchTagState {
|
||||
batchTags: string[]
|
||||
setBatchTags: (tags: string[]) => void
|
||||
tagMode: "add" | "replace"
|
||||
setTagMode: (mode: "add" | "replace") => void
|
||||
setTagModalOpen: (open: boolean) => void
|
||||
setBatchTagInput: (val: string) => void
|
||||
batchTagInput: string
|
||||
}
|
||||
|
||||
interface BatchClassifyState {
|
||||
batchCategory: string
|
||||
setBatchCategory: (cat: string) => void
|
||||
setClassifyModalOpen: (open: boolean) => void
|
||||
}
|
||||
|
||||
interface BatchMarkState {
|
||||
batchSmartView: SmartViewType
|
||||
setBatchSmartView: (view: SmartViewType) => void
|
||||
setMarkModalOpen: (open: boolean) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除
|
||||
*/
|
||||
export const useBatchDelete = ({
|
||||
selectedIds,
|
||||
invalidateAssets,
|
||||
showResult,
|
||||
setLoading,
|
||||
}: BatchOperationDeps) => {
|
||||
return useCallback(async () => {
|
||||
const ids = Array.from(selectedIds)
|
||||
setLoading(true)
|
||||
try {
|
||||
const result = await batchDeleteAssets(ids)
|
||||
invalidateAssets()
|
||||
showResult(result, "批量删除")
|
||||
if (result.failure_count === 0) {
|
||||
message.success(`成功删除 ${result.success_count} 个素材`)
|
||||
} else {
|
||||
message.warning(
|
||||
`删除完成:成功 ${result.success_count} 个,失败 ${result.failure_count} 个`,
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
message.error("批量删除失败,请重试")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [selectedIds, invalidateAssets, showResult, setLoading])
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量打标签
|
||||
*/
|
||||
export const useBatchTag = (
|
||||
deps: BatchOperationDeps &
|
||||
BatchTagState & {
|
||||
queryClient: ReturnType<typeof import("@tanstack/react-query").useQueryClient>
|
||||
},
|
||||
) => {
|
||||
const {
|
||||
selectedIds,
|
||||
showResult,
|
||||
setLoading,
|
||||
batchTags,
|
||||
tagMode,
|
||||
setTagModalOpen,
|
||||
setBatchTags,
|
||||
setBatchTagInput,
|
||||
setTagMode,
|
||||
queryClient,
|
||||
} = deps
|
||||
|
||||
const handleBatchTag = useCallback(async () => {
|
||||
if (batchTags.length === 0) {
|
||||
message.warning("请至少输入一个标签")
|
||||
return
|
||||
}
|
||||
const ids = Array.from(selectedIds)
|
||||
setLoading(true)
|
||||
try {
|
||||
const result = await batchTagAssets({
|
||||
asset_ids: ids,
|
||||
tags: batchTags,
|
||||
mode: tagMode,
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: ["assets"] })
|
||||
showResult(result, "批量打标签")
|
||||
setTagModalOpen(false)
|
||||
setBatchTags([])
|
||||
setBatchTagInput("")
|
||||
setTagMode("add")
|
||||
if (result.failure_count === 0) {
|
||||
message.success(`成功为 ${result.success_count} 个素材打标签`)
|
||||
} else {
|
||||
message.warning(
|
||||
`打标签完成:成功 ${result.success_count} 个,失败 ${result.failure_count} 个`,
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
message.error("批量打标签失败,请重试")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [
|
||||
batchTags,
|
||||
selectedIds,
|
||||
tagMode,
|
||||
queryClient,
|
||||
showResult,
|
||||
setLoading,
|
||||
setTagModalOpen,
|
||||
setBatchTags,
|
||||
setBatchTagInput,
|
||||
setTagMode,
|
||||
])
|
||||
|
||||
const handleTagInputKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter" && deps.batchTagInput.trim()) {
|
||||
e.preventDefault()
|
||||
const tag = deps.batchTagInput.trim()
|
||||
if (!batchTags.includes(tag)) {
|
||||
setBatchTags([...batchTags, tag])
|
||||
}
|
||||
setBatchTagInput("")
|
||||
}
|
||||
},
|
||||
[deps.batchTagInput, batchTags, setBatchTags, setBatchTagInput],
|
||||
)
|
||||
|
||||
const removeBatchTag = useCallback(
|
||||
(tag: string) => {
|
||||
setBatchTags(batchTags.filter((t) => t !== tag))
|
||||
},
|
||||
[batchTags, setBatchTags],
|
||||
)
|
||||
|
||||
return { handleBatchTag, handleTagInputKeyDown, removeBatchTag }
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量改分类
|
||||
*/
|
||||
export const useBatchClassify = (
|
||||
deps: BatchOperationDeps &
|
||||
BatchClassifyState & {
|
||||
queryClient: ReturnType<typeof import("@tanstack/react-query").useQueryClient>
|
||||
},
|
||||
) => {
|
||||
const {
|
||||
selectedIds,
|
||||
showResult,
|
||||
setLoading,
|
||||
batchCategory,
|
||||
setClassifyModalOpen,
|
||||
setBatchCategory,
|
||||
queryClient,
|
||||
} = deps
|
||||
|
||||
return useCallback(async () => {
|
||||
if (!batchCategory) {
|
||||
message.warning("请选择分类")
|
||||
return
|
||||
}
|
||||
const ids = Array.from(selectedIds)
|
||||
setLoading(true)
|
||||
try {
|
||||
const result = await batchClassifyAssets({
|
||||
asset_ids: ids,
|
||||
category: batchCategory,
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: ["assets"] })
|
||||
showResult(result, "批量改分类")
|
||||
setClassifyModalOpen(false)
|
||||
setBatchCategory("")
|
||||
if (result.failure_count === 0) {
|
||||
message.success(`成功将 ${result.success_count} 个素材改为「${batchCategory}」`)
|
||||
} else {
|
||||
message.warning(
|
||||
`改分类完成:成功 ${result.success_count} 个,失败 ${result.failure_count} 个`,
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
message.error("批量改分类失败,请重试")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [
|
||||
batchCategory,
|
||||
selectedIds,
|
||||
queryClient,
|
||||
showResult,
|
||||
setLoading,
|
||||
setClassifyModalOpen,
|
||||
setBatchCategory,
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量智能标记
|
||||
*/
|
||||
export const useBatchMark = (
|
||||
deps: BatchOperationDeps &
|
||||
BatchMarkState & {
|
||||
queryClient: ReturnType<typeof import("@tanstack/react-query").useQueryClient>
|
||||
},
|
||||
) => {
|
||||
const {
|
||||
selectedIds,
|
||||
showResult,
|
||||
setLoading,
|
||||
batchSmartView,
|
||||
setMarkModalOpen,
|
||||
setBatchSmartView,
|
||||
queryClient,
|
||||
} = deps
|
||||
|
||||
return useCallback(async () => {
|
||||
const ids = Array.from(selectedIds)
|
||||
setLoading(true)
|
||||
try {
|
||||
const result = await batchMarkAssets({
|
||||
asset_ids: ids,
|
||||
smart_view: batchSmartView,
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: ["assets"] })
|
||||
showResult(result, "批量智能标记")
|
||||
setMarkModalOpen(false)
|
||||
const label = SMART_VIEW_LABELS[batchSmartView]
|
||||
if (result.failure_count === 0) {
|
||||
message.success(`成功将 ${result.success_count} 个素材标记为「${label}」`)
|
||||
} else {
|
||||
message.warning(
|
||||
`智能标记完成:成功 ${result.success_count} 个,失败 ${result.failure_count} 个`,
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
message.error("批量智能标记失败,请重试")
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}, [
|
||||
batchSmartView,
|
||||
selectedIds,
|
||||
queryClient,
|
||||
showResult,
|
||||
setLoading,
|
||||
setMarkModalOpen,
|
||||
setBatchSmartView,
|
||||
])
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { SmartViewType } from "../../components/BatchMarkModal"
|
||||
|
||||
/** 智能标记 → 中文标签映射 */
|
||||
export const SMART_VIEW_LABELS: Record<SmartViewType, string> = {
|
||||
recommended: "推荐",
|
||||
caution: "慎用",
|
||||
high_risk: "高风险",
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { useCallback } from "react"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
import { message } from "antd"
|
||||
import { getAssetDiagnosis, deleteAsset, type BatchOperationResult } from "@/api/assets"
|
||||
import type { AssetItem } from "../../types"
|
||||
|
||||
interface UseSingleOperationsOptions {
|
||||
selectedIds: Set<string>
|
||||
setSelectedIds: (ids: Set<string>) => void
|
||||
invalidateAssets: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* 单个素材操作(诊断 + 删除)
|
||||
*/
|
||||
export const useSingleOperations = ({
|
||||
selectedIds,
|
||||
setSelectedIds,
|
||||
invalidateAssets,
|
||||
}: UseSingleOperationsOptions) => {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
/* 诊断 */
|
||||
const handleDiagnose = useCallback(
|
||||
async (asset: AssetItem, setDiagnosingId: (id: string | null) => void) => {
|
||||
setDiagnosingId(asset.id)
|
||||
try {
|
||||
const result = await getAssetDiagnosis(asset.id)
|
||||
const score = result.readiness_score ?? "-"
|
||||
message.success(`"${asset.name}" 诊断完成,就绪分:${score}`)
|
||||
queryClient.invalidateQueries({ queryKey: ["assets"] })
|
||||
} catch {
|
||||
message.error(`"${asset.name}" 诊断失败`)
|
||||
} finally {
|
||||
setDiagnosingId(null)
|
||||
}
|
||||
},
|
||||
[queryClient],
|
||||
)
|
||||
|
||||
/* 单个素材删除 */
|
||||
const handleSingleDelete = useCallback(
|
||||
async (assetId: string) => {
|
||||
try {
|
||||
await deleteAsset(assetId)
|
||||
invalidateAssets()
|
||||
// 从选中集合中移除
|
||||
const next = new Set(selectedIds)
|
||||
next.delete(assetId)
|
||||
setSelectedIds(next)
|
||||
message.success("素材已删除")
|
||||
} catch {
|
||||
message.error("删除失败,请重试")
|
||||
}
|
||||
},
|
||||
[invalidateAssets, selectedIds, setSelectedIds],
|
||||
)
|
||||
|
||||
return { handleDiagnose, handleSingleDelete }
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量操作通用工具
|
||||
*/
|
||||
export const useBatchHelpers = (
|
||||
queryClient: ReturnType<typeof useQueryClient>,
|
||||
setSelectedIds: (ids: Set<string>) => void,
|
||||
) => {
|
||||
const invalidateAssets = useCallback(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ["assets"] })
|
||||
queryClient.invalidateQueries({ queryKey: ["asset-libraries"] })
|
||||
}, [queryClient])
|
||||
|
||||
const showOperationResult = useCallback(
|
||||
(
|
||||
result: BatchOperationResult,
|
||||
title: string,
|
||||
setResult: (r: BatchOperationResult | null) => void,
|
||||
setTitle: (t: string) => void,
|
||||
setDrawerOpen: (open: boolean) => void,
|
||||
clearSelection = true,
|
||||
) => {
|
||||
setResult(result)
|
||||
setTitle(title)
|
||||
setDrawerOpen(true)
|
||||
if (clearSelection) setSelectedIds(new Set())
|
||||
},
|
||||
[setSelectedIds],
|
||||
)
|
||||
|
||||
return { invalidateAssets, showOperationResult }
|
||||
}
|
||||
@@ -1,23 +1,15 @@
|
||||
import { useState, useCallback } from "react"
|
||||
import { useQueryClient } from "@tanstack/react-query"
|
||||
import { message } from "antd"
|
||||
import { useSingleOperations, useBatchHelpers } from "./asset-operations/useSingleOperations"
|
||||
import {
|
||||
deleteAsset,
|
||||
getAssetDiagnosis,
|
||||
batchDeleteAssets,
|
||||
batchTagAssets,
|
||||
batchClassifyAssets,
|
||||
batchMarkAssets,
|
||||
type BatchOperationResult,
|
||||
} from "@/api/assets"
|
||||
import type { AssetItem } from "../types"
|
||||
useBatchDelete,
|
||||
useBatchTag,
|
||||
useBatchClassify,
|
||||
useBatchMark,
|
||||
} from "./asset-operations/batchOperations"
|
||||
import type { SmartViewType } from "../components/BatchMarkModal"
|
||||
import type { BatchOperationResult } from "@/api/assets"
|
||||
|
||||
/**
|
||||
* 素材操作 Hook
|
||||
* 封装素材的诊断、删除、批量打标签、批量改分类、批量智能标记等操作,
|
||||
* 以及相关弹窗和结果展示的状态管理
|
||||
*/
|
||||
interface UseAssetOperationsProps {
|
||||
selectedIds: Set<string>
|
||||
setSelectedIds: (ids: Set<string>) => void
|
||||
@@ -25,6 +17,10 @@ interface UseAssetOperationsProps {
|
||||
|
||||
export function useAssetOperations({ selectedIds, setSelectedIds }: UseAssetOperationsProps) {
|
||||
const queryClient = useQueryClient()
|
||||
const { invalidateAssets, showOperationResult: showResultBase } = useBatchHelpers(
|
||||
queryClient,
|
||||
setSelectedIds,
|
||||
)
|
||||
|
||||
/* ── 诊断状态 ── */
|
||||
const [diagnosingId, setDiagnosingId] = useState<string | null>(null)
|
||||
@@ -53,204 +49,72 @@ export function useAssetOperations({ selectedIds, setSelectedIds }: UseAssetOper
|
||||
/* ── 批量操作 loading ── */
|
||||
const [batchLoading, setBatchLoading] = useState(false)
|
||||
|
||||
/* ── 刷新数据辅助函数 ── */
|
||||
const invalidateAssets = useCallback(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ["assets"] })
|
||||
queryClient.invalidateQueries({ queryKey: ["asset-libraries"] })
|
||||
}, [queryClient])
|
||||
|
||||
/* ── 诊断 ── */
|
||||
const handleDiagnose = useCallback(
|
||||
async (asset: AssetItem) => {
|
||||
setDiagnosingId(asset.id)
|
||||
try {
|
||||
const result = await getAssetDiagnosis(asset.id)
|
||||
const score = result.readiness_score ?? "-"
|
||||
message.success(`"${asset.name}" 诊断完成,就绪分:${score}`)
|
||||
queryClient.invalidateQueries({ queryKey: ["assets"] })
|
||||
} catch {
|
||||
message.error(`"${asset.name}" 诊断失败`)
|
||||
} finally {
|
||||
setDiagnosingId(null)
|
||||
}
|
||||
},
|
||||
[queryClient],
|
||||
)
|
||||
|
||||
/* ── 单个素材删除 ── */
|
||||
const handleSingleDelete = useCallback(
|
||||
async (assetId: string) => {
|
||||
try {
|
||||
await deleteAsset(assetId)
|
||||
invalidateAssets()
|
||||
// 从选中集合中移除
|
||||
setSelectedIds(
|
||||
(() => {
|
||||
const next = new Set(selectedIds)
|
||||
next.delete(assetId)
|
||||
return next
|
||||
})(),
|
||||
)
|
||||
message.success("素材已删除")
|
||||
} catch {
|
||||
message.error("删除失败,请重试")
|
||||
}
|
||||
},
|
||||
[invalidateAssets, selectedIds, setSelectedIds],
|
||||
)
|
||||
|
||||
/* ── 显示操作结果 ── */
|
||||
/* ── 操作结果展示 ── */
|
||||
const showOperationResult = useCallback(
|
||||
(result: BatchOperationResult, title: string, clearSelection = true) => {
|
||||
setOperationResult(result)
|
||||
setOperationTitle(title)
|
||||
setResultDrawerOpen(true)
|
||||
if (clearSelection) setSelectedIds(new Set())
|
||||
showResultBase(
|
||||
result,
|
||||
title,
|
||||
setOperationResult,
|
||||
setOperationTitle,
|
||||
setResultDrawerOpen,
|
||||
clearSelection,
|
||||
)
|
||||
},
|
||||
[setSelectedIds],
|
||||
[showResultBase],
|
||||
)
|
||||
|
||||
/* ── 单个操作 ── */
|
||||
const { handleDiagnose: handleDiagnoseRaw, handleSingleDelete } = useSingleOperations({
|
||||
selectedIds,
|
||||
setSelectedIds,
|
||||
invalidateAssets,
|
||||
})
|
||||
|
||||
const handleDiagnose = useCallback(
|
||||
(asset: Parameters<typeof handleDiagnoseRaw>[0]) => handleDiagnoseRaw(asset, setDiagnosingId),
|
||||
[handleDiagnoseRaw],
|
||||
)
|
||||
|
||||
/* ── 批量操作 deps ── */
|
||||
const batchDeps = {
|
||||
selectedIds,
|
||||
invalidateAssets,
|
||||
showResult: showOperationResult,
|
||||
setLoading: setBatchLoading,
|
||||
queryClient,
|
||||
}
|
||||
|
||||
/* ── 批量删除 ── */
|
||||
const handleBatchDelete = useCallback(async () => {
|
||||
const ids = Array.from(selectedIds)
|
||||
setBatchLoading(true)
|
||||
try {
|
||||
const result = await batchDeleteAssets(ids)
|
||||
invalidateAssets()
|
||||
showOperationResult(result, "批量删除")
|
||||
if (result.failure_count === 0) {
|
||||
message.success(`成功删除 ${result.success_count} 个素材`)
|
||||
} else {
|
||||
message.warning(
|
||||
`删除完成:成功 ${result.success_count} 个,失败 ${result.failure_count} 个`,
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
message.error("批量删除失败,请重试")
|
||||
} finally {
|
||||
setBatchLoading(false)
|
||||
}
|
||||
}, [selectedIds, invalidateAssets, showOperationResult])
|
||||
const handleBatchDelete = useBatchDelete(batchDeps)
|
||||
|
||||
/* ── 批量打标签 ── */
|
||||
const handleBatchTag = useCallback(async () => {
|
||||
if (batchTags.length === 0) {
|
||||
message.warning("请至少输入一个标签")
|
||||
return
|
||||
}
|
||||
const ids = Array.from(selectedIds)
|
||||
setBatchLoading(true)
|
||||
try {
|
||||
const result = await batchTagAssets({
|
||||
asset_ids: ids,
|
||||
tags: batchTags,
|
||||
mode: tagMode,
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: ["assets"] })
|
||||
showOperationResult(result, "批量打标签")
|
||||
setTagModalOpen(false)
|
||||
setBatchTags([])
|
||||
setBatchTagInput("")
|
||||
setTagMode("add")
|
||||
if (result.failure_count === 0) {
|
||||
message.success(`成功为 ${result.success_count} 个素材打标签`)
|
||||
} else {
|
||||
message.warning(
|
||||
`打标签完成:成功 ${result.success_count} 个,失败 ${result.failure_count} 个`,
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
message.error("批量打标签失败,请重试")
|
||||
} finally {
|
||||
setBatchLoading(false)
|
||||
}
|
||||
}, [batchTags, selectedIds, tagMode, queryClient, showOperationResult])
|
||||
|
||||
/* ── 标签输入处理 ── */
|
||||
const handleTagInputKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.key === "Enter" && batchTagInput.trim()) {
|
||||
e.preventDefault()
|
||||
const tag = batchTagInput.trim()
|
||||
if (!batchTags.includes(tag)) {
|
||||
setBatchTags([...batchTags, tag])
|
||||
}
|
||||
setBatchTagInput("")
|
||||
}
|
||||
},
|
||||
[batchTagInput, batchTags],
|
||||
)
|
||||
|
||||
const removeBatchTag = useCallback(
|
||||
(tag: string) => {
|
||||
setBatchTags(batchTags.filter((t) => t !== tag))
|
||||
},
|
||||
[batchTags],
|
||||
)
|
||||
const { handleBatchTag, handleTagInputKeyDown, removeBatchTag } = useBatchTag({
|
||||
...batchDeps,
|
||||
batchTags,
|
||||
setBatchTags,
|
||||
tagMode,
|
||||
setTagMode,
|
||||
setTagModalOpen,
|
||||
setBatchTagInput,
|
||||
batchTagInput,
|
||||
})
|
||||
|
||||
/* ── 批量改分类 ── */
|
||||
const handleBatchClassify = useCallback(async () => {
|
||||
if (!batchCategory) {
|
||||
message.warning("请选择分类")
|
||||
return
|
||||
}
|
||||
const ids = Array.from(selectedIds)
|
||||
setBatchLoading(true)
|
||||
try {
|
||||
const result = await batchClassifyAssets({
|
||||
asset_ids: ids,
|
||||
category: batchCategory,
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: ["assets"] })
|
||||
showOperationResult(result, "批量改分类")
|
||||
setClassifyModalOpen(false)
|
||||
setBatchCategory("")
|
||||
if (result.failure_count === 0) {
|
||||
message.success(`成功将 ${result.success_count} 个素材改为「${batchCategory}」`)
|
||||
} else {
|
||||
message.warning(
|
||||
`改分类完成:成功 ${result.success_count} 个,失败 ${result.failure_count} 个`,
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
message.error("批量改分类失败,请重试")
|
||||
} finally {
|
||||
setBatchLoading(false)
|
||||
}
|
||||
}, [batchCategory, selectedIds, queryClient, showOperationResult])
|
||||
const handleBatchClassify = useBatchClassify({
|
||||
...batchDeps,
|
||||
batchCategory,
|
||||
setBatchCategory,
|
||||
setClassifyModalOpen,
|
||||
})
|
||||
|
||||
/* ── 批量智能标记 ── */
|
||||
const handleBatchMark = useCallback(async () => {
|
||||
const ids = Array.from(selectedIds)
|
||||
setBatchLoading(true)
|
||||
try {
|
||||
const result = await batchMarkAssets({
|
||||
asset_ids: ids,
|
||||
smart_view: batchSmartView,
|
||||
})
|
||||
queryClient.invalidateQueries({ queryKey: ["assets"] })
|
||||
showOperationResult(result, "批量智能标记")
|
||||
setMarkModalOpen(false)
|
||||
const labelMap: Record<SmartViewType, string> = {
|
||||
recommended: "推荐",
|
||||
caution: "慎用",
|
||||
high_risk: "高风险",
|
||||
}
|
||||
if (result.failure_count === 0) {
|
||||
message.success(
|
||||
`成功将 ${result.success_count} 个素材标记为「${labelMap[batchSmartView]}」`,
|
||||
)
|
||||
} else {
|
||||
message.warning(
|
||||
`智能标记完成:成功 ${result.success_count} 个,失败 ${result.failure_count} 个`,
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
message.error("批量智能标记失败,请重试")
|
||||
} finally {
|
||||
setBatchLoading(false)
|
||||
}
|
||||
}, [batchSmartView, selectedIds, queryClient, showOperationResult])
|
||||
const handleBatchMark = useBatchMark({
|
||||
...batchDeps,
|
||||
batchSmartView,
|
||||
setBatchSmartView,
|
||||
setMarkModalOpen,
|
||||
})
|
||||
|
||||
/* ── 关闭结果 Drawer ── */
|
||||
const handleResultDrawerClose = useCallback(() => {
|
||||
|
||||
Reference in New Issue
Block a user