fix(title): 预设样式解耦字号 + 右侧标题实时预览 #1232
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user