@@ -6440,3 +6440,47 @@
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ═══ 标题设置 — 颜色预设 ═══ */
|
||||
.ep-color-presets {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ep-color-swatch {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
border: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.15s, transform 0.1s;
|
||||
}
|
||||
|
||||
.ep-color-swatch:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.ep-color-swatch.active {
|
||||
border-color: var(--ep-primary, #4f8cff);
|
||||
}
|
||||
|
||||
.ep-color-picker {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.ep-color-picker::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ep-color-picker::-webkit-color-swatch {
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
@@ -196,6 +196,8 @@ const EditingPlanner: React.FC = () => {
|
||||
|
||||
{/* 右栏 260px:设置面板 */}
|
||||
<RightPanel
|
||||
titleConfig={titleConfig}
|
||||
onTitleConfigChange={setTitleConfig}
|
||||
rightTab={rightTab}
|
||||
onTabChange={setRightTab}
|
||||
selectedClip={clipOps.selectedClip}
|
||||
|
||||
@@ -5,12 +5,15 @@
|
||||
import React from "react"
|
||||
import type { ClipPropertiesPanelProps } from "@/pages/editing-planner/types/clipProperties"
|
||||
import SubtitleSettingsSection from "./clip-properties/SubtitleSettingsSection"
|
||||
import TitleSettingsSection from "./clip-properties/TitleSettingsSection"
|
||||
import BgmSettingsSection from "./clip-properties/BgmSettingsSection"
|
||||
import ClipDetailSection from "./clip-properties/ClipDetailSection"
|
||||
import StatsSection from "./clip-properties/StatsSection"
|
||||
import { useVoicePreview } from "@/pages/editing-planner/hooks/useVoicePreview"
|
||||
|
||||
const ClipPropertiesPanel: React.FC<ClipPropertiesPanelProps> = ({
|
||||
titleConfig,
|
||||
onTitleConfigChange,
|
||||
selectedClip,
|
||||
subtitleSettings,
|
||||
bgmSettings,
|
||||
@@ -40,6 +43,11 @@ const ClipPropertiesPanel: React.FC<ClipPropertiesPanelProps> = ({
|
||||
|
||||
return (
|
||||
<div className="ep-right-panel">
|
||||
{/* ═══ 标题设置 — #1789 ═══ */}
|
||||
{titleConfig && onTitleConfigChange && (
|
||||
<TitleSettingsSection config={titleConfig} onChange={onTitleConfigChange} />
|
||||
)}
|
||||
|
||||
{/* ═══ 字幕设置 ═══ */}
|
||||
<SubtitleSettingsSection
|
||||
settings={subtitleSettings}
|
||||
|
||||
@@ -5,9 +5,12 @@ import type { ClipData } from "../types"
|
||||
import type { SubtitleStyleConfig } from "../types/subtitle"
|
||||
import type { BgmMixConfig } from "@/api/bgm"
|
||||
import type { TemplateMode } from "@/api/editing-planner"
|
||||
import type { TitleConfig } from "@/api/template-editor"
|
||||
import type { AssetItem } from "@/api/assets"
|
||||
|
||||
interface RightPanelProps {
|
||||
titleConfig?: TitleConfig
|
||||
onTitleConfigChange?: (config: TitleConfig | ((prev: TitleConfig) => TitleConfig)) => void
|
||||
rightTab: "properties" | "clips"
|
||||
onTabChange: (tab: "properties" | "clips") => void
|
||||
// 属性 tab
|
||||
@@ -46,6 +49,8 @@ interface RightPanelProps {
|
||||
}
|
||||
|
||||
const RightPanel: React.FC<RightPanelProps> = ({
|
||||
titleConfig,
|
||||
onTitleConfigChange,
|
||||
rightTab,
|
||||
onTabChange,
|
||||
selectedClip,
|
||||
@@ -118,6 +123,8 @@ const RightPanel: React.FC<RightPanelProps> = ({
|
||||
>["onBgmSettingsChange"]
|
||||
return (
|
||||
<ClipPropertiesPanel
|
||||
titleConfig={titleConfig}
|
||||
onTitleConfigChange={onTitleConfigChange}
|
||||
selectedClip={selectedClip}
|
||||
subtitleSettings={sub}
|
||||
bgmSettings={bgm}
|
||||
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* 标题设置区块 — #1789
|
||||
* 提供字号滑块、字体预设、位置、颜色等控制入口
|
||||
*/
|
||||
import React from "react"
|
||||
import type { TitleConfig } from "@/api/template-editor"
|
||||
import { POSITION_OPTIONS, FONT_OPTIONS } from "@/pages/editing-planner/constants/clipProperties"
|
||||
|
||||
interface TitleSettingsSectionProps {
|
||||
config: TitleConfig
|
||||
onChange: (config: TitleConfig | ((prev: TitleConfig) => TitleConfig)) => void
|
||||
}
|
||||
|
||||
const TITLE_COLOR_PRESETS = [
|
||||
"#ffffff",
|
||||
"#000000",
|
||||
"#ff4444",
|
||||
"#ffaa00",
|
||||
"#44ff44",
|
||||
"#4488ff",
|
||||
"#ff44ff",
|
||||
"#ffff44",
|
||||
]
|
||||
|
||||
const TitleSettingsSection: React.FC<TitleSettingsSectionProps> = ({ config, onChange }) => {
|
||||
const update = (partial: Partial<TitleConfig>) => {
|
||||
onChange((prev: TitleConfig) => ({ ...prev, ...partial }))
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="ep-settings-section">
|
||||
<div className="ep-section-title">
|
||||
<span className="ep-section-icon">📝</span>
|
||||
标题设置
|
||||
</div>
|
||||
|
||||
{/* AI 自动选择开关 */}
|
||||
<div className="ep-toggle-row">
|
||||
<span className="ep-toggle-label">AI 自动选择</span>
|
||||
<div
|
||||
className={`ep-toggle ${config.ai_auto_select ? "active" : ""}`}
|
||||
onClick={() => update({ ai_auto_select: !config.ai_auto_select })}
|
||||
>
|
||||
<div className="ep-toggle-knob" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!config.ai_auto_select && (
|
||||
<>
|
||||
{/* 标题文本 */}
|
||||
<div className="ep-field">
|
||||
<label className="ep-field-label">标题文本</label>
|
||||
<input
|
||||
className="ep-form-select"
|
||||
type="text"
|
||||
placeholder="输入标题内容"
|
||||
value={config.content}
|
||||
onChange={(e) => update({ content: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 位置 */}
|
||||
<div className="ep-field">
|
||||
<label className="ep-field-label">位置</label>
|
||||
<select
|
||||
className="ep-form-select"
|
||||
value={config.position}
|
||||
onChange={(e) => update({ position: e.target.value })}
|
||||
>
|
||||
{POSITION_OPTIONS.map((opt) => (
|
||||
<option key={opt.value} value={opt.value}>
|
||||
{opt.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* 字体预设 */}
|
||||
<div className="ep-field">
|
||||
<label className="ep-field-label">字体</label>
|
||||
<select
|
||||
className="ep-form-select"
|
||||
value={config.font_preset}
|
||||
onChange={(e) => update({ font_preset: e.target.value })}
|
||||
>
|
||||
{FONT_OPTIONS.map((f) => (
|
||||
<option key={f} value={f}>
|
||||
{f}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* 字号滑块 */}
|
||||
<div className="ep-field">
|
||||
<label className="ep-field-label">字号</label>
|
||||
<div className="ep-slider-row">
|
||||
<input
|
||||
className="ep-slider"
|
||||
type="range"
|
||||
min={12}
|
||||
max={72}
|
||||
value={config.font_size}
|
||||
onChange={(e) => update({ font_size: Number(e.target.value) })}
|
||||
/>
|
||||
<span className="ep-slider-value">{config.font_size}px</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 颜色 */}
|
||||
<div className="ep-field">
|
||||
<label className="ep-field-label">颜色</label>
|
||||
<div className="ep-color-presets">
|
||||
{TITLE_COLOR_PRESETS.map((color) => (
|
||||
<div
|
||||
key={color}
|
||||
className={`ep-color-swatch${config.font_color === color ? " active" : ""}`}
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={() => update({ font_color: color })}
|
||||
/>
|
||||
))}
|
||||
<input
|
||||
type="color"
|
||||
className="ep-color-picker"
|
||||
value={config.font_color}
|
||||
onChange={(e) => update({ font_color: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TitleSettingsSection
|
||||
@@ -4,6 +4,7 @@
|
||||
import type { ClipData } from "./clip"
|
||||
import type { TemplateMode } from "@/api/editing-planner"
|
||||
import type { AssetItem } from "@/api/assets"
|
||||
import type { TitleConfig } from "@/api/template-editor"
|
||||
|
||||
export interface SubtitleSettings {
|
||||
enabled: boolean
|
||||
@@ -28,6 +29,10 @@ export interface BgmSettings {
|
||||
}
|
||||
|
||||
export interface ClipPropertiesPanelProps {
|
||||
/** 标题配置 — #1789 */
|
||||
titleConfig?: TitleConfig
|
||||
/** 标题配置变更 */
|
||||
onTitleConfigChange?: (config: TitleConfig | ((prev: TitleConfig) => TitleConfig)) => void
|
||||
selectedClip: ClipData | null
|
||||
subtitleSettings: SubtitleSettings
|
||||
bgmSettings: BgmSettings
|
||||
|
||||
@@ -13,6 +13,8 @@ export const formatTrimTime = (sec: number): string => {
|
||||
/** 生成时间标尺刻度 */
|
||||
export const generateRulerMarks = (totalDuration: number, step: number): number[] => {
|
||||
const marks: number[] = []
|
||||
// #1790: 无片段时不显示时间刻度
|
||||
if (totalDuration <= 0) return marks
|
||||
for (let t = 0; t <= totalDuration + step; t += step) {
|
||||
marks.push(t)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user