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账户
-
-
-
-
- 还没有账号?
-
- 立即注册
-
+
+ {/* 背景装饰元素 - 渐变模糊效果 */}
+
+
+ {/* 主要内容区域 */}
+
+
+ {/* 装饰性头部 */}
+
+
+
+ 欢迎回到
+ 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 (
+
+ {/* 背景装饰元素 - 渐变模糊效果 */}
+
+
+ {/* 主要内容区域 */}
+
+
+ {/* 装饰性头部 */}
+
+
+
+ 欢迎加入
+ 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}。定制和管理你的专属游戏角色。
+
+
+
+
+ {/* 角色管理标签页 */}
+
+
+ {/* 高级标签按钮组 */}
+
+ setActiveTab('my-characters')}
+ className={`px-6 py-2.5 rounded-lg transition-all duration-300 ${activeTab === 'my-characters' ? 'bg-white dark:bg-gray-700 font-medium shadow-md transform -translate-y-0.5' : 'hover:bg-gray-200 dark:hover:bg-gray-700/50'}`}
+ >
+ 我的角色
+
+ setActiveTab('create-character')}
+ className={`px-6 py-2.5 rounded-lg transition-all duration-300 ${activeTab === 'create-character' ? 'bg-white dark:bg-gray-700 font-medium shadow-md transform -translate-y-0.5' : 'hover:bg-gray-200 dark:hover:bg-gray-700/50'}`}
+ >
+ 创建角色
+
+
+
+
+ 导入角色
+
+
+
+ {/* 我的角色标签内容 */}
+ {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个角色
+
+
+
+
+ 创建设置
+
+
+
+
+ )}
+
+
+
+
+
+
+ {/* 页脚 */}
+
+
+ );
+}
\ 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.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)}
+ />
+
+ 🔍
+
+
+
+
+ {/* 皮肤分类标签 */}
+
+ setActiveCategory('all')}
+ >
+ 全部
+
+ setActiveCategory('recent')}
+ >
+ 最近上传
+
+ setActiveCategory('popular')}
+ >
+ 最受欢迎
+
+ setActiveCategory('favorites')}
+ >
+ 我的收藏
+
+
+
+ {/* 皮肤网格 */}
+
+ {filteredSkins.map((skin) => (
+
+
+
+
+
{skin.name}
+
+ ❤️
+
+
+ 上传于 {skin.createdAt}
+
+
+ 编辑
+
+
+ 分享
+
+
+ 下载
+
+
+
+
+ ))}
+
+
+ {/* 空状态 */}
+ {filteredSkins.length === 0 && (
+
+
+
+
+ 📦
+ 还没有皮肤
+
+ 上传你的第一个皮肤,开始你的Minecraft皮肤收藏之旅
+
+
+
+ 📤 上传第一个皮肤
+
+
+
+
+
+ )}
+
+
+ {/* 页脚 */}
+
);
}
\ 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. 下载和使用他人的皮肤
+
+
+
+ 浏览皮肤库,找到您喜欢的皮肤,点击进入详情页面,然后点击"下载皮肤"按钮将皮肤保存到本地。下载后,您可以通过以下步骤在游戏中使用:
+
+
+ 打开Minecraft启动器
+ 点击"皮肤"选项卡
+ 点击"浏览"按钮,选择下载的皮肤文件
+ 点击"保存"按钮应用新皮肤
+
+
+
+
+ 提示: 对于在多人服务器中使用自定义皮肤,您可能需要配置Yggdrasil验证。请参考下一节Yggdrasil教程了解更多信息。
+
+
+
+
+
+ {/* 教程项5 */}
+
+
+ 5. Yggdrasil验证简介
+
+
+
+ HITWH.GAMES平台提供了Yggdrasil验证服务,这是一个兼容Minecraft身份验证协议的系统,允许您在第三方服务器上使用您的自定义皮肤。
+
+
+
+
为什么需要Yggdrasil验证?
+
+ 在支持外置登录的Minecraft服务器上显示您的自定义皮肤
+ 跨服务器同步您的皮肤设置
+ 增强您的账号安全性
+ 支持多角色管理功能
+
+
+
+
+
+
+
+
+
+ 我们提供了便捷的拖拽配置 功能,可以通过将配置卡片拖放到支持的启动器(如HMCL、PCL)上快速完成Yggdrasil服务配置。详情请查看下一节Yggdrasil教程。
+
+
+
+
+
+
+ {/* 教程项6 */}
+
+
+ 6. 社区功能
+
+
+
+ 我们的平台不仅是一个皮肤管理工具,还是一个Minecraft玩家社区。您可以:
+
+
+ 分享您创作的皮肤给其他玩家
+ 查看和下载热门皮肤
+ 收藏您喜欢的皮肤作品
+ 与其他玩家交流皮肤制作技巧
+
+
+
+
+
+ {/* 页面导航按钮 */}
+
+
+
+ ← 返回帮助中心
+
+
+
+
+ 下一节:Yggdrasil教程 →
+
+
+
+
+
+ );
+}
\ 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安装方法:
+
+ 确保您已经安装了Minecraft Forge或Fabric
+ 下载与您Minecraft版本兼容的CustomSkinLoader模组文件(.jar格式)
+ 将模组文件放入Minecraft的mods文件夹中
+
+ Windows: %appdata%\.minecraft\mods
+ macOS: ~/Library/Application Support/minecraft/mods
+ Linux: ~/.minecraft/mods
+
+
+ 启动Minecraft,模组会自动生成配置文件
+
+
+
LiteLoader安装方法:
+
+ 安装LiteLoader
+ 下载CustomSkinLoader的LiteLoader版本
+ 将模组放入liteloader文件夹中
+ 启动Minecraft
+
+
+
+
+
+ {/* 教程项3 */}
+
+
+ 3. 配置OurSkin服务
+
+
+
+
+ 安装完成后,您需要配置CustomSkinLoader以使用我们的皮肤服务。以下是详细步骤:
+
+
+ 启动Minecraft一次,让模组生成配置文件
+ 关闭Minecraft,找到CustomSkinLoader的配置文件
+
+ 路径:.minecraft/config/CustomSkinLoader/CustomSkinLoader.json
+
+
+ 使用文本编辑器打开配置文件
+ 将我们的皮肤服务添加到加载列表中
+
+
+
+
配置文件示例:
+
+{`{
+ "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 - 查看加载状态
+
+
+
+
+
+
+ {/* 页面导航按钮 */}
+
+
+
+ ← 上一节:MultiLogin教程
+
+
+
+
+ 返回帮助中心
+
+
+
+
+
+ );
+}
\ 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。
+
+
+
+
+
+
+
config/paper-global.yaml
+
+
+
+
+
+
+
+
+
2. 配置 MultiLogin
+
+
+
+
对于 Velocity:
+
+ 安装 MultiLogin 插件,并按照以下步骤创建配置文件:
+
+
+ 创建以下两个文件用于支持 Mojang和LittleSkin 登录
+
+ 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 的配置和使用,请参考以下资源:
+
+
+
+
+
+
+ {/* 页面导航按钮 */}
+
+
+
+ ← 上一节:Yggdrasil教程
+
+
+
+
+ 下一节:CustomSkinLoader教程 →
+
+
+
+
+
+ );
+}
\ 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)可以直接通过拖放下方卡片到启动器来快速完成配置:
+
+
+ {/* 拖放配置卡片 */}
+
+
+ 操作方法:
+
+ 打开您的启动器(如HMCL、PCL等)
+ 将上方绿色卡片拖放到启动器窗口中
+ 在弹出的确认窗口中点击"确定"
+ 使用您的HITWH.GAMES账号登录
+
+
+ HMCL启动器配置
+
+ 打开HMCL启动器
+ 点击右上角的"账户"按钮
+ 点击"添加认证服务器"
+
+ 在"服务器地址"中输入:
+ https://skin.littlelan.cn/api/yggdrasil
+
+ 点击"下一步",然后输入您在HITWH.GAMES的用户名和密码
+ 完成后点击"登录"即可使用您的HITWH.GAMES账号
+
+
+ PCL启动器配置
+
+ 打开PCL启动器
+ 点击"设置"
+ 找到"登录设置"部分
+ 选择"外置登录"
+
+ 在"认证服务器"中输入:
+ https://skin.littlelan.cn/api/yggdrasil
+
+ 点击"保存"
+ 返回主界面,输入您在HITWH.GAMES的用户名和密码进行登录
+
+
+ 常见问题
+
+
+
无法看到皮肤?
+
+ 确保您已经在网站上上传了皮肤并分配给了您的角色。某些服务器可能禁用了自定义皮肤功能,这种情况下您的皮肤可能不会显示。
+
+
+
+
登录失败?
+
+ 请检查您的用户名和密码是否正确,以及认证服务器地址是否填写正确。如果问题持续存在,请联系我们的客服支持。
+
+
+
+
+
+
+ {/* 教程项3 */}
+
+
+ 3. 管理多账号
+
+
+
+ 我们的Yggdrasil服务支持多账号管理,您可以在一个主账户下创建多个子账户,每个子账户可以有不同的皮肤设置。
+
+
+
创建子账户:
+
+ 登录您的主账户
+ 进入"账户管理"页面
+ 点击"创建子账户"按钮
+ 设置子账户的用户名和密码
+ 为子账户上传或选择皮肤
+
+
+
+
+
+ {/* 教程项4 */}
+
+
+ 4. 高级问题与故障排除
+
+
+
+
+
问题:服务器无法验证我的Yggdrasil账号
+
+ 解决方法:请确认服务器已正确配置Yggdrasil验证功能。服务器管理员需要在服务器配置中添加我们的验证服务器地址。如果您是服务器管理员,请参考服务器的Yggdrasil集成文档。
+
+
+
+
问题:使用代理服务器时无法连接Yggdrasil服务
+
+ 解决方法:请确保您的代理服务器正确配置了HTTPS支持,并且没有阻止与我们Yggdrasil服务器的连接。您可能需要在代理设置中添加例外规则。
+
+
+
+
问题:更换皮肤后游戏中没有立即更新
+
+ 解决方法:皮肤更新可能需要一些时间才能在所有服务器上同步。尝试完全退出并重新启动游戏,或者尝试连接到不同的服务器以刷新皮肤缓存。
+
+
+
+
+
+
+
+ {/* 页面导航按钮 */}
+
+
+
+ ← 上一节:基础教程
+
+
+
+
+ 下一节:MultiLogin教程 →
+
+
+
+
+
+ );
+}
\ 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}
+
);
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的大家分享你的创作。简单、快捷!
-
-
-
+ useEffect(() => {
+ // 打字机效果
+ let mainTextIndex = 0;
+ let highlightTextIndex = 0;
+
+ const typeInterval = setInterval(() => {
+ if (isTypingMainText) {
+ if (mainTextIndex < fullText.length) {
+ setDisplayText(fullText.slice(0, mainTextIndex + 1));
+ mainTextIndex++;
+ } else {
+ setIsTypingMainText(false);
+ }
+ } else {
+ if (highlightTextIndex < highlightText.length) {
+ setDisplayHighlightText(highlightText.slice(0, highlightTextIndex + 1));
+ highlightTextIndex++;
+ } else {
+ clearInterval(typeInterval);
+ setTypingComplete(true);
+ }
+ }
+ }, 100); // 每个字符100ms的打字速度
+
+ // 光标闪烁效果
+ const cursorInterval = setInterval(() => {
+ setCursorVisible(prev => !prev);
+ }, 530); // 光标闪烁频率
+
+ // 清理函数
+ return () => {
+ clearInterval(typeInterval);
+ clearInterval(cursorInterval);
+ };
+ }, [isTypingMainText]);
+
+ useEffect(() => {
+ // 确保标题和描述文本始终可见
+ if (titleRef.current) {
+ titleRef.current.style.opacity = '1';
+ titleRef.current.style.transform = 'translateY(0)';
+ }
+ if (descRef.current) {
+ descRef.current.style.opacity = '1';
+ descRef.current.style.transform = 'translateY(0)';
+ }
+ }, []); // 空依赖数组,确保只在组件加载时执行一次
+
+ return (
+
+ {/* 服务器信息气泡组件 */}
+
+
+
+ {/* 背景装饰元素 - 渐变模糊效果 */}
+
+ {/* 英雄区域 */}
+
+
+
+
+ {displayText}
+
+ {displayHighlightText}
+
+
+
+
+
+ 上传你的自定义皮肤,查看预览效果,并与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皮肤库
+
+
+
+ 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}
+
+ ))}
+
+
+
+
+
+
+
+
+ {/* 页脚 */}
+
);
}
\ 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"
+ />
+
+
+
+
+
+ setSelectedCategory('all')}
+ className="text-sm"
+ >
+ 全部
+
+ setSelectedCategory('Steve')}
+ className="text-sm"
+ >
+ Steve
+
+ setSelectedCategory('Alex')}
+ className="text-sm"
+ >
+ Alex
+
+ setSelectedCategory('披风')}
+ className="text-sm"
+ >
+ 披风
+
+
+
+
+
+
+ {/* 皮肤列表 */}
+ {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}
+ 查看详情 →
+
+
+
+
+ ))}
+
+ ) : (
+
+
+ 🔍
+ 未找到皮肤
+
+ 没有找到匹配你搜索条件的皮肤,请尝试使用其他关键词或筛选条件
+
+ {
+ setSearchTerm('');
+ setSelectedCategory('all');
+ }}
+ >
+ 清除筛选条件
+
+
+
+ )}
+
+
+ );
+}
\ 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) => (
+ setFace(faceType)}
+ className={`px-4 py-2 rounded-md transition-colors ${face === faceType ? 'bg-blue-600 text-white' : 'bg-gray-200 hover:bg-gray-300'}`}
+ >
+ {faceType === 'front' && '正面'}
+ {faceType === 'back' && '背面'}
+ {faceType === 'top' && '顶部'}
+ {faceType === 'bottom' && '底部'}
+ {faceType === 'right' && '右侧'}
+ {faceType === 'left' && '左侧'}
+
+ ))}
+
+
+
+
+
+ >
+ );
+};
+
+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像素
+
+
+
+
-
- {isUploading ? '上传中...' : '上传皮肤'}
-
+
+
预览
+
+ {skinFile && (
+
+
文件名: {skinFile.name}
+
大小: {(skinFile.size / 1024).toFixed(2)} KB
+
+ )}
+
+
+
+
+
+ {isUploading ? '上传中...' : '上传皮肤'}
+
+
+
+
+
+ {/* 页脚 */}
+
);
}
\ 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,在客户端加载自定义皮肤。
+
+ 实用工具
+ →
+
+
+
+
+
+
+
+
+ {/* 统计信息区域 */}
+
+
+
+
+ 皮肤库统计
+
+
+
+
+
+
+
+
+ {/* 页脚 */}
+
+
+ );
+}
\ 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 (
+
+
setIsOpen(!isOpen)}
+ className="text-white hover:bg-gray-700"
+ >
+ {isOpen ? : }
+
+
+ {/* 移动端下拉菜单 */}
+ {isOpen && (
+
+
+ {session ? (
+ <>
+
setIsOpen(false)}
+ >
+ 用户主页
+
+
setIsOpen(false)}
+ >
+ 皮肤库
+
+
setIsOpen(false)}
+ >
+ 角色中心
+
+ >
+ ) : (
+ <>
+
{
+ handleProtectedLinkClick();
+ setIsOpen(false);
+ }}
+ className="block w-full text-left px-4 py-2 hover:bg-gray-700 rounded cursor-pointer hover:text-emerald-400 transition-colors"
+ style={{ textDecoration: 'underline', textDecorationStyle: 'dotted' }}
+ >
+ 用户主页
+
+
{
+ handleProtectedLinkClick();
+ setIsOpen(false);
+ }}
+ className="block w-full text-left px-4 py-2 hover:bg-gray-700 rounded cursor-pointer hover:text-emerald-400 transition-colors"
+ style={{ textDecoration: 'underline', textDecorationStyle: 'dotted' }}
+ >
+ 皮肤库
+
+
{
+ handleProtectedLinkClick();
+ setIsOpen(false);
+ }}
+ className="block w-full text-left px-4 py-2 hover:bg-gray-700 rounded cursor-pointer hover:text-emerald-400 transition-colors"
+ style={{ textDecoration: 'underline', textDecorationStyle: 'dotted' }}
+ >
+ 角色中心
+
+ >
+ )}
+
+ {session && (
+
+ )}
+
+ {!session && (
+
+
+ setIsOpen(false)}>登录
+
+
+ setIsOpen(false)}>注册
+
+
+ )}
+
+
+ )}
+
+ );
+};
+
+// 移动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 (
-
-
-
-
我的世界皮肤库
-
-
仪表盘
-
皮肤库
+ <>
+
+
+
+
+
+
我的世界皮肤库
+
+ {/* 桌面端导航链接 */}
+
+ {session ? (
+ <>
+ 用户主页
+ 皮肤库
+ 角色中心
+ >
+ ) : (
+ <>
+
+ 用户主页
+
+
+ 皮肤库
+
+
+ 角色中心
+
+ >
+ )}
+
-
-
- {session ? (
-
+
+
+ {/* 登录提示弹窗 */}
+ {showLoginPrompt && (
+
setShowLoginPrompt(false)}
+ >
+
e.stopPropagation()}
+ >
+
需要登录
+
+ 请先登录以访问此功能。
+
+
+ setShowLoginPrompt(false)}
+ >
登录
-
+ setShowLoginPrompt(false)}
+ >
注册
- )}
+
-
-
+ )}
+ >
);
-}
\ 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