diff --git a/dashboard/src/routes/resource/emoji.tsx b/dashboard/src/routes/resource/emoji.tsx deleted file mode 100644 index 4a8e6ee3..00000000 --- a/dashboard/src/routes/resource/emoji.tsx +++ /dev/null @@ -1,1688 +0,0 @@ -import { useState, useEffect, useCallback, useMemo } from 'react' -import { - Filter, - RefreshCw, - Trash2, - Edit, - Info, - ChevronLeft, - ChevronRight, - ChevronsLeft, - ChevronsRight, - CheckCircle2, - Ban, - Upload, - ArrowLeft, - Check, - X, - ImageIcon, -} from 'lucide-react' -import Uppy from '@uppy/core' -import Dashboard from '@uppy/react/dashboard' -import '@uppy/core/css/style.min.css' -import '@uppy/dashboard/css/style.min.css' -import '@/styles/uppy-custom.css' -import { fetchWithAuth } from '@/lib/fetch-with-auth' - -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { EmojiThumbnail } from '@/components/emoji-thumbnail' -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from '@/components/ui/card' -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog' -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from '@/components/ui/select' -import { Badge } from '@/components/ui/badge' -import { Label } from '@/components/ui/label' -import { Textarea } from '@/components/ui/textarea' -import { Checkbox } from '@/components/ui/checkbox' -import { ScrollArea } from '@/components/ui/scroll-area' -import { - AlertDialog, - AlertDialogAction, - AlertDialogCancel, - AlertDialogContent, - AlertDialogDescription, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, -} from '@/components/ui/alert-dialog' - -import { Markdown } from '@/components/ui/markdown' -import { useToast } from '@/hooks/use-toast' -import type { Emoji, EmojiStats } from '@/types/emoji' -import { - getEmojiList, - getEmojiDetail, - getEmojiStats, - updateEmoji, - deleteEmoji, - registerEmoji, - banEmoji, - getEmojiThumbnailUrl, - getEmojiOriginalUrl, - batchDeleteEmojis, - getEmojiUploadUrl, -} from '@/lib/emoji-api' - -export function EmojiManagementPage() { - const [emojiList, setEmojiList] = useState([]) - const [stats, setStats] = useState(null) - const [loading, setLoading] = useState(false) - const [page, setPage] = useState(1) - const [total, setTotal] = useState(0) - const [pageSize, setPageSize] = useState(20) - const [registeredFilter, setRegisteredFilter] = useState('all') - const [bannedFilter, setBannedFilter] = useState('all') - const [formatFilter, setFormatFilter] = useState('all') - const [sortBy, setSortBy] = useState('usage_count') - const [sortOrder, setSortOrder] = useState<'desc' | 'asc'>('desc') - const [selectedEmoji, setSelectedEmoji] = useState(null) - const [detailDialogOpen, setDetailDialogOpen] = useState(false) - const [editDialogOpen, setEditDialogOpen] = useState(false) - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) - const [selectedIds, setSelectedIds] = useState>(new Set()) - const [batchDeleteDialogOpen, setBatchDeleteDialogOpen] = useState(false) - const [jumpToPage, setJumpToPage] = useState('') - const [cardSize, setCardSize] = useState<'small' | 'medium' | 'large'>('medium') - const [uploadDialogOpen, setUploadDialogOpen] = useState(false) - - const { toast } = useToast() - - // 加载表情包列表 - const loadEmojiList = useCallback(async () => { - try { - setLoading(true) - const response = await getEmojiList({ - page, - page_size: pageSize, - is_registered: registeredFilter === 'all' ? undefined : registeredFilter === 'registered', - is_banned: bannedFilter === 'all' ? undefined : bannedFilter === 'banned', - format: formatFilter === 'all' ? undefined : formatFilter, - sort_by: sortBy, - sort_order: sortOrder, - }) - setEmojiList(response.data) - setTotal(response.total) - } catch (error) { - const message = error instanceof Error ? error.message : '加载表情包列表失败' - toast({ - title: '错误', - description: message, - variant: 'destructive', - }) - } finally { - setLoading(false) - } - }, [page, pageSize, registeredFilter, bannedFilter, formatFilter, sortBy, sortOrder, toast]) - - // 加载统计数据 - const loadStats = async () => { - try { - const response = await getEmojiStats() - setStats(response.data) - } catch (error) { - console.error('加载统计数据失败:', error) - } - } - - useEffect(() => { - loadEmojiList() - }, [loadEmojiList]) - - useEffect(() => { - loadStats() - }, []) - - // 查看详情 - const handleViewDetail = async (emoji: Emoji) => { - try { - const response = await getEmojiDetail(emoji.id) - setSelectedEmoji(response.data) - setDetailDialogOpen(true) - } catch (error) { - const message = error instanceof Error ? error.message : '加载详情失败' - toast({ - title: '错误', - description: message, - variant: 'destructive', - }) - } - } - - // 编辑表情包 - const handleEdit = (emoji: Emoji) => { - setSelectedEmoji(emoji) - setEditDialogOpen(true) - } - - // 删除表情包 - const handleDelete = (emoji: Emoji) => { - setSelectedEmoji(emoji) - setDeleteDialogOpen(true) - } - - // 确认删除 - const confirmDelete = async () => { - if (!selectedEmoji) return - - try { - await deleteEmoji(selectedEmoji.id) - toast({ - title: '成功', - description: '表情包已删除', - }) - setDeleteDialogOpen(false) - setSelectedEmoji(null) - loadEmojiList() - loadStats() - } catch (error) { - const message = error instanceof Error ? error.message : '删除失败' - toast({ - title: '错误', - description: message, - variant: 'destructive', - }) - } - } - - // 快速注册 - const handleRegister = async (emoji: Emoji) => { - try { - await registerEmoji(emoji.id) - toast({ - title: '成功', - description: '表情包已注册', - }) - loadEmojiList() - loadStats() - } catch (error) { - const message = error instanceof Error ? error.message : '注册失败' - toast({ - title: '错误', - description: message, - variant: 'destructive', - }) - } - } - - // 快速封禁 - const handleBan = async (emoji: Emoji) => { - try { - await banEmoji(emoji.id) - toast({ - title: '成功', - description: '表情包已封禁', - }) - loadEmojiList() - loadStats() - } catch (error) { - const message = error instanceof Error ? error.message : '封禁失败' - toast({ - title: '错误', - description: message, - variant: 'destructive', - }) - } - } - - // 切换选择 - const toggleSelect = (id: number) => { - const newSelected = new Set(selectedIds) - if (newSelected.has(id)) { - newSelected.delete(id) - } else { - newSelected.add(id) - } - setSelectedIds(newSelected) - } - - // 批量删除 - const handleBatchDelete = async () => { - try { - const result = await batchDeleteEmojis(Array.from(selectedIds)) - toast({ - title: '批量删除完成', - description: result.message, - }) - setSelectedIds(new Set()) - setBatchDeleteDialogOpen(false) - loadEmojiList() - loadStats() - } catch (error) { - toast({ - title: '批量删除失败', - description: error instanceof Error ? error.message : '批量删除失败', - variant: 'destructive', - }) - } - } - - // 页面跳转 - const handleJumpToPage = () => { - const targetPage = parseInt(jumpToPage) - const totalPages = Math.ceil(total / pageSize) - if (targetPage >= 1 && targetPage <= totalPages) { - setPage(targetPage) - setJumpToPage('') - } else { - toast({ - title: '无效的页码', - description: `请输入1-${totalPages}之间的页码`, - variant: 'destructive', - }) - } - } - - // 获取格式选项 - const formatOptions = stats?.formats ? Object.keys(stats.formats) : [] - - return ( -
- {/* 页面标题 */} -
-
-

表情包管理

-

- 管理麦麦的表情包资源 -

-
- -
- - -
- - {/* 统计卡片 */} - {stats && ( -
- - - 总数 - {stats.total} - - - - - 已注册 - - {stats.registered} - - - - - - 已封禁 - - {stats.banned} - - - - - - 未注册 - - {stats.unregistered} - - - -
- )} - - {/* 筛选和排序 */} - - - - - 筛选和排序 - - - -
-
- - -
- -
- - -
- -
- - -
- -
- - -
-
- -
-
- {selectedIds.size > 0 && ( - 已选择 {selectedIds.size} 个表情包 - )} - {/* 卡片尺寸切换 */} -
- - -
-
-
- - - {selectedIds.size > 0 && ( - <> - - - - )} -
-
- -
- -
-
-
- - {/* 表情包卡片列表 */} - - - 表情包列表 - - 共 {total} 个表情包,当前第 {page} 页 - - - - {/* 卡片网格视图 */} - {emojiList.length === 0 ? ( -
- 暂无数据 -
- ) : ( -
- {emojiList.map((emoji) => ( -
toggleSelect(emoji.id)} - > - {/* 选中指示器 */} -
-
- {selectedIds.has(emoji.id) && } -
-
- - {/* 状态标签 */} -
- {emoji.is_registered && ( - - 已注册 - - )} - {emoji.is_banned && ( - - 已封禁 - - )} -
- - {/* 图片 */} -
- -
- - {/* 底部信息和操作 */} -
- {/* 使用次数和格式 */} -
- - {emoji.format.toUpperCase()} - - {emoji.usage_count}次 -
- - {/* 操作按钮 - 悬停时显示 */} -
- - - {!emoji.is_registered && ( - - )} - {!emoji.is_banned && ( - - )} - -
-
-
- ))} -
- )} - - {/* 分页 */} - {/* 分页 - 增强版 */} - {total > 0 && ( -
-
- 显示 {(page - 1) * pageSize + 1} 到{' '} - {Math.min(page * pageSize, total)} 条,共 {total} 条 -
-
- {/* 首页 */} - - - {/* 上一页 */} - - - {/* 页码跳转 */} -
- setJumpToPage(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleJumpToPage()} - placeholder={page.toString()} - className="w-16 h-8 text-center" - min={1} - max={Math.ceil(total / pageSize)} - /> - -
- - {/* 下一页 */} - - - {/* 末页 */} - -
-
- )} -
-
- - {/* 详情对话框 */} - - - {/* 编辑对话框 */} - { - loadEmojiList() - loadStats() - }} - /> - - {/* 上传对话框 */} - { - loadEmojiList() - loadStats() - }} - /> - -
-
- - {/* 批量删除确认对话框 */} - - - - 确认批量删除 - - 你确定要删除选中的 {selectedIds.size} 个表情包吗?此操作不可撤销。 - - - - 取消 - - 确认删除 - - - - - - {/* 删除确认对话框 */} - - - - 确认删除 - - 确定要删除这个表情包吗?此操作无法撤销。 - - - - - - - - -
- ) -} - -// 详情对话框组件 -function EmojiDetailDialog({ - emoji, - open, - onOpenChange, -}: { - emoji: Emoji | null - open: boolean - onOpenChange: (open: boolean) => void -}) { - if (!emoji) return null - - const formatTime = (timestamp: number | null) => { - if (!timestamp) return '-' - return new Date(timestamp * 1000).toLocaleString('zh-CN') - } - - return ( - - - - 表情包详情 - - -
- {/* 表情包预览图 - 使用原图 */} -
-
- {emoji.description { - const target = e.target as HTMLImageElement - target.style.display = 'none' - const parent = target.parentElement - if (parent) { - parent.innerHTML = '' - } - }} - /> -
-
- -
-
- -
{emoji.id}
-
-
- -
- {emoji.format.toUpperCase()} -
-
-
- -
- -
- {emoji.full_path} -
-
- -
- -
- {emoji.emoji_hash} -
-
- -
- - {emoji.description ? ( -
- {emoji.description} -
- ) : ( -
-
- )} -
- -
- -
- {emoji.emotion ? ( - {emoji.emotion} - ) : ( - - - )} -
-
- -
-
- -
- {emoji.is_registered && ( - - 已注册 - - )} - {emoji.is_banned && ( - 已封禁 - )} - {!emoji.is_registered && !emoji.is_banned && ( - 未注册 - )} -
-
-
- -
{emoji.usage_count}
-
-
- -
-
- -
{formatTime(emoji.record_time)}
-
-
- -
{formatTime(emoji.register_time)}
-
-
- -
- -
{formatTime(emoji.last_used_time)}
-
-
-
-
-
- ) -} - -// 编辑对话框组件 -function EmojiEditDialog({ - emoji, - open, - onOpenChange, - onSuccess, -}: { - emoji: Emoji | null - open: boolean - onOpenChange: (open: boolean) => void - onSuccess: () => void -}) { - const [emotionInput, setEmotionInput] = useState('') - const [isRegistered, setIsRegistered] = useState(false) - const [isBanned, setIsBanned] = useState(false) - const [saving, setSaving] = useState(false) - - const { toast } = useToast() - - useEffect(() => { - if (emoji) { - setEmotionInput(emoji.emotion || '') - setIsRegistered(emoji.is_registered) - setIsBanned(emoji.is_banned) - } - }, [emoji]) - - const handleSave = async () => { - if (!emoji) return - - try { - setSaving(true) - // 将输入的标签字符串标准化为逗号分隔格式 - const emotionString = emotionInput - .split(/[,,]/) - .map((s) => s.trim()) - .filter(Boolean) - .join(',') - - await updateEmoji(emoji.id, { - emotion: emotionString || undefined, - is_registered: isRegistered, - is_banned: isBanned, - }) - - toast({ - title: '成功', - description: '表情包信息已更新', - }) - onOpenChange(false) - onSuccess() - } catch (error) { - const message = error instanceof Error ? error.message : '保存失败' - toast({ - title: '错误', - description: message, - variant: 'destructive', - }) - } finally { - setSaving(false) - } - } - - if (!emoji) return null - - return ( - - - - 编辑表情包 - 修改表情包的情绪和状态信息 - -
-
- -