fix(title): 预设样式解耦字号 + 右侧标题实时预览 #1232

Merged
xiaoxia merged 3 commits from fix/title-preset-decouple-and-preview into develop 2026-08-04 10:09:23 +08:00
3 changed files with 85 additions and 2 deletions
@@ -219,6 +219,8 @@ const GeneratePage: React.FC = () => {
progress={step3Preview.progress}
videoRatio={videoRatio}
onRegenerate={step3Preview.regeneratePreview}
titleText={titleSettings.title}
titleSettings={titleSettings}
/>
)}
@@ -1,10 +1,12 @@
/**
* 右侧预览视频面板
* Step3 生成预览后常驻显示预览视频
* Step4+ 显示标题实时预览
*/
import React from "react"
import { PlayCircleOutlined, LoadingOutlined } from "@ant-design/icons"
import type { PreviewResult, PreviewStatus } from "../hooks/useStep3Preview"
import type { TitleSettings } from "../types"
interface PreviewVideoPanelProps {
previewStatus: PreviewStatus
@@ -13,6 +15,45 @@ interface PreviewVideoPanelProps {
progress: number
videoRatio: string
onRegenerate: () => void
/** 标题文字(Step4 起传入) */
titleText?: string
/** 标题样式设置(Step4 起传入) */
titleSettings?: TitleSettings
}
/** 根据 position 返回垂直对齐样式 */
const positionAlignStyle = (position: string): React.CSSProperties => {
switch (position) {
case "top":
return { justifyContent: "flex-start", paddingTop: 16 }
case "center":
return { justifyContent: "center" }
case "bottom":
default:
return { justifyContent: "flex-end", paddingBottom: 16 }
}
}
/** 根据 TitleSettings 生成文字样式 */
const buildTitleStyle = (settings: TitleSettings): React.CSSProperties => {
const style: React.CSSProperties = {
fontFamily: settings.font,
fontSize: Math.min(settings.size, 36), // 预览区域限制最大字号
color: settings.color,
fontWeight: settings.bold ? 700 : 400,
fontStyle: settings.italic ? "italic" : "normal",
textAlign: "center",
lineHeight: 1.4,
wordBreak: "break-word",
padding: "0 12px",
}
if (settings.stroke) {
style.WebkitTextStroke = "1px rgba(0,0,0,0.6)"
}
if (settings.shadow) {
style.textShadow = "2px 2px 4px rgba(0,0,0,0.7)"
}
return style
}
export const PreviewVideoPanel: React.FC<PreviewVideoPanelProps> = ({
@@ -22,10 +63,13 @@ export const PreviewVideoPanel: React.FC<PreviewVideoPanelProps> = ({
progress,
videoRatio,
onRegenerate,
titleText,
titleSettings,
}) => {
const hasPreview = previewStatus === "ready" && previewResult
const isLoading = previewStatus === "pending" || previewStatus === "generating"
const isError = previewStatus === "error"
const showTitlePreview = !!titleSettings
return (
<div className="xx-generate-preview">
@@ -84,6 +128,43 @@ export const PreviewVideoPanel: React.FC<PreviewVideoPanelProps> = ({
</div>
)}
{/* 标题实时预览区域 */}
{showTitlePreview && (
<div
className="xx-preview-title-section"
style={{
marginTop: 12,
padding: "12px 0",
borderTop: "1px solid rgba(255,255,255,0.08)",
}}
>
<div
style={{
fontSize: 12,
color: "var(--text-tertiary)",
marginBottom: 8,
paddingLeft: 4,
}}
>
</div>
<div
style={{
background: "rgba(0,0,0,0.3)",
borderRadius: 8,
minHeight: 64,
display: "flex",
alignItems: "center",
...positionAlignStyle(titleSettings!.position),
}}
>
<span style={buildTitleStyle(titleSettings!)}>
{titleText && titleText.trim() ? titleText : "请选择或输入标题"}
</span>
</div>
</div>
)}
{/* 预览信息 */}
{hasPreview && previewResult && (
<div className="xx-preview-info">
@@ -15,10 +15,10 @@ export function useTitleStyleUpdaters({
titleSettings,
onTitleSettingsChange,
}: UseTitleStyleUpdatersOptions) {
/** 匹配预设:只用 color/bold/italic/stroke/shadow,不再匹配 size */
const getActivePreset = (settings: TitleSettings): string | null => {
for (const p of TITLE_PRESETS) {
if (
settings.size === p.style.size &&
settings.color === p.style.color &&
settings.bold === p.style.bold &&
settings.italic === p.style.italic &&
@@ -88,13 +88,13 @@ export function useTitleStyleUpdaters({
onTitleSettingsChange({ ...titleSettings, shadow: !titleSettings.shadow })
}, [titleSettings, onTitleSettingsChange])
/** 应用预设:只覆盖 color/bold/italic/stroke/shadow,不改变字号 */
const applyPreset = useCallback(
(presetKey: string) => {
const preset = TITLE_PRESETS.find((p) => p.key === presetKey)
if (!preset) return
onTitleSettingsChange({
...titleSettings,
size: preset.style.size,
color: preset.style.color,
bold: preset.style.bold,
italic: preset.style.italic,