diff --git a/src/app/globals.css b/src/app/globals.css index 12290ab..580ead7 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -8,6 +8,11 @@ --background: #ffffff; --foreground: #171717; --navbar-height: 64px; /* 与pt-16对应 */ + --primary-orange: #f97316; + --primary-orange-dark: #ea580c; + --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-normal: 300ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 500ms cubic-bezier(0.4, 0, 0.2, 1); } @media (prefers-color-scheme: dark) { @@ -21,6 +26,7 @@ body { color: var(--foreground); background: var(--background); font-family: 'Inter', Arial, Helvetica, sans-serif; + scroll-behavior: smooth; } /* Custom utility classes */ @@ -28,34 +34,65 @@ body { text-wrap: balance; } -/* Custom component classes */ +/* Enhanced Custom component classes with micro-interactions */ .btn-carrot { - background-color: #f97316; + background-color: var(--primary-orange); color: white; font-weight: 500; padding: 0.5rem 1rem; border-radius: 0.5rem; - transition: background-color 0.2s; - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + transition: all var(--transition-normal); + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); + position: relative; + overflow: hidden; } .btn-carrot:hover { - background-color: #ea580c; - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); + background-color: var(--primary-orange-dark); + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + transform: translateY(-2px); +} + +.btn-carrot:active { + transform: translateY(0); + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); +} + +.btn-carrot::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transition: left var(--transition-slow); +} + +.btn-carrot:hover::before { + left: 100%; } .btn-carrot-outline { - border: 2px solid #f97316; - color: #f97316; + border: 2px solid var(--primary-orange); + color: var(--primary-orange); font-weight: 500; padding: 0.5rem 1rem; border-radius: 0.5rem; - transition: all 0.2s; + transition: all var(--transition-normal); + position: relative; + overflow: hidden; } .btn-carrot-outline:hover { - background-color: #f97316; + background-color: var(--primary-orange); color: white; + transform: translateY(-2px); + box-shadow: 0 10px 15px -3px rgba(249, 115, 22, 0.3); +} + +.btn-carrot-outline:active { + transform: translateY(0); } .card-minecraft { @@ -63,11 +100,31 @@ body { border: 2px solid #fed7aa; border-radius: 0.5rem; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); - transition: all 0.2s; + transition: all var(--transition-normal); + position: relative; + overflow: hidden; } .card-minecraft:hover { box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); + transform: translateY(-4px); + border-color: var(--primary-orange); +} + +.card-minecraft::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(135deg, rgba(249, 115, 22, 0.1) 0%, transparent 50%); + opacity: 0; + transition: opacity var(--transition-normal); +} + +.card-minecraft:hover::after { + opacity: 1; } @media (prefers-color-scheme: dark) { @@ -75,6 +132,10 @@ body { background-color: #1f2937; border-color: #c2410c; } + + .card-minecraft:hover { + border-color: var(--primary-orange); + } } .text-gradient { @@ -82,10 +143,18 @@ body { background-clip: text; -webkit-background-clip: text; color: transparent; + transition: all var(--transition-normal); +} + +.text-gradient:hover { + background: linear-gradient(to right, #f97316, #ea580c); + background-clip: text; + -webkit-background-clip: text; } .bg-gradient-carrot { background: linear-gradient(to bottom right, #fb923c, #f97316, #ea580c); + transition: all var(--transition-normal); } /* 现代布局解决方案 */ @@ -104,4 +173,277 @@ body { .min-h-screen-nav { min-height: calc(100vh - var(--navbar-height)); } + + /* 增强的过渡效果 */ + .transition-all-enhanced { + transition: all var(--transition-normal); + } + + .transition-colors-enhanced { + transition: color var(--transition-normal), background-color var(--transition-normal), border-color var(--transition-normal); + } + + .transition-transform-enhanced { + transition: transform var(--transition-normal); + } + + /* 微交互效果 */ + .micro-interaction { + transition: all var(--transition-fast); + } + + .micro-interaction:hover { + transform: scale(1.02); + } + + .micro-interaction:active { + transform: scale(0.98); + } + + /* 加载动画 */ + .animate-pulse-slow { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + } + + .animate-pulse-fast { + animation: pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite; + } + + /* 弹跳动画 */ + .animate-bounce-slow { + animation: bounce 2s infinite; + } + + .animate-bounce-fast { + animation: bounce 1s infinite; + } + + /* 旋转动画 */ + .animate-spin-slow { + animation: spin 3s linear infinite; + } + + .animate-spin-fast { + animation: spin 1s linear infinite; + } + + /* 渐变动画 */ + .animate-gradient { + background-size: 200% 200%; + animation: gradient 3s ease infinite; + } + + /* 阴影动画 */ + .shadow-animated { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); + transition: box-shadow var(--transition-normal); + } + + .shadow-animated:hover { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); + } + + /* 模糊动画 */ + .backdrop-blur-animated { + backdrop-filter: blur(8px); + transition: backdrop-filter var(--transition-normal); + } + + .backdrop-blur-animated:hover { + backdrop-filter: blur(16px); + } +} + +/* 自定义关键帧动画 */ +@keyframes gradient { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} + +@keyframes float { + 0%, 100% { + transform: translateY(0px); + } + 50% { + transform: translateY(-10px); + } +} + +@keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } +} + +@keyframes slideInUp { + from { + transform: translateY(30px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes slideInDown { + from { + transform: translateY(-30px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes slideInLeft { + from { + transform: translateX(-30px); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slideInRight { + from { + transform: translateX(30px); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes scaleIn { + from { + transform: scale(0.9); + opacity: 0; + } + to { + transform: scale(1); + opacity: 1; + } +} + +@keyframes scaleOut { + from { + transform: scale(1); + opacity: 1; + } + to { + transform: scale(0.9); + opacity: 0; + } +} + +/* 动画工具类 */ +.animate-slide-in-up { + animation: slideInUp 0.3s ease-out; +} + +.animate-slide-in-down { + animation: slideInDown 0.3s ease-out; +} + +.animate-slide-in-left { + animation: slideInLeft 0.3s ease-out; +} + +.animate-slide-in-right { + animation: slideInRight 0.3s ease-out; +} + +.animate-scale-in { + animation: scaleIn 0.2s ease-out; +} + +.animate-scale-out { + animation: scaleOut 0.2s ease-out; +} + +/* 加载状态样式 */ +.loading-shimmer { + background: linear-gradient( + 90deg, + #f0f0f0 0%, + #e0e0e0 50%, + #f0f0f0 100% + ); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; +} + +.dark .loading-shimmer { + background: linear-gradient( + 90deg, + #374151 0%, + #4b5563 50%, + #374151 100% + ); + background-size: 200% 100%; + animation: shimmer 1.5s infinite; +} + +/* 焦点样式 */ +.focus-visible-enhanced { + outline: 2px solid var(--primary-orange); + outline-offset: 2px; +} + +/* 滚动条样式 */ +.custom-scrollbar::-webkit-scrollbar { + width: 6px; +} + +.custom-scrollbar::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.1); + border-radius: 3px; +} + +.custom-scrollbar::-webkit-scrollbar-thumb { + background: var(--primary-orange); + border-radius: 3px; +} + +.custom-scrollbar::-webkit-scrollbar-thumb:hover { + background: var(--primary-orange-dark); +} + +/* 响应式动效 */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +/* 触摸设备优化 */ +@media (hover: none) and (pointer: coarse) { + .btn-carrot:hover, + .btn-carrot-outline:hover, + .card-minecraft:hover { + transform: none; + } + + .btn-carrot:active, + .btn-carrot-outline:active, + .card-minecraft:active { + transform: scale(0.98); + } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 41af848..8096f60 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,8 +4,10 @@ import "./globals.css"; import Navbar from "@/components/Navbar"; import { AuthProvider } from "@/contexts/AuthContext"; import { MainContent } from "@/components/MainContent"; +import { MessageNotificationContainer } from "@/components/MessageNotification"; import { ErrorNotificationContainer } from "@/components/ErrorNotification"; import ScrollToTop from "@/components/ScrollToTop"; +import PageTransition from "@/components/PageTransition"; const inter = Inter({ subsets: ["latin"], @@ -35,8 +37,11 @@ export default function RootLayout({ - {children} + + {children} + + diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index fff1627..eb8a890 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -1,108 +1,7 @@ 'use client'; -import Link from 'next/link'; -import { motion } from 'framer-motion'; -import { HomeIcon, ArrowLeftIcon } from '@heroicons/react/24/outline'; +import { NotFoundPage } from '@/components/ErrorPage'; export default function NotFound() { - return ( -
- - {/* 404 数字 */} - -

- 404 -

-
- - - {/* 错误信息 */} - -

- 页面不见了 -

-

- 抱歉,我们找不到您要访问的页面。它可能已被移动、删除,或者您输入的链接不正确。 -

-
- - {/* Minecraft 风格的装饰 */} - -
-
- ? -
-
- ! -
-
-
- - {/* 操作按钮 */} - - - - 返回主页 - - - - - - {/* 额外的帮助信息 */} - -

如果问题持续存在,请 - - 联系我们 - - 的支持团队 -

-
- - - {/* 背景装饰 */} -
-
-
-
-
- ); + return ; } diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx index 5db2902..a03b9fb 100644 --- a/src/app/profile/page.tsx +++ b/src/app/profile/page.tsx @@ -2,27 +2,26 @@ import { useState, useEffect } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; -import { - UserCircleIcon, - PencilIcon, - TrashIcon, - PlusIcon, - EyeIcon, - ArrowDownTrayIcon, +import { useAuth } from '@/contexts/AuthContext'; +import { + UserCircleIcon, Cog6ToothIcon, + XCircleIcon, + ArrowPathIcon, + PlusIcon, + XMarkIcon, UserIcon, PhotoIcon, + HeartIcon, + TrashIcon, + PencilIcon, KeyIcon, EnvelopeIcon, - - HeartIcon, - ArrowLeftOnRectangleIcon, CloudArrowUpIcon, - XMarkIcon, - ArrowPathIcon, - XCircleIcon + EyeIcon, + ArrowDownTrayIcon, + ArrowLeftOnRectangleIcon } from '@heroicons/react/24/outline'; -import { useAuth } from '@/contexts/AuthContext'; import { getMyTextures, getFavoriteTextures, @@ -42,7 +41,16 @@ import { type Texture, type Profile } from '@/lib/api'; -import SkinViewer from '@/components/SkinViewer'; +import { messageManager } from '@/components/MessageNotification'; + +// 导入新的组件 +import UserProfileCard from '@/components/profile/UserProfileCard'; +import ProfileSidebar from '@/components/profile/ProfileSidebar'; +import MySkinsTab from '@/components/profile/MySkinsTab'; +import FavoritesTab from '@/components/profile/FavoritesTab'; +import UploadSkinModal from '@/components/profile/UploadSkinModal'; +import SkinViewer from '@/components/SkinViewer'; // Added SkinViewer import +import CharacterCard from '@/components/profile/CharacterCard'; // Added CharacterCard import interface UserProfile { id: number; @@ -169,101 +177,6 @@ export default function ProfilePage() { } }; - 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)); - setProfileSkins(prev => { - const newSkins = { ...prev }; - delete newSkins[uuid]; - return newSkins; - }); - 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); @@ -278,6 +191,12 @@ export default function ProfilePage() { } }; + const handleDeleteSkin = async (skinId: number) => { + if (!confirm('确定要删除这个皮肤吗?')) return; + + setMySkins(prev => prev.filter(skin => skin.id !== skinId)); + }; + const handleToggleFavorite = async (skinId: number) => { try { const response = await toggleFavorite(skinId); @@ -300,9 +219,9 @@ export default function ProfilePage() { } }; - const handleUploadSkin = async () => { - if (!selectedFile || !newSkinData.name.trim()) { - alert('请选择皮肤文件并输入皮肤名称'); + const handleUploadSkin = async (file: File, data: { name: string; description: string; type: 'SKIN' | 'CAPE'; is_public: boolean; is_slim: boolean }) => { + if (!file || !data.name.trim()) { + messageManager.warning('请选择皮肤文件并输入皮肤名称', { duration: 3000 }); return; } @@ -315,12 +234,12 @@ export default function ProfilePage() { 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 + const response = await uploadTexture(file, { + name: data.name.trim(), + description: data.description.trim(), + type: data.type, + is_public: data.is_public, + is_slim: data.is_slim }); clearInterval(progressInterval); @@ -343,14 +262,14 @@ export default function ProfilePage() { is_slim: false }); setShowUploadSkin(false); - alert('皮肤上传成功!'); + messageManager.success('皮肤上传成功!', { duration: 3000 }); } else { throw new Error(response.message || '上传皮肤失败'); } } catch (error) { console.error('上传皮肤失败:', error); - alert(error instanceof Error ? error.message : '上传皮肤失败,请稍后重试'); + messageManager.error(error instanceof Error ? error.message : '上传皮肤失败,请稍后重试', { duration: 3000 }); } finally { setIsUploading(false); setUploadProgress(0); @@ -359,18 +278,20 @@ export default function ProfilePage() { 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); + if (!file) return; + + // 检查文件类型 + if (!file.type.startsWith('image/')) { + messageManager.warning('请选择图片文件', { duration: 3000 }); + return; } + + // 检查文件大小 (2MB) + if (file.size > 2 * 1024 * 1024) { + messageManager.warning('文件大小不能超过2MB', { duration: 3000 }); + return; + } + setAvatarFile(file); }; const handleUploadAvatar = async () => { @@ -413,17 +334,17 @@ export default function ProfilePage() { setAvatarUploadProgress(100); // 更新用户头像URL - const updateResponse = await updateAvatarUrl(avatar_url); - if (updateResponse.code === 200) { + const response = await updateAvatarUrl(avatar_url); + if (response.code === 200) { setUserProfile(prev => prev ? { ...prev, avatar: avatar_url } : null); - alert('头像上传成功!'); + messageManager.success('头像上传成功!', { duration: 3000 }); } else { - throw new Error(updateResponse.message || '更新头像URL失败'); + throw new Error(response.message || '更新头像URL失败'); } } catch (error) { console.error('头像上传失败:', error); - alert(error instanceof Error ? error.message : '头像上传失败,请稍后重试'); + messageManager.error(error instanceof Error ? error.message : '头像上传失败,请稍后重试', { duration: 3000 }); } finally { setIsUploadingAvatar(false); setAvatarUploadProgress(0); @@ -438,13 +359,15 @@ export default function ProfilePage() { const response = await updateUserProfile({ avatar: '' }); if (response.code === 200) { setUserProfile(response.data); - alert('头像删除成功!'); + messageManager.success('头像删除成功!', { duration: 3000 }); } else { throw new Error(response.message || '删除头像失败'); } } catch (error) { console.error('删除头像失败:', error); - alert(error instanceof Error ? error.message : '删除头像失败,请稍后重试'); + messageManager.error(error instanceof Error ? error.message : '删除头像失败,请稍后重试', { duration: 3000 }); + } finally { + setAvatarFile(null); } }; @@ -456,85 +379,607 @@ export default function ProfilePage() { try { const response = await resetYggdrasilPassword(); if (response.code === 200) { - setYggdrasilPassword(response.data.password); - setShowYggdrasilPassword(true); - alert('Yggdrasil密码重置成功!请妥善保管新密码。'); + messageManager.success('Yggdrasil密码重置成功!请妥善保管新密码。', { duration: 5000 }); } else { throw new Error(response.message || '重置Yggdrasil密码失败'); } } catch (error) { console.error('重置Yggdrasil密码失败:', error); - alert(error instanceof Error ? error.message : '重置Yggdrasil密码失败,请稍后重试'); + messageManager.error(error instanceof Error ? error.message : '重置Yggdrasil密码失败,请稍后重试', { duration: 3000 }); } finally { setIsResettingYggdrasilPassword(false); } }; - const handleAssignSkin = async (profileUuid: string, skinId: number) => { + const handleCreateCharacter = async () => { + if (!newCharacterName.trim()) { + messageManager.warning('请输入角色名称', { duration: 3000 }); + return; + } + try { - const response = await updateProfile(profileUuid, { skin_id: skinId }); + const response = await createProfile(newCharacterName.trim()); if (response.code === 200) { - // 更新角色数据 - setProfiles(prev => prev.map(profile => - profile.uuid === profileUuid ? response.data : profile - )); - - // 更新皮肤显示 - const skinResponse = await getTexture(skinId); - if (skinResponse.code === 200 && skinResponse.data) { - setProfileSkins(prev => ({ - ...prev, - [profileUuid]: { - url: skinResponse.data.url, - isSlim: skinResponse.data.is_slim - } - })); - } - - setShowSkinSelector(null); - alert('皮肤配置成功!'); + setProfiles(prev => [...prev, response.data]); + setNewCharacterName(''); + messageManager.success('角色创建成功!', { duration: 3000 }); } else { - throw new Error(response.message || '配置皮肤失败'); + throw new Error(response.message || '创建角色失败'); } } catch (error) { - console.error('配置皮肤失败:', error); - alert(error instanceof Error ? error.message : '配置皮肤失败,请稍后重试'); + console.error('创建角色失败:', error); + messageManager.error(error instanceof Error ? error.message : '创建角色失败,请稍后重试', { duration: 3000 }); + } finally { } }; - const handleRemoveSkin = async (profileUuid: string) => { + 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 updateProfile(profileUuid, { skin_id: undefined }); + const response = await deleteProfile(uuid); if (response.code === 200) { - setProfiles(prev => prev.map(profile => - profile.uuid === profileUuid ? response.data : profile - )); - - // 移除皮肤显示 - setProfileSkins(prev => { - const newSkins = { ...prev }; - delete newSkins[profileUuid]; - return newSkins; - }); - - alert('皮肤移除成功!'); + setProfiles(prev => prev.filter(profile => profile.uuid !== uuid)); + messageManager.success('角色删除成功!', { duration: 3000 }); } else { - throw new Error(response.message || '移除皮肤失败'); + throw new Error(response.message || '删除角色失败'); } } catch (error) { - console.error('移除皮肤失败:', error); - alert(error instanceof Error ? error.message : '移除皮肤失败,请稍后重试'); + console.error('删除角色失败:', error); + messageManager.error(error instanceof Error ? error.message : '删除角色失败,请稍后重试', { duration: 3000 }); + } finally { } }; - const sidebarVariants = { - hidden: { x: -100, opacity: 0 }, - visible: { x: 0, opacity: 1, transition: { duration: 0.5, ease: "easeOut" as const } } + 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 + }))); + messageManager.success('角色切换成功!', { duration: 3000 }); + } else { + throw new Error(response.message || '设置活跃角色失败'); + } + } catch (error) { + console.error('设置活跃角色失败:', error); + messageManager.error(error instanceof Error ? error.message : '设置活跃角色失败,请稍后重试', { duration: 3000 }); + } finally { + } }; - const contentVariants = { - hidden: { x: 100, opacity: 0 }, - visible: { x: 0, opacity: 1, transition: { duration: 0.5, ease: "easeOut" as const, delay: 0.1 } } + const handleEditCharacter = async () => { + if (!editProfileName.trim()) { + messageManager.warning('请输入角色名称', { duration: 3000 }); + return; + } + + try { + const response = await updateProfile(editingProfile!, { name: editProfileName.trim() }); + if (response.code === 200) { + setProfiles(prev => prev.map(profile => + profile.uuid === editingProfile ? response.data : profile + )); + setEditingProfile(null); + setEditProfileName(''); + messageManager.success('角色编辑成功!', { duration: 3000 }); + } else { + throw new Error(response.message || '编辑角色失败'); + } + } catch (error) { + console.error('编辑角色失败:', error); + messageManager.error(error instanceof Error ? error.message : '编辑角色失败,请稍后重试', { duration: 3000 }); + } + }; + + const onEdit = (uuid: string, currentName: string) => { + setEditingProfile(uuid); + setEditProfileName(currentName); + }; + + const onCancelEdit = () => { + setEditingProfile(null); + setEditProfileName(''); + }; + + // 渲染内容区域 + const renderContent = () => { + switch (activeTab) { + case 'characters': + return ( + +
+

角色管理

+ 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) => ( + + ))} +
+ )} +
+ ); + case 'skins': + return ( + setShowUploadSkin(true)} + onToggleVisibility={handleToggleSkinVisibility} + onDelete={handleDeleteSkin} + /> + ); + case 'favorites': + return ( + + ); + case 'settings': + return ( + +

账户设置

+ +
+ {/* Avatar Settings */} + +

+ + 头像设置 +

+
+
+ {userProfile?.avatar ? ( + {userProfile.username} + ) : ( +
+ +
+ )} + document.getElementById('avatar-upload')?.click()} + > + + + +
+
+

+ 支持 JPG、PNG 格式,最大 2MB +

+ {avatarFile && ( +
+

+ 已选择: {avatarFile.name} +

+
+ )} + {isUploadingAvatar && ( +
+
+
+
+

+ 上传中... {avatarUploadProgress}% +

+
+ )} +
+ + + {isUploadingAvatar ? '上传中...' : '上传头像'} + + {userProfile?.avatar && ( + + 删除头像 + + )} +
+
+
+ + + {/* Yggdrasil Settings */} + +

+ + Yggdrasil 设置 +

+
+
+ +
+ + setShowYggdrasilPassword(!showYggdrasilPassword)} + className="bg-gradient-to-r from-gray-500 to-gray-600 text-white px-4 py-2 rounded-xl shadow-lg hover:shadow-xl transition-all duration-200" + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + {showYggdrasilPassword ? '隐藏' : '显示'} + + + {isResettingYggdrasilPassword ? '重置中...' : '重置密码'} + +
+

+ 此密码用于Minecraft客户端连接,请妥善保管 +

+
+
+
+ + {/* Basic Info */} + +

+ + 基本信息 +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + {/* Account Actions */} + +

+ + 账户操作 +

+
+ + 更换邮箱地址 + + + + 退出登录 + + +
+
+
+
+ ); + default: + return ( + +

+ 账户设置 +

+
+

功能开发中

+

该功能正在开发中,敬请期待!

+
+
+ ); + } + }; + + // 渲染皮肤选择器模态框 + const renderSkinSelector = () => { + if (!showSkinSelector) return null; + + const currentProfile = profiles.find(p => p.uuid === showSkinSelector); + const availableSkins = mySkins.filter(skin => skin.type === 'SKIN'); + + return ( + + + +
+

+ 为角色 "{currentProfile?.name}" 选择皮肤 +

+ +
+ +
+ { + // 移除皮肤 + if (currentProfile) { + updateProfile(currentProfile.uuid, { skin_id: undefined }); + setProfiles(prev => prev.map(p => + p.uuid === currentProfile.uuid ? { ...p, skin_id: undefined } : p + )); + setProfileSkins(prev => { + const newSkins = { ...prev }; + delete newSkins[currentProfile.uuid]; + return newSkins; + }); + setShowSkinSelector(null); + } + }} + > +
+ +

移除皮肤

+
+
+ + {availableSkins.map((skin) => ( + { + // 分配皮肤给角色 + if (currentProfile) { + updateProfile(currentProfile.uuid, { skin_id: skin.id }); + setProfiles(prev => prev.map(p => + p.uuid === currentProfile.uuid ? { ...p, skin_id: skin.id } : p + )); + setProfileSkins(prev => ({ + ...prev, + [currentProfile.uuid]: { url: skin.url, isSlim: skin.is_slim } + })); + setShowSkinSelector(null); + } + }} + > + +
+ {skin.name} +
+
+ ))} +
+ + {availableSkins.length === 0 && ( +
+ +

暂无可用皮肤,请先上传皮肤

+
+ )} +
+
+
+ ); }; if (!isAuthenticated) { @@ -611,1059 +1056,78 @@ export default function ProfilePage() { return (
{/* Animated Background */} -
+
-
+
- {/* Left Sidebar - 完全固定的 */} + {/* Left Sidebar - 使用CSS自定义属性考虑navbar高度 */} {/* User Profile Card */} -
-
- {userProfile?.avatar ? ( - {userProfile.username} - ) : ( -
- -
- )} -
-

{userProfile?.username}

-

{userProfile?.email}

-
-
- -
-
-
{mySkins.length}
-
皮肤
-
-
-
{favoriteSkins.length}
-
收藏
-
-
-
{userProfile?.points || 0}
-
积分
-
-
-
+ - {/* Navigation Menu - 固定的 */} - - - {/* Logout Button - 始终在底部可见 */} - - - 退出登录 - + {/* Profile Sidebar Component */} + setActiveTab(tab as 'characters' | 'skins' | 'favorites' | 'settings')} + skinCount={mySkins.length} + favoriteCount={favoriteSkins.length} + profilesCount={profiles.length} + onLogout={logout} + />
{/* 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="请输入角色名称" - /> -
-
- - -
-
-
-
- )} -
- - {/* Skin Selector Modal */} - - {showSkinSelector && ( - - -
-

选择皮肤

- -
- - {mySkins.length === 0 ? ( -
- -

暂无皮肤

-

您还没有上传任何皮肤

- -
- ) : ( - <> -
- -
- -
- {mySkins.map((skin) => ( - handleAssignSkin(showSkinSelector, skin.id)} - > -
- {skin.type === 'SKIN' ? ( - - ) : ( -
-
-
- 🧥 -
-

披风

-
-
- )} - {!skin.is_public && ( -
- 私密 -
- )} -
- -
-

{skin.name}

-

- {skin.type === 'SKIN' ? (skin.is_slim ? '细臂模型' : '经典模型') : '披风'} -

-
- - - {skin.download_count} - - - - {skin.favorite_count} - -
-
-
- ))} -
- - )} -
-
- )} -
- - {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 && ( - - 当前使用 - - )} -
- -
- {profileSkins[profile.uuid] ? ( - - ) : ( -
-
-
- )} -
- -
- {!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="请输入皮肤名称" - /> -
- -
- -