diff --git a/apps/web/src/pages/generate/hooks/useGenerateFormState/useTemplateSelection.ts b/apps/web/src/pages/generate/hooks/useGenerateFormState/useTemplateSelection.ts index 8ee66f4f5..2fe7a8c24 100755 --- a/apps/web/src/pages/generate/hooks/useGenerateFormState/useTemplateSelection.ts +++ b/apps/web/src/pages/generate/hooks/useGenerateFormState/useTemplateSelection.ts @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef, useCallback } from "react" +import { useState, useEffect, useRef, useCallback, useMemo } from "react" import { useQuery } from "@tanstack/react-query" import { message } from "antd" import { getEditingTemplates } from "@/api/editing-planner" @@ -22,8 +22,10 @@ export function useTemplateSelection() { }) // 双保险:后端 valid_only 已过滤,前端再按 is_active + segments 兜底, - // 保证下拉/自动选择只包含可用于生成的有效模板 - const validTemplates = allTemplates.filter(isValidTemplate) + // 保证下拉/自动选择只包含可用于生成的有效模板。 + // 用 useMemo 缓存引用,避免每次渲染都 .filter 创建新数组, + // 导致下游 useTitleCoverSync effect 无限触发、覆盖用户手动修改(#1789) + const validTemplates = useMemo(() => allTemplates.filter(isValidTemplate), [allTemplates]) const userTemplates = validTemplates // 用 ref 持有最新值,供稳定回调 handleInvalidTemplate 使用(避免闭包拿到旧值) diff --git a/apps/web/src/pages/generate/hooks/useGenerateFormState/useTitleCoverSync.ts b/apps/web/src/pages/generate/hooks/useGenerateFormState/useTitleCoverSync.ts index 1adc1818d..6ee207733 100755 --- a/apps/web/src/pages/generate/hooks/useGenerateFormState/useTitleCoverSync.ts +++ b/apps/web/src/pages/generate/hooks/useGenerateFormState/useTitleCoverSync.ts @@ -1,4 +1,4 @@ -import { useEffect } from "react" +import { useEffect, useRef } from "react" import type { TitleSettings } from "../../types" import type { CoverConfig } from "../../types/cover" import type { EditingTemplate } from "@/api/editing-planner" @@ -11,7 +11,11 @@ interface UseTitleCoverSyncOptions { } /** - * 当选中模板变化时,自动同步标题和封面配置 + * 当选中模板变化时,自动同步标题和封面配置。 + * + * #1789 修复:userTemplates 用 ref 持有最新值,不放入依赖数组。 + * 否则每次渲染 .filter() 创建的新数组引用都会触发 effect, + * 从模板 title_config 覆盖用户手动修改(如字号滑块拖动),导致回弹。 */ export function useTitleCoverSync({ selectedTemplate, @@ -19,8 +23,12 @@ export function useTitleCoverSync({ setTitleSettings, setCoverSettings, }: UseTitleCoverSyncOptions) { + // 用 ref 持有最新 userTemplates,避免数组引用变化导致 effect 反复触发 + const templatesRef = useRef(userTemplates) + templatesRef.current = userTemplates + useEffect(() => { - const tpl = userTemplates.find((t) => t.id === selectedTemplate) + const tpl = templatesRef.current.find((t) => t.id === selectedTemplate) if (tpl?.title_config) { setTitleSettings((prev: TitleSettings) => ({ ...prev, @@ -43,5 +51,7 @@ export function useTitleCoverSync({ thumbnail_url: tpl.cover_config!.thumbnail_url || prev.thumbnail_url, })) } - }, [selectedTemplate, userTemplates, setTitleSettings, setCoverSettings]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedTemplate, setTitleSettings, setCoverSettings]) + // ↑ 移除 userTemplates,只在 selectedTemplate 真正变化时触发 }