refactor(StickerPanel): 提取属性编辑面板组件

This commit is contained in:
2026-07-30 09:06:11 +08:00
parent 0f8f27d844
commit 532f5fcdfd
@@ -0,0 +1,197 @@
import React from "react"
import type { StickerItem, TextStickerPreset } from "../../types"
import { TEXT_STICKER_PRESET_LABELS } from "../../types"
import { TEXT_PRESET_STYLES } from "../constants"
export interface StickerPropsEditorProps {
sticker: StickerItem
totalDuration: number
onChange: (id: string, partial: Partial<StickerItem>) => void
}
export const StickerPropsEditor: React.FC<StickerPropsEditorProps> = ({
sticker,
totalDuration,
onChange,
}) => {
const update = (partial: Partial<StickerItem>) => {
onChange(sticker.id, partial)
}
return (
<div className="sticker-props-section">
<div className="sticker-section-title">属性调整</div>
{/* 位置 X */}
<div className="sticker-prop-row">
<span className="sticker-prop-label">位置 X</span>
<input
type="range"
className="sticker-prop-slider"
min={0}
max={100}
value={sticker.x}
onChange={(e) => update({ x: Number(e.target.value) })}
/>
<span className="sticker-prop-value">{sticker.x}%</span>
</div>
{/* 位置 Y */}
<div className="sticker-prop-row">
<span className="sticker-prop-label">位置 Y</span>
<input
type="range"
className="sticker-prop-slider"
min={0}
max={100}
value={sticker.y}
onChange={(e) => update({ y: Number(e.target.value) })}
/>
<span className="sticker-prop-value">{sticker.y}%</span>
</div>
{/* 尺寸 */}
<div className="sticker-prop-row">
<span className="sticker-prop-label">大小</span>
<input
type="range"
className="sticker-prop-slider"
min={5}
max={50}
value={sticker.width}
onChange={(e) =>
update({
width: Number(e.target.value),
height: Number(e.target.value),
})
}
/>
<span className="sticker-prop-value">{sticker.width}%</span>
</div>
{/* 旋转 */}
<div className="sticker-prop-row">
<span className="sticker-prop-label">旋转</span>
<input
type="range"
className="sticker-prop-slider"
min={-180}
max={180}
value={sticker.rotation}
onChange={(e) => update({ rotation: Number(e.target.value) })}
/>
<span className="sticker-prop-value">{sticker.rotation}°</span>
</div>
{/* 透明度 */}
<div className="sticker-prop-row">
<span className="sticker-prop-label">透明度</span>
<input
type="range"
className="sticker-prop-slider"
min={0}
max={100}
value={sticker.opacity}
onChange={(e) => update({ opacity: Number(e.target.value) })}
/>
<span className="sticker-prop-value">{sticker.opacity}%</span>
</div>
{/* 时间 */}
<div className="sticker-prop-row">
<span className="sticker-prop-label">开始</span>
<input
type="number"
className="sticker-prop-number"
min={0}
max={totalDuration}
step={0.1}
value={sticker.start_time}
onChange={(e) => update({ start_time: Number(e.target.value) })}
/>
<span className="sticker-prop-label">时长</span>
<input
type="number"
className="sticker-prop-number"
min={0}
max={totalDuration}
step={0.1}
value={sticker.duration}
onChange={(e) => update({ duration: Number(e.target.value) })}
/>
</div>
{/* 文字贴纸特有属性 */}
{sticker.type === "text" && (
<>
<div className="sticker-prop-row">
<span className="sticker-prop-label">花字</span>
<select
className="sticker-prop-select"
value={sticker.text_preset}
onChange={(e) => update({ text_preset: e.target.value as TextStickerPreset })}
>
{(Object.keys(TEXT_STICKER_PRESET_LABELS) as TextStickerPreset[]).map((p) => (
<option key={p} value={p}>
{TEXT_STICKER_PRESET_LABELS[p]}
</option>
))}
</select>
</div>
<div className="sticker-prop-row">
<span className="sticker-prop-label">字号</span>
<input
type="range"
className="sticker-prop-slider"
min={12}
max={72}
value={sticker.font_size}
onChange={(e) => update({ font_size: Number(e.target.value) })}
/>
<span className="sticker-prop-value">{sticker.font_size}px</span>
</div>
<div className="sticker-prop-row">
<span className="sticker-prop-label">颜色</span>
<input
type="color"
className="sticker-prop-color"
value={sticker.text_color}
onChange={(e) => update({ text_color: e.target.value })}
/>
</div>
</>
)}
{/* 预览 */}
<div className="sticker-preview-box">
<div
className="sticker-preview-item"
style={{
left: `${sticker.x}%`,
top: `${sticker.y}%`,
width: `${sticker.width}%`,
height: `${sticker.width}%`,
transform: `translate(-50%, -50%) rotate(${sticker.rotation}deg)`,
opacity: sticker.opacity / 100,
fontSize: sticker.type === "text" ? `${sticker.font_size}px` : undefined,
...TEXT_PRESET_STYLES[sticker.text_preset],
}}
>
{sticker.type === "emoji" && sticker.content}
{sticker.type === "text" && sticker.content}
{sticker.type === "image" && (
<img
src={sticker.content}
alt="sticker"
style={{
width: "100%",
height: "100%",
objectFit: "contain",
}}
/>
)}
</div>
</div>
</div>
)
}