diff --git a/.gitignore b/.gitignore index dd2e62d..6f74684 100644 --- a/.gitignore +++ b/.gitignore @@ -1,131 +1,134 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. -# dependencies -/node_modules -/.pnp +# Dependencies +node_modules/ +.pnp .pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions -# testing -/coverage +# Testing +coverage/ -# next.js -/.next/ -/out/ +# Production build outputs +.next/ +out/ +build/ +dist/ -# production -/build +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local -# misc -.DS_Store -*.pem - -# debug +# Logs npm-debug.log* yarn-debug.log* yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts -# .gitignore - -# 依赖目录 -node_modules/ - -# Next.js 构建输出 -.next/ -out/ - -# 生产依赖 -dist/ - -# 缓存文件 -.cache/ -*.cache - -# 环境变量 -.env*.local -.env - -# 系统文件 -.DS_Store -Thumbs.db - -# 日志文件 -*.log +pnpm-debug.log* +lerna-debug.log* logs/ +*.log -# 编辑器/IDE 配置 +# Editor directories and files .idea/ .vscode/ -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# 测试相关 -coverage/ -.nyc_output/ - -# TypeScript 编译输出 -*.js -*.js.map -!next.config.js - -# Tailwind CSS -*.css.map - -# macOS 系统文件 -._* - -# Windows 系统文件 -[Dd]esktop.ini - -# 包管理器锁文件(根据你使用的包管理器选择) -# yarn.lock # 如果使用 Yarn 请取消注释 -# package-lock.json # 如果使用 npm 请取消注释 -# pnpm-lock.yaml # 如果使用 pnpm 请取消注释 - -# Node.js 依赖 -node_modules/ - -# Next.js 特定构建输出 -.next/ -out/ - -# 环境变量 -.env*.local -.env - -# macOS -.DS_Store - -# Windows -Thumbs.db -Desktop.ini - -# Linux +*.swp +*.swo *~ -# VS Code -.vscode/ -# IntelliJ IDEA -.idea/ -# 保留 package-lock.json -!package-lock.json +# OS generated files +.DS_Store +Thumbs.db +Thumbs.db:encryptable +nehahn/ +ehahn.db +[Dd]esktop.ini +*.cab +*.msi +*.msix +*.msm +*.msp +*.lnk -# 忽略 npm 调试日志 -npm-debug.log* \ No newline at end of file +# TypeScript +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Vercel +.vercel/ + +#保留重要文件 +!package-lock.json \ No newline at end of file diff --git a/README.md b/README.md index 79e59e7..102e00c 100644 --- a/README.md +++ b/README.md @@ -1 +1,126 @@ -#3D 预览还没实现 +# Minecraft 皮肤管理平台 + +一个基于 Next.js 构建的现代化 Minecraft 皮肤管理平台,支持皮肤上传、管理、角色创建和 Yggdrasil 认证。 + +## 功能特性 + +- 📤 **皮肤上传与管理** - 支持上传、预览和管理多个 Minecraft 皮肤 +- 👤 **角色中心** - 创建和管理游戏角色,支持多角色切换 +- 🔐 **外置登录** - 通过拖拽功能实现与 PCL2 等启动器的 Yggdrasil 认证 +- 📚 **使用教程** - 提供基础教程、Yggdrasil 教程等帮助文档 +- 🎨 **响应式设计** - 适配桌面和移动设备的现代化界面 +- 🌙 **暗色模式** - 支持亮色/暗色主题切换 + +## 技术栈 + +- **前端框架**: Next.js 14 + React 18 +- **类型系统**: TypeScript +- **样式方案**: Tailwind CSS 4 + Radix UI +- **认证系统**: NextAuth.js +- **API 调用**: Axios +- **图标库**: Lucide React + +## 安装与运行 + +### 前置要求 + +- Node.js 20+ +- npm 或 yarn + +### 安装步骤 + +1. 克隆项目 +```bash +git clone [仓库地址] +cd my-app +``` + +2. 安装依赖 +```bash +npm install +# 或 +yarn install +``` + +3. 启动开发服务器 +```bash +npm run dev +# 或 +yarn dev +``` + +4. 构建生产版本 +```bash +npm run build +npm start +# 或 +yarn build +yarn start +``` + +## 项目结构 + +``` +├── public/ # 静态资源文件 +│ ├── skins/ # 皮肤文件存储 +│ └── images/ # 其他图片资源 +├── src/ # 源代码目录 +│ ├── app/ # Next.js App Router +│ │ ├── (auth)/ # 认证相关页面 +│ │ ├── api/ # API 路由 +│ │ ├── character-center/ # 角色中心页面 +│ │ ├── dashboard/ # 皮肤管理仪表盘 +│ │ ├── help/ # 帮助文档页面 +│ │ ├── skins/ # 皮肤上传和管理 +│ │ └── user-home/ # 用户主页 +│ ├── components/ # 可复用组件 +│ │ ├── Navbar.tsx # 导航栏组件 +│ │ ├── auth/ # 认证相关组件 +│ │ ├── skins/ # 皮肤相关组件 +│ │ └── ui/ # UI 组件库 +│ ├── lib/ # 工具函数和 API 调用 +│ ├── styles/ # 全局样式和主题 +│ └── types/ # TypeScript 类型定义 +└── next.config.js # Next.js 配置文件 +``` + +## 使用指南 + +### 皮肤上传 + +1. 登录平台后,在用户主页点击"上传皮肤"卡片 +2. 选择符合要求的 PNG 格式皮肤文件 +3. 上传成功后可在"我的皮肤"中查看和管理 + +### Yggdrasil 认证 + +1. 在用户主页找到"外置登录"卡片 +2. 拖拽此卡片到 PCL2 等支持 Yggdrasil 认证的启动器 +3. 启动器将自动完成认证流程 + +### 角色管理 + +1. 点击"角色中心"卡片进入角色管理界面 +2. 可以创建新角色、编辑现有角色信息 +3. 为不同角色分配不同的皮肤 + +## 注意事项 + +- 皮肤文件必须为 PNG 格式,且符合 Minecraft 皮肤尺寸规范 +- 3D 预览功能尚未实现,目前使用 2D 预览 +- 部分功能可能需要后端 API 支持,请确保相关接口已正确配置 + +## 许可证 + +[MIT](LICENSE) + +## 贡献指南 + +欢迎提交 Issue 和 Pull Request! + +## 联系方式 + +如有问题或建议,请通过以下方式联系我们: + +- Email: [项目邮箱] +- GitHub: [项目仓库地址] \ No newline at end of file diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 0000000..c1d0832 --- /dev/null +++ b/middleware.ts @@ -0,0 +1,49 @@ +import { NextResponse } from 'next/server'; +import type { NextRequest } from 'next/server'; +import { getToken } from 'next-auth/jwt'; + +// 更直接的中间件实现 +export async function middleware(request: NextRequest) { + const { pathname } = request.nextUrl; + + // 定义需要检查登录状态的页面 + const isRootPage = pathname === '/'; + const isLoginPage = pathname === '/login'; + const isRegisterPage = pathname === '/register'; + + // 只对这三个页面进行检查 + const shouldCheckAuth = isRootPage || isLoginPage || isRegisterPage; + + if (!shouldCheckAuth) { + return NextResponse.next(); + } + + try { + // 使用cookie存储的next-auth会话信息来检查登录状态 + // 从请求头中提取cookie信息 + const authCookie = request.cookies.get('next-auth.session-token') || + request.cookies.get('__Secure-next-auth.session-token'); + + // 如果存在auth cookie,认为用户已登录,重定向到用户主页 + if (authCookie) { + const url = request.nextUrl.clone(); + url.pathname = '/user-home'; + return NextResponse.redirect(url); + } + + // 不存在auth cookie,允许访问原页面 + return NextResponse.next(); + } catch (error) { + console.error('登录状态检查错误:', error); + // 发生错误时,为了安全起见,默认允许访问原页面 + return NextResponse.next(); + } +} + +// 配置中间件适用的路径 +export const config = { + // 直接匹配三个目标页面 + matcher: ['/', '/login', '/register'], +}; + +// 重要提示:对于(auth)路由组中的页面,Next.js路由系统会自动将'/login'和'/register'映射到正确的物理路径 \ No newline at end of file diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..40c3d68 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/package-lock.json b/package-lock.json index 49435c4..8456cbb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,6 @@ "dependencies": { "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-slot": "^1.2.3", - "@react-three/drei": "^9.122.0", - "@react-three/fiber": "^8.18.0", "axios": "^1.11.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -21,8 +19,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-dropzone": "^14.3.8", - "tailwind-merge": "^3.3.1", - "three": "^0.152.0" + "tailwind-merge": "^3.3.1" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -30,7 +27,6 @@ "@types/node": "^20", "@types/react": "^19.1.9", "@types/react-dom": "^19", - "@types/three": "^0.178.1", "autoprefixer": "^10.4.21", "eslint": "^9", "eslint-config-next": "15.4.4", @@ -76,12 +72,6 @@ "node": ">=6.9.0" } }, - "node_modules/@dimforge/rapier3d-compat": { - "version": "0.12.0", - "resolved": "https://registry.npmmirror.com/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", - "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", - "license": "Apache-2.0" - }, "node_modules/@emnapi/core": { "version": "1.4.5", "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.4.5.tgz", @@ -375,12 +365,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mediapipe/tasks-vision": { - "version": "0.10.17", - "resolved": "https://registry.npmmirror.com/@mediapipe/tasks-vision/-/tasks-vision-0.10.17.tgz", - "integrity": "sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==", - "license": "Apache-2.0" - }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -690,219 +674,6 @@ } } }, - "node_modules/@react-spring/animated": { - "version": "9.7.5", - "resolved": "https://registry.npmmirror.com/@react-spring/animated/-/animated-9.7.5.tgz", - "integrity": "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==", - "license": "MIT", - "dependencies": { - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@react-spring/core": { - "version": "9.7.5", - "resolved": "https://registry.npmmirror.com/@react-spring/core/-/core-9.7.5.tgz", - "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==", - "license": "MIT", - "dependencies": { - "@react-spring/animated": "~9.7.5", - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/react-spring/donate" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@react-spring/rafz": { - "version": "9.7.5", - "resolved": "https://registry.npmmirror.com/@react-spring/rafz/-/rafz-9.7.5.tgz", - "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==", - "license": "MIT" - }, - "node_modules/@react-spring/shared": { - "version": "9.7.5", - "resolved": "https://registry.npmmirror.com/@react-spring/shared/-/shared-9.7.5.tgz", - "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==", - "license": "MIT", - "dependencies": { - "@react-spring/rafz": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@react-spring/three": { - "version": "9.7.5", - "resolved": "https://registry.npmmirror.com/@react-spring/three/-/three-9.7.5.tgz", - "integrity": "sha512-RxIsCoQfUqOS3POmhVHa1wdWS0wyHAUway73uRLp3GAL5U2iYVNdnzQsep6M2NZ994BlW8TcKuMtQHUqOsy6WA==", - "license": "MIT", - "dependencies": { - "@react-spring/animated": "~9.7.5", - "@react-spring/core": "~9.7.5", - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "peerDependencies": { - "@react-three/fiber": ">=6.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "three": ">=0.126" - } - }, - "node_modules/@react-spring/types": { - "version": "9.7.5", - "resolved": "https://registry.npmmirror.com/@react-spring/types/-/types-9.7.5.tgz", - "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==", - "license": "MIT" - }, - "node_modules/@react-three/drei": { - "version": "9.122.0", - "resolved": "https://registry.npmmirror.com/@react-three/drei/-/drei-9.122.0.tgz", - "integrity": "sha512-SEO/F/rBCTjlLez7WAlpys+iGe9hty4rNgjZvgkQeXFSiwqD4Hbk/wNHMAbdd8vprO2Aj81mihv4dF5bC7D0CA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mediapipe/tasks-vision": "0.10.17", - "@monogrid/gainmap-js": "^3.0.6", - "@react-spring/three": "~9.7.5", - "@use-gesture/react": "^10.3.1", - "camera-controls": "^2.9.0", - "cross-env": "^7.0.3", - "detect-gpu": "^5.0.56", - "glsl-noise": "^0.0.0", - "hls.js": "^1.5.17", - "maath": "^0.10.8", - "meshline": "^3.3.1", - "react-composer": "^5.0.3", - "stats-gl": "^2.2.8", - "stats.js": "^0.17.0", - "suspend-react": "^0.1.3", - "three-mesh-bvh": "^0.7.8", - "three-stdlib": "^2.35.6", - "troika-three-text": "^0.52.0", - "tunnel-rat": "^0.1.2", - "utility-types": "^3.11.0", - "zustand": "^5.0.1" - }, - "peerDependencies": { - "@react-three/fiber": "^8", - "react": "^18", - "react-dom": "^18", - "three": ">=0.137" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/@react-three/drei/node_modules/@monogrid/gainmap-js": { - "version": "3.1.0", - "resolved": "https://registry.npmmirror.com/@monogrid/gainmap-js/-/gainmap-js-3.1.0.tgz", - "integrity": "sha512-Obb0/gEd/HReTlg8ttaYk+0m62gQJmCblMOjHSMHRrBP2zdfKMHLCRbh/6ex9fSUJMKdjjIEiohwkbGD3wj2Nw==", - "license": "MIT", - "dependencies": { - "promise-worker-transferable": "^1.0.4" - }, - "peerDependencies": { - "three": ">= 0.159.0" - } - }, - "node_modules/@react-three/drei/node_modules/zustand": { - "version": "5.0.6", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.6.tgz", - "integrity": "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - }, - "node_modules/@react-three/fiber": { - "version": "8.18.0", - "resolved": "https://registry.npmmirror.com/@react-three/fiber/-/fiber-8.18.0.tgz", - "integrity": "sha512-FYZZqD0UUHUswKz3LQl2Z7H24AhD14XGTsIRw3SJaXUxyfVMi+1yiZGmqTcPt/CkPpdU7rrxqcyQ1zJE5DjvIQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.17.8", - "@types/react-reconciler": "^0.26.7", - "@types/webxr": "*", - "base64-js": "^1.5.1", - "buffer": "^6.0.3", - "its-fine": "^1.0.6", - "react-reconciler": "^0.27.0", - "react-use-measure": "^2.1.7", - "scheduler": "^0.21.0", - "suspend-react": "^0.1.3", - "zustand": "^3.7.1" - }, - "peerDependencies": { - "expo": ">=43.0", - "expo-asset": ">=8.4", - "expo-file-system": ">=11.0", - "expo-gl": ">=11.0", - "react": ">=18 <19", - "react-dom": ">=18 <19", - "react-native": ">=0.64", - "three": ">=0.133" - }, - "peerDependenciesMeta": { - "expo": { - "optional": true - }, - "expo-asset": { - "optional": true - }, - "expo-file-system": { - "optional": true - }, - "expo-gl": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/@react-three/fiber/node_modules/scheduler": { - "version": "0.21.0", - "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.21.0.tgz", - "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1209,12 +980,6 @@ "tailwindcss": "4.1.11" } }, - "node_modules/@tweenjs/tween.js": { - "version": "23.1.3", - "resolved": "https://registry.npmmirror.com/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", - "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", - "license": "MIT" - }, "node_modules/@tybys/wasm-util": { "version": "0.10.0", "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", @@ -1226,12 +991,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@types/draco3d": { - "version": "1.4.10", - "resolved": "https://registry.npmmirror.com/@types/draco3d/-/draco3d-1.4.10.tgz", - "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==", - "license": "MIT" - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", @@ -1263,16 +1022,11 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/offscreencanvas": { - "version": "2019.7.3", - "resolved": "https://registry.npmmirror.com/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", - "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", - "license": "MIT" - }, "node_modules/@types/react": { "version": "19.1.9", "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.1.9.tgz", "integrity": "sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==", + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1288,42 +1042,6 @@ "@types/react": "^19.0.0" } }, - "node_modules/@types/react-reconciler": { - "version": "0.26.7", - "resolved": "https://registry.npmmirror.com/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", - "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/stats.js": { - "version": "0.17.4", - "resolved": "https://registry.npmmirror.com/@types/stats.js/-/stats.js-0.17.4.tgz", - "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", - "license": "MIT" - }, - "node_modules/@types/three": { - "version": "0.178.1", - "resolved": "https://registry.npmmirror.com/@types/three/-/three-0.178.1.tgz", - "integrity": "sha512-WSabew1mgWgRx2RfLfKY+9h4wyg6U94JfLbZEGU245j/WY2kXqU0MUfghS+3AYMV5ET1VlILAgpy77cB6a3Itw==", - "license": "MIT", - "dependencies": { - "@dimforge/rapier3d-compat": "~0.12.0", - "@tweenjs/tween.js": "~23.1.3", - "@types/stats.js": "*", - "@types/webxr": "*", - "@webgpu/types": "*", - "fflate": "~0.8.2", - "meshoptimizer": "~0.18.1" - } - }, - "node_modules/@types/webxr": { - "version": "0.5.22", - "resolved": "https://registry.npmmirror.com/@types/webxr/-/webxr-0.5.22.tgz", - "integrity": "sha512-Vr6Stjv5jPRqH690f5I5GLjVk8GSsoQSYJ2FVd/3jJF7KaqfwPi3ehfBS96mlQ2kPCwZaX6U0rG2+NGHBKkA/A==", - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.38.0", "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.38.0.tgz", @@ -1881,30 +1599,6 @@ "win32" ] }, - "node_modules/@use-gesture/core": { - "version": "10.3.1", - "resolved": "https://registry.npmmirror.com/@use-gesture/core/-/core-10.3.1.tgz", - "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==", - "license": "MIT" - }, - "node_modules/@use-gesture/react": { - "version": "10.3.1", - "resolved": "https://registry.npmmirror.com/@use-gesture/react/-/react-10.3.1.tgz", - "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==", - "license": "MIT", - "dependencies": { - "@use-gesture/core": "10.3.1" - }, - "peerDependencies": { - "react": ">= 16.8.0" - } - }, - "node_modules/@webgpu/types": { - "version": "0.1.64", - "resolved": "https://registry.npmmirror.com/@webgpu/types/-/types-0.1.64.tgz", - "integrity": "sha512-84kRIAGV46LJTlJZWxShiOrNL30A+9KokD7RB3dRCIqODFjodS5tCD5yyiZ8kIReGVZSDfA3XkkwyyOIF6K62A==", - "license": "BSD-3-Clause" - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz", @@ -2262,35 +1956,6 @@ "dev": true, "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", - "license": "MIT", - "dependencies": { - "require-from-string": "^2.0.2" - } - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2348,30 +2013,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmmirror.com/busboy/-/busboy-1.6.0.tgz", @@ -2442,15 +2083,6 @@ "node": ">=6" } }, - "node_modules/camera-controls": { - "version": "2.10.1", - "resolved": "https://registry.npmmirror.com/camera-controls/-/camera-controls-2.10.1.tgz", - "integrity": "sha512-KnaKdcvkBJ1Irbrzl8XD6WtZltkRjp869Jx8c0ujs9K+9WD+1D7ryBsCiVqJYUqt6i/HR5FxT7RLASieUD+Q5w==", - "license": "MIT", - "peerDependencies": { - "three": ">=0.126.1" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001731", "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001731.tgz", @@ -2573,28 +2205,11 @@ "node": ">= 0.6" } }, - "node_modules/cross-env": { - "version": "7.0.3", - "resolved": "https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.1" - }, - "bin": { - "cross-env": "src/bin/cross-env.js", - "cross-env-shell": "src/bin/cross-env-shell.js" - }, - "engines": { - "node": ">=10.14", - "npm": ">=6", - "yarn": ">=1" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2609,6 +2224,7 @@ "version": "3.1.3", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2742,15 +2358,6 @@ "node": ">=0.4.0" } }, - "node_modules/detect-gpu": { - "version": "5.0.70", - "resolved": "https://registry.npmmirror.com/detect-gpu/-/detect-gpu-5.0.70.tgz", - "integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==", - "license": "MIT", - "dependencies": { - "webgl-constants": "^1.1.1" - } - }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.0.4.tgz", @@ -2774,12 +2381,6 @@ "node": ">=0.10.0" } }, - "node_modules/draco3d": { - "version": "1.5.7", - "resolved": "https://registry.npmmirror.com/draco3d/-/draco3d-1.5.7.tgz", - "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==", - "license": "Apache-2.0" - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3505,12 +3106,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "license": "MIT" - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3804,12 +3399,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glsl-noise": { - "version": "0.0.0", - "resolved": "https://registry.npmmirror.com/glsl-noise/-/glsl-noise-0.0.0.tgz", - "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==", - "license": "MIT" - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", @@ -3926,32 +3515,6 @@ "node": ">= 0.4" } }, - "node_modules/hls.js": { - "version": "1.6.7", - "resolved": "https://registry.npmmirror.com/hls.js/-/hls.js-1.6.7.tgz", - "integrity": "sha512-QW2fnwDGKGc9DwQUGLbmMOz8G48UZK7PVNJPcOUql1b8jubKx4/eMHNP5mGqr6tYlJNDG1g10Lx2U/qPzL6zwQ==", - "license": "Apache-2.0" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz", @@ -3962,12 +3525,6 @@ "node": ">= 4" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "license": "MIT" - }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz", @@ -4266,12 +3823,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmmirror.com/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", - "license": "MIT" - }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz", @@ -4428,6 +3979,7 @@ "version": "2.0.0", "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/iterator.prototype": { @@ -4448,27 +4000,6 @@ "node": ">= 0.4" } }, - "node_modules/its-fine": { - "version": "1.2.5", - "resolved": "https://registry.npmmirror.com/its-fine/-/its-fine-1.2.5.tgz", - "integrity": "sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==", - "license": "MIT", - "dependencies": { - "@types/react-reconciler": "^0.28.0" - }, - "peerDependencies": { - "react": ">=18.0" - } - }, - "node_modules/its-fine/node_modules/@types/react-reconciler": { - "version": "0.28.9", - "resolved": "https://registry.npmmirror.com/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", - "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*" - } - }, "node_modules/jiti": { "version": "2.5.1", "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.5.1.tgz", @@ -4601,15 +4132,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmmirror.com/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lightningcss": { "version": "1.30.1", "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.30.1.tgz", @@ -4911,16 +4433,6 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/maath": { - "version": "0.10.8", - "resolved": "https://registry.npmmirror.com/maath/-/maath-0.10.8.tgz", - "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==", - "license": "MIT", - "peerDependencies": { - "@types/three": ">=0.134.0", - "three": ">=0.134.0" - } - }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz", @@ -4950,21 +4462,6 @@ "node": ">= 8" } }, - "node_modules/meshline": { - "version": "3.3.1", - "resolved": "https://registry.npmmirror.com/meshline/-/meshline-3.3.1.tgz", - "integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==", - "license": "MIT", - "peerDependencies": { - "three": ">=0.137" - } - }, - "node_modules/meshoptimizer": { - "version": "0.18.1", - "resolved": "https://registry.npmmirror.com/meshoptimizer/-/meshoptimizer-0.18.1.tgz", - "integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==", - "license": "MIT" - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", @@ -5502,6 +4999,7 @@ "version": "3.1.1", "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5579,12 +5077,6 @@ "dev": true, "license": "MIT" }, - "node_modules/potpack": { - "version": "1.0.2", - "resolved": "https://registry.npmmirror.com/potpack/-/potpack-1.0.2.tgz", - "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", - "license": "ISC" - }, "node_modules/preact": { "version": "10.27.0", "resolved": "https://registry.npmmirror.com/preact/-/preact-10.27.0.tgz", @@ -5623,16 +5115,6 @@ "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==", "license": "MIT" }, - "node_modules/promise-worker-transferable": { - "version": "1.0.4", - "resolved": "https://registry.npmmirror.com/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz", - "integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==", - "license": "Apache-2.0", - "dependencies": { - "is-promise": "^2.1.0", - "lie": "^3.0.2" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz", @@ -5693,18 +5175,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-composer": { - "version": "5.0.3", - "resolved": "https://registry.npmmirror.com/react-composer/-/react-composer-5.0.3.tgz", - "integrity": "sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==", - "license": "MIT", - "dependencies": { - "prop-types": "^15.6.0" - }, - "peerDependencies": { - "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", @@ -5741,46 +5211,6 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, - "node_modules/react-reconciler": { - "version": "0.27.0", - "resolved": "https://registry.npmmirror.com/react-reconciler/-/react-reconciler-0.27.0.tgz", - "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.21.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "react": "^18.0.0" - } - }, - "node_modules/react-reconciler/node_modules/scheduler": { - "version": "0.21.0", - "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.21.0.tgz", - "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/react-use-measure": { - "version": "2.1.7", - "resolved": "https://registry.npmmirror.com/react-use-measure/-/react-use-measure-2.1.7.tgz", - "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.13", - "react-dom": ">=16.13" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -5825,15 +5255,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz", @@ -6040,6 +5461,7 @@ "version": "2.0.0", "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -6052,6 +5474,7 @@ "version": "3.0.0", "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6149,32 +5572,6 @@ "dev": true, "license": "MIT" }, - "node_modules/stats-gl": { - "version": "2.4.2", - "resolved": "https://registry.npmmirror.com/stats-gl/-/stats-gl-2.4.2.tgz", - "integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==", - "license": "MIT", - "dependencies": { - "@types/three": "*", - "three": "^0.170.0" - }, - "peerDependencies": { - "@types/three": "*", - "three": "*" - } - }, - "node_modules/stats-gl/node_modules/three": { - "version": "0.170.0", - "resolved": "https://registry.npmmirror.com/three/-/three-0.170.0.tgz", - "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==", - "license": "MIT" - }, - "node_modules/stats.js": { - "version": "0.17.0", - "resolved": "https://registry.npmmirror.com/stats.js/-/stats.js-0.17.0.tgz", - "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==", - "license": "MIT" - }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -6382,15 +5779,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/suspend-react": { - "version": "0.1.3", - "resolved": "https://registry.npmmirror.com/suspend-react/-/suspend-react-0.1.3.tgz", - "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", - "license": "MIT", - "peerDependencies": { - "react": ">=17.0" - } - }, "node_modules/tailwind-merge": { "version": "3.3.1", "resolved": "https://registry.npmmirror.com/tailwind-merge/-/tailwind-merge-3.3.1.tgz", @@ -6436,45 +5824,6 @@ "node": ">=18" } }, - "node_modules/three": { - "version": "0.152.0", - "resolved": "https://registry.npmmirror.com/three/-/three-0.152.0.tgz", - "integrity": "sha512-uvKoYo4b2bnqzsR4RJFuWecxwMKcgT1nFNmiWooCNr6AxZLCtfkj/xcfFgoi5mFopSVorh7bnvTHPfeW8DINGg==", - "license": "MIT" - }, - "node_modules/three-mesh-bvh": { - "version": "0.7.8", - "resolved": "https://registry.npmmirror.com/three-mesh-bvh/-/three-mesh-bvh-0.7.8.tgz", - "integrity": "sha512-BGEZTOIC14U0XIRw3tO4jY7IjP7n7v24nv9JXS1CyeVRWOCkcOMhRnmENUjuV39gktAw4Ofhr0OvIAiTspQrrw==", - "deprecated": "Deprecated due to three.js version incompatibility. Please use v0.8.0, instead.", - "license": "MIT", - "peerDependencies": { - "three": ">= 0.151.0" - } - }, - "node_modules/three-stdlib": { - "version": "2.36.0", - "resolved": "https://registry.npmmirror.com/three-stdlib/-/three-stdlib-2.36.0.tgz", - "integrity": "sha512-kv0Byb++AXztEGsULgMAs8U2jgUdz6HPpAB/wDJnLiLlaWQX2APHhiTJIN7rqW+Of0eRgcp7jn05U1BsCP3xBA==", - "license": "MIT", - "dependencies": { - "@types/draco3d": "^1.4.0", - "@types/offscreencanvas": "^2019.6.4", - "@types/webxr": "^0.5.2", - "draco3d": "^1.4.1", - "fflate": "^0.6.9", - "potpack": "^1.0.1" - }, - "peerDependencies": { - "three": ">=0.128.0" - } - }, - "node_modules/three-stdlib/node_modules/fflate": { - "version": "0.6.10", - "resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.6.10.tgz", - "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.14", "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.14.tgz", @@ -6533,36 +5882,6 @@ "node": ">=8.0" } }, - "node_modules/troika-three-text": { - "version": "0.52.4", - "resolved": "https://registry.npmmirror.com/troika-three-text/-/troika-three-text-0.52.4.tgz", - "integrity": "sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==", - "license": "MIT", - "dependencies": { - "bidi-js": "^1.0.2", - "troika-three-utils": "^0.52.4", - "troika-worker-utils": "^0.52.0", - "webgl-sdf-generator": "1.1.1" - }, - "peerDependencies": { - "three": ">=0.125.0" - } - }, - "node_modules/troika-three-utils": { - "version": "0.52.4", - "resolved": "https://registry.npmmirror.com/troika-three-utils/-/troika-three-utils-0.52.4.tgz", - "integrity": "sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==", - "license": "MIT", - "peerDependencies": { - "three": ">=0.125.0" - } - }, - "node_modules/troika-worker-utils": { - "version": "0.52.0", - "resolved": "https://registry.npmmirror.com/troika-worker-utils/-/troika-worker-utils-0.52.0.tgz", - "integrity": "sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==", - "license": "MIT" - }, "node_modules/ts-api-utils": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz", @@ -6595,43 +5914,6 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, - "node_modules/tunnel-rat": { - "version": "0.1.2", - "resolved": "https://registry.npmmirror.com/tunnel-rat/-/tunnel-rat-0.1.2.tgz", - "integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==", - "license": "MIT", - "dependencies": { - "zustand": "^4.3.2" - } - }, - "node_modules/tunnel-rat/node_modules/zustand": { - "version": "4.5.7", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-4.5.7.tgz", - "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.2.2" - }, - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "immer": ">=9.0.6", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - } - } - }, "node_modules/tw-animate-css": { "version": "1.3.6", "resolved": "https://registry.npmmirror.com/tw-animate-css/-/tw-animate-css-1.3.6.tgz", @@ -6849,39 +6131,11 @@ "punycode": "^2.1.0" } }, - "node_modules/use-sync-external-store": { - "version": "1.5.0", - "resolved": "https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", - "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/utility-types": { - "version": "3.11.0", - "resolved": "https://registry.npmmirror.com/utility-types/-/utility-types-3.11.0.tgz", - "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/webgl-constants": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/webgl-constants/-/webgl-constants-1.1.1.tgz", - "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" - }, - "node_modules/webgl-sdf-generator": { - "version": "1.1.1", - "resolved": "https://registry.npmmirror.com/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", - "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==", - "license": "MIT" - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -7014,23 +6268,6 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zustand": { - "version": "3.7.2", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-3.7.2.tgz", - "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", - "license": "MIT", - "engines": { - "node": ">=12.7.0" - }, - "peerDependencies": { - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } - } } } } diff --git a/package.json b/package.json index 4005328..d3b9eef 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,6 @@ "dependencies": { "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-slot": "^1.2.3", - "@react-three/drei": "^9.122.0", - "@react-three/fiber": "^8.18.0", "axios": "^1.11.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -22,8 +20,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-dropzone": "^14.3.8", - "tailwind-merge": "^3.3.1", - "three": "^0.152.0" + "tailwind-merge": "^3.3.1" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -31,7 +28,6 @@ "@types/node": "^20", "@types/react": "^19.1.9", "@types/react-dom": "^19", - "@types/three": "^0.178.1", "autoprefixer": "^10.4.21", "eslint": "^9", "eslint-config-next": "15.4.4", diff --git a/public/test-skin.png b/public/test-skin.png new file mode 100644 index 0000000..1b9f2a6 Binary files /dev/null and b/public/test-skin.png differ diff --git a/public/test-skin2.png b/public/test-skin2.png new file mode 100644 index 0000000..1b9f2a6 Binary files /dev/null and b/public/test-skin2.png differ diff --git a/public/test-skin3.png b/public/test-skin3.png new file mode 100644 index 0000000..288c8cd Binary files /dev/null and b/public/test-skin3.png differ diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 9abd57d..b428a86 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -1,24 +1,121 @@ +'use client'; + import AuthForm from '@/components/auth/AuthForm'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; import Link from 'next/link'; +import Image from 'next/image'; +import { useSearchParams } from 'next/navigation'; export default function LoginPage() { + const searchParams = useSearchParams(); + const callbackUrl = searchParams.get('callbackUrl') || '/'; + return ( -
- - - 登录你的littlelan账户 - - - -
- 还没有账号? - - 立即注册 - +
+ {/* 背景装饰元素 - 渐变模糊效果 */} +
+
+
+
+
+ + {/* 主要内容区域 */} +
+
+ {/* 装饰性头部 */} +
+
+
+
+ Logo +
+
+
+

+ 欢迎回到 + HITWH + 社区 +

+

+ 登录你的账户,继续探索和创作独特的Minecraft皮肤 +

- - + + {/* 登录卡片 */} +
+ {/* 卡片阴影装饰 */} +
+
+ + +
+ + + + 🚪 登录账户 + +
+
+ + {/* 使用统一的认证表单组件 */} + + + {/* 分割线 */} +
+
+
+
+
+ 或者 +
+
+ + {/* 注册链接 */} +
+

+ 还没有账号?立即注册开始创作! +

+ +
+
+
+
+ + {/* 底部装饰 */} +
+
+ 🎨 皮肤创作 + + 🌍 社区分享 + + ⚡ 快速上传 + + 👥 角色中心 +
+
+
+
+ + {/* 浮动装饰元素 */} +
+
🔑
+
+
+
🌟
+
); } \ No newline at end of file diff --git a/src/app/(auth)/register/page.tsx b/src/app/(auth)/register/page.tsx new file mode 100644 index 0000000..9d4872e --- /dev/null +++ b/src/app/(auth)/register/page.tsx @@ -0,0 +1,164 @@ +'use client'; + +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import Link from 'next/link'; +import Image from 'next/image'; +import { useSearchParams } from 'next/navigation'; + +export default function RegisterPage() { + const searchParams = useSearchParams(); + const callbackUrl = searchParams.get('callbackUrl') || '/'; + + return ( +
+ {/* 背景装饰元素 - 渐变模糊效果 */} +
+
+
+
+
+ + {/* 主要内容区域 */} +
+
+ {/* 装饰性头部 */} +
+
+
+
+ Logo +
+
+
+

+ 欢迎加入 + HITWH + 社区 +

+

+ 创建你的账户,开始创作和分享独特的Minecraft皮肤 +

+
+ + {/* 注册卡片 */} +
+ {/* 卡片阴影装饰 */} +
+
+ + +
+ + + + 🎮 创建新账户 + +
+
+ +
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + {/* 分割线 */} +
+
+
+
+
+ 或者 +
+
+ + {/* 登录链接 */} +
+

+ 已有账号?直接登录开始游戏! +

+ +
+
+
+
+ + {/* 底部装饰 */} +
+
+ 🎨 皮肤创作 + + 🌍 社区分享 + + ⚡ 快速上传 + + 👥 角色中心 +
+
+
+
+ + {/* 浮动装饰元素 */} +
+
🎨
+
+
+
🌟
+
+
+ ); +} diff --git a/src/app/character-center/CharacterCenterClient.tsx b/src/app/character-center/CharacterCenterClient.tsx new file mode 100644 index 0000000..786b795 --- /dev/null +++ b/src/app/character-center/CharacterCenterClient.tsx @@ -0,0 +1,388 @@ +'use client'; + +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import Canvas2DSkinPreview from '@/components/skins/Canvas2DSkinPreview'; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import Link from 'next/link'; + +// 角色卡片组件 +function CharacterCard({ character }: { character: any }) { + return ( + + {/* 皮肤预览区域 */} +
+ {/* 装饰元素 */} +
+
+ + {/* 皮肤预览 - 使用Canvas2DSkinPreview组件 */} +
+ +
+
+ + {/* 角色信息区域 */} +
+
+
+
+

{character.name}

+

+ 创建于 {character.created} +

+
+ + 等级 {character.level} + +
+ + {/* 额外的角色信息 */} +
+ + + 活跃状态 + + + + 皮肤ID: {character.skinId} + +
+
+ + {/* 操作按钮区域 */} +
+ + + +
+
+
+ ); +} + +// 添加角色卡片组件 +function AddCharacterCard({ onAddClick }: { onAddClick: () => void }) { + return ( + +
+
+
+ 添加新角色 +
+
+ ); +} + + + +// 主客户端组件 +export default function CharacterCenterClient({ userName, characters }: { userName: string; characters: any[] }) { + // 使用简单的状态管理来模拟标签页 + const [activeTab, setActiveTab] = useState('my-characters'); + + // 角色创建表单状态 + const [characterForm, setCharacterForm] = useState({ + name: '', + description: '', + skinId: '', + capeId: '', + isActive: true, + }); + + // 生成UUID的简单方法(不使用外部库) + const generateUUID = () => { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + }; + + // 生成RSA密钥的简单模拟(实际项目中应在服务器端生成) + const generateRSAKey = () => { + // 模拟RSA私钥(实际项目中应使用加密库生成) + return `-----BEGIN RSA PRIVATE KEY----- +MOCK-RSA-KEY-FOR-DEMO-PURPOSES-ONLY +-----END RSA PRIVATE KEY-----`; + }; + + // 处理表单输入变化 + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setCharacterForm(prev => ({ + ...prev, + [name]: value + })); + }; + + // 处理复选框变化 + const handleCheckboxChange = (e: React.ChangeEvent) => { + setCharacterForm(prev => ({ + ...prev, + isActive: e.target.checked + })); + }; + + // 处理皮肤选择 + const handleSkinSelect = (skinId: string) => { + setCharacterForm(prev => ({ + ...prev, + skinId + })); + }; + + // 处理角色创建 + const handleCreateCharacter = async () => { + if (!characterForm.name.trim()) { + alert('请输入角色名称'); + return; + } + + try { + // 生成角色UUID + const characterUuid = generateUUID(); + + // 生成RSA私钥 + const rsaPrivateKey = generateRSAKey(); + + // 构建角色数据(根据数据库profiles表结构) + const newCharacter = { + uuid: characterUuid, + user_id: 'current-user-id', // 应从会话中获取真实用户ID + name: characterForm.name, + skin_id: characterForm.skinId || null, + cape_id: characterForm.capeId || null, + rsa_private_key: rsaPrivateKey, + is_active: characterForm.isActive, + description: characterForm.description + }; + + // 这里应该调用API发送到服务器 + console.log('创建角色数据:', newCharacter); + + // 模拟成功响应 + alert('角色创建成功!\nUUID: ' + characterUuid.substring(0, 8) + '...'); + + // 重置表单 + setCharacterForm({ + name: '', + description: '', + skinId: '', + capeId: '', + isActive: true, + }); + + // 切换回我的角色标签 + setActiveTab('my-characters'); + + } catch (error) { + console.error('创建角色失败:', error); + alert('创建角色失败,请重试'); + } + }; + + return ( +
+ {/* 背景装饰元素 - 渐变模糊效果 */} +
+
+
+
+
+ + {/* 主要内容区域 */} +
+ {/* 页面标题区域 */} +
+
+

角色中心

+

+ 欢迎回来,{userName}。定制和管理你的专属游戏角色。 +

+
+
+ + {/* 角色管理标签页 */} +
+
+ {/* 高级标签按钮组 */} +
+ + +
+ + +
+ + {/* 我的角色标签内容 */} + {activeTab === 'my-characters' && ( +
+
+ {/* 角色卡片列表 */} + {characters.map((character) => ( + + ))} + + {/* 添加新角色卡片 */} + setActiveTab('create-character')} /> +
+
+ )} + + {/* 创建角色标签内容 */} + {activeTab === 'create-character' && ( +
+

创建新角色

+
+
+
+ + +

+ 角色名称将作为Minecraft游戏内的用户名,请确保唯一性 +

+
+ +
+ +
+
handleSkinSelect('skin1')} + > + + {characterForm.skinId === 'skin1' && ( +
+ )} +
+
handleSkinSelect('skin2')} + > + + {characterForm.skinId === 'skin2' && ( +
+ )} +
+
handleSkinSelect('skin3')} + > + + {characterForm.skinId === 'skin3' && ( +
+ )} +
+
+
+ +
+ + +
+ +
+ + +
+ +
+

+ ℹ️ + 角色信息 +

+
    +
  • • 角色将自动生成唯一UUID
  • +
  • • 系统会为角色生成RSA密钥用于身份验证
  • +
  • • 角色名称必须符合Minecraft命名规范
  • +
  • • 每个用户最多可创建10个角色
  • +
+
+ + +
+
+
+ )} + + +
+ +
+ + {/* 页脚 */} +
+
+

我的世界皮肤库 - 你的Minecraft皮肤分享平台

+

© {new Date().getFullYear()} 版权所有

+
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/character-center/page.tsx b/src/app/character-center/page.tsx new file mode 100644 index 0000000..1f90e7f --- /dev/null +++ b/src/app/character-center/page.tsx @@ -0,0 +1,110 @@ +// src/app/character-center/page.tsx +// 角色中心页面 - 仅登录用户可访问 +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/lib/api/auth'; +import { redirect } from 'next/navigation'; +import CharacterCenterClient from './CharacterCenterClient'; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; + +export default async function CharacterCenter() { + // 检查用户登录状态 + const session = await getServerSession(authOptions); + + if (!session) { + // 未登录用户重定向到登录页面 + redirect('/'); + } + + // 安全地获取用户信息 + const userName = session.user?.name || '玩家'; + + // 模拟的角色数据 + const characters = []; + + // 渲染客户端组件 + return ; +} + +// 角色卡片组件 +function CharacterCard({ character }: { character: any }) { + return ( + +
+ {/* 角色状态标签 */} +
+ 活跃 +
+ + {/* 角色皮肤预览 */} +
+
+ {character.name} +
+
+
+ + + {character.name} +
+ 等级 {character.level} • 创建于 {character.created} +
+
+ + +
+
+
角色进度
+
+
+
+
+
+
+ + + + + +
+ ); +} + +// 添加角色卡片组件 +function AddCharacterCard() { + return ( + +
+

添加新角色

+

+ 创建一个新的游戏角色 +

+
+ ); +} + +// 统计卡片组件 +function StatCard({ title, value, icon }: { title: string; value: string; icon: string }) { + return ( + + +
{icon}
+ {title} +
+ +
{value}
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index bb769c5..9da0ced 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,37 +1,176 @@ // src/app/dashboard/page.tsx -import { getServerSession } from 'next-auth'; -import { authOptions } from '@/lib/api/auth'; -import { redirect } from 'next/navigation'; +'use client'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +//import { Separator } from '@/components/ui/separator'; +import Link from 'next/link'; +import { useState } from 'react'; +import Canvas2DSkinPreview from '@/components/skins/Canvas2DSkinPreview'; import SkinGrid from '@/components/skins/SkinGrid'; -export default async function Dashboard() { - const session = await getServerSession(authOptions); - - if (!session) { - redirect('/login'); - } - +export default function Dashboard() { + // 由于这是客户端组件,我们不能在这里使用getServerSession + // 在实际应用中,你应该使用useSession钩子或在服务器端获取会话 + // 这里我们模拟一个已登录的状态 + const session = { user: { id: 'test_user_1', name: '测试玩家', email: 'test@test.com' } }; + // 安全地获取用户ID - const userId = session.user?.id || 'unknown'; + const userId = session?.user?.id || 'unknown'; + + // 状态管理 + const [searchTerm, setSearchTerm] = useState(''); + const [activeCategory, setActiveCategory] = useState('all'); // 实际应用中这里会从API获取用户皮肤数据 const mockSkins = [ { id: '1', name: 'Steve皮肤', createdAt: '2023-05-01' }, { id: '2', name: 'Alex皮肤', createdAt: '2023-05-15' }, ]; + + // 根据搜索词和分类过滤皮肤 + const filteredSkins = mockSkins.filter(skin => + skin.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); return ( -
-

我的皮肤库

-
- - 上传新皮肤 - +
+ {/* 背景装饰元素 - 渐变模糊效果 */} +
+
+
+
- + + {/* 主要内容区域 */} +
+ {/* 页面标题和操作按钮 */} +
+
+

我的皮肤库

+

管理和分享你的Minecraft皮肤

+
+ + +
+ + {/* 搜索栏 */} +
+
+ setSearchTerm(e.target.value)} + /> +
+ 🔍 +
+
+
+ + {/* 皮肤分类标签 */} +
+ + + + +
+ + {/* 皮肤网格 */} +
+ {filteredSkins.map((skin) => ( + +
+
+ +
+ +
+

{skin.name}

+ +
+

上传于 {skin.createdAt}

+
+ + + +
+
+
+ ))} +
+ + {/* 空状态 */} + {filteredSkins.length === 0 && ( +
+ +
+ +
📦
+

还没有皮肤

+

+ 上传你的第一个皮肤,开始你的Minecraft皮肤收藏之旅 +

+ +
+
+
+ )} +
+ + {/* 页脚 */} +
+
+

我的世界皮肤库 - 你的Minecraft皮肤分享平台

+

© {new Date().getFullYear()} 版权所有

+
+
); } \ No newline at end of file diff --git a/src/app/globals.css b/src/app/globals.css index dc98be7..7a6ff9c 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -117,6 +117,80 @@ @apply border-border outline-ring/50; } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground font-sans; + background-image: + linear-gradient(45deg, #e6f7ef 25%, transparent 25%), + linear-gradient(-45deg, #e6f7ef 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #e6f7ef 75%), + linear-gradient(-45deg, transparent 75%, #e6f7ef 75%); + background-size: 40px 40px; + background-position: + 0 0, + 0 20px, + 20px -20px, + -20px 0px; + } + .dark body { + background-image: + linear-gradient(45deg, rgba(16, 185, 129, 0.1) 25%, transparent 25%), + linear-gradient(-45deg, rgba(16, 185, 129, 0.1) 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, rgba(16, 185, 129, 0.1) 75%), + linear-gradient(-45deg, transparent 75%, rgba(16, 185, 129, 0.1) 75%); + } +} + +@layer utilities { + .content-auto { + content-visibility: auto; + } + /* 响应式间距工具 */ + .spacing-sm { + @apply p-2 sm:p-3 md:p-4; + } + .spacing-md { + @apply p-3 sm:p-4 md:p-6; + } + .spacing-lg { + @apply p-4 sm:p-6 md:p-8; + } + /* 响应式字体大小 */ + .text-responsive { + @apply text-base sm:text-lg md:text-xl; + } + /* 响应式网格布局 */ + .grid-responsive { + @apply grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4; + } + /* 移动端安全区域padding */ + .safe-area-inset { + @apply px-4 sm:px-6; + } + /* 修复iOS上的点击延迟 */ + .ios-tap { + -webkit-tap-highlight-color: transparent; + } + /* 登录弹窗动画 */ + .animate-fadeIn { + animation: fadeIn 0.3s ease-in-out; + } + + .animate-slideUp { + animation: slideUp 0.3s ease-out; + } + + @keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } + } + + @keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } } } diff --git a/src/app/help/basic/page.tsx b/src/app/help/basic/page.tsx new file mode 100644 index 0000000..018b357 --- /dev/null +++ b/src/app/help/basic/page.tsx @@ -0,0 +1,182 @@ +// src/app/help/basic/page.tsx +// 基础教程页面 +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; + +export default function BasicTutorialPage() { + return ( +
+
+ {/* 页面导航 */} +
+ 首页 + / + 帮助中心 + / + 基础教程 +
+ + {/* 页面标题 */} +
+

基础教程

+

+ 了解平台的基本功能和使用方法,快速上手 +

+
+ + {/* 教程内容列表 */} +
+ {/* 教程项1 */} + + + 1. 注册与登录 + + +
+

+ 要使用我们的平台,您需要先注册一个账户。点击页面右上角的"注册"按钮,填写必要的信息,包括用户名、邮箱和密码。注册成功后,您可以使用这些凭据登录平台。 +

+
+

提示:

+

+ 请确保使用真实的邮箱地址,因为我们可能需要通过邮箱验证您的账户或重置密码。 +

+
+
+
+
+ + {/* 教程项2 */} + + + 2. 上传自定义皮肤 + + +
+

+ 登录后,点击页面顶部导航栏中的"上传皮肤"按钮。在弹出的对话框中,点击"选择文件"按钮,从您的电脑中选择PNG格式的皮肤文件。添加皮肤名称和描述,然后点击"上传"按钮完成操作。 +

+
+

注意事项:

+
    +
  • 皮肤文件必须是PNG格式
  • +
  • 尺寸必须符合Minecraft标准(64x32或64x64像素)
  • +
  • 确保您拥有上传皮肤的版权或使用权限
  • +
+
+
+
+
+ + {/* 教程项3 */} + + + 3. 管理您的皮肤 + + +

+ 您可以在"个人中心"页面查看和管理您上传的所有皮肤。在这里,您可以: +

+
    +
  • 设置某个皮肤为当前使用的皮肤
  • +
  • 编辑皮肤的名称和描述
  • +
  • 删除不再需要的皮肤
  • +
  • 查看皮肤的上传历史和使用统计
  • +
+
+
+ + {/* 教程项4 */} + + + 4. 下载和使用他人的皮肤 + + +

+ 浏览皮肤库,找到您喜欢的皮肤,点击进入详情页面,然后点击"下载皮肤"按钮将皮肤保存到本地。下载后,您可以通过以下步骤在游戏中使用: +

+
    +
  1. 打开Minecraft启动器
  2. +
  3. 点击"皮肤"选项卡
  4. +
  5. 点击"浏览"按钮,选择下载的皮肤文件
  6. +
  7. 点击"保存"按钮应用新皮肤
  8. +
+ +
+

+ 提示: 对于在多人服务器中使用自定义皮肤,您可能需要配置Yggdrasil验证。请参考下一节Yggdrasil教程了解更多信息。 +

+
+
+
+ + {/* 教程项5 */} + + + 5. Yggdrasil验证简介 + + +

+ HITWH.GAMES平台提供了Yggdrasil验证服务,这是一个兼容Minecraft身份验证协议的系统,允许您在第三方服务器上使用您的自定义皮肤。 +

+ +
+

为什么需要Yggdrasil验证?

+
    +
  • 在支持外置登录的Minecraft服务器上显示您的自定义皮肤
  • +
  • 跨服务器同步您的皮肤设置
  • +
  • 增强您的账号安全性
  • +
  • 支持多角色管理功能
  • +
+
+ +
+
+ + + +

+ 我们提供了便捷的拖拽配置功能,可以通过将配置卡片拖放到支持的启动器(如HMCL、PCL)上快速完成Yggdrasil服务配置。详情请查看下一节Yggdrasil教程。 +

+
+
+
+
+ + {/* 教程项6 */} + + + 6. 社区功能 + + +

+ 我们的平台不仅是一个皮肤管理工具,还是一个Minecraft玩家社区。您可以: +

+
    +
  • 分享您创作的皮肤给其他玩家
  • +
  • 查看和下载热门皮肤
  • +
  • 收藏您喜欢的皮肤作品
  • +
  • 与其他玩家交流皮肤制作技巧
  • +
+
+
+
+ + {/* 页面导航按钮 */} +
+ + + + + + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/help/customskinloader/page.tsx b/src/app/help/customskinloader/page.tsx new file mode 100644 index 0000000..c0ad7de --- /dev/null +++ b/src/app/help/customskinloader/page.tsx @@ -0,0 +1,196 @@ +// src/app/help/customskinloader/page.tsx +// CustomSkinLoader教程页面 +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; + +export default function CustomSkinLoaderTutorialPage() { + return ( +
+
+ {/* 页面导航 */} +
+ 首页 + / + 帮助中心 + / + CustomSkinLoader教程 +
+ + {/* 页面标题 */} +
+

CustomSkinLoader教程

+

+ 详细介绍如何配置CustomSkinLoader,在客户端加载自定义皮肤 +

+
+ + {/* 教程内容列表 */} +
+ {/* 教程项1 */} + + + 1. 什么是CustomSkinLoader? + + +

+ CustomSkinLoader是一个Minecraft客户端模组,允许玩家在不依赖Mojang官方服务器的情况下加载自定义皮肤。它支持从多个自定义来源获取皮肤,包括我们的皮肤平台。无论您是使用离线模式还是在不支持皮肤的服务器上玩游戏,CustomSkinLoader都能帮助您显示自己喜欢的皮肤。 +

+
+

主要特性:

+
    +
  • 从多个来源加载皮肤和披风
  • +
  • 支持离线模式和非官方服务器
  • +
  • 自定义加载优先级
  • +
  • 支持多种皮肤服务格式
  • +
+
+
+
+ + {/* 教程项2 */} + + + 2. 安装CustomSkinLoader + + +
+

Forge/Fabric安装方法:

+
    +
  1. 确保您已经安装了Minecraft Forge或Fabric
  2. +
  3. 下载与您Minecraft版本兼容的CustomSkinLoader模组文件(.jar格式)
  4. +
  5. 将模组文件放入Minecraft的mods文件夹中 +
      +
    • Windows: %appdata%\.minecraft\mods
    • +
    • macOS: ~/Library/Application Support/minecraft/mods
    • +
    • Linux: ~/.minecraft/mods
    • +
    +
  6. +
  7. 启动Minecraft,模组会自动生成配置文件
  8. +
+ +

LiteLoader安装方法:

+
    +
  1. 安装LiteLoader
  2. +
  3. 下载CustomSkinLoader的LiteLoader版本
  4. +
  5. 将模组放入liteloader文件夹中
  6. +
  7. 启动Minecraft
  8. +
+
+
+
+ + {/* 教程项3 */} + + + 3. 配置OurSkin服务 + + +
+

+ 安装完成后,您需要配置CustomSkinLoader以使用我们的皮肤服务。以下是详细步骤: +

+
    +
  1. 启动Minecraft一次,让模组生成配置文件
  2. +
  3. 关闭Minecraft,找到CustomSkinLoader的配置文件 +
      +
    • 路径:.minecraft/config/CustomSkinLoader/CustomSkinLoader.json
    • +
    +
  4. +
  5. 使用文本编辑器打开配置文件
  6. +
  7. 将我们的皮肤服务添加到加载列表中
  8. +
+ +
+

配置文件示例:

+
+{`{
+  "enable": true,
+  "loadlist": [
+    {
+      "name": "OurSkin",
+      "type": "CustomSkinAPI",
+      "root": "https://example.com/api/csl/"
+    },
+    {
+      "name": "Mojang",
+      "type": "MojangAPI",
+      "root": "https://api.mojang.com/"
+    }
+  ],
+  "disableHttpCheck": false,
+  "disableSkinLoadLogging": false
+}`}
+                  
+
+
+
+
+ + {/* 教程项4 */} + + + 4. 高级配置与故障排除 + + +
+

自定义加载优先级:

+

+ CustomSkinLoader会按照配置文件中服务的顺序尝试加载皮肤。将我们的服务放在列表顶部,可以优先使用我们平台的皮肤。 +

+ +

常见问题解决:

+
+
+
问题:皮肤未显示
+

+ 解决方法:检查配置文件中的URL是否正确,确认您的网络连接正常,尝试在游戏中按F5刷新皮肤。 +

+
+ +
+
问题:模组加载错误
+

+ 解决方法:确保您下载的模组版本与您的Minecraft版本兼容,检查是否有冲突的其他模组。 +

+
+ +
+
问题:配置文件不生效
+

+ 解决方法:确保配置文件格式正确(JSON格式),检查是否有语法错误,修改后重启Minecraft。 +

+
+
+ +

客户端命令:

+

+ 在游戏中,您可以使用以下命令管理CustomSkinLoader: +

+
    +
  • /customskinloader reload - 重新加载配置
  • +
  • /customskinloader setskin [username] [skinUrl] - 手动设置皮肤
  • +
  • /customskinloader status - 查看加载状态
  • +
+
+
+
+
+ + {/* 页面导航按钮 */} +
+ + + + + + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/help/multilogin/page.tsx b/src/app/help/multilogin/page.tsx new file mode 100644 index 0000000..9b0c89d --- /dev/null +++ b/src/app/help/multilogin/page.tsx @@ -0,0 +1,245 @@ +// src/app/help/multilogin/page.tsx +// MultiLogin教程页面 +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; + +export default function MultiLoginTutorialPage() { + return ( +
+
+ {/* 页面导航 */} +
+ 首页 + / + 帮助中心 + / + MultiLogin教程 +
+ + {/* 页面标题 */} +
+

MultiLogin教程

+

+ 学习如何在服务器中配置MultiLogin来支持多种登录方式共存 +

+
+ + {/* 教程内容列表 */} +
+ {/* 什么是MultiLogin */} + + + 什么是MultiLogin? + + +

+ MultiLogin 是一款服务端插件,功能是让您的服务器支持正版与多种外置登录共存,用来连接两个或多个外置验证服务器下的玩家,让他们能在一起玩。 +

+ +

+ 外置登录给服务器提供了类似正版的管理和登录方式,但对于一个拥有 Minecraft 正版账号的玩家来说,正版登录是更加简单方便的选择。使用MultiLogin,您可以让这两种登录方式在服务器上共存。 +

+ +
+

⚠️ 这并不能代替正版

+

+ 请始终考虑购买正版的 Minecraft。使用正版的 Minecraft 可以为你提供更省心的游玩体验。 +

+
+ +
+

注意:单服务端版本已过时

+

+ MultiLogin 自 0.6.12 版本后不再分发 Bukkit、Bungee 的本体,且在后续暂停维护。我们建议您改用 Velocity 以获得更好的体验。 +

+
+
+
+ + {/* Velocity配置 */} + + + Velocity配置 (Minecraft 1.13+) + + +

+ Modern forwarding 是 Velocity 支持的一种独创格式。它以高效的二进制格式转发所有玩家信息。但是,它仅适用于 Minecraft 1.13 或更高版本。 +

+ +

+ 本案例使用 Velocity + Paper + MultiLogin 作为示例。具体的 Velocity 配置请结合参考 Velocity 文档。 +

+ +
+
+

1. 配置 Velocity 转发

+ +
+
+
对于 Velocity:
+

+ 检查 velocity.toml 文件,确保 online-mode 项的值为 true 👈 +

+
+
{`# velocity.toml
+# Should we authenticate players with Mojang? By default, this is on.
+online-mode = true`}
+
+
+ +
+
对于 Paper 子服:
+
    +
  • 检查子服务器的 server.properties 文件,确保 online-mode 项的值为 false 👈
    + 这会阻止子服务器对玩家进行身份验证,Velocity 将会承担起对玩家进行身份验证的职责。
  • +
  • 检查子服务器的 config/paper-global.yaml 中的 online-mode 项的值为 true 👈
    + 这个值在任何情况下都应该与 velocity.toml 中的 online-mode 项的值保持一致。
  • +
  • 对于 Paper 1.18.2 或更低版本,online-mode 将会位于 settings.velocity-support.online-mode。
  • +
+ +
+
+

server.properties

+
+
online-mode=false
+
+
+ +
+

config/paper-global.yaml

+
+
online-mode: true
+
+
+
+
+
+
+ +
+

2. 配置 MultiLogin

+ +
+
+
对于 Velocity:
+

+ 安装 MultiLogin 插件,并按照以下步骤创建配置文件: +

+
    +
  1. 创建以下两个文件用于支持 Mojang和LittleSkin 登录
  2. +
      +
    • multilogin/services/offical.yml
    • +
    • multilogin/services/littleskin.yml
    • +
    +
+ +
+
+

multilogin/services/offical.yml

+
+
{`# 官方验证服务配置
+# Below, only the most basic configuration is provided.
+# You can refer to the template file to complete all configurations.
+
+
+# Please edit before use.
+id: 0
+
+
+name: 'Official'
+# Don't change it unless you really want to.
+serviceType: OFFICIAL`}
+
+
+ +
+

multilogin/services/littleskin.yml

+
+
{`# LittleSkin验证服务配置
+# Below, only the most basic configuration is provided.
+# You can refer to the template file to complete all configurations.
+
+
+# Please edit before use.
+id: 1
+
+
+name: 'LittleSkin'
+# Don't change it unless you really want to.
+serviceType: BLESSING_SKIN
+# Fill in the API root like \`https://skin.example.com/api/yggdrasil\`  
+apiRoot: 'https://skin.littlelan.cn/api/yggdrasil'`}
+
+
+
+
+ +
+
对于子服务器:
+

+ 仅需在 Velocity 正确配置 MultiLogin 插件即可,无需对子服进行修改。 +

+
+
+
+
+
+
+ + {/* 更多资源 */} + + + 更多资源 + + +

+ 如需了解更多关于 MultiLogin 的配置和使用,请参考以下资源: +

+ +
+
+
+ + {/* 页面导航按钮 */} +
+ + + + + + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/help/page.tsx b/src/app/help/page.tsx new file mode 100644 index 0000000..3fb0590 --- /dev/null +++ b/src/app/help/page.tsx @@ -0,0 +1,103 @@ +// src/app/help/page.tsx +// 帮助页面主入口 +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; + +export default function HelpPage() { + return ( +
+
+ {/* 页面标题 */} +
+

帮助中心

+

+ 查找关于使用我们平台的详细教程和指南 +

+
+ + {/* 教程分类卡片 */} +
+ + + + 基础教程 + + +

+ 了解平台基本功能、皮肤上传与管理、用户账户设置等基础知识。 +

+ +
+
+ + + + + + Yggdrasil教程 + + +

+ 学习如何使用Yggdrasil验证服务,实现皮肤同步与多账号管理。 +

+ +
+
+ + + + + + MultiLogin教程 + + +

+ 掌握MultiLogin插件的配置与使用方法,实现多账户共存。 +

+ +
+
+ + + + + + CustomSkinLoader教程 + + +

+ 详细介绍如何配置CustomSkinLoader,在客户端加载自定义皮肤。 +

+ +
+
+ +
+ + {/* 其他资源 */} +
+

其他资源

+
+

+ 如果您在使用过程中遇到问题,可以: +

+
    +
  • 查看我们的 常见问题解答
  • +
  • 联系我们的 客服支持
  • +
  • 加入我们的 社区论坛 讨论
  • +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/help/yggdrasil/DragConfigCard.tsx b/src/app/help/yggdrasil/DragConfigCard.tsx new file mode 100644 index 0000000..800b952 --- /dev/null +++ b/src/app/help/yggdrasil/DragConfigCard.tsx @@ -0,0 +1,51 @@ +// src/app/help/yggdrasil/DragConfigCard.tsx +'use client'; // 标记为客户端组件以支持交互功能 + +import { useState } from 'react'; + +interface DragConfigCardProps { + authUrl: string; + serviceName: string; +} + +export default function DragConfigCard({ authUrl, serviceName }: DragConfigCardProps) { + const [isDragging, setIsDragging] = useState(false); + + const handleDragStart = (e: React.DragEvent) => { + // 创建适合HMCL/PCL启动器的Yggdrasil配置链接 + const yggdrasilUrl = `authlib-injector:yggdrasil-api?url=${encodeURIComponent(authUrl)}&name=${encodeURIComponent(serviceName)}`; + e.dataTransfer.setData('text/plain', yggdrasilUrl); + setIsDragging(true); + }; + + const handleDragEnd = () => { + setIsDragging(false); + }; + + return ( +
+
+ + + +
+

{serviceName}

+

将此卡片拖到HMCL/PCL启动器以自动配置

+
+
+
+

+ + + + 自动配置Yggdrasil服务器地址和名称 +

+
+
+ ); +} \ No newline at end of file diff --git a/src/app/help/yggdrasil/page.tsx b/src/app/help/yggdrasil/page.tsx new file mode 100644 index 0000000..09d85e4 --- /dev/null +++ b/src/app/help/yggdrasil/page.tsx @@ -0,0 +1,200 @@ +// src/app/help/yggdrasil/page.tsx +// Yggdrasil教程页面 +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; +import DragConfigCard from './DragConfigCard'; + +export default function YggdrasilTutorialPage() { + return ( +
+
+ {/* 页面导航 */} +
+ 首页 + / + 帮助中心 + / + Yggdrasil教程 +
+ + {/* 页面标题 */} +
+

Yggdrasil教程

+

+ 学习如何使用Yggdrasil验证服务,实现皮肤同步与多账号管理 +

+
+ + {/* 教程内容列表 */} +
+ {/* 教程项1 */} + + + 1. 什么是Yggdrasil验证服务? + + +

+ Yggdrasil是Minecraft使用的身份验证服务协议,允许玩家在第三方服务器上验证其身份并使用自定义皮肤。我们的平台提供兼容Yggdrasil协议的身份验证服务,让您可以在支持的服务器上使用您的自定义皮肤。 +

+
+

优势:

+
    +
  • 在非官方服务器上使用自定义皮肤
  • +
  • 跨服务器同步皮肤设置
  • +
  • 增强账号安全性
  • +
+
+
+
+ + {/* 教程项2 */} + + + 2. 客户端配置 + + +

客户端配置说明

+

+ 要在Minecraft客户端中使用HITWH.GAMES皮肤系统,您需要配置游戏启动器以使用我们的Yggdrasil验证服务。下面是几种常用启动器的配置方法。 +

+ +
+

+ 无论使用哪种启动器,您都需要先在HITWH.GAMES网站上注册账号并创建角色,才能在游戏中看到您的自定义皮肤。 +

+
+ +

推荐:快速配置方法

+

+ 支持拖放功能的启动器(如HMCL、PCL)可以直接通过拖放下方卡片到启动器来快速完成配置: +

+ + {/* 拖放配置卡片 */} + + +

操作方法:

+
    +
  1. 打开您的启动器(如HMCL、PCL等)
  2. +
  3. 将上方绿色卡片拖放到启动器窗口中
  4. +
  5. 在弹出的确认窗口中点击"确定"
  6. +
  7. 使用您的HITWH.GAMES账号登录
  8. +
+ +

HMCL启动器配置

+
    +
  1. 打开HMCL启动器
  2. +
  3. 点击右上角的"账户"按钮
  4. +
  5. 点击"添加认证服务器"
  6. +
  7. + 在"服务器地址"中输入:
    + https://skin.littlelan.cn/api/yggdrasil +
  8. +
  9. 点击"下一步",然后输入您在HITWH.GAMES的用户名和密码
  10. +
  11. 完成后点击"登录"即可使用您的HITWH.GAMES账号
  12. +
+ +

PCL启动器配置

+
    +
  1. 打开PCL启动器
  2. +
  3. 点击"设置"
  4. +
  5. 找到"登录设置"部分
  6. +
  7. 选择"外置登录"
  8. +
  9. + 在"认证服务器"中输入:
    + https://skin.littlelan.cn/api/yggdrasil +
  10. +
  11. 点击"保存"
  12. +
  13. 返回主界面,输入您在HITWH.GAMES的用户名和密码进行登录
  14. +
+ +

常见问题

+
+
+

无法看到皮肤?

+

+ 确保您已经在网站上上传了皮肤并分配给了您的角色。某些服务器可能禁用了自定义皮肤功能,这种情况下您的皮肤可能不会显示。 +

+
+
+

登录失败?

+

+ 请检查您的用户名和密码是否正确,以及认证服务器地址是否填写正确。如果问题持续存在,请联系我们的客服支持。 +

+
+
+
+
+ + {/* 教程项3 */} + + + 3. 管理多账号 + + +

+ 我们的Yggdrasil服务支持多账号管理,您可以在一个主账户下创建多个子账户,每个子账户可以有不同的皮肤设置。 +

+
+

创建子账户:

+
    +
  1. 登录您的主账户
  2. +
  3. 进入"账户管理"页面
  4. +
  5. 点击"创建子账户"按钮
  6. +
  7. 设置子账户的用户名和密码
  8. +
  9. 为子账户上传或选择皮肤
  10. +
+
+
+
+ + {/* 教程项4 */} + + + 4. 高级问题与故障排除 + + +
+
+

问题:服务器无法验证我的Yggdrasil账号

+

+ 解决方法:请确认服务器已正确配置Yggdrasil验证功能。服务器管理员需要在服务器配置中添加我们的验证服务器地址。如果您是服务器管理员,请参考服务器的Yggdrasil集成文档。 +

+
+
+

问题:使用代理服务器时无法连接Yggdrasil服务

+

+ 解决方法:请确保您的代理服务器正确配置了HTTPS支持,并且没有阻止与我们Yggdrasil服务器的连接。您可能需要在代理设置中添加例外规则。 +

+
+
+

问题:更换皮肤后游戏中没有立即更新

+

+ 解决方法:皮肤更新可能需要一些时间才能在所有服务器上同步。尝试完全退出并重新启动游戏,或者尝试连接到不同的服务器以刷新皮肤缓存。 +

+
+
+
+
+
+ + {/* 页面导航按钮 */} +
+ + + + + + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3ab3e38..7ae4410 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,6 +3,8 @@ import './globals.css'; import type { Metadata } from 'next'; import { Inter } from 'next/font/google'; import Navbar from '@/components/Navbar'; +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/lib/api/auth'; const inter = Inter({ subsets: ['latin'] }); const grassIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAABCElEQVRYw+2W0Q6DIAxAZ3b/r7y7L0QNlKEFdLfEFyVQ+pVSCgBwB4DfGz4fQJxzGGOYc34d4DkAVVVVdQqgqioiIuccxhgAQFVVMMYg50wAY4w5gDEGKaWl0nPOoZQiAGPM7wGMMXDOwVqLtm2Rc0bXdTDGwDmHnPN/ADHGdF2HEAJijOj7HjFGpJTQ9z36vkeMESkl9H0PYwystYgxLgHGGHDOwTmHtm3hnEPbtmCMgXO+6DkA1lo45+C9R0oJwzAgpQRrLZxz8N4jpYRhGJBSgrUWzjlYa5cAzjkYY2CMQdM0YIyBMQbOOQEAwzCgrmtUVYW6rjEMA4ZhgDEGTdOAMQbOOQEA4ziiqio0TYO2bTGbYRiQc0bTNGiaBm3bYjY5Z4zjiKqqMAwD6rqGtRYpJYzjiJwzrLVomgZt22I2OWeM44i6rjEMA+q6RkoJ4zgi5wxrLZqmwWyGYUDOGXVdYxxH1HWNlBLGcUTOGdZaNE2D2QzDgJwz6rrGOI6o6xopJYzjiJwzrLVomgazGYYBOWfUdY1xHFHXNVJKGMfxfwB1XWO2OWeM44i6rjGOI+q6RkoJ4zgi5wxrLZqmwWyGYUDOGXVdYxxH1HWNlBLGcUTOGdZaNE2D2QzDgJwz6rrGOI6o6xopJYzj+AAQY4QQQgghhBBCCCGEkHv7AQm0VqDdH3i9AAAAAElFTkSuQmCC'; @@ -15,11 +17,14 @@ export const metadata: Metadata = { }, }; -export default function RootLayout({ +export default async function RootLayout({ children, }: { children: React.ReactNode; }) { + // 在服务器端获取会话状态 + const session = await getServerSession(authOptions); + return ( @@ -28,9 +33,14 @@ export default function RootLayout({ rel="stylesheet" /> - - -
{children}
+ + +
{children}
+
+
+ © 2024 我的世界皮肤库 - 为Minecraft玩家打造的皮肤分享平台 +
+
); diff --git a/src/app/page.tsx b/src/app/page.tsx index 678d84e..826676f 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,54 +1,153 @@ // src/app/page.tsx +//这个是介绍首页页面 +'use client'; import Link from 'next/link'; import Image from 'next/image'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { useEffect, useRef, useState } from 'react'; +import Canvas2DSkinPreview from '@/components/skins/Canvas2DSkinPreview'; +import ServerInfoBubble, { ServerInfoBubbleStyles } from '@/components/ServerInfoBubble'; + +// 渲染样式 +function BubbleStyles() { + return ; +} + +// 功能卡片组件 +function FeatureCard({ title, description, icon }: { title: string; description: string; icon: string }) { + return ( + + +
{icon}
+ {title} +
+ +

{description}

+
+
+ ); +} export default function HomePage() { - return ( -
- {/* 导航栏 */} - + const titleRef = useRef(null); + const descRef = useRef(null); + + // 打字机效果状态 + const [displayText, setDisplayText] = useState(''); + const [displayHighlightText, setDisplayHighlightText] = useState(''); + const [cursorVisible, setCursorVisible] = useState(true); + const fullText = '创建、分享和管理你的'; + const highlightText = 'Minecraft 皮肤'; + const [isTypingMainText, setIsTypingMainText] = useState(true); + const [typingComplete, setTypingComplete] = useState(false); - {/* 区域 */} -
-
-

- 创建、分享和管理你的 - Minecraft 皮肤 -

-

- 上传你的自定义皮肤,在3D预览中查看效果,并与HITWH的大家分享你的创作。简单、快捷! -

-
- -
-
-
-
-

皮肤预览区域

-

这里将展示3D皮肤预览

+ {/* 装饰性背景元素 */} +
+
+ +
+ {/* 背景装饰 */} +
+
+ +
+
+ +
+

皮肤预览区域

+

上传后立即查看你的皮肤效果

@@ -56,16 +155,34 @@ export default function HomePage() {
{/* 功能特性区域 */} -
+
-

+ {/* 强调区域 */} +
+
+

+ 探索无限可能的皮肤世界 +

+

+ 我们提供安全可靠的皮肤分享平台,让你的创意在Minecraft世界中脱颖而出 +

+
+
+ +

为什么选择我们的皮肤库?

-
+
@@ -83,19 +200,23 @@ export default function HomePage() {
{/* 行动号召区域 */} -
-
-

- 加入HITWH300众(误 +
+ {/* 装饰性背景元素 */} +
+
+ +
+

+ 加入HITWH皮肤社区

-

- 免费注册账号,开始创建和分享你的独特皮肤 +

+ 免费注册账号,开始创建和分享你的独特皮肤,与HITWH的大家一起探索无限可能

-
- -
@@ -103,72 +224,54 @@ export default function HomePage() {
{/* 页脚 */} -
+
-
-
-
- HITWH皮肤库 +
+
+ Logo + HITWH皮肤库
-

创建、分享和管理你的 Minecraft 皮肤

+

创建、分享和管理你的 Minecraft 皮肤

-

导航

-
    -
  • 首页
  • -
  • 皮肤库
  • -
  • 上传皮肤
  • +

    导航

    +
      +
    • 首页
    • +
    • 皮肤库
    • +
    • 上传皮肤
    • +
    • 角色中心
-

账号

-
    -
  • 登录
  • -
  • 注册
  • -
  • 我的皮肤
  • +

    账号

    +
      +
    • 登录
    • +
    • 注册
    • +
    • 我的皮肤
    • +
    • 个人主页
-

支持

-
    -
  • 帮助中心
  • -
  • 联系我们
  • -
  • 服务条款
  • +

    支持

    +
      +
    • 帮助中心
    • +
    • 联系我们
    • +
    • 服务条款
-
+

© {new Date().getFullYear()} HITWHGAMES. 保留所有权利.

); -} - -// 功能卡片组件 -function FeatureCard({ title, description, icon }: { title: string; description: string; icon: string }) { - return ( - - -
{icon}
- {title} -
- -

{description}

-
- - - -
- ); } \ No newline at end of file diff --git a/src/app/skins/[id]/page.tsx b/src/app/skins/[id]/page.tsx index 319f2b2..4bee5a3 100644 --- a/src/app/skins/[id]/page.tsx +++ b/src/app/skins/[id]/page.tsx @@ -1,6 +1,8 @@ import { notFound } from 'next/navigation'; -import SkinViewer3D from '@/components/skins/SkinViewer3D'; import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import Link from 'next/link'; +import Canvas2DSkinPreview from '@/components/skins/Canvas2DSkinPreview'; interface SkinDetailProps { params: { id: string }; @@ -14,33 +16,111 @@ export default function SkinDetail({ params }: SkinDetailProps) { description: '这是你的Minecraft角色皮肤', createdAt: '2023-06-01', downloadUrl: `/api/skins/${params.id}/download`, + author: '创作者名称', + tags: ['游戏', '角色', '自定义'] }; if (!skinData) return notFound(); return ( -
-

{skinData.name}

- -
-
- -
- -
-

{skinData.description}

-

创建于: {skinData.createdAt}

- -
- - +
+ {/* 背景装饰元素 - 渐变模糊效果 */} +
+
+
+
+
+ +
+
+
+

皮肤详情

+

查看、下载和分享这个精彩的Minecraft皮肤

+ +
+ +
+ {/* 皮肤预览卡片 */} + +
+ + 皮肤预览 + + +
+
+ +
+
+
+ + {/* 皮肤信息卡片 */} + +
+ + {skinData.name} + + +
+

描述

+

{skinData.description}

+
+ +
+

创作者

+

{skinData.author}

+
+ +
+

创建日期

+

{skinData.createdAt}

+
+ +
+

标签

+
+ {skinData.tags.map((tag, index) => ( + + {tag} + + ))} +
+
+
+ +
+ + +
+
+
+ + {/* 页脚 */} +
+
+

我的世界皮肤库 - 你的Minecraft皮肤分享平台

+

© {new Date().getFullYear()} 版权所有

+
+
); } \ No newline at end of file diff --git a/src/app/skins/page.tsx b/src/app/skins/page.tsx new file mode 100644 index 0000000..d9b36fd --- /dev/null +++ b/src/app/skins/page.tsx @@ -0,0 +1,177 @@ +'use client'; + +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardFooter } from '@/components/ui/card'; +import { Input } from '@/components/ui/input'; +import Link from 'next/link'; +import Canvas2DSkinPreview from '@/components/skins/Canvas2DSkinPreview'; + +export default function SkinsPage() { + // 模拟皮肤数据,添加type字段区分皮肤类型:steve、alex或cape + const allSkins = [ + { id: '1', name: '经典Steve皮肤', author: 'Mojang', createdAt: '2023-05-01', tags: ['经典', '默认'], imageUrl: '/test-skin.png', type: 'steve' }, + { id: '2', name: '史诗战士Steve', author: '玩家123', createdAt: '2023-06-15', tags: ['战士', '冒险'], imageUrl: '/test-skin.png', type: 'steve' }, + { id: '3', name: '魔法师学徒Steve', author: '创意大师', createdAt: '2023-07-20', tags: ['魔法', '幻想'], imageUrl: '/test-skin.png', type: 'steve' }, + { id: '4', name: '经典Alex皮肤', author: 'Mojang', createdAt: '2023-08-10', tags: ['经典', '默认'], imageUrl: '/test-skin2.png', type: 'alex' }, + { id: '5', name: '太空探险Alex', author: '星际旅行者', createdAt: '2023-09-05', tags: ['科技', '太空'], imageUrl: '/test-skin2.png', type: 'alex' }, + { id: '6', name: '像素艺术家Alex', author: '方块创作者', createdAt: '2023-10-12', tags: ['艺术', '现代'], imageUrl: '/test-skin2.png', type: 'alex' }, + { id: '7', name: '英雄披风', author: '披风大师', createdAt: '2023-11-01', tags: ['英雄', '经典'], imageUrl: '/test-skin3.png', type: 'cape' }, + { id: '8', name: '龙纹披风', author: '东方设计师', createdAt: '2023-12-15', tags: ['龙', '东方'], imageUrl: '/test-skin3.png', type: 'cape' }, + ]; + + // 过滤和搜索状态 + const [searchTerm, setSearchTerm] = useState(''); + const [selectedCategory, setSelectedCategory] = useState('all'); + + // 过滤皮肤 - 按类型(steve/alex/cape)分类 + const filteredSkins = allSkins.filter(skin => { + const matchesSearch = skin.name.toLowerCase().includes(searchTerm.toLowerCase()) || + skin.author.toLowerCase().includes(searchTerm.toLowerCase()); + const matchesCategory = selectedCategory === 'all' || + (selectedCategory === 'Steve' && skin.type === 'steve') || + (selectedCategory === 'Alex' && skin.type === 'alex') || + (selectedCategory === '披风' && skin.type === 'cape'); + return matchesSearch && matchesCategory; + }); + + return ( +
+ {/* 马赛克砖背景样式 */} +
+ +
+
+
+

探索无限可能的皮肤世界

+

发现、分享和下载最酷的Minecraft皮肤

+
+ +
+ + {/* 搜索和筛选区 */} + + +
+
+ setSearchTerm(e.target.value)} + className="pl-10 border-emerald-200 dark:border-gray-700 rounded-xl focus-visible:ring-emerald-500 dark:focus-visible:ring-emerald-400" + /> + + + +
+
+ + + + +
+
+
+
+ + {/* 皮肤列表 */} + {filteredSkins.length > 0 ? ( +
+ {filteredSkins.map((skin) => ( + + +
+
+ +
+ +
+
+

{skin.name}

+ + {skin.type === 'steve' ? 'Steve' : skin.type === 'alex' ? 'Alex' : '披风'} + +
+

作者: {skin.author}

+
+ {skin.tags.map((tag, index) => ( +
+ {tag} +
+ ))} +
+
+
+ +
+ 上传于 {skin.createdAt} + 查看详情 → +
+
+ +
+ ))} +
+ ) : ( + + +
🔍
+

未找到皮肤

+

+ 没有找到匹配你搜索条件的皮肤,请尝试使用其他关键词或筛选条件 +

+ +
+
+ )} +
+
+ ); +} \ No newline at end of file diff --git a/src/app/skins/preview/page.tsx b/src/app/skins/preview/page.tsx new file mode 100644 index 0000000..ec0a965 --- /dev/null +++ b/src/app/skins/preview/page.tsx @@ -0,0 +1,151 @@ +'use client'; + +import { useEffect, useRef, useState } from 'react'; + +// 全局样式以确保像素化渲染 +const styles = ` + .image-rendering-pixelated { + image-rendering: pixelated; + image-rendering: -moz-crisp-edges; + image-rendering: crisp-edges; + width: 300px; + height: 300px; + } +`; + +// 创建一个页面,展示Minecraft皮肤头部的六个面 +const MinecraftSkinPreview: React.FC = () => { + const canvasRef = useRef(null); + const [face, setFace] = useState('front'); // 默认为正面 + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + // 设置canvas尺寸 + const canvasSize = 300; + canvas.width = canvasSize; + canvas.height = canvasSize; + + // 设置背景 + ctx.fillStyle = '#f0f0f0'; + ctx.fillRect(0, 0, canvasSize, canvasSize); + + // 创建图像对象加载皮肤 + const skinImage = new Image(); + skinImage.src = '/test-skin.png'; + skinImage.crossOrigin = 'anonymous'; // 确保可以跨域访问 + + skinImage.onload = () => { + // 皮肤尺寸固定为64x64 + const skinWidth = 64; + const skinHeight = 64; + + // 确保使用64x64的坐标系统 + let sourceX = 0; + let sourceY = 0; + const sourceSize = 8; // 每个面的大小是8x8像素 + + // 根据选择的面设置源坐标 - 严格按照用户提供的UV映射表 + switch (face) { + case 'front': + sourceX = 8; // X: 8-16 + sourceY = 8; // Y: 8-16 + break; + case 'back': + sourceX = 16; // X: 16-24 + sourceY = 8; // Y: 8-16 + break; + case 'top': + sourceX = 8; // X: 8-16 + sourceY = 0; // Y: 0-8 + break; + case 'bottom': + sourceX = 0; // X: 0-8 + sourceY = 16; // Y: 16-24 + break; + case 'right': + sourceX = 0; // X: 0-8 + sourceY = 8; // Y: 8-16 + break; + case 'left': + sourceX = 24; // X: 24-32 + sourceY = 8; // Y: 8-16 + break; + default: + sourceX = 8; + sourceY = 8; + } + + // 计算缩放比例,将8x8像素放大到canvas的大小 + const scale = canvasSize / sourceSize; + + // 设置图像渲染质量为像素化 + ctx.imageSmoothingEnabled = false; + ctx.imageSmoothingQuality = 'low'; + + // 清除之前的内容 + ctx.fillStyle = '#f0f0f0'; + ctx.fillRect(0, 0, canvasSize, canvasSize); + + // 直接绘制皮肤的头部部分 + ctx.drawImage( + skinImage, + sourceX, sourceY, sourceSize, sourceSize, // 源区域 + 0, 0, canvasSize, canvasSize // 目标区域 + ); + }; + + // 错误处理 + skinImage.onerror = () => { + console.error('无法加载皮肤文件'); + ctx.fillStyle = '#ff0000'; + ctx.font = '16px Arial'; + ctx.textAlign = 'center'; + ctx.fillText('无法加载皮肤文件', canvasSize / 2, canvasSize / 2); + }; + }, [face]); + + return ( + <> + +
+

Minecraft 皮肤头部预览

+ +
+ +
+ {['front', 'back', 'top', 'bottom', 'right', 'left'].map((faceType) => ( + + ))} +
+
+ +
+
+ +
+
+
+ + ); +}; + +export default MinecraftSkinPreview; \ No newline at end of file diff --git a/src/app/skins/upload/page.tsx b/src/app/skins/upload/page.tsx index 7eb74ce..efcaa5e 100644 --- a/src/app/skins/upload/page.tsx +++ b/src/app/skins/upload/page.tsx @@ -1,12 +1,18 @@ 'use client'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { useDropzone } from 'react-dropzone'; -import SkinUploader from '@/components/skins/SkinUploader'; import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'; +import { Label } from '@/components/ui/label'; +import { UploadIcon } from 'lucide-react'; +import Link from 'next/link'; +import Canvas2DSkinPreview from '@/components/skins/Canvas2DSkinPreview'; export default function SkinUploadPage() { const [skinFile, setSkinFile] = useState(null); const [isUploading, setIsUploading] = useState(false); + const [skinFileUrl, setSkinFileUrl] = useState(''); + const [skinName, setSkinName] = useState(''); const { getRootProps, getInputProps } = useDropzone({ accept: { @@ -18,6 +24,19 @@ export default function SkinUploadPage() { } }); + // 当文件改变时,创建预览URL + useEffect(() => { + if (skinFile) { + const objectUrl = URL.createObjectURL(skinFile); + setSkinFileUrl(objectUrl); + + // 组件卸载时清理ObjectURL + return () => URL.revokeObjectURL(objectUrl); + } else { + setSkinFileUrl(''); + } + }, [skinFile]); + const handleUpload = async () => { if (!skinFile) return; @@ -38,36 +57,90 @@ export default function SkinUploadPage() { }; return ( -
-

上传新皮肤

- -
- -

拖放PNG格式的皮肤文件到这里,或点击选择文件

-

标准皮肤尺寸应为64x32像素

+
+ {/* 背景装饰元素 - 渐变模糊效果 */} +
+
+
+
- - {skinFile && ( -
-

预览

- -
-

文件名: {skinFile.name}

-

大小: {(skinFile.size / 1024).toFixed(2)} KB

+ +
+
+
+

上传新皮肤

+

创建并分享你的独特皮肤设计

+
- )} + + +
+ + + 皮肤上传 + 上传PNG格式的皮肤文件并查看预览 + + +
+
+ +
+ +
+ +
+

拖放PNG格式的皮肤文件到这里,或点击选择文件

+

标准皮肤尺寸应为64x32像素

+
+
+
+
- +
+

预览

+
+
+ +
+
+ {skinFile && ( +
+

文件名: {skinFile.name}

+

大小: {(skinFile.size / 1024).toFixed(2)} KB

+
+ )} +
+
+
+ + + +
+
+ + {/* 页脚 */} +
+
+

我的世界皮肤库 - 你的Minecraft皮肤分享平台

+

© {new Date().getFullYear()} 版权所有

+
+
); } \ No newline at end of file diff --git a/src/app/template.tsx b/src/app/template.tsx index 85149b3..f8032d3 100644 --- a/src/app/template.tsx +++ b/src/app/template.tsx @@ -1,12 +1,18 @@ // src/app/template.tsx 'use client'; -import { useEffect } from 'react'; +import { useEffect, Suspense } from 'react'; export default function Template({ children }: { children: React.ReactNode }) { useEffect(() => { // 模板特定的效果 }, []); - return <>{children}; + return ( +
+ + {children} + +
+ ); } \ No newline at end of file diff --git a/src/app/user-home/page.tsx b/src/app/user-home/page.tsx new file mode 100644 index 0000000..f4b2ea8 --- /dev/null +++ b/src/app/user-home/page.tsx @@ -0,0 +1,239 @@ +// src/app/user-home/page.tsx +// 用户日常使用的主页面 +'use client'; +import { useEffect, useState } from 'react'; +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { getSession } from 'next-auth/react'; + +export default function UserHome() { + const [session, setSession] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchSession = async () => { + try { + const sessionData = await getSession(); + if (!sessionData) { + window.location.href = '/'; + } + setSession(sessionData); + } catch (error) { + console.error('获取会话失败:', error); + window.location.href = '/'; + } finally { + setLoading(false); + } + }; + + fetchSession(); + }, []); + + if (loading) { + return
加载中...
; + } + + // 安全地获取用户信息 + const userName = session?.user?.name || '玩家'; + + + + return ( +
+ {/* 背景装饰元素 - 渐变模糊效果 */} +
+
+
+
+
+ + {/* 主要内容区域 */} +
+ {/* 欢迎区域 */} +
+ +
+ +
+
+ {userName?.[0] || 'U'} +
+
+

+ 欢迎回来,{userName}! +

+

这里是你的个人主页,继续探索和创建你的Minecraft皮肤吧

+
+
+
+
+
+ + {/* 快速操作区域 */} +
+ {/* 上传皮肤卡片 */} + +
+
+
+
📤
+

上传皮肤

+

分享你的创作

+
+
+ + + {/* 我的皮肤卡片 */} + +
+
+
+
👕
+

我的皮肤

+

管理我的收藏

+
+
+ + + {/* 外置登录卡片 */} +
{ + // 为拖拽提供Yggdrasil认证信息 + const yggdrasilData = JSON.stringify({ + name: "外置登录", + uuid: session?.user?.id || "user_uuid", + accessToken: "demo_token", + validateUrl: "http://localhost:3000/api/auth/validate", + refreshUrl: "http://localhost:3000/api/auth/refresh", + invalidateUrl: "http://localhost:3000/api/auth/invalidate", + userInfoUrl: "http://localhost:3000/api/auth/user", + authUrl: "http://localhost:3000/api/auth/authenticate" + }); + e.dataTransfer.setData('text/plain', yggdrasilData); + e.dataTransfer.effectAllowed = 'copy'; + }}> +
+
+
+
🔐
+

外置登录

+

拖拽到启动器实现认证

+
+ {/* 拖拽提示 */} +
+ 可拖拽 ↕️ +
+
+
+ + {/* 角色中心卡片 */} + +
+
+
+
👤
+

角色中心

+

管理游戏角色

+
+
+ +
+ + {/* 问题帮助模块 */} +
+ +
+ + 使用教程 + + +
+ +
+

基础教程

+

了解平台基本功能、皮肤上传与管理、用户账户设置等基础知识。

+
+ 适合新手入门 + +
+
+ + + +
+

Yggdrasil教程

+

学习如何使用Yggdrasil验证服务,实现皮肤同步与多账号管理。

+
+ 进阶功能 + +
+
+ + + +
+

MultiLogin教程

+

掌握MultiLogin插件的配置与使用方法,实现多账户共存。

+
+ 服务器管理员 + +
+
+ + + +
+

CustomSkinLoader教程

+

详细介绍如何配置CustomSkinLoader,在客户端加载自定义皮肤。

+
+ 实用工具 + +
+
+ +
+
+
+
+ + {/* 统计信息区域 */} +
+ +
+ + 皮肤库统计 + + +
+
+
1,256
+
总皮肤数
+
+
+
342
+
活跃用户
+
+
+
12,890
+
下载次数
+
+
+
89
+
今日新增
+
+
+
+
+
+
+ + {/* 页脚 */} +
+
+

我的世界皮肤库 - 你的Minecraft皮肤分享平台

+

© {new Date().getFullYear()} 版权所有

+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 660e03d..94073dc 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,59 +1,275 @@ // src/components/Navbar.tsx +'use client'; import Link from 'next/link'; import { Button } from '@/components/ui/button'; -import { getServerSession } from 'next-auth'; -import { authOptions } from '@/lib/api/auth'; -import { signOut } from '@/lib/api/auth'; +import { Session } from 'next-auth'; +import { serverSignOut } from '@/lib/api/actions'; +import { useState, FunctionComponent } from 'react'; +import { Menu, X } from 'lucide-react'; +import Image from 'next/image'; -export default async function Navbar() { - const session = await getServerSession(authOptions); +// 定义组件Props接口 +interface NavbarProps { + session: Session | null; +} - // 处理客户端退出函数 - const handleSignOut = async () => { - 'use server'; - await signOut(); - }; +// 移动端菜单组件 +interface MobileMenuProps { + session: Session | null; + userName: string; + serverSignOut: () => void; + handleProtectedLinkClick: () => void; +} + +const MobileMenu: FunctionComponent = ({ session, userName, serverSignOut, handleProtectedLinkClick }) => { + const [isOpen, setIsOpen] = useState(false); + + return ( +
+ + + {/* 移动端下拉菜单 */} + {isOpen && ( +
+
+ {session ? ( + <> + setIsOpen(false)} + > + 用户主页 + + setIsOpen(false)} + > + 皮肤库 + + setIsOpen(false)} + > + 角色中心 + + + ) : ( + <> + + + + + )} + + {session && ( +
+

你好, {userName}

+
+ +
+
+ )} + + {!session && ( +
+ + +
+ )} +
+
+ )} +
+ ); +}; + +// 移动Navbar组件到客户端 +const Navbar: FunctionComponent = ({ session }) => { + + // 添加日志以便调试 + console.log('Navbar会话状态:', session ? '已登录' : '未登录'); + console.log('会话用户:', session?.user); // 安全地获取用户名 const userName = session?.user?.name || '玩家'; + + // 控制登录提示弹窗的状态 + const [showLoginPrompt, setShowLoginPrompt] = useState(false); + + // 处理需要登录的链接点击 + const handleProtectedLinkClick = () => { + if (!session) { + setShowLoginPrompt(true); + } + }; return ( - + )} + ); -} \ No newline at end of file +}; + +// 直接导出客户端Navbar组件,会话获取将由上层组件处理 +export default Navbar; \ No newline at end of file diff --git a/src/components/ServerInfoBubble.tsx b/src/components/ServerInfoBubble.tsx new file mode 100644 index 0000000..6995ec8 --- /dev/null +++ b/src/components/ServerInfoBubble.tsx @@ -0,0 +1,259 @@ +// src/components/ServerInfoBubble.tsx +'use client'; +import { useState, useEffect, useRef } from 'react'; +import Image from 'next/image'; + +interface ServerInfoBubbleProps { + zIndex?: number; + dragDistanceLimit?: number; +} + +export default function ServerInfoBubble({ + zIndex = 1000, + dragDistanceLimit = 200 +}: ServerInfoBubbleProps) { + // 服务器信息,这些将轮流显示 + const serverInfo = { + players: { + title: '在线玩家', + content: '服务器内有 {playerCount} 名玩家正在游玩' + }, + address: { + title: '服务器地址', + content: 'mc.hitwh.edu.cn:25565' + }, + description: { + title: '服务器介绍', + content: '欢迎来到HITWH Minecraft服务器,这是一个充满创造力的社区' + } + }; + + const [currentInfo, setCurrentInfo] = useState<'players' | 'address' | 'description'>('players'); + const [displayText, setDisplayText] = useState(''); + const [cursorVisible, setCursorVisible] = useState(true); + const [isTyping, setIsTyping] = useState(true); + const [playerCount, setPlayerCount] = useState(0); // 模拟玩家数量 + + // 拖拽相关状态 + const [dragPosition, setDragPosition] = useState({ x: 0, y: 0 }); + const [isDragging, setIsDragging] = useState(false); + const initialDragPos = useRef({ x: 0, y: 0 }); + const bubbleRef = useRef(null); + + // 随机生成玩家数量(模拟从服务器获取) + useEffect(() => { + const updatePlayerCount = () => { + setPlayerCount(Math.floor(Math.random() * 50) + 1); // 1-50随机玩家数 + }; + + updatePlayerCount(); + const countInterval = setInterval(updatePlayerCount, 30000); // 每30秒更新一次 + + return () => clearInterval(countInterval); + }, []); + + // 打字机效果 + useEffect(() => { + if (!isTyping) return; + + const fullText = serverInfo[currentInfo].content.replace('{playerCount}', playerCount.toString()); + let index = 0; + + const typeInterval = setInterval(() => { + if (index < fullText.length) { + setDisplayText(fullText.slice(0, index + 1)); + index++; + } else { + clearInterval(typeInterval); + setIsTyping(false); + } + }, 50); // 打字速度 + + return () => clearInterval(typeInterval); + }, [currentInfo, isTyping, playerCount]); + + // 光标闪烁效果 + useEffect(() => { + const cursorInterval = setInterval(() => { + setCursorVisible(prev => !prev); + }, 530); + + return () => clearInterval(cursorInterval); + }, []); + + // 轮流显示不同信息 + useEffect(() => { + const infoInterval = setInterval(() => { + setIsTyping(true); + setDisplayText(''); + + // 切换到下一个信息 + if (currentInfo === 'players') { + setCurrentInfo('address'); + } else if (currentInfo === 'address') { + setCurrentInfo('description'); + } else { + setCurrentInfo('players'); + } + }, 8000); // 每个信息显示8秒 + + return () => clearInterval(infoInterval); + }, [currentInfo]); + + // 动态计算右下角位置 + const [screenPosition, setScreenPosition] = useState({ bottom: 16, right: 16 }); + + // 监听窗口大小变化,重新计算位置 + useEffect(() => { + const updatePosition = () => { + setScreenPosition({ bottom: 16, right: 16 }); + }; + + // 初始计算位置 + updatePosition(); + + // 监听窗口大小变化 + window.addEventListener('resize', updatePosition); + + return () => { + window.removeEventListener('resize', updatePosition); + }; + }, []); + + // 开始拖拽 + const startDragging = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + + setIsDragging(true); + initialDragPos.current = { + x: e.clientX - dragPosition.x, + y: e.clientY - dragPosition.y + }; + + // 暂停动画并改变光标 + if (bubbleRef.current) { + bubbleRef.current.style.animationPlayState = 'paused'; + bubbleRef.current.style.cursor = 'grabbing'; + } + }; + + // 结束拖拽并返回原位 + const endDragging = () => { + if (!isDragging) return; + + setIsDragging(false); + + // 恢复动画和光标 + if (bubbleRef.current) { + bubbleRef.current.style.animationPlayState = 'running'; + bubbleRef.current.style.cursor = 'move'; + } + + // 使用CSS动画返回原位 + if (bubbleRef.current) { + bubbleRef.current.style.transition = 'transform 0.8s cubic-bezier(0.34, 1.56, 0.64, 1)'; + bubbleRef.current.style.transform = 'translate(0, 0)'; + + // 更新状态并清除过渡效果 + setTimeout(() => { + setDragPosition({ x: 0, y: 0 }); + if (bubbleRef.current) { + bubbleRef.current.style.transition = ''; + } + }, 800); + } + }; + + // 处理拖拽移动 + const handleMouseMove = (e: MouseEvent) => { + if (!isDragging) return; + + const newX = e.clientX - initialDragPos.current.x; + const newY = e.clientY - initialDragPos.current.y; + + // 限制拖拽范围 + const limitedX = Math.max(-dragDistanceLimit, Math.min(dragDistanceLimit, newX)); + const limitedY = Math.max(-dragDistanceLimit, Math.min(dragDistanceLimit, newY)); + + // 直接操作DOM进行拖动,避免频繁的状态更新 + if (bubbleRef.current) { + bubbleRef.current.style.transform = `translate(${limitedX}px, ${limitedY}px)`; + } + + // 同时更新状态以便组件状态一致 + setDragPosition({ x: limitedX, y: limitedY }); + }; + + // 设置全局鼠标事件监听 + useEffect(() => { + if (isDragging) { + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseup', endDragging); + document.addEventListener('mouseleave', endDragging); + + return () => { + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', endDragging); + document.removeEventListener('mouseleave', endDragging); + }; + } + }, [isDragging, dragDistanceLimit]); + + return ( +
+ {/* 服务器头像 */} +
+
+
+
+ 服 +
+
+
+ + {/* 对话框气泡 */} +
+ {/* 气泡尾部 */} +
+ + {/* 气泡内容 */} +
+ {serverInfo[currentInfo].title} +
+
+ {displayText} + +
+
+
+ ); +} + +// 添加浮动动画样式 +export const ServerInfoBubbleStyles = ` +@keyframes float { + 0%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-8px); + } +} + +.animate-float { + animation: float 6s ease-in-out infinite; +}`; \ No newline at end of file diff --git a/src/components/auth/AuthForm.tsx b/src/components/auth/AuthForm.tsx index 245914f..81dde0c 100644 --- a/src/components/auth/AuthForm.tsx +++ b/src/components/auth/AuthForm.tsx @@ -1,9 +1,11 @@ // src/components/auth/AuthForm.tsx +// 认证表单组件 - 包含登录和注册功能,同时提供测试账号信息 'use client'; import { useState } from 'react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; +import { signIn } from 'next-auth/react'; interface AuthFormProps { type: 'login' | 'register'; @@ -20,12 +22,30 @@ export default function AuthForm({ type }: AuthFormProps) { e.preventDefault(); setIsLoading(true); - // 这里应该调用认证API - console.log('提交表单', { username, password, email, minecraftUsername }); - - // 模拟API请求 - await new Promise(resolve => setTimeout(resolve, 1000)); - setIsLoading(false); + try { + if (type === 'login') { + // 使用next-auth的signIn功能进行登录 + const result = await signIn('credentials', { + username: username || email, // 使用username或email作为用户名字段 + email: email, // 传递email字段 + password, + redirect: true, // 登录成功后自动重定向 + callbackUrl: '/user-home' // 指定重定向目标 + }); + + console.log('登录结果:', result); + } else { + // 注册逻辑可以在这里实现 + console.log('注册信息', { username, password, email, minecraftUsername }); + // 模拟注册请求 + await new Promise(resolve => setTimeout(resolve, 1000)); + } + } catch (error) { + console.error('认证失败:', error); + // 这里可以添加错误处理逻辑 + } finally { + setIsLoading(false); + } }; return ( @@ -35,11 +55,24 @@ export default function AuthForm({ type }: AuthFormProps) { setUsername(e.target.value)} + placeholder={type === 'login' ? "用户名或邮箱" : "输入用户名"} />
+ + {type === 'login' && ( +
+ + setEmail(e.target.value)} + placeholder="或直接输入邮箱登录" + /> +
+ )}
{isLoading ? '处理中...' : type === 'login' ? '登录' : '注册'} + + {type === 'login' && ( +
+
测试账号(开发环境)
+
用户名: test
+
密码: test
+
+ )} ); } \ No newline at end of file diff --git a/src/components/skins/Canvas2DSkinPreview.tsx b/src/components/skins/Canvas2DSkinPreview.tsx new file mode 100644 index 0000000..248faad --- /dev/null +++ b/src/components/skins/Canvas2DSkinPreview.tsx @@ -0,0 +1,81 @@ +// Canvas 2D皮肤预览组件 - 只渲染头部正脸 +'use client'; +import React, { useRef, useEffect } from 'react'; + +interface Canvas2DSkinPreviewProps { + skinUrl: string; + size?: number; + className?: string; +} + +const Canvas2DSkinPreview: React.FC = ({ + skinUrl, + size = 128, + className = '' +}) => { + const canvasRef = useRef(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + // 设置canvas尺寸 + canvas.width = size; + canvas.height = size; + + // 清空canvas + ctx.clearRect(0, 0, size, size); + + // 创建图像对象加载皮肤 + const skinImage = new Image(); + skinImage.src = skinUrl; + skinImage.crossOrigin = 'anonymous'; // 确保可以跨域访问 + + skinImage.onload = () => { + // 确保使用像素化渲染 + ctx.imageSmoothingEnabled = false; + + // 皮肤头部正脸坐标(64x64皮肤) + const sourceX = 8; + const sourceY = 8; + const sourceSize = 8; + + // 计算缩放比例 + const scale = size / sourceSize; + + // 绘制皮肤头部正脸(像素化渲染) + ctx.drawImage( + skinImage, + sourceX, // 源图像的x坐标 + sourceY, // 源图像的y坐标 + sourceSize, // 源图像的宽度 + sourceSize, // 源图像的高度 + 0, // 目标canvas的x坐标 + 0, // 目标canvas的y坐标 + size, // 目标canvas的宽度 + size // 目标canvas的高度 + ); + }; + + // 错误处理 + skinImage.onerror = () => { + console.error('无法加载皮肤图片:', skinUrl); + // 绘制一个默认的灰色方块 + ctx.fillStyle = '#cccccc'; + ctx.fillRect(0, 0, size, size); + }; + }, [skinUrl, size]); + + return ( + + ); +}; + +export default Canvas2DSkinPreview; \ No newline at end of file diff --git a/src/components/skins/SkinGrid.tsx b/src/components/skins/SkinGrid.tsx index 56d1bb3..da04865 100644 --- a/src/components/skins/SkinGrid.tsx +++ b/src/components/skins/SkinGrid.tsx @@ -1,6 +1,7 @@ // src/components/skins/SkinGrid.tsx import Link from 'next/link'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import Canvas2DSkinPreview from './Canvas2DSkinPreview'; interface Skin { id: string; @@ -10,19 +11,34 @@ interface Skin { export default function SkinGrid({ skins }: { skins: Skin[] }) { if (skins.length === 0) { - return

你还没有上传任何皮肤

; + return null; // 空状态由父组件处理 } return ( -
+
{skins.map((skin) => ( - - - - {skin.name} + + +
+ +
+
+ +
+ + + {skin.name} - -

创建于: {skin.createdAt}

+ +

上传于: {skin.createdAt}

diff --git a/src/components/skins/SkinUploader.tsx b/src/components/skins/SkinUploader.tsx deleted file mode 100644 index c87044e..0000000 --- a/src/components/skins/SkinUploader.tsx +++ /dev/null @@ -1,18 +0,0 @@ -// src/components/skins/SkinUploader.tsx -import Image from 'next/image'; - -export default function SkinUploader({ file }: { file: File }) { - const imageUrl = URL.createObjectURL(file); - - return ( -
- 皮肤预览 -
- ); -} \ No newline at end of file diff --git a/src/components/skins/SkinViewer3D.tsx b/src/components/skins/SkinViewer3D.tsx deleted file mode 100644 index ec85349..0000000 --- a/src/components/skins/SkinViewer3D.tsx +++ /dev/null @@ -1,81 +0,0 @@ -// src/components/skins/SkinViewer3D.tsx -//这部分还没写完!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -'use client'; -import { extend, Canvas } from '@react-three/fiber'; -import { OrbitControls, useTexture } from '@react-three/drei'; -import { Suspense } from 'react'; -import * as THREE from 'three'; - -// 显式扩展 Three.js 类 -extend({ - Mesh: THREE.Mesh, - BoxGeometry: THREE.BoxGeometry, - MeshStandardMaterial: THREE.MeshStandardMaterial, - AmbientLight: THREE.AmbientLight, - SpotLight: THREE.SpotLight, - MeshBasicMaterial: THREE.MeshBasicMaterial -}); - -interface SkinModelProps { - skinUrl: string; -} - -const SkinModel: React.FC = ({ skinUrl }) => { - const texture = useTexture(skinUrl); - - return ( - - - - - ); -}; - -const FallbackModel: React.FC = () => ( - - - - -); - -interface SkinViewer3DProps { - skinUrl: string; -} - -const SkinViewer3D: React.FC = ({ skinUrl }) => { - return ( -
- - - - }> - - - - -
- ); -}; - -export default SkinViewer3D; \ No newline at end of file diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index d05bbc6..4d63cd5 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
{ + console.log('serverSignOut函数执行开始'); + + // 使用更直接的方式清除会话并重定向 + // 1. 首先清除所有可能的cookie + const { cookies } = await import('next/headers'); + const cookieStore = cookies(); + + // 获取并记录当前所有的cookie + const allCookies = cookieStore.getAll(); + console.log('当前cookies:', allCookies.map(c => c.name)); + + // 清除所有可能的NextAuth相关cookie + let deletedCookies = 0; + allCookies.forEach(cookie => { + if (cookie.name.includes('next-auth')) { + console.log('删除cookie:', cookie.name); + cookieStore.delete(cookie.name); + deletedCookies++; + } + }); + + console.log(`总共删除了${deletedCookies}个cookie`); + + // 强制重定向到登录页面 + // 确保页面完全刷新而不是客户端导航 + console.log('执行重定向到登录页面'); + const { redirect } = await import('next/navigation'); + redirect('/login'); +}; + +// 导出登录函数 - 供服务器端使用 +export const login = async (credentials: { + username?: string; + email?: string; + password: string +}) => { + try { + // 对于测试环境,可以直接验证测试账号 + const TEST_USERNAME = 'test'; + const TEST_PASSWORD = 'test'; + // 对于测试环境,可以直接验证测试账号 - 支持通过username或email字段登录 + const usernameField = credentials.username || credentials.email; + if (usernameField === TEST_USERNAME && credentials.password === TEST_PASSWORD) { + return { + success: true, + user: { + id: 'test_user_1', + name: '测试玩家', + email: 'test@test.com', + minecraftUsername: 'SteveTest' + } + }; + } + + // 实际环境中调用API + const response = await axios.post(`${API_URL}/auth/login`, credentials); + return { success: true, ...response.data }; + } catch (error) { + console.error('登录失败:', error); + return { success: false, error: '登录失败,请检查用户名和密码' }; + } +}; + +// 导出注册函数 +export const register = async (userData: { + username: string; + password: string; + email: string; + minecraftUsername: string; +}) => { + try { + // 实际环境中调用API + const response = await axios.post(`${API_URL}/auth/register`, userData); + return { success: true, ...response.data }; + } catch (error) { + console.error('注册失败:', error); + return { success: false, error: '注册失败,请稍后再试' }; + } +}; \ No newline at end of file diff --git a/src/lib/api/auth.ts b/src/lib/api/auth.ts index 6548ed8..f012f4c 100644 --- a/src/lib/api/auth.ts +++ b/src/lib/api/auth.ts @@ -22,10 +22,31 @@ export const authOptions: AuthOptions = { name: 'Credentials', credentials: { username: { label: "用户名", type: "text" }, + email: { label: "邮箱", type: "email" }, password: { label: "密码", type: "password" } }, async authorize(credentials) { + // 默认测试账号 - 用于开发和测试环境 + const TEST_USERNAME = 'test'; + const TEST_PASSWORD = 'test'; + try { + // 检查是否是测试账号 - 支持通过username或email字段登录 + const usernameField = credentials?.username || credentials?.email; + if (usernameField === TEST_USERNAME && credentials?.password === TEST_PASSWORD) { + // 返回模拟的测试用户数据 + return { + id: 'test_user_1', + name: '测试玩家', + email: 'test@test.com', + minecraftUsername: 'SteveTest' + }; + + + + } + + // 正常的API登录流程 const response = await axios.post(`${API_URL}/auth/login`, { username: credentials?.username, password: credentials?.password @@ -48,7 +69,7 @@ export const authOptions: AuthOptions = { ], pages: { signIn: '/login', - signOut: '/login' + signOut: '/login' // 退出登录后重定向到登录页面 }, callbacks: { async jwt({ token, user }) { @@ -62,6 +83,15 @@ export const authOptions: AuthOptions = { session.user.id = token.id as string; } return session; + }, + // 登录成功后重定向到用户主页 + async redirect({ url, baseUrl }) { + // 如果已经有明确的重定向URL,则使用它 + if (url.startsWith(baseUrl)) { + return url; + } + // 否则默认重定向到用户主页 + return `${baseUrl}/user-home`; } }, secret: process.env.NEXTAUTH_SECRET, @@ -70,25 +100,4 @@ export const authOptions: AuthOptions = { // 导出 NextAuth 处理函数 export const nextAuthHandler = NextAuth(authOptions); -// 导出登录函数 -export const login = async (credentials: { - username: string; - password: string -}) => { - // 实现同上... -}; - -// 导出注册函数 -export const register = async (userData: { - username: string; - password: string; - email: string; - minecraftUsername: string; -}) => { - // 实现同上... -}; -export async function signOut() { - // 实际应用中这里会调用API退出登录 - console.log('用户退出登录'); - // 在客户端组件中,你可能会使用 next-auth 的 signOut 方法 -} \ No newline at end of file +// Server Actions已移至actions.ts文件 diff --git a/src/lib/constants.ts b/src/lib/constants.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/lib/utils/minecraft.ts b/src/lib/utils/minecraft.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/styles/globals.css b/src/styles/globals.css deleted file mode 100644 index 40fda50..0000000 --- a/src/styles/globals.css +++ /dev/null @@ -1,33 +0,0 @@ -/* src/styles/globals.css */ -@tailwind base; -@tailwind components; -@tailwind utilities; - -/* 添加 Minecraft 像素风格字体 */ -@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap'); - -/* 自定义全局样式 */ -@layer base { - body { - @apply bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-100; - font-family: 'Inter', sans-serif; - } - - h1, h2, h3, h4, h5, h6 { - @apply font-bold; - } - - /* Minecraft 风格按钮 */ - .btn-minecraft { - @apply px-4 py-2 rounded-md font-bold text-white bg-minecraft-green - border-2 border-b-4 border-minecraft-dark-green - hover:bg-minecraft-dark-green hover:border-minecraft-dark - active:transform active:translate-y-1 active:border-b-2 - transition-all duration-100; - } - - /* 像素风格元素 */ - .pixel-border { - @apply border-2 border-gray-800; - } -} \ No newline at end of file diff --git a/src/types/api/response.ts b/src/types/api/response.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/types/global.d.ts b/src/types/global.d.ts index adeafcb..56a2d5f 100644 --- a/src/types/global.d.ts +++ b/src/types/global.d.ts @@ -1,17 +1,8 @@ // src/types/global.d.ts -import * as THREE from 'three'; -import { Object3DNode } from '@react-three/fiber'; - declare global { - namespace JSX { - interface IntrinsicElements { - // Three.js 元素 - mesh: Object3DNode; - boxGeometry: Object3DNode; - meshStandardMaterial: Object3DNode; - ambientLight: Object3DNode; - spotLight: Object3DNode; - meshBasicMaterial: Object3DNode; - } - } + interface Window { + __NEXT_DATA__?: any; + gtag?: (...args: any[]) => void; + // 可以在这里扩展window对象的类型 + } } \ No newline at end of file diff --git a/src/types/three.d.ts b/src/types/three.d.ts deleted file mode 100644 index b5f00ff..0000000 --- a/src/types/three.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -// src/types/three.d.ts -import * as THREE from 'three'; -import { Object3DNode } from '@react-three/fiber'; - -declare global { - namespace JSX { - interface IntrinsicElements { - // 使用大写开头的类名 - mesh: Object3DNode; - boxGeometry: Object3DNode; - meshStandardMaterial: Object3DNode; - ambientLight: Object3DNode; - spotLight: Object3DNode; - meshBasicMaterial: Object3DNode; - } - } -} \ No newline at end of file diff --git a/数据库参考.txt b/数据库参考.txt new file mode 100644 index 0000000..e402cfe --- /dev/null +++ b/数据库参考.txt @@ -0,0 +1,114 @@ +-- 用户表,支持积分系统和权限管理 +CREATE TABLE `user` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `username` VARCHAR(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名', + `password` VARCHAR(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '密码哈希', + `email` VARCHAR(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '邮箱地址', + `avatar` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '头像URL(存储在MinIO中)', + `points` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户积分', + `role` VARCHAR(50) NOT NULL DEFAULT 'user' COMMENT '用户角色(user, vip, admin等)', + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '用户状态(1:正常, 0:禁用, -1:删除)', + `last_login_at` TIMESTAMP NULL COMMENT '最后登录时间', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_username` (`username`), + UNIQUE KEY `uk_email` (`email`), + INDEX `idx_role` (`role`), + INDEX `idx_status` (`status`), + INDEX `idx_points` (`points` DESC) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; + +-- 材质表,存储皮肤和披风 +CREATE TABLE `textures` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '材质的唯一ID', + `uploader_id` BIGINT UNSIGNED NOT NULL COMMENT '上传者的用户ID', + `name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '材质名称', + `description` TEXT COMMENT '材质描述', + `type` ENUM('SKIN', 'CAPE') NOT NULL COMMENT '材质类型(皮肤或披风)', + `url` VARCHAR(255) NOT NULL COMMENT '材质在MinIO中的永久访问URL', + `hash` VARCHAR(64) NOT NULL COMMENT '材质文件的SHA-256哈希值,用于快速去重和校验', + `size` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '文件大小(字节)', + `is_public` BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否公开到皮肤广场', + `download_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '下载次数', + `favorite_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '收藏次数', + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(1:正常, 0:审核中, -1:已删除)', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_hash` (`hash`), + INDEX `idx_uploader_id` (`uploader_id`), + INDEX `idx_public_type_status` (`is_public`, `type`, `status`), + INDEX `idx_download_count` (`download_count` DESC), + INDEX `idx_favorite_count` (`favorite_count` DESC), + CONSTRAINT `fk_textures_uploader` FOREIGN KEY (`uploader_id`) REFERENCES `user` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='皮肤与披风材质表'; + +-- 用户材质收藏表 +CREATE TABLE `user_texture_favorites` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '收藏记录的唯一ID', + `user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID', + `texture_id` BIGINT UNSIGNED NOT NULL COMMENT '收藏的材质ID', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '收藏时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_user_texture` (`user_id`, `texture_id`), + INDEX `idx_user_id` (`user_id`), + INDEX `idx_texture_id` (`texture_id`), + INDEX `idx_created_at` (`created_at`), + CONSTRAINT `fk_favorites_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_favorites_texture` FOREIGN KEY (`texture_id`) REFERENCES `textures` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户材质收藏表'; + +-- 用户角色信息表(Minecraft档案) +CREATE TABLE `profiles` ( + `uuid` VARCHAR(36) NOT NULL COMMENT '角色的UUID,通常为Minecraft玩家的UUID', + `user_id` BIGINT UNSIGNED NOT NULL COMMENT '关联的用户ID', + `name` VARCHAR(16) NOT NULL COMMENT '角色名(Minecraft游戏内名称)', + `skin_id` BIGINT UNSIGNED NULL DEFAULT NULL COMMENT '当前使用的皮肤ID', + `cape_id` BIGINT UNSIGNED NULL DEFAULT NULL COMMENT '当前使用的披风ID', + `rsa_private_key` TEXT NOT NULL COMMENT '用于签名的RSA-2048私钥(PEM格式)', + `is_active` BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否为活跃档案', + `last_used_at` TIMESTAMP NULL COMMENT '最后使用时间', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`uuid`), + UNIQUE KEY `uk_name` (`name`), + INDEX `idx_user_id` (`user_id`), + INDEX `idx_active` (`is_active`), + CONSTRAINT `fk_profiles_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_profiles_skin` FOREIGN KEY (`skin_id`) REFERENCES `textures` (`id`) ON DELETE SET NULL, + CONSTRAINT `fk_profiles_cape` FOREIGN KEY (`cape_id`) REFERENCES `textures` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户角色信息表(Minecraft档案)'; + +-- Casbin权限管理相关表 +-- casbin_rule表用于存储RBAC权限规则 +CREATE TABLE `casbin_rule` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '规则ID', + `ptype` VARCHAR(100) NOT NULL COMMENT '策略类型(p, g等)', + `v0` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '主体(用户或角色)', + `v1` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '资源对象', +-- 材质表,存储皮肤和披风 +CREATE TABLE `textures` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '材质的唯一ID', + `uploader_id` BIGINT UNSIGNED NOT NULL COMMENT '上传者的用户ID', + `name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '材质名称', + `description` TEXT COMMENT '材质描述', + `type` ENUM('SKIN', 'CAPE') NOT NULL COMMENT '材质类型(皮肤或披风)', + `url` VARCHAR(255) NOT NULL COMMENT '材质在MinIO中的永久访问URL', + `hash` VARCHAR(64) NOT NULL COMMENT '材质文件的SHA-256哈希值,用于快速去重和校验', + `size` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '文件大小(字节)', + `is_public` BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否公开到皮肤广场', + `download_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '下载次数', + `favorite_count` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '收藏次数', + "is_silm" BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否为细手臂模型(Steve/Alex),默认为粗手臂模型(Steve)', + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态(1:正常, 0:审核中, -1:已删除)', + `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_hash` (`hash`), + INDEX `idx_uploader_id` (`uploader_id`), + INDEX `idx_public_type_status` (`is_public`, `type`, `status`), + INDEX `idx_download_count` (`download_count` DESC), + INDEX `idx_favorite_count` (`favorite_count` DESC), + CONSTRAINT `fk_textures_uploader` FOREIGN KEY (`uploader_id`) REFERENCES `user` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='皮肤与披风材质表'; \ No newline at end of file