@@ -1,89 +0,0 @@
|
||||
import { useState, useEffect, useRef, useCallback, useMemo } from "react"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { message } from "antd"
|
||||
import { getEditingTemplates } from "@/api/editing-planner"
|
||||
import type { EditingTemplate } from "@/api/editing-planner"
|
||||
import { findFirstValidTemplate, isValidTemplate } from "./templateFallback"
|
||||
|
||||
/** 失效模板自动切换的提示文案 */
|
||||
export const INVALID_TEMPLATE_FALLBACK_TOAST = "原模板已失效,已自动切换"
|
||||
|
||||
export function useTemplateSelection() {
|
||||
// selectedTemplate 纯内存状态,绝不写入 localStorage/sessionStorage/URL,
|
||||
// 因此失效模板 ID 不会被持久化、刷新后也不会恢复(#1777 要求 4)
|
||||
const [selectedTemplate, setSelectedTemplate] = useState("")
|
||||
|
||||
const { data: allTemplates = [] } = useQuery<EditingTemplate[]>({
|
||||
queryKey: ["generate-templates"],
|
||||
// valid_only:后端过滤掉没有片段配置的无效模板(#1769/#1772)。
|
||||
// 旧后端忽略该 query 参数时,下方 isValidTemplate 前端兜底再过滤一次。
|
||||
queryFn: () => getEditingTemplates({ validOnly: true }),
|
||||
staleTime: 60_000,
|
||||
})
|
||||
|
||||
// 双保险:后端 valid_only 已过滤,前端再按 is_active + segments 兜底,
|
||||
// 保证下拉/自动选择只包含可用于生成的有效模板。
|
||||
// 用 useMemo 缓存引用,避免每次渲染都 .filter 创建新数组,
|
||||
// 导致下游 useTitleCoverSync effect 无限触发、覆盖用户手动修改(#1789)
|
||||
const validTemplates = useMemo(() => allTemplates.filter(isValidTemplate), [allTemplates])
|
||||
const userTemplates = validTemplates
|
||||
|
||||
// 用 ref 持有最新值,供稳定回调 handleInvalidTemplate 使用(避免闭包拿到旧值)
|
||||
const templatesRef = useRef(validTemplates)
|
||||
templatesRef.current = validTemplates
|
||||
const selectedRef = useRef(selectedTemplate)
|
||||
selectedRef.current = selectedTemplate
|
||||
// 已提示过失效的模板 ID,避免用户停留在失效模板上时 clips 防抖请求反复弹 toast;
|
||||
// 用户手动切换/成功切换后重置,保证下一个失效模板仍能提示
|
||||
const fallbackNotifiedRef = useRef<string>("")
|
||||
|
||||
/* 自动选择:模板加载完成且当前未选中时,自动选中第一个有效模板。
|
||||
* 用户手动选择(setSelectedTemplate 被显式调用)后 selectedTemplate 非空,
|
||||
* 本 effect 直接 return,绝不覆盖用户的手动选择(#1777 要求 4:手动优先)。 */
|
||||
useEffect(() => {
|
||||
if (selectedTemplate) return
|
||||
const firstValid = validTemplates[0]
|
||||
if (firstValid) {
|
||||
setSelectedTemplate(firstValid.id)
|
||||
}
|
||||
}, [validTemplates, selectedTemplate])
|
||||
|
||||
/** 用户手动选择模板:优先级最高,重置失效提示标记 */
|
||||
const handleSelectTemplate = useCallback((id: string) => {
|
||||
fallbackNotifiedRef.current = ""
|
||||
setSelectedTemplate(id)
|
||||
}, [])
|
||||
|
||||
/**
|
||||
* 运行时失效回退(#1777 要求 3):
|
||||
* 创建片段接口返回 404(模板不存在)/ 400/422(模板无片段配置)时调用。
|
||||
* - 清除失效选择,自动切换到第一个有效模板,并 toast 提示;
|
||||
* - 没有有效模板时清空选择,Step1 展示明确的「暂无可用模板」空状态引导,
|
||||
* 不让用户卡在失效模板上。
|
||||
* 返回 true 表示已按「模板失效」处理(调用方可据此静默原始错误提示)。
|
||||
*/
|
||||
const handleInvalidTemplate = useCallback((): boolean => {
|
||||
const current = selectedRef.current
|
||||
// 同一个失效模板只提示一次(clips 防抖 effect 在素材/模板变化时会反复触发)
|
||||
if (current && fallbackNotifiedRef.current === current) return true
|
||||
|
||||
const fallback = findFirstValidTemplate(templatesRef.current)
|
||||
fallbackNotifiedRef.current = current || "__empty__"
|
||||
if (fallback) {
|
||||
setSelectedTemplate(fallback.id)
|
||||
message.warning(INVALID_TEMPLATE_FALLBACK_TOAST)
|
||||
} else {
|
||||
// 没有任何有效模板:清空选择,交由 Step1 空状态引导用户去模板编辑器创建
|
||||
setSelectedTemplate("")
|
||||
message.warning("当前没有可用模板,请先在「模板编辑器」中创建并配置片段")
|
||||
}
|
||||
return true
|
||||
}, [])
|
||||
|
||||
return {
|
||||
selectedTemplate,
|
||||
setSelectedTemplate: handleSelectTemplate,
|
||||
userTemplates,
|
||||
handleInvalidTemplate,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user