refactor(editing-planner): 拆分 useEditPlanClips Hook,按职责拆为子模块 (#1007)
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m6s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m6s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m1s
CI/CD Pipeline / Unit Tests (push) Successful in 5m42s
CI/CD Pipeline / Integration Tests (push) Successful in 1m58s
CI/CD Pipeline / Frontend Lint (push) Successful in 45s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m8s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 20m24s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m43s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 7m31s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m57s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been skipped
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Validate - Code Quality (push) Failing after 3m6s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 1m6s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 1m1s
CI/CD Pipeline / Unit Tests (push) Successful in 5m42s
CI/CD Pipeline / Integration Tests (push) Successful in 1m58s
CI/CD Pipeline / Frontend Lint (push) Successful in 45s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m8s
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Build Staging API Image (push) Successful in 20m24s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m43s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 7m31s
CI/CD Pipeline / Build Production API Image (push) Has been skipped
CI/CD Pipeline / Build Production Web Image (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m57s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { useUndoRedo } from "../useUndoRedo"
|
||||
import type { EditPlanClip } from "@/api/template-editor"
|
||||
import { useEditPlanClipList } from "./useEditPlanClipList"
|
||||
import { useEditPlanClipMutations } from "./useEditPlanClipMutations"
|
||||
|
||||
/**
|
||||
* 模板片段管理 Hook
|
||||
* 对接后端 PR#389 片段 CRUD API
|
||||
*
|
||||
* 功能:
|
||||
* - 加载/刷新片段列表
|
||||
* - 单个增删改查
|
||||
* - 批量删除
|
||||
* - 拖拽重排序
|
||||
* - 从素材批量导入
|
||||
* - 乐观更新 + 撤销重做
|
||||
*/
|
||||
export function useEditPlanClips(planId: string | undefined) {
|
||||
// 列表数据 + 选中状态
|
||||
const {
|
||||
clips,
|
||||
clipsTotal,
|
||||
clipsLoading,
|
||||
refetchClips,
|
||||
selectedClipId,
|
||||
setSelectedClipId,
|
||||
selectedClip,
|
||||
} = useEditPlanClipList(planId)
|
||||
|
||||
// CRUD 操作
|
||||
const mutations = useEditPlanClipMutations({
|
||||
planId,
|
||||
selectedClipId,
|
||||
setSelectedClipId,
|
||||
clipsLength: clips.length,
|
||||
})
|
||||
|
||||
// 本地撤销重做(供拖拽等即时操作使用)
|
||||
const {
|
||||
state: localClips,
|
||||
set: setLocalClips,
|
||||
undo,
|
||||
redo,
|
||||
canUndo,
|
||||
canRedo,
|
||||
reset: resetLocalClips,
|
||||
} = useUndoRedo<EditPlanClip[]>([])
|
||||
|
||||
return {
|
||||
// 数据
|
||||
clips,
|
||||
clipsTotal,
|
||||
clipsLoading,
|
||||
selectedClipId,
|
||||
selectedClip,
|
||||
// 选中
|
||||
setSelectedClipId,
|
||||
// 操作
|
||||
addClip: mutations.addClip,
|
||||
updateClip: mutations.updateClip,
|
||||
removeClip: mutations.removeClip,
|
||||
batchRemoveClips: mutations.batchRemoveClips,
|
||||
reorderClips: mutations.reorderClips,
|
||||
importFromAssets: mutations.importFromAssets,
|
||||
refetchClips,
|
||||
// 状态
|
||||
isCreating: mutations.isCreating,
|
||||
isUpdating: mutations.isUpdating,
|
||||
isDeleting: mutations.isDeleting,
|
||||
isReordering: mutations.isReordering,
|
||||
isImporting: mutations.isImporting,
|
||||
// 本地撤销重做
|
||||
localClips,
|
||||
setLocalClips,
|
||||
undo,
|
||||
redo,
|
||||
canUndo,
|
||||
canRedo,
|
||||
resetLocalClips,
|
||||
}
|
||||
}
|
||||
|
||||
export default useEditPlanClips
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useState } from "react"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import type { EditPlanClip } from "@/api/template-editor"
|
||||
import { getEditPlanClips } from "@/api/template-editor"
|
||||
|
||||
const QUERY_KEY = "editPlanClips"
|
||||
|
||||
interface UseEditPlanClipListResult {
|
||||
clips: EditPlanClip[]
|
||||
clipsTotal: number
|
||||
clipsLoading: boolean
|
||||
refetchClips: () => void
|
||||
selectedClipId: string | null
|
||||
setSelectedClipId: (id: string | null) => void
|
||||
selectedClip: EditPlanClip | null
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑计划片段列表 Hook
|
||||
* 封装片段列表查询、选中状态
|
||||
*/
|
||||
export function useEditPlanClipList(planId: string | undefined): UseEditPlanClipListResult {
|
||||
const {
|
||||
data: clipListData,
|
||||
isLoading: clipsLoading,
|
||||
refetch: refetchClips,
|
||||
} = useQuery({
|
||||
queryKey: [QUERY_KEY, planId],
|
||||
queryFn: () => getEditPlanClips(planId!, { limit: 500 }),
|
||||
enabled: !!planId,
|
||||
staleTime: 30_000,
|
||||
})
|
||||
|
||||
const clips: EditPlanClip[] = clipListData?.items ?? []
|
||||
const clipsTotal = clipListData?.total ?? 0
|
||||
|
||||
const [selectedClipId, setSelectedClipId] = useState<string | null>(null)
|
||||
const selectedClip = clips.find((c) => c.id === selectedClipId) ?? null
|
||||
|
||||
return {
|
||||
clips,
|
||||
clipsTotal,
|
||||
clipsLoading,
|
||||
refetchClips,
|
||||
selectedClipId,
|
||||
setSelectedClipId,
|
||||
selectedClip,
|
||||
}
|
||||
}
|
||||
+34
-84
@@ -1,26 +1,12 @@
|
||||
/**
|
||||
* 模板片段管理 Hook
|
||||
* 对接后端 PR#389 片段 CRUD API,替代原来的 config.segments 模式
|
||||
*
|
||||
* 功能:
|
||||
* - 加载/刷新片段列表
|
||||
* - 单个增删改查
|
||||
* - 批量删除
|
||||
* - 拖拽重排序
|
||||
* - 从素材批量导入
|
||||
* - 乐观更新 + 撤销重做
|
||||
*/
|
||||
import { useCallback, useState } from "react"
|
||||
import { useCallback } from "react"
|
||||
import { message } from "antd"
|
||||
import { useQuery, useQueryClient, useMutation } from "@tanstack/react-query"
|
||||
import { useQueryClient, useMutation } from "@tanstack/react-query"
|
||||
import type {
|
||||
EditPlanClip,
|
||||
CreateEditPlanClipRequest,
|
||||
UpdateEditPlanClipRequest,
|
||||
ClipReorderItem,
|
||||
} from "@/api/template-editor"
|
||||
import {
|
||||
getEditPlanClips,
|
||||
createEditPlanClip,
|
||||
updateEditPlanClip,
|
||||
deleteEditPlanClip,
|
||||
@@ -28,51 +14,37 @@ import {
|
||||
batchDeleteEditPlanClips,
|
||||
createClipsFromAssets,
|
||||
} from "@/api/template-editor"
|
||||
import { useUndoRedo } from "./useUndoRedo"
|
||||
|
||||
const QUERY_KEY = "editPlanClips"
|
||||
|
||||
export function useEditPlanClips(planId: string | undefined) {
|
||||
interface UseEditPlanClipMutationsOptions {
|
||||
planId: string | undefined
|
||||
selectedClipId: string | null
|
||||
setSelectedClipId: (id: string | null) => void
|
||||
clipsLength: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑计划片段 CRUD Hook
|
||||
* 封装创建、更新、删除、批量删除、重排序、素材导入等操作
|
||||
*/
|
||||
export function useEditPlanClipMutations({
|
||||
planId,
|
||||
selectedClipId,
|
||||
setSelectedClipId,
|
||||
clipsLength,
|
||||
}: UseEditPlanClipMutationsOptions) {
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
/* ── 片段列表查询 ── */
|
||||
const {
|
||||
data: clipListData,
|
||||
isLoading: clipsLoading,
|
||||
refetch: refetchClips,
|
||||
} = useQuery({
|
||||
queryKey: [QUERY_KEY, planId],
|
||||
queryFn: () => getEditPlanClips(planId!, { limit: 500 }),
|
||||
enabled: !!planId,
|
||||
staleTime: 30_000,
|
||||
})
|
||||
|
||||
const clips: EditPlanClip[] = clipListData?.items ?? []
|
||||
const clipsTotal = clipListData?.total ?? 0
|
||||
|
||||
/* ── 选中片段 ── */
|
||||
const [selectedClipId, setSelectedClipId] = useState<string | null>(null)
|
||||
const selectedClip = clips.find((c) => c.id === selectedClipId) ?? null
|
||||
|
||||
/* ── 本地撤销/重做(用于拖拽等即时操作的回退) ── */
|
||||
const {
|
||||
state: localClips,
|
||||
set: setLocalClips,
|
||||
undo,
|
||||
redo,
|
||||
canUndo,
|
||||
canRedo,
|
||||
reset: resetLocalClips,
|
||||
} = useUndoRedo<EditPlanClip[]>([])
|
||||
|
||||
// 当服务端数据变化时同步本地
|
||||
// 注意:实际使用时以服务端为准,本地仅用于拖拽等临时操作
|
||||
const invalidate = () => {
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEY, planId] })
|
||||
}
|
||||
|
||||
/* ── 创建片段 ── */
|
||||
const createMutation = useMutation({
|
||||
mutationFn: (data: CreateEditPlanClipRequest) => createEditPlanClip(planId!, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEY, planId] })
|
||||
invalidate()
|
||||
message.success("片段已添加")
|
||||
},
|
||||
onError: () => {
|
||||
@@ -83,10 +55,10 @@ export function useEditPlanClips(planId: string | undefined) {
|
||||
const addClip = useCallback(
|
||||
(data: Omit<CreateEditPlanClipRequest, "order"> & { order?: number }) => {
|
||||
if (!planId) return
|
||||
const order = data.order ?? clips.length
|
||||
const order = data.order ?? clipsLength
|
||||
createMutation.mutate({ ...data, order })
|
||||
},
|
||||
[planId, clips.length, createMutation],
|
||||
[planId, clipsLength, createMutation],
|
||||
)
|
||||
|
||||
/* ── 更新片段 ── */
|
||||
@@ -94,7 +66,7 @@ export function useEditPlanClips(planId: string | undefined) {
|
||||
mutationFn: ({ clipId, data }: { clipId: string; data: UpdateEditPlanClipRequest }) =>
|
||||
updateEditPlanClip(planId!, clipId, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEY, planId] })
|
||||
invalidate()
|
||||
},
|
||||
onError: () => {
|
||||
message.error("更新片段失败")
|
||||
@@ -113,7 +85,7 @@ export function useEditPlanClips(planId: string | undefined) {
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (clipId: string) => deleteEditPlanClip(planId!, clipId),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEY, planId] })
|
||||
invalidate()
|
||||
message.success("片段已删除")
|
||||
},
|
||||
onError: () => {
|
||||
@@ -129,14 +101,14 @@ export function useEditPlanClips(planId: string | undefined) {
|
||||
}
|
||||
deleteMutation.mutate(clipId)
|
||||
},
|
||||
[planId, selectedClipId, deleteMutation],
|
||||
[planId, selectedClipId, setSelectedClipId, deleteMutation],
|
||||
)
|
||||
|
||||
/* ── 批量删除 ── */
|
||||
const batchDeleteMutation = useMutation({
|
||||
mutationFn: (clipIds: string[]) => batchDeleteEditPlanClips(planId!, clipIds),
|
||||
onSuccess: (res) => {
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEY, planId] })
|
||||
invalidate()
|
||||
message.success(`已删除 ${res.deleted_count} 个片段`)
|
||||
},
|
||||
onError: () => {
|
||||
@@ -152,19 +124,18 @@ export function useEditPlanClips(planId: string | undefined) {
|
||||
}
|
||||
batchDeleteMutation.mutate(clipIds)
|
||||
},
|
||||
[planId, selectedClipId, batchDeleteMutation],
|
||||
[planId, selectedClipId, setSelectedClipId, batchDeleteMutation],
|
||||
)
|
||||
|
||||
/* ── 重排序(拖拽结束后一次性提交) ── */
|
||||
/* ── 重排序 ── */
|
||||
const reorderMutation = useMutation({
|
||||
mutationFn: (items: ClipReorderItem[]) => reorderEditPlanClips(planId!, items),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEY, planId] })
|
||||
invalidate()
|
||||
},
|
||||
onError: () => {
|
||||
message.error("排序失败")
|
||||
// 失败后刷新回服务端状态
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEY, planId] })
|
||||
invalidate()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -180,7 +151,7 @@ export function useEditPlanClips(planId: string | undefined) {
|
||||
const importFromAssetsMutation = useMutation({
|
||||
mutationFn: (assetIds: string[]) => createClipsFromAssets(planId!, assetIds),
|
||||
onSuccess: (res) => {
|
||||
queryClient.invalidateQueries({ queryKey: [QUERY_KEY, planId] })
|
||||
invalidate()
|
||||
message.success(`已导入 ${res.created_count} 个素材片段`)
|
||||
},
|
||||
onError: () => {
|
||||
@@ -197,37 +168,16 @@ export function useEditPlanClips(planId: string | undefined) {
|
||||
)
|
||||
|
||||
return {
|
||||
// 数据
|
||||
clips,
|
||||
clipsTotal,
|
||||
clipsLoading,
|
||||
selectedClipId,
|
||||
selectedClip,
|
||||
// 选中
|
||||
setSelectedClipId,
|
||||
// 操作
|
||||
addClip,
|
||||
updateClip,
|
||||
removeClip,
|
||||
batchRemoveClips,
|
||||
reorderClips,
|
||||
importFromAssets,
|
||||
refetchClips,
|
||||
// 状态
|
||||
isCreating: createMutation.isPending,
|
||||
isUpdating: updateMutation.isPending,
|
||||
isDeleting: deleteMutation.isPending,
|
||||
isReordering: reorderMutation.isPending,
|
||||
isImporting: importFromAssetsMutation.isPending,
|
||||
// 本地撤销重做(供拖拽等场景使用)
|
||||
localClips,
|
||||
setLocalClips,
|
||||
undo,
|
||||
redo,
|
||||
canUndo,
|
||||
canRedo,
|
||||
resetLocalClips,
|
||||
}
|
||||
}
|
||||
|
||||
export default useEditPlanClips
|
||||
Reference in New Issue
Block a user