import { useState, useCallback, useEffect, useMemo } from 'react'; import { Card, Row, Col, Tag, Space, Empty, Modal, Image, Button, Spin, Select, Typography, Tooltip, Popconfirm, Form, Input, } from 'antd'; import { ReloadOutlined, AppstoreOutlined, PictureOutlined, PlusOutlined, DeleteOutlined, } from '@ant-design/icons'; import { stickerApi } from '../../api/diary/stickers'; import type { StickerPack, Sticker } from '../../api/diary/types'; import { PageContainer } from '../../components/PageContainer'; import { useApiRequest } from '../../hooks/useApiRequest'; import { useThemeMode } from '../../hooks/useThemeMode'; const { Text, Paragraph } = Typography; // --- Category color mapping --- const CATEGORY_COLORS: Record = { animal: '#E07A5F', food: '#81B29A', nature: '#6DB1BF', emoji: '#F2CC8F', school: '#9B8EC4', holiday: '#D4A5A5', travel: '#5F9EA0', sport: '#E8A87C', }; const CATEGORY_LABELS: Record = { animal: '动物', food: '美食', nature: '自然', emoji: '表情', school: '校园', holiday: '节日', travel: '旅行', sport: '运动', }; // --- Filter state --- interface StickerFilters { category: string | undefined; } const DEFAULT_FILTERS: StickerFilters = { category: undefined, }; export default function StickerPackList() { const isDark = useThemeMode(); const { execute } = useApiRequest(); // --- Data state --- const [packs, setPacks] = useState([]); const [loading, setLoading] = useState(false); const [filters, setFilters] = useState(DEFAULT_FILTERS); // --- Pack detail modal state --- const [modalOpen, setModalOpen] = useState(false); const [currentPack, setCurrentPack] = useState(null); const [stickers, setStickers] = useState([]); const [stickersLoading, setStickersLoading] = useState(false); // --- Create modal state --- const [createOpen, setCreateOpen] = useState(false); const [createForm] = Form.useForm(); const [creating, setCreating] = useState(false); // --- Fetch sticker packs --- const fetchPacks = useCallback(async (currentFilters: StickerFilters) => { setLoading(true); try { const params: { category?: string } = {}; if (currentFilters.category) { params.category = currentFilters.category; } const result = await stickerApi.listPacks(params); setPacks(result); } catch { // Error handled by useApiRequest if used via execute, or silently here setPacks([]); } finally { setLoading(false); } }, []); // --- Initial load --- useEffect(() => { fetchPacks(DEFAULT_FILTERS); }, [fetchPacks]); // --- Handle filter change --- const handleFilterChange = useCallback((value: string | undefined) => { const next = { category: value }; setFilters(next); fetchPacks(next); }, [fetchPacks]); // --- Reset filters --- const handleResetFilters = useCallback(() => { const reset = { ...DEFAULT_FILTERS }; setFilters(reset); fetchPacks(reset); }, [fetchPacks]); // --- Open pack detail modal --- const openPackDetail = useCallback(async (pack: StickerPack) => { setCurrentPack(pack); setModalOpen(true); setStickersLoading(true); setStickers([]); const result = await execute( () => stickerApi.listStickers(pack.id), undefined, '加载贴纸失败', ); setStickersLoading(false); if (result) { setStickers(result); } }, [execute]); // --- Close pack detail modal --- const closePackDetail = useCallback(() => { setModalOpen(false); setCurrentPack(null); setStickers([]); }, []); // --- Create sticker pack --- const handleCreate = useCallback(async () => { const values = await createForm.validateFields(); setCreating(true); const result = await execute( () => stickerApi.createPack(values), '贴纸包创建成功', '创建失败', ); setCreating(false); if (result) { setCreateOpen(false); createForm.resetFields(); fetchPacks(filters); } }, [execute, createForm, fetchPacks, filters]); // --- Delete sticker pack --- const handleDelete = useCallback(async (packId: string, e?: React.MouseEvent) => { e?.stopPropagation(); const result = await execute( () => stickerApi.deletePack(packId), '贴纸包已删除', '删除失败', ); if (result !== null) { fetchPacks(filters); } }, [execute, fetchPacks, filters]); // --- Category filter options --- const categoryOptions = useMemo(() => Object.entries(CATEGORY_LABELS).map(([value, label]) => ({ value, label, })), [], ); // --- Render helpers --- const renderCategoryTag = useCallback((category?: string) => { if (!category) return null; const color = CATEGORY_COLORS[category] || '#8c8c8c'; const label = CATEGORY_LABELS[category] || category; return ( {label} ); }, []); const renderPriceTag = useCallback((isFree: boolean) => { if (isFree) { return ( 免费 ); } return ( 付费 ); }, []); // --- Pack card style --- const cardStyle = useMemo(() => ({ borderRadius: 16, overflow: 'hidden' as const, border: `1px solid ${isDark ? '#3A3530' : '#f0e8e0'}`, background: isDark ? '#1f1f1f' : '#ffffff', cursor: 'pointer' as const, transition: 'all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1)', height: '100%', }), [isDark]); const cardHoverShadow = '0 8px 24px rgba(224, 122, 95, 0.15)'; // --- Pack cards --- const packCards = useMemo(() => { if (packs.length === 0 && !loading) { return ( ); } return packs.map((pack) => ( openPackDetail(pack)} onMouseEnter={(e) => { (e.currentTarget as HTMLElement).style.boxShadow = cardHoverShadow; (e.currentTarget as HTMLElement).style.transform = 'translateY(-4px)'; }} onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.boxShadow = 'none'; (e.currentTarget as HTMLElement).style.transform = 'translateY(0)'; }} > {/* Cover image */}
{pack.cover_image_url ? ( {pack.name} ) : ( )} {/* Price badge overlay */}
{renderPriceTag(pack.is_free)}
{/* Pack info */}
{pack.name} {pack.description && ( {pack.description} )} {renderCategoryTag(pack.category)} {pack.sticker_count} handleDelete(pack.id, e ?? undefined)} okText="删除" cancelText="取消" okButtonProps={{ danger: true }} > e.stopPropagation()} >
)); }, [packs, loading, isDark, cardStyle, openPackDetail, cardHoverShadow, renderPriceTag, renderCategoryTag]); return ( } onResetFilters={handleResetFilters} actions={ } > {packCards} {/* Pack detail modal */} {currentPack?.name ?? '贴纸包详情'} {currentPack && renderPriceTag(currentPack.is_free)} } open={modalOpen} onCancel={closePackDetail} footer={null} width={720} destroyOnClose styles={{ body: { background: isDark ? '#141414' : undefined, paddingTop: 20, }, }} > {currentPack && ( <> {/* Pack meta */}
{renderCategoryTag(currentPack.category)} 共 {currentPack.sticker_count} 个贴纸 {currentPack.description && ( {currentPack.description} )}
{/* Sticker grid */} {stickers.length > 0 ? ( {stickers.map((sticker) => (
{ (e.currentTarget as HTMLElement).style.transform = 'scale(1.05)'; }} onMouseLeave={(e) => { (e.currentTarget as HTMLElement).style.transform = 'scale(1)'; }} > {sticker.name} 查看 ), }} style={{ maxWidth: '100%', maxHeight: 120, objectFit: 'contain', }} fallback="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHZpZXdCb3g9IjAgMCA0MCA0MCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHJ4PSI4IiBmaWxsPSIjRjVGNUY1Ii8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiNDQ0MiIGZvbnQtc2l6ZT0iMTQiPuWbvueJh+WKoOi9veWksei0pTwvdGV4dD48L3N2Zz4=" /> {sticker.name}
))}
) : ( !stickersLoading && ( ) )}
)}
{/* Create sticker pack modal */} 新建贴纸包 } open={createOpen} onOk={handleCreate} onCancel={() => { setCreateOpen(false); createForm.resetFields(); }} confirmLoading={creating} okText="创建" cancelText="取消" okButtonProps={{ style: { background: '#E07A5F', borderColor: '#E07A5F', }, }} destroyOnClose width={520} >
); }