Files
xiaoxia-saas/apps/web/src/pages/editing-planner/components/SubtitleStylePanel.tsx
T
xiaoxia 3862996045
CI/CD Pipeline / Check if frontend-only change (push) Waiting to run
CI/CD Pipeline / Validate - Code Quality (push) Waiting to run
CI/CD Pipeline / Validate - Type Check (mypy) (push) Waiting to run
CI/CD Pipeline / Validate - Migration (alembic) (push) Waiting to run
CI/CD Pipeline / Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (push) Waiting to run
CI/CD Pipeline / Frontend Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (push) Waiting to run
CI/CD Pipeline / PR Build Web Image (push) Waiting to run
CI/CD Pipeline / PR Build Worker Image (push) Waiting to run
CI/CD Pipeline / Build Staging API Image (push) Waiting to run
CI/CD Pipeline / Build Staging Web Image (push) Waiting to run
CI/CD Pipeline / Build Staging Worker Image (push) Waiting to run
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (push) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (push) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (push) Waiting to run
CI/CD Pipeline / Build Production Web Image (push) Waiting to run
CI/CD Pipeline / Build Production Worker Image (push) Waiting to run
CI/CD Pipeline / Deploy Production (push) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (push) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (push) Blocked by required conditions
refactor(#783): 统一API文件命名风格为kebab-case (#788)
2026-07-23 22:34:56 +08:00

224 lines
7.2 KiB
TypeScript

/**
* 字幕样式配置面板 — Drawer 形式
* 字幕开关(手动 / ASR 自动识别)、字体大小、颜色、描边/阴影、位置、ASR 语言
*/
import React from "react"
import { Drawer, Slider, ColorPicker, Select } from "antd"
import type { Color } from "antd/es/color-picker"
import type { SubtitleStyleConfig } from "../types/subtitle"
/* ──────────── 选项常量 ──────────── */
const POSITION_OPTIONS = [
{ value: "top", label: "顶部" },
{ value: "center", label: "居中" },
{ value: "bottom", label: "底部" },
]
const FONT_OPTIONS = ["思源黑体", "思源宋体", "苹方", "PingFang", "微软雅黑", "楷体", "华康俪金黑"]
const ANIMATION_OPTIONS = [
{ value: "none", label: "无" },
{ value: "fade", label: "淡入淡出" },
{ value: "slide", label: "滑动" },
{ value: "typewriter", label: "打字机" },
]
const ASR_LANGUAGE_OPTIONS = [
{ value: "zh", label: "中文" },
{ value: "en", label: "English" },
]
/* ──────────── Props ──────────── */
interface SubtitleStylePanelProps {
open: boolean
onClose: () => void
config: SubtitleStyleConfig
onChange: (config: SubtitleStyleConfig) => void
}
const SubtitleStylePanel: React.FC<SubtitleStylePanelProps> = ({
open,
onClose,
config,
onChange,
}) => {
const update = (partial: Partial<SubtitleStyleConfig>) => {
onChange({ ...config, ...partial })
}
return (
<Drawer
title="💬 字幕样式配置"
placement="right"
width={380}
open={open}
onClose={onClose}
className="subtitle-style-drawer"
>
{/* ── 字幕开关 ── */}
<div className="sub-field">
<div className="sub-toggle-row">
<span className="sub-label">启用字幕</span>
<div
className={`ep-toggle${config.enabled ? " active" : ""}`}
onClick={() => update({ enabled: !config.enabled })}
>
<div className="ep-toggle-knob" />
</div>
</div>
</div>
{config.enabled && (
<>
{/* ── 模式切换 ── */}
<div className="sub-field">
<label className="sub-label">字幕来源</label>
<div className="sub-mode-switch">
<button
className={`sub-mode-btn${config.mode === "manual" ? " active" : ""}`}
onClick={() => update({ mode: "manual" })}
>
✏️ 手动输入
</button>
<button
className={`sub-mode-btn${config.mode === "asr" ? " active" : ""}`}
onClick={() => update({ mode: "asr" })}
>
🤖 ASR 识别
</button>
</div>
</div>
{/* ── ASR 语言(仅 ASR 模式) ── */}
{config.mode === "asr" && (
<div className="sub-field">
<label className="sub-label">识别语言</label>
<Select
className="sub-select"
value={config.asrLanguage}
onChange={(v) => update({ asrLanguage: v })}
options={ASR_LANGUAGE_OPTIONS}
popupMatchSelectWidth={false}
/>
</div>
)}
{/* ── 字体大小 ── */}
<div className="sub-field">
<label className="sub-label">
字体大小 <span className="sub-value">{config.fontSize}px</span>
</label>
<Slider
min={12}
max={48}
value={config.fontSize}
onChange={(v) => update({ fontSize: v })}
/>
</div>
{/* ── 字体颜色 ── */}
<div className="sub-field">
<label className="sub-label">字体颜色</label>
<div className="sub-color-row">
<ColorPicker
value={config.fontColor}
onChange={(_color: Color, hex: string) => update({ fontColor: hex })}
showText
/>
</div>
</div>
{/* ── 字体 ── */}
<div className="sub-field">
<label className="sub-label">字体</label>
<Select
className="sub-select"
value={config.font}
onChange={(v) => update({ font: v })}
options={FONT_OPTIONS.map((f) => ({ value: f, label: f }))}
popupMatchSelectWidth={false}
/>
</div>
{/* ── 字幕位置 ── */}
<div className="sub-field">
<label className="sub-label">位置</label>
<div className="sub-position-group">
{POSITION_OPTIONS.map((opt) => (
<button
key={opt.value}
className={`sub-position-btn${config.position === opt.value ? " active" : ""}`}
onClick={() =>
update({
position: opt.value as SubtitleStyleConfig["position"],
})
}
>
{opt.label}
</button>
))}
</div>
</div>
{/* ── 描边 / 阴影 ── */}
<div className="sub-field">
<label className="sub-label">效果</label>
<div className="sub-effect-btns">
<button
className={`sub-effect-btn${config.stroke ? " active" : ""}`}
onClick={() => update({ stroke: !config.stroke })}
>
S 描边
</button>
<button
className={`sub-effect-btn${config.shadow ? " active" : ""}`}
onClick={() => update({ shadow: !config.shadow })}
>
阴影
</button>
</div>
</div>
{/* ── 动画 ── */}
<div className="sub-field">
<label className="sub-label">动画效果</label>
<Select
className="sub-select"
value={config.animation}
onChange={(v) => update({ animation: v })}
options={ANIMATION_OPTIONS.map((o) => ({
value: o.value,
label: o.label,
}))}
popupMatchSelectWidth={false}
/>
</div>
{/* ── 预览 ── */}
<div className="sub-field">
<label className="sub-label">预览</label>
<div className="sub-preview-box">
<span
className="sub-preview-text"
style={{
fontSize: `${Math.min(config.fontSize, 28)}px`,
color: config.fontColor,
fontFamily: config.font,
WebkitTextStroke: config.stroke ? "1px #000" : undefined,
textShadow: config.shadow ? "2px 2px 4px rgba(0,0,0,0.8)" : undefined,
}}
>
这是一段字幕预览
</span>
</div>
</div>
</>
)}
</Drawer>
)
}
export default SubtitleStylePanel