Files
xiaoxia-saas/apps/web/src/pages/editing-planner/hooks/template-management/usePlanLoading.ts
T

158 lines
6.1 KiB
TypeScript

import { useEffect, type Dispatch, type SetStateAction } from "react"
import { message } from "antd"
import { getEditPlan, getEditPlanClips } from "@/api/template-editor"
import type { ClipData, ClipType, TtsConfig, TtsMode, TrimConfig } from "../../types"
import type { SubtitleStyleConfig } from "../../types/subtitle"
import type { TitleConfig } from "@/api/template-editor"
import type { BgmMixConfig } from "@/api/bgm"
import type { TransitionEffect } from "@/api/template-editor"
import type { CoverConfig } from "../../types"
interface UsePlanLoadingOptions {
loadedPlanId: string | null
resetClips: (clips: ClipData[]) => void
setLoadedTemplateId: (id: string | null) => void
setDraftName: (name: string) => void
setTitleConfig: Dispatch<SetStateAction<TitleConfig>>
setSubtitleSettings: Dispatch<SetStateAction<SubtitleStyleConfig>>
setBgmSettings: Dispatch<SetStateAction<BgmMixConfig>>
setCoverConfig: Dispatch<SetStateAction<CoverConfig>>
}
/**
* 加载已有计划草稿数据到编辑器
* 从列表页"编辑"按钮进入时,URL 带 planId,需要还原计划配置
*/
export function usePlanLoading({
loadedPlanId,
resetClips,
setLoadedTemplateId,
setDraftName,
setTitleConfig,
setSubtitleSettings,
setBgmSettings,
setCoverConfig,
}: UsePlanLoadingOptions) {
useEffect(() => {
if (!loadedPlanId) return
Promise.all([
getEditPlan(loadedPlanId),
getEditPlanClips(loadedPlanId, { limit: 500 }).catch(() => ({
items: [],
total: 0,
})),
])
.then(([plan, clipsRes]) => {
setLoadedTemplateId(plan.template_id)
setDraftName(plan.name)
const cfg = plan.config
if (cfg.title_config) {
setTitleConfig({
ai_auto_select: cfg.title_config!.ai_auto_select,
content: cfg.title_config!.content,
position: cfg.title_config!.position,
font_preset: cfg.title_config!.font_preset,
font_size: cfg.title_config!.font_size,
font_color: cfg.title_config!.font_color || "#ffffff",
})
}
if (cfg.subtitle_config) {
setSubtitleSettings((prev) => ({
...prev,
enabled: cfg.subtitle_config!.enabled,
position: (cfg.subtitle_config!.position ||
"bottom") as SubtitleStyleConfig["position"],
font: cfg.subtitle_config!.font,
fontSize: cfg.subtitle_config!.size,
fontColor: cfg.subtitle_config!.color || "#ffffff",
animation: cfg.subtitle_config!.animation,
}))
}
if (cfg.bgm_config) {
setBgmSettings((prev) => ({
...prev,
enabled: cfg.bgm_config!.enabled,
music_id: cfg.bgm_config!.music_id || "",
}))
}
if (cfg.cover_config) {
setCoverConfig((prev: CoverConfig) => ({
...prev,
enabled: cfg.cover_config!.enabled ?? prev.enabled,
mode: (cfg.cover_config!.mode as CoverConfig["mode"]) || prev.mode,
frame_time: cfg.cover_config!.frame_time ?? prev.frame_time,
upload_url: cfg.cover_config!.upload_url || prev.upload_url,
thumbnail_url: cfg.cover_config!.thumbnail_url || prev.thumbnail_url,
ai_suggested_time: cfg.cover_config!.ai_suggested_time ?? prev.ai_suggested_time,
}))
}
/* 还原片段:优先从后端 clips 表,其次从 config.segments 兜底 */
const backendClips = clipsRes?.items || []
if (backendClips.length > 0) {
const sorted = [...backendClips].sort((a, b) => a.order - b.order)
const mapped: ClipData[] = sorted.map((clip) => ({
id: clip.id,
template_segment_id: (clip.config?.template_segment_id as string) || "",
type: (clip.clip_type === "voiceover" ? "voice" : "pip") as ClipType,
duration: clip.duration || 3,
startOffset: 0,
script_text: clip.text_content || "",
order: clip.order,
media_asset_id: clip.asset_id || undefined,
transition:
clip.transition_effect && clip.transition_effect !== "none"
? {
type: clip.transition_effect as TransitionEffect["type"],
duration: clip.transition_duration || 0.3,
}
: undefined,
speed: clip.playback_speed
? { rate: clip.playback_speed, pitchCorrection: true }
: undefined,
tts_config: (clip.config?.tts_config as TtsConfig) || undefined,
trim_config: (clip.config?.trim_config as TrimConfig) || undefined,
}))
setTimeout(() => resetClips(mapped), 100)
} else if (cfg.segments && cfg.segments.length > 0) {
/* 兜底:从 config.segments 还原(老数据兼容) */
const mapped: ClipData[] = cfg.segments.map((seg, idx) => ({
id: `seg-${idx}`,
template_segment_id: `seg-${idx}`,
type: (seg.material_type === "voiceover" ? "voice" : "pip") as ClipType,
duration: (seg.duration_min + seg.duration_max) / 2,
startOffset: 0,
script_text: "",
order: seg.segment_order,
transition: seg.transition
? {
type: seg.transition.type as TransitionEffect["type"],
duration: seg.transition.duration,
}
: undefined,
speed: seg.playback_speed
? { rate: seg.playback_speed, pitchCorrection: true }
: undefined,
tts_config: seg.tts_config
? { ...seg.tts_config, mode: seg.tts_config.mode as TtsMode }
: undefined,
trim_config: seg.trim_config || undefined,
}))
setTimeout(() => resetClips(mapped), 100)
}
})
.catch(() => message.error("加载模板草稿失败"))
}, [
loadedPlanId,
resetClips,
setLoadedTemplateId,
setDraftName,
setTitleConfig,
setSubtitleSettings,
setBgmSettings,
setCoverConfig,
])
}