158 lines
6.1 KiB
TypeScript
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,
|
|
])
|
|
}
|