'use client'; import { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { UserCircleIcon, PencilIcon, TrashIcon, PlusIcon, EyeIcon, ArrowDownTrayIcon, Cog6ToothIcon, UserIcon, PhotoIcon, KeyIcon, EnvelopeIcon, HeartIcon, ArrowLeftOnRectangleIcon, CloudArrowUpIcon, XMarkIcon, ArrowPathIcon, XCircleIcon } from '@heroicons/react/24/outline'; import { useAuth } from '@/contexts/AuthContext'; import { getMyTextures, getFavoriteTextures, toggleFavorite, getProfiles, createProfile, updateProfile, deleteProfile, setActiveProfile, getUserProfile, updateUserProfile, uploadTexture, type Texture, type Profile } from '@/lib/api'; interface UserProfile { id: number; username: string; email: string; avatar?: string; points: number; role: string; status: number; last_login_at?: string; created_at: string; updated_at: string; } export default function ProfilePage() { const [activeTab, setActiveTab] = useState<'characters' | 'skins' | 'favorites' | 'settings'>('characters'); const [profiles, setProfiles] = useState([]); const [mySkins, setMySkins] = useState([]); const [favoriteSkins, setFavoriteSkins] = useState([]); const [userProfile, setUserProfile] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isUploading, setIsUploading] = useState(false); const [showCreateCharacter, setShowCreateCharacter] = useState(false); const [showUploadSkin, setShowUploadSkin] = useState(false); const [newCharacterName, setNewCharacterName] = useState(''); const [newSkinData, setNewSkinData] = useState({ name: '', description: '', type: 'SKIN' as 'SKIN' | 'CAPE', is_public: false, is_slim: false }); const [selectedFile, setSelectedFile] = useState(null); const [editingProfile, setEditingProfile] = useState(null); const [editProfileName, setEditProfileName] = useState(''); const [uploadProgress, setUploadProgress] = useState(0); const [avatarFile, setAvatarFile] = useState(null); const [isUploadingAvatar, setIsUploadingAvatar] = useState(false); const [avatarUploadProgress, setAvatarUploadProgress] = useState(0); const [error, setError] = useState(null); const { user, isAuthenticated, logout } = useAuth(); // 加载用户数据 useEffect(() => { if (isAuthenticated) { loadUserData(); } }, [isAuthenticated]); const loadUserData = async () => { setIsLoading(true); setError(null); try { // 加载用户信息 const userResponse = await getUserProfile(); if (userResponse.code === 200) { setUserProfile(userResponse.data); } else { throw new Error(userResponse.message || '获取用户信息失败'); } // 加载用户档案 const profilesResponse = await getProfiles(); if (profilesResponse.code === 200) { setProfiles(profilesResponse.data); } else { throw new Error(profilesResponse.message || '获取角色列表失败'); } // 加载用户皮肤 const mySkinsResponse = await getMyTextures({ page: 1, page_size: 50 }); if (mySkinsResponse.code === 200) { setMySkins(mySkinsResponse.data.list || []); } else { throw new Error(mySkinsResponse.message || '获取皮肤列表失败'); } // 加载收藏的皮肤 const favoritesResponse = await getFavoriteTextures({ page: 1, page_size: 50 }); if (favoritesResponse.code === 200) { setFavoriteSkins(favoritesResponse.data.list || []); } else { throw new Error(favoritesResponse.message || '获取收藏列表失败'); } } catch (error) { console.error('加载用户数据失败:', error); setError(error instanceof Error ? error.message : '加载数据失败,请稍后重试'); } finally { setIsLoading(false); } }; const handleCreateCharacter = async () => { if (!newCharacterName.trim()) { alert('请输入角色名称'); return; } try { const response = await createProfile(newCharacterName.trim()); if (response.code === 200) { setProfiles(prev => [...prev, response.data]); setNewCharacterName(''); setShowCreateCharacter(false); alert('角色创建成功!'); } else { throw new Error(response.message || '创建角色失败'); } } catch (error) { console.error('创建角色失败:', error); alert(error instanceof Error ? error.message : '创建角色失败,请稍后重试'); } }; const handleDeleteCharacter = async (uuid: string) => { const character = profiles.find(p => p.uuid === uuid); if (!character) return; if (!confirm(`确定要删除角色 "${character.name}" 吗?此操作不可恢复。`)) return; try { const response = await deleteProfile(uuid); if (response.code === 200) { setProfiles(prev => prev.filter(profile => profile.uuid !== uuid)); alert('角色删除成功!'); } else { throw new Error(response.message || '删除角色失败'); } } catch (error) { console.error('删除角色失败:', error); alert(error instanceof Error ? error.message : '删除角色失败,请稍后重试'); } }; const handleSetActiveCharacter = async (uuid: string) => { try { const response = await setActiveProfile(uuid); if (response.code === 200) { setProfiles(prev => prev.map(profile => ({ ...profile, is_active: profile.uuid === uuid }))); alert('角色切换成功!'); } else { throw new Error(response.message || '设置活跃角色失败'); } } catch (error) { console.error('设置活跃角色失败:', error); alert(error instanceof Error ? error.message : '设置活跃角色失败,请稍后重试'); } }; const handleEditCharacter = async (uuid: string) => { if (!editProfileName.trim()) { alert('请输入角色名称'); return; } try { const response = await updateProfile(uuid, { name: editProfileName.trim() }); if (response.code === 200) { setProfiles(prev => prev.map(profile => profile.uuid === uuid ? response.data : profile )); setEditingProfile(null); setEditProfileName(''); alert('角色编辑成功!'); } else { throw new Error(response.message || '编辑角色失败'); } } catch (error) { console.error('编辑角色失败:', error); alert(error instanceof Error ? error.message : '编辑角色失败,请稍后重试'); } }; const handleDeleteSkin = async (skinId: number) => { if (!confirm('确定要删除这个皮肤吗?')) return; setMySkins(prev => prev.filter(skin => skin.id !== skinId)); }; const handleToggleSkinVisibility = async (skinId: number) => { try { const skin = mySkins.find(s => s.id === skinId); if (!skin) return; // TODO: 添加更新皮肤API调用 setMySkins(prev => prev.map(skin => skin.id === skinId ? { ...skin, is_public: !skin.is_public } : skin )); } catch (error) { console.error('切换皮肤可见性失败:', error); } }; const handleToggleFavorite = async (skinId: number) => { try { const response = await toggleFavorite(skinId); if (response.code === 200) { // 重新加载收藏数据 const favoritesResponse = await getFavoriteTextures({ page: 1, page_size: 50 }); if (favoritesResponse.code === 200) { setFavoriteSkins(favoritesResponse.data.list || []); } } } catch (error) { console.error('切换收藏状态失败:', error); } }; const handleFileSelect = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { setSelectedFile(file); } }; const handleUploadSkin = async () => { if (!selectedFile || !newSkinData.name.trim()) { alert('请选择皮肤文件并输入皮肤名称'); return; } setIsUploading(true); setUploadProgress(0); try { // 使用直接上传接口 const progressInterval = setInterval(() => { setUploadProgress(prev => Math.min(prev + 10, 80)); }, 200); const response = await uploadTexture(selectedFile, { name: newSkinData.name.trim(), description: newSkinData.description.trim(), type: newSkinData.type, is_public: newSkinData.is_public, is_slim: newSkinData.is_slim }); clearInterval(progressInterval); setUploadProgress(100); if (response.code === 200) { // 重新加载皮肤数据 const mySkinsResponse = await getMyTextures({ page: 1, page_size: 50 }); if (mySkinsResponse.code === 200) { setMySkins(mySkinsResponse.data.list || []); } // 重置表单 setSelectedFile(null); setNewSkinData({ name: '', description: '', type: 'SKIN', is_public: false, is_slim: false }); setShowUploadSkin(false); alert('皮肤上传成功!'); } else { throw new Error(response.message || '上传皮肤失败'); } } catch (error) { console.error('上传皮肤失败:', error); alert(error instanceof Error ? error.message : '上传皮肤失败,请稍后重试'); } finally { setIsUploading(false); setUploadProgress(0); } }; const handleAvatarFileSelect = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { // 验证文件类型和大小 if (!file.type.startsWith('image/')) { alert('请选择图片文件'); return; } if (file.size > 2 * 1024 * 1024) { alert('文件大小不能超过2MB'); return; } setAvatarFile(file); } }; const handleUploadAvatar = async () => { if (!avatarFile) return; setIsUploadingAvatar(true); setAvatarUploadProgress(0); try { // 获取上传URL const uploadUrlResponse = await generateAvatarUploadUrl(avatarFile.name); if (uploadUrlResponse.code !== 200) { throw new Error(uploadUrlResponse.message || '获取上传URL失败'); } const { post_url, form_data, avatar_url } = uploadUrlResponse.data; // 模拟上传进度 const progressInterval = setInterval(() => { setAvatarUploadProgress(prev => Math.min(prev + 20, 80)); }, 200); // 上传文件到预签名URL const formData = new FormData(); Object.entries(form_data).forEach(([key, value]) => { formData.append(key, value as string); }); formData.append('file', avatarFile); const uploadResponse = await fetch(post_url, { method: 'POST', body: formData, }); if (!uploadResponse.ok) { throw new Error('文件上传失败'); } clearInterval(progressInterval); setAvatarUploadProgress(100); // 更新用户头像URL const updateResponse = await updateAvatarUrl(avatar_url); if (updateResponse.code === 200) { setUserProfile(prev => prev ? { ...prev, avatar: avatar_url } : null); alert('头像上传成功!'); } else { throw new Error(updateResponse.message || '更新头像URL失败'); } } catch (error) { console.error('头像上传失败:', error); alert(error instanceof Error ? error.message : '头像上传失败,请稍后重试'); } finally { setIsUploadingAvatar(false); setAvatarUploadProgress(0); setAvatarFile(null); } }; const handleDeleteAvatar = async () => { if (!confirm('确定要删除头像吗?')) return; try { const response = await updateAvatarUrl(''); if (response.code === 200) { setUserProfile(prev => prev ? { ...prev, avatar: undefined } : null); alert('头像删除成功!'); } else { throw new Error(response.message || '删除头像失败'); } } catch (error) { console.error('删除头像失败:', error); alert(error instanceof Error ? error.message : '删除头像失败,请稍后重试'); } }; const sidebarVariants = { hidden: { x: -100, opacity: 0 }, visible: { x: 0, opacity: 1, transition: { duration: 0.5, ease: "easeOut" as const } } }; const contentVariants = { hidden: { x: 100, opacity: 0 }, visible: { x: 0, opacity: 1, transition: { duration: 0.5, ease: "easeOut" as const, delay: 0.1 } } }; if (!isAuthenticated) { return (

请先登录

登录后即可查看个人资料

); } if (isLoading) { return (

加载中...

正在加载您的个人资料

); } if (error) { return (

加载失败

{error}

重新加载
); } return (
{/* Animated Background */}
{/* Left Sidebar - 完全固定的 */} {/* User Profile Card */}
{userProfile?.avatar ? ( {userProfile.username} ) : (
)}

{userProfile?.username}

{userProfile?.email}

{mySkins.length}
皮肤
{favoriteSkins.length}
收藏
{userProfile?.points || 0}
积分
{/* Navigation Menu - 固定的 */} {/* Logout Button - 始终在底部可见 */} 退出登录
{/* Right Content Area - 考虑左侧fixed侧栏的空间 */} {/* Characters Tab */} {activeTab === 'characters' && (

角色管理

setShowCreateCharacter(true)} className="bg-gradient-to-r from-orange-500 to-amber-500 text-white px-4 py-2 rounded-xl flex items-center space-x-2 shadow-lg hover:shadow-xl transition-all duration-200" whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} > 创建角色
{/* Create Character Modal */} {showCreateCharacter && (

创建新角色

setNewCharacterName(e.target.value)} className="w-full px-4 py-3 bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-2 focus:ring-orange-500 focus:border-transparent" placeholder="请输入角色名称" />
)}
{profiles.length === 0 ? (

暂无角色

创建你的第一个Minecraft角色吧!

) : (
{profiles.map((profile) => (
{editingProfile === profile.uuid ? ( setEditProfileName(e.target.value)} className="text-lg font-semibold bg-transparent border-b border-orange-500 focus:outline-none text-gray-900 dark:text-white" onBlur={() => handleEditCharacter(profile.uuid)} onKeyPress={(e) => e.key === 'Enter' && handleEditCharacter(profile.uuid)} autoFocus /> ) : (

{profile.name}

)} {profile.is_active && ( 当前使用 )}
{!profile.is_active && ( )}
))}
)}
)} {/* Skins Tab */} {activeTab === 'skins' && (

我的皮肤

setShowUploadSkin(true)} className="bg-gradient-to-r from-orange-500 to-amber-500 text-white px-4 py-2 rounded-xl flex items-center space-x-2 shadow-lg hover:shadow-xl transition-all duration-200" whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }} > 上传皮肤
{/* Upload Skin Modal */} {showUploadSkin && (

上传皮肤

点击选择文件或拖拽到此处

{selectedFile && (

已选择: {selectedFile.name}

)}
setNewSkinData(prev => ({ ...prev, name: e.target.value }))} className="w-full px-4 py-3 bg-gray-50 dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-xl focus:ring-2 focus:ring-orange-500 focus:border-transparent" placeholder="请输入皮肤名称" />