更新皮肤分类功能,添加帮助页面和Yggdrasil教程
This commit is contained in:
231
.gitignore
vendored
231
.gitignore
vendored
@@ -1,131 +1,134 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
# dependencies
|
# Dependencies
|
||||||
/node_modules
|
node_modules/
|
||||||
/.pnp
|
.pnp
|
||||||
.pnp.*
|
.pnp.*
|
||||||
.yarn/*
|
|
||||||
!.yarn/patches
|
|
||||||
!.yarn/plugins
|
|
||||||
!.yarn/releases
|
|
||||||
!.yarn/versions
|
|
||||||
|
|
||||||
# testing
|
# Testing
|
||||||
/coverage
|
coverage/
|
||||||
|
|
||||||
# next.js
|
# Production build outputs
|
||||||
/.next/
|
.next/
|
||||||
/out/
|
out/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
|
||||||
# production
|
# Environment variables
|
||||||
/build
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
# misc
|
# Logs
|
||||||
.DS_Store
|
|
||||||
*.pem
|
|
||||||
|
|
||||||
# debug
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
.pnpm-debug.log*
|
pnpm-debug.log*
|
||||||
|
lerna-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
|
|
||||||
logs/
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
# 编辑器/IDE 配置
|
# Editor directories and files
|
||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
*.suo
|
*.swp
|
||||||
*.ntvs*
|
*.swo
|
||||||
*.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
|
|
||||||
*~
|
*~
|
||||||
# VS Code
|
|
||||||
.vscode/
|
|
||||||
|
|
||||||
# IntelliJ IDEA
|
# OS generated files
|
||||||
.idea/
|
.DS_Store
|
||||||
# 保留 package-lock.json
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
nehahn/
|
||||||
|
ehahn.db
|
||||||
|
[Dd]esktop.ini
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# 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
|
!package-lock.json
|
||||||
|
|
||||||
# 忽略 npm 调试日志
|
|
||||||
npm-debug.log*
|
|
||||||
127
README.md
127
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: [项目仓库地址]
|
||||||
49
middleware.ts
Normal file
49
middleware.ts
Normal file
@@ -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'映射到正确的物理路径
|
||||||
5
next-env.d.ts
vendored
Normal file
5
next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||||
781
package-lock.json
generated
781
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-label": "^2.1.7",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@react-three/drei": "^9.122.0",
|
|
||||||
"@react-three/fiber": "^8.18.0",
|
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -22,8 +20,7 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-dropzone": "^14.3.8",
|
"react-dropzone": "^14.3.8",
|
||||||
"tailwind-merge": "^3.3.1",
|
"tailwind-merge": "^3.3.1"
|
||||||
"three": "^0.152.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
@@ -31,7 +28,6 @@
|
|||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19.1.9",
|
"@types/react": "^19.1.9",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
"@types/three": "^0.178.1",
|
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"eslint": "^9",
|
"eslint": "^9",
|
||||||
"eslint-config-next": "15.4.4",
|
"eslint-config-next": "15.4.4",
|
||||||
|
|||||||
BIN
public/test-skin.png
Normal file
BIN
public/test-skin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/test-skin2.png
Normal file
BIN
public/test-skin2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/test-skin3.png
Normal file
BIN
public/test-skin3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 876 B |
@@ -1,24 +1,121 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import AuthForm from '@/components/auth/AuthForm';
|
import AuthForm from '@/components/auth/AuthForm';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
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 Link from 'next/link';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { useSearchParams } from 'next/navigation';
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const callbackUrl = searchParams.get('callbackUrl') || '/';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-md mx-auto">
|
<div className="min-h-screen bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800 relative overflow-hidden pb-8">
|
||||||
<Card>
|
{/* 背景装饰元素 - 渐变模糊效果 */}
|
||||||
<CardHeader>
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
<CardTitle className="text-center">登录你的littlelan账户</CardTitle>
|
<div className="absolute -top-20 -right-20 w-80 h-80 bg-emerald-400/20 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute -bottom-20 -left-20 w-96 h-96 bg-teal-500/20 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute top-1/3 left-1/4 w-64 h-64 bg-emerald-300/10 rounded-full blur-3xl"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 主要内容区域 */}
|
||||||
|
<div className="relative z-10 flex flex-col items-center justify-center min-h-screen px-4 py-12">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
{/* 装饰性头部 */}
|
||||||
|
<div className="text-center mb-8 sm:mb-10">
|
||||||
|
<div className="flex justify-center mb-6">
|
||||||
|
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 bg-emerald-600 rounded-2xl transform -rotate-6 shadow-lg transition-all duration-500 hover:rotate-0">
|
||||||
|
<div className="w-12 h-12 sm:w-14 sm:h-14 bg-white rounded-xl transform rotate-6 flex items-center justify-center transition-all duration-500 hover:rotate-0">
|
||||||
|
<Image
|
||||||
|
src="/images/mc-favicon.ico"
|
||||||
|
alt="Logo"
|
||||||
|
width={40}
|
||||||
|
height={40}
|
||||||
|
className="rounded-xl w-8 h-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl sm:text-4xl font-bold text-gray-800 dark:text-white mb-3 tracking-tight">
|
||||||
|
欢迎回到
|
||||||
|
<span className="text-emerald-600 dark:text-emerald-400"> HITWH</span>
|
||||||
|
社区
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 text-sm sm:text-base leading-relaxed max-w-sm mx-auto">
|
||||||
|
登录你的账户,继续探索和创作独特的Minecraft皮肤
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 登录卡片 */}
|
||||||
|
<div className="relative group">
|
||||||
|
{/* 卡片阴影装饰 */}
|
||||||
|
<div className="absolute inset-0 bg-emerald-500 rounded-2xl transform rotate-2 opacity-20 transition-all duration-500 group-hover:rotate-1 group-hover:scale-102"></div>
|
||||||
|
<div className="absolute inset-0 bg-teal-400 rounded-2xl transform -rotate-1 opacity-15 transition-all duration-500 group-hover:-rotate-0.5 group-hover:scale-101"></div>
|
||||||
|
|
||||||
|
<Card className="relative bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm border border-emerald-200 dark:border-emerald-900/50 shadow-xl rounded-2xl overflow-hidden">
|
||||||
|
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-teal-500"></div>
|
||||||
|
|
||||||
|
<CardHeader className="text-center pb-6 pt-8">
|
||||||
|
<CardTitle className="text-xl sm:text-2xl font-bold text-gray-800 dark:text-white">
|
||||||
|
🚪 登录账户
|
||||||
|
</CardTitle>
|
||||||
|
<div className="w-16 h-1 bg-gradient-to-r from-emerald-500 to-teal-500 mx-auto mt-2 rounded-full"></div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="space-y-6 px-6 pb-8">
|
||||||
|
{/* 使用统一的认证表单组件 */}
|
||||||
<AuthForm type="login" />
|
<AuthForm type="login" />
|
||||||
<div className="mt-4 text-center text-sm">
|
|
||||||
还没有账号?
|
{/* 分割线 */}
|
||||||
<Link href="/register" className="ml-1 text-blue-500 hover:underline">
|
<div className="relative my-6">
|
||||||
立即注册
|
<div className="absolute inset-0 flex items-center">
|
||||||
|
<div className="w-full border-t border-gray-200 dark:border-gray-700"></div>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex justify-center text-sm">
|
||||||
|
<span className="px-3 bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 rounded-full">或者</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 注册链接 */}
|
||||||
|
<div className="text-center space-y-4">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
还没有账号?立即注册开始创作!
|
||||||
|
</p>
|
||||||
|
<Button asChild variant="outline" className="w-full border-emerald-600 text-emerald-600 hover:bg-emerald-50 dark:border-emerald-400 dark:text-emerald-400 dark:hover:bg-emerald-900/20 rounded-xl py-6 transition-all duration-300 transform hover:-translate-y-1">
|
||||||
|
<Link href={`/register?callbackUrl=${encodeURIComponent(callbackUrl)}`}>
|
||||||
|
🎮 创建新账户
|
||||||
</Link>
|
</Link>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 底部装饰 */}
|
||||||
|
<div className="text-center mt-8 sm:mt-10">
|
||||||
|
<div className="flex flex-wrap justify-center gap-x-4 gap-y-2 text-xs sm:text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
<span>🎨 皮肤创作</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>🌍 社区分享</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>⚡ 快速上传</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>👥 角色中心</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 浮动装饰元素 */}
|
||||||
|
<div className="absolute bottom-8 left-8 text-emerald-600 opacity-20 dark:opacity-10">
|
||||||
|
<div className="text-4xl">🔑</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-8 right-8 text-teal-500 opacity-20 dark:opacity-10">
|
||||||
|
<div className="text-4xl">🌟</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
164
src/app/(auth)/register/page.tsx
Normal file
164
src/app/(auth)/register/page.tsx
Normal file
@@ -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 (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800 relative overflow-hidden pb-8">
|
||||||
|
{/* 背景装饰元素 - 渐变模糊效果 */}
|
||||||
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
|
<div className="absolute -top-20 -right-20 w-80 h-80 bg-emerald-400/20 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute -bottom-20 -left-20 w-96 h-96 bg-teal-500/20 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute top-1/3 left-1/4 w-64 h-64 bg-emerald-300/10 rounded-full blur-3xl"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 主要内容区域 */}
|
||||||
|
<div className="relative z-10 flex flex-col items-center justify-center min-h-screen px-4 py-12">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
{/* 装饰性头部 */}
|
||||||
|
<div className="text-center mb-8 sm:mb-10">
|
||||||
|
<div className="flex justify-center mb-6">
|
||||||
|
<div className="inline-flex items-center justify-center w-16 h-16 sm:w-20 sm:h-20 bg-emerald-600 rounded-2xl transform -rotate-6 shadow-lg transition-all duration-500 hover:rotate-0">
|
||||||
|
<div className="w-12 h-12 sm:w-14 sm:h-14 bg-white rounded-xl transform rotate-6 flex items-center justify-center transition-all duration-500 hover:rotate-0">
|
||||||
|
<Image
|
||||||
|
src="/images/mc-favicon.ico"
|
||||||
|
alt="Logo"
|
||||||
|
width={40}
|
||||||
|
height={40}
|
||||||
|
className="rounded-xl w-8 h-8"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h1 className="text-3xl sm:text-4xl font-bold text-gray-800 dark:text-white mb-3 tracking-tight">
|
||||||
|
欢迎加入
|
||||||
|
<span className="text-emerald-600 dark:text-emerald-400"> HITWH</span>
|
||||||
|
社区
|
||||||
|
</h1>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 text-sm sm:text-base leading-relaxed max-w-sm mx-auto">
|
||||||
|
创建你的账户,开始创作和分享独特的Minecraft皮肤
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 注册卡片 */}
|
||||||
|
<div className="relative group">
|
||||||
|
{/* 卡片阴影装饰 */}
|
||||||
|
<div className="absolute inset-0 bg-emerald-500 rounded-2xl transform rotate-2 opacity-20 transition-all duration-500 group-hover:rotate-1 group-hover:scale-102"></div>
|
||||||
|
<div className="absolute inset-0 bg-teal-400 rounded-2xl transform -rotate-1 opacity-15 transition-all duration-500 group-hover:-rotate-0.5 group-hover:scale-101"></div>
|
||||||
|
|
||||||
|
<Card className="relative bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm border border-emerald-200 dark:border-emerald-900/50 shadow-xl rounded-2xl overflow-hidden">
|
||||||
|
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-teal-500"></div>
|
||||||
|
|
||||||
|
<CardHeader className="text-center pb-6 pt-8">
|
||||||
|
<CardTitle className="text-xl sm:text-2xl font-bold text-gray-800 dark:text-white">
|
||||||
|
🎮 创建新账户
|
||||||
|
</CardTitle>
|
||||||
|
<div className="w-16 h-1 bg-gradient-to-r from-emerald-500 to-teal-500 mx-auto mt-2 rounded-full"></div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6 px-6 pb-8">
|
||||||
|
<form action="/api/auth/signup" method="post">
|
||||||
|
<input type="hidden" name="callbackUrl" value={callbackUrl} />
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="username" className="text-gray-700 dark:text-gray-300">用户名</Label>
|
||||||
|
<Input
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
type="text"
|
||||||
|
placeholder="请输入用户名"
|
||||||
|
required
|
||||||
|
className="border-emerald-200 dark:border-emerald-900/30 focus:border-emerald-500 dark:focus:border-emerald-400 transition-all rounded-xl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email" className="text-gray-700 dark:text-gray-300">邮箱</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="请输入您的邮箱"
|
||||||
|
required
|
||||||
|
className="border-emerald-200 dark:border-emerald-900/30 focus:border-emerald-500 dark:focus:border-emerald-400 transition-all rounded-xl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="password" className="text-gray-700 dark:text-gray-300">密码</Label>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
placeholder="请输入密码"
|
||||||
|
required
|
||||||
|
className="border-emerald-200 dark:border-emerald-900/30 focus:border-emerald-500 dark:focus:border-emerald-400 transition-all rounded-xl"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
className="w-full mt-6 bg-emerald-600 hover:bg-emerald-700 text-white transition-all duration-300 transform hover:-translate-y-1 shadow-md hover:shadow-lg rounded-xl py-6"
|
||||||
|
>
|
||||||
|
注册
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{/* 分割线 */}
|
||||||
|
<div className="relative my-6">
|
||||||
|
<div className="absolute inset-0 flex items-center">
|
||||||
|
<div className="w-full border-t border-gray-200 dark:border-gray-700"></div>
|
||||||
|
</div>
|
||||||
|
<div className="relative flex justify-center text-sm">
|
||||||
|
<span className="px-3 bg-white dark:bg-gray-800 text-gray-500 dark:text-gray-400 rounded-full">或者</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 登录链接 */}
|
||||||
|
<div className="text-center space-y-4">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
已有账号?直接登录开始游戏!
|
||||||
|
</p>
|
||||||
|
<Button asChild variant="outline" className="w-full border-emerald-600 text-emerald-600 hover:bg-emerald-50 dark:border-emerald-400 dark:text-emerald-400 dark:hover:bg-emerald-900/20 rounded-xl py-6 transition-all duration-300 transform hover:-translate-y-1">
|
||||||
|
<Link href={`/login?callbackUrl=${encodeURIComponent(callbackUrl)}`}>
|
||||||
|
🎮 登录现有账户
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 底部装饰 */}
|
||||||
|
<div className="text-center mt-8 sm:mt-10">
|
||||||
|
<div className="flex flex-wrap justify-center gap-x-4 gap-y-2 text-xs sm:text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
<span>🎨 皮肤创作</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>🌍 社区分享</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>⚡ 快速上传</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>👥 角色中心</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 浮动装饰元素 */}
|
||||||
|
<div className="absolute bottom-8 left-8 text-emerald-600 opacity-20 dark:opacity-10">
|
||||||
|
<div className="text-4xl">🎨</div>
|
||||||
|
</div>
|
||||||
|
<div className="absolute top-8 right-8 text-teal-500 opacity-20 dark:opacity-10">
|
||||||
|
<div className="text-4xl">🌟</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
388
src/app/character-center/CharacterCenterClient.tsx
Normal file
388
src/app/character-center/CharacterCenterClient.tsx
Normal file
@@ -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 (
|
||||||
|
<Card className="overflow-hidden transition-all duration-300 hover:shadow-xl bg-white dark:bg-gray-800 rounded-xl w-full flex flex-col md:flex-row border border-gray-100 dark:border-gray-700">
|
||||||
|
{/* 皮肤预览区域 */}
|
||||||
|
<div className="w-full md:w-1/5 aspect-video md:aspect-auto md:h-32 bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-900/40 dark:to-indigo-900/40 flex items-center justify-center relative overflow-hidden">
|
||||||
|
{/* 装饰元素 */}
|
||||||
|
<div className="absolute -right-4 -bottom-4 w-20 h-20 bg-blue-200 dark:bg-blue-800/30 rounded-full opacity-50 blur-xl"></div>
|
||||||
|
<div className="absolute -left-4 -top-4 w-16 h-16 bg-indigo-200 dark:bg-indigo-800/30 rounded-full opacity-50 blur-xl"></div>
|
||||||
|
|
||||||
|
{/* 皮肤预览 - 使用Canvas2DSkinPreview组件 */}
|
||||||
|
<div className="relative z-10">
|
||||||
|
<Canvas2DSkinPreview
|
||||||
|
skinUrl={`/test-skin.png?skinId=${character.skinId}`}
|
||||||
|
size={112}
|
||||||
|
className="transition-transform duration-500 hover:scale-110"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 角色信息区域 */}
|
||||||
|
<div className="w-full md:w-4/5 p-6 flex flex-col justify-between">
|
||||||
|
<div>
|
||||||
|
<div className="flex flex-col md:flex-row md:justify-between md:items-start gap-3 mb-2">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-2xl font-bold tracking-tight m-0">{character.name}</h3>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||||
|
创建于 {character.created}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<span className="bg-emerald-100 dark:bg-emerald-900/50 text-emerald-700 dark:text-emerald-300 px-3 py-1.5 rounded-full text-sm font-medium inline-block self-start">
|
||||||
|
等级 {character.level}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 额外的角色信息 */}
|
||||||
|
<div className="mt-3 flex flex-wrap gap-x-6 gap-y-2 text-sm text-gray-600 dark:text-gray-300">
|
||||||
|
<span className="flex items-center gap-1.5">
|
||||||
|
<span className="text-emerald-500">◆</span>
|
||||||
|
活跃状态
|
||||||
|
</span>
|
||||||
|
<span className="flex items-center gap-1.5">
|
||||||
|
<span className="text-blue-500">◆</span>
|
||||||
|
皮肤ID: {character.skinId}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 操作按钮区域 */}
|
||||||
|
<div className="flex justify-end gap-3 mt-6">
|
||||||
|
<Button variant="ghost" className="rounded-lg px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all">
|
||||||
|
详情
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" className="rounded-lg px-4 py-2 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-all">
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" className="rounded-lg px-4 py-2 text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-all">
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加角色卡片组件
|
||||||
|
function AddCharacterCard({ onAddClick }: { onAddClick: () => void }) {
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
className="overflow-hidden border-2 border-dashed border-gray-300 dark:border-gray-700 hover:border-emerald-500 dark:hover:border-emerald-400 transition-all duration-300 cursor-pointer w-full flex items-center justify-center h-32 md:h-auto bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm"
|
||||||
|
onClick={onAddClick}
|
||||||
|
>
|
||||||
|
<div className="w-full h-32 flex flex-col items-center justify-center text-gray-500 dark:text-gray-400 group">
|
||||||
|
<div className="text-5xl mb-2 text-gray-400 dark:text-gray-600 group-hover:text-emerald-500 dark:group-hover:text-emerald-400 transition-all duration-300 transform group-hover:scale-110">+</div>
|
||||||
|
<span className="text-base font-medium group-hover:text-emerald-600 dark:group-hover:text-emerald-400 transition-colors">添加新角色</span>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 主客户端组件
|
||||||
|
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<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setCharacterForm(prev => ({
|
||||||
|
...prev,
|
||||||
|
[name]: value
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理复选框变化
|
||||||
|
const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
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 (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800 relative overflow-hidden text-gray-900 dark:text-gray-100">
|
||||||
|
{/* 背景装饰元素 - 渐变模糊效果 */}
|
||||||
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
|
<div className="absolute -top-20 -right-20 w-80 h-80 bg-emerald-400/20 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute -bottom-20 -left-20 w-96 h-96 bg-teal-500/20 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute top-1/3 left-1/4 w-64 h-64 bg-emerald-300/10 rounded-full blur-3xl"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 主要内容区域 */}
|
||||||
|
<main className="container mx-auto px-4 py-12 relative z-10">
|
||||||
|
{/* 页面标题区域 */}
|
||||||
|
<section className="mb-12 max-w-4xl mx-auto">
|
||||||
|
<div className="bg-gradient-to-r from-emerald-600 to-teal-500 rounded-2xl p-8 text-white shadow-xl transform transition-all duration-300 hover:shadow-2xl backdrop-blur-sm">
|
||||||
|
<h1 className="text-3xl md:text-4xl font-bold mb-3 tracking-tight">角色中心</h1>
|
||||||
|
<p className="text-base md:text-lg opacity-90 leading-relaxed">
|
||||||
|
欢迎回来,{userName}。定制和管理你的专属游戏角色。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 角色管理标签页 */}
|
||||||
|
<div className="w-full mb-16 max-w-4xl mx-auto">
|
||||||
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center mb-8 gap-4">
|
||||||
|
{/* 高级标签按钮组 */}
|
||||||
|
<div className="bg-white/80 dark:bg-gray-800/80 rounded-xl flex p-1.5 shadow-sm backdrop-blur-sm border border-emerald-100 dark:border-emerald-900/30">
|
||||||
|
<button
|
||||||
|
onClick={() => 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'}`}
|
||||||
|
>
|
||||||
|
我的角色
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => 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'}`}
|
||||||
|
>
|
||||||
|
创建角色
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl px-6 py-2.5 shadow-md hover:shadow-lg transition-all duration-300 transform hover:-translate-y-1">
|
||||||
|
<Link href="#">导入角色</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 我的角色标签内容 */}
|
||||||
|
{activeTab === 'my-characters' && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
{/* 角色卡片列表 */}
|
||||||
|
{characters.map((character) => (
|
||||||
|
<CharacterCard key={character.id} character={character} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* 添加新角色卡片 */}
|
||||||
|
<AddCharacterCard onAddClick={() => setActiveTab('create-character')} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 创建角色标签内容 */}
|
||||||
|
{activeTab === 'create-character' && (
|
||||||
|
<div className="bg-white/95 dark:bg-gray-800/95 rounded-2xl p-8 shadow-xl border border-emerald-200 dark:border-emerald-900/30 backdrop-blur-sm">
|
||||||
|
<h3 className="text-2xl font-bold mb-8 tracking-tight">创建新角色</h3>
|
||||||
|
<div className="max-w-2xl">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label htmlFor="character-name" className="text-base font-medium">角色名称 <span className="text-red-500">*</span></Label>
|
||||||
|
<Input
|
||||||
|
id="character-name"
|
||||||
|
name="name"
|
||||||
|
value={characterForm.name}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="输入Minecraft游戏内名称(16个字符以内)"
|
||||||
|
className="rounded-xl px-4 py-3 border-emerald-200 dark:border-emerald-900/30 focus:ring-2 focus:ring-emerald-500 focus:border-transparent transition-all"
|
||||||
|
maxLength={16}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
角色名称将作为Minecraft游戏内的用户名,请确保唯一性
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label className="text-base font-medium">选择皮肤</Label>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
|
||||||
|
<div
|
||||||
|
className={`border-2 rounded-xl p-3 cursor-pointer aspect-square bg-emerald-50 dark:bg-emerald-900/20 transition-all hover:shadow-xl transform hover:-translate-y-1 relative ${characterForm.skinId === 'skin1' ? 'border-emerald-500' : 'border-emerald-200/50 dark:border-emerald-900/10'}`}
|
||||||
|
onClick={() => handleSkinSelect('skin1')}
|
||||||
|
>
|
||||||
|
<Canvas2DSkinPreview
|
||||||
|
skinUrl="/test-skin.png"
|
||||||
|
size={128}
|
||||||
|
className="w-full h-full"
|
||||||
|
/>
|
||||||
|
{characterForm.skinId === 'skin1' && (
|
||||||
|
<div className="absolute top-3 right-3 w-3 h-3 bg-emerald-500 rounded-full"></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`border-2 rounded-xl p-3 cursor-pointer aspect-square transition-all hover:shadow-xl transform hover:-translate-y-1 relative ${characterForm.skinId === 'skin2' ? 'border-emerald-500 bg-emerald-50 dark:bg-emerald-900/20' : 'border-emerald-200/50 dark:border-emerald-900/10 hover:bg-emerald-50/50 dark:hover:bg-emerald-900/10'}`}
|
||||||
|
onClick={() => handleSkinSelect('skin2')}
|
||||||
|
>
|
||||||
|
<Canvas2DSkinPreview
|
||||||
|
skinUrl="/test-skin2.png"
|
||||||
|
size={128}
|
||||||
|
className="max-w-full max-h-full"
|
||||||
|
/>
|
||||||
|
{characterForm.skinId === 'skin2' && (
|
||||||
|
<div className="absolute top-3 right-3 w-3 h-3 bg-emerald-500 rounded-full"></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`border-2 rounded-xl p-3 cursor-pointer aspect-square transition-all hover:shadow-xl transform hover:-translate-y-1 relative ${characterForm.skinId === 'skin3' ? 'border-emerald-500 bg-emerald-50 dark:bg-emerald-900/20' : 'border-emerald-200/50 dark:border-emerald-900/10 hover:bg-emerald-50/50 dark:hover:bg-emerald-900/10'}`}
|
||||||
|
onClick={() => handleSkinSelect('skin3')}
|
||||||
|
>
|
||||||
|
<Canvas2DSkinPreview
|
||||||
|
skinUrl="/test-skin3.png"
|
||||||
|
size={128}
|
||||||
|
className="w-full h-full"
|
||||||
|
/>
|
||||||
|
{characterForm.skinId === 'skin3' && (
|
||||||
|
<div className="absolute top-3 right-3 w-3 h-3 bg-emerald-500 rounded-full"></div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label htmlFor="character-description" className="text-base font-medium">角色描述(可选)</Label>
|
||||||
|
<Input
|
||||||
|
id="character-description"
|
||||||
|
name="description"
|
||||||
|
value={characterForm.description}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
placeholder="描述你的角色..."
|
||||||
|
className="rounded-xl px-4 py-3 border-gray-200 dark:border-gray-700 focus:ring-2 focus:ring-emerald-500 focus:border-transparent transition-all"
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-2 pt-2">
|
||||||
|
<input
|
||||||
|
id="is-active"
|
||||||
|
type="checkbox"
|
||||||
|
checked={characterForm.isActive}
|
||||||
|
onChange={handleCheckboxChange}
|
||||||
|
className="rounded border-gray-300 text-emerald-600 focus:ring-emerald-500"
|
||||||
|
/>
|
||||||
|
<Label htmlFor="is-active" className="text-sm">设为活跃角色</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-amber-50 dark:bg-amber-900/20 p-4 rounded-lg border border-amber-200 dark:border-amber-900/30">
|
||||||
|
<h4 className="font-medium text-amber-800 dark:text-amber-400 mb-2 flex items-center">
|
||||||
|
<span className="mr-2">ℹ️</span>
|
||||||
|
角色信息
|
||||||
|
</h4>
|
||||||
|
<ul className="text-sm text-gray-600 dark:text-gray-400 space-y-1.5">
|
||||||
|
<li>• 角色将自动生成唯一UUID</li>
|
||||||
|
<li>• 系统会为角色生成RSA密钥用于身份验证</li>
|
||||||
|
<li>• 角色名称必须符合Minecraft命名规范</li>
|
||||||
|
<li>• 每个用户最多可创建10个角色</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
className="w-full bg-emerald-600 hover:bg-emerald-700 py-6 rounded-xl text-base font-medium shadow-md hover:shadow-lg transition-all transform hover:-translate-y-1"
|
||||||
|
onClick={handleCreateCharacter}
|
||||||
|
>
|
||||||
|
创建设置
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* 页脚 */}
|
||||||
|
<footer className="bg-gray-900/80 backdrop-blur-md text-gray-300 py-8 mt-16 border-t border-gray-800">
|
||||||
|
<div className="container mx-auto px-4 text-center relative z-10">
|
||||||
|
<p className="mb-2">我的世界皮肤库 - 你的Minecraft皮肤分享平台</p>
|
||||||
|
<p className="text-sm text-gray-400">© {new Date().getFullYear()} 版权所有</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
110
src/app/character-center/page.tsx
Normal file
110
src/app/character-center/page.tsx
Normal file
@@ -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 <CharacterCenterClient userName={userName} characters={characters} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 角色卡片组件
|
||||||
|
function CharacterCard({ character }: { character: any }) {
|
||||||
|
return (
|
||||||
|
<Card className="overflow-hidden hover:shadow-lg transition-all duration-300 border-0 shadow-md">
|
||||||
|
<div className="relative">
|
||||||
|
{/* 角色状态标签 */}
|
||||||
|
<div className="absolute top-3 right-3 bg-green-500 text-white text-xs px-2 py-1 rounded-full">
|
||||||
|
活跃
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 角色皮肤预览 */}
|
||||||
|
<div className="h-48 bg-gradient-to-b from-blue-50 to-green-50 dark:from-gray-700 dark:to-gray-800 flex items-center justify-center p-4">
|
||||||
|
<div className="relative w-28 h-48">
|
||||||
|
<img
|
||||||
|
src="/test-skin.png"
|
||||||
|
alt={character.name}
|
||||||
|
className="object-contain w-full h-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CardHeader className="pb-2">
|
||||||
|
<CardTitle className="text-xl">{character.name}</CardTitle>
|
||||||
|
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||||
|
等级 {character.level} • 创建于 {character.created}
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div>
|
||||||
|
<div className="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">角色进度</div>
|
||||||
|
<div className="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-2">
|
||||||
|
<div
|
||||||
|
className="bg-green-600 h-2 rounded-full"
|
||||||
|
style={{ width: `${(character.level / 100) * 100}%` }}
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
|
||||||
|
<CardFooter className="flex justify-between border-t pt-4">
|
||||||
|
<Button variant="ghost" size="sm" className="text-gray-600 dark:text-gray-300">
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" className="bg-green-600 hover:bg-green-700">
|
||||||
|
切换到
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加角色卡片组件
|
||||||
|
function AddCharacterCard() {
|
||||||
|
return (
|
||||||
|
<Card className="overflow-hidden hover:shadow-lg transition-all duration-300 border-2 border-dashed border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800 h-full flex flex-col items-center justify-center p-6 cursor-pointer">
|
||||||
|
<div className="text-6xl mb-4">➕</div>
|
||||||
|
<h3 className="text-lg font-bold text-gray-700 dark:text-gray-300 mb-2">添加新角色</h3>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400 text-center">
|
||||||
|
创建一个新的游戏角色
|
||||||
|
</p>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 统计卡片组件
|
||||||
|
function StatCard({ title, value, icon }: { title: string; value: string; icon: string }) {
|
||||||
|
return (
|
||||||
|
<Card className="border-0 shadow-md">
|
||||||
|
<CardHeader>
|
||||||
|
<div className="text-4xl mb-2">{icon}</div>
|
||||||
|
<CardTitle className="text-lg">{title}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="text-3xl font-bold">{value}</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,18 +1,26 @@
|
|||||||
// src/app/dashboard/page.tsx
|
// src/app/dashboard/page.tsx
|
||||||
import { getServerSession } from 'next-auth';
|
'use client';
|
||||||
import { authOptions } from '@/lib/api/auth';
|
import { Button } from '@/components/ui/button';
|
||||||
import { redirect } from 'next/navigation';
|
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';
|
import SkinGrid from '@/components/skins/SkinGrid';
|
||||||
|
|
||||||
export default async function Dashboard() {
|
export default function Dashboard() {
|
||||||
const session = await getServerSession(authOptions);
|
// 由于这是客户端组件,我们不能在这里使用getServerSession
|
||||||
|
// 在实际应用中,你应该使用useSession钩子或在服务器端获取会话
|
||||||
if (!session) {
|
// 这里我们模拟一个已登录的状态
|
||||||
redirect('/login');
|
const session = { user: { id: 'test_user_1', name: '测试玩家', email: 'test@test.com' } };
|
||||||
}
|
|
||||||
|
|
||||||
// 安全地获取用户ID
|
// 安全地获取用户ID
|
||||||
const userId = session.user?.id || 'unknown';
|
const userId = session?.user?.id || 'unknown';
|
||||||
|
|
||||||
|
// 状态管理
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const [activeCategory, setActiveCategory] = useState('all');
|
||||||
|
|
||||||
// 实际应用中这里会从API获取用户皮肤数据
|
// 实际应用中这里会从API获取用户皮肤数据
|
||||||
const mockSkins = [
|
const mockSkins = [
|
||||||
@@ -20,18 +28,149 @@ export default async function Dashboard() {
|
|||||||
{ id: '2', name: 'Alex皮肤', createdAt: '2023-05-15' },
|
{ id: '2', name: 'Alex皮肤', createdAt: '2023-05-15' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 根据搜索词和分类过滤皮肤
|
||||||
|
const filteredSkins = mockSkins.filter(skin =>
|
||||||
|
skin.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="min-h-screen bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800 relative overflow-hidden text-gray-900 dark:text-gray-100">
|
||||||
<h1 className="text-3xl font-bold mb-6">我的皮肤库</h1>
|
{/* 背景装饰元素 - 渐变模糊效果 */}
|
||||||
<div className="mb-6">
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
<a
|
<div className="absolute -top-20 -right-20 w-80 h-80 bg-emerald-400/20 rounded-full blur-3xl"></div>
|
||||||
href="/skins/upload"
|
<div className="absolute -bottom-20 -left-20 w-96 h-96 bg-teal-500/20 rounded-full blur-3xl"></div>
|
||||||
className="bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded"
|
<div className="absolute top-1/3 left-1/4 w-64 h-64 bg-emerald-300/10 rounded-full blur-3xl"></div>
|
||||||
>
|
|
||||||
上传新皮肤
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<SkinGrid skins={mockSkins} />
|
|
||||||
|
{/* 主要内容区域 */}
|
||||||
|
<main className="container mx-auto px-4 py-12 relative z-10">
|
||||||
|
{/* 页面标题和操作按钮 */}
|
||||||
|
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-10 gap-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold mb-2 text-gray-800 dark:text-white">我的皮肤库</h1>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">管理和分享你的Minecraft皮肤</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button asChild className="bg-emerald-600 hover:bg-emerald-700 text-white transition-all duration-300 transform hover:-translate-y-1 shadow-md hover:shadow-lg">
|
||||||
|
<Link href="/skins/upload">
|
||||||
|
<span className="mr-2">📤</span>上传新皮肤
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 搜索栏 */}
|
||||||
|
<div className="mb-8 max-w-4xl mx-auto">
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="搜索你的皮肤..."
|
||||||
|
className="pl-10 pr-4 py-2 w-full border-emerald-200 dark:border-emerald-900/30 focus:border-emerald-500 dark:focus:border-emerald-400 rounded-xl shadow-sm transition-all bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
/>
|
||||||
|
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400">
|
||||||
|
🔍
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 皮肤分类标签 */}
|
||||||
|
<div className="flex overflow-x-auto gap-3 pb-4 mb-8 scrollbar-hide max-w-4xl mx-auto">
|
||||||
|
<Button
|
||||||
|
variant={activeCategory === 'all' ? 'default' : 'ghost'}
|
||||||
|
className={`rounded-full ${activeCategory === 'all' ? 'bg-emerald-600 hover:bg-emerald-700 text-white shadow-md' : 'hover:text-emerald-600 dark:hover:text-emerald-400'} transition-all`}
|
||||||
|
onClick={() => setActiveCategory('all')}
|
||||||
|
>
|
||||||
|
全部
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={activeCategory === 'recent' ? 'default' : 'ghost'}
|
||||||
|
className={`rounded-full ${activeCategory === 'recent' ? 'bg-emerald-600 hover:bg-emerald-700 text-white shadow-md' : 'hover:text-emerald-600 dark:hover:text-emerald-400'} transition-all`}
|
||||||
|
onClick={() => setActiveCategory('recent')}
|
||||||
|
>
|
||||||
|
最近上传
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={activeCategory === 'popular' ? 'default' : 'ghost'}
|
||||||
|
className={`rounded-full ${activeCategory === 'popular' ? 'bg-emerald-600 hover:bg-emerald-700 text-white shadow-md' : 'hover:text-emerald-600 dark:hover:text-emerald-400'} transition-all`}
|
||||||
|
onClick={() => setActiveCategory('popular')}
|
||||||
|
>
|
||||||
|
最受欢迎
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={activeCategory === 'favorites' ? 'default' : 'ghost'}
|
||||||
|
className={`rounded-full ${activeCategory === 'favorites' ? 'bg-emerald-600 hover:bg-emerald-700 text-white shadow-md' : 'hover:text-emerald-600 dark:hover:text-emerald-400'} transition-all`}
|
||||||
|
onClick={() => setActiveCategory('favorites')}
|
||||||
|
>
|
||||||
|
我的收藏
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 皮肤网格 */}
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6 max-w-6xl mx-auto">
|
||||||
|
{filteredSkins.map((skin) => (
|
||||||
|
<Card key={skin.id} className="overflow-hidden border border-emerald-200 dark:border-emerald-900/30 rounded-2xl hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm">
|
||||||
|
<div className="aspect-square bg-gray-100/95 dark:bg-gray-900/95 flex items-center justify-center p-4 relative overflow-hidden group">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-emerald-50/50 to-teal-50/50 dark:from-emerald-900/10 dark:to-teal-900/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<Canvas2DSkinPreview
|
||||||
|
skinUrl={`/test-skin.png`}
|
||||||
|
size={128}
|
||||||
|
className="max-w-full max-h-full relative z-10 transition-transform duration-500 group-hover:scale-110"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CardContent className="p-5">
|
||||||
|
<div className="flex justify-between items-start mb-2">
|
||||||
|
<h3 className="font-semibold text-lg text-gray-800 dark:text-white truncate group-hover:text-emerald-600 dark:group-hover:text-emerald-400 transition-colors">{skin.name}</h3>
|
||||||
|
<Button variant="ghost" size="icon" className="h-8 w-8 text-gray-500 hover:text-rose-500 dark:hover:text-rose-400 rounded-full">
|
||||||
|
❤️
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400 mb-4">上传于 {skin.createdAt}</p>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button size="sm" variant="ghost" className="text-xs flex-1 border border-emerald-200 dark:border-emerald-900/30 hover:bg-emerald-50 dark:hover:bg-emerald-900/20 rounded-lg">
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" variant="ghost" className="text-xs flex-1 border border-emerald-200 dark:border-emerald-900/30 hover:bg-emerald-50 dark:hover:bg-emerald-900/20 rounded-lg">
|
||||||
|
分享
|
||||||
|
</Button>
|
||||||
|
<Button size="sm" variant="ghost" className="text-xs flex-1 border border-emerald-200 dark:border-emerald-900/30 hover:bg-emerald-50 dark:hover:bg-emerald-900/20 rounded-lg">
|
||||||
|
下载
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 空状态 */}
|
||||||
|
{filteredSkins.length === 0 && (
|
||||||
|
<div className="text-center py-16 max-w-4xl mx-auto">
|
||||||
|
<Card className="border border-emerald-200 dark:border-emerald-900/30 rounded-2xl overflow-hidden bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm shadow-xl">
|
||||||
|
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-teal-500"></div>
|
||||||
|
<CardContent className="p-8">
|
||||||
|
<div className="text-6xl mb-4">📦</div>
|
||||||
|
<h3 className="text-xl font-semibold mb-2 text-gray-800 dark:text-white">还没有皮肤</h3>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-6 max-w-md mx-auto">
|
||||||
|
上传你的第一个皮肤,开始你的Minecraft皮肤收藏之旅
|
||||||
|
</p>
|
||||||
|
<Button asChild className="bg-emerald-600 hover:bg-emerald-700 text-white transition-all duration-300 transform hover:-translate-y-1 shadow-md hover:shadow-lg">
|
||||||
|
<Link href="/skins/upload">
|
||||||
|
<span className="mr-2">📤</span>上传第一个皮肤
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* 页脚 */}
|
||||||
|
<footer className="bg-gray-900/80 backdrop-blur-md text-gray-300 py-8 mt-16 border-t border-gray-800">
|
||||||
|
<div className="container mx-auto px-4 text-center relative z-10">
|
||||||
|
<p className="mb-2">我的世界皮肤库 - 你的Minecraft皮肤分享平台</p>
|
||||||
|
<p className="text-sm text-gray-400">© {new Date().getFullYear()} 版权所有</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -117,6 +117,80 @@
|
|||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
}
|
}
|
||||||
body {
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
182
src/app/help/basic/page.tsx
Normal file
182
src/app/help/basic/page.tsx
Normal file
@@ -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 (
|
||||||
|
<main className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 py-12">
|
||||||
|
<div className="container mx-auto px-4 max-w-4xl">
|
||||||
|
{/* 页面导航 */}
|
||||||
|
<div className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||||||
|
<Link href="/" className="hover:text-blue-600 dark:hover:text-blue-400">首页</Link>
|
||||||
|
<span>/</span>
|
||||||
|
<Link href="/help" className="hover:text-blue-600 dark:hover:text-blue-400">帮助中心</Link>
|
||||||
|
<span>/</span>
|
||||||
|
<span className="text-blue-600 dark:text-blue-400">基础教程</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 页面标题 */}
|
||||||
|
<div className="mb-12">
|
||||||
|
<h1 className="text-3xl font-bold text-blue-700 dark:text-blue-400 mb-4">基础教程</h1>
|
||||||
|
<p className="text-xl text-gray-600 dark:text-gray-400">
|
||||||
|
了解平台的基本功能和使用方法,快速上手
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 教程内容列表 */}
|
||||||
|
<div className="space-y-8">
|
||||||
|
{/* 教程项1 */}
|
||||||
|
<Card className="overflow-hidden border-blue-200 dark:border-blue-900/50">
|
||||||
|
<CardHeader className="bg-blue-50 dark:bg-blue-900/20">
|
||||||
|
<CardTitle className="text-xl text-blue-700 dark:text-blue-400">1. 注册与登录</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
要使用我们的平台,您需要先注册一个账户。点击页面右上角的"注册"按钮,填写必要的信息,包括用户名、邮箱和密码。注册成功后,您可以使用这些凭据登录平台。
|
||||||
|
</p>
|
||||||
|
<div className="bg-gray-50 dark:bg-gray-900/50 p-4 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">提示:</h4>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
请确保使用真实的邮箱地址,因为我们可能需要通过邮箱验证您的账户或重置密码。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 教程项2 */}
|
||||||
|
<Card className="overflow-hidden border-blue-200 dark:border-blue-900/50">
|
||||||
|
<CardHeader className="bg-blue-50 dark:bg-blue-900/20">
|
||||||
|
<CardTitle className="text-xl text-blue-700 dark:text-blue-400">2. 上传自定义皮肤</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
登录后,点击页面顶部导航栏中的"上传皮肤"按钮。在弹出的对话框中,点击"选择文件"按钮,从您的电脑中选择PNG格式的皮肤文件。添加皮肤名称和描述,然后点击"上传"按钮完成操作。
|
||||||
|
</p>
|
||||||
|
<div className="bg-amber-50 dark:bg-amber-900/20 p-4 rounded-lg border border-amber-200 dark:border-amber-900/50">
|
||||||
|
<h4 className="font-semibold text-amber-700 dark:text-amber-400 mb-2">注意事项:</h4>
|
||||||
|
<ul className="list-disc list-inside space-y-1 text-gray-600 dark:text-gray-400">
|
||||||
|
<li>皮肤文件必须是PNG格式</li>
|
||||||
|
<li>尺寸必须符合Minecraft标准(64x32或64x64像素)</li>
|
||||||
|
<li>确保您拥有上传皮肤的版权或使用权限</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 教程项3 */}
|
||||||
|
<Card className="overflow-hidden border-blue-200 dark:border-blue-900/50">
|
||||||
|
<CardHeader className="bg-blue-50 dark:bg-blue-900/20">
|
||||||
|
<CardTitle className="text-xl text-blue-700 dark:text-blue-400">3. 管理您的皮肤</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
您可以在"个人中心"页面查看和管理您上传的所有皮肤。在这里,您可以:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-2 text-gray-600 dark:text-gray-400 ml-4 mb-4">
|
||||||
|
<li>设置某个皮肤为当前使用的皮肤</li>
|
||||||
|
<li>编辑皮肤的名称和描述</li>
|
||||||
|
<li>删除不再需要的皮肤</li>
|
||||||
|
<li>查看皮肤的上传历史和使用统计</li>
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 教程项4 */}
|
||||||
|
<Card className="overflow-hidden border-blue-200 dark:border-blue-900/50">
|
||||||
|
<CardHeader className="bg-blue-50 dark:bg-blue-900/20">
|
||||||
|
<CardTitle className="text-xl text-blue-700 dark:text-blue-400">4. 下载和使用他人的皮肤</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
浏览皮肤库,找到您喜欢的皮肤,点击进入详情页面,然后点击"下载皮肤"按钮将皮肤保存到本地。下载后,您可以通过以下步骤在游戏中使用:
|
||||||
|
</p>
|
||||||
|
<ol className="list-decimal list-inside space-y-2 text-gray-600 dark:text-gray-400 ml-4 mb-6">
|
||||||
|
<li>打开Minecraft启动器</li>
|
||||||
|
<li>点击"皮肤"选项卡</li>
|
||||||
|
<li>点击"浏览"按钮,选择下载的皮肤文件</li>
|
||||||
|
<li>点击"保存"按钮应用新皮肤</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div className="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg border border-blue-200 dark:border-blue-900/50">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
<strong>提示:</strong> 对于在多人服务器中使用自定义皮肤,您可能需要配置Yggdrasil验证。请参考下一节Yggdrasil教程了解更多信息。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 教程项5 */}
|
||||||
|
<Card className="overflow-hidden border-blue-200 dark:border-blue-900/50">
|
||||||
|
<CardHeader className="bg-blue-50 dark:bg-blue-900/20">
|
||||||
|
<CardTitle className="text-xl text-blue-700 dark:text-blue-400">5. Yggdrasil验证简介</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
HITWH.GAMES平台提供了Yggdrasil验证服务,这是一个兼容Minecraft身份验证协议的系统,允许您在第三方服务器上使用您的自定义皮肤。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-4 mb-6">
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white">为什么需要Yggdrasil验证?</h4>
|
||||||
|
<ul className="list-disc list-inside space-y-2 text-gray-600 dark:text-gray-400 ml-4">
|
||||||
|
<li>在支持外置登录的Minecraft服务器上显示您的自定义皮肤</li>
|
||||||
|
<li>跨服务器同步您的皮肤设置</li>
|
||||||
|
<li>增强您的账号安全性</li>
|
||||||
|
<li>支持多角色管理功能</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-green-50 dark:bg-green-900/20 p-4 rounded-lg border border-green-200 dark:border-green-900/50">
|
||||||
|
<div className="flex items-start gap-3">
|
||||||
|
<svg className="w-5 h-5 text-green-600 dark:text-green-400 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||||
|
</svg>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
我们提供了便捷的<span className="font-semibold">拖拽配置</span>功能,可以通过将配置卡片拖放到支持的启动器(如HMCL、PCL)上快速完成Yggdrasil服务配置。详情请查看下一节Yggdrasil教程。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 教程项6 */}
|
||||||
|
<Card className="overflow-hidden border-blue-200 dark:border-blue-900/50">
|
||||||
|
<CardHeader className="bg-blue-50 dark:bg-blue-900/20">
|
||||||
|
<CardTitle className="text-xl text-blue-700 dark:text-blue-400">6. 社区功能</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
我们的平台不仅是一个皮肤管理工具,还是一个Minecraft玩家社区。您可以:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-2 text-gray-600 dark:text-gray-400 ml-4">
|
||||||
|
<li>分享您创作的皮肤给其他玩家</li>
|
||||||
|
<li>查看和下载热门皮肤</li>
|
||||||
|
<li>收藏您喜欢的皮肤作品</li>
|
||||||
|
<li>与其他玩家交流皮肤制作技巧</li>
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 页面导航按钮 */}
|
||||||
|
<div className="flex justify-between items-center mt-12">
|
||||||
|
<Link href="/help">
|
||||||
|
<Button variant="ghost" className="text-gray-600 dark:text-gray-400">
|
||||||
|
← 返回帮助中心
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Link href="/help/yggdrasil">
|
||||||
|
<Button className="bg-purple-600 hover:bg-purple-700 text-white">
|
||||||
|
下一节:Yggdrasil教程 →
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
196
src/app/help/customskinloader/page.tsx
Normal file
196
src/app/help/customskinloader/page.tsx
Normal file
@@ -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 (
|
||||||
|
<main className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 py-12">
|
||||||
|
<div className="container mx-auto px-4 max-w-4xl">
|
||||||
|
{/* 页面导航 */}
|
||||||
|
<div className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||||||
|
<Link href="/" className="hover:text-green-600 dark:hover:text-green-400">首页</Link>
|
||||||
|
<span>/</span>
|
||||||
|
<Link href="/help" className="hover:text-green-600 dark:hover:text-green-400">帮助中心</Link>
|
||||||
|
<span>/</span>
|
||||||
|
<span className="text-green-600 dark:text-green-400">CustomSkinLoader教程</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 页面标题 */}
|
||||||
|
<div className="mb-12">
|
||||||
|
<h1 className="text-3xl font-bold text-green-700 dark:text-green-400 mb-4">CustomSkinLoader教程</h1>
|
||||||
|
<p className="text-xl text-gray-600 dark:text-gray-400">
|
||||||
|
详细介绍如何配置CustomSkinLoader,在客户端加载自定义皮肤
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 教程内容列表 */}
|
||||||
|
<div className="space-y-8">
|
||||||
|
{/* 教程项1 */}
|
||||||
|
<Card className="overflow-hidden border-green-200 dark:border-green-900/50">
|
||||||
|
<CardHeader className="bg-green-50 dark:bg-green-900/20">
|
||||||
|
<CardTitle className="text-xl text-green-700 dark:text-green-400">1. 什么是CustomSkinLoader?</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
CustomSkinLoader是一个Minecraft客户端模组,允许玩家在不依赖Mojang官方服务器的情况下加载自定义皮肤。它支持从多个自定义来源获取皮肤,包括我们的皮肤平台。无论您是使用离线模式还是在不支持皮肤的服务器上玩游戏,CustomSkinLoader都能帮助您显示自己喜欢的皮肤。
|
||||||
|
</p>
|
||||||
|
<div className="bg-cyan-50 dark:bg-cyan-900/20 p-4 rounded-lg border border-cyan-200 dark:border-cyan-900/50">
|
||||||
|
<h4 className="font-semibold text-cyan-700 dark:text-cyan-400 mb-2">主要特性:</h4>
|
||||||
|
<ul className="list-disc list-inside space-y-1 text-gray-600 dark:text-gray-400">
|
||||||
|
<li>从多个来源加载皮肤和披风</li>
|
||||||
|
<li>支持离线模式和非官方服务器</li>
|
||||||
|
<li>自定义加载优先级</li>
|
||||||
|
<li>支持多种皮肤服务格式</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 教程项2 */}
|
||||||
|
<Card className="overflow-hidden border-green-200 dark:border-green-900/50">
|
||||||
|
<CardHeader className="bg-green-50 dark:bg-green-900/20">
|
||||||
|
<CardTitle className="text-xl text-green-700 dark:text-green-400">2. 安装CustomSkinLoader</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white">Forge/Fabric安装方法:</h4>
|
||||||
|
<ol className="list-decimal list-inside space-y-2 text-gray-600 dark:text-gray-400 ml-4">
|
||||||
|
<li>确保您已经安装了Minecraft Forge或Fabric</li>
|
||||||
|
<li>下载与您Minecraft版本兼容的CustomSkinLoader模组文件(.jar格式)</li>
|
||||||
|
<li>将模组文件放入Minecraft的mods文件夹中
|
||||||
|
<ul className="list-disc list-inside ml-8 mt-2">
|
||||||
|
<li>Windows: <code className="bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">%appdata%\.minecraft\mods</code></li>
|
||||||
|
<li>macOS: <code className="bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">~/Library/Application Support/minecraft/mods</code></li>
|
||||||
|
<li>Linux: <code className="bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">~/.minecraft/mods</code></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>启动Minecraft,模组会自动生成配置文件</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white">LiteLoader安装方法:</h4>
|
||||||
|
<ol className="list-decimal list-inside space-y-2 text-gray-600 dark:text-gray-400 ml-4">
|
||||||
|
<li>安装LiteLoader</li>
|
||||||
|
<li>下载CustomSkinLoader的LiteLoader版本</li>
|
||||||
|
<li>将模组放入liteloader文件夹中</li>
|
||||||
|
<li>启动Minecraft</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 教程项3 */}
|
||||||
|
<Card className="overflow-hidden border-green-200 dark:border-green-900/50">
|
||||||
|
<CardHeader className="bg-green-50 dark:bg-green-900/20">
|
||||||
|
<CardTitle className="text-xl text-green-700 dark:text-green-400">3. 配置OurSkin服务</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
安装完成后,您需要配置CustomSkinLoader以使用我们的皮肤服务。以下是详细步骤:
|
||||||
|
</p>
|
||||||
|
<ol className="list-decimal list-inside space-y-3 text-gray-600 dark:text-gray-400 ml-4">
|
||||||
|
<li>启动Minecraft一次,让模组生成配置文件</li>
|
||||||
|
<li>关闭Minecraft,找到CustomSkinLoader的配置文件
|
||||||
|
<ul className="list-disc list-inside ml-8 mt-2">
|
||||||
|
<li>路径:<code className="bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">.minecraft/config/CustomSkinLoader/CustomSkinLoader.json</code></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>使用文本编辑器打开配置文件</li>
|
||||||
|
<li>将我们的皮肤服务添加到加载列表中</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div className="bg-gray-50 dark:bg-gray-900/50 p-4 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">配置文件示例:</h4>
|
||||||
|
<pre className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm">
|
||||||
|
{`{
|
||||||
|
"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
|
||||||
|
}`}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 教程项4 */}
|
||||||
|
<Card className="overflow-hidden border-green-200 dark:border-green-900/50">
|
||||||
|
<CardHeader className="bg-green-50 dark:bg-green-900/20">
|
||||||
|
<CardTitle className="text-xl text-green-700 dark:text-green-400">4. 高级配置与故障排除</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white">自定义加载优先级:</h4>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
CustomSkinLoader会按照配置文件中服务的顺序尝试加载皮肤。将我们的服务放在列表顶部,可以优先使用我们平台的皮肤。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white">常见问题解决:</h4>
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-900/50">
|
||||||
|
<h5 className="font-semibold text-red-700 dark:text-red-400 mb-1">问题:皮肤未显示</h5>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 text-sm">
|
||||||
|
解决方法:检查配置文件中的URL是否正确,确认您的网络连接正常,尝试在游戏中按F5刷新皮肤。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-900/50">
|
||||||
|
<h5 className="font-semibold text-red-700 dark:text-red-400 mb-1">问题:模组加载错误</h5>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 text-sm">
|
||||||
|
解决方法:确保您下载的模组版本与您的Minecraft版本兼容,检查是否有冲突的其他模组。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-3 bg-red-50 dark:bg-red-900/20 rounded-lg border border-red-200 dark:border-red-900/50">
|
||||||
|
<h5 className="font-semibold text-red-700 dark:text-red-400 mb-1">问题:配置文件不生效</h5>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 text-sm">
|
||||||
|
解决方法:确保配置文件格式正确(JSON格式),检查是否有语法错误,修改后重启Minecraft。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white">客户端命令:</h4>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
在游戏中,您可以使用以下命令管理CustomSkinLoader:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-2 text-gray-600 dark:text-gray-400 ml-4">
|
||||||
|
<li><code className="bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">/customskinloader reload</code> - 重新加载配置</li>
|
||||||
|
<li><code className="bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">/customskinloader setskin [username] [skinUrl]</code> - 手动设置皮肤</li>
|
||||||
|
<li><code className="bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded">/customskinloader status</code> - 查看加载状态</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 页面导航按钮 */}
|
||||||
|
<div className="flex justify-between items-center mt-12">
|
||||||
|
<Link href="/help/multilogin">
|
||||||
|
<Button variant="ghost" className="text-gray-600 dark:text-gray-400">
|
||||||
|
← 上一节:MultiLogin教程
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Link href="/help">
|
||||||
|
<Button variant="default" className="bg-emerald-600 hover:bg-emerald-700 text-white">
|
||||||
|
返回帮助中心
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
245
src/app/help/multilogin/page.tsx
Normal file
245
src/app/help/multilogin/page.tsx
Normal file
@@ -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 (
|
||||||
|
<main className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 py-12">
|
||||||
|
<div className="container mx-auto px-4 max-w-4xl">
|
||||||
|
{/* 页面导航 */}
|
||||||
|
<div className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||||||
|
<Link href="/" className="hover:text-amber-600 dark:hover:text-amber-400">首页</Link>
|
||||||
|
<span>/</span>
|
||||||
|
<Link href="/help" className="hover:text-amber-600 dark:hover:text-amber-400">帮助中心</Link>
|
||||||
|
<span>/</span>
|
||||||
|
<span className="text-amber-600 dark:text-amber-400">MultiLogin教程</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 页面标题 */}
|
||||||
|
<div className="mb-12">
|
||||||
|
<h1 className="text-3xl font-bold text-amber-700 dark:text-amber-400 mb-4">MultiLogin教程</h1>
|
||||||
|
<p className="text-xl text-gray-600 dark:text-gray-400">
|
||||||
|
学习如何在服务器中配置MultiLogin来支持多种登录方式共存
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 教程内容列表 */}
|
||||||
|
<div className="space-y-8">
|
||||||
|
{/* 什么是MultiLogin */}
|
||||||
|
<Card className="overflow-hidden border-amber-200 dark:border-amber-900/50">
|
||||||
|
<CardHeader className="bg-amber-50 dark:bg-amber-900/20">
|
||||||
|
<CardTitle className="text-xl text-amber-700 dark:text-amber-400">什么是MultiLogin?</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
||||||
|
MultiLogin 是一款服务端插件,功能是让您的服务器支持正版与多种外置登录共存,用来连接两个或多个外置验证服务器下的玩家,让他们能在一起玩。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
||||||
|
外置登录给服务器提供了类似正版的管理和登录方式,但对于一个拥有 Minecraft 正版账号的玩家来说,正版登录是更加简单方便的选择。使用MultiLogin,您可以让这两种登录方式在服务器上共存。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="bg-yellow-50 dark:bg-yellow-900/20 p-4 rounded-lg border border-yellow-200 dark:border-yellow-900/50 mb-6">
|
||||||
|
<h4 className="font-semibold text-yellow-700 dark:text-yellow-400 mb-2">⚠️ 这并不能代替正版</h4>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
请始终考虑购买正版的 Minecraft。使用正版的 Minecraft 可以为你提供更省心的游玩体验。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-red-50 dark:bg-red-900/20 p-4 rounded-lg border border-red-200 dark:border-red-900/50">
|
||||||
|
<h4 className="font-semibold text-red-700 dark:text-red-400 mb-2">注意:单服务端版本已过时</h4>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
MultiLogin 自 0.6.12 版本后不再分发 Bukkit、Bungee 的本体,且在后续暂停维护。我们建议您改用 Velocity 以获得更好的体验。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Velocity配置 */}
|
||||||
|
<Card className="overflow-hidden border-amber-200 dark:border-amber-900/50">
|
||||||
|
<CardHeader className="bg-amber-50 dark:bg-amber-900/20">
|
||||||
|
<CardTitle className="text-xl text-amber-700 dark:text-amber-400">Velocity配置 (Minecraft 1.13+)</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
||||||
|
Modern forwarding 是 Velocity 支持的一种独创格式。它以高效的二进制格式转发所有玩家信息。但是,它仅适用于 Minecraft 1.13 或更高版本。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
||||||
|
本案例使用 Velocity + Paper + MultiLogin 作为示例。具体的 Velocity 配置请结合参考 Velocity 文档。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xl font-semibold text-gray-800 dark:text-white mb-4">1. 配置 Velocity 转发</h4>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h5 className="font-semibold text-gray-700 dark:text-gray-300 mb-3">对于 Velocity:</h5>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-3">
|
||||||
|
检查 velocity.toml 文件,确保 online-mode 项的值为 true 👈
|
||||||
|
</p>
|
||||||
|
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm">
|
||||||
|
<pre>{`# velocity.toml
|
||||||
|
# Should we authenticate players with Mojang? By default, this is on.
|
||||||
|
online-mode = true`}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h5 className="font-semibold text-gray-700 dark:text-gray-300 mb-3">对于 Paper 子服:</h5>
|
||||||
|
<ul className="list-disc list-inside space-y-3 text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
<li>检查子服务器的 server.properties 文件,确保 online-mode 项的值为 false 👈<br/>
|
||||||
|
这会阻止子服务器对玩家进行身份验证,Velocity 将会承担起对玩家进行身份验证的职责。</li>
|
||||||
|
<li>检查子服务器的 config/paper-global.yaml 中的 online-mode 项的值为 true 👈<br/>
|
||||||
|
这个值在任何情况下都应该与 velocity.toml 中的 online-mode 项的值保持一致。</li>
|
||||||
|
<li>对于 Paper 1.18.2 或更低版本,online-mode 将会位于 settings.velocity-support.online-mode。</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-500 mb-1">server.properties</p>
|
||||||
|
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm">
|
||||||
|
<pre>online-mode=false</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-500 mb-1">config/paper-global.yaml</p>
|
||||||
|
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm">
|
||||||
|
<pre>online-mode: true</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h4 className="text-xl font-semibold text-gray-800 dark:text-white mb-4">2. 配置 MultiLogin</h4>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h5 className="font-semibold text-gray-700 dark:text-gray-300 mb-3">对于 Velocity:</h5>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
安装 MultiLogin 插件,并按照以下步骤创建配置文件:
|
||||||
|
</p>
|
||||||
|
<ol className="list-decimal list-inside space-y-3 text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
<li>创建以下两个文件用于支持 Mojang和LittleSkin 登录</li>
|
||||||
|
<ul className="list-disc list-inside ml-8 mt-2 text-gray-600 dark:text-gray-400">
|
||||||
|
<li>multilogin/services/offical.yml</li>
|
||||||
|
<li>multilogin/services/littleskin.yml</li>
|
||||||
|
</ul>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-500 mb-1">multilogin/services/offical.yml</p>
|
||||||
|
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm">
|
||||||
|
<pre>{`# 官方验证服务配置
|
||||||
|
# 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`}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-500 mb-1">multilogin/services/littleskin.yml</p>
|
||||||
|
<div className="bg-gray-900 text-gray-100 p-4 rounded-lg overflow-x-auto text-sm">
|
||||||
|
<pre>{`# 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'`}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h5 className="font-semibold text-gray-700 dark:text-gray-300 mb-3">对于子服务器:</h5>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
仅需在 Velocity 正确配置 MultiLogin 插件即可,无需对子服进行修改。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 更多资源 */}
|
||||||
|
<Card className="overflow-hidden border-amber-200 dark:border-amber-900/50">
|
||||||
|
<CardHeader className="bg-amber-50 dark:bg-amber-900/20">
|
||||||
|
<CardTitle className="text-xl text-amber-700 dark:text-amber-400">更多资源</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
如需了解更多关于 MultiLogin 的配置和使用,请参考以下资源:
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-3">
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/CaaMoe/MultiLogin/wiki" target="_blank" rel="noopener noreferrer" className="text-blue-600 dark:text-blue-400 hover:underline flex items-center">
|
||||||
|
<svg className="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm6.066 9.645c.183 4.04-2.83 8.544-8.164 8.544-1.622 0-3.131-.476-4.402-1.291 1.524.18 3.045-.244 4.252-1.189-1.256-.023-2.317-.854-2.684-1.995.451.086.895.061 1.298-.049-1.381-.278-2.335-1.522-2.304-2.853.388.215.83.344 1.301.359-1.279-.855-1.641-2.544-.889-3.835 1.416 1.738 3.533 2.881 5.92 3.001-.419-1.796.944-3.527 2.799-3.527.825 0 1.572.349 2.096.907.654-.128 1.27-.368 1.824-.697-.215.671-.67 1.233-1.263 1.589.581-.07 1.135-.224 1.649-.453-.384.578-.87 1.084-1.433 1.489z"/>
|
||||||
|
</svg>
|
||||||
|
MultiLogin GitHub Wiki
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://docs.papermc.io/velocity" target="_blank" rel="noopener noreferrer" className="text-blue-600 dark:text-blue-400 hover:underline flex items-center">
|
||||||
|
<svg className="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm6.066 9.645c.183 4.04-2.83 8.544-8.164 8.544-1.622 0-3.131-.476-4.402-1.291 1.524.18 3.045-.244 4.252-1.189-1.256-.023-2.317-.854-2.684-1.995.451.086.895.061 1.298-.049-1.381-.278-2.335-1.522-2.304-2.853.388.215.83.344 1.301.359-1.279-.855-1.641-2.544-.889-3.835 1.416 1.738 3.533 2.881 5.92 3.001-.419-1.796.944-3.527 2.799-3.527.825 0 1.572.349 2.096.907.654-.128 1.27-.368 1.824-.697-.215.671-.67 1.233-1.263 1.589.581-.07 1.135-.224 1.649-.453-.384.578-.87 1.084-1.433 1.489z"/>
|
||||||
|
</svg>
|
||||||
|
Velocity 官方文档
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/yushijinhun/authlib-injector/wiki" target="_blank" rel="noopener noreferrer" className="text-blue-600 dark:text-blue-400 hover:underline flex items-center">
|
||||||
|
<svg className="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0zm6.066 9.645c.183 4.04-2.83 8.544-8.164 8.544-1.622 0-3.131-.476-4.402-1.291 1.524.18 3.045-.244 4.252-1.189-1.256-.023-2.317-.854-2.684-1.995.451.086.895.061 1.298-.049-1.381-.278-2.335-1.522-2.304-2.853.388.215.83.344 1.301.359-1.279-.855-1.641-2.544-.889-3.835 1.416 1.738 3.533 2.881 5.92 3.001-.419-1.796.944-3.527 2.799-3.527.825 0 1.572.349 2.096.907.654-.128 1.27-.368 1.824-.697-.215.671-.67 1.233-1.263 1.589.581-.07 1.135-.224 1.649-.453-.384.578-.87 1.084-1.433 1.489z"/>
|
||||||
|
</svg>
|
||||||
|
authlib-injector Wiki
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 页面导航按钮 */}
|
||||||
|
<div className="flex justify-between items-center mt-12">
|
||||||
|
<Link href="/help/yggdrasil">
|
||||||
|
<Button variant="ghost" className="text-gray-600 dark:text-gray-400">
|
||||||
|
← 上一节:Yggdrasil教程
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Link href="/help/customskinloader">
|
||||||
|
<Button className="bg-green-600 hover:bg-green-700 text-white">
|
||||||
|
下一节:CustomSkinLoader教程 →
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
103
src/app/help/page.tsx
Normal file
103
src/app/help/page.tsx
Normal file
@@ -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 (
|
||||||
|
<main className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 py-12">
|
||||||
|
<div className="container mx-auto px-4 max-w-4xl">
|
||||||
|
{/* 页面标题 */}
|
||||||
|
<div className="text-center mb-12">
|
||||||
|
<h1 className="text-4xl font-bold text-gray-800 dark:text-white mb-4">帮助中心</h1>
|
||||||
|
<p className="text-xl text-gray-600 dark:text-gray-400">
|
||||||
|
查找关于使用我们平台的详细教程和指南
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 教程分类卡片 */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<Link href="/help/basic" className="block">
|
||||||
|
<Card className="h-full overflow-hidden hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 border-blue-200 dark:border-blue-900/50">
|
||||||
|
<CardHeader className="bg-blue-50 dark:bg-blue-900/20">
|
||||||
|
<CardTitle className="text-blue-700 dark:text-blue-400">基础教程</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
了解平台基本功能、皮肤上传与管理、用户账户设置等基础知识。
|
||||||
|
</p>
|
||||||
|
<Button className="mt-4 bg-blue-600 hover:bg-blue-700 text-white">
|
||||||
|
开始学习
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href="/help/yggdrasil" className="block">
|
||||||
|
<Card className="h-full overflow-hidden hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 border-purple-200 dark:border-purple-900/50">
|
||||||
|
<CardHeader className="bg-purple-50 dark:bg-purple-900/20">
|
||||||
|
<CardTitle className="text-purple-700 dark:text-purple-400">Yggdrasil教程</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
学习如何使用Yggdrasil验证服务,实现皮肤同步与多账号管理。
|
||||||
|
</p>
|
||||||
|
<Button className="mt-4 bg-purple-600 hover:bg-purple-700 text-white">
|
||||||
|
开始学习
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href="/help/multilogin" className="block">
|
||||||
|
<Card className="h-full overflow-hidden hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 border-amber-200 dark:border-amber-900/50">
|
||||||
|
<CardHeader className="bg-amber-50 dark:bg-amber-900/20">
|
||||||
|
<CardTitle className="text-amber-700 dark:text-amber-400">MultiLogin教程</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
掌握MultiLogin插件的配置与使用方法,实现多账户共存。
|
||||||
|
</p>
|
||||||
|
<Button className="mt-4 bg-amber-600 hover:bg-amber-700 text-white">
|
||||||
|
开始学习
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href="/help/customskinloader" className="block">
|
||||||
|
<Card className="h-full overflow-hidden hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1 border-green-200 dark:border-green-900/50">
|
||||||
|
<CardHeader className="bg-green-50 dark:bg-green-900/20">
|
||||||
|
<CardTitle className="text-green-700 dark:text-green-400">CustomSkinLoader教程</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
详细介绍如何配置CustomSkinLoader,在客户端加载自定义皮肤。
|
||||||
|
</p>
|
||||||
|
<Button className="mt-4 bg-green-600 hover:bg-green-700 text-white">
|
||||||
|
开始学习
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 其他资源 */}
|
||||||
|
<div className="mt-16 bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm rounded-2xl p-8 shadow-lg border border-gray-200 dark:border-gray-700">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-800 dark:text-white mb-6">其他资源</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
如果您在使用过程中遇到问题,可以:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside space-y-2 text-gray-600 dark:text-gray-400 ml-4">
|
||||||
|
<li>查看我们的 <Link href="#" className="text-blue-600 dark:text-blue-400 hover:underline">常见问题解答</Link></li>
|
||||||
|
<li>联系我们的 <Link href="#" className="text-blue-600 dark:text-blue-400 hover:underline">客服支持</Link></li>
|
||||||
|
<li>加入我们的 <Link href="#" className="text-blue-600 dark:text-blue-400 hover:underline">社区论坛</Link> 讨论</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
51
src/app/help/yggdrasil/DragConfigCard.tsx
Normal file
51
src/app/help/yggdrasil/DragConfigCard.tsx
Normal file
@@ -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<HTMLDivElement>) => {
|
||||||
|
// 创建适合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 (
|
||||||
|
<div
|
||||||
|
className={`bg-green-50 dark:bg-green-900/20 p-6 rounded-lg border-2 border-dashed border-green-300 dark:border-green-700 mb-8 cursor-move hover:bg-green-100 dark:hover:bg-green-900/30 transition-colors ${isDragging ? 'opacity-50' : 'opacity-100'}`}
|
||||||
|
draggable="true"
|
||||||
|
onDragStart={handleDragStart}
|
||||||
|
onDragEnd={handleDragEnd}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center gap-4">
|
||||||
|
<svg className="w-10 h-10 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||||
|
</svg>
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-green-700 dark:text-green-400">{serviceName}</h4>
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-400">将此卡片拖到HMCL/PCL启动器以自动配置</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 p-3 bg-white/50 dark:bg-gray-800/50 rounded-md text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
<p className="flex items-center gap-2">
|
||||||
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
自动配置Yggdrasil服务器地址和名称
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
200
src/app/help/yggdrasil/page.tsx
Normal file
200
src/app/help/yggdrasil/page.tsx
Normal file
@@ -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 (
|
||||||
|
<main className="min-h-screen bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 py-12">
|
||||||
|
<div className="container mx-auto px-4 max-w-4xl">
|
||||||
|
{/* 页面导航 */}
|
||||||
|
<div className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||||||
|
<Link href="/" className="hover:text-purple-600 dark:hover:text-purple-400">首页</Link>
|
||||||
|
<span>/</span>
|
||||||
|
<Link href="/help" className="hover:text-purple-600 dark:hover:text-purple-400">帮助中心</Link>
|
||||||
|
<span>/</span>
|
||||||
|
<span className="text-purple-600 dark:text-purple-400">Yggdrasil教程</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 页面标题 */}
|
||||||
|
<div className="mb-12">
|
||||||
|
<h1 className="text-3xl font-bold text-purple-700 dark:text-purple-400 mb-4">Yggdrasil教程</h1>
|
||||||
|
<p className="text-xl text-gray-600 dark:text-gray-400">
|
||||||
|
学习如何使用Yggdrasil验证服务,实现皮肤同步与多账号管理
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 教程内容列表 */}
|
||||||
|
<div className="space-y-8">
|
||||||
|
{/* 教程项1 */}
|
||||||
|
<Card className="overflow-hidden border-purple-200 dark:border-purple-900/50">
|
||||||
|
<CardHeader className="bg-purple-50 dark:bg-purple-900/20">
|
||||||
|
<CardTitle className="text-xl text-purple-700 dark:text-purple-400">1. 什么是Yggdrasil验证服务?</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
Yggdrasil是Minecraft使用的身份验证服务协议,允许玩家在第三方服务器上验证其身份并使用自定义皮肤。我们的平台提供兼容Yggdrasil协议的身份验证服务,让您可以在支持的服务器上使用您的自定义皮肤。
|
||||||
|
</p>
|
||||||
|
<div className="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg border border-blue-200 dark:border-blue-900/50">
|
||||||
|
<h4 className="font-semibold text-blue-700 dark:text-blue-400 mb-2">优势:</h4>
|
||||||
|
<ul className="list-disc list-inside space-y-1 text-gray-600 dark:text-gray-400">
|
||||||
|
<li>在非官方服务器上使用自定义皮肤</li>
|
||||||
|
<li>跨服务器同步皮肤设置</li>
|
||||||
|
<li>增强账号安全性</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 教程项2 */}
|
||||||
|
<Card className="overflow-hidden border-purple-200 dark:border-purple-900/50">
|
||||||
|
<CardHeader className="bg-purple-50 dark:bg-purple-900/20">
|
||||||
|
<CardTitle className="text-xl text-purple-700 dark:text-purple-400">2. 客户端配置</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-4">客户端配置说明</h3>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-6">
|
||||||
|
要在Minecraft客户端中使用HITWH.GAMES皮肤系统,您需要配置游戏启动器以使用我们的Yggdrasil验证服务。下面是几种常用启动器的配置方法。
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg border border-blue-200 dark:border-blue-900/50 mb-6">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
无论使用哪种启动器,您都需要先在HITWH.GAMES网站上注册账号并创建角色,才能在游戏中看到您的自定义皮肤。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-4">推荐:快速配置方法</h3>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
支持拖放功能的启动器(如HMCL、PCL)可以直接通过拖放下方卡片到启动器来快速完成配置:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* 拖放配置卡片 */}
|
||||||
|
<DragConfigCard
|
||||||
|
authUrl="https://skin.littlelan.cn/api/yggdrasil"
|
||||||
|
serviceName="HITWH.GAMES 皮肤系统"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">操作方法:</h4>
|
||||||
|
<ol className="list-decimal list-inside space-y-2 text-gray-600 dark:text-gray-400 ml-4 mb-6">
|
||||||
|
<li>打开您的启动器(如HMCL、PCL等)</li>
|
||||||
|
<li>将上方绿色卡片拖放到启动器窗口中</li>
|
||||||
|
<li>在弹出的确认窗口中点击"确定"</li>
|
||||||
|
<li>使用您的HITWH.GAMES账号登录</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-4">HMCL启动器配置</h3>
|
||||||
|
<ol className="list-decimal list-inside space-y-3 text-gray-600 dark:text-gray-400 ml-4 mb-8">
|
||||||
|
<li>打开HMCL启动器</li>
|
||||||
|
<li>点击右上角的"账户"按钮</li>
|
||||||
|
<li>点击"添加认证服务器"</li>
|
||||||
|
<li>
|
||||||
|
在"服务器地址"中输入:<br/>
|
||||||
|
<code className="bg-gray-100 dark:bg-gray-800 px-3 py-2 rounded block mt-2">https://skin.littlelan.cn/api/yggdrasil</code>
|
||||||
|
</li>
|
||||||
|
<li>点击"下一步",然后输入您在HITWH.GAMES的用户名和密码</li>
|
||||||
|
<li>完成后点击"登录"即可使用您的HITWH.GAMES账号</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-4">PCL启动器配置</h3>
|
||||||
|
<ol className="list-decimal list-inside space-y-3 text-gray-600 dark:text-gray-400 ml-4 mb-8">
|
||||||
|
<li>打开PCL启动器</li>
|
||||||
|
<li>点击"设置"</li>
|
||||||
|
<li>找到"登录设置"部分</li>
|
||||||
|
<li>选择"外置登录"</li>
|
||||||
|
<li>
|
||||||
|
在"认证服务器"中输入:<br/>
|
||||||
|
<code className="bg-gray-100 dark:bg-gray-800 px-3 py-2 rounded block mt-2">https://skin.littlelan.cn/api/yggdrasil</code>
|
||||||
|
</li>
|
||||||
|
<li>点击"保存"</li>
|
||||||
|
<li>返回主界面,输入您在HITWH.GAMES的用户名和密码进行登录</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3 className="text-lg font-semibold text-gray-800 dark:text-white mb-4">常见问题</h3>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="p-4 bg-gray-50 dark:bg-gray-900/50 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">无法看到皮肤?</h4>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
确保您已经在网站上上传了皮肤并分配给了您的角色。某些服务器可能禁用了自定义皮肤功能,这种情况下您的皮肤可能不会显示。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-gray-50 dark:bg-gray-900/50 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">登录失败?</h4>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
请检查您的用户名和密码是否正确,以及认证服务器地址是否填写正确。如果问题持续存在,请联系我们的客服支持。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 教程项3 */}
|
||||||
|
<Card className="overflow-hidden border-purple-200 dark:border-purple-900/50">
|
||||||
|
<CardHeader className="bg-purple-50 dark:bg-purple-900/20">
|
||||||
|
<CardTitle className="text-xl text-purple-700 dark:text-purple-400">3. 管理多账号</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">
|
||||||
|
我们的Yggdrasil服务支持多账号管理,您可以在一个主账户下创建多个子账户,每个子账户可以有不同的皮肤设置。
|
||||||
|
</p>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white">创建子账户:</h4>
|
||||||
|
<ol className="list-decimal list-inside space-y-2 text-gray-600 dark:text-gray-400 ml-4">
|
||||||
|
<li>登录您的主账户</li>
|
||||||
|
<li>进入"账户管理"页面</li>
|
||||||
|
<li>点击"创建子账户"按钮</li>
|
||||||
|
<li>设置子账户的用户名和密码</li>
|
||||||
|
<li>为子账户上传或选择皮肤</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 教程项4 */}
|
||||||
|
<Card className="overflow-hidden border-purple-200 dark:border-purple-900/50">
|
||||||
|
<CardHeader className="bg-purple-50 dark:bg-purple-900/20">
|
||||||
|
<CardTitle className="text-xl text-purple-700 dark:text-purple-400">4. 高级问题与故障排除</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="p-4 bg-gray-50 dark:bg-gray-900/50 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">问题:服务器无法验证我的Yggdrasil账号</h4>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
解决方法:请确认服务器已正确配置Yggdrasil验证功能。服务器管理员需要在服务器配置中添加我们的验证服务器地址。如果您是服务器管理员,请参考服务器的Yggdrasil集成文档。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-gray-50 dark:bg-gray-900/50 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">问题:使用代理服务器时无法连接Yggdrasil服务</h4>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
解决方法:请确保您的代理服务器正确配置了HTTPS支持,并且没有阻止与我们Yggdrasil服务器的连接。您可能需要在代理设置中添加例外规则。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-gray-50 dark:bg-gray-900/50 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||||
|
<h4 className="font-semibold text-gray-800 dark:text-white mb-2">问题:更换皮肤后游戏中没有立即更新</h4>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
解决方法:皮肤更新可能需要一些时间才能在所有服务器上同步。尝试完全退出并重新启动游戏,或者尝试连接到不同的服务器以刷新皮肤缓存。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 页面导航按钮 */}
|
||||||
|
<div className="flex justify-between items-center mt-12">
|
||||||
|
<Link href="/help/basic">
|
||||||
|
<Button variant="ghost" className="text-gray-600 dark:text-gray-400">
|
||||||
|
← 上一节:基础教程
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Link href="/help/multilogin">
|
||||||
|
<Button className="bg-amber-600 hover:bg-amber-700 text-white">
|
||||||
|
下一节:MultiLogin教程 →
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ import './globals.css';
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { Inter } from 'next/font/google';
|
import { Inter } from 'next/font/google';
|
||||||
import Navbar from '@/components/Navbar';
|
import Navbar from '@/components/Navbar';
|
||||||
|
import { getServerSession } from 'next-auth';
|
||||||
|
import { authOptions } from '@/lib/api/auth';
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] });
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
const grassIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAABCElEQVRYw+2W0Q6DIAxAZ3b/r7y7L0QNlKEFdLfEFyVQ+pVSCgBwB4DfGz4fQJxzGGOYc34d4DkAVVVVdQqgqioiIuccxhgAQFVVMMYg50wAY4w5gDEGKaWl0nPOoZQiAGPM7wGMMXDOwVqLtm2Rc0bXdTDGwDmHnPN/ADHGdF2HEAJijOj7HjFGpJTQ9z36vkeMESkl9H0PYwystYgxLgHGGHDOwTmHtm3hnEPbtmCMgXO+6DkA1lo45+C9R0oJwzAgpQRrLZxz8N4jpYRhGJBSgrUWzjlYa5cAzjkYY2CMQdM0YIyBMQbOOQEAwzCgrmtUVYW6rjEMA4ZhgDEGTdOAMQbOOQEA4ziiqio0TYO2bTGbYRiQc0bTNGiaBm3bYjY5Z4zjiKqqMAwD6rqGtRYpJYzjiJwzrLVomgZt22I2OWeM44i6rjEMA+q6RkoJ4zgi5wxrLZqmwWyGYUDOGXVdYxxH1HWNlBLGcUTOGdZaNE2D2QzDgJwz6rrGOI6o6xopJYzjiJwzrLVomgazGYYBOWfUdY1xHFHXNVJKGMfxfwB1XWO2OWeM44i6rjGOI+q6RkoJ4zgi5wxrLZqmwWyGYUDOGXVdYxxH1HWNlBLGcUTOGdZaNE2D2QzDgJwz6rrGOI6o6xopJYzj+AAQY4QQQgghhBBCCCGEkHv7AQm0VqDdH3i9AAAAAElFTkSuQmCC';
|
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,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
// 在服务器端获取会话状态
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
<head>
|
<head>
|
||||||
@@ -28,9 +33,14 @@ export default function RootLayout({
|
|||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
/>
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body className={`${inter.className} bg-gray-50 dark:bg-gray-900`}>
|
<body className={`${inter.className} bg-gray-50 dark:bg-gray-900 min-h-screen flex flex-col`}>
|
||||||
<Navbar />
|
<Navbar session={session} />
|
||||||
<main className="container mx-auto px-4 py-8">{children}</main>
|
<main className="flex-grow container mx-auto px-4 py-6 sm:py-8">{children}</main>
|
||||||
|
<footer className="bg-gray-800 text-white py-4 text-center text-sm">
|
||||||
|
<div className="container mx-auto px-4">
|
||||||
|
© 2024 我的世界皮肤库 - 为Minecraft玩家打造的皮肤分享平台
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
285
src/app/page.tsx
285
src/app/page.tsx
@@ -1,54 +1,153 @@
|
|||||||
// src/app/page.tsx
|
// src/app/page.tsx
|
||||||
|
//这个是介绍首页页面
|
||||||
|
'use client';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
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 <style>{ServerInfoBubbleStyles}</style>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 功能卡片组件
|
||||||
|
function FeatureCard({ title, description, icon }: { title: string; description: string; icon: string }) {
|
||||||
|
return (
|
||||||
|
<Card className="overflow-hidden transition-all duration-300 hover:shadow-xl bg-white dark:bg-gray-800 border-0 shadow-lg h-full flex flex-col">
|
||||||
|
<CardHeader className="bg-gradient-to-r from-emerald-50 to-teal-50 dark:from-emerald-900/20 dark:to-teal-900/20 border-b border-emerald-100 dark:border-emerald-900/30 pb-5">
|
||||||
|
<div className="text-4xl mb-3">{icon}</div>
|
||||||
|
<CardTitle className="text-xl font-bold text-gray-800 dark:text-white">{title}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="pt-6 pb-6 px-6 flex-grow">
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">{description}</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
return (
|
const titleRef = useRef<HTMLHeadingElement>(null);
|
||||||
<div className="min-h-screen bg-gradient-to-br from-green-50 to-cyan-100 dark:from-gray-900 dark:to-gray-800">
|
const descRef = useRef<HTMLParagraphElement>(null);
|
||||||
{/* 导航栏 */}
|
|
||||||
<nav className="bg-white dark:bg-gray-800 shadow-md py-4 px-6 flex justify-between items-center">
|
|
||||||
<div className="flex items-center space-x-2">
|
|
||||||
<div className="bg-gray-200 border-2 border-dashed rounded-xl w-10 h-10" />
|
|
||||||
<span className="text-xl font-bold text-green-600 dark:text-green-400">HITWH皮肤库</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex space-x-4">
|
|
||||||
<Button asChild variant="outline" className="border-green-600 text-green-600 hover:bg-green-50">
|
|
||||||
<Link href="/login">登录</Link>
|
|
||||||
</Button>
|
|
||||||
<Button asChild className="bg-green-600 hover:bg-green-700">
|
|
||||||
<Link href="/register">注册账号</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{/* 区域 */}
|
// 打字机效果状态
|
||||||
<section className="container mx-auto px-4 py-16 flex flex-col md:flex-row items-center justify-between">
|
const [displayText, setDisplayText] = useState('');
|
||||||
<div className="md:w-1/2 mb-12 md:mb-0">
|
const [displayHighlightText, setDisplayHighlightText] = useState('');
|
||||||
<h1 className="text-4xl md:text-5xl font-bold text-gray-800 dark:text-white mb-6">
|
const [cursorVisible, setCursorVisible] = useState(true);
|
||||||
创建、分享和管理你的
|
const fullText = '创建、分享和管理你的';
|
||||||
<span className="text-green-600 dark:text-green-400"> Minecraft 皮肤</span>
|
const highlightText = 'Minecraft 皮肤';
|
||||||
|
const [isTypingMainText, setIsTypingMainText] = useState(true);
|
||||||
|
const [typingComplete, setTypingComplete] = useState(false);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800 relative overflow-hidden">
|
||||||
|
{/* 服务器信息气泡组件 */}
|
||||||
|
<ServerInfoBubble zIndex={1000} dragDistanceLimit={300} />
|
||||||
|
<BubbleStyles />
|
||||||
|
|
||||||
|
{/* 背景装饰元素 - 渐变模糊效果 */}
|
||||||
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
|
<div className="absolute -top-20 -right-20 w-80 h-80 bg-emerald-400/20 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute -bottom-20 -left-20 w-96 h-96 bg-teal-500/20 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute top-1/3 left-1/4 w-64 h-64 bg-emerald-300/10 rounded-full blur-3xl"></div>
|
||||||
|
</div>
|
||||||
|
{/* 英雄区域 */}
|
||||||
|
<section className="container mx-auto px-4 py-24 flex flex-col md:flex-row items-center justify-between gap-12 relative z-10">
|
||||||
|
<div className="md:w-1/2 mb-12 md:mb-0 space-y-8">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-gray-800 dark:text-white tracking-tight leading-tight">
|
||||||
|
{displayText}
|
||||||
|
<span className="text-emerald-600 dark:text-emerald-400">
|
||||||
|
{displayHighlightText}
|
||||||
|
</span>
|
||||||
|
<span className={`inline-block w-2 h-8 ml-1 bg-gray-800 dark:bg-white transition-opacity ${cursorVisible ? 'opacity-100' : 'opacity-0'}`}>
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-lg text-gray-600 dark:text-gray-300 mb-8 max-w-lg">
|
<p className="text-lg md:text-xl text-gray-600 dark:text-gray-300 mb-8 max-w-lg leading-relaxed">
|
||||||
上传你的自定义皮肤,在3D预览中查看效果,并与HITWH的大家分享你的创作。简单、快捷!
|
上传你的自定义皮肤,查看预览效果,并与HITWH的大家分享你的创作。简单、快捷!
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row gap-4">
|
</div>
|
||||||
<Button asChild size="lg" className="text-lg bg-green-600 hover:bg-green-700 py-6">
|
<div className="flex flex-col sm:flex-row gap-5">
|
||||||
|
<Button asChild size="lg" className="text-lg bg-emerald-600 hover:bg-emerald-700 py-6 px-8 rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
||||||
<Link href="/skins/upload">立即上传皮肤</Link>
|
<Link href="/skins/upload">立即上传皮肤</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild variant="outline" size="lg" className="text-lg border-green-600 text-green-600 hover:bg-green-50 py-6">
|
<Button asChild variant="outline" size="lg" className="text-lg border-emerald-600 text-emerald-600 hover:bg-emerald-50 dark:hover:bg-emerald-900/10 py-6 px-8 rounded-xl shadow-md hover:shadow-lg transition-all duration-300">
|
||||||
<Link href="/dashboard">浏览皮肤库</Link>
|
<Link href="/user-home">用户主页</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="md:w-1/2 flex justify-center">
|
<div className="md:w-1/2 flex justify-center">
|
||||||
<div className="relative w-full max-w-md">
|
<div className="relative w-full max-w-md">
|
||||||
<div className="absolute inset-0 bg-green-500 rounded-2xl transform rotate-6"></div>
|
{/* 装饰性背景元素 */}
|
||||||
<div className="relative bg-gray-200 border-2 border-dashed rounded-xl w-full h-96 flex items-center justify-center">
|
<div className="absolute -inset-4 bg-gradient-to-br from-emerald-500 to-teal-500 rounded-3xl transform rotate-3 blur-xl opacity-20"></div>
|
||||||
<div className="text-center p-6">
|
<div className="absolute -right-6 -bottom-6 w-32 h-32 bg-emerald-400 rounded-full blur-2xl opacity-20"></div>
|
||||||
<h3 className="text-xl font-bold mb-4">皮肤预览区域</h3>
|
|
||||||
<p className="text-gray-600">这里将展示3D皮肤预览</p>
|
<div className="relative bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm rounded-2xl w-full h-96 flex items-center justify-center border border-emerald-200 dark:border-emerald-900/30 shadow-xl overflow-hidden">
|
||||||
|
{/* 背景装饰 */}
|
||||||
|
<div className="absolute top-0 left-0 right-0 h-1/3 bg-gradient-to-b from-emerald-50 to-teal-50 dark:from-emerald-900/10 dark:to-teal-900/10"></div>
|
||||||
|
<div className="absolute -bottom-16 -right-16 w-32 h-32 bg-emerald-200 dark:bg-emerald-900/20 rounded-full blur-xl"></div>
|
||||||
|
|
||||||
|
<div className="text-center p-8 relative z-10">
|
||||||
|
<div className="flex justify-center mb-6">
|
||||||
|
<Canvas2DSkinPreview
|
||||||
|
skinUrl="/test-skin.png"
|
||||||
|
size={192}
|
||||||
|
className="border-4 border-emerald-100 dark:border-emerald-800/50 rounded-lg shadow-lg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h3 className="text-2xl font-bold mb-4 text-gray-800 dark:text-white">皮肤预览区域</h3>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 max-w-xs mx-auto">上传后立即查看你的皮肤效果</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,16 +155,34 @@ export default function HomePage() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 功能特性区域 */}
|
{/* 功能特性区域 */}
|
||||||
<section className="py-16 bg-white dark:bg-gray-800">
|
<section className="py-20 bg-white dark:bg-gray-800">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<h2 className="text-3xl font-bold text-center text-gray-800 dark:text-white mb-16">
|
{/* 强调区域 */}
|
||||||
|
<div className="mb-20 rounded-2xl overflow-hidden max-w-4xl mx-auto shadow-xl">
|
||||||
|
<div className="bg-gradient-to-r from-emerald-600 via-teal-500 to-cyan-500 py-16 px-8 text-center backdrop-blur-sm">
|
||||||
|
<h3
|
||||||
|
ref={titleRef}
|
||||||
|
className="text-3xl md:text-4xl font-bold text-white mb-6 transition-all duration-1000"
|
||||||
|
>
|
||||||
|
探索无限可能的皮肤世界
|
||||||
|
</h3>
|
||||||
|
<p
|
||||||
|
ref={descRef}
|
||||||
|
className="text-xl text-white/90 max-w-3xl mx-auto transition-all duration-1000"
|
||||||
|
>
|
||||||
|
我们提供安全可靠的皮肤分享平台,让你的创意在Minecraft世界中脱颖而出
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 className="text-3xl md:text-4xl font-bold text-center text-gray-800 dark:text-white mb-16 tracking-tight">
|
||||||
为什么选择我们的皮肤库?
|
为什么选择我们的皮肤库?
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-5xl mx-auto">
|
||||||
<FeatureCard
|
<FeatureCard
|
||||||
title="3D实时预览"
|
title="皮肤预览"
|
||||||
description="上传后立即在3D模型中查看皮肤效果,支持360度旋转查看"
|
description="上传后立即查看皮肤效果,清晰展示头部正脸细节"
|
||||||
icon="👁️"
|
icon="👁️"
|
||||||
/>
|
/>
|
||||||
<FeatureCard
|
<FeatureCard
|
||||||
@@ -75,7 +192,7 @@ export default function HomePage() {
|
|||||||
/>
|
/>
|
||||||
<FeatureCard
|
<FeatureCard
|
||||||
title="社区分享"
|
title="社区分享"
|
||||||
description="社区认同和安全保障(大概?"
|
description="与HITWH的大家分享你的创作,获得社区认同和反馈"
|
||||||
icon="🌍"
|
icon="🌍"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -83,19 +200,23 @@ export default function HomePage() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 行动号召区域 */}
|
{/* 行动号召区域 */}
|
||||||
<section className="py-20 bg-gradient-to-r from-green-500 to-cyan-500 dark:from-green-700 dark:to-cyan-700">
|
<section className="py-24 bg-gradient-to-r from-emerald-600 to-teal-500 dark:from-emerald-700 dark:to-teal-700 relative overflow-hidden">
|
||||||
<div className="container mx-auto px-4 text-center">
|
{/* 装饰性背景元素 */}
|
||||||
<h2 className="text-3xl md:text-4xl font-bold text-white mb-6">
|
<div className="absolute top-1/4 left-1/4 w-64 h-64 bg-white/10 rounded-full blur-3xl"></div>
|
||||||
加入HITWH300众(误
|
<div className="absolute bottom-1/4 right-1/4 w-80 h-80 bg-white/5 rounded-full blur-3xl"></div>
|
||||||
|
|
||||||
|
<div className="container mx-auto px-4 text-center relative z-10">
|
||||||
|
<h2 className="text-3xl md:text-5xl font-bold text-white mb-6 tracking-tight leading-tight">
|
||||||
|
加入HITWH皮肤社区
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-xl text-green-100 max-w-2xl mx-auto mb-10">
|
<p className="text-xl text-white/90 max-w-2xl mx-auto mb-12 leading-relaxed">
|
||||||
免费注册账号,开始创建和分享你的独特皮肤
|
免费注册账号,开始创建和分享你的独特皮肤,与HITWH的大家一起探索无限可能
|
||||||
</p>
|
</p>
|
||||||
<div className="flex flex-col sm:flex-row justify-center gap-4">
|
<div className="flex flex-col sm:flex-row justify-center gap-5">
|
||||||
<Button asChild size="lg" className="text-lg bg-white text-green-600 hover:bg-green-50 py-6">
|
<Button asChild size="lg" className="text-lg bg-white text-emerald-600 hover:bg-emerald-50 py-6 px-8 rounded-xl shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1">
|
||||||
<Link href="/register">立即注册</Link>
|
<Link href="/register">立即注册</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild variant="outline" size="lg" className="text-lg border-white text-white hover:bg-green-600 py-6">
|
<Button asChild variant="outline" size="lg" className="text-lg border-white text-white hover:bg-white/10 py-6 px-8 rounded-xl shadow-md hover:shadow-lg transition-all duration-300">
|
||||||
<Link href="/login">登录账号</Link>
|
<Link href="/login">登录账号</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -103,48 +224,50 @@ export default function HomePage() {
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* 页脚 */}
|
{/* 页脚 */}
|
||||||
<footer className="bg-gray-800 text-gray-300 py-10">
|
<footer className="bg-gray-900/95 backdrop-blur-md text-gray-300 py-16 relative z-10">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="flex flex-col md:flex-row justify-between items-center">
|
<div className="flex flex-col md:flex-row justify-between items-center">
|
||||||
<div className="mb-6 md:mb-0">
|
<div className="mb-10 md:mb-0 text-center md:text-left">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-3 justify-center md:justify-start">
|
||||||
<div className="bg-gray-200 border-2 border-dashed rounded-xl w-8 h-8" />
|
<Image src="/images/mc-favicon.ico" alt="Logo" width={40} height={40} className="rounded-xl w-10 h-10" />
|
||||||
<span className="text-xl font-bold text-green-400">HITWH皮肤库</span>
|
<span className="text-2xl font-bold text-emerald-400">HITWH皮肤库</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-2 text-sm">创建、分享和管理你的 Minecraft 皮肤</p>
|
<p className="mt-3 text-sm text-gray-400 max-w-xs mx-auto md:mx-0">创建、分享和管理你的 Minecraft 皮肤</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-8">
|
<div className="grid grid-cols-2 md:grid-cols-3 gap-8">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-green-400 font-semibold mb-3">导航</h3>
|
<h3 className="text-emerald-400 font-semibold mb-4 text-lg">导航</h3>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-3">
|
||||||
<li><Link href="/" className="hover:text-white">首页</Link></li>
|
<li><Link href="/" className="hover:text-white transition-colors">首页</Link></li>
|
||||||
<li><Link href="/dashboard" className="hover:text-white">皮肤库</Link></li>
|
<li><Link href="/dashboard" className="hover:text-white transition-colors">皮肤库</Link></li>
|
||||||
<li><Link href="/skins/upload" className="hover:text-white">上传皮肤</Link></li>
|
<li><Link href="/skins/upload" className="hover:text-white transition-colors">上传皮肤</Link></li>
|
||||||
|
<li><Link href="/character-center" className="hover:text-white transition-colors">角色中心</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-green-400 font-semibold mb-3">账号</h3>
|
<h3 className="text-emerald-400 font-semibold mb-4 text-lg">账号</h3>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-3">
|
||||||
<li><Link href="/login" className="hover:text-white">登录</Link></li>
|
<li><Link href="/login" className="hover:text-white transition-colors">登录</Link></li>
|
||||||
<li><Link href="/register" className="hover:text-white">注册</Link></li>
|
<li><Link href="/register" className="hover:text-white transition-colors">注册</Link></li>
|
||||||
<li><Link href="/dashboard" className="hover:text-white">我的皮肤</Link></li>
|
<li><Link href="/dashboard" className="hover:text-white transition-colors">我的皮肤</Link></li>
|
||||||
|
<li><Link href="/user-home" className="hover:text-white transition-colors">个人主页</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-green-400 font-semibold mb-3">支持</h3>
|
<h3 className="text-emerald-400 font-semibold mb-4 text-lg">支持</h3>
|
||||||
<ul className="space-y-2">
|
<ul className="space-y-3">
|
||||||
<li><Link href="/help" className="hover:text-white">帮助中心</Link></li>
|
<li><Link href="/help" className="hover:text-white transition-colors">帮助中心</Link></li>
|
||||||
<li><Link href="/contact" className="hover:text-white">联系我们</Link></li>
|
<li><Link href="/contact" className="hover:text-white transition-colors">联系我们</Link></li>
|
||||||
<li><Link href="/terms" className="hover:text-white">服务条款</Link></li>
|
<li><Link href="/terms" className="hover:text-white transition-colors">服务条款</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-gray-700 mt-10 pt-6 text-center text-sm">
|
<div className="border-t border-gray-800 mt-12 pt-8 text-center text-sm text-gray-400">
|
||||||
<p>© {new Date().getFullYear()} HITWHGAMES. 保留所有权利.</p>
|
<p>© {new Date().getFullYear()} HITWHGAMES. 保留所有权利.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -152,23 +275,3 @@ export default function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 功能卡片组件
|
|
||||||
function FeatureCard({ title, description, icon }: { title: string; description: string; icon: string }) {
|
|
||||||
return (
|
|
||||||
<Card className="h-full transition-transform hover:scale-[1.02] hover:shadow-lg">
|
|
||||||
<CardHeader>
|
|
||||||
<div className="text-4xl mb-4">{icon}</div>
|
|
||||||
<CardTitle className="text-xl">{title}</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
<p className="text-gray-600 dark:text-gray-300">{description}</p>
|
|
||||||
</CardContent>
|
|
||||||
<CardFooter>
|
|
||||||
<Button variant="link" className="text-green-600 dark:text-green-400 p-0">
|
|
||||||
了解更多 →
|
|
||||||
</Button>
|
|
||||||
</CardFooter>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import SkinViewer3D from '@/components/skins/SkinViewer3D';
|
|
||||||
import { Button } from '@/components/ui/button';
|
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 {
|
interface SkinDetailProps {
|
||||||
params: { id: string };
|
params: { id: string };
|
||||||
@@ -14,33 +16,111 @@ export default function SkinDetail({ params }: SkinDetailProps) {
|
|||||||
description: '这是你的Minecraft角色皮肤',
|
description: '这是你的Minecraft角色皮肤',
|
||||||
createdAt: '2023-06-01',
|
createdAt: '2023-06-01',
|
||||||
downloadUrl: `/api/skins/${params.id}/download`,
|
downloadUrl: `/api/skins/${params.id}/download`,
|
||||||
|
author: '创作者名称',
|
||||||
|
tags: ['游戏', '角色', '自定义']
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!skinData) return notFound();
|
if (!skinData) return notFound();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-4xl mx-auto">
|
<div className="min-h-screen bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800 relative overflow-hidden py-12">
|
||||||
<h1 className="text-3xl font-bold mb-6">{skinData.name}</h1>
|
{/* 背景装饰元素 - 渐变模糊效果 */}
|
||||||
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
<div className="absolute -top-20 -right-20 w-80 h-80 bg-emerald-400/20 rounded-full blur-3xl"></div>
|
||||||
<div className="bg-gray-100 dark:bg-gray-800 rounded-lg p-4">
|
<div className="absolute -bottom-20 -left-20 w-96 h-96 bg-teal-500/20 rounded-full blur-3xl"></div>
|
||||||
<SkinViewer3D skinUrl={skinData.downloadUrl} />
|
<div className="absolute top-1/3 left-1/4 w-64 h-64 bg-emerald-300/10 rounded-full blur-3xl"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="container mx-auto px-4 max-w-6xl relative z-10">
|
||||||
|
<div className="mb-8 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="mb-4">{skinData.description}</p>
|
<h1 className="text-3xl font-bold text-gray-800 dark:text-white mb-2 tracking-tight">皮肤详情</h1>
|
||||||
<p className="text-gray-500 mb-6">创建于: {skinData.createdAt}</p>
|
<p className="text-gray-600 dark:text-gray-400">查看、下载和分享这个精彩的Minecraft皮肤</p>
|
||||||
|
</div>
|
||||||
<div className="flex gap-3">
|
<Button asChild variant="ghost" className="hover:text-emerald-600 dark:hover:text-emerald-400 transition-all duration-300">
|
||||||
<Button asChild>
|
<Link href="/skins">返回皮肤库</Link>
|
||||||
<a href={skinData.downloadUrl} download>下载皮肤</a>
|
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="outline">
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||||
|
{/* 皮肤预览卡片 */}
|
||||||
|
<Card className="border border-emerald-200 dark:border-emerald-900/50 shadow-xl rounded-2xl overflow-hidden bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm">
|
||||||
|
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-teal-500"></div>
|
||||||
|
<CardHeader className="bg-emerald-50 dark:bg-gray-800/80 border-b border-emerald-100 dark:border-gray-700 pb-6">
|
||||||
|
<CardTitle className="text-xl text-gray-800 dark:text-white">皮肤预览</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="pt-6">
|
||||||
|
<div className="bg-gray-50 dark:bg-gray-900 rounded-xl p-6 aspect-square flex items-center justify-center relative overflow-hidden group">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<Canvas2DSkinPreview
|
||||||
|
skinUrl="/test-skin2.png"
|
||||||
|
size={256}
|
||||||
|
className="max-w-full max-h-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 皮肤信息卡片 */}
|
||||||
|
<Card className="border border-emerald-200 dark:border-emerald-900/50 shadow-xl rounded-2xl overflow-hidden bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm">
|
||||||
|
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-teal-500"></div>
|
||||||
|
<CardHeader className="bg-emerald-50 dark:bg-gray-800/80 border-b border-emerald-100 dark:border-gray-700 pb-6">
|
||||||
|
<CardTitle className="text-xl text-gray-800 dark:text-white">{skinData.name}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="pt-6 space-y-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">描述</h3>
|
||||||
|
<p className="text-gray-700 dark:text-gray-300 leading-relaxed">{skinData.description}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">创作者</h3>
|
||||||
|
<p className="text-gray-700 dark:text-gray-300">{skinData.author}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">创建日期</h3>
|
||||||
|
<p className="text-gray-700 dark:text-gray-300">{skinData.createdAt}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-sm font-medium text-gray-500 dark:text-gray-400">标签</h3>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{skinData.tags.map((tag, index) => (
|
||||||
|
<span key={index} className="px-3 py-1 bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-400 rounded-full text-sm">
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="border-t border-emerald-100 dark:border-gray-700 py-6 px-6 bg-emerald-50/50 dark:bg-gray-800/80">
|
||||||
|
<div className="w-full flex gap-4">
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
className="flex-1 bg-emerald-600 hover:bg-emerald-700 text-white transition-all duration-300 transform hover:-translate-y-1 shadow-md hover:shadow-lg"
|
||||||
|
>
|
||||||
|
<a href={skinData.downloadUrl} download className="w-full">下载皮肤</a>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="flex-1 border-emerald-600 text-emerald-600 hover:bg-emerald-50 dark:border-emerald-400 dark:text-emerald-400 dark:hover:bg-emerald-900/20 transition-all duration-300"
|
||||||
|
>
|
||||||
分享
|
分享
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 页脚 */}
|
||||||
|
<footer className="bg-gray-900/80 backdrop-blur-md text-gray-300 py-8 mt-16 border-t border-gray-800">
|
||||||
|
<div className="container mx-auto px-4 text-center relative z-10">
|
||||||
|
<p className="mb-2">我的世界皮肤库 - 你的Minecraft皮肤分享平台</p>
|
||||||
|
<p className="text-sm text-gray-400">© {new Date().getFullYear()} 版权所有</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
177
src/app/skins/page.tsx
Normal file
177
src/app/skins/page.tsx
Normal file
@@ -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 (
|
||||||
|
<div className="min-h-screen py-12 relative">
|
||||||
|
{/* 马赛克砖背景样式 */}
|
||||||
|
<div className="absolute inset-0 z-0 bg-[radial-gradient(#e6f7ef_15%,transparent_16%)] bg-[length:24px_24px] dark:bg-[radial-gradient(rgba(16,185,129,0.1)_15%,transparent_16%)] dark:bg-[length:28px_28px]"></div>
|
||||||
|
|
||||||
|
<div className="container mx-auto px-4 max-w-6xl relative z-10">
|
||||||
|
<div className="mb-8 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-800 dark:text-white mb-2 tracking-tight">探索无限可能的皮肤世界</h1>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">发现、分享和下载最酷的Minecraft皮肤</p>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
className="bg-emerald-600 hover:bg-emerald-700 text-white transition-all duration-300 transform hover:-translate-y-1 shadow-md hover:shadow-lg"
|
||||||
|
>
|
||||||
|
<Link href="/skins/upload">上传皮肤</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 搜索和筛选区 */}
|
||||||
|
<Card className="mb-8 border border-emerald-200 dark:border-emerald-900/50 shadow-lg rounded-2xl overflow-hidden bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm">
|
||||||
|
<CardContent className="pt-6 px-6">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div className="relative col-span-2">
|
||||||
|
<Input
|
||||||
|
placeholder="搜索皮肤名称或作者..."
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => 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"
|
||||||
|
/>
|
||||||
|
<svg className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-3 gap-2 col-span-1">
|
||||||
|
<Button
|
||||||
|
variant={selectedCategory === 'all' ? 'default' : 'secondary'}
|
||||||
|
onClick={() => setSelectedCategory('all')}
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
|
全部
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={selectedCategory === 'Steve' ? 'default' : 'secondary'}
|
||||||
|
onClick={() => setSelectedCategory('Steve')}
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
|
Steve
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={selectedCategory === 'Alex' ? 'default' : 'secondary'}
|
||||||
|
onClick={() => setSelectedCategory('Alex')}
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
|
Alex
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={selectedCategory === '披风' ? 'default' : 'secondary'}
|
||||||
|
onClick={() => setSelectedCategory('披风')}
|
||||||
|
className="text-sm"
|
||||||
|
>
|
||||||
|
披风
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 皮肤列表 */}
|
||||||
|
{filteredSkins.length > 0 ? (
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||||
|
{filteredSkins.map((skin) => (
|
||||||
|
<Card
|
||||||
|
key={skin.id}
|
||||||
|
className={`border ${skin.type === 'steve' ? 'border-blue-200 dark:border-blue-900/50' : skin.type === 'alex' ? 'border-pink-200 dark:border-pink-900/50' : 'border-purple-200 dark:border-purple-900/50'} shadow-lg rounded-2xl overflow-hidden bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm transition-all duration-300 hover:shadow-xl transform hover:-translate-y-1 relative`}
|
||||||
|
>
|
||||||
|
<Link href={`/skins/${skin.id}`} className="block">
|
||||||
|
<div className="aspect-square bg-gray-50 dark:bg-gray-900 flex items-center justify-center p-4 relative overflow-hidden group">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<Canvas2DSkinPreview
|
||||||
|
skinUrl={skin.imageUrl}
|
||||||
|
size={128}
|
||||||
|
className="max-w-full max-h-full relative z-10 transition-transform duration-500 group-hover:scale-110"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CardContent className="pt-6 pb-4 px-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="font-bold text-lg text-gray-800 dark:text-white group-hover:text-emerald-600 dark:group-hover:text-emerald-400 transition-colors">{skin.name}</h3>
|
||||||
|
<span className={`text-xs px-2 py-1 rounded-full ${skin.type === 'steve' ? 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400' : skin.type === 'alex' ? 'bg-pink-100 text-pink-700 dark:bg-pink-900/30 dark:text-pink-400' : 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'}`}>
|
||||||
|
{skin.type === 'steve' ? 'Steve' : skin.type === 'alex' ? 'Alex' : '披风'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">作者: {skin.author}</p>
|
||||||
|
<div className="flex flex-wrap gap-2 mt-2">
|
||||||
|
{skin.tags.map((tag, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className="inline-block px-2 py-1 text-xs bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-400 rounded-full"
|
||||||
|
>
|
||||||
|
{tag}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="px-6 py-4 border-t border-emerald-100 dark:border-gray-700 bg-emerald-50/50 dark:bg-gray-800/80">
|
||||||
|
<div className="w-full flex justify-between items-center">
|
||||||
|
<span className="text-xs text-gray-500 dark:text-gray-400">上传于 {skin.createdAt}</span>
|
||||||
|
<span className="text-xs text-emerald-600 dark:text-emerald-400 font-medium">查看详情 →</span>
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Link>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Card className="border border-emerald-200 dark:border-emerald-900/50 shadow-lg rounded-2xl overflow-hidden bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm text-center py-12">
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="text-6xl mb-4">🔍</div>
|
||||||
|
<h3 className="text-xl font-bold text-gray-800 dark:text-white">未找到皮肤</h3>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 max-w-md mx-auto">
|
||||||
|
没有找到匹配你搜索条件的皮肤,请尝试使用其他关键词或筛选条件
|
||||||
|
</p>
|
||||||
|
<Button
|
||||||
|
className="mt-4 bg-emerald-600 hover:bg-emerald-700 text-white transition-all duration-300 transform hover:-translate-y-1 shadow-md hover:shadow-lg"
|
||||||
|
onClick={() => {
|
||||||
|
setSearchTerm('');
|
||||||
|
setSelectedCategory('all');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
清除筛选条件
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
151
src/app/skins/preview/page.tsx
Normal file
151
src/app/skins/preview/page.tsx
Normal file
@@ -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<HTMLCanvasElement>(null);
|
||||||
|
const [face, setFace] = useState<string>('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 (
|
||||||
|
<>
|
||||||
|
<style>{styles}</style>
|
||||||
|
<div className="p-8 max-w-4xl mx-auto">
|
||||||
|
<h1 className="text-2xl font-bold mb-6">Minecraft 皮肤头部预览</h1>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<label className="block mb-2 font-medium">选择要查看的面:</label>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{['front', 'back', 'top', 'bottom', 'right', 'left'].map((faceType) => (
|
||||||
|
<button
|
||||||
|
key={faceType}
|
||||||
|
onClick={() => 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' && '左侧'}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<div className="bg-white p-4 rounded-lg shadow-md border border-gray-200">
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
className="image-rendering-pixelated" // 确保浏览器也使用像素化渲染
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MinecraftSkinPreview;
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
'use client';
|
'use client';
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useDropzone } from 'react-dropzone';
|
import { useDropzone } from 'react-dropzone';
|
||||||
import SkinUploader from '@/components/skins/SkinUploader';
|
|
||||||
import { Button } from '@/components/ui/button';
|
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() {
|
export default function SkinUploadPage() {
|
||||||
const [skinFile, setSkinFile] = useState<File | null>(null);
|
const [skinFile, setSkinFile] = useState<File | null>(null);
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
|
const [skinFileUrl, setSkinFileUrl] = useState<string>('');
|
||||||
|
const [skinName, setSkinName] = useState<string>('');
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
accept: {
|
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 () => {
|
const handleUpload = async () => {
|
||||||
if (!skinFile) return;
|
if (!skinFile) return;
|
||||||
|
|
||||||
@@ -38,36 +57,90 @@ export default function SkinUploadPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-2xl mx-auto">
|
<div className="min-h-screen bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800 relative overflow-hidden py-12">
|
||||||
<h1 className="text-3xl font-bold mb-6">上传新皮肤</h1>
|
{/* 背景装饰元素 - 渐变模糊效果 */}
|
||||||
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
|
<div className="absolute -top-20 -right-20 w-80 h-80 bg-emerald-400/20 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute -bottom-20 -left-20 w-96 h-96 bg-teal-500/20 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute top-1/3 left-1/4 w-64 h-64 bg-emerald-300/10 rounded-full blur-3xl"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="container mx-auto px-4 max-w-2xl relative z-10">
|
||||||
|
<div className="mb-8 flex flex-col sm:flex-row justify-between items-center gap-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-800 dark:text-white mb-2 tracking-tight">上传新皮肤</h1>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">创建并分享你的独特皮肤设计</p>
|
||||||
|
</div>
|
||||||
|
<Button asChild variant="ghost" className="hover:text-emerald-600 dark:hover:text-emerald-400 transition-all duration-300">
|
||||||
|
<Link href="/skins">返回皮肤库</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card className="border border-emerald-200 dark:border-emerald-900/50 shadow-xl rounded-2xl overflow-hidden bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm">
|
||||||
|
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-teal-500"></div>
|
||||||
|
|
||||||
|
<CardHeader className="bg-emerald-50/50 dark:bg-gray-800/80 border-b border-emerald-100 dark:border-gray-700">
|
||||||
|
<CardTitle className="text-2xl text-gray-800 dark:text-white">皮肤上传</CardTitle>
|
||||||
|
<CardDescription className="text-gray-600 dark:text-gray-400">上传PNG格式的皮肤文件并查看预览</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="pt-6">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label className="text-gray-700 dark:text-gray-300 font-medium">皮肤文件</Label>
|
||||||
<div
|
<div
|
||||||
{...getRootProps()}
|
{...getRootProps()}
|
||||||
className="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center cursor-pointer mb-6"
|
className="border-2 border-dashed border-emerald-200 dark:border-emerald-900/30 rounded-xl p-8 text-center cursor-pointer hover:border-emerald-400 dark:hover:border-emerald-600/50 transition-all duration-300 bg-emerald-50/30 dark:bg-gray-700/30 group"
|
||||||
>
|
>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
<p className="text-gray-500">拖放PNG格式的皮肤文件到这里,或点击选择文件</p>
|
<div className="space-y-3">
|
||||||
<p className="text-sm text-gray-400 mt-2">标准皮肤尺寸应为64x32像素</p>
|
<UploadIcon className="mx-auto h-10 w-10 text-emerald-500 dark:text-emerald-400 transition-transform duration-300 group-hover:scale-110" />
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-sm font-medium text-gray-700 dark:text-gray-300">拖放PNG格式的皮肤文件到这里,或点击选择文件</p>
|
||||||
|
<p className="text-xs text-gray-500 dark:text-gray-400">标准皮肤尺寸应为64x32像素</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{skinFile && (
|
<div className="space-y-4 p-4 border rounded-xl bg-white dark:bg-gray-700/50 border-emerald-100 dark:border-gray-700">
|
||||||
<div className="mb-6">
|
<h3 className="text-lg font-medium text-gray-700 dark:text-gray-300">预览</h3>
|
||||||
<h3 className="text-lg font-medium mb-3">预览</h3>
|
<div className="flex justify-center">
|
||||||
<SkinUploader file={skinFile} />
|
<div className="w-full max-w-md aspect-square bg-gray-50 dark:bg-gray-900 rounded-lg shadow-md flex items-center justify-center">
|
||||||
<div className="mt-3">
|
<Canvas2DSkinPreview
|
||||||
<p>文件名: {skinFile.name}</p>
|
skinUrl={skinFileUrl || "/test-skin.png"}
|
||||||
<p>大小: {(skinFile.size / 1024).toFixed(2)} KB</p>
|
size={256}
|
||||||
|
className="max-w-full max-h-full"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{skinFile && (
|
||||||
|
<div className="mt-3 space-y-1">
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-300">文件名: {skinFile.name}</p>
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-300">大小: {(skinFile.size / 1024).toFixed(2)} KB</p>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
<CardFooter className="bg-emerald-50/30 dark:bg-gray-800/50 border-t border-emerald-100 dark:border-gray-700 py-6 px-6">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleUpload}
|
onClick={handleUpload}
|
||||||
disabled={!skinFile || isUploading}
|
disabled={!skinFile || isUploading}
|
||||||
className="w-full"
|
className="w-full bg-emerald-600 hover:bg-emerald-700 text-white py-6 transition-all duration-300 transform hover:-translate-y-1 shadow-md hover:shadow-lg"
|
||||||
>
|
>
|
||||||
{isUploading ? '上传中...' : '上传皮肤'}
|
{isUploading ? '上传中...' : '上传皮肤'}
|
||||||
</Button>
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 页脚 */}
|
||||||
|
<footer className="bg-gray-900/80 backdrop-blur-md text-gray-300 py-8 mt-16 border-t border-gray-800">
|
||||||
|
<div className="container mx-auto px-4 text-center relative z-10">
|
||||||
|
<p className="mb-2">我的世界皮肤库 - 你的Minecraft皮肤分享平台</p>
|
||||||
|
<p className="text-sm text-gray-400">© {new Date().getFullYear()} 版权所有</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,18 @@
|
|||||||
// src/app/template.tsx
|
// src/app/template.tsx
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useEffect } from 'react';
|
import { useEffect, Suspense } from 'react';
|
||||||
|
|
||||||
export default function Template({ children }: { children: React.ReactNode }) {
|
export default function Template({ children }: { children: React.ReactNode }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 模板特定的效果
|
// 模板特定的效果
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return <>{children}</>;
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800">
|
||||||
|
<Suspense>
|
||||||
|
{children}
|
||||||
|
</Suspense>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
239
src/app/user-home/page.tsx
Normal file
239
src/app/user-home/page.tsx
Normal file
@@ -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 <div className="min-h-screen flex items-center justify-center">加载中...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 安全地获取用户信息
|
||||||
|
const userName = session?.user?.name || '玩家';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800 relative overflow-hidden text-gray-900 dark:text-gray-100">
|
||||||
|
{/* 背景装饰元素 - 渐变模糊效果 */}
|
||||||
|
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
|
<div className="absolute -top-20 -right-20 w-80 h-80 bg-emerald-400/20 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute -bottom-20 -left-20 w-96 h-96 bg-teal-500/20 rounded-full blur-3xl"></div>
|
||||||
|
<div className="absolute top-1/3 left-1/4 w-64 h-64 bg-emerald-300/10 rounded-full blur-3xl"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 主要内容区域 */}
|
||||||
|
<main className="container mx-auto px-4 py-12 relative z-10">
|
||||||
|
{/* 欢迎区域 */}
|
||||||
|
<section className="mb-16 max-w-4xl mx-auto">
|
||||||
|
<Card className="rounded-2xl overflow-hidden shadow-xl border border-emerald-200 dark:border-emerald-900/50 bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm">
|
||||||
|
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-teal-500"></div>
|
||||||
|
<CardContent className="p-8">
|
||||||
|
<div className="flex flex-col md:flex-row items-center md:items-start gap-6">
|
||||||
|
<div className="w-24 h-24 rounded-full bg-gradient-to-br from-emerald-500 to-teal-500 flex items-center justify-center text-white text-4xl shadow-lg">
|
||||||
|
{userName?.[0] || 'U'}
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<h1 className="text-2xl md:text-3xl font-bold text-gray-800 dark:text-white mb-2">
|
||||||
|
欢迎回来,{userName}!
|
||||||
|
</h1>
|
||||||
|
<p className="text-base md:text-lg opacity-90 leading-relaxed text-gray-600 dark:text-gray-400">这里是你的个人主页,继续探索和创建你的Minecraft皮肤吧</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 快速操作区域 */}
|
||||||
|
<section className="mb-16 max-w-4xl mx-auto grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-5">
|
||||||
|
{/* 上传皮肤卡片 */}
|
||||||
|
<Link href="/skins/upload" className="block transform transition-all duration-300 hover:-translate-y-1">
|
||||||
|
<div className="bg-white/95 dark:bg-gray-800/95 p-6 rounded-2xl shadow-md hover:shadow-xl border border-emerald-100 dark:border-emerald-900/30 text-center relative overflow-hidden group backdrop-blur-sm">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-emerald-50 to-teal-50 dark:from-emerald-900/10 dark:to-teal-900/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<div className="relative z-10">
|
||||||
|
<div className="text-5xl mb-4 transition-transform duration-300 group-hover:scale-110">📤</div>
|
||||||
|
<h3 className="font-bold text-lg mb-1 text-gray-800 dark:text-white">上传皮肤</h3>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">分享你的创作</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* 我的皮肤卡片 */}
|
||||||
|
<Link href="/dashboard" className="block transform transition-all duration-300 hover:-translate-y-1">
|
||||||
|
<div className="bg-white/95 dark:bg-gray-800/95 p-6 rounded-2xl shadow-md hover:shadow-xl border border-emerald-100 dark:border-emerald-900/30 text-center relative overflow-hidden group backdrop-blur-sm">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-blue-50 to-cyan-50 dark:from-blue-900/10 dark:to-cyan-900/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<div className="relative z-10">
|
||||||
|
<div className="text-5xl mb-4 transition-transform duration-300 group-hover:scale-110">👕</div>
|
||||||
|
<h3 className="font-bold text-lg mb-1 text-gray-800 dark:text-white">我的皮肤</h3>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">管理我的收藏</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{/* 外置登录卡片 */}
|
||||||
|
<div className="transform transition-all duration-300 hover:-translate-y-1" draggable="true" onDragStart={(e) => {
|
||||||
|
// 为拖拽提供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';
|
||||||
|
}}>
|
||||||
|
<div className="bg-white/95 dark:bg-gray-800/95 p-6 rounded-2xl shadow-md hover:shadow-xl border border-emerald-100 dark:border-emerald-900/30 text-center relative overflow-hidden group backdrop-blur-sm cursor-grab active:cursor-grabbing">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-purple-50 to-indigo-50 dark:from-purple-900/10 dark:to-indigo-900/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<div className="relative z-10">
|
||||||
|
<div className="text-5xl mb-4 transition-transform duration-300 group-hover:scale-110">🔐</div>
|
||||||
|
<h3 className="font-bold text-lg mb-1 text-gray-800 dark:text-white">外置登录</h3>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">拖拽到启动器实现认证</p>
|
||||||
|
</div>
|
||||||
|
{/* 拖拽提示 */}
|
||||||
|
<div className="absolute bottom-2 right-2 text-xs text-purple-500 dark:text-purple-400 opacity-70">
|
||||||
|
可拖拽 ↕️
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 角色中心卡片 */}
|
||||||
|
<Link href="/character-center" className="block transform transition-all duration-300 hover:-translate-y-1">
|
||||||
|
<div className="bg-white/95 dark:bg-gray-800/95 p-6 rounded-2xl shadow-md hover:shadow-xl border border-emerald-100 dark:border-emerald-900/30 text-center relative overflow-hidden group backdrop-blur-sm">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-amber-50 to-orange-50 dark:from-amber-900/10 dark:to-orange-900/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<div className="relative z-10">
|
||||||
|
<div className="text-5xl mb-4 transition-transform duration-300 group-hover:scale-110">👤</div>
|
||||||
|
<h3 className="font-bold text-lg mb-1 text-gray-800 dark:text-white">角色中心</h3>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400">管理游戏角色</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 问题帮助模块 */}
|
||||||
|
<section className="mb-16 max-w-4xl mx-auto">
|
||||||
|
<Card className="rounded-2xl overflow-hidden shadow-xl border border-emerald-200 dark:border-emerald-900/50 bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm">
|
||||||
|
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-teal-500"></div>
|
||||||
|
<CardHeader className="bg-emerald-50/50 dark:bg-gray-800/80 border-b border-emerald-100 dark:border-gray-700">
|
||||||
|
<CardTitle className="text-xl text-gray-800 dark:text-white">使用教程</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<Link href="/help/basic" className="block">
|
||||||
|
<div className="h-full p-6 rounded-xl bg-gradient-to-br from-blue-50 to-cyan-50 dark:from-blue-900/20 dark:to-cyan-900/20 border border-blue-100 dark:border-blue-800/50 transition-all duration-300 hover:shadow-md hover:-translate-y-1">
|
||||||
|
<h3 className="font-bold text-xl mb-3 text-blue-700 dark:text-blue-400">基础教程</h3>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">了解平台基本功能、皮肤上传与管理、用户账户设置等基础知识。</p>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-blue-600 dark:text-blue-500">适合新手入门</span>
|
||||||
|
<span className="text-blue-600 dark:text-blue-500">→</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href="/help/yggdrasil" className="block">
|
||||||
|
<div className="h-full p-6 rounded-xl bg-gradient-to-br from-purple-50 to-violet-50 dark:from-purple-900/20 dark:to-violet-900/20 border border-purple-100 dark:border-purple-800/50 transition-all duration-300 hover:shadow-md hover:-translate-y-1">
|
||||||
|
<h3 className="font-bold text-xl mb-3 text-purple-700 dark:text-purple-400">Yggdrasil教程</h3>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">学习如何使用Yggdrasil验证服务,实现皮肤同步与多账号管理。</p>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-purple-600 dark:text-purple-500">进阶功能</span>
|
||||||
|
<span className="text-purple-600 dark:text-purple-500">→</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href="/help/multilogin" className="block">
|
||||||
|
<div className="h-full p-6 rounded-xl bg-gradient-to-br from-amber-50 to-yellow-50 dark:from-amber-900/20 dark:to-yellow-900/20 border border-amber-100 dark:border-amber-800/50 transition-all duration-300 hover:shadow-md hover:-translate-y-1">
|
||||||
|
<h3 className="font-bold text-xl mb-3 text-amber-700 dark:text-amber-400">MultiLogin教程</h3>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">掌握MultiLogin插件的配置与使用方法,实现多账户共存。</p>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-amber-600 dark:text-amber-500">服务器管理员</span>
|
||||||
|
<span className="text-amber-600 dark:text-amber-500">→</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href="/help/customskinloader" className="block">
|
||||||
|
<div className="h-full p-6 rounded-xl bg-gradient-to-br from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20 border border-green-100 dark:border-green-800/50 transition-all duration-300 hover:shadow-md hover:-translate-y-1">
|
||||||
|
<h3 className="font-bold text-xl mb-3 text-green-700 dark:text-green-400">CustomSkinLoader教程</h3>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400 mb-4">详细介绍如何配置CustomSkinLoader,在客户端加载自定义皮肤。</p>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm text-green-600 dark:text-green-500">实用工具</span>
|
||||||
|
<span className="text-green-600 dark:text-green-500">→</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 统计信息区域 */}
|
||||||
|
<section className="mb-16 max-w-4xl mx-auto">
|
||||||
|
<Card className="rounded-2xl overflow-hidden shadow-xl border border-emerald-200 dark:border-emerald-900/50 bg-white/95 dark:bg-gray-800/95 backdrop-blur-sm">
|
||||||
|
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-teal-500"></div>
|
||||||
|
<CardHeader className="bg-emerald-50/50 dark:bg-gray-800/80 border-b border-emerald-100 dark:border-gray-700">
|
||||||
|
<CardTitle className="text-xl text-gray-800 dark:text-white">皮肤库统计</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="p-6">
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-6">
|
||||||
|
<div className="text-center p-4 rounded-xl bg-emerald-50 dark:bg-emerald-900/20 border border-emerald-100 dark:border-emerald-900/30 transition-all duration-300 hover:shadow-md hover:-translate-y-1">
|
||||||
|
<div className="text-4xl font-bold text-emerald-600 dark:text-emerald-400">1,256</div>
|
||||||
|
<div className="text-gray-600 dark:text-gray-400">总皮肤数</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center p-4 rounded-xl bg-teal-50 dark:bg-teal-900/20 border border-teal-100 dark:border-teal-900/30 transition-all duration-300 hover:shadow-md hover:-translate-y-1">
|
||||||
|
<div className="text-4xl font-bold text-teal-600 dark:text-teal-400">342</div>
|
||||||
|
<div className="text-gray-600 dark:text-gray-400">活跃用户</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center p-4 rounded-xl bg-cyan-50 dark:bg-cyan-900/20 border border-cyan-100 dark:border-cyan-900/30 transition-all duration-300 hover:shadow-md hover:-translate-y-1">
|
||||||
|
<div className="text-4xl font-bold text-cyan-600 dark:text-cyan-400">12,890</div>
|
||||||
|
<div className="text-gray-600 dark:text-gray-400">下载次数</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-center p-4 rounded-xl bg-lime-50 dark:bg-lime-900/20 border border-lime-100 dark:border-lime-900/30 transition-all duration-300 hover:shadow-md hover:-translate-y-1">
|
||||||
|
<div className="text-4xl font-bold text-lime-600 dark:text-lime-400">89</div>
|
||||||
|
<div className="text-gray-600 dark:text-gray-400">今日新增</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{/* 页脚 */}
|
||||||
|
<footer className="bg-gray-900/80 backdrop-blur-md text-gray-300 py-8 border-t border-gray-800">
|
||||||
|
<div className="container mx-auto px-4 text-center relative z-10">
|
||||||
|
<p className="mb-2">我的世界皮肤库 - 你的Minecraft皮肤分享平台</p>
|
||||||
|
<p className="text-sm text-gray-400">© {new Date().getFullYear()} 版权所有</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,59 +1,275 @@
|
|||||||
// src/components/Navbar.tsx
|
// src/components/Navbar.tsx
|
||||||
|
'use client';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { getServerSession } from 'next-auth';
|
import { Session } from 'next-auth';
|
||||||
import { authOptions } from '@/lib/api/auth';
|
import { serverSignOut } from '@/lib/api/actions';
|
||||||
import { signOut } from '@/lib/api/auth';
|
import { useState, FunctionComponent } from 'react';
|
||||||
|
import { Menu, X } from 'lucide-react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
|
||||||
export default async function Navbar() {
|
// 定义组件Props接口
|
||||||
const session = await getServerSession(authOptions);
|
interface NavbarProps {
|
||||||
|
session: Session | null;
|
||||||
|
}
|
||||||
|
|
||||||
// 处理客户端退出函数
|
// 移动端菜单组件
|
||||||
const handleSignOut = async () => {
|
interface MobileMenuProps {
|
||||||
'use server';
|
session: Session | null;
|
||||||
await signOut();
|
userName: string;
|
||||||
};
|
serverSignOut: () => void;
|
||||||
|
handleProtectedLinkClick: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
// 安全地获取用户名
|
const MobileMenu: FunctionComponent<MobileMenuProps> = ({ session, userName, serverSignOut, handleProtectedLinkClick }) => {
|
||||||
const userName = session?.user?.name || '玩家';
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="bg-gray-800 text-white py-4">
|
<div className="md:hidden">
|
||||||
<div className="container mx-auto px-4 flex justify-between items-center">
|
<Button
|
||||||
<div className="flex items-center space-x-6">
|
variant="ghost"
|
||||||
<Link href="/" className="text-xl font-bold">我的世界皮肤库</Link>
|
size="icon"
|
||||||
<div className="hidden md:flex space-x-4">
|
onClick={() => setIsOpen(!isOpen)}
|
||||||
<Link href="/dashboard" className="hover:text-gray-300">仪表盘</Link>
|
className="text-white hover:bg-gray-700"
|
||||||
<Link href="/skins" className="hover:text-gray-300">皮肤库</Link>
|
>
|
||||||
</div>
|
{isOpen ? <X size={24} /> : <Menu size={24} />}
|
||||||
</div>
|
</Button>
|
||||||
|
|
||||||
<div className="flex items-center space-x-4">
|
{/* 移动端下拉菜单 */}
|
||||||
|
{isOpen && (
|
||||||
|
<div className="absolute top-16 right-4 bg-gray-800 border border-gray-700 rounded-lg shadow-lg py-2 w-48 z-50">
|
||||||
|
<div className="space-y-2 px-2">
|
||||||
{session ? (
|
{session ? (
|
||||||
<div className="flex items-center space-x-4">
|
<>
|
||||||
<span>你好, {userName}</span>
|
<Link
|
||||||
<form action={handleSignOut}>
|
href="/user-home"
|
||||||
|
className="block px-4 py-2 hover:bg-gray-700 rounded hover:text-emerald-400 transition-colors"
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
>
|
||||||
|
用户主页
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/skins"
|
||||||
|
className="block px-4 py-2 hover:bg-gray-700 rounded hover:text-emerald-400 transition-colors"
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
>
|
||||||
|
皮肤库
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href="/character-center"
|
||||||
|
className="block px-4 py-2 hover:bg-gray-700 rounded hover:text-emerald-400 transition-colors"
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
>
|
||||||
|
角色中心
|
||||||
|
</Link>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
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' }}
|
||||||
|
>
|
||||||
|
用户主页
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
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' }}
|
||||||
|
>
|
||||||
|
皮肤库
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
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' }}
|
||||||
|
>
|
||||||
|
角色中心
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{session && (
|
||||||
|
<div className="border-t border-gray-700 my-1 pt-2">
|
||||||
|
<p className="px-4 mb-2 text-sm text-gray-300">你好, {userName}</p>
|
||||||
|
<form action={serverSignOut} className="w-full">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
type="submit"
|
type="submit"
|
||||||
className="text-white border-white hover:bg-gray-700"
|
className="w-full text-red-300 border-white hover:bg-gray-700 hover:text-red-200"
|
||||||
>
|
>
|
||||||
退出登录
|
退出登录
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
)}
|
||||||
<div className="flex space-x-2">
|
|
||||||
<Button asChild variant="outline" className="text-white border-white hover:bg-gray-700">
|
{!session && (
|
||||||
<Link href="/login">登录</Link>
|
<div className="border-t border-gray-700 my-1 pt-2 space-y-2">
|
||||||
|
<Button asChild variant="outline" className="w-full text-emerald-400 border-emerald-400/30 hover:bg-gray-700 hover:text-emerald-300 transition-all duration-300">
|
||||||
|
<Link href="/login" onClick={() => setIsOpen(false)}>登录</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button asChild className="bg-green-600 hover:bg-green-700">
|
<Button asChild className="w-full bg-emerald-600 hover:bg-emerald-700 transition-all duration-300 transform hover:-translate-y-0.5">
|
||||||
<Link href="/register">注册</Link>
|
<Link href="/register" onClick={() => setIsOpen(false)}>注册</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 移动Navbar组件到客户端
|
||||||
|
const Navbar: FunctionComponent<NavbarProps> = ({ 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 (
|
||||||
|
<>
|
||||||
|
<nav className="bg-gray-900 text-white py-4 shadow-md backdrop-blur-sm bg-opacity-90 z-100">
|
||||||
|
<div className="container mx-auto px-4 flex justify-between items-center">
|
||||||
|
<div className="flex items-center space-x-6">
|
||||||
|
<Link href="/" className="flex items-center space-x-2 group">
|
||||||
|
<Image
|
||||||
|
src="/images/mc-favicon.ico"
|
||||||
|
alt="Logo"
|
||||||
|
width={32}
|
||||||
|
height={32}
|
||||||
|
className="rounded-xl w-8 h-8 transition-transform duration-300 group-hover:scale-110"
|
||||||
|
/>
|
||||||
|
<span className="text-xl font-bold bg-gradient-to-r from-emerald-400 to-teal-400 text-transparent bg-clip-text">我的世界皮肤库</span>
|
||||||
|
</Link>
|
||||||
|
{/* 桌面端导航链接 */}
|
||||||
|
<div className="hidden md:flex space-x-4">
|
||||||
|
{session ? (
|
||||||
|
<>
|
||||||
|
<Link href="/user-home" className="hover:text-emerald-400 transition-colors">用户主页</Link>
|
||||||
|
<Link href="/skins" className="hover:text-emerald-400 transition-colors">皮肤库</Link>
|
||||||
|
<Link href="/character-center" className="hover:text-emerald-400 transition-colors">角色中心</Link>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
onClick={handleProtectedLinkClick}
|
||||||
|
className="hover:text-emerald-400 cursor-pointer transition-colors"
|
||||||
|
style={{ textDecoration: 'underline', textDecorationStyle: 'dotted' }}
|
||||||
|
>
|
||||||
|
用户主页
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleProtectedLinkClick}
|
||||||
|
className="hover:text-emerald-400 cursor-pointer transition-colors"
|
||||||
|
style={{ textDecoration: 'underline', textDecorationStyle: 'dotted' }}
|
||||||
|
>
|
||||||
|
皮肤库
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleProtectedLinkClick}
|
||||||
|
className="hover:text-emerald-400 cursor-pointer transition-colors"
|
||||||
|
style={{ textDecoration: 'underline', textDecorationStyle: 'dotted' }}
|
||||||
|
>
|
||||||
|
角色中心
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
{/* 桌面端登录状态 */}
|
||||||
|
<div className="hidden md:flex items-center space-x-4">
|
||||||
|
{session && <span>你好, {userName}</span>}
|
||||||
|
{session ? (
|
||||||
|
<form action={serverSignOut}>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
type="submit"
|
||||||
|
className="text-red-300 border-white hover:bg-gray-700 hover:text-red-200 transition-all duration-300"
|
||||||
|
>
|
||||||
|
退出登录
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Button asChild variant="outline" className="text-emerald-400 border-emerald-400/30 hover:bg-gray-700 hover:text-emerald-300 transition-all duration-300">
|
||||||
|
<Link href="/login">登录</Link>
|
||||||
|
</Button>
|
||||||
|
<Button asChild className="bg-emerald-600 hover:bg-emerald-700 transition-all duration-300 transform hover:-translate-y-0.5">
|
||||||
|
<Link href="/register">注册</Link>
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 移动端菜单按钮 */}
|
||||||
|
<MobileMenu session={session} userName={userName} serverSignOut={serverSignOut} handleProtectedLinkClick={handleProtectedLinkClick} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{/* 登录提示弹窗 */}
|
||||||
|
{showLoginPrompt && (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 animate-fadeIn"
|
||||||
|
onClick={() => setShowLoginPrompt(false)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="bg-white/95 dark:bg-gray-800/95 backdrop-blur-md rounded-xl shadow-2xl p-8 max-w-md w-full mx-4 transform transition-all duration-300 animate-slideUp"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<h3 className="text-xl font-bold text-gray-900 dark:text-white mb-4 text-center">需要登录</h3>
|
||||||
|
<p className="text-gray-700 dark:text-gray-300 mb-6 text-center">
|
||||||
|
请先登录以访问此功能。
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
className="bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl px-6 py-2.5 shadow-lg hover:shadow-xl transition-all duration-300 transform hover:-translate-y-1"
|
||||||
|
onClick={() => setShowLoginPrompt(false)}
|
||||||
|
>
|
||||||
|
<Link href="/login">登录</Link>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
asChild
|
||||||
|
variant="outline"
|
||||||
|
className="border-emerald-600 text-emerald-600 hover:bg-emerald-50 dark:hover:bg-emerald-900/10 rounded-xl px-6 py-2.5 shadow-md hover:shadow-lg transition-all duration-300 transform hover:-translate-y-1"
|
||||||
|
onClick={() => setShowLoginPrompt(false)}
|
||||||
|
>
|
||||||
|
<Link href="/register">注册</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 直接导出客户端Navbar组件,会话获取将由上层组件处理
|
||||||
|
export default Navbar;
|
||||||
259
src/components/ServerInfoBubble.tsx
Normal file
259
src/components/ServerInfoBubble.tsx
Normal file
@@ -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<HTMLDivElement>(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 (
|
||||||
|
<div
|
||||||
|
ref={bubbleRef}
|
||||||
|
className="fixed flex flex-col sm:flex-row gap-4 animate-float cursor-move"
|
||||||
|
style={{
|
||||||
|
zIndex,
|
||||||
|
bottom: `${screenPosition.bottom}px`,
|
||||||
|
right: `${screenPosition.right}px`,
|
||||||
|
userSelect: 'none',
|
||||||
|
touchAction: 'none',
|
||||||
|
pointerEvents: 'all',
|
||||||
|
transform: `translate(${dragPosition.x}px, ${dragPosition.y}px)`
|
||||||
|
}}
|
||||||
|
onMouseDown={startDragging}
|
||||||
|
>
|
||||||
|
{/* 服务器头像 */}
|
||||||
|
<div className="relative">
|
||||||
|
<div className="absolute -inset-1 bg-gradient-to-r from-emerald-400 to-teal-500 rounded-full blur-md opacity-75"></div>
|
||||||
|
<div className="relative w-16 h-16 rounded-full bg-gray-900 overflow-hidden border-2 border-white dark:border-gray-800 shadow-lg">
|
||||||
|
<div className="w-full h-full flex items-center justify-center bg-emerald-600 text-white font-bold text-xl">
|
||||||
|
服
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 对话框气泡 */}
|
||||||
|
<div className="relative bg-white dark:bg-gray-800 rounded-2xl p-4 max-w-xs shadow-xl border border-emerald-200 dark:border-emerald-900/30">
|
||||||
|
{/* 气泡尾部 */}
|
||||||
|
<div className="absolute top-1/2 transform -translate-y-1/2 w-4 h-4 bg-white dark:bg-gray-800 border-t border-l border-emerald-200 dark:border-emerald-900/30 left-0 -translate-x-2 rotate-45"></div>
|
||||||
|
|
||||||
|
{/* 气泡内容 */}
|
||||||
|
<div className="font-medium text-emerald-600 dark:text-emerald-400 mb-2">
|
||||||
|
{serverInfo[currentInfo].title}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-700 dark:text-gray-300 relative min-h-[2rem]">
|
||||||
|
{displayText}
|
||||||
|
<span className={`inline-block w-2 h-4 ml-0.5 bg-gray-500 dark:bg-gray-400 transition-opacity ${cursorVisible && isTyping ? 'opacity-100' : 'opacity-0'}`}></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加浮动动画样式
|
||||||
|
export const ServerInfoBubbleStyles = `
|
||||||
|
@keyframes float {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-float {
|
||||||
|
animation: float 6s ease-in-out infinite;
|
||||||
|
}`;
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
// src/components/auth/AuthForm.tsx
|
// src/components/auth/AuthForm.tsx
|
||||||
|
// 认证表单组件 - 包含登录和注册功能,同时提供测试账号信息
|
||||||
'use client';
|
'use client';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Label } from '@/components/ui/label';
|
import { Label } from '@/components/ui/label';
|
||||||
|
import { signIn } from 'next-auth/react';
|
||||||
|
|
||||||
interface AuthFormProps {
|
interface AuthFormProps {
|
||||||
type: 'login' | 'register';
|
type: 'login' | 'register';
|
||||||
@@ -20,12 +22,30 @@ export default function AuthForm({ type }: AuthFormProps) {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
// 这里应该调用认证API
|
try {
|
||||||
console.log('提交表单', { username, password, email, minecraftUsername });
|
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' // 指定重定向目标
|
||||||
|
});
|
||||||
|
|
||||||
// 模拟API请求
|
console.log('登录结果:', result);
|
||||||
|
} else {
|
||||||
|
// 注册逻辑可以在这里实现
|
||||||
|
console.log('注册信息', { username, password, email, minecraftUsername });
|
||||||
|
// 模拟注册请求
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('认证失败:', error);
|
||||||
|
// 这里可以添加错误处理逻辑
|
||||||
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -35,11 +55,24 @@ export default function AuthForm({ type }: AuthFormProps) {
|
|||||||
<Input
|
<Input
|
||||||
id="username"
|
id="username"
|
||||||
type="text"
|
type="text"
|
||||||
required
|
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
placeholder={type === 'login' ? "用户名或邮箱" : "输入用户名"}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{type === 'login' && (
|
||||||
|
<div>
|
||||||
|
<Label htmlFor="email">电子邮箱(可选)</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
placeholder="或直接输入邮箱登录"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="password">密码</Label>
|
<Label htmlFor="password">密码</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -79,6 +112,14 @@ export default function AuthForm({ type }: AuthFormProps) {
|
|||||||
<Button type="submit" className="w-full" disabled={isLoading}>
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
||||||
{isLoading ? '处理中...' : type === 'login' ? '登录' : '注册'}
|
{isLoading ? '处理中...' : type === 'login' ? '登录' : '注册'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{type === 'login' && (
|
||||||
|
<div className="mt-4 p-3 bg-emerald-50 dark:bg-emerald-900/20 rounded-lg text-sm border border-emerald-200 dark:border-emerald-800">
|
||||||
|
<div className="font-medium mb-1 text-emerald-700 dark:text-emerald-400">测试账号(开发环境)</div>
|
||||||
|
<div>用户名: <span className="font-mono bg-white dark:bg-gray-800 px-1.5 py-0.5 rounded">test</span></div>
|
||||||
|
<div>密码: <span className="font-mono bg-white dark:bg-gray-800 px-1.5 py-0.5 rounded">test</span></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
81
src/components/skins/Canvas2DSkinPreview.tsx
Normal file
81
src/components/skins/Canvas2DSkinPreview.tsx
Normal file
@@ -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<Canvas2DSkinPreviewProps> = ({
|
||||||
|
skinUrl,
|
||||||
|
size = 128,
|
||||||
|
className = ''
|
||||||
|
}) => {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(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 (
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
className={`image-rendering-pixelated ${className}`}
|
||||||
|
style={{ imageRendering: 'pixelated' }} // 确保所有浏览器都使用像素化渲染
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Canvas2DSkinPreview;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// src/components/skins/SkinGrid.tsx
|
// src/components/skins/SkinGrid.tsx
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
|
import Canvas2DSkinPreview from './Canvas2DSkinPreview';
|
||||||
|
|
||||||
interface Skin {
|
interface Skin {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -10,19 +11,34 @@ interface Skin {
|
|||||||
|
|
||||||
export default function SkinGrid({ skins }: { skins: Skin[] }) {
|
export default function SkinGrid({ skins }: { skins: Skin[] }) {
|
||||||
if (skins.length === 0) {
|
if (skins.length === 0) {
|
||||||
return <p>你还没有上传任何皮肤</p>;
|
return null; // 空状态由父组件处理
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
|
||||||
{skins.map((skin) => (
|
{skins.map((skin) => (
|
||||||
<Link key={skin.id} href={`/skins/${skin.id}`}>
|
<Link
|
||||||
<Card className="hover:shadow-lg transition-shadow">
|
key={skin.id}
|
||||||
<CardHeader>
|
href={`/skins/${skin.id}`}
|
||||||
<CardTitle>{skin.name}</CardTitle>
|
className="block transform transition-all duration-300 hover:-translate-y-1"
|
||||||
|
>
|
||||||
|
<Card className="h-full border border-gray-100 dark:border-gray-700 overflow-hidden group">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-emerald-50 to-teal-50 dark:from-emerald-900/5 dark:to-teal-900/5 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
|
||||||
|
<div className="aspect-square bg-gray-50 dark:bg-gray-900 flex items-center justify-center p-4 relative">
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-gray-50 to-gray-100 dark:from-gray-900 dark:to-gray-800 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
||||||
|
<Canvas2DSkinPreview
|
||||||
|
skinUrl={`/test-skin.png`}
|
||||||
|
size={128}
|
||||||
|
className="max-w-full max-h-full relative z-10 transition-transform duration-500 group-hover:scale-110"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<CardHeader className="relative z-10 pb-2">
|
||||||
|
<CardTitle className="text-lg font-bold text-gray-800 dark:text-white">{skin.name}</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="relative z-10">
|
||||||
<p className="text-gray-500">创建于: {skin.createdAt}</p>
|
<p className="text-sm text-gray-500 dark:text-gray-400">上传于: {skin.createdAt}</p>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -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 (
|
|
||||||
<div className="border rounded-md p-2 bg-gray-50">
|
|
||||||
<Image
|
|
||||||
src={imageUrl}
|
|
||||||
alt="皮肤预览"
|
|
||||||
width={128}
|
|
||||||
height={64}
|
|
||||||
className="object-contain mx-auto"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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<SkinModelProps> = ({ skinUrl }) => {
|
|
||||||
const texture = useTexture(skinUrl);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<mesh rotation={[0, Math.PI / 4, 0]}>
|
|
||||||
<boxGeometry args={[1, 2, 0.5]} />
|
|
||||||
<meshStandardMaterial map={texture} side={THREE.DoubleSide} />
|
|
||||||
</mesh>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const FallbackModel: React.FC = () => (
|
|
||||||
<mesh>
|
|
||||||
<boxGeometry args={[1, 1, 1]} />
|
|
||||||
<meshBasicMaterial color="#cccccc" wireframe opacity={0.5} transparent />
|
|
||||||
</mesh>
|
|
||||||
);
|
|
||||||
|
|
||||||
interface SkinViewer3DProps {
|
|
||||||
skinUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SkinViewer3D: React.FC<SkinViewer3DProps> = ({ skinUrl }) => {
|
|
||||||
return (
|
|
||||||
<div className="w-full h-96 bg-gray-100 dark:bg-gray-800 rounded-lg">
|
|
||||||
<Canvas
|
|
||||||
camera={{
|
|
||||||
position: [3, 1.5, 3],
|
|
||||||
fov: 50,
|
|
||||||
near: 0.1,
|
|
||||||
far: 100
|
|
||||||
}}
|
|
||||||
shadows
|
|
||||||
>
|
|
||||||
<ambientLight intensity={0.7} />
|
|
||||||
<spotLight
|
|
||||||
position={[5, 8, 5]}
|
|
||||||
angle={0.3}
|
|
||||||
penumbra={1}
|
|
||||||
intensity={1.5}
|
|
||||||
castShadow
|
|
||||||
shadow-mapSize-width={1024}
|
|
||||||
shadow-mapSize-height={1024}
|
|
||||||
/>
|
|
||||||
<Suspense fallback={<FallbackModel />}>
|
|
||||||
<SkinModel skinUrl={skinUrl} />
|
|
||||||
<OrbitControls
|
|
||||||
enableZoom={true}
|
|
||||||
enablePan={true}
|
|
||||||
minPolarAngle={Math.PI / 6}
|
|
||||||
maxPolarAngle={Math.PI / 1.8}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
</Canvas>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SkinViewer3D;
|
|
||||||
@@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|||||||
<div
|
<div
|
||||||
data-slot="card"
|
data-slot="card"
|
||||||
className={cn(
|
className={cn(
|
||||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border border-gray-200 dark:border-gray-700 py-6 shadow-lg relative transition-all duration-300 hover:-translate-y-1 hover:shadow-xl",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
87
src/lib/api/actions.ts
Normal file
87
src/lib/api/actions.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// src/lib/api/actions.ts
|
||||||
|
'use server';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_URL = process.env.NEXT_PUBLIC_API_URL || '/api';
|
||||||
|
|
||||||
|
// 导出退出登录函数 - 供服务器端使用
|
||||||
|
export const serverSignOut = async () => {
|
||||||
|
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: '注册失败,请稍后再试' };
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -22,10 +22,31 @@ export const authOptions: AuthOptions = {
|
|||||||
name: 'Credentials',
|
name: 'Credentials',
|
||||||
credentials: {
|
credentials: {
|
||||||
username: { label: "用户名", type: "text" },
|
username: { label: "用户名", type: "text" },
|
||||||
|
email: { label: "邮箱", type: "email" },
|
||||||
password: { label: "密码", type: "password" }
|
password: { label: "密码", type: "password" }
|
||||||
},
|
},
|
||||||
async authorize(credentials) {
|
async authorize(credentials) {
|
||||||
|
// 默认测试账号 - 用于开发和测试环境
|
||||||
|
const TEST_USERNAME = 'test';
|
||||||
|
const TEST_PASSWORD = 'test';
|
||||||
|
|
||||||
try {
|
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`, {
|
const response = await axios.post(`${API_URL}/auth/login`, {
|
||||||
username: credentials?.username,
|
username: credentials?.username,
|
||||||
password: credentials?.password
|
password: credentials?.password
|
||||||
@@ -48,7 +69,7 @@ export const authOptions: AuthOptions = {
|
|||||||
],
|
],
|
||||||
pages: {
|
pages: {
|
||||||
signIn: '/login',
|
signIn: '/login',
|
||||||
signOut: '/login'
|
signOut: '/login' // 退出登录后重定向到登录页面
|
||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
async jwt({ token, user }) {
|
async jwt({ token, user }) {
|
||||||
@@ -62,6 +83,15 @@ export const authOptions: AuthOptions = {
|
|||||||
session.user.id = token.id as string;
|
session.user.id = token.id as string;
|
||||||
}
|
}
|
||||||
return session;
|
return session;
|
||||||
|
},
|
||||||
|
// 登录成功后重定向到用户主页
|
||||||
|
async redirect({ url, baseUrl }) {
|
||||||
|
// 如果已经有明确的重定向URL,则使用它
|
||||||
|
if (url.startsWith(baseUrl)) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
// 否则默认重定向到用户主页
|
||||||
|
return `${baseUrl}/user-home`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
@@ -70,25 +100,4 @@ export const authOptions: AuthOptions = {
|
|||||||
// 导出 NextAuth 处理函数
|
// 导出 NextAuth 处理函数
|
||||||
export const nextAuthHandler = NextAuth(authOptions);
|
export const nextAuthHandler = NextAuth(authOptions);
|
||||||
|
|
||||||
// 导出登录函数
|
// Server Actions已移至actions.ts文件
|
||||||
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 方法
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
17
src/types/global.d.ts
vendored
17
src/types/global.d.ts
vendored
@@ -1,17 +1,8 @@
|
|||||||
// src/types/global.d.ts
|
// src/types/global.d.ts
|
||||||
import * as THREE from 'three';
|
|
||||||
import { Object3DNode } from '@react-three/fiber';
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace JSX {
|
interface Window {
|
||||||
interface IntrinsicElements {
|
__NEXT_DATA__?: any;
|
||||||
// Three.js 元素
|
gtag?: (...args: any[]) => void;
|
||||||
mesh: Object3DNode<THREE.Mesh, typeof THREE.Mesh>;
|
// 可以在这里扩展window对象的类型
|
||||||
boxGeometry: Object3DNode<THREE.BoxGeometry, typeof THREE.BoxGeometry>;
|
|
||||||
meshStandardMaterial: Object3DNode<THREE.MeshStandardMaterial, typeof THREE.MeshStandardMaterial>;
|
|
||||||
ambientLight: Object3DNode<THREE.AmbientLight, typeof THREE.AmbientLight>;
|
|
||||||
spotLight: Object3DNode<THREE.SpotLight, typeof THREE.SpotLight>;
|
|
||||||
meshBasicMaterial: Object3DNode<THREE.MeshBasicMaterial, typeof THREE.MeshBasicMaterial>;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
17
src/types/three.d.ts
vendored
17
src/types/three.d.ts
vendored
@@ -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<THREE.Mesh, typeof THREE.Mesh>;
|
|
||||||
boxGeometry: Object3DNode<THREE.BoxGeometry, typeof THREE.BoxGeometry>;
|
|
||||||
meshStandardMaterial: Object3DNode<THREE.MeshStandardMaterial, typeof THREE.MeshStandardMaterial>;
|
|
||||||
ambientLight: Object3DNode<THREE.AmbientLight, typeof THREE.AmbientLight>;
|
|
||||||
spotLight: Object3DNode<THREE.SpotLight, typeof THREE.SpotLight>;
|
|
||||||
meshBasicMaterial: Object3DNode<THREE.MeshBasicMaterial, typeof THREE.MeshBasicMaterial>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
114
数据库参考.txt
Normal file
114
数据库参考.txt
Normal file
@@ -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='皮肤与披风材质表';
|
||||||
Reference in New Issue
Block a user