64387c00bb
CI/CD Pipeline / Check if frontend-only change (push) Blocked by required conditions
CI/CD Pipeline / Validate - Code Quality (push) Blocked by required conditions
CI/CD Pipeline / Validate - Type Check (mypy) (push) Blocked by required conditions
CI/CD Pipeline / Validate - Migration (alembic) (push) Blocked by required conditions
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) Blocked by required conditions
CI/CD Pipeline / Frontend Unit Tests (push) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (push) Blocked by required conditions
CI/CD Pipeline / PR Build Web Image (push) Blocked by required conditions
CI/CD Pipeline / PR Build Worker Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging API Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging Web Image (push) Blocked by required conditions
CI/CD Pipeline / Build Staging Worker Image (push) Blocked by required conditions
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) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (push) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (push) Blocked by required conditions
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
CI/CD Pipeline / Canary Release to Production (push) Blocked by required conditions
143 lines
4.2 KiB
TypeScript
143 lines
4.2 KiB
TypeScript
/**
|
|
* 贴纸素材库(emoji / 图片 / 文字花字)
|
|
*/
|
|
import React, { useState } from "react"
|
|
import type { StickerType, TextStickerPreset } from "@/pages/editing-planner/types"
|
|
import {
|
|
EMOJI_LIST,
|
|
STICKER_TYPE_TABS,
|
|
TEXT_PRESET_STYLES,
|
|
TEXT_STICKER_PRESET_LABELS,
|
|
} from "@/pages/editing-planner/constants/sticker"
|
|
|
|
interface StickerLibraryProps {
|
|
activeTab: StickerType
|
|
onTabChange: (tab: StickerType) => void
|
|
onAddSticker: (type: StickerType, content: string) => void
|
|
}
|
|
|
|
const StickerLibrary: React.FC<StickerLibraryProps> = ({
|
|
activeTab,
|
|
onTabChange,
|
|
onAddSticker,
|
|
}) => {
|
|
const [textInput, setTextInput] = useState("")
|
|
const imageInputRef = React.useRef<HTMLInputElement>(null)
|
|
|
|
const handleAddImage = () => {
|
|
const val = imageInputRef.current?.value.trim()
|
|
if (val) {
|
|
onAddSticker("image", val)
|
|
if (imageInputRef.current) imageInputRef.current.value = ""
|
|
}
|
|
}
|
|
|
|
const handleAddText = () => {
|
|
if (textInput.trim()) {
|
|
onAddSticker("text", textInput.trim())
|
|
setTextInput("")
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{/* 类型 Tab */}
|
|
<div className="sticker-tabs">
|
|
{STICKER_TYPE_TABS.map((t) => (
|
|
<button
|
|
key={t.value}
|
|
className={`sticker-tab${activeTab === t.value ? " active" : ""}`}
|
|
onClick={() => onTabChange(t.value)}
|
|
>
|
|
{t.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{/* Tab 内容区 */}
|
|
<div className="sticker-tab-content">
|
|
{/* Emoji 素材库 */}
|
|
{activeTab === "emoji" && (
|
|
<div className="sticker-emoji-grid">
|
|
{EMOJI_LIST.map((emoji) => (
|
|
<button
|
|
key={emoji}
|
|
className="sticker-emoji-btn"
|
|
onClick={() => onAddSticker("emoji", emoji)}
|
|
>
|
|
{emoji}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* 图片贴纸 */}
|
|
{activeTab === "image" && (
|
|
<div className="sticker-image-input">
|
|
<input
|
|
ref={imageInputRef}
|
|
type="text"
|
|
className="sticker-url-input"
|
|
placeholder="输入图片 URL 添加贴纸..."
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter" && e.currentTarget.value.trim()) {
|
|
handleAddImage()
|
|
}
|
|
}}
|
|
/>
|
|
<button className="sticker-url-add-btn" onClick={handleAddImage}>
|
|
添加
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
{/* 文字花字 */}
|
|
{activeTab === "text" && (
|
|
<div className="sticker-text-section">
|
|
<div className="sticker-text-input-row">
|
|
<input
|
|
type="text"
|
|
className="sticker-text-input"
|
|
placeholder="输入文字内容..."
|
|
value={textInput}
|
|
onChange={(e) => setTextInput(e.target.value)}
|
|
/>
|
|
<button
|
|
className="sticker-text-add-btn"
|
|
disabled={!textInput.trim()}
|
|
onClick={handleAddText}
|
|
>
|
|
添加
|
|
</button>
|
|
</div>
|
|
<div className="sticker-text-presets">
|
|
<div className="sticker-preset-title">花字预设预览</div>
|
|
<div className="sticker-preset-grid">
|
|
{(Object.keys(TEXT_STICKER_PRESET_LABELS) as TextStickerPreset[]).map((p) => (
|
|
<div
|
|
key={p}
|
|
className="sticker-preset-preview"
|
|
style={{
|
|
background:
|
|
p === "bubble"
|
|
? "rgba(0,0,0,0.5)"
|
|
: p === "gradient"
|
|
? "linear-gradient(90deg,#f093fb,#f5576c)"
|
|
: "#1a1a2e",
|
|
}}
|
|
>
|
|
<span style={TEXT_PRESET_STYLES[p]}>示例</span>
|
|
<div className="sticker-preset-name">{TEXT_STICKER_PRESET_LABELS[p]}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
)
|
|
}
|
|
|
|
export default StickerLibrary
|