commit 9c0bcc23ae7397f9a952422ddb74b5390f7b72fb Author: hanqin Date: Wed Mar 18 09:19:39 2026 +0800 初始化提交 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f5282bb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,237 @@ +# AGENTS.md - Build God Project + +This document provides guidelines for agentic coding agents working on this repository. + +## Project Overview + +Build God is a full-stack application with: +- **Backend**: ASP.NET Core 8.0 Web API (C#) +- **Frontend**: Vue 3 + TypeScript + Vite + Element Plus + +--- + +## 1. Build, Lint, and Test Commands + +### Backend (.NET API) + +Located in: `BuildGod_Api/` + +```bash +# Build the solution +dotnet build BuildGod_Api.sln + +# Run the API (from BuildGod_Api directory) +dotnet run --project Build_God_Api/Build_God_Api.csproj + +# Build and run with Docker +docker build -t build-god-api BuildGod_Api/ +docker run -p 5091:80 build-god-api +``` + +**Note**: No test framework is currently configured for the backend. + +### Frontend (Vue 3) + +Located in: `Build_God_Admin_Frontend/Frontend/` + +```bash +# Install dependencies +npm install + +# Start development server +npm run dev + +# Build for production +npm run build + +# Type-check only +npm run type-check + +# Preview production build +npm run preview +``` + +**Running a single test**: No tests are currently configured. + +--- + +## 2. Code Style Guidelines + +### Backend (.NET/C#) + +#### General Conventions +- Use **file-scoped namespaces** (`namespace Build_God_Api.Controllers;`) +- Enable **nullable reference types** (`enable`) +- Use **primary constructors** for dependency injection +- Use **async/await** for all I/O operations + +#### Naming Conventions +- **Types/Classes**: PascalCase (`AccountController`, `AccountService`) +- **Methods**: PascalCase (`GetAccount`, `Register`) +- **Properties**: PascalCase (`UserName`, `Email`) +- **Local variables**: camelCase (`accountId`, `existingAccount`) +- **Parameters**: camelCase (`accountName`, `emailAddress`) +- **Interfaces**: Prefix with `I` (`IAccountService`, `ICurrentUserService`) + +#### Project Structure +``` +Controllers/ # API endpoints +Services/ # Business logic (interface + implementation) +DB/ # Database entities/models +``` + +#### Error Handling +- Return `BadRequest("error message")` for validation errors +- Return `Ok(result)` for successful operations +- Use try-catch with logging for operations that may fail +- Use `ILogger` for logging + +#### Dependency Injection +```csharp +public class AccountController(IAccountService service) : ControllerBase +{ + private readonly IAccountService _service = service; + // ... +} +``` + +#### Route Conventions +- Use `[Route("api/god/[controller]")]` +- Use `[ApiController]` attribute +- Use HTTP method attributes: `[HttpGet]`, `[HttpPost]`, `[HttpPut]`, `[HttpDelete]` + +--- + +### Frontend (Vue 3 + TypeScript) + +#### General Conventions +- Use **Composition API** with ` + + + + +``` + +#### Pinia Store Pattern +```typescript +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' + +export const useAuthStore = defineStore('auth', () => { + const token = ref('') + const isAuthenticated = computed(() => !!token.value) + + const login = async () => { /* ... */ } + + return { token, isAuthenticated, login } +}) +``` + +#### UI Library (Element Plus) +- Use Element Plus components: `ElButton`, `ElInput`, `ElTable`, etc. +- Use `ElMessage` for notifications +- Use `ElMessageBox` for confirmations +- Follow Element Plus prop naming conventions + +#### Error Handling +- Use try-catch with async/await +- Show errors with `ElMessage.error()` +- Handle 401 (unauthorized) by redirecting to login + +--- + +## 3. API Integration + +### Backend API Base URL +- Development: `http://localhost:5091/api/god/` +- Routes follow pattern: `api/god/{entity}` (e.g., `api/god/account/login`) + +### Authentication +- Use JWT Bearer tokens +- Store token in `localStorage` as `auth_token` +- Include in requests: `Authorization: Bearer {token}` + +### Common Endpoints +- POST `/api/god/account/register` - Register new account +- POST `/api/god/account/login` - User login +- POST `/api/god/account/login/admin` - Admin login (use: name="admin", password="build_god.123") + +--- + +## 4. Development Workflow + +1. **Start Backend**: Run `dotnet run` in `BuildGod_Api/` +2. **Start Frontend**: Run `npm run dev` in `Build_God_Admin_Frontend/Frontend/` +3. **Access**: Frontend runs on `http://localhost:5173` by default + +--- + +## 5. Adding New Features + +### Backend +1. Create model in `DB/` folder (extend `BaseEntity`) +2. Create service interface in `Services/` +3. Implement service in `Services/` +4. Create controller in `Controllers/` +5. Register service in `Program.cs` + +### Frontend +1. Create API module in `src/api/` +2. Add Pinia store in `src/stores/` (if needed) +3. Create view component in `src/views/` +4. Add route in `src/router/index.ts` + +--- + +## 6. Important Notes + +- The admin login credentials are hardcoded: `admin` / `build_god.123` +- Backend runs on port 5091 +- Frontend uses Vite proxy for API calls (configured in `vite.config.ts`) +- Use `npm run type-check` before committing to catch TypeScript errors +- Always use `await` with async operations - never ignore promises diff --git a/Build_God_Admin_Frontend/.gitignore b/Build_God_Admin_Frontend/.gitignore new file mode 100644 index 0000000..d28c088 --- /dev/null +++ b/Build_God_Admin_Frontend/.gitignore @@ -0,0 +1,37 @@ +# ============== 依赖目录 ============== +# npm/yarn/pnpm 安装的第三方依赖,体积大,无需提交,部署时重新安装即可 +node_modules/ +.pnpm-store/ + +# ============== 打包构建产物 ============== +# Vue-CLI 打包输出目录 +dist/ +# Vite 打包默认输出目录(和dist二选一即可,兼容写法) +dist-ssr/ +# 旧版webpack打包目录 +build/ + +# ============== 环境变量配置文件 ============== +# 包含密钥、接口地址等敏感信息,绝对禁止提交 +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# ============== 日志与缓存文件 ============== +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# ============== 编辑器/IDE 配置文件 ============== +.idea/ +.vscode/ +*.swp +*.swo +.DS_Store # macOS系统自动生成的隐藏文件 + +# ============== 测试/编译缓存 ============== +coverage/ +*.local \ No newline at end of file diff --git a/Build_God_Admin_Frontend/Frontend/.env.development b/Build_God_Admin_Frontend/Frontend/.env.development new file mode 100644 index 0000000..40bd306 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/.env.development @@ -0,0 +1,2 @@ +# 开发环境配置 +VITE_API_URL=http://localhost:5091/api/god/ diff --git a/Build_God_Admin_Frontend/Frontend/.env.production b/Build_God_Admin_Frontend/Frontend/.env.production new file mode 100644 index 0000000..1e3a2ec --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/.env.production @@ -0,0 +1,2 @@ +# 生产环境配置 +VITE_API_URL=https://api.example.com/api diff --git a/Build_God_Admin_Frontend/Frontend/.gitignore b/Build_God_Admin_Frontend/Frontend/.gitignore new file mode 100644 index 0000000..cd68f14 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/.gitignore @@ -0,0 +1,39 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +.DS_Store +dist +dist-ssr +coverage +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +*.tsbuildinfo + +.eslintcache + +# Cypress +/cypress/videos/ +/cypress/screenshots/ + +# Vitest +__screenshots__/ + +# Vite +*.timestamp-*-*.mjs diff --git a/Build_God_Admin_Frontend/Frontend/.vs/Frontend/CopilotIndices/17.14.1204.46620/CodeChunks.db b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/CopilotIndices/17.14.1204.46620/CodeChunks.db new file mode 100644 index 0000000..bd340e9 Binary files /dev/null and b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/CopilotIndices/17.14.1204.46620/CodeChunks.db differ diff --git a/Build_God_Admin_Frontend/Frontend/.vs/Frontend/CopilotIndices/17.14.1204.46620/SemanticSymbols.db b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/CopilotIndices/17.14.1204.46620/SemanticSymbols.db new file mode 100644 index 0000000..ee74a2b Binary files /dev/null and b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/CopilotIndices/17.14.1204.46620/SemanticSymbols.db differ diff --git a/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/386c32c4-b9ee-4dbd-a581-3a705798da36.vsidx b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/386c32c4-b9ee-4dbd-a581-3a705798da36.vsidx new file mode 100644 index 0000000..56fc604 Binary files /dev/null and b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/386c32c4-b9ee-4dbd-a581-3a705798da36.vsidx differ diff --git a/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/4728dbf9-f440-46eb-88e4-4db8163ed89b.vsidx b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/4728dbf9-f440-46eb-88e4-4db8163ed89b.vsidx new file mode 100644 index 0000000..70aef67 Binary files /dev/null and b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/4728dbf9-f440-46eb-88e4-4db8163ed89b.vsidx differ diff --git a/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/5605984b-62f9-4eaa-8bf4-955fb18f6973.vsidx b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/5605984b-62f9-4eaa-8bf4-955fb18f6973.vsidx new file mode 100644 index 0000000..faaf35b Binary files /dev/null and b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/5605984b-62f9-4eaa-8bf4-955fb18f6973.vsidx differ diff --git a/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/9255374b-c7f1-458c-a33b-8b234c94b5a0.vsidx b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/9255374b-c7f1-458c-a33b-8b234c94b5a0.vsidx new file mode 100644 index 0000000..7f56ef1 Binary files /dev/null and b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/9255374b-c7f1-458c-a33b-8b234c94b5a0.vsidx differ diff --git a/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/a4763d64-25e3-4dbe-bf7f-24febff8dc81.vsidx b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/a4763d64-25e3-4dbe-bf7f-24febff8dc81.vsidx new file mode 100644 index 0000000..70aef67 Binary files /dev/null and b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/FileContentIndex/a4763d64-25e3-4dbe-bf7f-24febff8dc81.vsidx differ diff --git a/Build_God_Admin_Frontend/Frontend/.vs/Frontend/config/applicationhost.config b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/config/applicationhost.config new file mode 100644 index 0000000..cdd2df8 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/config/applicationhost.config @@ -0,0 +1,1026 @@ + + + + + + + +
+
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/.vs/Frontend/v17/.wsuo b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/v17/.wsuo new file mode 100644 index 0000000..813d295 Binary files /dev/null and b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/v17/.wsuo differ diff --git a/Build_God_Admin_Frontend/Frontend/.vs/Frontend/v17/DocumentLayout.backup.json b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/v17/DocumentLayout.backup.json new file mode 100644 index 0000000..5e61e86 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/v17/DocumentLayout.backup.json @@ -0,0 +1,63 @@ +{ + "Version": 1, + "WorkspaceRootPath": "D:\\Test\\Build_God\\Build_God_Admin_Frontend\\Frontend\\", + "Documents": [], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:129:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:128:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:136:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:135:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:134:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:133:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:132:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:131:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:130:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:131:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/Build_God_Admin_Frontend/Frontend/.vs/Frontend/v17/DocumentLayout.json b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/v17/DocumentLayout.json new file mode 100644 index 0000000..ecf552c --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/.vs/Frontend/v17/DocumentLayout.json @@ -0,0 +1,59 @@ +{ + "Version": 1, + "WorkspaceRootPath": "D:\\Test\\Build_God\\Build_God_Admin_Frontend\\Frontend\\", + "Documents": [], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:129:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:128:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:136:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:135:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:134:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:133:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:132:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:131:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:130:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:131:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/Build_God_Admin_Frontend/Frontend/.vs/ProjectSettings.json b/Build_God_Admin_Frontend/Frontend/.vs/ProjectSettings.json new file mode 100644 index 0000000..f8b4888 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/Build_God_Admin_Frontend/Frontend/.vs/VSWorkspaceState.json b/Build_God_Admin_Frontend/Frontend/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..6b61141 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/.vs/VSWorkspaceState.json @@ -0,0 +1,6 @@ +{ + "ExpandedNodes": [ + "" + ], + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/Build_God_Admin_Frontend/Frontend/.vs/slnx.sqlite b/Build_God_Admin_Frontend/Frontend/.vs/slnx.sqlite new file mode 100644 index 0000000..c7a1067 Binary files /dev/null and b/Build_God_Admin_Frontend/Frontend/.vs/slnx.sqlite differ diff --git a/Build_God_Admin_Frontend/Frontend/ADMIN_SYSTEM_README.md b/Build_God_Admin_Frontend/Frontend/ADMIN_SYSTEM_README.md new file mode 100644 index 0000000..6bf4147 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/ADMIN_SYSTEM_README.md @@ -0,0 +1,173 @@ +# 后台管理系统 + +一个基于 Vue 3 + TypeScript + Vite 的现代化后台管理系统框架。 + +## 🎨 特性 + +- ✨ **暗黑主题设计** - 黑白双色优雅设计,舒适的深色主题 +- 🔐 **认证系统** - 完整的登录/登出功能 +- 📊 **仪表板** - 数据统计和概览 +- 👥 **用户管理** - CRUD 操作示例 +- 📦 **产品管理** - 完整的产品管理模块 +- 📋 **订单管理** - 订单管理系统 +- ⚙️ **系统设置** - 系统配置管理 +- 🎯 **响应式布局** - 完美适配各种屏幕尺寸 + +## 🚀 快速开始 + +### 安装依赖 + +```bash +npm install +``` + +### 开发模式 + +```bash +npm run dev +``` + +访问 `http://localhost:5173` 查看应用 + +### 生产构建 + +```bash +npm run build +``` + +### 类型检查 + +```bash +npm run type-check +``` + +### 预览构建结果 + +```bash +npm run preview +``` + +## 📁 项目结构 + +``` +src/ +├── assets/ # 静态资源和样式 +├── components/ # 可复用组件 +│ ├── Header.vue # 顶部导航栏 +│ └── Sidebar.vue # 侧边栏菜单 +├── router/ # 路由配置 +├── stores/ # Pinia 状态管理 +│ ├── auth.ts # 认证状态 +│ └── counter.ts # 计数器示例 +├── views/ # 页面组件 +│ ├── LoginView.vue # 登录页面 +│ ├── AdminLayout.vue # 后台布局 +│ └── admin/ # 后台管理页面 +│ ├── DashboardView.vue # 仪表板 +│ ├── UsersView.vue # 用户管理 +│ ├── ProductsView.vue # 产品管理 +│ ├── OrdersView.vue # 订单管理 +│ └── SettingsView.vue # 系统设置 +└── main.ts # 应用入口 +``` + +## 🔑 认证信息 + +### 测试账号 + +- **用户名**: `admin` +- **密码**: `123456` + +> 注:当前使用本地模拟认证,实际项目需要连接真实 API 服务器 + +## 🎯 功能模块 + +### 仪表板 (Dashboard) +- 数据统计卡片 +- 最近订单表格 +- 统计数据可视化 + +### 用户管理 +- 用户列表展示 +- 搜索功能 +- 新增用户 +- 编辑用户信息 +- 删除用户 +- 状态管理(启用/禁用) + +### 产品管理 +- 产品列表管理 +- 产品搜索和过滤 +- 新增产品 +- 编辑产品 +- 删除产品 +- 库存管理 +- 产品分类 + +### 订单管理 +- 订单列表展示 +- 订单搜索和过滤 +- 订单状态管理 +- 新增/编辑/删除订单 +- 支付方式记录 + +### 系统设置 +- 基本设置 +- 安全设置 +- 备份配置 +- 文件上传设置 + +## 🎨 主题配置 + +系统使用黑白双色暗黑主题: + +- **主色调**: `#667eea` (紫蓝色) +- **背景色**: `#0a0e27` (深蓝黑) +- **卡片色**: `#1f2937` (深灰) +- **文字色**: `#e5e7eb` (浅灰) + +## 📦 技术栈 + +- **Vue 3** - 渐进式 JavaScript 框架 +- **TypeScript** - JavaScript 超集 +- **Vite** - 下一代前端构建工具 +- **Vue Router** - 官方路由库 +- **Pinia** - Vue 3 状态管理 +- **Element Plus** - UI 组件库 +- **Axios** - HTTP 客户端库 + +## 🔒 路由保护 + +系统包含路由导航守卫,确保: +- 未登录用户无法访问后台页面 +- 已登录用户不能重复进入登录页 +- 自动重定向到登录页 + +## 💾 数据持久化 + +认证信息保存在 `localStorage` 中: +- `auth_token` - 用户令牌 +- `user` - 用户信息 + +## 🛠 开发建议 + +1. **状态管理**: 使用 Pinia store 管理全局状态 +2. **API 集成**: 将模拟数据替换为真实 API 调用 +3. **表单验证**: 为表单添加完善的验证规则 +4. **错误处理**: 添加全局错误处理和日志记录 +5. **权限管理**: 根据用户角色实现权限控制 + +## 📝 注意事项 + +- 当前使用本地状态管理,实际项目需要后端 API 支持 +- 表单数据为演示用途,需要根据实际需求修改 +- 样式可根据公司品牌进行定制 +- 建议在生产环境中启用权限检查和数据验证 + +## 🤝 贡献 + +欢迎提交 Issue 和 Pull Request + +## 📄 许可证 + +MIT diff --git a/Build_God_Admin_Frontend/Frontend/API_IMPLEMENTATION_COMPLETE.md b/Build_God_Admin_Frontend/Frontend/API_IMPLEMENTATION_COMPLETE.md new file mode 100644 index 0000000..7f52ff7 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/API_IMPLEMENTATION_COMPLETE.md @@ -0,0 +1,434 @@ +# ✨ API 集成完成!完整总结 + +## 🎉 已为你完成的工作 + +### 1. ✅ Axios 配置文件 (`src/api/index.ts`) + +完整的 HTTP 客户端配置,包括: + +```typescript +✓ axios 实例创建 +✓ 请求拦截器 - 自动添加 Authorization header +✓ 响应拦截器 - 自动解析响应、处理错误 +✓ Token 过期处理 - 自动清除并跳转登录 +✓ 环境变量支持 - 开发/生产不同 API 地址 +``` + +### 2. ✅ API 服务层 (`src/api/auth.ts`) + +完整的登录 API 接口定义,包括: + +```typescript +✓ 类型定义 (TypeScript) +✓ loginApi() - 登录接口 +✓ logoutApi() - 登出接口 +✓ getUserInfoApi() - 获取用户信息 +✓ refreshTokenApi() - 刷新 token +✓ 详细的 JSDoc 注释 +``` + +### 3. ✅ 认证 Store 更新 (`src/stores/auth.ts`) + +集成后端 API 的状态管理: + +```typescript +✓ async login() - 调用真实 API +✓ async logout() - 调用登出 API +✓ setUser() - 更新用户信息 +✓ localStorage 持久化 +✓ 错误处理 +``` + +### 4. ✅ 登录页面更新 (`src/views/LoginView.vue`) + +支持异步 API 调用的登录页面: + +```typescript +✓ async handleLogin() +✓ 加载状态管理 +✓ 错误处理和提示 +✓ 回车键提交 +✓ 完整的错误消息 +``` + +### 5. ✅ 环境配置文件 + +``` +✓ .env.development - 开发环境 API 地址 +✓ .env.production - 生产环境 API 地址 +✓ .env.local - 本地覆盖配置 +``` + +### 6. ✅ 完整的文档 + +``` +✓ API_INTEGRATION_GUIDE.md - 详细集成指南 +✓ BACKEND_API_EXAMPLE.md - 后端实现参考 (Node.js/Python) +✓ API_INTEGRATION_COMPLETE.md - 完整工作流程说明 +✓ API_QUICK_REFERENCE.md - 快速参考卡片 +``` + +## 📂 项目结构 + +``` +src/ +├── api/ +│ ├── index.ts ← axios 实例 + 拦截器 +│ └── auth.ts ← 登录 API 接口定义 ← 可在这里添加更多 API +├── stores/ +│ ├── auth.ts ← 认证状态管理(已与 API 集成) +│ └── counter.ts +├── views/ +│ ├── LoginView.vue ← 登录页面(已支持异步 API) +│ ├── AdminLayout.vue +│ └── admin/ +│ ├── DashboardView.vue +│ ├── UsersView.vue +│ ├── ProductsView.vue +│ ├── OrdersView.vue +│ └── SettingsView.vue +├── components/ +│ ├── Header.vue +│ ├── Sidebar.vue +│ └── ... +├── router/ +│ └── index.ts +├── main.ts +└── App.vue + +root/ +├── .env.development ← 开发环境配置 +├── .env.production ← 生产环境配置 +├── .env.local ← 本地覆盖 +├── API_INTEGRATION_GUIDE.md ← 详细教程 +├── BACKEND_API_EXAMPLE.md ← 后端代码示例 +├── API_INTEGRATION_COMPLETE.md ← 完整说明 +└── API_QUICK_REFERENCE.md ← 快速参考 +``` + +## 🔄 完整工作流 + +### 前端流程 + +``` +1️⃣ 用户在登录页输入用户名和密码 + └─> username.value = 'admin' + └─> password.value = '123456' + +2️⃣ 点击"登录"按钮 + └─> handleLogin() 被调用 + +3️⃣ 登录方法开始 + └─> loading.value = true + └─> authStore.login(username, password) 被调用 + +4️⃣ Store 中的登录方法 + └─> loginApi({ username, password }) 被调用 + └─> 返回 Promise + +5️⃣ API 函数 + └─> http.post('/auth/login', { username, password }) + └─> 返回 Promise + +6️⃣ Axios 请求拦截器 + └─> 检查 localStorage 中是否有 auth_token + └─> 如果有,添加 Authorization: Bearer + └─> 发送 HTTP 请求 + +7️⃣ HTTP 请求发送到后端 + └─> POST http://localhost:3000/api/auth/login + └─> Content-Type: application/json + └─> Body: { "username": "admin", "password": "123456" } + +8️⃣ 后端处理请求 + └─> 验证用户名和密码 + └─> 生成 JWT token + └─> 返回 JSON 响应 + +9️⃣ Axios 响应拦截器 + └─> 检查响应状态码 + └─> 如果 401,清除 token 并跳转登录 + └─> 提取 response.data 并返回 + +🔟 Store 接收响应 + └─> 检查 response.code === 200 + └─> 保存 token 和 user 到 localStorage + └─> 返回 true + +1️⃣1️⃣ 组件接收结果 + └─> 显示"登录成功"提示 + └─> 路由跳转到 /admin/dashboard + +✨ 完成!用户已登录 +``` + +## 💻 后端需要做什么 + +### 最小实现示例 (Node.js Express) + +```javascript +app.post('/api/auth/login', (req, res) => { + const { username, password } = req.body + + // 验证用户 + if (username === 'admin' && password === '123456') { + // 生成 token (使用 JWT) + const token = jwt.sign({ id: 1, username }, 'secret') + + // 返回正确格式的响应 + res.json({ + code: 200, + message: '登录成功', + data: { + token, + user: { + id: '1', + username: 'admin', + email: 'admin@example.com', + role: 'admin' + } + } + }) + } else { + res.status(401).json({ + code: 401, + message: '用户名或密码错误' + }) + } +}) +``` + +### 响应格式必须严格遵守 + +✅ **必须返回** +```json +{ + "code": 200, + "message": "登录成功", + "data": { + "token": "jwt_token_here", + "user": { + "id": "1", + "username": "admin", + "email": "admin@example.com", + "role": "admin" + } + } +} +``` + +❌ **不要这样返回** +```json +{ + "token": "...", // ❌ 缺少 code 和 message + "user": {...} +} +``` + +## 🚀 立即开始 + +### 第 1 步:准备后端服务 + +```bash +# 方案 A: 使用提供的 Node.js 示例 +# 参考 BACKEND_API_EXAMPLE.md 创建 server.js +node server.js + +# 方案 B: 使用你自己的后端 +# 确保实现了 POST /api/auth/login 接口 +# 返回上述格式的 JSON +``` + +### 第 2 步:配置前端 API 地址 + +编辑 `.env.development`(可选,因为已配置默认值): + +``` +VITE_API_URL=http://localhost:3000/api +``` + +### 第 3 步:启动前端开发服务器 + +```bash +npm run dev +``` + +### 第 4 步:测试登录 + +1. 访问 http://localhost:5173 +2. 输入用户名和密码 +3. 点击登录 +4. 应该看到"登录成功"提示并跳转到仪表板 + +### 第 5 步:验证请求 + +1. 打开浏览器开发者工具 (F12) +2. 点击 Network 标签 +3. 再次登录 +4. 看到 POST /api/auth/login 请求 +5. 点击查看请求和响应的详细信息 + +## 📝 代码使用示例 + +### 在登录页使用 + +```typescript +// LoginView.vue +import { useAuthStore } from '@/stores/auth' + +const authStore = useAuthStore() +const success = await authStore.login('admin', '123456') +``` + +### 在其他组件中获取用户信息 + +```typescript +import { useAuthStore } from '@/stores/auth' + +const authStore = useAuthStore() +console.log(authStore.user) // 用户信息 +console.log(authStore.token) // JWT token +console.log(authStore.isAuthenticated) // 是否已登录 +``` + +### 创建新的 API 接口 + +```typescript +// src/api/users.ts +import http from './index' + +export const getUsersApi = () => http.get('/users') +export const createUserApi = (data) => http.post('/users', data) +export const updateUserApi = (id, data) => http.put(`/users/${id}`, data) +export const deleteUserApi = (id) => http.delete(`/users/${id}`) + +// 在组件中使用 +import { getUsersApi } from '@/api/users' +const response = await getUsersApi() +``` + +## ✅ 完成清单 + +### 前端完成 +- [x] Axios 配置 +- [x] 请求/响应拦截器 +- [x] API 服务层 +- [x] 认证 Store +- [x] 登录页面 +- [x] 类型定义 +- [x] 环境配置 +- [x] 完整文档 + +### 等待后端完成 +- [ ] 实现 POST /api/auth/login 接口 +- [ ] 返回正确的 JSON 格式 +- [ ] 配置 CORS 允许前端请求 +- [ ] 其他业务接口 (可选) + +## 🎓 后续学习 + +### 添加其他 API + +参考登录 API,在 `src/api/` 中创建新文件: + +``` +src/api/ +├── index.ts # axios 实例 +├── auth.ts # ✓ 已完成 +├── users.ts # 待添加 +├── products.ts # 待添加 +├── orders.ts # 待添加 +└── ... +``` + +### 完整的 CRUD 示例 (用户管理) + +```typescript +// src/api/users.ts +import http from './index' + +export interface User { + id: number + username: string + email: string +} + +// 查 - Read +export const getUsersApi = (page = 1, limit = 10) => + http.get('/users', { params: { page, limit } }) + +// 增 - Create +export const createUserApi = (data: User) => + http.post('/users', data) + +// 改 - Update +export const updateUserApi = (id: number, data: Partial) => + http.put(`/users/${id}`, data) + +// 删 - Delete +export const deleteUserApi = (id: number) => + http.delete(`/users/${id}`) +``` + +## 🔍 调试技巧 + +### 查看请求和响应 + +```typescript +// 在 api/index.ts 中添加日志 +instance.interceptors.request.use((config) => { + console.log('📤 发送请求:', config.method?.toUpperCase(), config.url) + console.log('数据:', config.data) + return config +}) + +instance.interceptors.response.use((response) => { + console.log('📥 收到响应:', response) + return response +}) +``` + +### 检查 localStorage + +```typescript +// 在浏览器控制台执行 +localStorage.getItem('auth_token') // 查看 token +localStorage.getItem('user') // 查看用户信息 +``` + +### 测试 API 接口 + +```bash +# 使用 curl +curl -X POST http://localhost:3000/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"123456"}' + +# 或使用 Postman/Insomnia GUI 工具 +``` + +## 🎉 总结 + +你现在拥有: + +✅ **完整的前端 API 架构** - 随时可以调用后端接口 +✅ **登录示例** - 展示了完整的请求/响应流程 +✅ **可扩展的 API 服务层** - 轻松添加更多接口 +✅ **完整的文档** - 学习和参考资料 +✅ **后端代码示例** - Node.js 和 Python 实现 + +现在你需要做的只是: +1. 实现后端登录接口 +2. 启动后端和前端 +3. 测试登录功能 + +其他所有的 CRUD 操作都可以按照登录示例的方式进行! + +祝你开发愉快!🚀 + +--- + +📚 **相关文档** +- [详细 API 集成指南](./API_INTEGRATION_GUIDE.md) +- [后端实现参考](./BACKEND_API_EXAMPLE.md) +- [快速参考卡片](./API_QUICK_REFERENCE.md) diff --git a/Build_God_Admin_Frontend/Frontend/API_INTEGRATION_COMPLETE.md b/Build_God_Admin_Frontend/Frontend/API_INTEGRATION_COMPLETE.md new file mode 100644 index 0000000..c4fe061 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/API_INTEGRATION_COMPLETE.md @@ -0,0 +1,507 @@ +# API 集成完整指南 + +你好!现在项目已经完全配置好了 axios 和 API 层。以下是完整的说明。 + +## 🎯 快速概览 + +### 已为你完成的工作 + +✅ **Axios 配置** - 完整的 HTTP 客户端配置 +✅ **API 服务层** - 登录 API 接口定义 +✅ **认证 Store** - 与后端 API 集成的状态管理 +✅ **登录页面** - 支持异步 API 调用 +✅ **环境配置** - 开发和生产环境变量 +✅ **详细文档** - API 集成指南和后端示例 + +## 📁 项目结构 + +``` +src/ +├── api/ +│ ├── index.ts ← axios 实例(请求/响应拦截器) +│ └── auth.ts ← 登录 API 接口(可扩展) +├── stores/ +│ └── auth.ts ← 认证状态(调用 API) +├── views/ +│ └── LoginView.vue ← 登录页面(异步登录) +└── ... + +root/ +├── .env.development ← 开发环境配置 +├── .env.production ← 生产环境配置 +├── API_INTEGRATION_GUIDE.md ← API 集成详细指南 +├── BACKEND_API_EXAMPLE.md ← 后端实现参考 +└── ... +``` + +## 🔄 工作流程 + +### 1️⃣ 用户登录流程 + +``` +用户输入用户名和密码 + ↓ +点击"登录"按钮 + ↓ +LoginView.vue 调用 authStore.login(username, password) + ↓ +authStore 调用 loginApi({ username, password }) + ↓ +axios 发送 POST /api/auth/login 请求 + ↓ +axios 拦截器处理: + - 添加 Authorization 头 + - 处理错误响应 + ↓ +后端返回 JSON 响应 + ↓ +前端解析 response.data(自动由拦截器处理) + ↓ +保存 token 和 user 到 localStorage + ↓ +路由跳转到仪表板 +``` + +### 2️⃣ 后续请求流程 + +``` +任何组件需要调用 API + ↓ +导入 API 函数(如 getUsersApi) + ↓ +await getUsersApi() + ↓ +axios 请求拦截器自动添加 Authorization: Bearer + ↓ +后端验证 token + ↓ +后端返回数据 + ↓ +axios 响应拦截器处理(提取 data) + ↓ +组件接收数据 +``` + +## 💻 代码实现细节 + +### axios 实例 (`src/api/index.ts`) + +```typescript +import axios from 'axios' + +const instance = axios.create({ + baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api', + timeout: 10000 +}) + +// 请求拦截器 - 自动添加 token +instance.interceptors.request.use((config) => { + const token = localStorage.getItem('auth_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config +}) + +// 响应拦截器 - 处理错误和 token 过期 +instance.interceptors.response.use( + (response) => response.data, // ← 返回 response.data + (error) => { + if (error.response?.status === 401) { + // token 过期,清除并跳转 + localStorage.removeItem('auth_token') + window.location.href = '/login' + } + return Promise.reject(error) + } +) + +export default instance +``` + +**关键点:** +1. 所有请求自动添加 `Authorization: Bearer ` +2. 所有响应自动提取 `response.data` +3. 401 错误自动清除 token 并跳转 + +### API 接口定义 (`src/api/auth.ts`) + +```typescript +import http from './index' + +export interface LoginRequest { + username: string + password: string +} + +export interface LoginResponse { + code: number + message: string + data: { + token: string + user: User + } +} + +export const loginApi = (credentials: LoginRequest): Promise => { + return http.post('/auth/login', credentials) +} +``` + +**使用方式:** +```typescript +const response = await loginApi({ username: 'admin', password: '123456' }) +// response.code === 200 +// response.data.token +// response.data.user +``` + +### 认证 Store (`src/stores/auth.ts`) + +```typescript +export const useAuthStore = defineStore('auth', () => { + const login = async (username: string, password: string): Promise => { + try { + const response = await loginApi({ username, password }) + + if (response.code === 200 && response.data) { + token.value = response.data.token + user.value = response.data.user + localStorage.setItem('auth_token', token.value) + return true + } + } catch (error) { + console.error('登录失败:', error) + return false + } + } + + return { login, logout, user, token, isAuthenticated } +}) +``` + +### 登录页面 (`src/views/LoginView.vue`) + +```typescript +const handleLogin = async () => { + loading.value = true + try { + // 调用异步登录 + const success = await authStore.login(username.value, password.value) + if (success) { + ElMessage.success('登录成功') + router.push('/admin/dashboard') + } else { + ElMessage.error('登录失败') + } + } catch (error: any) { + ElMessage.error(error?.message || '登录出错') + } finally { + loading.value = false + } +} +``` + +## 🛠 后端 API 需要返回什么? + +### 登录接口 + +**端点:** `POST /api/auth/login` + +**请求:** +```json +{ + "username": "admin", + "password": "123456" +} +``` + +**响应格式(必须严格按照):** +```json +{ + "code": 200, + "message": "登录成功", + "data": { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "user": { + "id": "1", + "username": "admin", + "email": "admin@example.com", + "role": "admin" + } + } +} +``` + +**关键点:** +- `code` 必须是 200 表示成功 +- `data.token` 是 JWT token,后续请求在 `Authorization: Bearer ` 中使用 +- `data.user` 必须包含至少 `id, username, email, role` 字段 + +## 📝 添加更多 API 接口 + +### 例子:用户列表 API + +1. **在 `src/api/` 中创建 `users.ts`:** + +```typescript +import http from './index' + +export interface User { + id: number + username: string + email: string + role: string +} + +export interface UsersResponse { + code: number + message: string + data: { + items: User[] + total: number + } +} + +// 获取用户列表 +export const getUsersApi = ( + page: number = 1, + limit: number = 10 +): Promise => { + return http.get('/users', { + params: { page, limit } + }) +} + +// 创建用户 +export const createUserApi = (data: User): Promise => { + return http.post('/users', data) +} + +// 更新用户 +export const updateUserApi = (id: number, data: Partial): Promise => { + return http.put(`/users/${id}`, data) +} + +// 删除用户 +export const deleteUserApi = (id: number): Promise => { + return http.delete(`/users/${id}`) +} +``` + +2. **在组件中使用:** + +```typescript +import { getUsersApi } from '@/api/users' + +const users = ref([]) +const loading = ref(false) + +const fetchUsers = async () => { + loading.value = true + try { + const response = await getUsersApi(1, 10) + if (response.code === 200) { + users.value = response.data.items + } + } catch (error) { + ElMessage.error('获取用户列表失败') + } finally { + loading.value = false + } +} + +onMounted(() => { + fetchUsers() +}) +``` + +## 🌐 环境配置 + +### 开发环境 (`.env.development`) + +``` +VITE_API_URL=http://localhost:3000/api +``` + +### 生产环境 (`.env.production`) + +``` +VITE_API_URL=https://api.example.com/api +``` + +### 在代码中使用 + +```typescript +const baseURL = import.meta.env.VITE_API_URL +console.log(baseURL) // 自动根据环境选择 + +// 或直接在 axios 配置中 +baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api' +``` + +## 🧪 测试步骤 + +### 1. 启动后端服务 + +```bash +# Node.js Express 示例 +node server.js + +# 或 Python Flask 示例 +python app.py + +# 或你自己的后端服务 +``` + +### 2. 验证后端在运行 + +访问 `http://localhost:3000/api/auth/login` (应该看到 404 或错误,说明服务在运行) + +### 3. 使用 cURL 测试登录 + +```bash +curl -X POST http://localhost:3000/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"123456"}' +``` + +应该返回: +```json +{ + "code": 200, + "message": "登录成功", + "data": { + "token": "...", + "user": {...} + } +} +``` + +### 4. 启动前端 + +```bash +npm run dev +``` + +### 5. 在前端登录 + +- 访问 http://localhost:5173 +- 输入用户名和密码 +- 点击登录 +- 如果成功,应该跳转到仪表板 + +### 6. 检查浏览器开发者工具 + +- **Network 标签:** 查看请求和响应 +- **Application 标签:** 查看 localStorage 中的 `auth_token` +- **Console 标签:** 查看是否有错误 + +## 🔒 安全建议 + +1. **始终使用 HTTPS** (生产环境) +2. **密码必须加密存储** (使用 bcrypt 等) +3. **使用 JWT 令牌** 而不是 session +4. **设置合理的 token 过期时间** (如 7 天) +5. **实现 token 刷新机制** (可选但推荐) +6. **验证 CORS 配置** 只允许特定域名 +7. **记录所有登录尝试** 用于审计 +8. **使用速率限制** 防止暴力破解 + +## 常见问题 + +### Q: 如何在其他组件中获取当前用户? + +```typescript +import { useAuthStore } from '@/stores/auth' + +const authStore = useAuthStore() +console.log(authStore.user) // 当前用户 +console.log(authStore.token) // 当前 token +``` + +### Q: 如何处理 token 刷新? + +参考 `src/api/auth.ts` 中已定义的 `refreshTokenApi` 函数,在响应拦截器中实现 token 刷新逻辑。 + +### Q: 如何在请求中添加其他头信息? + +```typescript +// 在 src/api/index.ts 中修改请求拦截器 +instance.interceptors.request.use((config) => { + const token = localStorage.getItem('auth_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + // 添加其他头 + config.headers['X-Custom-Header'] = 'value' + return config +}) +``` + +### Q: 如何处理超时错误? + +```typescript +const login = async () => { + try { + await authStore.login(username, password) + } catch (error: any) { + if (error.code === 'ECONNABORTED') { + ElMessage.error('请求超时,请检查网络') + } else { + ElMessage.error(error.message) + } + } +} +``` + +### Q: 前后端如何联调? + +1. 后端启动在 http://localhost:3000 +2. 前端启动在 http://localhost:5173 +3. 确保 `.env.development` 中 `VITE_API_URL=http://localhost:3000/api` +4. 后端需要配置 CORS 允许 http://localhost:5173 访问 + +### Q: CORS 错误怎么解决? + +后端需要配置 CORS: + +**Express 示例:** +```javascript +const cors = require('cors') +app.use(cors({ + origin: 'http://localhost:5173', + credentials: true +})) +``` + +**Flask 示例:** +```python +from flask_cors import CORS +CORS(app, resources={r"/api/*": {"origins": "http://localhost:5173"}}) +``` + +## 📚 相关文档 + +- [API 集成详细指南](./API_INTEGRATION_GUIDE.md) - 完整的 API 使用说明 +- [后端 API 实现参考](./BACKEND_API_EXAMPLE.md) - Node.js/Python 后端示例代码 +- [项目总结](./PROJECT_SUMMARY.md) - 整个项目的概览 + +## 🎉 总结 + +你现在有: + +✅ 完整的 axios 配置(请求/响应拦截器) +✅ API 服务层架构 +✅ 登录示例(已与后端集成) +✅ 完整的类型定义 (TypeScript) +✅ 环境变量配置 +✅ 详细的文档和后端示例 + +**下一步:** +1. 启动你的后端服务,确保监听 3000 端口 +2. 实现后端的 `/api/auth/login` 接口,返回上述格式的 JSON +3. 运行 `npm run dev` 启动前端 +4. 测试登录功能 + +有任何问题,请参考文档或查看相关代码注释!🚀 diff --git a/Build_God_Admin_Frontend/Frontend/API_INTEGRATION_GUIDE.md b/Build_God_Admin_Frontend/Frontend/API_INTEGRATION_GUIDE.md new file mode 100644 index 0000000..7d5d685 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/API_INTEGRATION_GUIDE.md @@ -0,0 +1,399 @@ +# API 集成指南 + +## 概述 + +项目已完整配置 axios,包括请求拦截器、响应拦截器和 API 服务层。 + +## 文件结构 + +``` +src/api/ +├── index.ts # axios 实例配置(请求/响应拦截器) +└── auth.ts # 登录相关 API 接口定义 +``` + +## 核心概念 + +### 1. axios 实例配置 (`src/api/index.ts`) + +```typescript +// 创建 axios 实例 +const instance = axios.create({ + baseURL: 'http://localhost:3000/api', // 从环境变量读取 + timeout: 10000 +}) +``` + +**请求拦截器**:自动在请求头添加 Authorization token +```typescript +instance.interceptors.request.use((config) => { + const token = localStorage.getItem('auth_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config +}) +``` + +**响应拦截器**:统一处理错误和 token 过期 +```typescript +instance.interceptors.response.use( + (response) => response.data, // 成功时返回 data + (error) => { + if (error.response?.status === 401) { + // Token 过期,清除登录状态并跳转到登录页 + } + } +) +``` + +### 2. API 服务层 (`src/api/auth.ts`) + +定义所有 API 接口和类型: + +```typescript +// 定义请求/响应类型 +export interface LoginRequest { + username: string + password: string +} + +export interface LoginResponse { + code: number + message: string + data: { + token: string + user: User + } +} + +// 定义 API 接口 +export const loginApi = (credentials: LoginRequest): Promise => { + return http.post('/auth/login', credentials) +} +``` + +### 3. 状态管理 (`src/stores/auth.ts`) + +在 Pinia store 中调用 API: + +```typescript +const login = async (username: string, password: string): Promise => { + try { + const response = await loginApi({ username, password }) + + if (response.code === 200 && response.data) { + token.value = response.data.token + user.value = response.data.user + + // 保存到本地存储 + localStorage.setItem('auth_token', token.value) + localStorage.setItem('user', JSON.stringify(user.value)) + + return true + } + } catch (error) { + console.error('登录失败:', error) + return false + } +} +``` + +## 登录流程详解 + +### 前端请求流程 + +``` +1. 用户在登录页输入用户名和密码 + ↓ +2. 点击"登录"按钮调用 handleLogin() + ↓ +3. authStore.login(username, password) 被调用 + ↓ +4. loginApi({ username, password }) 发送 HTTP 请求 + ↓ +5. axios 拦截器处理(添加 headers 等) + ↓ +6. 发送 POST /auth/login 请求到后端 +``` + +### 后端需要返回的数据格式 + +```json +{ + "code": 200, + "message": "登录成功", + "data": { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "user": { + "id": "1", + "username": "admin", + "email": "admin@example.com", + "role": "admin" + } + } +} +``` + +**字段说明:** +- `code`: HTTP 状态码(200 表示成功) +- `message`: 返回消息 +- `data.token`: JWT token,用于后续请求认证 +- `data.user`: 用户信息对象 + - `id`: 用户 ID + - `username`: 用户名 + - `email`: 邮箱 + - `role`: 用户角色(admin 或 user) + +## 环境配置 + +### `.env.development` (开发环境) +``` +VITE_API_URL=http://localhost:3000/api +``` + +### `.env.production` (生产环境) +``` +VITE_API_URL=https://api.example.com/api +``` + +### 在代码中获取环境变量 + +```typescript +const baseURL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api' +``` + +## 使用示例 + +### 1. 调用登录 API + +```typescript +import { loginApi } from '@/api/auth' + +const response = await loginApi({ + username: 'admin', + password: '123456' +}) + +if (response.code === 200) { + console.log('登录成功,用户信息:', response.data.user) + console.log('Token:', response.data.token) +} +``` + +### 2. 在组件中使用 store + +```vue + +``` + +### 3. 创建新的 API 接口 + +在 `src/api/` 中创建新文件,如 `users.ts`: + +```typescript +import http from './index' + +export interface User { + id: number + username: string + email: string + role: string +} + +// 获取用户列表 +export const getUsersApi = (page: number, limit: number) => { + return http.get('/users', { + params: { page, limit } + }) +} + +// 创建用户 +export const createUserApi = (data: User) => { + return http.post('/users', data) +} + +// 更新用户 +export const updateUserApi = (id: number, data: Partial) => { + return http.put(`/users/${id}`, data) +} + +// 删除用户 +export const deleteUserApi = (id: number) => { + return http.delete(`/users/${id}`) +} +``` + +## 错误处理 + +### 全局错误处理 + +axios 响应拦截器已处理以下情况: + +| 状态码 | 处理方式 | +|-------|--------| +| 401 | Token 过期,清除登录状态,跳转到登录页 | +| 其他 4xx/5xx | 返回错误信息 | +| 网络错误 | 返回"网络错误"提示 | + +### 在组件中处理错误 + +```typescript +try { + const response = await loginApi({ username, password }) + // 处理成功 +} catch (error: any) { + ElMessage.error(error?.message || '登录失败') +} +``` + +## 请求示例 + +### POST 请求 (登录) + +```typescript +// 请求 +POST /api/auth/login +Content-Type: application/json +Authorization: Bearer + +{ + "username": "admin", + "password": "123456" +} + +// 响应 +{ + "code": 200, + "message": "登录成功", + "data": { + "token": "...", + "user": { ... } + } +} +``` + +### GET 请求 (获取列表) + +```typescript +// 请求 +GET /api/users?page=1&limit=10 +Authorization: Bearer + +// 响应 +{ + "code": 200, + "message": "成功", + "data": { + "items": [...], + "total": 100 + } +} +``` + +### PUT 请求 (更新) + +```typescript +// 请求 +PUT /api/users/1 +Content-Type: application/json +Authorization: Bearer + +{ + "username": "new_name", + "email": "new@email.com" +} + +// 响应 +{ + "code": 200, + "message": "更新成功", + "data": { ... } +} +``` + +## 常见问题 + +### Q: Token 过期怎么处理? + +A: 后端返回 401 时,拦截器会自动清除 token 并跳转到登录页。 + +### Q: 如何添加其他请求头? + +A: 修改 `src/api/index.ts` 中的拦截器: + +```typescript +instance.interceptors.request.use((config) => { + config.headers['Custom-Header'] = 'value' + return config +}) +``` + +### Q: 如何处理超时? + +A: 已在 axios 配置中设置 `timeout: 10000`(10秒)。修改 `src/api/index.ts` 中的 `timeout` 值。 + +### Q: 如何调用 API 时显示加载状态? + +A: 在组件中使用 `loading` ref: + +```typescript +const loading = ref(false) +loading.value = true +try { + const response = await loginApi(...) +} finally { + loading.value = false +} +``` + +## 集成后端步骤 + +1. **启动后端服务** + ```bash + # 后端服务应该运行在 http://localhost:3000 + ``` + +2. **更新 API 地址** (如果不同) + ``` + 修改 .env.development 中的 VITE_API_URL + ``` + +3. **确保后端返回正确格式** + ```json + { + "code": 200, + "message": "...", + "data": { ... } + } + ``` + +4. **测试登录流程** + - 访问 http://localhost:5173 + - 输入用户名和密码 + - 查看浏览器控制台的网络请求 + +5. **检查 Token 存储** + - 打开浏览器开发者工具 → Application → LocalStorage + - 应该看到 `auth_token` 和 `user` 字段 + +## 下一步 + +- [ ] 集成后端登录接口 +- [ ] 添加用户列表 API (`src/api/users.ts`) +- [ ] 添加产品 API (`src/api/products.ts`) +- [ ] 添加订单 API (`src/api/orders.ts`) +- [ ] 实现 token 刷新机制 +- [ ] 添加错误日志上报 diff --git a/Build_God_Admin_Frontend/Frontend/API_QUICK_REFERENCE.md b/Build_God_Admin_Frontend/Frontend/API_QUICK_REFERENCE.md new file mode 100644 index 0000000..dab5b79 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/API_QUICK_REFERENCE.md @@ -0,0 +1,306 @@ +# API 快速参考卡片 + +## 📋 一页纸速查表 + +### 项目文件对应关系 + +| 文件 | 作用 | 修改频率 | +|-----|------|--------| +| `src/api/index.ts` | axios 配置 + 拦截器 | 很少 | +| `src/api/auth.ts` | API 接口定义 | 经常 | +| `src/stores/auth.ts` | 认证状态管理 | 偶尔 | +| `src/views/LoginView.vue` | 登录页面 | 偶尔 | +| `.env.development` | 开发环境 API 地址 | 很少 | +| `.env.production` | 生产环境 API 地址 | 很少 | + +### 工作流程简图 + +``` +前端组件 + ↓ +import { loginApi } from '@/api/auth' + ↓ +await loginApi({ username, password }) + ↓ +axios instance (请求拦截器添加 token) + ↓ +POST /api/auth/login + ↓ +后端返回 JSON + ↓ +axios instance (响应拦截器提取 data) + ↓ +return response.data + ↓ +前端接收数据 +``` + +### 常用代码片段 + +**1. 登录** +```typescript +import { useAuthStore } from '@/stores/auth' + +const authStore = useAuthStore() +const success = await authStore.login('admin', '123456') +``` + +**2. 获取当前用户** +```typescript +const authStore = useAuthStore() +console.log(authStore.user) +console.log(authStore.token) +``` + +**3. 登出** +```typescript +const authStore = useAuthStore() +await authStore.logout() +``` + +**4. 创建新 API 接口** +```typescript +// src/api/users.ts +import http from './index' + +export const getUsersApi = () => { + return http.get('/users') +} + +// 组件中使用 +import { getUsersApi } from '@/api/users' +const response = await getUsersApi() +``` + +### 后端需要的东西 + +**登录接口** +``` +POST /api/auth/login + +请求: +{ + "username": "admin", + "password": "123456" +} + +响应: +{ + "code": 200, + "message": "登录成功", + "data": { + "token": "jwt_token_here", + "user": { + "id": "1", + "username": "admin", + "email": "admin@example.com", + "role": "admin" + } + } +} +``` + +**所有其他接口** +``` +Authorization: Bearer (自动添加) + +返回格式: +{ + "code": 200, + "message": "...", + "data": {...} +} +``` + +### 调试技巧 + +**1. 查看网络请求** +- 打开浏览器开发者工具 (F12) +- 点击 Network 标签 +- 登录看看 POST /api/auth/login 的请求和响应 + +**2. 查看存储的 token** +- 打开浏览器开发者工具 (F12) +- 点击 Application 标签 +- 找到 LocalStorage +- 查看 `auth_token` 和 `user` + +**3. 打印错误** +```typescript +try { + await loginApi(...) +} catch (error) { + console.log(error) // 查看具体错误 +} +``` + +### 环境变量配置 + +**开发环境** (`.env.development`) +``` +VITE_API_URL=http://localhost:3000/api +``` + +**生产环境** (`.env.production`) +``` +VITE_API_URL=https://api.yourdomain.com/api +``` + +**在代码中使用** +```typescript +console.log(import.meta.env.VITE_API_URL) +``` + +### 常见错误解决 + +| 错误 | 原因 | 解决 | +|-----|------|------| +| CORS error | 跨域问题 | 后端配置 CORS | +| 401 | token 过期 | 自动清除,跳转登录 | +| 404 | 接口不存在 | 检查后端路由 | +| 500 | 服务器错误 | 检查后端日志 | +| 网络错误 | 后端未启动 | 启动后端服务 | + +### 完整登录示例 + +```typescript + + + +``` + +### API 调用模板 + +```typescript +// 1. 创建接口文件 src/api/xxx.ts +import http from './index' + +export interface XXXRequest { + // 请求参数 +} + +export interface XXXResponse { + code: number + message: string + data: { + // 响应数据 + } +} + +export const xxxApi = (params: XXXRequest): Promise => { + return http.post('/xxx', params) +} + +// 2. 在组件中使用 +import { xxxApi } from '@/api/xxx' + +const response = await xxxApi({ /* 参数 */ }) +if (response.code === 200) { + // 成功处理 +} +``` + +### HTTP 方法速查 + +```typescript +// GET 请求 +http.get('/users') +http.get('/users?page=1&limit=10') +http.get('/users', { params: { page: 1 } }) + +// POST 请求 +http.post('/users', { name: 'John' }) + +// PUT 请求 +http.put('/users/1', { name: 'Jane' }) + +// DELETE 请求 +http.delete('/users/1') + +// PATCH 请求 +http.patch('/users/1', { name: 'Bob' }) +``` + +### token 在请求中的流程 + +``` +1. 用户登录 → token 保存到 localStorage +2. 请求拦截器检查 localStorage 中的 token +3. 如果有 token,添加到请求头:Authorization: Bearer +4. 后端验证 token +5. 后端返回数据或 401 错误 +6. 响应拦截器检查状态码 +7. 如果 401,清除 token 并跳转登录页 +``` + +### 测试后端接口的命令 + +```bash +# 测试登录 +curl -X POST http://localhost:3000/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"123456"}' + +# 获取响应中的 token +# 复制 token 值 + +# 测试其他接口(需要 token) +curl -X GET http://localhost:3000/api/users \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" +``` + +## 🚀 快速启动清单 + +- [ ] 后端启动在 http://localhost:3000 +- [ ] 后端实现 POST /api/auth/login 接口 +- [ ] 后端返回正确的 JSON 格式 +- [ ] 前端运行 `npm run dev` +- [ ] 前端 .env.development 中 VITE_API_URL 指向后端 +- [ ] 打开浏览器访问 http://localhost:5173 +- [ ] 输入用户名和密码登录 +- [ ] 查看浏览器 Network 标签验证请求 +- [ ] 检查 localStorage 中是否保存了 token +- [ ] 登录成功后跳转到仪表板 + +## 📞 获取帮助 + +1. 查看 `API_INTEGRATION_GUIDE.md` - 详细教程 +2. 查看 `BACKEND_API_EXAMPLE.md` - 后端代码示例 +3. 查看组件代码注释 +4. 打开浏览器开发者工具查看错误 +5. 检查后端日志 diff --git a/Build_God_Admin_Frontend/Frontend/BACKEND_API_EXAMPLE.md b/Build_God_Admin_Frontend/Frontend/BACKEND_API_EXAMPLE.md new file mode 100644 index 0000000..850db89 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/BACKEND_API_EXAMPLE.md @@ -0,0 +1,401 @@ +# 后端 API 实现参考 + +这是一个使用 Node.js + Express 的简单后端示例,展示如何实现登录接口。 + +## 快速开始 (Node.js + Express) + +### 1. 初始化项目 + +```bash +mkdir admin-backend +cd admin-backend +npm init -y +npm install express cors jsonwebtoken bcryptjs dotenv +npm install -D nodemon typescript ts-node +``` + +### 2. 创建 `server.js` + +```javascript +const express = require('express') +const cors = require('cors') +const jwt = require('jsonwebtoken') +require('dotenv').config() + +const app = express() +const PORT = process.env.PORT || 3000 +const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key' + +// 中间件 +app.use(cors()) +app.use(express.json()) + +// 模拟用户数据 +const users = [ + { + id: '1', + username: 'admin', + password: '123456', // 实际应该加密存储 + email: 'admin@example.com', + role: 'admin' + }, + { + id: '2', + username: 'user', + password: '123456', + email: 'user@example.com', + role: 'user' + } +] + +// 登录接口 +app.post('/api/auth/login', (req, res) => { + const { username, password } = req.body + + // 验证输入 + if (!username || !password) { + return res.status(400).json({ + code: 400, + message: '用户名和密码不能为空' + }) + } + + // 查找用户 + const user = users.find(u => u.username === username && u.password === password) + + if (!user) { + return res.status(401).json({ + code: 401, + message: '用户名或密码错误' + }) + } + + // 生成 JWT token + const token = jwt.sign( + { id: user.id, username: user.username, role: user.role }, + JWT_SECRET, + { expiresIn: '7d' } + ) + + // 返回成功响应 + res.json({ + code: 200, + message: '登录成功', + data: { + token, + user: { + id: user.id, + username: user.username, + email: user.email, + role: user.role + } + } + }) +}) + +// 登出接口 +app.post('/api/auth/logout', (req, res) => { + res.json({ + code: 200, + message: '登出成功' + }) +}) + +// 获取用户信息接口 (需要认证) +app.get('/api/auth/userinfo', authenticateToken, (req, res) => { + const user = users.find(u => u.id === req.user.id) + + if (!user) { + return res.status(404).json({ + code: 404, + message: '用户不存在' + }) + } + + res.json({ + code: 200, + message: '获取成功', + data: { + id: user.id, + username: user.username, + email: user.email, + role: user.role + } + }) +}) + +// Token 验证中间件 +function authenticateToken(req, res, next) { + const authHeader = req.headers['authorization'] + const token = authHeader && authHeader.split(' ')[1] // Bearer + + if (!token) { + return res.status(401).json({ + code: 401, + message: 'Token 缺失' + }) + } + + jwt.verify(token, JWT_SECRET, (err, user) => { + if (err) { + return res.status(401).json({ + code: 401, + message: 'Token 无效或已过期' + }) + } + req.user = user + next() + }) +} + +// 启动服务器 +app.listen(PORT, () => { + console.log(\`✅ 服务器运行在 http://localhost:\${PORT}\`) + console.log(\`📝 API 文档: http://localhost:\${PORT}/api/docs\`) +}) +``` + +### 3. 创建 `.env` + +```env +PORT=3000 +JWT_SECRET=your-super-secret-key-change-in-production +NODE_ENV=development +``` + +### 4. 运行服务器 + +```bash +node server.js +# 或使用 nodemon 自动重启 +npx nodemon server.js +``` + +## API 端点说明 + +### 登录接口 + +**请求** +``` +POST /api/auth/login +Content-Type: application/json + +{ + "username": "admin", + "password": "123456" +} +``` + +**成功响应 (200)** +```json +{ + "code": 200, + "message": "登录成功", + "data": { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "user": { + "id": "1", + "username": "admin", + "email": "admin@example.com", + "role": "admin" + } + } +} +``` + +**错误响应 (401)** +```json +{ + "code": 401, + "message": "用户名或密码错误" +} +``` + +### 获取用户信息接口 + +**请求** +``` +GET /api/auth/userinfo +Authorization: Bearer +``` + +**成功响应** +```json +{ + "code": 200, + "message": "获取成功", + "data": { + "id": "1", + "username": "admin", + "email": "admin@example.com", + "role": "admin" + } +} +``` + +## 使用 cURL 测试 + +```bash +# 登录 +curl -X POST http://localhost:3000/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"123456"}' + +# 获取用户信息 +curl -X GET http://localhost:3000/api/auth/userinfo \ + -H "Authorization: Bearer YOUR_TOKEN_HERE" +``` + +## 使用 Postman 测试 + +1. 打开 Postman +2. 创建 POST 请求到 `http://localhost:3000/api/auth/login` +3. 在 Body → raw → JSON 中输入: + ```json + { + "username": "admin", + "password": "123456" + } + ``` +4. 点击 Send +5. 复制响应中的 token + +## Python Flask 示例 + +```python +from flask import Flask, request, jsonify +from flask_cors import CORS +from functools import wraps +import jwt +from datetime import datetime, timedelta + +app = Flask(__name__) +CORS(app) +app.config['JSON_AS_ASCII'] = False +SECRET_KEY = 'your-secret-key' + +# 模拟用户数据 +USERS = [ + {'id': '1', 'username': 'admin', 'password': '123456', 'email': 'admin@example.com', 'role': 'admin'}, + {'id': '2', 'username': 'user', 'password': '123456', 'email': 'user@example.com', 'role': 'user'} +] + +def token_required(f): + @wraps(f) + def decorated(*args, **kwargs): + token = request.headers.get('Authorization', '').replace('Bearer ', '') + if not token: + return jsonify({'code': 401, 'message': 'Token 缺失'}), 401 + try: + data = jwt.decode(token, SECRET_KEY, algorithms=['HS256']) + request.user_id = data['id'] + except: + return jsonify({'code': 401, 'message': 'Token 无效'}), 401 + return f(*args, **kwargs) + return decorated + +@app.route('/api/auth/login', methods=['POST']) +def login(): + data = request.json + username = data.get('username') + password = data.get('password') + + if not username or not password: + return jsonify({'code': 400, 'message': '用户名和密码不能为空'}), 400 + + user = next((u for u in USERS if u['username'] == username and u['password'] == password), None) + + if not user: + return jsonify({'code': 401, 'message': '用户名或密码错误'}), 401 + + token = jwt.encode( + {'id': user['id'], 'username': user['username']}, + SECRET_KEY, + algorithm='HS256' + ) + + return jsonify({ + 'code': 200, + 'message': '登录成功', + 'data': { + 'token': token, + 'user': { + 'id': user['id'], + 'username': user['username'], + 'email': user['email'], + 'role': user['role'] + } + } + }) + +@app.route('/api/auth/userinfo', methods=['GET']) +@token_required +def get_userinfo(): + user = next((u for u in USERS if u['id'] == request.user_id), None) + if not user: + return jsonify({'code': 404, 'message': '用户不存在'}), 404 + + return jsonify({ + 'code': 200, + 'message': '获取成功', + 'data': { + 'id': user['id'], + 'username': user['username'], + 'email': user['email'], + 'role': user['role'] + } + }) + +if __name__ == '__main__': + app.run(debug=True, port=3000) +``` + +## Java Spring Boot 示例 + +```java +@RestController +@RequestMapping("/api/auth") +@CrossOrigin(origins = "*") +public class AuthController { + + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest request) { + // 验证用户 + User user = authenticateUser(request.getUsername(), request.getPassword()); + + if (user == null) { + return ResponseEntity.status(401).body(new ApiResponse(401, "用户名或密码错误")); + } + + // 生成 JWT token + String token = JwtUtils.generateToken(user); + + return ResponseEntity.ok(new ApiResponse(200, "登录成功", + new LoginResponse(token, user))); + } + + @GetMapping("/userinfo") + @PreAuthorize("isAuthenticated()") + public ResponseEntity getUserInfo() { + User user = getCurrentUser(); + return ResponseEntity.ok(new ApiResponse(200, "获取成功", user)); + } +} +``` + +## 注意事项 + +1. **密码加密**:在生产环境中,必须加密存储密码(使用 bcrypt 等) +2. **HTTPS**:生产环境必须使用 HTTPS +3. **CORS**:根据前端域名配置 CORS +4. **Token 有效期**:建议设置合理的过期时间(如 7 天) +5. **错误处理**:返回统一的错误格式 +6. **日志记录**:记录所有登录尝试 +7. **速率限制**:防止暴力破解 + +## 与前端集成 + +1. 启动后端服务:`node server.js` +2. 启动前端开发服务:`npm run dev` +3. 前端会自动从 `http://localhost:3000/api` 调用后端接口 +4. 在登录页输入 `admin / 123456` 进行测试 diff --git a/Build_God_Admin_Frontend/Frontend/PROJECT_SUMMARY.md b/Build_God_Admin_Frontend/Frontend/PROJECT_SUMMARY.md new file mode 100644 index 0000000..a7c25dd --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/PROJECT_SUMMARY.md @@ -0,0 +1,501 @@ +# 🎉 后台管理系统 - 项目完成总结 + +## ✨ 项目概述 + +一个现代化的 Vue 3 + TypeScript 后台管理系统框架,具有完整的登录认证、用户管理、产品管理、订单管理等功能模块,采用优雅的暗黑主题设计。 + +--- + +## 🎨 设计主题 + +### 颜色方案(黑白双色暗黑风格) +```css +主色调:#667eea (紫蓝色) - 用于强调和交互元素 +副色调:#764ba2 (深紫) - 用于渐变和悬停效果 + +背景色:#0a0e27 (深蓝黑) - 主要背景 +卡片色:#1f2937 (深灰) - 内容卡片背景 +边框色:#374151 (浅深灰) - 边框分割线 +文字色:#e5e7eb (浅灰) - 主文本 +辅文字:#9ca3af (中灰) - 辅助文本 + +成功:#22c55e (绿色) +警告:#f59e0b (橙色) +错误:#ef4444 (红色) +信息:#3b82f6 (蓝色) +``` + +### 设计风格 +- ✅ 极简现代设计 +- ✅ 深色护眼主题 +- ✅ 圆角柔和设计 +- ✅ 平滑过渡动画 +- ✅ 响应式布局 + +--- + +## 📁 完整的文件清单 + +### 核心文件 +``` +✅ src/main.ts - 应用入口,集成 Element Plus +✅ src/App.vue - 根组件,简化为路由容器 +✅ src/router/index.ts - 路由配置(含导航守卫) +✅ src/assets/main.css - 暗黑主题全局样式 +``` + +### 状态管理 +``` +✅ src/stores/auth.ts - 用户认证状态管理 + - 登录/登出功能 + - 用户信息管理 + - Token 管理 + - localStorage 持久化 +``` + +### 页面组件 +``` +✅ src/views/LoginView.vue - 登录页面 + - 用户名/密码输入 + - 表单验证 + - 记住登录状态 + +✅ src/views/AdminLayout.vue - 后台主布局 + - 侧边栏 + 内容区域布局 + - 顶部导航栏 + - 退出登录功能 + +✅ src/views/admin/DashboardView.vue - 仪表板 + - 数据统计卡片 + - 最近订单表格 + - 数据概览展示 + +✅ src/views/admin/UsersView.vue - 用户管理 + - 用户列表展示 + - 搜索功能 + - 新增用户 + - 编辑用户 + - 删除用户 + - 状态管理 + +✅ src/views/admin/ProductsView.vue - 产品管理 + - 产品列表管理 + - 搜索过滤 + - 新增产品 + - 编辑产品 + - 删除产品 + - 库存管理 + - 价格管理 + +✅ src/views/admin/OrdersView.vue - 订单管理 + - 订单列表展示 + - 高级过滤 + - 订单搜索 + - 新增订单 + - 编辑订单 + - 删除订单 + - 状态跟踪 + +✅ src/views/admin/SettingsView.vue - 系统设置 + - 基本设置 + - 安全设置 + - 备份配置 + - 文件类型管理 +``` + +### 可复用组件 +``` +✅ src/components/Header.vue - 顶部导航栏 + - 菜单切换按钮 + - 用户信息展示 + - 退出登录按钮 + +✅ src/components/Sidebar.vue - 侧边栏菜单 + - 可折叠菜单 + - 图标 + 文字标签 + - 当前路由高亮 + - 菜单项导航 +``` + +### 文档文件 +``` +✅ ADMIN_SYSTEM_README.md - 项目完整文档 +✅ QUICK_START.md - 快速开始指南 +✅ PROJECT_SUMMARY.md - 本文件 +``` + +--- + +## 🎯 功能清单 + +### 已实现的功能 + +#### 1. 认证系统 ✅ +- [x] 登录页面(用户名/密码) +- [x] 登录状态验证 +- [x] 自动登录(localStorage 记忆) +- [x] 登出功能 +- [x] 路由保护守卫 +- [x] 登录失败提示 + +#### 2. 仪表板 ✅ +- [x] 数据统计卡片(4 个指标) +- [x] 统计数据展示 +- [x] 最近订单表格 +- [x] 欢迎信息 + +#### 3. 用户管理(CRUD 完整示例)✅ +- [x] 用户列表展示 +- [x] 搜索功能(用户名/邮箱) +- [x] 新增用户对话框 +- [x] 编辑用户信息 +- [x] 删除用户 +- [x] 状态管理(启用/禁用) +- [x] 角色管理(管理员/普通用户) +- [x] 创建时间记录 + +#### 4. 产品管理(CRUD 完整示例)✅ +- [x] 产品列表展示 +- [x] 搜索功能(名称/SKU) +- [x] 新增产品 +- [x] 编辑产品信息 +- [x] 删除产品 +- [x] 库存管理 +- [x] 价格管理 +- [x] 分类管理 +- [x] 上架/下架状态 +- [x] 库存警告显示 + +#### 5. 订单管理(CRUD 完整示例)✅ +- [x] 订单列表展示 +- [x] 搜索功能(订单号/客户名) +- [x] 状态过滤 +- [x] 新增订单 +- [x] 编辑订单 +- [x] 删除订单 +- [x] 支付方式管理 +- [x] 订单状态追踪 +- [x] 金额管理 + +#### 6. 系统设置 ✅ +- [x] 基本设置表单 +- [x] 安全设置 +- [x] 备份配置 +- [x] 文件上传设置 +- [x] 文件类型管理 +- [x] 设置保存功能 + +#### 7. UI 组件 ✅ +- [x] 侧边栏菜单(可折叠) +- [x] 顶部导航栏 +- [x] 对话框表单 +- [x] 数据表格 +- [x] 搜索输入 +- [x] 状态标签 +- [x] 按钮组件 +- [x] 表单输入 + +#### 8. 交互功能 ✅ +- [x] 弹出确认对话框 +- [x] 提示消息提醒 +- [x] 加载状态显示 +- [x] 表单验证 +- [x] 回车键提交 +- [x] 菜单切换动画 + +#### 9. 路由系统 ✅ +- [x] 登录路由 +- [x] 后台管理路由 +- [x] 路由懒加载 +- [x] 导航守卫(认证检查) +- [x] 自动重定向 +- [x] 404 处理 + +--- + +## 🚀 技术架构 + +### 技术栈 +``` +Frontend: + - Vue 3.5.27 前端框架 + - TypeScript 5.9.3 类型安全 + - Vite 7.3.1 构建工具 + - Vue Router 4.6.4 路由管理 + - Pinia 3.0.4 状态管理 + - Element Plus 2.7.0 UI 组件库 + - Axios HTTP 请求 +``` + +### 架构特点 +``` +1. 组件化设计 + - 可复用组件模式 + - 父子组件通信 + - Emits 事件机制 + +2. 状态管理 + - Pinia 集中管理状态 + - localStorage 持久化 + - 单一数据源 + +3. 路由管理 + - 嵌套路由结构 + - 导航守卫保护 + - 懒加载优化 + +4. 样式系统 + - Scoped CSS + - CSS 变量(可扩展) + - 响应式设计 + +5. 类型安全 + - 完整的 TypeScript 支持 + - 接口定义 + - 类型检查 +``` + +--- + +## 📊 数据流 + +### 登录流程 +``` +用户输入 → 表单验证 → authStore.login() + → localStorage 保存 → 路由重定向 → 仪表板 +``` + +### 页面导航 +``` +点击菜单 → Router.push() → 导航守卫检查 + → 页面组件加载 → 内容展示 +``` + +### 数据操作 +``` +表单提交 → 数据验证 → ref 状态更新 + → UI 重新渲染 → 操作反馈 +``` + +--- + +## 🎨 样式系统 + +### 响应式设计 +```css +✅ 桌面端:完整布局 +✅ 平板端:侧边栏可收缩 +✅ 手机端:响应式卡片布局 +``` + +### 颜色管理 +```css +主题颜色统一使用: +- 主调:#667eea(可自定义) +- 背景:#0a0e27(可自定义) +- 卡片:#1f2937(可自定义) +- 文本:#e5e7eb(可自定义) +``` + +--- + +## 🔐 安全特性 + +### 已实现 +``` +✅ 路由认证守卫 +✅ 登录状态管理 +✅ 表单数据验证 +✅ 错误提示处理 +✅ Token 管理 +``` + +### 建议增强 +``` +⚠️ HTTPS 传输 +⚠️ API 签名验证 +⚠️ CSRF 令牌 +⚠️ XSS 防护 +⚠️ 权限细粒度控制 +``` + +--- + +## 📈 性能优化 + +### 已优化项 +``` +✅ 路由懒加载 +✅ 组件动态导入 +✅ 事件委托 +✅ 防抖处理 +``` + +### 可优化项 +``` +⚠️ 虚拟滚动(大列表) +⚠️ 分页加载 +⚠️ 图片懒加载 +⚠️ 缓存策略 +⚠️ 代码分割优化 +``` + +--- + +## 🎓 学习路径 + +### 初级开发者 +1. 理解 Vue 3 基础 +2. 学习组件通信 +3. 理解 Pinia 状态管理 +4. 学习 Vue Router 路由 + +### 中级开发者 +1. API 集成 +2. 表单验证和处理 +3. 错误处理 +4. 权限管理 + +### 高级开发者 +1. 性能优化 +2. 安全加固 +3. 自动化测试 +4. 部署流程 + +--- + +## 📋 使用说明 + +### 快速启动 +```bash +# 1. 安装依赖 +npm install + +# 2. 开发模式 +npm run dev + +# 3. 访问 +http://localhost:5173 + +# 4. 登录信息 +用户名: admin +密码: 123456 +``` + +### 生产构建 +```bash +# 构建 +npm run build + +# 预览 +npm run preview + +# 类型检查 +npm run type-check +``` + +--- + +## 🛠 后续开发计划 + +### Phase 1: 基础功能(当前 ✅) +- [x] 登录认证 +- [x] CRUD 模块 +- [x] 主题设计 + +### Phase 2: 功能扩展(推荐) +- [ ] API 后端集成 +- [ ] 权限管理系统 +- [ ] 审计日志 +- [ ] 批量操作 +- [ ] 导出功能 + +### Phase 3: 用户体验(优化) +- [ ] 深浅主题切换 +- [ ] 国际化多语言 +- [ ] 快捷键支持 +- [ ] 高级搜索 +- [ ] 自定义仪表板 + +### Phase 4: 系统优化(性能) +- [ ] 性能监控 +- [ ] 错误追踪 +- [ ] 用户行为分析 +- [ ] 缓存策略 +- [ ] CDN 配置 + +--- + +## 💡 开发建议 + +### 代码组织 +``` +建议创建 src/api 文件夹管理所有 API 调用 +建议创建 src/services 文件夹存放业务逻辑 +建议创建 src/types 文件夹存放类型定义 +建议创建 src/utils 文件夹存放工具函数 +``` + +### 命名规范 +``` +✅ 组件文件:PascalCase(如 UserForm.vue) +✅ 页面文件:PascalCase + View 后缀(如 UsersView.vue) +✅ Store 文件:camelCase + Store 后缀(如 authStore.ts) +✅ 类名:PascalCase +✅ 变量/函数:camelCase +✅ 常量:UPPER_CASE +``` + +### Git 管理 +```bash +# 建议分支结构 +main - 生产环境 +develop - 开发环境 +feature/* - 功能分支 +bugfix/* - 修复分支 +hotfix/* - 紧急修复 +``` + +--- + +## 🎉 项目成果总结 + +✅ **完整的后台管理系统框架** +- 5 个功能模块(仪表板、用户、产品、订单、设置) +- 2 个可复用组件(导航栏、侧边栏) +- 完整的 CRUD 示例 + +✅ **专业的暗黑主题设计** +- 黑白双色优雅设计 +- 响应式布局 +- 平滑动画效果 + +✅ **完善的路由和认证系统** +- 登录/登出功能 +- 路由保护守卫 +- localStorage 持久化 + +✅ **生产级代码质量** +- TypeScript 类型安全 +- 完整的错误处理 +- 用户友好的提示反馈 + +✅ **详细的文档和注释** +- 项目完整文档 +- 快速开始指南 +- 代码注释清晰 + +--- + +## 📞 联系方式 + +如有任何问题或建议,欢迎反馈。 + +--- + +**项目创建时间**: 2026-02-02 +**框架版本**: Vue 3.5.27 + TypeScript 5.9.3 +**主题**: 暗黑风格(黑白双色) +**状态**: ✅ 已完成且可直接使用 + +祝你开发愉快!🚀 diff --git a/Build_God_Admin_Frontend/Frontend/QUICK_START.md b/Build_God_Admin_Frontend/Frontend/QUICK_START.md new file mode 100644 index 0000000..ed5099a --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/QUICK_START.md @@ -0,0 +1,170 @@ +# 快速开始指南 + +## 系统已搭建完成 ✅ + +你的后台管理系统框架已经完全搭建好!以下是完成的内容: + +## 📦 已安装的依赖 + +- ✅ Vue 3 +- ✅ TypeScript +- ✅ Vue Router +- ✅ Pinia (状态管理) +- ✅ Element Plus (UI 库) +- ✅ Axios (HTTP 请求) +- ✅ Vite (构建工具) + +## 🏗️ 已创建的文件结构 + +### 状态管理 +- `src/stores/auth.ts` - 认证状态管理(用户登录/登出) + +### 页面组件 +- `src/views/LoginView.vue` - 登录页面 +- `src/views/AdminLayout.vue` - 后台主布局 +- `src/views/admin/DashboardView.vue` - 仪表板 +- `src/views/admin/UsersView.vue` - 用户管理(CRUD 示例) +- `src/views/admin/ProductsView.vue` - 产品管理(CRUD 示例) +- `src/views/admin/OrdersView.vue` - 订单管理(CRUD 示例) +- `src/views/admin/SettingsView.vue` - 系统设置 + +### 可复用组件 +- `src/components/Header.vue` - 顶部导航栏 +- `src/components/Sidebar.vue` - 侧边栏菜单 + +### 配置文件 +- `src/router/index.ts` - 路由配置(包含导航守卫) +- `src/main.ts` - 应用入口 +- `src/App.vue` - 根组件 +- `src/assets/main.css` - 暗黑主题样式 + +## 🎨 设计特点 + +### 暗黑主题配置 +- 主色调:紫蓝色 (#667eea) +- 背景:深蓝黑 (#0a0e27) +- 卡片:深灰 (#1f2937) +- 文本:浅灰 (#e5e7eb) + +### UI 特性 +- 侧边栏可折叠 +- 响应式设计 +- 平滑过渡动画 +- 深色主题优化 + +## 🚀 运行项目 + +### 开发环境 +```bash +npm run dev +``` +访问 http://localhost:5173 + +### 生产构建 +```bash +npm run build +``` + +### 类型检查 +```bash +npm run type-check +``` + +## 🔐 登录信息 + +- **用户名**: admin +- **密码**: 123456 + +## 📋 功能清单 + +✅ 登录/登出功能 +✅ 仪表板(数据统计) +✅ 用户管理(增删改查) +✅ 产品管理(增删改查) +✅ 订单管理(增删改查) +✅ 系统设置 +✅ 侧边栏导航 +✅ 顶部导航栏 +✅ 路由保护 +✅ 响应式布局 + +## 🎯 后续开发步骤 + +1. **后端 API 集成** + - 替换 mock 数据为真实 API 调用 + - 使用 axios 进行 HTTP 请求 + - 添加错误处理和加载状态 + +2. **权限管理** + - 根据用户角色显示/隐藏菜单项 + - 实现页面级权限控制 + - 添加操作级权限检查 + +3. **数据验证** + - 为所有表单添加输入验证 + - 实现客户端表单验证规则 + - 添加服务端验证提示 + +4. **功能扩展** + - 添加更多管理模块 + - 实现导出/导入功能 + - 添加批量操作功能 + - 实现高级过滤和搜索 + +5. **性能优化** + - 实现虚拟滚动 + - 添加分页功能 + - 优化图片加载 + - 实现懒加载 + +6. **用户体验** + - 添加更多交互反馈 + - 实现快捷键支持 + - 添加深色/浅色主题切换 + - 实现国际化多语言 + +## 📚 文件修改说明 + +### 关键文件 + +1. **src/router/index.ts** + - 完全重写了路由配置 + - 添加了导航守卫(认证检查) + - 配置了后台管理模块路由 + +2. **src/main.ts** + - 集成了 Element Plus + - 初始化认证状态 + +3. **src/App.vue** + - 简化为只展示路由视图 + +4. **src/assets/main.css** + - 更新为暗黑主题样式 + +## 💡 开发建议 + +1. 所有 CRUD 页面都有完整的搜索、编辑、删除功能示例 +2. 使用 Pinia store 管理全局状态 +3. 表单验证可以使用 Element Plus 的表单验证功能 +4. API 调用使用 axios 库,可创建 `src/api` 文件夹管理所有 API +5. 创建 `src/services` 文件夹存放业务逻辑 + +## 🔗 项目结构优化建议 + +``` +src/ +├── api/ # API 接口管理 +│ ├── users.ts +│ ├── products.ts +│ ├── orders.ts +│ └── index.ts +├── services/ # 业务逻辑服务 +├── types/ # TypeScript 类型定义 +├── utils/ # 工具函数 +├── hooks/ # 自定义 Hooks +├── directives/ # 自定义指令 +└── ... +``` + +祝你开发愉快!如有任何问题,随时联系我。 diff --git a/Build_God_Admin_Frontend/Frontend/README.md b/Build_God_Admin_Frontend/Frontend/README.md new file mode 100644 index 0000000..2bb42be --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/README.md @@ -0,0 +1,42 @@ +# Build_God_Admin_Frontend + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). + +## Recommended Browser Setup + +- Chromium-based browsers (Chrome, Edge, Brave, etc.): + - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) + - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) +- Firefox: + - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) + - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) + +## Type Support for `.vue` Imports in TS + +TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. + +## Customize configuration + +See [Vite Configuration Reference](https://vite.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Type-Check, Compile and Minify for Production + +```sh +npm run build +``` diff --git a/Build_God_Admin_Frontend/Frontend/README_API_SUMMARY.md b/Build_God_Admin_Frontend/Frontend/README_API_SUMMARY.md new file mode 100644 index 0000000..46b6ac3 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/README_API_SUMMARY.md @@ -0,0 +1,235 @@ +# 🎯 Axios API 集成 - 一句话总结 + +## 你问的问题 + +> 是不是少了axios相关的东西?调用后台接口,后台接口会传递json数据上来,我应该怎么做?你只用做一个登录示例就可以了。 + +## 答案 + +✅ **已为你完成!** 完整的 axios 配置和登录 API 示例已创建。 + +--- + +## 📂 新增文件 + +### API 层(前端代码) + +``` +src/api/ +├── index.ts ← axios 实例 + 拦截器 +│ • baseURL 自动添加 API 前缀 +│ • 请求拦截器自动添加 token +│ • 响应拦截器自动处理错误 +│ +└── auth.ts ← 登录 API + • loginApi(username, password) - 调用 POST /api/auth/login + • logoutApi() + • getUserInfoApi() + • 完整的 TypeScript 类型定义 +``` + +### 环境配置 + +``` +.env.development ← API 地址:http://localhost:3000/api +.env.production ← API 地址:https://api.example.com/api +.env.local ← 本地覆盖 +``` + +### 文档 + +``` +WHAT_TO_DO_NEXT.md ← 👈 你接下来该做什么(先读这个) +API_IMPLEMENTATION_COMPLETE.md ← 完整总结 +API_INTEGRATION_GUIDE.md ← 详细教程 +BACKEND_API_EXAMPLE.md ← 后端代码示例 +API_QUICK_REFERENCE.md ← 快速参考 +``` + +--- + +## 🔄 工作流程 + +### 简单版本 + +``` +用户登录 + ↓ +await authStore.login(username, password) + ↓ +loginApi(credentials) + ↓ +http.post('/auth/login', credentials) + ↓ +axios 拦截器处理 + ↓ +POST http://localhost:3000/api/auth/login + ↓ +后端返回 JSON + ↓ +axios 拦截器提取 response.data + ↓ +前端收到数据 + ↓ +保存 token 和 user 到 localStorage + ↓ +登录成功! +``` + +--- + +## 💻 后端返回的 JSON 格式 + +你的后端需要返回这样的格式: + +```json +{ + "code": 200, + "message": "登录成功", + "data": { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "user": { + "id": "1", + "username": "admin", + "email": "admin@example.com", + "role": "admin" + } + } +} +``` + +**关键点:** +- `code` 必须是 200 表示成功 +- `data.token` 是 JWT,后续请求会自动在 `Authorization: Bearer ` 中发送 +- `data.user` 包含用户信息 + +--- + +## 🚀 立即测试 + +### 选项 1:使用提供的后端示例 + +```bash +# 创建后端项目 +mkdir admin-backend && cd admin-backend +npm init -y && npm install express cors jsonwebtoken + +# 复制 BACKEND_API_EXAMPLE.md 中的代码到 server.js +# 然后运行 +node server.js + +# 在另一个终端启动前端 +npm run dev + +# 访问 http://localhost:5173 +# 输入 admin / 123456 登录 +``` + +### 选项 2:集成你自己的后端 + +1. 实现 `POST /api/auth/login` 接口 +2. 返回上述格式的 JSON +3. 配置 CORS +4. 启动后端 +5. 运行 `npm run dev` +6. 测试登录 + +--- + +## 📝 代码示例 + +### 在组件中使用(已更新) + +```typescript +import { useAuthStore } from '@/stores/auth' + +const authStore = useAuthStore() + +// 登录 +const success = await authStore.login('admin', '123456') +if (success) { + console.log('登录成功!') +} + +// 获取当前用户 +console.log(authStore.user) +console.log(authStore.token) + +// 登出 +await authStore.logout() +``` + +### 添加新的 API(参考登录示例) + +```typescript +// src/api/users.ts +import http from './index' + +export const getUsersApi = () => { + return http.get('/users') +} + +export const createUserApi = (data) => { + return http.post('/users', data) +} + +// 在组件中使用 +const response = await getUsersApi() +if (response.code === 200) { + console.log(response.data) +} +``` + +--- + +## ✅ 检查清单 + +### 前端(✅ 已完成) + +- [x] Axios 配置 +- [x] 拦截器设置 +- [x] API 服务层 +- [x] 登录示例 +- [x] TypeScript 类型 +- [x] 环境配置 + +### 你需要做(⏳ 待实施) + +- [ ] 实现后端登录接口 +- [ ] 配置 CORS +- [ ] 启动后端 +- [ ] 测试集成 + +--- + +## 📚 快速参考 + +| 需求 | 文件 | 说明 | +|------|------|------| +| 快速上手 | `WHAT_TO_DO_NEXT.md` | 下一步该做什么 | +| 详细教程 | `API_INTEGRATION_GUIDE.md` | 完整 API 使用指南 | +| 后端参考 | `BACKEND_API_EXAMPLE.md` | Node.js/Python 实现 | +| 代码参考 | `API_QUICK_REFERENCE.md` | 快速代码片段 | +| 原理说明 | `API_IMPLEMENTATION_COMPLETE.md` | 完整工作流程 | + +--- + +## 🎉 总结 + +``` +✅ 前端:完整的 API 层 + 登录示例 +⏳ 你的工作:实现后端 API + 配置 CORS + 测试 + +预计时间:15 分钟 +结果:功能完整的登录系统 +``` + +**现在就开始吧!** 🚀 + +--- + +**相关文档快速链接:** +- 👉 [接下来该做什么](./WHAT_TO_DO_NEXT.md) +- 📖 [详细集成指南](./API_INTEGRATION_GUIDE.md) +- 💻 [后端代码示例](./BACKEND_API_EXAMPLE.md) +- ⚡ [快速参考](./API_QUICK_REFERENCE.md) diff --git a/Build_God_Admin_Frontend/Frontend/WHAT_TO_DO_NEXT.md b/Build_God_Admin_Frontend/Frontend/WHAT_TO_DO_NEXT.md new file mode 100644 index 0000000..67d7d58 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/WHAT_TO_DO_NEXT.md @@ -0,0 +1,390 @@ +# 🎯 API 集成总结 - 你该做什么 + +## ✅ 已经为你完成的 + +### 1. 前端 API 架构 + +``` +src/api/ +├── index.ts - axios 实例 + 拦截器(请求时自动添加 token) +└── auth.ts - 登录相关 API 定义 +``` + +### 2. 更新的文件 + +``` +src/stores/auth.ts - 已更新为调用真实 API +src/views/LoginView.vue - 已更新为异步登录 +``` + +### 3. 环境配置 + +``` +.env.development - API 地址:http://localhost:3000/api +.env.production - API 地址:https://api.example.com/api (修改为你的) +.env.local - 本地覆盖配置 +``` + +### 4. 完整的文档 + +``` +API_IMPLEMENTATION_COMPLETE.md - 最完整的总结(推荐先读这个) +API_INTEGRATION_GUIDE.md - 详细 API 使用指南 +BACKEND_API_EXAMPLE.md - 后端代码示例(Node.js/Python) +API_QUICK_REFERENCE.md - 快速参考卡片 +``` + +## 🚀 你现在需要做什么 + +### 方案 A:使用我提供的后端示例 + +**第 1 步:创建后端项目** + +```bash +mkdir admin-backend +cd admin-backend +npm init -y +npm install express cors jsonwebtoken +``` + +**第 2 步:创建 `server.js`** + +参考 `BACKEND_API_EXAMPLE.md` 中的 Node.js 示例代码,复制到 `server.js` + +**第 3 步:启动后端** + +```bash +node server.js +``` + +你会看到: +``` +✅ 服务器运行在 http://localhost:3000 +``` + +**第 4 步:启动前端** + +在另一个终端: + +```bash +npm run dev +``` + +**第 5 步:测试** + +1. 访问 http://localhost:5173 +2. 输入 `admin / 123456` +3. 点击登录 +4. 应该看到"登录成功"并跳转到仪表板 ✨ + +--- + +### 方案 B:使用你自己的后端 + +**需要做什么:** + +1. **实现登录接口** + + 端点:`POST /api/auth/login` + + 请求: + ```json + { + "username": "admin", + "password": "123456" + } + ``` + + 响应: + ```json + { + "code": 200, + "message": "登录成功", + "data": { + "token": "jwt_token_here", + "user": { + "id": "1", + "username": "admin", + "email": "admin@example.com", + "role": "admin" + } + } + } + ``` + +2. **配置 CORS** + + 允许前端跨域请求: + ``` + Access-Control-Allow-Origin: http://localhost:5173 + Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS + Access-Control-Allow-Headers: Content-Type, Authorization + ``` + +3. **启动后端服务** + + 确保监听 `3000` 端口(或修改 `.env.development` 中的地址) + +4. **启动前端** + + ```bash + npm run dev + ``` + +--- + +## 🔧 常见问题 + +### Q: 前端提示"网络错误" + +**解决方案:** +1. 检查后端是否启动:`http://localhost:3000` +2. 检查 `.env.development` 中的 API 地址是否正确 +3. 查看浏览器开发者工具的 Network 标签查看请求 + +### Q: 前端提示"CORS error" + +**解决方案:** +- 后端必须配置 CORS,允许来自 `http://localhost:5173` 的请求 + +**Express 示例:** +```javascript +const cors = require('cors') +app.use(cors({ + origin: 'http://localhost:5173', + credentials: true +})) +``` + +### Q: 登录提示"401 未授权" + +**解决方案:** +- 检查后端返回的 `code` 是否为 200 +- 检查用户名和密码是否匹配 +- 查看后端日志 + +### Q: 登录后页面不跳转 + +**解决方案:** +1. 检查浏览器控制台是否有 JavaScript 错误 +2. 检查是否成功保存到 localStorage(打开开发者工具 → Application → LocalStorage) +3. 查看 Network 标签验证响应数据格式是否正确 + +### Q: 如何修改 API 地址? + +**开发环境:** +编辑 `.env.development`: +``` +VITE_API_URL=http://你的后端地址:3000/api +``` + +**生产环境:** +编辑 `.env.production`: +``` +VITE_API_URL=https://你的生产API地址/api +``` + +--- + +## 📚 后续开发 + +### 添加更多 API 接口 + +参考 `src/api/auth.ts`,在 `src/api/` 中创建新文件: + +```typescript +// src/api/users.ts +import http from './index' + +export const getUsersApi = () => { + return http.get('/users') +} + +export const createUserApi = (data) => { + return http.post('/users', data) +} + +export const updateUserApi = (id, data) => { + return http.put(`/users/${id}`, data) +} + +export const deleteUserApi = (id) => { + return http.delete(`/users/${id}`) +} +``` + +### 在组件中使用 + +```typescript +import { getUsersApi } from '@/api/users' + +const users = ref([]) +const loading = ref(false) + +const fetchUsers = async () => { + loading.value = true + try { + const response = await getUsersApi() + if (response.code === 200) { + users.value = response.data.items + } + } catch (error) { + ElMessage.error('获取失败') + } finally { + loading.value = false + } +} + +onMounted(() => { + fetchUsers() +}) +``` + +--- + +## 🧪 测试步骤 + +### 1. 测试后端接口(使用 curl) + +```bash +# 登录 +curl -X POST http://localhost:3000/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"123456"}' + +# 应该返回 +# { +# "code": 200, +# "message": "登录成功", +# "data": { +# "token": "...", +# "user": {...} +# } +# } +``` + +### 2. 测试前端集成 + +1. 启动后端:`node server.js` +2. 启动前端:`npm run dev` +3. 打开 http://localhost:5173 +4. 输入用户名和密码 +5. 点击登录 +6. 查看浏览器开发者工具: + - **Network** 标签:查看请求/响应 + - **Application** 标签:查看 localStorage 中的 token + - **Console** 标签:查看任何错误信息 + +--- + +## 📋 文件说明 + +| 文件 | 说明 | 何时修改 | +|-----|------|--------| +| `src/api/index.ts` | Axios 配置和拦截器 | 很少 | +| `src/api/auth.ts` | API 接口定义 | 添加新接口时 | +| `src/stores/auth.ts` | 认证状态管理 | 很少 | +| `src/views/LoginView.vue` | 登录页面 | 很少 | +| `.env.development` | 开发环境 API 地址 | 改变后端地址时 | +| `.env.production` | 生产环境 API 地址 | 部署到生产前 | + +--- + +## 🎓 学习资源 + +1. **快速参考** - 看 `API_QUICK_REFERENCE.md` +2. **详细指南** - 看 `API_INTEGRATION_GUIDE.md` +3. **后端示例** - 看 `BACKEND_API_EXAMPLE.md` +4. **完整说明** - 看 `API_IMPLEMENTATION_COMPLETE.md` + +--- + +## ✅ 完成清单 + +### 前端部分(✅ 已完成) + +- [x] Axios 配置 +- [x] 请求拦截器 +- [x] 响应拦截器 +- [x] API 服务层 +- [x] 认证 Store +- [x] 登录页面 +- [x] 环境配置 +- [x] 文档 +- [x] 类型定义 + +### 你需要完成的(⏳ 待实施) + +- [ ] 后端登录接口 +- [ ] 后端 CORS 配置 +- [ ] 后端数据库存储 +- [ ] 其他业务接口 + +--- + +## 🚀 快速开始 + +### 最快的方式(使用示例后端) + +```bash +# 终端 1 - 启动后端 +cd admin-backend +node server.js + +# 终端 2 - 启动前端 +npm run dev + +# 打开浏览器访问 http://localhost:5173 +# 输入 admin / 123456 登录 +``` + +--- + +## 💡 核心概念 + +### 请求流程 +``` +前端 → 添加 token → 发送 HTTP 请求 → 后端 ← 验证 token ← 返回数据 +``` + +### 响应格式 +```json +{ + "code": 200, // HTTP 状态码 + "message": "...", // 消息 + "data": {...} // 实际数据 +} +``` + +### Token 管理 +``` +登录时保存 → 每个请求自动添加 → 验证失败自动清除 → 跳转登录 +``` + +--- + +## 📞 如果遇到问题 + +1. 查看浏览器控制台错误信息 +2. 查看后端日志 +3. 使用 curl 测试后端接口 +4. 查看相关文档 +5. 检查文件名和 API 路径是否正确 + +--- + +## 🎉 总结 + +你现在有: + +✅ 完整的前端 API 架构(可直接使用) +✅ 登录示例代码(参考如何集成) +✅ 后端代码示例(参考如何实现) +✅ 详细的文档(学习和参考) + +**只需 3 步即可让系统运行:** +1. 实现后端登录接口 +2. 启动后端和前端 +3. 在登录页测试 + +准备好了吗?让我们开始吧!🚀 diff --git a/Build_God_Admin_Frontend/Frontend/env.d.ts b/Build_God_Admin_Frontend/Frontend/env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/Build_God_Admin_Frontend/Frontend/index.html b/Build_God_Admin_Frontend/Frontend/index.html new file mode 100644 index 0000000..e25dc5e --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/Build_God_Admin_Frontend/Frontend/package-lock.json b/Build_God_Admin_Frontend/Frontend/package-lock.json new file mode 100644 index 0000000..c880063 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/package-lock.json @@ -0,0 +1,3561 @@ +{ + "name": "build-god-admin-frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "build-god-admin-frontend", + "version": "0.0.0", + "dependencies": { + "axios": "^1.13.4", + "element-plus": "^2.7.0", + "jwt-decode": "^4.0.0", + "pinia": "^3.0.4", + "vue": "^3.5.27", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.4", + "@types/node": "^24.10.9", + "@vitejs/plugin-vue": "^6.0.3", + "@vue/tsconfig": "^0.8.1", + "npm-run-all2": "^8.0.4", + "typescript": "~5.9.3", + "vite": "^7.3.1", + "vite-plugin-vue-devtools": "^8.0.5", + "vue-tsc": "^3.2.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.0.tgz", + "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", + "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.28.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-proposal-decorators": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz", + "integrity": "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-decorators": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-decorators": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz", + "integrity": "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.4", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.5", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.4", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmmirror.com/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tsconfig/node24": { + "version": "24.0.4", + "resolved": "https://registry.npmmirror.com/@tsconfig/node24/-/node24-24.0.4.tgz", + "integrity": "sha512-2A933l5P5oCbv6qSxHs7ckKwobs8BDAe9SJ/Xr2Hy+nDlwmLE1GhFh/g/vXGRZWgxBg9nX/5piDtHR9Dkw/XuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "24.10.9", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.10.9.tgz", + "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.3.tgz", + "integrity": "sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.53" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.27", + "resolved": "https://registry.npmmirror.com/@volar/language-core/-/language-core-2.4.27.tgz", + "integrity": "sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.27" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.27", + "resolved": "https://registry.npmmirror.com/@volar/source-map/-/source-map-2.4.27.tgz", + "integrity": "sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.27", + "resolved": "https://registry.npmmirror.com/@volar/typescript/-/typescript-2.4.27.tgz", + "integrity": "sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.27", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/babel-helper-vue-transform-on": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.5.0.tgz", + "integrity": "sha512-0dAYkerNhhHutHZ34JtTl2czVQHUNWv6xEbkdF5W+Yrv5pCWsqjeORdOgbtW2I9gWlt+wBmVn+ttqN9ZxR5tzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/babel-plugin-jsx": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.5.0.tgz", + "integrity": "sha512-mneBhw1oOqCd2247O0Yw/mRwC9jIGACAJUlawkmMBiNmL4dGA2eMzuNZVNqOUfYTa6vqmND4CtOPzmEEEqLKFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.2", + "@vue/babel-helper-vue-transform-on": "1.5.0", + "@vue/babel-plugin-resolve-type": "1.5.0", + "@vue/shared": "^3.5.18" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + } + } + }, + "node_modules/@vue/babel-plugin-resolve-type": { + "version": "1.5.0", + "resolved": "https://registry.npmmirror.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.5.0.tgz", + "integrity": "sha512-Wm/60o+53JwJODm4Knz47dxJnLDJ9FnKnGZJbUUf8nQRAtt6P+undLUAVU3Ha33LxOJe6IPoifRQ6F/0RrU31w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/parser": "^7.28.0", + "@vue/compiler-sfc": "^3.5.18" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.27", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.27.tgz", + "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.27", + "entities": "^7.0.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.27", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", + "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.27", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", + "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.27", + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.27", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", + "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-core": { + "version": "8.0.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-core/-/devtools-core-8.0.5.tgz", + "integrity": "sha512-dpCw8nl0GDBuiL9SaY0mtDxoGIEmU38w+TQiYEPOLhW03VDC0lfNMYXS/qhl4I0YlysGp04NLY4UNn6xgD0VIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^8.0.5", + "@vue/devtools-shared": "^8.0.5", + "mitt": "^3.0.1", + "nanoid": "^5.1.5", + "pathe": "^2.0.3", + "vite-hot-client": "^2.1.0" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-core/node_modules/@vue/devtools-kit": { + "version": "8.0.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-8.0.5.tgz", + "integrity": "sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.0.5", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^2.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-core/node_modules/@vue/devtools-shared": { + "version": "8.0.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-8.0.5.tgz", + "integrity": "sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/devtools-core/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@vue/devtools-core/node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.2.4.tgz", + "integrity": "sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.27", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.0.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.2" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.27", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.27.tgz", + "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.27", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.27.tgz", + "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.27", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", + "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.27", + "@vue/runtime-core": "3.5.27", + "@vue/shared": "3.5.27", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.27", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.27.tgz", + "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27" + }, + "peerDependencies": { + "vue": "3.5.27" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.27", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.27.tgz", + "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.8.1", + "resolved": "https://registry.npmmirror.com/@vue/tsconfig/-/tsconfig-0.8.1.tgz", + "integrity": "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "license": "MIT", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/alien-signals": { + "version": "3.1.2", + "resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-3.1.2.tgz", + "integrity": "sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.4", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.4.tgz", + "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001766", + "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/default-browser": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.283", + "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", + "dev": true, + "license": "ISC" + }, + "node_modules/element-plus": { + "version": "2.7.0", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.7.0.tgz", + "integrity": "sha512-WAiaFLavuWFxof9qwkC27jvkh9nRcNnB506g1vvJSiVaVqjCBWUFCIyJKeN11M1qcv2cS5VV5PfSLjTIkrw87A==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.3", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmmirror.com/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmmirror.com/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "8.0.4", + "resolved": "https://registry.npmmirror.com/npm-run-all2/-/npm-run-all2-8.0.4.tgz", + "integrity": "sha512-wdbB5My48XKp2ZfJUlhnLVihzeuA1hgBnqB2J9ahV77wLS+/YAJAlN8I+X3DIFIPZ3m5L7nplmlbhNiFDmXRDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "picomatch": "^4.0.2", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": "^20.5.0 || >=22.0.0", + "npm": ">= 10" + } + }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmmirror.com/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/read-package-json-fast": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", + "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unplugin-utils": { + "version": "0.3.1", + "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.3.1.tgz", + "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmmirror.com/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-dev-rpc": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/vite-dev-rpc/-/vite-dev-rpc-1.1.0.tgz", + "integrity": "sha512-pKXZlgoXGoE8sEKiKJSng4hI1sQ4wi5YT24FCrwrLt6opmkjlqPPVmiPWWJn8M8byMxRGzp1CrFuqQs4M/Z39A==", + "dev": true, + "license": "MIT", + "dependencies": { + "birpc": "^2.4.0", + "vite-hot-client": "^2.1.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.9.0 || ^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.1 || ^7.0.0-0" + } + }, + "node_modules/vite-hot-client": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/vite-hot-client/-/vite-hot-client-2.1.0.tgz", + "integrity": "sha512-7SpgZmU7R+dDnSmvXE1mfDtnHLHQSisdySVR7lO8ceAXvM0otZeuQQ6C8LrS5d/aYyP/QZ0hI0L+dIPrm4YlFQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-inspect": { + "version": "11.3.3", + "resolved": "https://registry.npmmirror.com/vite-plugin-inspect/-/vite-plugin-inspect-11.3.3.tgz", + "integrity": "sha512-u2eV5La99oHoYPHE6UvbwgEqKKOQGz86wMg40CCosP6q8BkB6e5xPneZfYagK4ojPJSj5anHCrnvC20DpwVdRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.1.0", + "debug": "^4.4.1", + "error-stack-parser-es": "^1.0.5", + "ohash": "^2.0.11", + "open": "^10.2.0", + "perfect-debounce": "^2.0.0", + "sirv": "^3.0.1", + "unplugin-utils": "^0.3.0", + "vite-dev-rpc": "^1.1.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-inspect/node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "8.0.5", + "resolved": "https://registry.npmmirror.com/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-8.0.5.tgz", + "integrity": "sha512-p619BlKFOqQXJ6uDWS1vUPQzuJOD6xJTfftj57JXBGoBD/yeQCowR7pnWcr/FEX4/HVkFbreI6w2uuGBmQOh6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^8.0.5", + "@vue/devtools-kit": "^8.0.5", + "@vue/devtools-shared": "^8.0.5", + "sirv": "^3.0.2", + "vite-plugin-inspect": "^11.3.3", + "vite-plugin-vue-inspector": "^5.3.2" + }, + "engines": { + "node": ">=v14.21.3" + }, + "peerDependencies": { + "vite": "^6.0.0 || ^7.0.0-0" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/@vue/devtools-kit": { + "version": "8.0.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-8.0.5.tgz", + "integrity": "sha512-q2VV6x1U3KJMTQPUlRMyWEKVbcHuxhqJdSr6Jtjz5uAThAIrfJ6WVZdGZm5cuO63ZnSUz0RCsVwiUUb0mDV0Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^8.0.5", + "birpc": "^2.6.1", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^2.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/@vue/devtools-shared": { + "version": "8.0.5", + "resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-8.0.5.tgz", + "integrity": "sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/vite-plugin-vue-devtools/node_modules/perfect-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-2.1.0.tgz", + "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite-plugin-vue-inspector": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.2.tgz", + "integrity": "sha512-YvEKooQcSiBTAs0DoYLfefNja9bLgkFM7NI2b07bE2SruuvX0MEa9cMaxjKVMkeCp5Nz9FRIdcN1rOdFVBeL6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0" + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.27", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.27.tgz", + "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-sfc": "3.5.27", + "@vue/runtime-dom": "3.5.27", + "@vue/server-renderer": "3.5.27", + "@vue/shared": "3.5.27" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.2.4.tgz", + "integrity": "sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.27", + "@vue/language-core": "3.2.4" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/Build_God_Admin_Frontend/Frontend/package.json b/Build_God_Admin_Frontend/Frontend/package.json new file mode 100644 index 0000000..2f82a44 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/package.json @@ -0,0 +1,35 @@ +{ + "name": "build-god-admin-frontend", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "run-p type-check \"build-only {@}\" --", + "preview": "vite preview", + "build-only": "vite build", + "type-check": "vue-tsc --build" + }, + "dependencies": { + "axios": "^1.13.4", + "element-plus": "^2.7.0", + "jwt-decode": "^4.0.0", + "pinia": "^3.0.4", + "vue": "^3.5.27", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "@tsconfig/node24": "^24.0.4", + "@types/node": "^24.10.9", + "@vitejs/plugin-vue": "^6.0.3", + "@vue/tsconfig": "^0.8.1", + "npm-run-all2": "^8.0.4", + "typescript": "~5.9.3", + "vite": "^7.3.1", + "vite-plugin-vue-devtools": "^8.0.5", + "vue-tsc": "^3.2.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } +} diff --git a/Build_God_Admin_Frontend/Frontend/public/favicon.ico b/Build_God_Admin_Frontend/Frontend/public/favicon.ico new file mode 100644 index 0000000..df36fcf Binary files /dev/null and b/Build_God_Admin_Frontend/Frontend/public/favicon.ico differ diff --git a/Build_God_Admin_Frontend/Frontend/src/App.vue b/Build_God_Admin_Frontend/Frontend/src/App.vue new file mode 100644 index 0000000..23de264 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/App.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/api/auth.ts b/Build_God_Admin_Frontend/Frontend/src/api/auth.ts new file mode 100644 index 0000000..bbe5c2e --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/api/auth.ts @@ -0,0 +1,70 @@ +import http from './index' + +// 定义登录请求/响应的类型 +export interface LoginRequest { + name: string + password: string +} + +export interface User { + id: string + name: string + email: string + role: 'admin' | 'user' +} + +export interface LoginResponse { + token: string +} + +// 定义 API 响应的通用格式 +export interface ApiResponse { + code: number + message: string + data?: T +} + +/** + * 只有admin用户能登录,账号密码存在api里得固定值 + * @param credentials - 登录凭证 { username, password } + * @returns Promise + * + * 示例请求: + * POST /api/god/account/login/admin + * { + * "name": "admin", + * "password": "123456" + * } + * + * 示例响应: + * { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + * } + */ +export const loginApi = (credentials: LoginRequest): Promise => { + return http.post('account/login/admin', credentials) +} + +/** + * 用户登出 + * @returns Promise + */ +export const logoutApi = (): Promise> => { + return http.post('/auth/logout', {}) +} + +/** + * 获取当前用户信息 + * @returns Promise> + */ +export const getUserInfoApi = (): Promise> => { + return http.get('/auth/userinfo') +} + +/** + * 刷新 token + * @returns Promise + */ +export const refreshTokenApi = (): Promise => { + return http.post('/auth/refresh', {}) +} diff --git a/Build_God_Admin_Frontend/Frontend/src/api/bag.ts b/Build_God_Admin_Frontend/Frontend/src/api/bag.ts new file mode 100644 index 0000000..165fc10 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/api/bag.ts @@ -0,0 +1,89 @@ +import http, { type EnumInfoDto } from './index' + +export interface Bag { + id: number + name: string + rarity: number + capacity: number + description: string | null +} + +export interface CharacterBag { + id: number + characterId: number + bagId: number + bagName: string | null + bagCapacity: number +} + +export interface BagItem { + id: number + characterBagId: number + itemType: number + itemId: number + quantity: number + itemName: string | null + itemRarity: number | null +} + +export interface AddBagItemDto { + itemType: number + itemId: number + quantity: number +} + +export interface AssignBagDto { + characterId: number + bagId: number +} + +// Bag配置管理 +export const GetAllBags = (): Promise => { + return http.get('bag/all') +} + +export const GetBagById = (id: number): Promise => { + return http.get(`bag/${id}`) +} + +export const CreateBag = (data: Bag): Promise => { + return http.post('bag', data) +} + +export const UpdateBag = (id: number, data: Bag): Promise => { + return http.put(`bag/${id}`, data) +} + +export const DeleteBag = (id: number): Promise => { + return http.delete(`bag/${id}`) +} + +// 角色背包管理 +export const GetCharacterBag = (characterId: number): Promise => { + return http.get(`bag/character/${characterId}`) +} + +export const AssignBagToCharacter = (data: AssignBagDto): Promise => { + return http.post('bag/assign', data) +} + +// 背包物品管理 +export const GetBagItems = (characterBagId: number): Promise => { + return http.get(`bag/${characterBagId}/items`) +} + +export const AddItemToBag = (characterBagId: number, data: AddBagItemDto): Promise => { + return http.post(`bag/${characterBagId}/items`, data) +} + +export const RemoveItemFromBag = (characterBagId: number, itemId: number): Promise => { + return http.delete(`bag/${characterBagId}/items/${itemId}`) +} + +export const GetBagRarities = (): Promise => { + return http.get('bag/rarities') +} + +export const GetBagItemTypes = (): Promise => { + return http.get('bag/item-types') +} diff --git a/Build_God_Admin_Frontend/Frontend/src/api/character.ts b/Build_God_Admin_Frontend/Frontend/src/api/character.ts new file mode 100644 index 0000000..9ebad78 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/api/character.ts @@ -0,0 +1,31 @@ +import http from './index' + +export interface Character { + id: number + name: string + accountId: number + currentExp: number + levelId: number + money: number + maxHP: number + currentHP: number + attack: number + breakthroughRate: number + spiritId: number | null + spiritFieldId: number | null + lastLogin: string + trainingOn: string | null + isLocked: boolean +} + +export const GetAllCharacters = (): Promise => { + return http.get('character/all') +} + +export const GetCharacterById = (id: number): Promise => { + return http.get(`character/${id}`) +} + +export const GetCharacterByAccountId = (accountId: number): Promise => { + return http.get(`character/accountId/${accountId}`) +} diff --git a/Build_God_Admin_Frontend/Frontend/src/api/equipment.ts b/Build_God_Admin_Frontend/Frontend/src/api/equipment.ts new file mode 100644 index 0000000..5c3712c --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/api/equipment.ts @@ -0,0 +1,114 @@ +import http, { type EnumInfoDto } from "../api/index"; + +export interface EquipmentTemplate { + id: number; + name: string; + description: string; + type: number; + rarity: number; + requirdLevelId: number; + setId: number | null; + money: number; + attributePool: string; + randomAttrCount: number; + maxEnhanceLevel: number; +} + +export interface EquipmentAttribute { + type: string; + value: number; +} + +export interface EquipmentInstance { + id: number; + characterBagId: number; + equipmentTemplateId: number; + name: string; + type: number; + rarity: number; + attributes: string; + enhanceLevel: number; + enhanceBonusPercent: number; + requirdLevelId: number; + setId: number | null; +} + +export interface EnhanceConfig { + id: number; + level: number; + reqItemId: number; + reqItemCount: number; + successRate: number; + bonusPercent: number; +} + +export interface EquipmentAttributePool { + type: number; + min: number; + max: number; + weight: number; +} + +//获取所有的equipment +export interface PagedResult { + items: T[]; + totalCount: number; +} + +interface SearchEquipmentDto { + pageNumber: number | undefined; + pageSize: number | undefined; + equipmentType: number | undefined; +} + +export const GetEquipmentTemplateList = ( + eqType?: number, + pageNumber?: number, + pageSize?: number, +): Promise | EquipmentTemplate[]> => { + var dto: SearchEquipmentDto = { + pageNumber: pageNumber, + pageSize: pageSize, + equipmentType: eqType, + }; + return http.post("equipment/all", dto); +}; + +//获取装备类型 +export const GetEquipmentTypes = (): Promise => { + return http.get("equipment/types"); +}; + +//获取装备稀有度 +export const GetEquipmentRarities = (): Promise => { + return http.get("equipment/rarities"); +}; + +//获取装备属性类型 +export const GetEquipmentAttributeTypes = (): Promise => { + return http.get("equipment/attribute-types"); +}; + +//添加装备模板 +export const AddEquipmentTemplate = (data: EquipmentTemplate): Promise => { + return http.post("equipment", data); +}; + +//修改装备模板 +export const UpdateEquipmentTemplate = (data: EquipmentTemplate): Promise => { + return http.put("equipment", data); +}; + +//删除装备模板 +export const DeleteEquipmentTemplate = (id: number): Promise => { + return http.delete(`equipment/${id}`); +}; + +//获取装备实例列表 +export const GetEquipmentInstanceList = ( + characterBagId?: number, + pageNumber?: number, + pageSize?: number, +): Promise | EquipmentInstance[]> => { + return http.get("equipment/instance/all", { params: { characterBagId, pageNumber, pageSize } }); +}; diff --git a/Build_God_Admin_Frontend/Frontend/src/api/index.ts b/Build_God_Admin_Frontend/Frontend/src/api/index.ts new file mode 100644 index 0000000..af74d4a --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/api/index.ts @@ -0,0 +1,62 @@ +import axios from 'axios' + +export interface EnumInfoDto{ + id: number; + name: string; + displayName: string | null; + description: string | null; +} + +// 创建 axios 实例 +const instance = axios.create({ + // 根据环境设置 API 地址 + baseURL: import.meta.env.VITE_API_URL || 'http://localhost:5091/api/god/', + timeout: 10000, + headers: { + 'Content-Type': 'application/json' + } +}) + +// 请求拦截器 - 添加 token +instance.interceptors.request.use( + (config) => { + const token = sessionStorage.getItem('auth_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => { + return Promise.reject(error) + } +) + +// 响应拦截器 - 处理错误 +instance.interceptors.response.use( + (response) => { + // 如果后端统一返回格式,这里可以解析 + return response.data + }, + (error) => { + if (error.response) { + // 服务器返回错误状态码 + const { status, data } = error.response + + if (status === 401) { + // 令牌过期,清除登录状态 + sessionStorage.removeItem('auth_token') + sessionStorage.removeItem('user') + window.location.href = '/login' + } + return Promise.reject(data || error.message) + } else if (error.request) { + // 请求已发送但没有收到响应 + return Promise.reject('网络错误,请检查连接') + } else { + // 请求配置有问题 + return Promise.reject(error.message) + } + } +) + +export default instance diff --git a/Build_God_Admin_Frontend/Frontend/src/api/level.ts b/Build_God_Admin_Frontend/Frontend/src/api/level.ts new file mode 100644 index 0000000..bac7d75 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/api/level.ts @@ -0,0 +1,32 @@ +import http from "../api/index"; + +export interface Level{ + id: number; + name: string; + levelId: number | null; + currentLevelMinExp: number | null; + nextLevelId: number | null; + baseBreakthroughRate: number; + failIncrement: number; + description: string; +} + +//获取所有的level +export const GetLevelList = (): Promise => { + return http.get("level/all"); +}; + +//添加等级 +export const AddLevel = (data: Level): Promise => { + return http.post("level", data); +} + +//修改等级 +export const UpdateLevel = (data: Level): Promise => { + return http.put("level", data); +} + +//删除等级 +export const DeleteLevel = (id: number): Promise => { + return http.delete(`level/${id}`); +} \ No newline at end of file diff --git a/Build_God_Admin_Frontend/Frontend/src/api/mission.ts b/Build_God_Admin_Frontend/Frontend/src/api/mission.ts new file mode 100644 index 0000000..1f35e33 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/api/mission.ts @@ -0,0 +1,82 @@ +import http, { type EnumInfoDto } from "../api/index"; + +export interface Mission { + id: number; + name: string; + type?: number; + title: string; + description: string; + requiredLevelId?: number; + repeatable: boolean; + isAvailable: boolean; + spendTimeMinutes?: number; + obtainPercentage?: number; + preMissionId?: number; + difficulty?: number; + rewards?: MissionReward[]; +} + +export interface MissionReward { + id: number; + missionId?: number; + rewardType?: number; + itemId?: number; + itemName: string; + count?: number; +} + +export interface PagedResult { + items: T[]; + totalCount: number; +} + +interface SearchMissionDto { + pageNumber: number | undefined; + pageSize: number | undefined; + missionType: number | undefined; +} + +export const GetMissionList = ( + type?: number, + pageNumber?: number, + pageSize?: number, +): Promise | Mission[]> => { + var dto: SearchMissionDto = { + pageNumber: pageNumber, + pageSize: pageSize, + missionType: type, + }; + return http.post("mission/all", dto); +}; + +export const GetMissionTypes = (): Promise => { + return http.get("mission/types"); +}; + +export const GetMissionDifficultys = (): Promise => { + return http.get("mission/difficulties"); +}; + +export const AddMission = (data: Mission): Promise => { + return http.post("mission", data); +}; + +export const UpdateMission = (data: Mission): Promise => { + return http.put("mission", data); +}; + +export const DeleteMission = (id: number): Promise => { + return http.delete(`mission/${id}`); +}; + +export const AddReward = (data: MissionReward): Promise => { + return http.post("mission/reward/add", data); +}; + +export const UpdateReward = (data: MissionReward): Promise => { + return http.post("mission/reward/update", data); +}; + +export const GetRewardTypes = (): Promise => { + return http.get("mission/reward/types"); +}; diff --git a/Build_God_Admin_Frontend/Frontend/src/api/missionProgress.ts b/Build_God_Admin_Frontend/Frontend/src/api/missionProgress.ts new file mode 100644 index 0000000..98552f7 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/api/missionProgress.ts @@ -0,0 +1,42 @@ +import http, { type EnumInfoDto } from './index' + +export interface MissionProgress { + id: number + missionId: number + targetType: number + targetItemId: number | null + targetItemName: string | null + targetCount: number + description: string | null +} + +export interface CharacterMissionProgress { + id: number + characterId: number + missionId: number + missionProgressId: number + currentCount: number + isCompleted: boolean + updatedOn: string +} + +// Mission Progress Config APIs +export const GetMissionProgresses = (missionId: number): Promise => { + return http.get(`mission/${missionId}/progresses`) +} + +export const CreateMissionProgress = (missionId: number, data: MissionProgress): Promise => { + return http.post(`mission/${missionId}/progresses`, data) +} + +export const UpdateMissionProgress = (id: number, data: MissionProgress): Promise => { + return http.put(`mission/progresses/${id}`, data) +} + +export const DeleteMissionProgress = (id: number): Promise => { + return http.delete(`mission/progresses/${id}`) +} + +export const GetProgressTargetTypes = (): Promise => { + return http.get('mission/progress/target-types') +} diff --git a/Build_God_Admin_Frontend/Frontend/src/api/pill.ts b/Build_God_Admin_Frontend/Frontend/src/api/pill.ts new file mode 100644 index 0000000..db1d893 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/api/pill.ts @@ -0,0 +1,49 @@ +import http, { type EnumInfoDto } from "../api/index"; + +export interface Pill { + id: number; + name: string; + grade?: number; + type?: number; + rarity?: number; + money?: number; + description: string; + requirdLevelId?: number; + effectValue?: number; + duration?: number; +} + +//获取所有 +export const GetPillList = (): Promise => { + return http.get("pill/all"); +}; + +//添加丹药 +export const AddPill = (data: Pill): Promise => { + return http.post("pill", data); +} + +//修改丹药 +export const UpdatePill = (data: Pill): Promise => { + return http.put("pill", data); +} + +//删除丹药 +export const DeletePill = (id: number): Promise => { + return http.delete(`pill/${id}`); +} + +//获取丹药等级 +export const getPillGrades = (): Promise => { + return http.get('pill/grades'); +} + +//获取丹药类型 +export const getPillTypes = (): Promise => { + return http.get('pill/types'); +} + +//获取丹药稀有度 +export const getPillRarities = (): Promise => { + return http.get('pill/rarities'); +} diff --git a/Build_God_Admin_Frontend/Frontend/src/api/spirit.ts b/Build_God_Admin_Frontend/Frontend/src/api/spirit.ts new file mode 100644 index 0000000..3229ba4 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/api/spirit.ts @@ -0,0 +1,27 @@ +import http from "../api/index"; + +export interface Profession { + id: number; + name: string; + description: string; + attackRate: number; + defendRate: number; + healthRate: number; + criticalRate: number; +} + +export const GetProfessionList = (): Promise => { + return http.get("profession/all"); +}; + +export const AddProfession = (data: Profession): Promise => { + return http.post("profession", data); +} + +export const UpdateProfession = (data: Profession): Promise => { + return http.put("profession", data); +} + +export const DeleteProfession = (id: number): Promise => { + return http.delete(`profession/${id}`); +} diff --git a/Build_God_Admin_Frontend/Frontend/src/api/statistics.ts b/Build_God_Admin_Frontend/Frontend/src/api/statistics.ts new file mode 100644 index 0000000..3a0cf2d --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/api/statistics.ts @@ -0,0 +1,16 @@ +import http from './index' + +export interface StatisticsSummary { + equipmentCount: number + levelCount: number + professionCount: number + pillCount: number + missionCount: number + equipmentByRarity: Record + missionByDifficulty: Record + pillByGrade: Record +} + +export const GetStatisticsSummary = (): Promise => { + return http.get('statistics/summary') +} diff --git a/Build_God_Admin_Frontend/Frontend/src/assets/base.css b/Build_God_Admin_Frontend/Frontend/src/assets/base.css new file mode 100644 index 0000000..8816868 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/assets/base.css @@ -0,0 +1,86 @@ +/* color palette from */ +:root { + --vt-c-white: #ffffff; + --vt-c-white-soft: #f8f8f8; + --vt-c-white-mute: #f2f2f2; + + --vt-c-black: #181818; + --vt-c-black-soft: #222222; + --vt-c-black-mute: #282828; + + --vt-c-indigo: #2c3e50; + + --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); + --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); + --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); + --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); + + --vt-c-text-light-1: var(--vt-c-indigo); + --vt-c-text-light-2: rgba(60, 60, 60, 0.66); + --vt-c-text-dark-1: var(--vt-c-white); + --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); +} + +/* semantic color variables for this project */ +:root { + --color-background: var(--vt-c-white); + --color-background-soft: var(--vt-c-white-soft); + --color-background-mute: var(--vt-c-white-mute); + + --color-border: var(--vt-c-divider-light-2); + --color-border-hover: var(--vt-c-divider-light-1); + + --color-heading: var(--vt-c-text-light-1); + --color-text: var(--vt-c-text-light-1); + + --section-gap: 160px; +} + +@media (prefers-color-scheme: dark) { + :root { + --color-background: var(--vt-c-black); + --color-background-soft: var(--vt-c-black-soft); + --color-background-mute: var(--vt-c-black-mute); + + --color-border: var(--vt-c-divider-dark-2); + --color-border-hover: var(--vt-c-divider-dark-1); + + --color-heading: var(--vt-c-text-dark-1); + --color-text: var(--vt-c-text-dark-2); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + font-weight: normal; +} + +body { + min-height: 100vh; + color: var(--color-text); + background: var(--color-background); + transition: + color 0.5s, + background-color 0.5s; + line-height: 1.6; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Fira Sans', + 'Droid Sans', + 'Helvetica Neue', + sans-serif; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} diff --git a/Build_God_Admin_Frontend/Frontend/src/assets/logo.svg b/Build_God_Admin_Frontend/Frontend/src/assets/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/assets/logo.svg @@ -0,0 +1 @@ + diff --git a/Build_God_Admin_Frontend/Frontend/src/assets/main.css b/Build_God_Admin_Frontend/Frontend/src/assets/main.css new file mode 100644 index 0000000..3876722 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/assets/main.css @@ -0,0 +1,28 @@ +@import './base.css'; + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body { + background-color: #0a0e27; + color: #e5e7eb; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; +} + +html { + height: 100%; +} + +body { + min-height: 100%; +} + +#app { + width: 100%; + height: 100%; + background-color: #0a0e27; +} diff --git a/Build_God_Admin_Frontend/Frontend/src/components/Header.vue b/Build_God_Admin_Frontend/Frontend/src/components/Header.vue new file mode 100644 index 0000000..66f0354 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/components/Header.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/components/HelloWorld.vue b/Build_God_Admin_Frontend/Frontend/src/components/HelloWorld.vue new file mode 100644 index 0000000..d174cf8 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/components/HelloWorld.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/components/Sidebar.vue b/Build_God_Admin_Frontend/Frontend/src/components/Sidebar.vue new file mode 100644 index 0000000..1696171 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/components/Sidebar.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/components/TheWelcome.vue b/Build_God_Admin_Frontend/Frontend/src/components/TheWelcome.vue new file mode 100644 index 0000000..8b731d9 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/components/TheWelcome.vue @@ -0,0 +1,95 @@ + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/components/WelcomeItem.vue b/Build_God_Admin_Frontend/Frontend/src/components/WelcomeItem.vue new file mode 100644 index 0000000..6d7086a --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/components/WelcomeItem.vue @@ -0,0 +1,87 @@ + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/components/icons/IconCommunity.vue b/Build_God_Admin_Frontend/Frontend/src/components/icons/IconCommunity.vue new file mode 100644 index 0000000..2dc8b05 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/components/icons/IconCommunity.vue @@ -0,0 +1,7 @@ + diff --git a/Build_God_Admin_Frontend/Frontend/src/components/icons/IconDocumentation.vue b/Build_God_Admin_Frontend/Frontend/src/components/icons/IconDocumentation.vue new file mode 100644 index 0000000..6d4791c --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/components/icons/IconDocumentation.vue @@ -0,0 +1,7 @@ + diff --git a/Build_God_Admin_Frontend/Frontend/src/components/icons/IconEcosystem.vue b/Build_God_Admin_Frontend/Frontend/src/components/icons/IconEcosystem.vue new file mode 100644 index 0000000..c3a4f07 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/components/icons/IconEcosystem.vue @@ -0,0 +1,7 @@ + diff --git a/Build_God_Admin_Frontend/Frontend/src/components/icons/IconSupport.vue b/Build_God_Admin_Frontend/Frontend/src/components/icons/IconSupport.vue new file mode 100644 index 0000000..7452834 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/components/icons/IconSupport.vue @@ -0,0 +1,7 @@ + diff --git a/Build_God_Admin_Frontend/Frontend/src/components/icons/IconTooling.vue b/Build_God_Admin_Frontend/Frontend/src/components/icons/IconTooling.vue new file mode 100644 index 0000000..660598d --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/components/icons/IconTooling.vue @@ -0,0 +1,19 @@ + + diff --git a/Build_God_Admin_Frontend/Frontend/src/constants/theme.ts b/Build_God_Admin_Frontend/Frontend/src/constants/theme.ts new file mode 100644 index 0000000..0b05ee0 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/constants/theme.ts @@ -0,0 +1,41 @@ +export const ICONS = { + dashboard: '📊', + equipment: '⚔️', + level: '📈', + spirit: '✨', + pill: '💊', + bag:'🎒', + mission: '📜', + + reward: { + pill: '💊', + equipment: '⚔️', + experience: '⭐', + money: '💰', + bag:'👜', + default: '🎁' + } +} + +export const COLORS = { + equipment: '#a855f7', + level: '#f59e0b', + spirit: '#06b6d4', + pill: '#22c55e', + mission: '#3b82f6', + dashboard: '#8b5cf6', + + rarity: { + common: '#9ca3af', + rare: '#22c55e', + epic: '#3b82f6', + legendary: '#f59e0b' + }, + + difficulty: { + easy: '#22c55e', + normal: '#f59e0b', + hard: '#ef4444', + purgatory: '#dc2626' + } +} diff --git a/Build_God_Admin_Frontend/Frontend/src/main.ts b/Build_God_Admin_Frontend/Frontend/src/main.ts new file mode 100644 index 0000000..0e882bb --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/main.ts @@ -0,0 +1,27 @@ +import './assets/main.css' + +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +// Element Plus default styles +import 'element-plus/dist/index.css' +// Element Plus dark css variables (provides dark theme variables) +import 'element-plus/theme-chalk/dark/css-vars.css' + +import App from './App.vue' +import router from './router' +import { useAuthStore } from './stores/auth' + +const app = createApp(App) + +const pinia = createPinia() +app.use(pinia) + +// 初始化认证状态(在 router 安装之前) +const authStore = useAuthStore() +authStore.initAuth() + +app.use(router) +app.use(ElementPlus) + +app.mount('#app') diff --git a/Build_God_Admin_Frontend/Frontend/src/router/index.ts b/Build_God_Admin_Frontend/Frontend/src/router/index.ts new file mode 100644 index 0000000..f96b19a --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/router/index.ts @@ -0,0 +1,104 @@ +import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' +import { useAuthStore } from '../stores/auth' +import LoginView from '../views/LoginView.vue' +import AdminLayout from '../views/AdminLayout.vue' + +const routes: RouteRecordRaw[] = [ + { + path: '/login', + name: 'login', + component: LoginView, + meta: { requiresAuth: false } + }, + { + path: '/admin', + component: AdminLayout, + meta: { requiresAuth: true }, + children: [ + { + path: 'dashboard', + name: 'dashboard', + component: () => import('../views/admin/DashboardView.vue'), + meta: { title: '仪表板' } + }, + { + path: 'users', + name: 'users', + component: () => import('../views/admin/UsersView.vue'), + meta: { title: '用户管理' } + }, + { + path: 'equipments', + name: 'equipments', + component: () => import('../views/admin/EquipmentsView.vue'), + meta: { title: '装备管理' } + }, + { + path: 'levels', + name: 'levels', + component: () => import('../views/admin/LevelsView.vue'), + meta: { title: '境界管理' } + }, + { + path: 'professions', + name: 'professions', + component: () => import('../views/admin/SpiritsView.vue'), + meta: { title: '职业管理' } + }, + { + path: 'pills', + name: 'pills', + component: () => import('../views/admin/PillsView.vue'), + meta: { title: '丹药管理' } + }, + { + path: 'missions', + name: 'missions', + component: () => import('../views/admin/MissionView.vue'), + meta: { title: '任务管理' } + }, + { + path: 'bags', + name: 'bags', + component: () => import('../views/admin/BagsView.vue'), + meta: { title: '背包管理' } + }, + { + path: 'settings', + name: 'settings', + component: () => import('../views/admin/SettingsView.vue'), + meta: { title: '系统设置' } + } + ] + }, + { + path: '/', + redirect: '/admin/dashboard' + }, + { + path: '/:pathMatch(.*)*', + redirect: '/admin/dashboard' + } +] + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes +}) + +// 导航守卫 +router.beforeEach((to, from, next) => { + const authStore = useAuthStore() + const isAuthenticated = authStore.isAuthenticated + const requiresAuth = to.meta.requiresAuth !== false + + if (requiresAuth && !isAuthenticated) { + next('/login') + } else if (to.path === '/login' && isAuthenticated) { + next('/admin/dashboard') + } else { + next() + } +}) + +export default router diff --git a/Build_God_Admin_Frontend/Frontend/src/stores/auth.ts b/Build_God_Admin_Frontend/Frontend/src/stores/auth.ts new file mode 100644 index 0000000..b9bb995 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/stores/auth.ts @@ -0,0 +1,73 @@ +import { defineStore } from "pinia"; +import { ref, computed } from "vue"; +import { loginApi, logoutApi } from "@/api/auth"; +import type { User } from "@/api/auth"; +import { jwtDecode } from "jwt-decode"; +import router from "@/router"; + +export const useAuthStore = defineStore("auth", () => { + const user = ref(null); + const token = ref(""); + const isAuthenticated = computed(() => !!token.value); + + /** + * 登录 - 调用后台 API + * @param username 用户名 + * @param password 密码 + * @returns 登录是否成功 + */ + const login = async (name: string, password: string): Promise => { + try { + const response = await loginApi({ name, password }); + if (response.token) { + token.value = response.token; + sessionStorage.setItem("auth_token", token.value); + const userInfo = jwtDecode(response.token); + console.log(userInfo); + user.value = userInfo + return true; + } else { + return false; + } + } catch (error) { + console.error("登录失败:", error); + return false; + } + }; + + /** + * 登出 + */ + const logout = async () => { + try { + //await logoutApi(); + router.push('/login') + } catch (error) { + console.error("登出失败:", error); + } finally { + token.value = ""; + user.value = null; + sessionStorage.removeItem("auth_token"); + sessionStorage.removeItem("auth_token"); + } + }; + + /** + * 初始化认证状态(从 sessionStorage 恢复) + */ + const initAuth = () => { + const savedToken = sessionStorage.getItem("auth_token"); + if (savedToken) { + token.value = savedToken; + } + }; + + return { + user, + token, + isAuthenticated, + login, + logout, + initAuth + }; +}); diff --git a/Build_God_Admin_Frontend/Frontend/src/stores/counter.ts b/Build_God_Admin_Frontend/Frontend/src/stores/counter.ts new file mode 100644 index 0000000..b6757ba --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/stores/counter.ts @@ -0,0 +1,12 @@ +import { ref, computed } from 'vue' +import { defineStore } from 'pinia' + +export const useCounterStore = defineStore('counter', () => { + const count = ref(0) + const doubleCount = computed(() => count.value * 2) + function increment() { + count.value++ + } + + return { count, doubleCount, increment } +}) diff --git a/Build_God_Admin_Frontend/Frontend/src/views/AboutView.vue b/Build_God_Admin_Frontend/Frontend/src/views/AboutView.vue new file mode 100644 index 0000000..756ad2a --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/AboutView.vue @@ -0,0 +1,15 @@ + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/views/AdminLayout.vue b/Build_God_Admin_Frontend/Frontend/src/views/AdminLayout.vue new file mode 100644 index 0000000..19835a0 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/AdminLayout.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/views/HomeView.vue b/Build_God_Admin_Frontend/Frontend/src/views/HomeView.vue new file mode 100644 index 0000000..d5c0217 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/HomeView.vue @@ -0,0 +1,9 @@ + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/views/LoginView.vue b/Build_God_Admin_Frontend/Frontend/src/views/LoginView.vue new file mode 100644 index 0000000..7afa743 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/LoginView.vue @@ -0,0 +1,166 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/views/admin/BagsView.vue b/Build_God_Admin_Frontend/Frontend/src/views/admin/BagsView.vue new file mode 100644 index 0000000..f1d188f --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/admin/BagsView.vue @@ -0,0 +1,365 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/views/admin/DashboardView.vue b/Build_God_Admin_Frontend/Frontend/src/views/admin/DashboardView.vue new file mode 100644 index 0000000..0c982be --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/admin/DashboardView.vue @@ -0,0 +1,460 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/views/admin/EquipmentsView.vue b/Build_God_Admin_Frontend/Frontend/src/views/admin/EquipmentsView.vue new file mode 100644 index 0000000..a82b17a --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/admin/EquipmentsView.vue @@ -0,0 +1,640 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/views/admin/LevelsView.vue b/Build_God_Admin_Frontend/Frontend/src/views/admin/LevelsView.vue new file mode 100644 index 0000000..2d2468b --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/admin/LevelsView.vue @@ -0,0 +1,433 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/views/admin/MissionView.vue b/Build_God_Admin_Frontend/Frontend/src/views/admin/MissionView.vue new file mode 100644 index 0000000..f5e95e4 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/admin/MissionView.vue @@ -0,0 +1,1042 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/views/admin/PillsView.vue b/Build_God_Admin_Frontend/Frontend/src/views/admin/PillsView.vue new file mode 100644 index 0000000..6a14f7b --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/admin/PillsView.vue @@ -0,0 +1,542 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/views/admin/SettingsView.vue b/Build_God_Admin_Frontend/Frontend/src/views/admin/SettingsView.vue new file mode 100644 index 0000000..73d49e4 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/admin/SettingsView.vue @@ -0,0 +1,296 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/src/views/admin/SpiritsView.vue b/Build_God_Admin_Frontend/Frontend/src/views/admin/SpiritsView.vue new file mode 100644 index 0000000..0641818 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/admin/SpiritsView.vue @@ -0,0 +1,375 @@ + + + + + \ No newline at end of file diff --git a/Build_God_Admin_Frontend/Frontend/src/views/admin/UsersView.vue b/Build_God_Admin_Frontend/Frontend/src/views/admin/UsersView.vue new file mode 100644 index 0000000..e3093bb --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/src/views/admin/UsersView.vue @@ -0,0 +1,426 @@ + + + + + diff --git a/Build_God_Admin_Frontend/Frontend/tsconfig.app.json b/Build_God_Admin_Frontend/Frontend/tsconfig.app.json new file mode 100644 index 0000000..913b8f2 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/tsconfig.app.json @@ -0,0 +1,12 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], + "exclude": ["src/**/__tests__/*"], + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/Build_God_Admin_Frontend/Frontend/tsconfig.json b/Build_God_Admin_Frontend/Frontend/tsconfig.json new file mode 100644 index 0000000..66b5e57 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.node.json" + }, + { + "path": "./tsconfig.app.json" + } + ] +} diff --git a/Build_God_Admin_Frontend/Frontend/tsconfig.node.json b/Build_God_Admin_Frontend/Frontend/tsconfig.node.json new file mode 100644 index 0000000..822562d --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "extends": "@tsconfig/node24/tsconfig.json", + "include": [ + "vite.config.*", + "vitest.config.*", + "cypress.config.*", + "nightwatch.conf.*", + "playwright.config.*", + "eslint.config.*" + ], + "compilerOptions": { + "noEmit": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["node"] + } +} diff --git a/Build_God_Admin_Frontend/Frontend/vite.config.ts b/Build_God_Admin_Frontend/Frontend/vite.config.ts new file mode 100644 index 0000000..4217010 --- /dev/null +++ b/Build_God_Admin_Frontend/Frontend/vite.config.ts @@ -0,0 +1,18 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import vueDevTools from 'vite-plugin-vue-devtools' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [ + vue(), + vueDevTools(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + }, +}) diff --git a/Build_God_Api/.dockerignore b/Build_God_Api/.dockerignore new file mode 100644 index 0000000..fe1152b --- /dev/null +++ b/Build_God_Api/.dockerignore @@ -0,0 +1,30 @@ +**/.classpath +**/.dockerignore +**/.env +**/.git +**/.gitignore +**/.project +**/.settings +**/.toolstarget +**/.vs +**/.vscode +**/*.*proj.user +**/*.dbmdl +**/*.jfm +**/azds.yaml +**/bin +**/charts +**/docker-compose* +**/Dockerfile* +**/node_modules +**/npm-debug.log +**/obj +**/secrets.dev.yaml +**/values.dev.yaml +LICENSE +README.md +!**/.gitignore +!.git/HEAD +!.git/config +!.git/packed-refs +!.git/refs/heads/** \ No newline at end of file diff --git a/Build_God_Api/.gitignore b/Build_God_Api/.gitignore new file mode 100644 index 0000000..7220077 --- /dev/null +++ b/Build_God_Api/.gitignore @@ -0,0 +1,52 @@ +# ===================== 核心:编译/构建产物 ===================== +bin/ +obj/ +*.dll +*.pdb +publish/ +*.exe +*.manifest +*.resources +*.cache + +# ===================== IDE/编辑器配置 ===================== +# Visual Studio +.vs/ +*.suo +*.user +*.sln.docstates +*.vcxproj.user +*.vcxproj.filters + +# Rider +.idea/ +*.iml +*.sln.iml + +# VS Code +.vscode/ + +# ===================== 运行时/依赖缓存 ===================== +project.assets.json +packages/ +.nuget/ +*.nuspec +dotnet-*.json + +# ===================== 环境配置/敏感信息 ===================== +appsettings.Development.json +appsettings.Staging.json +appsettings.Production.json +launchSettings.json +.user-secrets/ + +# ===================== 临时文件/日志 ===================== +*.tmp +*.temp +*.bak +*.swp +~* +*.log +logs/ +temp/ +tmp/ \ No newline at end of file diff --git a/Build_God_Api/Build_God_Api.sln b/Build_God_Api/Build_God_Api.sln new file mode 100644 index 0000000..53dc6b1 --- /dev/null +++ b/Build_God_Api/Build_God_Api.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36811.4 d17.14 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build_God_Api", "Build_God_Api\Build_God_Api.csproj", "{A69A7FB4-26C9-4BAC-8A1C-4912FD062DF4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A69A7FB4-26C9-4BAC-8A1C-4912FD062DF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A69A7FB4-26C9-4BAC-8A1C-4912FD062DF4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A69A7FB4-26C9-4BAC-8A1C-4912FD062DF4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A69A7FB4-26C9-4BAC-8A1C-4912FD062DF4}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C243A1B1-A5E8-40A6-B46C-35138EAC6C53} + EndGlobalSection +EndGlobal diff --git a/Build_God_Api/Build_God_Api/Build_God_Api.csproj b/Build_God_Api/Build_God_Api/Build_God_Api.csproj new file mode 100644 index 0000000..5f01627 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Build_God_Api.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + enable + 34d14f51-4f4c-4f8e-81d7-17b45add3813 + Linux + + + + + + + + + + + + + + + + + diff --git a/Build_God_Api/Build_God_Api/Build_God_Api.http b/Build_God_Api/Build_God_Api/Build_God_Api.http new file mode 100644 index 0000000..8f7db19 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Build_God_Api.http @@ -0,0 +1,6 @@ +@Build_God_Api_HostAddress = http://localhost:5091 + +GET {{Build_God_Api_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Build_God_Api/Build_God_Api/Common/EnumHelper.cs b/Build_God_Api/Build_God_Api/Common/EnumHelper.cs new file mode 100644 index 0000000..b08b11d --- /dev/null +++ b/Build_God_Api/Build_God_Api/Common/EnumHelper.cs @@ -0,0 +1,41 @@ +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Reflection; + +namespace Build_God_Api.Common +{ + + public class EnumHelper + { + public static List GetEnumList() where T : Enum + { + var result = new List(); + + foreach (var value in Enum.GetValues(typeof(T)).Cast()) + { + var fieldInfo = typeof(T).GetField(value.ToString()); + var description = fieldInfo?.GetCustomAttribute()?.Description; + var displayName = fieldInfo?.GetCustomAttribute()?.Name; + + result.Add(new EnumInfoDto + { + Id = Convert.ToInt32(value), + Name = value.ToString(), + DisplayName = displayName ?? value.ToString(), + Description = description ?? value.ToString() + }); + } + + return result; + } + } + + // 枚举信息DTO + public class EnumInfoDto + { + public int Id { get; set; } + public string? Name { get; set; } + public string? DisplayName { get; set; } + public string? Description { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/Controllers/AccountController.cs b/Build_God_Api/Build_God_Api/Controllers/AccountController.cs new file mode 100644 index 0000000..f0a03f4 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Controllers/AccountController.cs @@ -0,0 +1,94 @@ +using Build_God_Api.DB; +using Build_God_Api.Services; +using Microsoft.AspNetCore.Mvc; +using System.ComponentModel.DataAnnotations; + +namespace Build_God_Api.Controllers +{ + /// + /// 本地测试账号 Tom 123456 email:976802198@qq.com + /// + /// + [ApiController] + [Route("api/god/[controller]")] + public class AccountController(IAccountService service) : ControllerBase + { + private readonly IAccountService _service = service; + + [HttpPost("register")] + public async Task> Register(AccountPostCmd cmd) + { + var exist = await _service.ExistsAccount(cmd.Name, cmd.Email); + if (exist) return BadRequest("账号已经存在"); + + cmd.Password = _service.CalculateHash(cmd.Name, cmd.Password); + var account = new Account + { + Name = cmd.Name, + Email = cmd.Email, + Password = cmd.Password, + Active = true, + IsAdmin = false + }; + + var bo = await _service.Register(account); + if (!bo) + { + return BadRequest("注册账号失败"); + } + + return Ok(account.Id); + } + + [HttpPost("login")] + public async Task> Login(LoginCmd cmd) + { + var account = await _service.GetAccountByName(cmd.Name); + if (account == null) return BadRequest("账号或者密码错误"); + if (account.Password != _service.CalculateHash(cmd.Name, cmd.Password)) + return BadRequest("账号或者密码错误"); + var token = _service.GenerateToken(account.Id, account.Name, false); + return new LoginResult { Token = token }; + } + + [HttpPost("login/admin")] + public ActionResult AdminLogin(LoginCmd cmd) + { + if (cmd.Name != "admin" && cmd.Password != "love_god.123") + { + return BadRequest("只有管理员账号才能使用此接口登录"); + } + var token = _service.GenerateToken(0, "admin", true); + return new LoginResult { Token = token }; + } + } + + public class AccountPostCmd + { + [StringLength(30, MinimumLength = 3)] + [RegularExpression(@"^[a-zA-Z0-9_]+$")] + public string Name { get; set; } = string.Empty; + + [EmailAddress] + [StringLength(50)] + public string Email { get; set; } = string.Empty; + + [StringLength(30, MinimumLength = 4)] + public string Password { get; set; } = string.Empty; + } + + public class LoginCmd + { + [StringLength(30, MinimumLength = 3)] + [RegularExpression(@"^[a-zA-Z0-9_]+$")] + public string Name { get; set; } = string.Empty; + + [StringLength(30, MinimumLength = 4)] + public string Password { get; set; } = string.Empty; + } + + public class LoginResult + { + public string Token { get; set; } = string.Empty; + } +} diff --git a/Build_God_Api/Build_God_Api/Controllers/BagController.cs b/Build_God_Api/Build_God_Api/Controllers/BagController.cs new file mode 100644 index 0000000..5b6754e --- /dev/null +++ b/Build_God_Api/Build_God_Api/Controllers/BagController.cs @@ -0,0 +1,121 @@ +using Build_God_Api.Common; +using Build_God_Api.DB; +using Build_God_Api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Build_God_Api.Controllers +{ + [ApiController] + [Route("api/god/[controller]")] + public class BagController(IBagService service) : ControllerBase + { + private readonly IBagService service = service; + + // ============ Bag配置管理 ============ + + [HttpGet("all")] + [Authorize] + public async Task>> GetAllBags() + { + return await service.GetAllBags(); + } + + [HttpGet("{id}")] + [Authorize] + public async Task> GetBagById(int id) + { + return await service.GetBagById(id); + } + + [HttpPost] + [Authorize(Roles = "admin")] + public async Task> CreateBag([FromBody] Bag bag) + { + return await service.CreateBag(bag); + } + + [HttpPut("{id}")] + [Authorize(Roles = "admin")] + public async Task> UpdateBag(int id, [FromBody] Bag bag) + { + bag.Id = id; + return await service.UpdateBag(bag); + } + + [HttpDelete("{id}")] + [Authorize(Roles = "admin")] + public async Task> DeleteBag(int id) + { + return await service.DeleteBag(id); + } + + // ============ 角色背包管理 ============ + + [HttpGet("character/{characterId}")] + [Authorize] + public async Task> GetCharacterBag(int characterId) + { + return await service.GetCharacterBag(characterId); + } + + [HttpPost("assign")] + [Authorize(Roles = "admin")] + public async Task> AssignBagToCharacter([FromBody] AssignBagDto dto) + { + return await service.AssignBagToCharacter(dto.CharacterId, dto.BagId); + } + + // ============ 背包物品管理 ============ + + [HttpGet("{characterBagId}/items")] + [Authorize] + public async Task>> GetBagItems(int characterBagId) + { + return await service.GetBagItems(characterBagId); + } + + [HttpPost("{characterBagId}/items")] + [Authorize(Roles = "admin")] + public async Task> AddItemToBag(int characterBagId, [FromBody] AddBagItemDto dto) + { + return await service.AddItemToBag(characterBagId, dto.ItemType, dto.ItemId, dto.Quantity); + } + + [HttpDelete("{characterBagId}/items/{itemId}")] + [Authorize(Roles = "admin")] + public async Task> RemoveItemFromBag(int characterBagId, int itemId) + { + return await service.RemoveItemFromBag(characterBagId, itemId); + } + + // ============ 枚举 ============ + + [HttpGet("rarities")] + [Authorize] + public ActionResult> GetBagRarities() + { + return EnumHelper.GetEnumList(); + } + + [HttpGet("item-types")] + [Authorize] + public ActionResult> GetBagItemTypes() + { + return EnumHelper.GetEnumList(); + } + } + + public class AssignBagDto + { + public int CharacterId { get; set; } + public int BagId { get; set; } + } + + public class AddBagItemDto + { + public int ItemType { get; set; } + public int ItemId { get; set; } + public int Quantity { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/Controllers/CharacterController.cs b/Build_God_Api/Build_God_Api/Controllers/CharacterController.cs new file mode 100644 index 0000000..280f217 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Controllers/CharacterController.cs @@ -0,0 +1,100 @@ +using Build_God_Api.DB; +using Build_God_Api.Dto; +using Build_God_Api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Build_God_Api.Controllers +{ + [ApiController] + [Route("api/god/[controller]")] + public class CharacterController(ICharacterService characterService, ICurrentUserService currentUserService) : ControllerBase + { + private readonly ICharacterService characterService = characterService; + private readonly ICurrentUserService currentUserService = currentUserService; + + /// + /// 获取当前账号的角色列表(包含境界和灵根信息) + /// + [HttpGet("list")] + [Authorize] + public async Task>> GetCharacterList() + { + var result = await characterService.GetCharacterListWithDetails(currentUserService.UserId); + return result; + } + + [HttpGet("accountId/{accountId}")] + [Authorize] + public async Task> GetCharacterByAccountId(int accountId) + { + var result = await characterService.GetCharacterByAccountId(accountId); + return result; + } + + [HttpGet("all")] + [Authorize(Roles = "admin")] + public async Task>> GetAllCharacters() + { + return await characterService.GetAllCharacters(); + } + + [HttpGet("{characterId}")] + [Authorize(Roles = "admin")] + public async Task> GetCharacterById(int characterId) + { + return await characterService.GetCharacterById(characterId); + } + + [HttpPost("register")] + [Authorize] + public async Task> RegisterCharacter([FromBody] CharacterRegisterDto dto) + { + return await characterService.RegisterCharacter(dto); + } + + /// + /// 选择角色(更新最后登录时间) + /// + [HttpPost("select/{characterId}")] + [Authorize] + public async Task> SelectCharacter(int characterId) + { + return await characterService.SelectCharacter(characterId); + } + + [HttpDelete("{characterId}")] + [Authorize] + public async Task> DeleteCharacter(int characterId) + { + return await characterService.DeleteCharacter(characterId); + } + + [HttpPost("{characterId}/breakthrough")] + [Authorize] + public async Task> Breakthrough(int characterId) + { + return await characterService.Breakthrough(characterId); + } + + /// + /// 开始打坐 + /// + [HttpPost("{characterId}/training/start")] + [Authorize] + public async Task> StartTraining(int characterId) + { + return await characterService.StartTraining(characterId); + } + + /// + /// 停止打坐并结算经验 + /// + [HttpPost("{characterId}/training/stop")] + [Authorize] + public async Task> StopTraining(int characterId) + { + return await characterService.StopTraining(characterId); + } + } +} diff --git a/Build_God_Api/Build_God_Api/Controllers/EquipmentController.cs b/Build_God_Api/Build_God_Api/Controllers/EquipmentController.cs new file mode 100644 index 0000000..aa0e276 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Controllers/EquipmentController.cs @@ -0,0 +1,75 @@ +using Build_God_Api.Common; +using Build_God_Api.DB; +using Build_God_Api.Dto; +using Build_God_Api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Build_God_Api.Controllers +{ + [ApiController] + [Route("api/god/[controller]")] + public class EquipmentController(IEquipmentService service) : ControllerBase + { + private readonly IEquipmentService service = service; + + [HttpPost] + [Authorize(Roles = "admin")] + public async Task> Add([FromBody] EquipmentTemplate item) + { + return await service.Add(item); + } + + [HttpPost("list")] + [Authorize(Roles = "admin")] + public async Task> Add([FromBody] List items) + { + return await service.Add(items); + } + + [HttpDelete("{id}")] + [Authorize(Roles = "admin")] + public async Task> Delete(int id) + { + return await service.Delete(id); + } + + [HttpPut] + [Authorize(Roles = "admin")] + public async Task> Update([FromBody] EquipmentTemplate item) + { + return await service.Update(item); + } + + [HttpPost("all")] + [Authorize] + public async Task>> GetAll([FromBody] SearchEquipmentDto dto) + { + return await service.GetAll(dto); + } + + [HttpGet("types")] + [Authorize] + public ActionResult> GetEquipmentTypes() + { + var result = EnumHelper.GetEnumList(); + return result; + } + + [HttpGet("rarities")] + [Authorize] + public ActionResult> GetEquipmentRarity() + { + var result = EnumHelper.GetEnumList(); + return result; + } + + [HttpGet("attribute-types")] + [Authorize] + public ActionResult> GetEquipmentAttributeTypes() + { + var result = EnumHelper.GetEnumList(); + return result; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Controllers/Game/TrainingController.cs b/Build_God_Api/Build_God_Api/Controllers/Game/TrainingController.cs new file mode 100644 index 0000000..4228b96 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Controllers/Game/TrainingController.cs @@ -0,0 +1,24 @@ +using Build_God_Api.Services.Game; +using Microsoft.AspNetCore.Mvc; + +namespace Build_God_Api.Controllers.Game +{ + [ApiController] + [Route("api/god/[controller]")] + public class TrainingController(ITrainingService trainingService) : ControllerBase + { + private readonly ITrainingService trainingService = trainingService; + + [HttpPost("start/{characterId}")] + public async Task> StartTraining(int characterId) + { + return await trainingService.StartTraining(characterId); + } + + [HttpPost("end/{characterId}")] + public async Task> EndTraining(int characterId) + { + return await trainingService.EndTraining(characterId); + } + } +} diff --git a/Build_God_Api/Build_God_Api/Controllers/LevelController.cs b/Build_God_Api/Build_God_Api/Controllers/LevelController.cs new file mode 100644 index 0000000..a4b938f --- /dev/null +++ b/Build_God_Api/Build_God_Api/Controllers/LevelController.cs @@ -0,0 +1,42 @@ +using Build_God_Api.DB; +using Build_God_Api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Build_God_Api.Controllers +{ + [ApiController] + [Route("api/god/[controller]")] + public class LevelController(ILevelService service) : ControllerBase + { + private readonly ILevelService service = service; + + [HttpPost] + [Authorize(Roles = "admin")] + public async Task> Add([FromBody] Level item) + { + return await service.Add(item); + } + + [HttpDelete("{id}")] + [Authorize(Roles = "admin")] + public async Task> Delete(int id) + { + return await service.Delete(id); + } + + [HttpPut] + [Authorize(Roles = "admin")] + public async Task> Update([FromBody] Level item) + { + return await service.Update(item); + } + + [HttpGet("all")] + [Authorize] + public async Task>> GetAll() + { + return await service.GetAll(); + } + } +} diff --git a/Build_God_Api/Build_God_Api/Controllers/MissionController.cs b/Build_God_Api/Build_God_Api/Controllers/MissionController.cs new file mode 100644 index 0000000..f471fa9 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Controllers/MissionController.cs @@ -0,0 +1,130 @@ +using Build_God_Api.Common; +using Build_God_Api.DB; +using Build_God_Api.Dto; +using Build_God_Api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Build_God_Api.Controllers +{ + [ApiController] + [Route("api/god/[controller]")] + public class MissionController( + IMissionService service, + IMissionProgressService progressService + ) : ControllerBase + { + private readonly IMissionService service = service; + private readonly IMissionProgressService progressService = progressService; + + // ============ Mission ============ + + [HttpPost] + [Authorize(Roles = "admin")] + public async Task> Add([FromBody] Mission item) + { + return await service.Add(item); + } + + [HttpDelete("{id}")] + [Authorize(Roles = "admin")] + public async Task> Delete(int id) + { + return await service.Delete(id); + } + + [HttpPut] + [Authorize(Roles = "admin")] + public async Task> Update([FromBody] Mission item) + { + return await service.Update(item); + } + + [HttpPost("all")] + [Authorize] + public async Task>> GetAll([FromBody] SearchMissionDto dto) + { + return await service.GetAll(dto); + } + + [HttpPost("reward/add")] + [Authorize] + public async Task> AddReward([FromBody] MissionReward reward) + { + return await service.AddReward(reward); + } + + [HttpPost("reward/update")] + [Authorize] + public async Task> UpdateReward([FromBody] MissionReward reward) + { + return await service.UpdateReward(reward); + } + + // ============ Mission Progress ============ + + [HttpGet("{missionId}/progresses")] + [Authorize] + public async Task>> GetProgresses(int missionId) + { + return await progressService.GetByMissionId(missionId); + } + + [HttpPost("{missionId}/progresses")] + [Authorize(Roles = "admin")] + public async Task> AddProgress(int missionId, [FromBody] MissionProgress progress) + { + progress.MissionId = missionId; + return await progressService.Create(progress); + } + + [HttpPut("progresses/{id}")] + [Authorize(Roles = "admin")] + public async Task> UpdateProgress(int id, [FromBody] MissionProgress progress) + { + progress.Id = id; + return await progressService.Update(progress); + } + + [HttpDelete("progresses/{id}")] + [Authorize(Roles = "admin")] + public async Task> DeleteProgress(int id) + { + return await progressService.Delete(id); + } + + // ============ Enums ============ + + [HttpGet("types")] + [Authorize] + public ActionResult> GetMissionTypes() + { + var result = EnumHelper.GetEnumList(); + return result; + } + + [HttpGet("difficulties")] + [Authorize] + public ActionResult> GetMissionDifficultys() + { + var result = EnumHelper.GetEnumList(); + return result; + } + + [HttpGet("reward/types")] + [Authorize] + public ActionResult> GetMissionRewardTypes() + { + var result = EnumHelper.GetEnumList(); + return result; + } + + [HttpGet("progress/target-types")] + [Authorize] + public ActionResult> GetProgressTargetTypes() + { + var result = EnumHelper.GetEnumList(); + return result; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Controllers/PillController.cs b/Build_God_Api/Build_God_Api/Controllers/PillController.cs new file mode 100644 index 0000000..9977cac --- /dev/null +++ b/Build_God_Api/Build_God_Api/Controllers/PillController.cs @@ -0,0 +1,59 @@ +using Build_God_Api.Common; +using Build_God_Api.DB; +using Build_God_Api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Build_God_Api.Controllers +{ + [ApiController] + [Route("api/god/[controller]")] + public class PillController(IPillService service) : ControllerBase + { + private readonly IPillService service = service; + + [HttpPost] + [Authorize(Roles = "admin")] + public async Task> Add([FromBody] Pill item) + { + return await service.Add(item); + } + + [HttpDelete("{id}")] + [Authorize(Roles = "admin")] + public async Task> Delete(int id) + { + return await service.Delete(id); + } + + [HttpPut] + [Authorize(Roles = "admin")] + public async Task> Update([FromBody] Pill item) + { + return await service.Update(item); + } + + [HttpGet("all")] + [Authorize] + public async Task>> GetAll() + { + return await service.GetAll(); + } + + [HttpGet("types")] + [Authorize] + public ActionResult> GetTypes() + { + var result = EnumHelper.GetEnumList(); + return result; + } + + [HttpGet("rarities")] + [Authorize] + public ActionResult> GetRarities() + { + var result = EnumHelper.GetEnumList(); + return result; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Controllers/ProfessionController.cs b/Build_God_Api/Build_God_Api/Controllers/ProfessionController.cs new file mode 100644 index 0000000..e7af23e --- /dev/null +++ b/Build_God_Api/Build_God_Api/Controllers/ProfessionController.cs @@ -0,0 +1,43 @@ +using Build_God_Api.Common; +using Build_God_Api.DB; +using Build_God_Api.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Build_God_Api.Controllers +{ + [ApiController] + [Route("api/god/[controller]")] + public class ProfessionController(IProfessionService professionService) : ControllerBase + { + private readonly IProfessionService service = professionService; + + [HttpPost] + [Authorize(Roles = "admin")] + public async Task> Add([FromBody] Profession item) + { + return await service.Add(item); + } + + [HttpDelete("{id}")] + [Authorize(Roles = "admin")] + public async Task> Delete(int id) + { + return await service.Delete(id); + } + + [HttpPut] + [Authorize(Roles = "admin")] + public async Task> Update([FromBody] Profession item) + { + return await service.Update(item); + } + + [HttpGet("all")] + [Authorize] + public async Task>> GetAll() + { + return await service.GetAll(); + } + } +} diff --git a/Build_God_Api/Build_God_Api/Controllers/StatisticsController.cs b/Build_God_Api/Build_God_Api/Controllers/StatisticsController.cs new file mode 100644 index 0000000..4bbace3 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Controllers/StatisticsController.cs @@ -0,0 +1,18 @@ +using Build_God_Api.Services; +using Microsoft.AspNetCore.Mvc; + +namespace Build_God_Api.Controllers +{ + [ApiController] + [Route("api/god/[controller]")] + public class StatisticsController(IStatisticsService service) : ControllerBase + { + private readonly IStatisticsService service = service; + + [HttpGet("summary")] + public async Task> GetSummary() + { + return await service.GetSummary(); + } + } +} diff --git a/Build_God_Api/Build_God_Api/DB/Account.cs b/Build_God_Api/Build_God_Api/DB/Account.cs new file mode 100644 index 0000000..9c07d46 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/Account.cs @@ -0,0 +1,19 @@ +namespace Build_God_Api.DB +{ + public class Account : BaseEntity + { + public string Name { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string Password { get; set; } = string.Empty; + public bool Active { get; set; } + public bool IsAdmin { get; set; } + } + + + public enum ThirdPartyProvider + { + QQ, + WeChat, + System + } +} diff --git a/Build_God_Api/Build_God_Api/DB/Bag.cs b/Build_God_Api/Build_God_Api/DB/Bag.cs new file mode 100644 index 0000000..c9eba61 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/Bag.cs @@ -0,0 +1,50 @@ +using SqlSugar; +using System.ComponentModel; + +namespace Build_God_Api.DB +{ + /// + /// 背包配置表 (预设5种背包类型) + /// + public class Bag : BaseEntity + { + /// + /// 背包名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 背包稀有度 + /// + public BagRarity Rarity { get; set; } + + /// + /// 背包容量 + /// + public int Capacity { get; set; } + + /// + /// 描述 + /// + [SugarColumn(IsNullable = true)] + public string? Description { get; set; } + } + + /// + /// 背包稀有度枚举 + /// + public enum BagRarity + { + [Description("普通")] + Common = 1, + + [Description("稀有")] + Rare = 2, + + [Description("史诗")] + Epic = 3, + + [Description("传说")] + Legendary = 4 + } +} diff --git a/Build_God_Api/Build_God_Api/DB/BagItem.cs b/Build_God_Api/Build_God_Api/DB/BagItem.cs new file mode 100644 index 0000000..5d5b118 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/BagItem.cs @@ -0,0 +1,43 @@ +using SqlSugar; +using System.ComponentModel; + +namespace Build_God_Api.DB +{ + /// + /// 背包物品表 + /// + public class BagItem : BaseEntity + { + /// + /// 角色背包关联ID + /// + public int CharacterBagId { get; set; } + + /// + /// 物品类型 + /// + public BagItemType ItemType { get; set; } + + /// + /// 物品ID (装备ID或丹药ID) + /// + public int ItemId { get; set; } + + /// + /// 数量 + /// + public int Quantity { get; set; } + } + + /// + /// 背包物品类型枚举 + /// + public enum BagItemType + { + [Description("装备")] + Equipment = 1, + + [Description("丹药")] + Pill = 2 + } +} diff --git a/Build_God_Api/Build_God_Api/DB/BaseEntity.cs b/Build_God_Api/Build_God_Api/DB/BaseEntity.cs new file mode 100644 index 0000000..453f1f0 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/BaseEntity.cs @@ -0,0 +1,20 @@ +using SqlSugar; + +namespace Build_God_Api.DB +{ + public class BaseEntity + { + //数据是自增需要加上IsIdentity + //数据库是主键需要加上IsPrimaryKey + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + + public DateTime CreatedOn { get; set; } = DateTime.UtcNow; + + public DateTime UpdatedOn { get; set; } = DateTime.UtcNow; + + public int CreatedBy { get; set; } + + public int UpdatedBy { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/DB/Character.cs b/Build_God_Api/Build_God_Api/DB/Character.cs new file mode 100644 index 0000000..700cd56 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/Character.cs @@ -0,0 +1,83 @@ + +using System.ComponentModel.DataAnnotations.Schema; + +namespace Build_God_Api.DB +{ + /// + /// 账号角色 + /// + public class Character : BaseEntity + { + /// + /// 姓名 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 账号ID + /// + public int AccountId { get; set; } + + /// + /// 当前经验值 + /// + public decimal CurrentExp { get; set; } + + /// + /// 当前境界 + /// + public int LevelId { get; set; } + + /// + /// 灵石 + /// + public decimal Money { get; set; } + + /// + /// 最大生命值 + /// + public decimal MaxHP { get; set; } + + /// + /// 当前生命值 + /// + public decimal CurrentHP { get; set; } + + /// + /// 攻击力 + /// + public decimal Attack { get; set; } + + /// + /// 突破成功的概率[0-100] + /// + public decimal BreakthroughRate { get; set; } + + /// + /// 职业ID + /// + [SqlSugar.SugarColumn(IsNullable = false)] + public int? ProfessionId { get; set; } + + /// + /// 灵田ID + /// + public int? SpiritFieldId { get; set; } + + /// + /// 最后登录时间 + /// + public DateTime LastLogin { get; set; } + + /// + /// 是不是在修炼中 + /// + [SqlSugar.SugarColumn(IsNullable = true)] + public DateTime? TrainingOn { get; set; } + + /// + /// 是否删除 + /// + public bool isLocked { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/DB/CharacterBag.cs b/Build_God_Api/Build_God_Api/DB/CharacterBag.cs new file mode 100644 index 0000000..5265406 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/CharacterBag.cs @@ -0,0 +1,20 @@ +using SqlSugar; + +namespace Build_God_Api.DB +{ + /// + /// 角色背包关联表 + /// + public class CharacterBag : BaseEntity + { + /// + /// 角色ID + /// + public int CharacterId { get; set; } + + /// + /// 背包配置ID + /// + public int BagId { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/DB/CharacterMissionProgress.cs b/Build_God_Api/Build_God_Api/DB/CharacterMissionProgress.cs new file mode 100644 index 0000000..9b975bd --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/CharacterMissionProgress.cs @@ -0,0 +1,40 @@ +using SqlSugar; + +namespace Build_God_Api.DB +{ + /// + /// 角色任务进度表 + /// + public class CharacterMissionProgress : BaseEntity + { + /// + /// 角色ID + /// + public int CharacterId { get; set; } + + /// + /// 任务ID + /// + public int MissionId { get; set; } + + /// + /// 进度配置ID + /// + public int MissionProgressId { get; set; } + + /// + /// 当前完成数量 + /// + public int CurrentCount { get; set; } + + /// + /// 该进度是否已完成 + /// + public bool IsCompleted { get; set; } + + /// + /// 更新时间 + /// + public new DateTime UpdatedOn { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/DB/ChatMessage.cs b/Build_God_Api/Build_God_Api/DB/ChatMessage.cs new file mode 100644 index 0000000..fc9e744 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/ChatMessage.cs @@ -0,0 +1,15 @@ +namespace Build_God_Api.DB +{ + public class ChatMessage : BaseEntity + { + public int? CharacterId { get; set; } + + public string CharacterName { get; set; } = string.Empty; + + public string Content { get; set; } = string.Empty; + + public ChatMessageType Type { get; set; } + + public DateTime CreatedAt { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/DB/ChatMessageType.cs b/Build_God_Api/Build_God_Api/DB/ChatMessageType.cs new file mode 100644 index 0000000..a599344 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/ChatMessageType.cs @@ -0,0 +1,9 @@ +namespace Build_God_Api.DB +{ + public enum ChatMessageType + { + Player = 0, + System = 1, + World = 2 + } +} diff --git a/Build_God_Api/Build_God_Api/DB/Equipment.cs b/Build_God_Api/Build_God_Api/DB/Equipment.cs new file mode 100644 index 0000000..f6e0212 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/Equipment.cs @@ -0,0 +1,217 @@ +using SqlSugar; +using System.ComponentModel; + +namespace Build_God_Api.DB +{ + /// + /// 装备模板 - 定义装备的基础信息和随机属性池 + /// + public class EquipmentTemplate : BaseEntity + { + public string Name { get; set; } = string.Empty; + + /// + /// 描述 + /// + public string Description { get; set; } = string.Empty; + + /// + /// 装备类型(武器/防具/饰品等) + /// + public EquipmentType Type { get; set; } + + /// + /// 稀有度 + /// + public Rarity Rarity { get; set; } + + /// + /// 需求等级(角色需达到该等级才能穿戴) + /// + public int RequirdLevelId { get; set; } + + /// + /// 所属套装ID + /// + [SugarColumn(IsNullable = true)] + public int? SetId { get; set; } + + /// + /// 价格 + /// + public int Money { get; set; } + + /// + /// 随机属性池配置(JSON) + /// 格式: [{"type":"AttackFixed","min":100,"max":1000,"weight":1},{"type":"AttackPercent","min":0,"max":50,"weight":1}] + /// + [SugarColumn(ColumnDataType = "text")] + public string AttributePool { get; set; } + + /// + /// 随机生成几条属性 + /// + public int RandomAttrCount { get; set; } + + /// + /// 最大强化等级 + /// + public int MaxEnhanceLevel { get; set; } + } + + /// + /// 装备实例 - 角色拥有的具体装备 + /// + public class EquipmentInstance : BaseEntity + { + /// + /// 角色背包关联ID + /// + public int CharacterBagId { get; set; } + + /// + /// 装备模板ID + /// + public int EquipmentTemplateId { get; set; } + + /// + /// 装备名称(复制自模板) + /// + public string Name { get; set; } = string.Empty; + + /// + /// 装备类型 + /// + public EquipmentType Type { get; set; } + + /// + /// 稀有度 + /// + public Rarity Rarity { get; set; } + + /// + /// 随机属性(JSON) + /// 格式: [{"type":"AttackFixed","value":856},{"type":"AttackPercent","value":40}] + /// + public string Attributes { get; set; } = "[]"; + + /// + /// 强化等级 + /// + public int EnhanceLevel { get; set; } + + /// + /// 强化额外加成百分比 + /// + public int EnhanceBonusPercent { get; set; } + + /// + /// 需求等级 + /// + public int RequirdLevelId { get; set; } + + /// + /// 套装ID + /// + [SugarColumn(IsNullable = true)] + public int? SetId { get; set; } + } + + /// + /// 强化配置 + /// + public class EnhanceConfig : BaseEntity + { + /// + /// 强化等级 + /// + public int Level { get; set; } + + /// + /// 所需材料ID + /// + public int ReqItemId { get; set; } + + /// + /// 所需材料数量 + /// + public int ReqItemCount { get; set; } + + /// + /// 成功率(百分比) + /// + public int SuccessRate { get; set; } + + /// + /// 成功后增加的强化加成百分比 + /// + public int BonusPercent { get; set; } + } + + /// + /// 属性类型枚举 + /// + public enum EquipmentAttributeType + { + [Description("攻击力-固定")] + AttackFixed = 1, + + [Description("攻击力-百分比")] + AttackPercent = 2, + + [Description("防御力-固定")] + DefendFixed = 3, + + [Description("防御力-百分比")] + DefendPercent = 4, + + [Description("暴击率-百分比")] + CriticalPercent = 5, + + [Description("闪避-百分比")] + MissPercent = 6, + + [Description("生命值-固定")] + HealthBonusFixed = 7, + + [Description("生命值-百分比")] + HealthBonusPercent = 8, + + [Description("法力-固定")] + ManaBonusFixed = 9, + + [Description("法力-百分比")] + ManaBonusPercent = 10, + + [Description("突破概率-百分比")] + BreakthroughBonusPercent = 11 + } + + public enum EquipmentType + { + [Description("武器")] + Weapon = 1, + [Description("防具")] + Armor, + [Description("头盔")] + Helmet, + [Description("项链")] + Necklace, + [Description("戒指")] + Ring, + [Description("鞋子")] + Boots + } + + public enum Rarity + { + [Description("普通")] + Common = 1, + [Description("稀有")] + Rare, + [Description("史诗")] + Epic, + [Description("传说")] + Legendary + } +} diff --git a/Build_God_Api/Build_God_Api/DB/Level.cs b/Build_God_Api/Build_God_Api/DB/Level.cs new file mode 100644 index 0000000..915b926 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/Level.cs @@ -0,0 +1,40 @@ +namespace Build_God_Api.DB +{ + /// + /// 境界 + /// + public class Level : BaseEntity + { + public string Name { get; set; } = string.Empty; + + /// + /// 境界层级,数值越大等级越高 + /// + public int LevelId { get; set; } + + /// + /// 当前等级最小经验值 + /// + public int CurrentLevelMinExp { get; set; } + + /// + /// 下一境界ID + /// + public int? NextLevelId { get; set; } + + /// + /// 基础突破概率[0-100] + /// + public decimal BaseBreakthroughRate { get; set; } + + /// + /// 突破失败后增加的概率[0-100] + /// + public decimal FailIncrement { get; set; } + + /// + /// 描述 + /// + public string Description { get; set; } = string.Empty; + } +} diff --git a/Build_God_Api/Build_God_Api/DB/Mission.cs b/Build_God_Api/Build_God_Api/DB/Mission.cs new file mode 100644 index 0000000..dcf9b03 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/Mission.cs @@ -0,0 +1,121 @@ +using SqlSugar; +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace Build_God_Api.DB +{ + /// + /// 任务实体(修仙游戏) + /// + public class Mission : BaseEntity + { + /// + /// 任务名称 + /// + [SugarColumn(Length = 50, IsNullable = false)] + public string Name { get; set; } = string.Empty; + + /// + /// 任务类型 + /// + [SugarColumn(IsNullable = false)] + public MissionType Type { get; set; } = MissionType.MainStory; + + /// + /// 任务标题(展示用,与Name不同) + /// + [SugarColumn(Length = 100, IsNullable = false)] + public string Title { get; set; } = string.Empty; + + /// + /// 任务简介 + /// + [SugarColumn(Length = 500, IsNullable = false)] + public string Description { get; set; } = string.Empty; + + /// + /// 需求修为境界ID + /// + [SugarColumn(IsNullable = false)] + public int RequiredLevelId { get; set; } = 1; + + /// + /// 是否可以重复接取 + /// true-无限/每日重置,false-仅可接取一次 + /// + [SugarColumn(IsNullable = false)] + public bool Repeatable { get; set; } = false; + + /// + /// 是否可接取(true-可接,false-未开放) + /// + [SugarColumn(IsNullable = false, DefaultValue = "0")] + public bool IsAvailable { get; set; } = false; + + /// + /// 任务花费时间(分钟) + /// + [SugarColumn(IsNullable = false, DefaultValue = "0")] + public int SpendTimeMinutes { get; set; } = 0; + + /// + /// 任务获取概率(0-100,百分比) + /// + [Range(0, 100)] + [SugarColumn(IsNullable = false, DefaultValue = "100")] + public decimal ObtainPercentage { get; set; } = 100; + + /// + /// 前置任务ID(0代表无前置) + /// + [SugarColumn(IsNullable = false, DefaultValue = "0")] + public int PreMissionId { get; set; } = 0; + + /// + /// 任务难度 + /// + [SugarColumn(IsNullable = false)] + public MissionDifficulty Difficulty { get; set; } = MissionDifficulty.Normal; + + //这里不能给rewards初始化,不然导航属性会没办法查询到数据 + [SqlSugar.Navigate(SqlSugar.NavigateType.OneToMany, nameof(MissionReward.MissionId), nameof(Id))] + public List? Rewards { get; set; } + + [SqlSugar.Navigate(SqlSugar.NavigateType.OneToMany, nameof(MissionProgress.MissionId), nameof(Id))] + public List? Progresses { get; set; } + } + + /// + /// 任务类型枚举 + /// + public enum MissionType + { + [Description("悬赏任务")] + MainStory = 1, + + [Description("日常任务")] + DailyTask = 2, + + [Description("采集任务")] + BranchTask = 3 + } + + /// + /// 任务难度枚举 + /// + public enum MissionDifficulty + { + [Description("简单")] + Easy = 1, + + [Description("普通")] + Normal = 2, + + [Description("困难")] + Hard = 3, + + [Description("炼狱")] + Purgatory = 4 + } +} diff --git a/Build_God_Api/Build_God_Api/DB/MissionProgress.cs b/Build_God_Api/Build_God_Api/DB/MissionProgress.cs new file mode 100644 index 0000000..6ab6aab --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/MissionProgress.cs @@ -0,0 +1,65 @@ +using SqlSugar; +using System.ComponentModel; + +namespace Build_God_Api.DB +{ + /// + /// 任务目标 + /// + public class MissionProgress : BaseEntity + { + /// + /// 任务ID + /// + public int MissionId { get; set; } + + /// + /// 目标类型 + /// + public ProgressTargetType TargetType { get; set; } + + /// + /// 目标物品ID (可选) + /// + [SugarColumn(IsNullable = true)] + public int? TargetItemId { get; set; } + + /// + /// 目标物品名称 (展示用) + /// + [SugarColumn(Length = 100, IsNullable = true)] + public string? TargetItemName { get; set; } + + /// + /// 目标数量 + /// + public int TargetCount { get; set; } + + /// + /// 描述 + /// + [SugarColumn(Length = 500, IsNullable = true)] + public string? Description { get; set; } + } + + /// + /// 进度目标类型枚举 + /// + public enum ProgressTargetType + { + [Description("收集物品")] + CollectItem = 1, + + [Description("钓鱼")] + Fish = 2, + + [Description("击杀怪物")] + KillMonster = 3, + + [Description("消耗物品")] + ConsumeItem = 4, + + [Description("自定义")] + Custom = 5 + } +} diff --git a/Build_God_Api/Build_God_Api/DB/MissionReward.cs b/Build_God_Api/Build_God_Api/DB/MissionReward.cs new file mode 100644 index 0000000..5743c91 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/MissionReward.cs @@ -0,0 +1,51 @@ +using System.ComponentModel; + +namespace Build_God_Api.DB +{ + /// + /// 任务奖励关联表(一对多) + /// + public class MissionReward : BaseEntity + { + /// + /// 任务ID + /// + public int MissionId { get; set; } + + /// + /// 奖励类型(丹药/装备/法宝/功法等) + /// + public RewardType RewardType { get; set; } + + /// + /// 奖励物品ID(关联对应物品表) + /// + public int ItemId { get; set; } + + /// + /// 物品名称(展示用,非数据库字段) + /// + [SqlSugar.SugarColumn(IsIgnore = true)] + public string ItemName { get; set; } = string.Empty; + + /// + /// 奖励数量 + /// + public int Count { get; set; } + } + + /// + /// 奖励类型枚举 + /// + public enum RewardType + { + [Description("丹药")] + Pill = 1, + [Description("装备")] + Equipment = 2, + [Description("经验")] + Exp = 3, + [Description("灵石")] + Money = 4 + } +} diff --git a/Build_God_Api/Build_God_Api/DB/Pill.cs b/Build_God_Api/Build_God_Api/DB/Pill.cs new file mode 100644 index 0000000..f8bc5e7 --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/Pill.cs @@ -0,0 +1,72 @@ +using System.ComponentModel; + +namespace Build_God_Api.DB +{ + /// + /// 丹药 + /// + public class Pill : BaseEntity + { + /// + /// 丹药名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 丹药类型(疗伤、突破、增益等) + /// + public PillType Type { get; set; } + + /// + /// 丹药稀有度 + /// + public Rarity Rarity { get; set; } + + /// + /// 价格 + /// + public int Money { get; set; } + + /// + /// 描述 + /// + public string Description { get; set; } = string.Empty; + + /// + /// 所需境界 + /// + public int RequirdLevelId { get; set; } + + /// + /// 提升效果(比如 增加1000血、提升百分之20的修炼效率) + /// + public decimal EffectValue { get; set; } + + /// + /// 持续时间(秒) 如果是 0 就表示是永久 + /// + public decimal Duration { get; set; } + } + + public enum PillType + { + [Description("增益")] + Buff = 1, + [Description("疗伤")] + Healing, + [Description("突破")] + Breakthrough + } + + public enum PillRarity + { + [Description("下品")] + Low = 1, + [Description("中品")] + Medium, + [Description("上品")] + High, + [Description("极品")] + Premium + } +} diff --git a/Build_God_Api/Build_God_Api/DB/Profession.cs b/Build_God_Api/Build_God_Api/DB/Profession.cs new file mode 100644 index 0000000..653736c --- /dev/null +++ b/Build_God_Api/Build_God_Api/DB/Profession.cs @@ -0,0 +1,40 @@ +using System.ComponentModel; + +namespace Build_God_Api.DB +{ + /// + /// 职业 + /// + public class Profession : BaseEntity + { + /// + /// 职业名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 职业描述 + /// + public string Description { get; set; } = string.Empty; + + /// + /// 攻击系数 + /// + public decimal AttackRate { get; set; } = 1.0m; + + /// + /// 防御系数 + /// + public decimal DefendRate { get; set; } = 1.0m; + + /// + /// 生命系数 + /// + public decimal HealthRate { get; set; } = 1.0m; + + /// + /// 暴击系数 + /// + public decimal CriticalRate { get; set; } = 1.0m; + } +} diff --git a/Build_God_Api/Build_God_Api/Dockerfile b/Build_God_Api/Build_God_Api/Dockerfile new file mode 100644 index 0000000..c994d28 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Dockerfile @@ -0,0 +1,30 @@ +# 请参阅 https://aka.ms/customizecontainer 以了解如何自定义调试容器,以及 Visual Studio 如何使用此 Dockerfile 生成映像以更快地进行调试。 + +# 此阶段用于在快速模式(默认为调试配置)下从 VS 运行时 +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER $APP_UID +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + + +# 此阶段用于生成服务项目 +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["Build_God_Api/Build_God_Api.csproj", "Build_God_Api/"] +RUN dotnet restore "./Build_God_Api/Build_God_Api.csproj" +COPY . . +WORKDIR "/src/Build_God_Api" +RUN dotnet build "./Build_God_Api.csproj" -c $BUILD_CONFIGURATION -o /app/build + +# 此阶段用于发布要复制到最终阶段的服务项目 +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./Build_God_Api.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +# 此阶段在生产中使用,或在常规模式下从 VS 运行时使用(在不使用调试配置时为默认值) +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Build_God_Api.dll"] \ No newline at end of file diff --git a/Build_God_Api/Build_God_Api/Dto/CharacterDto.cs b/Build_God_Api/Build_God_Api/Dto/CharacterDto.cs new file mode 100644 index 0000000..f31bafa --- /dev/null +++ b/Build_God_Api/Build_God_Api/Dto/CharacterDto.cs @@ -0,0 +1,22 @@ +namespace Build_God_Api.Dto +{ + public class CharacterDto + { + public int Id { get; set; } + public string Name { get; set; } + public string LevelName { get; set; } + public int LevelId { get; set; } + public decimal MaxHP { get; set; } + public decimal Attack { get; set; } + public string ProfessionName { get; set; } + public decimal Money { get; set; } + public decimal CurrentExp { get; set; } + public DateTime? TrainingOn { get; set; } + public string? NextLevelName { get; set; } + public decimal? NextLevelMinExp { get; set; } + public decimal BreakthroughRate { get; set; } + public bool CanBreakthrough { get; set; } + public DateTime LastLogin { get; set; } + public DateTime CreatedOn { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/Dto/EquipmentDtos.cs b/Build_God_Api/Build_God_Api/Dto/EquipmentDtos.cs new file mode 100644 index 0000000..2d75cf5 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Dto/EquipmentDtos.cs @@ -0,0 +1,14 @@ +using Build_God_Api.DB; + +namespace Build_God_Api.Dto +{ + /// + /// 查询装备的条件 + /// + public class SearchEquipmentDto + { + public int PageNumber { get; set; } + public int PageSize { get; set; } = 0; + public int? EquipmentType { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/Dto/MissionDtos.cs b/Build_God_Api/Build_God_Api/Dto/MissionDtos.cs new file mode 100644 index 0000000..b0d7a6a --- /dev/null +++ b/Build_God_Api/Build_God_Api/Dto/MissionDtos.cs @@ -0,0 +1,11 @@ +using Build_God_Api.DB; + +namespace Build_God_Api.Dto +{ + public class SearchMissionDto + { + public int PageNumber { get; set; } + public int PageSize { get; set; } = 0; + public int? MissionType { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/Dto/PagedResult.cs b/Build_God_Api/Build_God_Api/Dto/PagedResult.cs new file mode 100644 index 0000000..161c88f --- /dev/null +++ b/Build_God_Api/Build_God_Api/Dto/PagedResult.cs @@ -0,0 +1,30 @@ +namespace Build_God_Api.Dto +{ + public class PagedResult where T : class + { + /// + /// 当前页的数据 + /// + public List Items { get; set; } = []; + + /// + /// 总记录数 + /// + public long TotalCount { get; set; } + + /// + /// 每页大小 + /// + public int PageSize { get; set; } + + /// + /// 当前页码(从1开始) + /// + public int PageNumber { get; set; } + + /// + /// 总页数 + /// + public int TotalPages => PageSize > 0 ? (int)Math.Ceiling((double)TotalCount / PageSize) : 0; + } +} diff --git a/Build_God_Api/Build_God_Api/Hubs/ChatHub.cs b/Build_God_Api/Build_God_Api/Hubs/ChatHub.cs new file mode 100644 index 0000000..0ac38f7 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Hubs/ChatHub.cs @@ -0,0 +1,101 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.SignalR; +using Build_God_Api.DB; +using Build_God_Api.Services; +using System.Security.Claims; + +namespace Build_God_Api.Hubs +{ + [Authorize(AuthenticationSchemes = "Bearer")] + public class ChatHub : Hub + { + private readonly IChatService _chatService; + private readonly ICurrentUserService _currentUserService; + private readonly ICharacterService _characterService; + private readonly IHubContext _hubContext; + private readonly ILogger _logger; + + public ChatHub( + IChatService chatService, + ICurrentUserService currentUserService, + ICharacterService characterService, + IHubContext hubContext, + ILogger logger) + { + _chatService = chatService; + _currentUserService = currentUserService; + _characterService = characterService; + _hubContext = hubContext; + _logger = logger; + } + + public async Task> GetRecentMessages(int count = 100) + { + var messages = await _chatService.GetRecentMessagesAsync(count); + return messages.Select(m => new ChatMessageDto + { + Id = m.Id, + CharacterId = m.CharacterId, + CharacterName = m.CharacterName, + Content = m.Content, + Type = m.Type, + CreatedAt = m.CreatedAt + }).ToList(); + } + + public async Task SendMessage(string content, int type = 0) + { + if (string.IsNullOrWhiteSpace(content) || content.Length > 500) + return null; + + var accountId = _currentUserService.UserId; + var character = await _characterService.GetCharacterByAccountId(accountId); + + if (character == null) + return null; + + var messageType = (ChatMessageType)type; + var message = await _chatService.AddMessageAsync( + character.Id, + character.Name, + content.Trim(), + messageType); + + var dto = new ChatMessageDto + { + Id = message.Id, + CharacterId = message.CharacterId, + CharacterName = message.CharacterName, + Content = message.Content, + Type = message.Type, + CreatedAt = message.CreatedAt + }; + + await _hubContext.Clients.All.SendAsync("ReceiveMessage", dto); + + return dto; + } + + public override async Task OnConnectedAsync() + { + _logger.LogInformation("User connected: {ConnectionId}", Context.ConnectionId); + await base.OnConnectedAsync(); + } + + public override async Task OnDisconnectedAsync(Exception? exception) + { + _logger.LogInformation("User disconnected: {ConnectionId}", Context.ConnectionId); + await base.OnDisconnectedAsync(exception); + } + } + + public class ChatMessageDto + { + public int Id { get; set; } + public int? CharacterId { get; set; } + public string CharacterName { get; set; } = string.Empty; + public string Content { get; set; } = string.Empty; + public ChatMessageType Type { get; set; } + public DateTime CreatedAt { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/MarkDown.md b/Build_God_Api/Build_God_Api/MarkDown.md new file mode 100644 index 0000000..d7e3482 --- /dev/null +++ b/Build_God_Api/Build_God_Api/MarkDown.md @@ -0,0 +1,20 @@ +# 后台API + +所有的API都在这里面了😎 + +# 职业 +将系统的灵根设定成职业 + +- 亡灵 : 攻击系数 * 1.6 防御系数 * 0.8 生命系数 * 0.9 暴击系数 * 1.1 +- 骑士 : 攻击系数 * 0.85 防御系数 * 1.6 生命系数 * 1.3 暴击系数 * 0.9 +- 游侠 : 攻击系数 * 1.1 防御系数 * 0.9 生命系数 * 0.85 暴击系数 * 1.5 +- 战士 : 攻击系数 * 1.0 防御系数 * 1.0 生命系数 * 1.5 暴击系数 * 0.95 + +## 打坐 🧘 +结算公式:基础系数 × 等级 × 等级 × (1 + buff) × 秒数 +--基础系数:一个很小的数字,用于整体平衡,比如0.2、0.5或者1,我设定得1,后面再调整 +--buff:加成 + +## 等级 +每一级得最小经验门槛儿计算: 100 * level * level * level; + diff --git a/Build_God_Api/Build_God_Api/Program.cs b/Build_God_Api/Build_God_Api/Program.cs new file mode 100644 index 0000000..3b2208a --- /dev/null +++ b/Build_God_Api/Build_God_Api/Program.cs @@ -0,0 +1,197 @@ +using Build_God_Api.DB; +using Build_God_Api.Services; +using Build_God_Api.Services.Game; +using Build_God_Api.Hubs; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.IdentityModel.Tokens; +using Microsoft.OpenApi.Models; +using SqlSugar; +using System.Text; + +namespace Build_God_Api +{ + public class Program + { + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + + builder.Services.AddControllers(); + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" }); + + // 添加Bearer认证 + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Description = "输入JWT Token: Bearer {token}", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Scheme = "Bearer" + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] {} + } + }); + }); + + //sqlsugar + builder.Services.AddHttpContextAccessor(); + builder.Services.AddSingleton(s => + { + var config = new ConnectionConfig() + { + ConnectionString = builder.Configuration["connection"], + DbType = DbType.PostgreSQL, + IsAutoCloseConnection = true, + InitKeyType = InitKeyType.Attribute, + MoreSettings = new ConnMoreSettings + { + EnableCodeFirstUpdatePrecision = true + }, + AopEvents = new AopEvents + { + OnLogExecuting = (sql, pars) => + { + Console.WriteLine($"Sql:{sql}"); + Console.WriteLine($"parameters:{string.Join(",", pars.Select(p => $"{p.ParameterName}:{p.Value}"))}"); + } + } + }; + + var sqlSugarClient = new SqlSugarScope(config); + + sqlSugarClient.DbMaintenance.CreateDatabase(); + sqlSugarClient.CodeFirst.InitTables(typeof(Account)); + sqlSugarClient.CodeFirst.InitTables(typeof(Character)); + sqlSugarClient.CodeFirst.InitTables(typeof(Level)); + sqlSugarClient.CodeFirst.InitTables(typeof(Profession)); + sqlSugarClient.CodeFirst.InitTables(typeof(EquipmentTemplate)); + sqlSugarClient.CodeFirst.InitTables(typeof(EquipmentInstance)); + sqlSugarClient.CodeFirst.InitTables(typeof(EnhanceConfig)); + sqlSugarClient.CodeFirst.InitTables(typeof(Pill)); + sqlSugarClient.CodeFirst.InitTables(typeof(Mission)); + sqlSugarClient.CodeFirst.InitTables(typeof(MissionReward)); + sqlSugarClient.CodeFirst.InitTables(typeof(Bag)); + sqlSugarClient.CodeFirst.InitTables(typeof(CharacterBag)); + sqlSugarClient.CodeFirst.InitTables(typeof(BagItem)); + sqlSugarClient.CodeFirst.InitTables(typeof(MissionProgress)); + sqlSugarClient.CodeFirst.InitTables(typeof(CharacterMissionProgress)); + sqlSugarClient.CodeFirst.InitTables(typeof(ChatMessage)); + + return sqlSugarClient; + }); + + //jwt + builder.Services.AddAuthentication().AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, op => + { + op.TokenValidationParameters = new TokenValidationParameters + { + ValidateIssuer = true, + ValidIssuer = builder.Configuration["JWT:Issuer"], + ValidateAudience = true, + ValidAudience = builder.Configuration["JWT:Audience"], + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JWT:SecretKey"]!)), + ValidateLifetime = true, + ClockSkew = TimeSpan.FromSeconds(30), + RequireExpirationTime = true, + }; + + op.Events = new JwtBearerEvents + { + OnMessageReceived = context => + { + var accessToken = context.Request.Query["access_token"]; + var path = context.HttpContext.Request.Path; + if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs")) + { + context.Token = accessToken; + } + return Task.CompletedTask; + }, + // 可选:添加认证失败日志,方便排查 + OnAuthenticationFailed = context => + { + Console.WriteLine($"认证失败:{context.Exception.Message}"); + return Task.CompletedTask; + } + }; + }); + builder.Services.AddSignalR(); + builder.Services.AddMemoryCache(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); + + builder.Services.AddCors(options => + { + options.AddPolicy("AllowLocal", policy => + { + var allowedOrigins = builder.Configuration["AllowedOrigins"]?.Split(',') ?? []; + + if (builder.Environment.IsDevelopment()) + { + var devOrigins = new[] { "http://localhost:5174", "http://localhost:5173" }; + policy.WithOrigins(devOrigins.Concat(allowedOrigins).ToArray()); + } + else + { + policy.WithOrigins(allowedOrigins); + } + + policy.AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials(); + }); + }); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseCors("AllowLocal"); + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseHttpsRedirection(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.MapControllers(); + app.MapHub("/hubs/chat"); + + app.Run(); + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/AccountService.cs b/Build_God_Api/Build_God_Api/Services/AccountService.cs new file mode 100644 index 0000000..7691a36 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/AccountService.cs @@ -0,0 +1,123 @@ +using Build_God_Api.DB; +using Microsoft.IdentityModel.Tokens; +using SqlSugar; +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; + +namespace Build_God_Api.Services +{ + public interface IAccountService + { + Task GetAccount(long id); + Task GetAccountByEmail(string email); + Task GetAccountByName(string name); + Task ExistsAccount(string name, string email); + Task Register(Account account); + Task UpdateAccount(Account account); + Task ActivateAccount(long id); + Task DeactivateAccount(long id); + Task DeleteAccount(long id); + string CalculateHash(string name, string password); + string GenerateToken(long id, string name, bool isAdmin); + } + + public class AccountService + ( + ISqlSugarClient context, + IConfiguration configuration, + ILogger logger, + ICurrentUserService currentUserService + ) : IAccountService + { + private readonly ISqlSugarClient _context = context; + private readonly IConfiguration _configuration = configuration; + private readonly ILogger _logger = logger; + private readonly ICurrentUserService currentUserService = currentUserService; + + public async Task GetAccount(long id) + => await _context.Queryable().FirstAsync(x => x.Id == id); + public async Task GetAccountByName(string name) + => await _context.Queryable().FirstAsync(a => a.Name == name); + public async Task GetAccountByEmail(string email) + => await _context.Queryable().FirstAsync(a => a.Email == email); + public async Task ExistsAccount(string name, string email) + => await _context.Queryable().AnyAsync(a => a.Name == name || a.Email == email); + + public async Task Register(Account account) + { + var accountToUpdate = await GetAccountByEmail(account.Email); + if (accountToUpdate != null) return false; + await _context.Insertable(account).ExecuteCommandAsync(); + return true; + } + + public async Task UpdateAccount(Account account) + { + var accountToUpdate = await GetAccount(account.Id); + if (accountToUpdate == null) return null; + + accountToUpdate.Email = account.Email; + accountToUpdate.Name = account.Name; + accountToUpdate.Password = account.Password; + accountToUpdate.Active = account.Active; + accountToUpdate.UpdatedOn = DateTime.UtcNow; + + return accountToUpdate.Id; + } + public async Task ActivateAccount(long id) + { + var account = await GetAccount(id); + if (account == null) return false; + account.Active = true; + await _context.Updateable(account).ExecuteCommandAsync(); + return true; + } + public async Task DeactivateAccount(long id) + { + var account = await GetAccount(id); + if (account == null) return false; + account.Active = false; + await _context.Updateable(account).ExecuteCommandAsync(); + return true; + } + public async Task DeleteAccount(long id) + { + var account = await GetAccount(id); + if (account == null) return false; + await _context.Deleteable(account).ExecuteCommandAsync(); + return true; + } + public string CalculateHash(string name, string password) + { + var new_password = password[..1] + name[..1] + password[1..^1] + name[^1] + password[^1] + '+'; + var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(new_password)); + return Convert.ToBase64String(bytes); + } + + public string GenerateToken(long id, string name, bool isAdmin) + { + _logger.LogInformation("Generating token for {name}, key: {key}, issuer: {issuer}", name, _configuration["JWT:SecretKey"], _configuration["JWT:Issuer"]); + var claims = new[] + { + new Claim("Id", id.ToString()), + new Claim("Name", name), + new Claim(ClaimTypes.Role, isAdmin ? "admin" : "user") + }; + var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JWT:SecretKey"]!)); + var signingCredentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256); + var expires = Convert.ToInt32(_configuration["JWT:Expires"]); + var token = new JwtSecurityToken( + _configuration["JWT:Issuer"], + _configuration["JWT:Audience"], + claims, + DateTime.Now, + DateTime.Now.AddMinutes(expires), + signingCredentials + ); + var jwtToken = new JwtSecurityTokenHandler().WriteToken(token); + return jwtToken; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/BagService.cs b/Build_God_Api/Build_God_Api/Services/BagService.cs new file mode 100644 index 0000000..4e53a79 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/BagService.cs @@ -0,0 +1,256 @@ +using Build_God_Api.DB; +using SqlSugar; + +namespace Build_God_Api.Services +{ + public interface IBagService + { + // Bag配置管理 + Task> GetAllBags(); + Task GetBagById(int id); + Task CreateBag(Bag bag); + Task UpdateBag(Bag bag); + Task DeleteBag(int id); + + // 角色背包管理 + Task GetCharacterBag(int characterId); + Task AssignBagToCharacter(int characterId, int bagId); + + // 背包物品管理 + Task> GetBagItems(int characterBagId); + Task AddItemToBag(int characterBagId, int itemType, int itemId, int quantity); + Task RemoveItemFromBag(int characterBagId, int bagItemId); + } + + public class BagItemDto + { + public int Id { get; set; } + public int CharacterBagId { get; set; } + public int ItemType { get; set; } + public int ItemId { get; set; } + public int Quantity { get; set; } + public string? ItemName { get; set; } + public int? ItemRarity { get; set; } + } + + public class CharacterBagDto + { + public int Id { get; set; } + public int CharacterId { get; set; } + public int BagId { get; set; } + public string? BagName { get; set; } + public int BagCapacity { get; set; } + } + + public class BagService(ISqlSugarClient db) : IBagService + { + private readonly ISqlSugarClient db = db; + + // ============ Bag配置管理 ============ + + public async Task> GetAllBags() + { + return await db.Queryable().ToListAsync(); + } + + public async Task GetBagById(int id) + { + return await db.Queryable().FirstAsync(x => x.Id == id); + } + + public async Task CreateBag(Bag bag) + { + await db.Insertable(bag).ExecuteCommandAsync(); + return true; + } + + public async Task UpdateBag(Bag bag) + { + var existing = await GetBagById(bag.Id); + if (existing == null) + throw new Exception("背包配置不存在"); + + existing.Name = bag.Name; + existing.Rarity = bag.Rarity; + existing.Capacity = bag.Capacity; + existing.Description = bag.Description; + + await db.Updateable(existing).ExecuteCommandAsync(); + return true; + } + + public async Task DeleteBag(int id) + { + // 检查是否有角色正在使用此背包 + var characterBags = await db.Queryable() + .Where(x => x.BagId == id) + .ToListAsync(); + + if (characterBags.Any()) + { + // 删除相关的物品和关联 + foreach (var cb in characterBags) + { + await db.Deleteable().Where(x => x.CharacterBagId == cb.Id).ExecuteCommandAsync(); + } + await db.Deleteable().Where(x => x.BagId == id).ExecuteCommandAsync(); + } + + await db.Deleteable().Where(x => x.Id == id).ExecuteCommandAsync(); + return true; + } + + public async Task GetCharacterBag(int characterId) + { + var characterBag = await db.Queryable() + .FirstAsync(x => x.CharacterId == characterId); + + if (characterBag == null) + return null; + + var bag = await GetBagById(characterBag.BagId); + + return new CharacterBagDto + { + Id = characterBag.Id, + CharacterId = characterBag.CharacterId, + BagId = characterBag.BagId, + BagName = bag?.Name, + BagCapacity = bag?.Capacity ?? 0 + }; + } + + public async Task AssignBagToCharacter(int characterId, int bagId) + { + var bag = await GetBagById(bagId); + if (bag == null) + throw new Exception("背包配置不存在"); + + var existing = await db.Queryable() + .FirstAsync(x => x.CharacterId == characterId); + + if (existing != null) + { + // 角色已有背包,更新背包配置 + existing.BagId = bagId; + await db.Updateable(existing).ExecuteCommandAsync(); + } + else + { + // 创建新的关联 + var characterBag = new CharacterBag + { + CharacterId = characterId, + BagId = bagId + }; + await db.Insertable(characterBag).ExecuteCommandAsync(); + } + + return true; + } + + // ============ 背包物品管理 ============ + + public async Task> GetBagItems(int characterBagId) + { + var characterBag = await db.Queryable() + .FirstAsync(x => x.Id == characterBagId); + + if (characterBag == null) + throw new Exception("角色背包不存在"); + + var bag = await GetBagById(characterBag.BagId); + + var bagItems = await db.Queryable() + .Where(x => x.CharacterBagId == characterBagId) + .ToListAsync(); + + var result = new List(); + foreach (var item in bagItems) + { + var dto = new BagItemDto + { + Id = item.Id, + CharacterBagId = item.CharacterBagId, + ItemType = (int)item.ItemType, + ItemId = item.ItemId, + Quantity = item.Quantity + }; + + if (item.ItemType == BagItemType.Equipment) + { + var equipment = await db.Queryable() + .FirstAsync(x => x.Id == item.ItemId); + dto.ItemName = equipment?.Name; + dto.ItemRarity = equipment != null ? (int)equipment.Rarity : null; + } + else if (item.ItemType == BagItemType.Pill) + { + var pill = await db.Queryable() + .FirstAsync(x => x.Id == item.ItemId); + dto.ItemName = pill?.Name; + } + + result.Add(dto); + } + + return result; + } + + public async Task AddItemToBag(int characterBagId, int itemType, int itemId, int quantity) + { + var characterBag = await db.Queryable() + .FirstAsync(x => x.Id == characterBagId); + + if (characterBag == null) + throw new Exception("角色背包不存在"); + + var bag = await GetBagById(characterBag.BagId); + if (bag == null) + throw new Exception("背包配置不存在"); + + var existingItem = await db.Queryable() + .FirstAsync(x => x.CharacterBagId == characterBagId + && x.ItemType == (BagItemType)itemType + && x.ItemId == itemId); + + if (existingItem != null) + { + existingItem.Quantity += quantity; + await db.Updateable(existingItem).ExecuteCommandAsync(); + } + else + { + var currentCount = await db.Queryable() + .Where(x => x.CharacterBagId == characterBagId) + .CountAsync(); + + if (currentCount >= bag.Capacity) + throw new Exception("背包已满"); + + var newItem = new BagItem + { + CharacterBagId = characterBagId, + ItemType = (BagItemType)itemType, + ItemId = itemId, + Quantity = quantity + }; + await db.Insertable(newItem).ExecuteCommandAsync(); + } + + return true; + } + + public async Task RemoveItemFromBag(int characterBagId, int bagItemId) + { + var item = await db.Queryable() + .FirstAsync(x => x.Id == bagItemId && x.CharacterBagId == characterBagId); + + if (item == null) + throw new Exception("物品不存在"); + + await db.Deleteable(item).ExecuteCommandAsync(); + return true; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/CharacterService.cs b/Build_God_Api/Build_God_Api/Services/CharacterService.cs new file mode 100644 index 0000000..9a0b9c6 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/CharacterService.cs @@ -0,0 +1,306 @@ +using Build_God_Api.DB; +using Build_God_Api.Dto; +using Build_God_Api.Services.Game; +using SqlSugar; +using System.Security.Cryptography; + +namespace Build_God_Api.Services +{ +public interface ICharacterService + { + public Task GetCharacterByAccountId(int accountId); + public Task> GetCharactersByAccountId(int accountId); + public Task> GetCharacterListWithDetails(int accountId); + public Task> GetAllCharacters(); + public Task GetCharacterById(int characterId); + + public Task ExistsByNameAsync(string name); + + public Task RegisterCharacter(CharacterRegisterDto character); + + public Task DeleteCharacter(int characterId); + + /// + /// 选择角色(更新最后登录时间) + /// + public Task SelectCharacter(int characterId); + + /// + /// 突破 + /// + /// + public Task Breakthrough(int characterId); + + /// + /// 开始打坐 + /// + public Task StartTraining(int characterId); + + /// + /// 停止打坐并结算经验 + /// + /// 获得的经验值 + public Task StopTraining(int characterId); + } + + public class CharacterService(ISqlSugarClient db, + ICurrentUserService currentUserService, + ICharacterAttributeCalculateService calculateService) : ICharacterService + { + private readonly ISqlSugarClient db = db; + private readonly ICurrentUserService currentUserService = currentUserService; + private readonly ICharacterAttributeCalculateService calculateService = calculateService; + + public async Task Breakthrough(int characterId) + { + var character = await db.Queryable().FirstAsync(x => x.Id == characterId) ?? throw new Exception("角色不存在"); + var currentLevel = await db.Queryable().FirstAsync(x => x.LevelId == character.LevelId); + var nextLevelId = currentLevel?.NextLevelId ?? 1; + var nextLevel = await db.Queryable().FirstAsync(x => x.LevelId == nextLevelId) ?? throw new Exception("你暂时无敌了,没有等级可以突破了"); + + if (character.CurrentExp < nextLevel.CurrentLevelMinExp) + { + throw new Exception($"距离可以突破你还差 {nextLevel.CurrentLevelMinExp - character.CurrentExp}"); + } + + if (currentLevel?.BaseBreakthroughRate <= 0) + { + throw new Exception("当前境界无法突破"); + } + + if (character.BreakthroughRate >= 100) + { + character.LevelId = nextLevelId; + character.BreakthroughRate = nextLevel.BaseBreakthroughRate; + await calculateService.CalculateAndUpdateAttributesAsync(character); + return true; + } + + decimal randomValue = GenerateSecureRandomDecimal(0, 100, 1); + bool isSuccess = randomValue < character.BreakthroughRate; + + if (isSuccess) + { + character.LevelId = nextLevelId; + character.BreakthroughRate = nextLevel.BaseBreakthroughRate; + await calculateService.CalculateAndUpdateAttributesAsync(character); + } + else + { + character.BreakthroughRate += currentLevel!.FailIncrement; + if (character.BreakthroughRate > 100) + { + character.BreakthroughRate = 100; + } + } + + await db.Updateable(character).ExecuteCommandAsync(); + return isSuccess; + } + + public async Task StartTraining(int characterId) + { + var character = await db.Queryable().FirstAsync(x => x.Id == characterId) + ?? throw new Exception("角色不存在"); + + if (character.TrainingOn.HasValue) + { + throw new Exception("你已经在打坐了"); + } + + character.TrainingOn = DateTime.Now; + await db.Updateable(character).ExecuteCommandAsync(); + return true; + } + + public async Task StopTraining(int characterId) + { + var character = await db.Queryable().FirstAsync(x => x.Id == characterId) + ?? throw new Exception("角色不存在"); + + if (!character.TrainingOn.HasValue) + { + throw new Exception("你还没有开始打坐"); + } + + var duration = DateTime.Now - character.TrainingOn.Value; + var seconds = (decimal)duration.TotalSeconds; + + // exp = 1 * seconds * level * level * (1 + buff) + var expGained = 1 * seconds * character.LevelId * character.LevelId * (1 + 0); + + character.CurrentExp += Math.Round(expGained); + character.TrainingOn = null; + await db.Updateable(character).ExecuteCommandAsync(); + + return expGained; + } + + private readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create(); + + /// + /// 生成安全的高精度随机小数(生产环境用) + /// + /// 最小值 + /// 最大值 + /// 小数位数 + /// 随机数 + private decimal GenerateSecureRandomDecimal(decimal min, decimal max, int decimalPlaces) + { + byte[] randomBytes = new byte[4]; + _rng.GetBytes(randomBytes); + int randomInt = BitConverter.ToInt32(randomBytes, 0); + + decimal randomDecimal = (decimal)Math.Abs(randomInt) / int.MaxValue; + decimal result = min + (randomDecimal * (max - min)); + + return Math.Round(result, decimalPlaces); + } + + public async Task DeleteCharacter(int characterId) + { + var item = await db.Queryable().FirstAsync(x => x.Id == characterId && x.AccountId == currentUserService.UserId) ?? throw new Exception("你没有权力这么做"); + item.isLocked = true; + await db.Updateable(item).ExecuteCommandAsync(); + return true; + } + + public async Task SelectCharacter(int characterId) + { + var character = await db.Queryable().FirstAsync(x => x.Id == characterId && x.AccountId == currentUserService.UserId && x.isLocked == false) + ?? throw new Exception("角色不存在"); + character.LastLogin = DateTime.Now; + await db.Updateable(character).ExecuteCommandAsync(); + return true; + } + + public async Task ExistsByNameAsync(string name) + { + return await db.Queryable().AnyAsync(x => x.Name == name); + } + +public async Task GetCharacterByAccountId(int accountId) + { + return await db.Queryable().FirstAsync(x => x.AccountId == accountId && x.isLocked == false); + } + + public async Task> GetCharactersByAccountId(int accountId) + { + return await db.Queryable() + .Where(x => x.AccountId == accountId && x.isLocked == false) + .OrderBy(x => x.CreatedOn) + .ToListAsync(); + } + + /// + /// 获取角色列表(包含境界和灵根信息) + /// + public async Task> GetCharacterListWithDetails(int accountId) + { + var characters = await db.Queryable() + .Where(x => x.AccountId == accountId && x.isLocked == false) + .OrderBy(x => x.CreatedOn) + .ToListAsync(); + + var result = new List(); + foreach (var c in characters) + { + var level = await db.Queryable().FirstAsync(x => x.LevelId == c.LevelId); + Level? nextLevel = null; + if (level?.NextLevelId.HasValue == true) + { + nextLevel = await db.Queryable().FirstAsync(x => x.LevelId == level.NextLevelId); + } + Profession? profession = null; + if (c.ProfessionId.HasValue && c.ProfessionId > 0) + { + profession = await db.Queryable().FirstAsync(x => x.Id == c.ProfessionId); + } + + bool canBreakthrough = false; + if (nextLevel != null && c.CurrentExp >= nextLevel.CurrentLevelMinExp) + { + canBreakthrough = true; + } + + result.Add(new CharacterDto + { + Id = c.Id, + Name = c.Name, + LevelName = level?.Name ?? "未知", + LevelId = c.LevelId, + MaxHP = c.MaxHP, + Attack = c.Attack, + ProfessionName = profession?.Name, + Money = c.Money, + CurrentExp = c.CurrentExp, + TrainingOn = c.TrainingOn, + NextLevelName = nextLevel?.Name, + NextLevelMinExp = nextLevel?.CurrentLevelMinExp, + BreakthroughRate = c.BreakthroughRate, + CanBreakthrough = canBreakthrough, + LastLogin = c.LastLogin, + CreatedOn = c.CreatedOn + }); + } + + return result; + } + + public async Task> GetAllCharacters() + { + return await db.Queryable().Where(x => x.isLocked == false).ToListAsync(); + } + + public async Task GetCharacterById(int characterId) + { + return await db.Queryable().FirstAsync(x => x.Id == characterId && x.isLocked == false); + } + + public async Task RegisterCharacter(CharacterRegisterDto character) + { + var bo = await ExistsByNameAsync(character.Name); + if (bo) + { + throw new Exception("已经有道友叫这个名字了,请想另外一个吊炸天的名字吧"); + } + + // 限制每个账号最多3个角色 + var existingCount = await db.Queryable() + .CountAsync(x => x.AccountId == currentUserService.UserId && x.isLocked == false); + if (existingCount >= 3) + { + throw new Exception("每个账号最多只能创建3个角色"); + } + + Character newOne = new() + { + Name = character.Name, + AccountId = currentUserService.UserId, + CurrentExp = 0, + LevelId = 1, + Money = 0, + MaxHP = 100, + CurrentHP = 100, + Attack = 0, + ProfessionId = character.ProfessionId, + SpiritFieldId = 0, + BreakthroughRate = 0, + LastLogin = DateTime.Now + }; + + await db.Insertable(newOne).ExecuteCommandAsync(); + + return true; + } + } + + /// + /// 注册角色 + /// + public class CharacterRegisterDto + { + public string Name { get; set; } = string.Empty; + public int ProfessionId { get; set; } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/ChatService.cs b/Build_God_Api/Build_God_Api/Services/ChatService.cs new file mode 100644 index 0000000..aaa14bf --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/ChatService.cs @@ -0,0 +1,61 @@ +using Build_God_Api.DB; +using SqlSugar; + +namespace Build_God_Api.Services +{ + public class ChatService(ISqlSugarClient db) : IChatService + { + public async Task> GetRecentMessagesAsync(int count = 100) + { + return await db.Queryable() + .OrderBy(x => x.CreatedAt) + .Take(count) + .ToListAsync(); + } + + public async Task AddMessageAsync(int? characterId, string characterName, string content, ChatMessageType type) + { + var message = new ChatMessage + { + CharacterId = characterId, + CharacterName = characterName, + Content = content, + Type = type, + CreatedAt = DateTime.Now + }; + + await db.Insertable(message).ExecuteCommandAsync(); + + await CleanupOldMessagesAsync(); + + return message; + } + + public async Task CleanupOldMessagesAsync(int maxCount = 1000, int daysToKeep = 7) + { + var cutoffDate = DateTime.Now.AddDays(-daysToKeep); + + var totalCount = await db.Queryable().CountAsync(); + + if (totalCount > maxCount) + { + var deleteCount = totalCount - maxCount; + + var oldMessages = await db.Queryable() + .OrderBy(x => x.CreatedAt) + .Take(deleteCount) + .ToListAsync(); + + if (oldMessages.Any()) + { + var ids = oldMessages.Select(x => x.Id).ToList(); + await db.Deleteable().In(ids).ExecuteCommandAsync(); + } + } + + await db.Deleteable() + .Where(x => x.CreatedAt < cutoffDate) + .ExecuteCommandAsync(); + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/CurrentUserService.cs b/Build_God_Api/Build_God_Api/Services/CurrentUserService.cs new file mode 100644 index 0000000..a78c151 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/CurrentUserService.cs @@ -0,0 +1,35 @@ +namespace Build_God_Api.Services +{ + public interface ICurrentUserService + { + int UserId { get; } + bool IsAdmin { get; } + } + + public class CurrentUserService(IHttpContextAccessor accessor) : ICurrentUserService + { + private readonly IHttpContextAccessor _accessor = accessor; + + public int UserId + { + get + { + var idClaim = _accessor.HttpContext?.User.FindFirst("Id")?.Value; + if (int.TryParse(idClaim, out var id)) + return id; + return 0; + } + } + + public bool IsAdmin + { + get + { + var role = _accessor.HttpContext?.User.FindFirst("Role")?.Value; + if (!string.IsNullOrEmpty(role)) + return role == "admin"; + return false; + } + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/EquipmentService.cs b/Build_God_Api/Build_God_Api/Services/EquipmentService.cs new file mode 100644 index 0000000..91704bc --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/EquipmentService.cs @@ -0,0 +1,95 @@ +using Build_God_Api.DB; +using Build_God_Api.Dto; +using SqlSugar; + +namespace Build_God_Api.Services +{ + public interface IEquipmentService + { + public Task Add(EquipmentTemplate item); + + public Task Add(List items); + + public Task Delete(int id); + + public Task Update(EquipmentTemplate item); + + public Task> GetAll(SearchEquipmentDto dto); + + public Task ExistsByNameAsync(string name); + } + + public class EquipmentService(ISqlSugarClient db,ICurrentUserService currentUserService) : IEquipmentService + { + private readonly ISqlSugarClient db = db; + private readonly ICurrentUserService currentUserService = currentUserService; + + public async Task Add(EquipmentTemplate item) + { + var bo = await this.ExistsByNameAsync(item.Name); + + if (bo) + { + throw new Exception($"已存在名为{item.Name}的装备"); + } + item.CreatedOn = DateTime.UtcNow; + item.CreatedBy = currentUserService.UserId; + await db.Insertable(item).ExecuteCommandAsync(); + return true; + } + + public async Task Add(List items) + { + var bo = await db.Queryable().Where(x => items.Any(i => x.Name == i.Name)).AnyAsync(); + if (bo) + { + throw new Exception("存在重复的装备名称,添加失败"); + } + foreach(var item in items) + { + item.CreatedOn = DateTime.UtcNow; + item.CreatedBy = currentUserService.UserId; + } + await db.Insertable(items).ExecuteCommandAsync(); + return true; + } + + public async Task Delete(int id) + { + var item = await db.Queryable().FirstAsync(x => x.Id == id) ?? throw new Exception("没找到对应的装备"); + await db.Deleteable(item).ExecuteCommandAsync(); + return true; + } + + public async Task ExistsByNameAsync(string name) + { + return await db.Queryable().AnyAsync(x => x.Name == name); + } + + public async Task> GetAll(SearchEquipmentDto dto) + { + var list = await db.Queryable().Skip((dto.PageNumber - 1) * dto.PageSize).Take(dto.PageSize) + .WhereIF(dto.EquipmentType != null, x => (int)x.Type == dto.EquipmentType) + .OrderBy(x=>x.CreatedOn) + .ToListAsync(); + var total = await db.Queryable() + .WhereIF(dto.EquipmentType != null, x => (int)x.Type == dto.EquipmentType).CountAsync(); + var result = new PagedResult + { + PageNumber = 1, + TotalCount = total, + Items = list + }; + return result; + } + + public async Task Update(EquipmentTemplate item) + { + var equipment = await db.Queryable().FirstAsync(x => x.Id == item.Id) ?? throw new Exception("没找到对应装备"); + item.UpdatedOn = DateTime.UtcNow; + item.UpdatedBy = currentUserService.UserId; + await db.Updateable(item).ExecuteCommandAsync(); + return true; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/Game/CharacterAttributeCalculateService.cs b/Build_God_Api/Build_God_Api/Services/Game/CharacterAttributeCalculateService.cs new file mode 100644 index 0000000..adb4991 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/Game/CharacterAttributeCalculateService.cs @@ -0,0 +1,43 @@ +using Build_God_Api.DB; +using SqlSugar; + +namespace Build_God_Api.Services.Game +{ + /// + /// 角色属性计算服务 + /// + public interface ICharacterAttributeCalculateService + { + Task CalculateAndUpdateAttributesAsync(Character character); + } + + public class CharacterAttributeCalculateService(ISqlSugarClient context): ICharacterAttributeCalculateService + { + private readonly ISqlSugarClient _context = context; + + public async Task CalculateAndUpdateAttributesAsync(Character character) + { + Profession? profession = null; + if (character.ProfessionId.HasValue && character.ProfessionId > 0) + { + profession = await _context.Queryable().FirstAsync(x => x.Id == character.ProfessionId); + } + + // 无职业则默认系数为1 + decimal attackRate = profession?.AttackRate ?? 1m; + decimal defendRate = profession?.DefendRate ?? 1m; + decimal healthRate = profession?.HealthRate ?? 1m; + decimal criticalRate = profession?.CriticalRate ?? 1m; + + // 血量 = 基础值(100) + 等级 * 经验 * 5 * 生命系数 + character.MaxHP = 100 + character.LevelId * character.CurrentExp * 5 * healthRate; + + // 攻击力 = 基础值(10) + 等级 * 经验 * 2 * 攻击系数 + character.Attack = 10 + character.LevelId * character.CurrentExp * 2 * attackRate; + + await _context.Updateable(character).ExecuteCommandAsync(); + + return true; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/Game/TrainingService.cs b/Build_God_Api/Build_God_Api/Services/Game/TrainingService.cs new file mode 100644 index 0000000..f7c7d27 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/Game/TrainingService.cs @@ -0,0 +1,66 @@ +using Build_God_Api.DB; +using SqlSugar; + +namespace Build_God_Api.Services.Game +{ + /// + /// 角色修炼服务 + /// + public interface ITrainingService + { + /// + /// 开始修炼 + /// + /// + Task StartTraining(int characterId); + + Task EndTraining(int characterId); + + Task IsTraining(int characterId); + } + + public class TrainingService(ISqlSugarClient context,ICharacterAttributeCalculateService characterAttributeCalculateService) : ITrainingService + { + private readonly ISqlSugarClient _context = context; + private readonly ICharacterAttributeCalculateService characterAttributeCalculateService = characterAttributeCalculateService; + + public async Task StartTraining(int characterId) + { + var character = await _context.Queryable().FirstAsync(x => x.Id == characterId) ?? throw new Exception("意料之外的角色ID"); + + var isTraining = await IsTraining(characterId); + if (isTraining) + { + return false; + } + + character.TrainingOn = DateTime.UtcNow; + return true; + } + + public async Task IsTraining(int characterId) + { + var character = await _context.Queryable().FirstAsync(x => x.Id == characterId); + return character?.TrainingOn != null; + } + + public async Task EndTraining(int characterId) + { + var character = await _context.Queryable().FirstAsync(x => x.Id == characterId) ?? throw new Exception("意料之外的角色ID"); + var isTraining = await IsTraining(characterId); + if (!isTraining) + { + return false; + } + + var totalTime = DateTime.UtcNow - character.TrainingOn!.Value; + //基础修炼加成 + var exp = (decimal)totalTime.TotalMinutes; + character.CurrentExp += exp; + character.TrainingOn = null; + await characterAttributeCalculateService.CalculateAndUpdateAttributesAsync(character); + + return true; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/IChatService.cs b/Build_God_Api/Build_God_Api/Services/IChatService.cs new file mode 100644 index 0000000..3c15e43 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/IChatService.cs @@ -0,0 +1,11 @@ +using Build_God_Api.DB; + +namespace Build_God_Api.Services +{ + public interface IChatService + { + Task> GetRecentMessagesAsync(int count = 100); + Task AddMessageAsync(int? characterId, string characterName, string content, ChatMessageType type); + Task CleanupOldMessagesAsync(int maxCount = 1000, int daysToKeep = 7); + } +} diff --git a/Build_God_Api/Build_God_Api/Services/LevelService.cs b/Build_God_Api/Build_God_Api/Services/LevelService.cs new file mode 100644 index 0000000..2294769 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/LevelService.cs @@ -0,0 +1,63 @@ +using Build_God_Api.DB; +using SqlSugar; + +namespace Build_God_Api.Services +{ + public interface ILevelService + { + public Task Add(Level item); + + public Task Delete(int id); + + public Task Update(Level item); + + public Task> GetAll(); + + public Task ExistsByNameAsync(string name); + } + + public class LevelService(ISqlSugarClient db, ICurrentUserService currentUserService) : ILevelService + { + private readonly ISqlSugarClient db = db; + private readonly ICurrentUserService currentUserService = currentUserService; + + public async Task Add(Level item) + { + var bo = await this.ExistsByNameAsync(item.Name); + if (bo) + { + throw new Exception($"已经添加过名为{item.Name}的境界"); + } + item.CreatedOn = DateTime.UtcNow; + item.CreatedBy = currentUserService.UserId; + await db.Insertable(item).ExecuteCommandAsync(); + return true; + } + + public async Task Delete(int id) + { + var item = await db.Queryable().FirstAsync(x => x.Id == id) ?? throw new Exception("没有找到对应境界"); + await db.Deleteable(item).ExecuteCommandAsync(); + return true; + } + + public async Task ExistsByNameAsync(string name) + { + return await db.Queryable().AnyAsync(x => x.Name == name); + } + + public async Task> GetAll() + { + return await db.Queryable().OrderBy(x => x.LevelId).ToListAsync(); + } + + public async Task Update(Level item) + { + var level = await db.Queryable().FirstAsync(x => x.Id == item.Id) ?? throw new Exception("没有找到对应境界"); + item.UpdatedOn = DateTime.UtcNow; + item.UpdatedBy = currentUserService.UserId; + await db.Updateable(item).ExecuteCommandAsync(); + return true; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/MissionProgressService.cs b/Build_God_Api/Build_God_Api/Services/MissionProgressService.cs new file mode 100644 index 0000000..469f001 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/MissionProgressService.cs @@ -0,0 +1,129 @@ +using Build_God_Api.DB; +using SqlSugar; + +namespace Build_God_Api.Services +{ + public interface IMissionProgressService + { + // 任务进度配置管理 + Task> GetByMissionId(int missionId); + Task GetById(int id); + Task Create(MissionProgress progress); + Task Update(MissionProgress progress); + Task Delete(int id); + + // 角色任务进度 + Task> GetCharacterProgress(int characterId, int missionId); + Task UpdateCharacterProgress(int characterId, int missionProgressId, int count); + Task InitCharacterProgress(int characterId, int missionId); + } + + public class MissionProgressService(ISqlSugarClient db) : IMissionProgressService + { + private readonly ISqlSugarClient db = db; + + // ============ 任务进度配置管理 ============ + + public async Task> GetByMissionId(int missionId) + { + return await db.Queryable() + .Where(x => x.MissionId == missionId) + .ToListAsync(); + } + + public async Task GetById(int id) + { + return await db.Queryable() + .FirstAsync(x => x.Id == id); + } + + public async Task Create(MissionProgress progress) + { + await db.Insertable(progress).ExecuteCommandAsync(); + return true; + } + + public async Task Update(MissionProgress progress) + { + var existing = await GetById(progress.Id); + if (existing == null) + throw new Exception("进度配置不存在"); + + existing.TargetType = progress.TargetType; + existing.TargetItemId = progress.TargetItemId; + existing.TargetItemName = progress.TargetItemName; + existing.TargetCount = progress.TargetCount; + existing.Description = progress.Description; + + await db.Updateable(existing).ExecuteCommandAsync(); + return true; + } + + public async Task Delete(int id) + { + await db.Deleteable().Where(x => x.Id == id).ExecuteCommandAsync(); + return true; + } + + // ============ 角色任务进度 ============ + + public async Task> GetCharacterProgress(int characterId, int missionId) + { + return await db.Queryable() + .Where(x => x.CharacterId == characterId && x.MissionId == missionId) + .ToListAsync(); + } + + public async Task UpdateCharacterProgress(int characterId, int missionProgressId, int count) + { + var progress = await db.Queryable() + .FirstAsync(x => x.CharacterId == characterId && x.MissionProgressId == missionProgressId); + + if (progress == null) + throw new Exception("角色进度不存在"); + + var missionProgress = await GetById(missionProgressId); + if (missionProgress == null) + throw new Exception("进度配置不存在"); + + progress.CurrentCount = count; + progress.IsCompleted = count >= missionProgress.TargetCount; + progress.UpdatedOn = DateTime.UtcNow; + + await db.Updateable(progress).ExecuteCommandAsync(); + return true; + } + + public async Task InitCharacterProgress(int characterId, int missionId) + { + // 检查是否已经初始化过 + var existing = await db.Queryable() + .AnyAsync(x => x.CharacterId == characterId && x.MissionId == missionId); + + if (existing) + return true; + + // 获取任务的进度配置 + var progresses = await GetByMissionId(missionId); + if (!progresses.Any()) + return true; // 没有进度配置,直接返回 + + // 为每个进度配置创建角色进度记录 + foreach (var progress in progresses) + { + var characterProgress = new CharacterMissionProgress + { + CharacterId = characterId, + MissionId = missionId, + MissionProgressId = progress.Id, + CurrentCount = 0, + IsCompleted = false, + UpdatedOn = DateTime.UtcNow + }; + await db.Insertable(characterProgress).ExecuteCommandAsync(); + } + + return true; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/MissionService.cs b/Build_God_Api/Build_God_Api/Services/MissionService.cs new file mode 100644 index 0000000..d0be340 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/MissionService.cs @@ -0,0 +1,166 @@ +using Build_God_Api.DB; +using Build_God_Api.Dto; +using SqlSugar; + +namespace Build_God_Api.Services +{ + public interface IMissionService + { + public Task Add(Mission item); + + public Task Delete(int id); + + public Task Update(Mission item); + + public Task> GetAll(SearchMissionDto dto); + + public Task ExistsByNameAsync(string name); + + public Task AddReward(MissionReward reward); + + public Task UpdateReward(MissionReward reward); + } + + public class MissionService(ISqlSugarClient db,ICurrentUserService currentUserService) : IMissionService + { + private readonly ISqlSugarClient db = db; + private readonly ICurrentUserService currentUserService = currentUserService; + + public async Task Add(Mission item) + { + var bo = await this.ExistsByNameAsync(item.Name); + if (bo) + { + throw new Exception($"已经创建过名为{item.Name}的任务"); + } + item.CreatedOn = DateTime.UtcNow; + item.CreatedBy = currentUserService.UserId; + await db.Insertable(item).ExecuteCommandAsync(); + return true; + } + + public async Task AddReward(MissionReward reward) + { + var item = await db.Queryable().FirstAsync(x => x.Id == reward.MissionId) ?? throw new Exception("没找到对应任务"); + var oldOne = await db.Queryable() + .FirstAsync(x => x.MissionId == reward.MissionId && x.RewardType == reward.RewardType && x.ItemId == reward.ItemId); + if (oldOne != null) + { + throw new Exception("已经添加过相同的奖励了"); + } + var newOne = new MissionReward + { + MissionId = reward.MissionId, + RewardType = reward.RewardType, + ItemId = reward.ItemId, + Count = reward.Count + }; + await db.Insertable(newOne).ExecuteCommandAsync(); + return true; + } + + public async Task Delete(int id) + { + var item = await db.Queryable().FirstAsync(x => x.Id == id) ?? throw new Exception("没找到对应的任务"); + await db.DeleteNav(item).Include(x => x.Rewards).Include(x => x.Progresses).ExecuteCommandAsync(); + return true; + } + + public Task ExistsByNameAsync(string name) + { + return db.Queryable().AnyAsync(x => x.Name == name); + } + + public async Task> GetAll(SearchMissionDto dto) + { + var list = await db.Queryable() + .Skip((dto.PageNumber - 1) * dto.PageSize) + .Take(dto.PageSize) + .WhereIF(dto.MissionType != null, x => (int)x.Type == dto.MissionType) + .OrderBy(x => x.CreatedOn) + .Includes(x => x.Rewards) + .Includes(x => x.Progresses) + .ToListAsync(); + + foreach(var mission in list) + { + if (mission.Rewards != null) + { + foreach(var reward in mission.Rewards) + { + switch (reward.RewardType) + { + case RewardType.Equipment: + var eq = await db.Queryable().FirstAsync(x => x.Id == reward.ItemId); + reward.ItemName = eq.Name; + break; + case RewardType.Pill: + var pill = await db.Queryable().FirstAsync(x => x.Id == reward.ItemId); + reward.ItemName = pill.Name; + break; + default: + reward.ItemName = string.Empty; + break; + } + } + } + + if (mission.Progresses != null) + { + foreach (var progress in mission.Progresses) + { + switch (progress.TargetType) + { + case ProgressTargetType.CollectItem: + case ProgressTargetType.ConsumeItem: + var pill = await db.Queryable().FirstAsync(x => x.Id == progress.TargetItemId); + progress.TargetItemName = pill?.Name; + break; + default: + break; + } + } + } + } + + var total = await db.Queryable() + .WhereIF(dto.MissionType != null, x => (int)x.Type == dto.MissionType) + .CountAsync(); + + var result = new PagedResult + { + PageNumber = 1, + TotalCount = total, + Items = list + }; + return result; + } + + public async Task Update(Mission item) + { + var task = await db.Queryable().FirstAsync(x => x.Id == item.Id) ?? throw new Exception("没找到对应任务"); + item.UpdatedBy = currentUserService.UserId; + item.UpdatedOn = DateTime.UtcNow; + await db.Updateable(item).ExecuteCommandAsync(); + return true; + } + + public async Task UpdateReward(MissionReward reward) + { + var task = await db.Queryable() + .FirstAsync(x => x.Id == reward.MissionId) ?? throw new Exception("没找到对应任务"); + + var oldOne = await db.Queryable() + .FirstAsync(x => x.MissionId == reward.MissionId + && x.RewardType == reward.RewardType) ?? throw new Exception("没有找到对应的奖励"); + + oldOne.Count = reward.Count; + oldOne.ItemId = reward.ItemId; + oldOne.UpdatedBy = currentUserService.UserId; + oldOne.UpdatedOn = DateTime.UtcNow; + + await db.Updateable(oldOne).ExecuteCommandAsync(); + return true; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/PillService.cs b/Build_God_Api/Build_God_Api/Services/PillService.cs new file mode 100644 index 0000000..7dfbb18 --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/PillService.cs @@ -0,0 +1,63 @@ +using Build_God_Api.DB; +using SqlSugar; + +namespace Build_God_Api.Services +{ + public interface IPillService + { + public Task Add(Pill item); + + public Task Delete(int id); + + public Task Update(Pill item); + + public Task> GetAll(); + + public Task ExistsByNameAsync(string name); + } + + public class PillService(ISqlSugarClient db, ICurrentUserService currentUserService) : IPillService + { + private readonly ISqlSugarClient db = db; + private readonly ICurrentUserService currentUserService = currentUserService; + + public async Task Add(Pill item) + { + var bo = await ExistsByNameAsync(item.Name); + if (bo) + { + throw new Exception($"已经添加过名为{item.Name}的丹药"); + } + item.CreatedOn = DateTime.Now; + item.CreatedBy = currentUserService.UserId; + await db.Insertable(item).ExecuteCommandAsync(); + return true; + } + + public async Task Delete(int id) + { + var item = await db.Queryable().FirstAsync(x => x.Id == id) ?? throw new Exception("没有找到对应丹药"); + await db.Deleteable(item).ExecuteCommandAsync(); + return true; + } + + public async Task ExistsByNameAsync(string name) + { + return await db.Queryable().AnyAsync(x => x.Name == name); + } + + public async Task> GetAll() + { + return await db.Queryable().ToListAsync(); + } + + public async Task Update(Pill item) + { + var pill = await db.Queryable().FirstAsync(x => x.Id == item.Id) ?? throw new Exception("没有找到对应丹药"); + item.UpdatedOn = DateTime.UtcNow; + item.UpdatedBy = currentUserService.UserId; + await db.Updateable(item).ExecuteCommandAsync(); + return true; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/SpiritService.cs b/Build_God_Api/Build_God_Api/Services/SpiritService.cs new file mode 100644 index 0000000..7249edf --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/SpiritService.cs @@ -0,0 +1,66 @@ +using Build_God_Api.DB; +using SqlSugar; + +namespace Build_God_Api.Services +{ + public interface IProfessionService + { + public Task Add(Profession item); + + public Task Delete(int id); + + public Task Update(Profession item); + + public Task> GetAll(); + + public Task ExistsByNameAsync(string name); + } + + /// + /// 职业服务 + /// + public class ProfessionService(ISqlSugarClient db,ICurrentUserService currentUserService) : IProfessionService + { + private readonly ISqlSugarClient db = db; + private readonly ICurrentUserService currentUserService = currentUserService; + + public async Task Add(Profession item) + { + var bo = await this.ExistsByNameAsync(item.Name); + if (bo) + { + throw new Exception($"已添加名为{item.Name}的职业"); + } + item.CreatedOn = DateTime.Now; + item.CreatedBy = currentUserService.UserId; + await db.Insertable(item).ExecuteCommandAsync(); + return true; + } + + public async Task Delete(int id) + { + var item = await db.Queryable().FirstAsync(x => x.Id == id) ?? throw new Exception("没找到对应的职业"); + await db.Deleteable(item).ExecuteCommandAsync(); + return true; + } + + public async Task ExistsByNameAsync(string name) + { + return await db.Queryable().AnyAsync(x => x.Name == name); + } + + public async Task> GetAll() + { + return await db.Queryable().ToListAsync(); + } + + public async Task Update(Profession item) + { + var profession = await db.Queryable().FirstAsync(x => x.Id == item.Id) ?? throw new Exception("没找到对应的职业"); + item.UpdatedOn = DateTime.UtcNow; + item.UpdatedBy = currentUserService.UserId; + await db.Updateable(item).ExecuteCommandAsync(); + return true; + } + } +} diff --git a/Build_God_Api/Build_God_Api/Services/StatisticsService.cs b/Build_God_Api/Build_God_Api/Services/StatisticsService.cs new file mode 100644 index 0000000..c6ff68c --- /dev/null +++ b/Build_God_Api/Build_God_Api/Services/StatisticsService.cs @@ -0,0 +1,56 @@ +using Build_God_Api.DB; +using SqlSugar; + +namespace Build_God_Api.Services +{ + public interface IStatisticsService + { + Task GetSummary(); + } + + public class StatisticsSummary + { + public int EquipmentCount { get; set; } + public int LevelCount { get; set; } + public int ProfessionCount { get; set; } + public int PillCount { get; set; } + public int MissionCount { get; set; } + public Dictionary EquipmentByRarity { get; set; } = new(); + public Dictionary MissionByDifficulty { get; set; } = new(); + } + + public class StatisticsService(ISqlSugarClient db) : IStatisticsService + { + private readonly ISqlSugarClient db = db; + + public async Task GetSummary() + { + var equipmentCount = await db.Queryable().CountAsync(); + var levelCount = await db.Queryable().CountAsync(); + var professionCount = await db.Queryable().CountAsync(); + var pillCount = await db.Queryable().CountAsync(); + var missionCount = await db.Queryable().CountAsync(); + + var equipmentByRarity = await db.Queryable() + .GroupBy(x => x.Rarity) + .Select(x => new { Rarity = x.Rarity, Count = SqlFunc.AggregateCount(x.Id) }) + .ToListAsync(); + + var missionByDifficulty = await db.Queryable() + .GroupBy(x => x.Difficulty) + .Select(x => new { Difficulty = x.Difficulty, Count = SqlFunc.AggregateCount(x.Id) }) + .ToListAsync(); + + return new StatisticsSummary + { + EquipmentCount = equipmentCount, + LevelCount = levelCount, + ProfessionCount = professionCount, + PillCount = pillCount, + MissionCount = missionCount, + EquipmentByRarity = equipmentByRarity.ToDictionary(x => (int)x.Rarity, x => x.Count), + MissionByDifficulty = missionByDifficulty.ToDictionary(x => (int)x.Difficulty, x => x.Count) + }; + } + } +} diff --git a/Build_God_Api/Build_God_Api/appsettings.json b/Build_God_Api/Build_God_Api/appsettings.json new file mode 100644 index 0000000..e18d96f --- /dev/null +++ b/Build_God_Api/Build_God_Api/appsettings.json @@ -0,0 +1,17 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "AllowedOrigins": "http://localhost:5174,http://localhost:5173", + "connection": "Host=47.105.124.101;Port=5432;Database=god;Username=God;Password=build_god.123;", + "JWT": { + "SecretKey": "If_you_have_a_dream_you_got_to_protect_it", + "Issuer": "BuildGod", + "Expires": 240, + "Audience": "ob" + } +} diff --git a/Build_God_Game/.gitignore b/Build_God_Game/.gitignore new file mode 100644 index 0000000..2b82f4f --- /dev/null +++ b/Build_God_Game/.gitignore @@ -0,0 +1,49 @@ +# 依赖包(npm/yarn/pnpm 安装的包) +node_modules/ +# 包管理锁文件(可选忽略,建议保留 pnpm-lock.yaml/yarn.lock,忽略 package-lock.json 避免冲突) +package-lock.json +yarn.lock +pnpm-lock.yaml +# 编译/构建产物 +dist/ +build/ +# Vite 特有临时文件 +.vite/ +# Vue CLI 特有配置/缓存 +.vue-cli-service/ +# 环境变量文件(存放本地开发的环境变量,包含敏感信息如接口密钥) +.env +.env.local +.env.*.local +# 日志文件 +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +# IDE/编辑器配置(根据你使用的工具选择) +.idea/ # WebStorm/IntelliJ +.vscode/ # VS Code(可保留 settings.json,排除其他) +!.vscode/extensions.json +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +.DS_Store # Mac 系统隐藏文件 +Thumbs.db # Windows 系统缩略图文件 +# 本地缓存/临时文件 +*.swp +*.swo +tmp/ +temp/ +# 测试相关(如有单元测试/端到端测试) +coverage/ # 测试覆盖率报告 +cypress/videos/ +cypress/screenshots/ +# 打包压缩文件 +*.zip +*.tar.gz +*.rar +# TypeScript 编译产物(如果用 TS 开发) +*.tsbuildinfo +types/ +# pnpm 存储目录 +.pnpm-store/ \ No newline at end of file diff --git a/Build_God_Game/.vs/Build_God_Game/CopilotIndices/17.14.1204.46620/CodeChunks.db b/Build_God_Game/.vs/Build_God_Game/CopilotIndices/17.14.1204.46620/CodeChunks.db new file mode 100644 index 0000000..f3738eb Binary files /dev/null and b/Build_God_Game/.vs/Build_God_Game/CopilotIndices/17.14.1204.46620/CodeChunks.db differ diff --git a/Build_God_Game/.vs/Build_God_Game/CopilotIndices/17.14.1204.46620/SemanticSymbols.db b/Build_God_Game/.vs/Build_God_Game/CopilotIndices/17.14.1204.46620/SemanticSymbols.db new file mode 100644 index 0000000..18a51b3 Binary files /dev/null and b/Build_God_Game/.vs/Build_God_Game/CopilotIndices/17.14.1204.46620/SemanticSymbols.db differ diff --git a/Build_God_Game/.vs/Build_God_Game/FileContentIndex/2099cff6-a572-4dd2-99fc-659228ba18be.vsidx b/Build_God_Game/.vs/Build_God_Game/FileContentIndex/2099cff6-a572-4dd2-99fc-659228ba18be.vsidx new file mode 100644 index 0000000..06fc80a Binary files /dev/null and b/Build_God_Game/.vs/Build_God_Game/FileContentIndex/2099cff6-a572-4dd2-99fc-659228ba18be.vsidx differ diff --git a/Build_God_Game/.vs/Build_God_Game/FileContentIndex/440a4ccf-fbf1-4ea3-b521-123cfbcd8f11.vsidx b/Build_God_Game/.vs/Build_God_Game/FileContentIndex/440a4ccf-fbf1-4ea3-b521-123cfbcd8f11.vsidx new file mode 100644 index 0000000..94a536f Binary files /dev/null and b/Build_God_Game/.vs/Build_God_Game/FileContentIndex/440a4ccf-fbf1-4ea3-b521-123cfbcd8f11.vsidx differ diff --git a/Build_God_Game/.vs/Build_God_Game/v17/.wsuo b/Build_God_Game/.vs/Build_God_Game/v17/.wsuo new file mode 100644 index 0000000..deb1eef Binary files /dev/null and b/Build_God_Game/.vs/Build_God_Game/v17/.wsuo differ diff --git a/Build_God_Game/.vs/Build_God_Game/v17/DocumentLayout.backup.json b/Build_God_Game/.vs/Build_God_Game/v17/DocumentLayout.backup.json new file mode 100644 index 0000000..b2ea2a9 --- /dev/null +++ b/Build_God_Game/.vs/Build_God_Game/v17/DocumentLayout.backup.json @@ -0,0 +1,63 @@ +{ + "Version": 1, + "WorkspaceRootPath": "D:\\Test\\Build_God\\Build_God_Game\\", + "Documents": [], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:129:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:128:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:136:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:135:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:134:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:133:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:132:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:131:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:130:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:131:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{1c4feeaa-4718-4aa9-859d-94ce25d182ba}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/Build_God_Game/.vs/Build_God_Game/v17/DocumentLayout.json b/Build_God_Game/.vs/Build_God_Game/v17/DocumentLayout.json new file mode 100644 index 0000000..8f3f5ce --- /dev/null +++ b/Build_God_Game/.vs/Build_God_Game/v17/DocumentLayout.json @@ -0,0 +1,59 @@ +{ + "Version": 1, + "WorkspaceRootPath": "D:\\Test\\Build_God\\Build_God_Game\\", + "Documents": [], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 200, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:129:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:128:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:136:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:135:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:134:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:133:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + }, + { + "$type": "Bookmark", + "Name": "ST:132:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:131:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:130:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:131:0:{116d2292-e37d-41cd-a077-ebacac4c8cc4}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/Build_God_Game/.vs/ProjectSettings.json b/Build_God_Game/.vs/ProjectSettings.json new file mode 100644 index 0000000..f8b4888 --- /dev/null +++ b/Build_God_Game/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/Build_God_Game/.vs/VSWorkspaceState.json b/Build_God_Game/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..6b61141 --- /dev/null +++ b/Build_God_Game/.vs/VSWorkspaceState.json @@ -0,0 +1,6 @@ +{ + "ExpandedNodes": [ + "" + ], + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/Build_God_Game/.vs/slnx.sqlite b/Build_God_Game/.vs/slnx.sqlite new file mode 100644 index 0000000..a074508 Binary files /dev/null and b/Build_God_Game/.vs/slnx.sqlite differ diff --git a/Build_God_Game/.vscode/extensions.json b/Build_God_Game/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/Build_God_Game/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/Build_God_Game/README.md b/Build_God_Game/README.md new file mode 100644 index 0000000..33895ab --- /dev/null +++ b/Build_God_Game/README.md @@ -0,0 +1,5 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/Build_God_Game/package.json b/Build_God_Game/package.json new file mode 100644 index 0000000..d8c21ad --- /dev/null +++ b/Build_God_Game/package.json @@ -0,0 +1,37 @@ +{ + "name": "build-god-game", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@microsoft/signalr": "^10.0.0", + "@vueuse/core": "^14.2.1", + "axios": "^1.13.6", + "element-plus": "^2.13.5", + "jwt-decode": "^4.0.0", + "motion": "^12.35.2", + "ogl": "^1.0.11", + "pinia": "^3.0.4", + "three": "^0.183.2", + "vue": "^3.5.25", + "vue-router": "^5.0.3" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.2.1", + "@types/node": "^24.10.1", + "@types/three": "^0.183.1", + "@vitejs/plugin-vue": "^6.0.2", + "@vue/tsconfig": "^0.8.1", + "autoprefixer": "^10.4.27", + "postcss": "^8.5.8", + "tailwindcss": "^4.2.1", + "typescript": "~5.9.3", + "vite": "^7.3.1", + "vue-tsc": "^3.1.5" + } +} diff --git a/Build_God_Game/public/vite.svg b/Build_God_Game/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/Build_God_Game/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Build_God_Game/src/App.vue b/Build_God_Game/src/App.vue new file mode 100644 index 0000000..c048298 --- /dev/null +++ b/Build_God_Game/src/App.vue @@ -0,0 +1,6 @@ + + + diff --git a/Build_God_Game/src/api/auth.ts b/Build_God_Game/src/api/auth.ts new file mode 100644 index 0000000..527011d --- /dev/null +++ b/Build_God_Game/src/api/auth.ts @@ -0,0 +1,61 @@ +import axios from 'axios' + +const API_BASE_URL = '/api' + +const instance = axios.create({ + baseURL: API_BASE_URL, + timeout: 10000, + headers: { + 'Content-Type': 'application/json' + } +}) + +instance.interceptors.request.use( + (config) => { + const token = localStorage.getItem('auth_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => Promise.reject(error) +) + +instance.interceptors.response.use( + (response) => response.data, + (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('auth_token') + localStorage.removeItem('user') + window.location.href = '/login' + } + return Promise.reject(error.response?.data || error.message) + } +) + +export interface LoginRequest { + name: string + password: string +} + +export interface RegisterRequest { + name: string + email: string + password: string +} + +export interface AuthResponse { + token: string +} + +export const authApi = { + login: (data: LoginRequest): Promise => { + return instance.post('/account/login', data) + }, + + register: (data: RegisterRequest): Promise => { + return instance.post('/account/register', data) + } +} + +export default instance diff --git a/Build_God_Game/src/api/character.ts b/Build_God_Game/src/api/character.ts new file mode 100644 index 0000000..47cfec5 --- /dev/null +++ b/Build_God_Game/src/api/character.ts @@ -0,0 +1,105 @@ +import axios from 'axios' + +const API_BASE_URL = '/api' +const API_GOD_URL = '/api/god' + +const instance = axios.create({ + baseURL: API_BASE_URL, + timeout: 10000, + headers: { + 'Content-Type': 'application/json' + } +}) + +instance.interceptors.request.use( + (config) => { + const token = localStorage.getItem('auth_token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => Promise.reject(error) +) + +instance.interceptors.response.use( + (response) => response.data, + (error) => { + if (error.response?.status === 401) { + localStorage.removeItem('auth_token') + localStorage.removeItem('user') + window.location.href = '/login' + } + return Promise.reject(error.response?.data || error.message) + } +) + +export interface CharacterDto { + id: number + name: string + levelName: string + levelId: number + maxHP: number + attack: number + professionName?: string + money: number + currentExp: number + trainingOn?: string + nextLevelName?: string + nextLevelMinExp?: number + breakthroughRate: number + canBreakthrough: boolean + lastLogin: string + createdOn: string +} + +export interface ProfessionDto { + id: number + name: string + description: string + attackRate: number + defendRate: number + healthRate: number + criticalRate: number +} + +export interface CreateCharacterRequest { + name: string + professionId: number +} + +export const characterApi = { + getCharacterList: (): Promise => { + return instance.get('/character/list') + }, + + createCharacter: (data: CreateCharacterRequest): Promise => { + return instance.post('/character/register', data) + }, + + deleteCharacter: (characterId: number): Promise => { + return instance.delete(`/character/${characterId}`) + }, + + selectCharacter: (characterId: number): Promise => { + return instance.post(`/character/select/${characterId}`) + }, + + getProfessions: (): Promise => { + return instance.get(`/profession/all`) + }, + + startTraining: (characterId: number): Promise => { + return instance.post(`/character/${characterId}/training/start`) + }, + + stopTraining: (characterId: number): Promise => { + return instance.post(`/character/${characterId}/training/stop`) + }, + + breakthrough: (characterId: number): Promise => { + return instance.post(`/character/${characterId}/breakthrough`) + } +} + +export default instance diff --git a/Build_God_Game/src/assets/vue.svg b/Build_God_Game/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/Build_God_Game/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Build_God_Game/src/components/ChatBox.vue b/Build_God_Game/src/components/ChatBox.vue new file mode 100644 index 0000000..78d5c63 --- /dev/null +++ b/Build_God_Game/src/components/ChatBox.vue @@ -0,0 +1,338 @@ + + + + + diff --git a/Build_God_Game/src/components/GlareHover/GlareHover.vue b/Build_God_Game/src/components/GlareHover/GlareHover.vue new file mode 100644 index 0000000..f518c9e --- /dev/null +++ b/Build_God_Game/src/components/GlareHover/GlareHover.vue @@ -0,0 +1,113 @@ + + + diff --git a/Build_God_Game/src/components/GooeyNav/GooeyNav.vue b/Build_God_Game/src/components/GooeyNav/GooeyNav.vue new file mode 100644 index 0000000..1cee48e --- /dev/null +++ b/Build_God_Game/src/components/GooeyNav/GooeyNav.vue @@ -0,0 +1,389 @@ + + + + + diff --git a/Build_God_Game/src/components/Particles/Particles.vue b/Build_God_Game/src/components/Particles/Particles.vue new file mode 100644 index 0000000..ed70345 --- /dev/null +++ b/Build_God_Game/src/components/Particles/Particles.vue @@ -0,0 +1,347 @@ + + + + + diff --git a/Build_God_Game/src/composables/useChat.ts b/Build_God_Game/src/composables/useChat.ts new file mode 100644 index 0000000..5994852 --- /dev/null +++ b/Build_God_Game/src/composables/useChat.ts @@ -0,0 +1,106 @@ +import { ref, onUnmounted } from 'vue' +import * as signalR from '@microsoft/signalr' +import { useAuthStore } from '@/stores/auth' + +export interface ChatMessage { + id: number + characterId: number | null + characterName: string + content: string + type: 0 | 1 | 2 + createdAt: string +} + +export function useChat() { + const authStore = useAuthStore() + const messages = ref([]) + const isConnected = ref(false) + const connection = ref(null) + const isLoading = ref(false) + + const API_URL = 'http://localhost:5091' + + const connect = async () => { + if (connection.value) { + return + } + + const token = authStore.token + + connection.value = new signalR.HubConnectionBuilder() + .withUrl(`${API_URL}/hubs/chat`, { + accessTokenFactory: () => token, + }) + .withAutomaticReconnect() + .configureLogging(signalR.LogLevel.Information) + .build() + + connection.value.on('ReceiveMessage', (message: ChatMessage) => { + messages.value.push(message) + if (messages.value.length > 100) { + messages.value = messages.value.slice(0, 100) + } + }) + + try { + await connection.value.start() + isConnected.value = true + const history = await connection.value.invoke('GetRecentMessages', 100) + messages.value = history + } catch (err) { + console.error('SignalR connection failed:', err) + isConnected.value = false + } + + connection.value.onclose(() => { + isConnected.value = false + }) + + connection.value.onreconnecting(() => { + isConnected.value = false + }) + + connection.value.onreconnected(() => { + isConnected.value = true + }) + } + + const disconnect = async () => { + if (connection.value) { + await connection.value.stop() + connection.value = null + isConnected.value = false + } + } + + const sendMessage = async (content: string, type: 0 | 1 | 2 = 0) => { + if (!connection.value || !isConnected.value) { + console.warn('SignalR not connected') + return null + } + + try { + isLoading.value = true + const result = await connection.value.invoke('SendMessage', content, type) + return result + } catch (err) { + console.error('Send message failed:', err) + return null + } finally { + isLoading.value = false + } + } + + onUnmounted(() => { + disconnect() + }) + + return { + messages, + isConnected, + isLoading, + connect, + disconnect, + sendMessage + } +} diff --git a/Build_God_Game/src/main.ts b/Build_God_Game/src/main.ts new file mode 100644 index 0000000..495a430 --- /dev/null +++ b/Build_God_Game/src/main.ts @@ -0,0 +1,14 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import './style.css' +import App from './App.vue' +import router from './router' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' + +const app = createApp(App) +app.use(ElementPlus) +app.use(createPinia()) +app.use(router) + +app.mount('#app') diff --git a/Build_God_Game/src/router/index.ts b/Build_God_Game/src/router/index.ts new file mode 100644 index 0000000..2a0937f --- /dev/null +++ b/Build_God_Game/src/router/index.ts @@ -0,0 +1,70 @@ +import { createRouter, createWebHistory } from 'vue-router' +import { useAuthStore } from '@/stores/auth' +import { useCharacterStore } from '@/stores/character' + +const router = createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes: [ + { + path: '/', + redirect: '/login' + }, + { + path: '/login', + name: 'login', + component: () => import('@/views/LoginView.vue'), + meta: { requiresGuest: true } + }, + { + path: '/register', + name: 'register', + component: () => import('@/views/RegisterView.vue'), + meta: { requiresGuest: true } + }, + { + path: '/character', + name: 'character', + component: () => import('@/views/CharacterView.vue'), + meta: { requiresAuth: true } + }, + { + path: '/game', + name: 'game', + component: () => import('@/views/GameView.vue'), + meta: { requiresAuth: true } + }, + { + path: '/training', + name: 'training', + component: () => import('@/views/TrainingView.vue'), + meta: { requiresAuth: true } + } + ] +}) + +router.beforeEach((to, _from, next) => { + const authStore = useAuthStore() + const characterStore = useCharacterStore() + authStore.initAuth() + + if (to.meta.requiresAuth && !authStore.isAuthenticated) { + next('/login') + } else if (to.meta.requiresGuest && authStore.isAuthenticated) { + // 已登录用户访问登录/注册页,跳转到角色选择或游戏页面 + if (characterStore.characters.length > 0 && characterStore.currentCharacter) { + next('/game') + } else { + next('/character') + } + } else if (to.path === '/login' && authStore.isAuthenticated) { + // 访问登录页且已登录,跳转到角色选择 + next('/character') + } else if (to.path === '/game' && authStore.isAuthenticated && !characterStore.currentCharacter) { + // 访问游戏页但没有选择角色,跳转到角色选择 + next('/character') + } else { + next() + } +}) + +export default router diff --git a/Build_God_Game/src/stores/auth.ts b/Build_God_Game/src/stores/auth.ts new file mode 100644 index 0000000..dbd4712 --- /dev/null +++ b/Build_God_Game/src/stores/auth.ts @@ -0,0 +1,95 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { jwtDecode } from 'jwt-decode' +import { authApi, type LoginRequest, type RegisterRequest } from '@/api/auth' +import { useRouter } from 'vue-router' + +interface JwtPayload { + name: string + isAdmin: boolean + exp?: number +} + +export const useAuthStore = defineStore('auth', () => { + const token = ref('') + const username = ref('') + const isAdmin = ref(false) + const isLoading = ref(false) + const router = useRouter() + + const isAuthenticated = computed(() => !!token.value) + + const initAuth = () => { + const storedToken = localStorage.getItem('auth_token') + if (storedToken) { + try { + const decoded = jwtDecode(storedToken) + if (decoded.exp && decoded.exp * 1000 < Date.now()) { + logout() + } else { + token.value = storedToken + username.value = decoded.name + isAdmin.value = decoded.isAdmin || false + } + } catch { + logout() + } + } + } + + const login = async (credentials: LoginRequest): Promise => { + isLoading.value = true + try { + const response = await authApi.login(credentials) + token.value = response.token + localStorage.setItem('auth_token', response.token) + + const decoded = jwtDecode(response.token) + username.value = decoded.name + isAdmin.value = decoded.isAdmin || false + localStorage.setItem('user', JSON.stringify({ name: decoded.name, isAdmin: decoded.isAdmin })) + + return true + } catch (error) { + console.error('Login failed:', error) + return false + } finally { + isLoading.value = false + } + } + + const register = async (data: RegisterRequest): Promise => { + isLoading.value = true + try { + await authApi.register(data) + return true + } catch (error) { + console.error('Register failed:', error) + return false + } finally { + isLoading.value = false + } + } + + const logout = () => { + token.value = '' + username.value = '' + isAdmin.value = false + localStorage.removeItem('auth_token') + localStorage.removeItem('user') + } + + initAuth() + + return { + token, + username, + isAdmin, + isLoading, + isAuthenticated, + login, + register, + logout, + initAuth + } +}) diff --git a/Build_God_Game/src/stores/character.ts b/Build_God_Game/src/stores/character.ts new file mode 100644 index 0000000..f10e66c --- /dev/null +++ b/Build_God_Game/src/stores/character.ts @@ -0,0 +1,174 @@ +import { defineStore } from 'pinia' +import { ref } from 'vue' +import { characterApi, type CharacterDto, type CreateCharacterRequest, type ProfessionDto } from '@/api/character' + +export const useCharacterStore = defineStore('character', () => { + const characters = ref([]) + const currentCharacter = ref(null) + const professions = ref([]) + const isLoading = ref(false) + + const fetchCharacters = async (): Promise => { + isLoading.value = true + try { + const data = await characterApi.getCharacterList() + characters.value = data || [] + return true + } catch (error) { + console.error('Failed to fetch characters:', error) + return false + } finally { + isLoading.value = false + } + } + + const fetchProfessions = async (): Promise => { + try { + const data = await characterApi.getProfessions() + professions.value = data || [] + return true + } catch (error) { + console.error('Failed to fetch professions:', error) + return false + } + } + + const createCharacter = async (name: string, professionId: number): Promise => { + isLoading.value = true + try { + const data: CreateCharacterRequest = { name, professionId } + await characterApi.createCharacter(data) + await fetchCharacters() + return true + } catch (error) { + console.error('Failed to create character:', error) + return false + } finally { + isLoading.value = false + } + } + + const deleteCharacter = async (characterId: number): Promise => { + isLoading.value = true + try { + await characterApi.deleteCharacter(characterId) + await fetchCharacters() + return true + } catch (error) { + console.error('Failed to delete character:', error) + return false + } finally { + isLoading.value = false + } + } + + const selectCharacter = async (characterId: number): Promise => { + isLoading.value = true + try { + await characterApi.selectCharacter(characterId) + const char = characters.value.find(c => c.id === characterId) + if (char) { + currentCharacter.value = char + localStorage.setItem('current_character', JSON.stringify(char)) + } + return true + } catch (error) { + console.error('Failed to select character:', error) + return false + } finally { + isLoading.value = false + } + } + + const initCurrentCharacter = () => { + const stored = localStorage.getItem('current_character') + if (stored) { + try { + currentCharacter.value = JSON.parse(stored) + } catch { + currentCharacter.value = null + } + } + } + + const clearCurrentCharacter = () => { + currentCharacter.value = null + localStorage.removeItem('current_character') + } + + const startTraining = async (): Promise => { + if (!currentCharacter.value) return false + isLoading.value = true + try { + await characterApi.startTraining(currentCharacter.value.id) + currentCharacter.value.trainingOn = new Date().toISOString() + localStorage.setItem('current_character', JSON.stringify(currentCharacter.value)) + return true + } catch (error) { + console.error('Failed to start training:', error) + return false + } finally { + isLoading.value = false + } + } + + const stopTraining = async (): Promise => { + if (!currentCharacter.value) return 0 + isLoading.value = true + try { + const expGained = await characterApi.stopTraining(currentCharacter.value.id) + currentCharacter.value.trainingOn = undefined + currentCharacter.value.currentExp += expGained + localStorage.setItem('current_character', JSON.stringify(currentCharacter.value)) + return expGained + } catch (error) { + console.error('Failed to stop training:', error) + return 0 + } finally { + isLoading.value = false + } + } + + const breakthrough = async (): Promise<{ success: boolean; message: string }> => { + if (!currentCharacter.value) return { success: false, message: '角色不存在' } + isLoading.value = true + try { + const result = await characterApi.breakthrough(currentCharacter.value.id) + await fetchCharacters() + const updatedChar = characters.value.find(c => c.id === currentCharacter.value!.id) + if (updatedChar) { + currentCharacter.value = updatedChar + localStorage.setItem('current_character', JSON.stringify(updatedChar)) + } + if (result) { + return { success: true, message: '突破成功' } + } else { + return { success: false, message: '突破失败,请再接再厉' } + } + } catch (error: any) { + console.error('Failed to breakthrough:', error) + return { success: false, message: error?.message || '突破失败' } + } finally { + isLoading.value = false + } + } + + initCurrentCharacter() + + return { + characters, + currentCharacter, + professions, + isLoading, + fetchCharacters, + fetchProfessions, + createCharacter, + deleteCharacter, + selectCharacter, + initCurrentCharacter, + clearCurrentCharacter, + startTraining, + stopTraining, + breakthrough + } +}) diff --git a/Build_God_Game/src/style.css b/Build_God_Game/src/style.css new file mode 100644 index 0000000..d16b809 --- /dev/null +++ b/Build_God_Game/src/style.css @@ -0,0 +1,27 @@ +@import "tailwindcss"; + +@theme { + --font-sans: 'Microsoft YaHei', 'PingFang SC', sans-serif; +} + +:root { + font-family: var(--font-sans); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, body, #app { + width: 100%; + min-height: 100vh; + touch-action: manipulation; +} + +body { + overflow-x: hidden; +} diff --git a/Build_God_Game/src/views/CharacterView.vue b/Build_God_Game/src/views/CharacterView.vue new file mode 100644 index 0000000..fb4e3fa --- /dev/null +++ b/Build_God_Game/src/views/CharacterView.vue @@ -0,0 +1,705 @@ + + + + + diff --git a/Build_God_Game/src/views/GameView.vue b/Build_God_Game/src/views/GameView.vue new file mode 100644 index 0000000..59c400a --- /dev/null +++ b/Build_God_Game/src/views/GameView.vue @@ -0,0 +1,444 @@ + + + + + diff --git a/Build_God_Game/src/views/LoginView.vue b/Build_God_Game/src/views/LoginView.vue new file mode 100644 index 0000000..3948367 --- /dev/null +++ b/Build_God_Game/src/views/LoginView.vue @@ -0,0 +1,294 @@ + + + + + diff --git a/Build_God_Game/src/views/RegisterView.vue b/Build_God_Game/src/views/RegisterView.vue new file mode 100644 index 0000000..54482e7 --- /dev/null +++ b/Build_God_Game/src/views/RegisterView.vue @@ -0,0 +1,333 @@ + + + + + diff --git a/Build_God_Game/src/views/TrainingView.vue b/Build_God_Game/src/views/TrainingView.vue new file mode 100644 index 0000000..736a41f --- /dev/null +++ b/Build_God_Game/src/views/TrainingView.vue @@ -0,0 +1,304 @@ + + + + + diff --git a/Build_God_Game/tsconfig.app.json b/Build_God_Game/tsconfig.app.json new file mode 100644 index 0000000..9d430dc --- /dev/null +++ b/Build_God_Game/tsconfig.app.json @@ -0,0 +1,21 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + + /* Linting */ + "strict": false, + "noUnusedLocals": false, + "noUnusedParameters": false, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "strictNullChecks": false + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/Build_God_Game/tsconfig.json b/Build_God_Game/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/Build_God_Game/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/Build_God_Game/tsconfig.node.json b/Build_God_Game/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/Build_God_Game/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/Build_God_Game/vite.config.ts b/Build_God_Game/vite.config.ts new file mode 100644 index 0000000..caaf80e --- /dev/null +++ b/Build_God_Game/vite.config.ts @@ -0,0 +1,30 @@ +import { fileURLToPath, URL } from 'node:url' +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import tailwindcss from '@tailwindcss/vite' + +export default defineConfig({ + plugins: [ + vue(), + tailwindcss(), + ], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + }, + }, + server: { + port: 5174, + proxy: { + '/api': { + target: 'http://localhost:5091', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, '/api/god') + }, + '/hubs': { + target: 'http://localhost:5091', + changeOrigin: true + } + } + } +}) diff --git a/Build_God_Game/模拟导图.xls b/Build_God_Game/模拟导图.xls new file mode 100644 index 0000000..ae203f0 Binary files /dev/null and b/Build_God_Game/模拟导图.xls differ