diff --git a/apps/web/src/pages/generate/GeneratePage.tsx b/apps/web/src/pages/generate/GeneratePage.tsx index 4890343ff..5f59a0eed 100644 --- a/apps/web/src/pages/generate/GeneratePage.tsx +++ b/apps/web/src/pages/generate/GeneratePage.tsx @@ -137,8 +137,8 @@ const GeneratePage: React.FC = () => { } }, [selectedVoice, isBatch, voiceModePerVideo, setVoiceLibraryIds]) - /* ── 标题面板模式:默认展示样式参数(false);需要大卡片模板网格时再切 true ── */ - const enableTemplates = false + /* ── 标题面板模式:true = 内联大卡片模板网格(默认),false = 旧预设+参数 Tab ── */ + const enableTemplates = true /* ── Step5 保存中状态 ── */ const [finishing, setFinishing] = useState(false) diff --git a/apps/web/src/pages/generate/components/Step4TitleSettings.tsx b/apps/web/src/pages/generate/components/Step4TitleSettings.tsx index d3788b4d3..0beb3a5bb 100644 --- a/apps/web/src/pages/generate/components/Step4TitleSettings.tsx +++ b/apps/web/src/pages/generate/components/Step4TitleSettings.tsx @@ -6,12 +6,13 @@ * - 批量:N 个独立标题输入框(AutoComplete 支持标题库选择) * - 标题样式(字体/颜色/位置/大小/粗斜描边/预设):全局统一 */ -import React, { useMemo, useState } from "react" +import React, { useMemo } from "react" import type { TitleSettings } from "../types" +import { POSITION_OPTIONS } from "../constants" +import { FONT_OPTIONS } from "@/components/title/constants" import { useStep4Title } from "../hooks/useStep4Title" import TitleLibraryAutoComplete from "./title/TitleLibraryAutoComplete" -import TitleStyleModal from "./title/TitleStyleModal" -import Button from "@/components/ui/Button" +import TitleStylePanel from "./title/TitleStylePanel" import type { TitleTemplate } from "@/components/title/template-types" interface Step4TitleSettingsProps { @@ -57,6 +58,13 @@ interface Step4TitleSettingsProps { const Step4TitleSettings: React.FC = (props) => { const t = useStep4Title(props) const { + onUpdatePosition, + onUpdateFont, + onUpdateSize, + onToggleBold, + onToggleItalic, + onToggleStroke, + onToggleShadow, onApplyPreset, onUpdateStyle, activePreset, @@ -64,12 +72,14 @@ const Step4TitleSettings: React.FC = (props) => { previewCount = 1, previewTitles, onPreviewTitlesChange, + enableTemplates, + selectedTemplateId, + onApplyTemplate, onConfirmGenerate, generating, selectedCount = 1, } = props - const [showTitleStyleModal, setShowTitleStyleModal] = useState(false) const isBatch = previewCount > 1 /** 更新单个变体标题;变体0同步写回 titleSettings.title(全局样式面板/草稿/TTS 链路依赖) */ @@ -174,30 +184,27 @@ const Step4TitleSettings: React.FC = (props) => { )} - {/* 标题样式:点击打开弹窗(参考字幕模板/封面模板 Modal 模式) */} - setShowTitleStyleModal(false)} - titleText={previewTitles?.[0] ?? t.titleSettings.title} + {/* 标题样式面板(全局共用) */} + { - onUpdateStyle?.(nextSettings) - if (nextPreset) onApplyPreset(nextPreset) - }} + titlePresets={titlePresets} + POSITION_OPTIONS={POSITION_OPTIONS} + FONT_OPTIONS={FONT_OPTIONS} + enableTemplates={enableTemplates} + selectedTemplateId={selectedTemplateId} + onApplyTemplate={onApplyTemplate} /> -
- - -
) } diff --git a/apps/web/src/pages/generate/components/title/TitleStyleModal.css b/apps/web/src/pages/generate/components/title/TitleStyleModal.css deleted file mode 100644 index bde83e411..000000000 --- a/apps/web/src/pages/generate/components/title/TitleStyleModal.css +++ /dev/null @@ -1,90 +0,0 @@ -.tsm-body { - padding: 0; -} - -.tsm-preview { - width: 100%; - background: #111; - border-radius: 8px; - padding: 12px; - margin-bottom: 16px; - display: flex; - justify-content: center; - overflow: hidden; -} - -.tsm-presets-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); - gap: 12px; - max-height: 440px; - overflow-y: auto; - padding: 4px 2px 8px; -} - -.tsm-preset-card { - display: flex; - flex-direction: column; - align-items: center; - background: #fff; - border: 2px solid #e5e7eb; - border-radius: 10px; - padding: 8px 6px 6px; - cursor: pointer; - transition: all 0.15s ease; - overflow: hidden; -} -.tsm-preset-card:hover { - border-color: #c4b5fd; - transform: translateY(-1px); - box-shadow: 0 4px 10px rgba(124, 58, 237, 0.12); -} -.tsm-preset-card.active { - border-color: #7c3aed; - box-shadow: 0 0 0 3px rgba(124, 58, 237, 0.18); -} - -.tsm-preset-preview { - width: 100%; - aspect-ratio: 9 / 16; - background: #1a1a1a; - border-radius: 6px; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; -} - -.tsm-preset-meta { - display: flex; - align-items: center; - gap: 4px; - margin-top: 6px; - font-size: 12px; - color: #374151; -} -.tsm-preset-emoji { - font-size: 14px; -} -.tsm-preset-label { - font-weight: 500; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 100px; -} - -.tsm-custom-panel { - max-height: 440px; - overflow-y: auto; - padding: 4px 2px 8px; -} - -.tsm-footer { - display: flex; - justify-content: flex-end; - gap: 8px; - padding-top: 16px; - margin-top: 16px; - border-top: 1px solid #f3f4f6; -} diff --git a/apps/web/src/pages/generate/components/title/TitleStyleModal.tsx b/apps/web/src/pages/generate/components/title/TitleStyleModal.tsx deleted file mode 100644 index af4b5292b..000000000 --- a/apps/web/src/pages/generate/components/title/TitleStyleModal.tsx +++ /dev/null @@ -1,189 +0,0 @@ -/** - * 标题样式弹窗(Step3/智能剪辑用) - * - * 交互: - * - 点击"标题样式"按钮/卡片打开 - * - 默认 Tab「预设」:10 个爆款预设卡片网格(Canvas 实时预览),点击选中 - * - Tab「自定义」:详细参数(位置/字体/字号/粗斜/描边/阴影/颜色/背景) - * - 底部「取消」「保存」按钮:保存才会把临时编辑的 settings 应用到父组件 - * - * 设计参考:CoverSettingsModal / 字幕模板选择弹窗 - */ -import React, { useEffect, useState } from "react" -import { Tabs } from "antd" -import Modal from "@/components/ui/Modal" -import Button from "@/components/ui/Button" -import type { TitleSettings } from "../../types" -import { POSITION_OPTIONS } from "../../constants" -import { FONT_OPTIONS } from "@/components/title/constants" -import TitleStylePanel from "./TitleStylePanel" -import TitleMiniPreview from "./TitleMiniPreview" -import { titleStyleConfigToCamel } from "@/components/title/utils" -import { TITLE_PRESETS as TITLE_PRESETS_DATA } from "@/components/title/constants" -import "./TitleStyleModal.css" - -interface TitleStyleModalProps { - open: boolean - onClose: () => void - /** 当前标题文字(用于预览) */ - titleText?: string - /** 当前生效的标题样式 */ - settings: TitleSettings - /** 保存回调:把编辑后的 settings 回传父组件 */ - onSave: (settings: TitleSettings, presetKey: string | null) => void - /** 当前选中的爆款预设 key(用于高亮) */ - activePreset?: string | null -} - -/** 把预设应用到 base settings(参考 useTitleStyleUpdaters.applyPreset 逻辑) */ -function applyPresetToSettings(base: TitleSettings, presetKey: string): TitleSettings { - const preset = TITLE_PRESETS_DATA.find((p) => p.key === presetKey) - if (!preset) return base - return { - ...base, - ...(titleStyleConfigToCamel(preset.style) as Partial), - lineOverrides: [], - } -} - -const TitleStyleModal: React.FC = ({ - open, - onClose, - titleText = "标题", - settings, - onSave, - activePreset: externalActivePreset = null, -}) => { - // 弹窗内维护一份草稿 settings,点保存才提交到父组件;取消/关闭丢弃 - const [draft, setDraft] = useState(settings) - const [activePreset, setActivePreset] = useState(externalActivePreset) - - // 每次打开弹窗,用父组件最新 settings 重置草稿 - useEffect(() => { - if (open) { - setDraft(settings) - setActivePreset(externalActivePreset) - } - }, [open, settings, externalActivePreset]) - - const handleApplyPreset = (presetKey: string) => { - setActivePreset(presetKey) - const next = applyPresetToSettings(draft, presetKey) - setDraft(next) - } - - const handleUpdateStyle = (patch: Partial) => { - setDraft((prev) => ({ ...prev, ...patch })) - // 手动调了参数,清除预设高亮(因为不再严格匹配某个预设) - setActivePreset(null) - } - - const handleUpdatePosition = (position: string) => - handleUpdateStyle({ position: position as TitleSettings["position"] }) - const handleUpdateFont = (font: string) => handleUpdateStyle({ font }) - const handleUpdateSize = (size: number) => handleUpdateStyle({ size }) - const handleToggleBold = () => handleUpdateStyle({ bold: !draft.bold }) - const handleToggleItalic = () => handleUpdateStyle({ italic: !draft.italic }) - const handleToggleStroke = () => handleUpdateStyle({ stroke: !draft.stroke }) - const handleToggleShadow = () => handleUpdateStyle({ shadow: !draft.shadow }) - - const handleSave = () => { - onSave(draft, activePreset) - onClose() - } - - return ( - -
- {/* 顶部预览 */} -
- -
- - - {TITLE_PRESETS_DATA.map((p) => { - const previewSettings = applyPresetToSettings(draft, p.key) - const isActive = activePreset === p.key - return ( - - ) - })} -
- ), - }, - { - key: "custom", - label: "自定义", - children: ( -
- -
- ), - }, - ]} - /> - - - {/* 底部按钮栏 */} -
- - -
-
- ) -} - -export default TitleStyleModal