mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-11 23:45:11 +08:00
feat: 初始化项目结构与核心功能
引入项目基础目录结构,包含多语言支持、主要页面与组件、核心游戏逻辑、UI 组件库、加密与本地持久化、自动化 Docker 构建流程、GitHub issue 模板(中英文)、README(中英文)、LICENSE 及开发配置文件。实现 OGame 单机版主要功能模块,为后续开发和扩展奠定基础。
This commit is contained in:
31
.github/ISSUE_TEMPLATE/BUG反馈.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/BUG反馈.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: BUG反馈
|
||||
about: 报告项目中发现的缺陷或问题
|
||||
title: '[BUG] 简要描述问题'
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**问题描述**
|
||||
清晰准确地描述遇到的问题
|
||||
|
||||
**重现步骤**
|
||||
|
||||
1. 第一步操作
|
||||
2. 第二步操作
|
||||
3. 出现问题的操作
|
||||
|
||||
**期望行为**
|
||||
描述您认为正确的行为应该是怎样的
|
||||
|
||||
**实际行为**
|
||||
描述实际发生的错误行为
|
||||
|
||||
**环境信息**
|
||||
|
||||
- 操作系统:
|
||||
- 浏览器(如适用):
|
||||
- 项目版本:
|
||||
|
||||
**截图或日志(可选)**
|
||||
如果有错误截图或日志,请提供
|
||||
31
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report defects or issues found in the project
|
||||
title: '[BUG] Brief description of the issue'
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Issue Description**
|
||||
Clearly and accurately describe the problem you encountered
|
||||
|
||||
**Steps to Reproduce**
|
||||
|
||||
1. First operation step
|
||||
2. Second operation step
|
||||
3. Operation where the issue occurs
|
||||
|
||||
**Expected Behavior**
|
||||
Describe what you think the correct behavior should be
|
||||
|
||||
**Actual Behavior**
|
||||
Describe the actual erroneous behavior that occurred
|
||||
|
||||
**Environment Information**
|
||||
|
||||
- Operating System:
|
||||
- Browser (if applicable):
|
||||
- Project Version:
|
||||
|
||||
**Screenshots or Logs (optional)**
|
||||
If you have error screenshots or logs, please provide them
|
||||
19
.github/ISSUE_TEMPLATE/documentation-improvement.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/documentation-improvement.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Documentation Improvement
|
||||
about: Report documentation issues or suggest improvements
|
||||
title: '[Docs] Brief description of the issue'
|
||||
labels: 'documentation'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Documentation Location**
|
||||
Point out the documentation path or URL that needs improvement
|
||||
|
||||
**Current Issue**
|
||||
Describe the current problem or unclear areas in the documentation
|
||||
|
||||
**Improvement Suggestions**
|
||||
Provide specific improvement suggestions
|
||||
|
||||
**Additional Information (optional)**
|
||||
Any other information that helps improve the documentation
|
||||
19
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/feature-request.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Request to add new features or improve existing ones
|
||||
title: '[Feature] Brief description of the feature'
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**Feature Description**
|
||||
Clearly describe the feature you would like to see added
|
||||
|
||||
**Feature Background**
|
||||
Explain why this feature is needed and what problem it solves
|
||||
|
||||
**Suggested Implementation (optional)**
|
||||
If you have specific implementation ideas, you can describe them here
|
||||
|
||||
**Additional Information**
|
||||
Any other information that helps understand this feature
|
||||
19
.github/ISSUE_TEMPLATE/feedback-suggestion.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/feedback-suggestion.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: Feedback & Suggestion
|
||||
about: Propose feature suggestions or improvements for this project
|
||||
title: '[Suggestion] Brief description of your suggestion'
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**What is your suggestion?**
|
||||
Please clearly describe the feature or improvement you would like to see
|
||||
|
||||
**Why is this feature/improvement needed?**
|
||||
Explain what problem this suggestion solves or what value it brings
|
||||
|
||||
**Your expected implementation (optional)**
|
||||
If you have specific implementation ideas, you can describe them here
|
||||
|
||||
**Additional information (optional)**
|
||||
Any other information that helps understand this suggestion
|
||||
19
.github/ISSUE_TEMPLATE/功能请求.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/功能请求.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: 功能请求
|
||||
about: 请求添加新功能或改进现有功能
|
||||
title: '[功能] 简要描述功能'
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**功能描述**
|
||||
清晰描述您希望添加的功能
|
||||
|
||||
**功能背景**
|
||||
说明为什么需要这个功能,它能解决什么问题
|
||||
|
||||
**建议实现方案(可选)**
|
||||
如果有具体的实现想法,可以在这里描述
|
||||
|
||||
**附加信息**
|
||||
任何其他有助于理解这个功能的信息
|
||||
19
.github/ISSUE_TEMPLATE/反馈建议.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/反馈建议.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: 反馈建议
|
||||
about: 为这个项目提出功能建议或改进意见
|
||||
title: '[建议] 简要描述您的建议'
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**您的建议是什么?**
|
||||
请清晰描述您希望添加的功能或改进点
|
||||
|
||||
**为什么需要这个功能/改进?**
|
||||
说明这个建议会解决什么问题或带来什么价值
|
||||
|
||||
**您期望的实现方式(可选)**
|
||||
如果有具体的实现想法,可以在这里描述
|
||||
|
||||
**附加信息(可选)**
|
||||
任何其他有助于理解这个建议的信息
|
||||
19
.github/ISSUE_TEMPLATE/文档改进.md
vendored
Normal file
19
.github/ISSUE_TEMPLATE/文档改进.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
name: 文档改进
|
||||
about: 报告文档问题或建议改进
|
||||
title: '[文档] 简要描述问题'
|
||||
labels: 'documentation'
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
**文档位置**
|
||||
指出需要改进的文档路径或 URL
|
||||
|
||||
**当前问题**
|
||||
描述当前文档存在的问题或不清晰的地方
|
||||
|
||||
**改进建议**
|
||||
提出具体的改进建议
|
||||
|
||||
**附加信息(可选)**
|
||||
任何其他有助于改进文档的信息
|
||||
105
.github/workflows/ogame-vue-ts.yml
vendored
Normal file
105
.github/workflows/ogame-vue-ts.yml
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
name: 自动化创建Docker镜像
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-amd64:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: 检查代码
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: 登录 GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
|
||||
- name: 登录 Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: 构建并推送 amd64 Docker镜像
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/blist:amd64
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/blist:amd64
|
||||
|
||||
build-arm64:
|
||||
runs-on: ubuntu-22.04-arm
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: 检查代码
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: 登录 GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
|
||||
- name: 登录 Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: 构建并推送 arm64 Docker镜像
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
ghcr.io/${{ github.repository_owner }}/blist:arm64
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/blist:arm64
|
||||
|
||||
create-manifest:
|
||||
needs: [build-amd64, build-arm64]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: 登录 GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GHCR_TOKEN }}
|
||||
|
||||
- name: 登录 Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: 创建并推送多架构清单
|
||||
run: |
|
||||
# GitHub Container Registry
|
||||
docker manifest create ghcr.io/${{ github.repository_owner }}/blist:latest \
|
||||
ghcr.io/${{ github.repository_owner }}/blist:amd64 \
|
||||
ghcr.io/${{ github.repository_owner }}/blist:arm64
|
||||
docker manifest push ghcr.io/${{ github.repository_owner }}/blist:latest
|
||||
|
||||
# Docker Hub
|
||||
docker manifest create ${{ secrets.DOCKERHUB_USERNAME }}/blist:latest \
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/blist:amd64 \
|
||||
${{ secrets.DOCKERHUB_USERNAME }}/blist:arm64
|
||||
docker manifest push ${{ secrets.DOCKERHUB_USERNAME }}/blist:latest
|
||||
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
CLAUDE.md
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.claude/*
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
FROM node:latest
|
||||
|
||||
RUN mkdir -p /workspace
|
||||
|
||||
WORKDIR /workspace
|
||||
|
||||
RUN npm config set registry https://registry.npmmirror.com
|
||||
|
||||
RUN cd /workspace
|
||||
|
||||
RUN git clone https://github.com/setube/ogame-vue-ts.git
|
||||
|
||||
RUN mv ./ogame-vue-ts/* . ; rm -rf ./ogame-vue-ts/
|
||||
|
||||
RUN npm install -g pnpm ; pnpm install ; npx vite build
|
||||
|
||||
CMD ["npx", "vite", "preview", "--host", "0.0.0.0", "--port", "25121"]
|
||||
32
LICENSE
Normal file
32
LICENSE
Normal file
@@ -0,0 +1,32 @@
|
||||
Creative Commons Attribution-NonCommercial 4.0 International Public License
|
||||
|
||||
This work is licensed under the Creative Commons Attribution-NonCommercial 4.0
|
||||
International Public License. To view a copy of this license, visit
|
||||
https://creativecommons.org/licenses/by-nc/4.0/legalcode.
|
||||
|
||||
You are free to:
|
||||
Share — copy and redistribute the material in any medium or format
|
||||
Adapt — remix, transform, and build upon the material
|
||||
|
||||
Under the following terms:
|
||||
Attribution — You must give appropriate credit, provide a link to the license,
|
||||
and indicate if changes were made. You may do so in any reasonable manner,
|
||||
but not in any way that suggests the licensor endorses you or your use.
|
||||
|
||||
NonCommercial — You may not use the material for commercial purposes.
|
||||
|
||||
No additional restrictions — You may not apply legal terms or technological
|
||||
measures that legally restrict others from doing anything the license permits.
|
||||
|
||||
Notice:
|
||||
This work is the original creation of Jun Qian (谦君). Source code available at:
|
||||
https://github.com/setube/ogame-vue-ts
|
||||
This license does not constitute a waiver of any copyright or related rights.
|
||||
|
||||
When you share adaptations of this work, you must:
|
||||
- Provide prominent attribution to the original author
|
||||
- Retain this license document
|
||||
- Clearly indicate modifications made and dates
|
||||
- Distribute under the same CC BY-NC 4.0 license
|
||||
|
||||
© 2025 Jun Qian - All rights reserved (except those granted by this license)
|
||||
241
README.md
Normal file
241
README.md
Normal file
@@ -0,0 +1,241 @@
|
||||
<div align="center">
|
||||
<img src="public/logo.svg" alt="OGame Vue TS Logo" width="128" height="128">
|
||||
|
||||
# OGame Vue TS
|
||||
|
||||
A modern web-based implementation of the classic OGame space strategy game, built with Vue 3 and TypeScript.
|
||||
|
||||
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
||||
[](https://vuejs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://vitejs.dev/)
|
||||
|
||||
</div>
|
||||
|
||||
## 📖 About
|
||||
|
||||
OGame Vue TS is a single-player, browser-based space strategy game inspired by the classic OGame. Build your empire across the galaxy, research technologies, construct ships, and engage in epic space battles. This project is built with modern web technologies, offering a smooth and responsive gaming experience entirely in your browser with local data persistence.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🌍 **Multi-language Support** - Available in 6 languages: English, Chinese (Simplified & Traditional), German, Russian, and Korean
|
||||
- 🏗️ **Building Management** - Construct and upgrade various buildings on planets and moons
|
||||
- 🔬 **Research Technologies** - Unlock advanced technologies to enhance your empire
|
||||
- 🚀 **Fleet Management** - Build ships, send missions, and engage in tactical space battles
|
||||
- 🛡️ **Defense Systems** - Deploy defensive structures to protect your colonies
|
||||
- 👨✈️ **Officers System** - Recruit officers to gain strategic advantages
|
||||
- ⚔️ **Battle Simulator** - Test combat scenarios before committing resources
|
||||
- 🌌 **Galaxy View** - Explore the universe and interact with other planets
|
||||
- 💾 **Local Data Persistence** - All game data is encrypted and stored locally in your browser
|
||||
- 🌓 **Dark/Light Mode** - Choose your preferred visual theme
|
||||
- 📊 **Queue Management** - Manage multiple build and research queues
|
||||
- 🌙 **Moon Generation** - Chance-based moon creation from debris fields
|
||||
|
||||
## 🛠️ Tech Stack
|
||||
|
||||
- **Frontend Framework:** [Vue 3](https://vuejs.org/) with Composition API (`<script setup>`)
|
||||
- **Language:** [TypeScript](https://www.typescriptlang.org/) with strict type checking
|
||||
- **Build Tool:** [Vite](https://vitejs.dev/) (Custom Rolldown-Vite 7.2.5)
|
||||
- **State Management:** [Pinia](https://pinia.vuejs.org/) with persisted state plugin
|
||||
- **Routing:** [Vue Router 4](https://router.vuejs.org/)
|
||||
- **UI Components:** [shadcn-vue](https://www.shadcn-vue.com/) (New York style)
|
||||
- **Styling:** [Tailwind CSS v4](https://tailwindcss.com/) with CSS variables
|
||||
- **Icons:** [Lucide Vue Next](https://lucide.dev/)
|
||||
- **Animations:** [tw-animate-css](https://www.npmjs.com/package/tw-animate-css)
|
||||
- **Internationalization:** Custom i18n implementation
|
||||
- **Encryption:** [CryptoJS](https://cryptojs.gitbook.io/) for data security
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) (version 18 or higher recommended)
|
||||
- [pnpm](https://pnpm.io/) (version 10.13.1 or higher)
|
||||
|
||||
### Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://github.com/setube/ogame-vue-ts.git
|
||||
|
||||
# Navigate to project directory
|
||||
cd ogame-vue-ts
|
||||
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
# Start development server (runs on port 25121)
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
Open your browser and visit `http://localhost:25121`
|
||||
|
||||
### Build for Production
|
||||
|
||||
```bash
|
||||
# Build the application
|
||||
pnpm build
|
||||
|
||||
# Preview production build
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
ogame-vue-ts/
|
||||
├── public/ # Static assets
|
||||
│ └── logo.svg # Application logo
|
||||
├── src/
|
||||
│ ├── assets/ # Dynamic assets
|
||||
│ ├── components/ # Vue components
|
||||
│ │ └── ui/ # shadcn-vue UI components
|
||||
│ ├── composables/ # Vue composables
|
||||
│ ├── config/ # Game configuration
|
||||
│ ├── lib/ # Utility libraries
|
||||
│ ├── locales/ # i18n translation files
|
||||
│ ├── logic/ # Game logic modules
|
||||
│ │ ├── buildingLogic.ts
|
||||
│ │ ├── buildingValidation.ts
|
||||
│ │ ├── fleetLogic.ts
|
||||
│ │ ├── moonLogic.ts
|
||||
│ │ ├── moonValidation.ts
|
||||
│ │ ├── researchLogic.ts
|
||||
│ │ ├── researchValidation.ts
|
||||
│ │ ├── shipLogic.ts
|
||||
│ │ └── shipValidation.ts
|
||||
│ ├── router/ # Vue Router configuration
|
||||
│ ├── stores/ # Pinia state stores
|
||||
│ ├── types/ # TypeScript type definitions
|
||||
│ ├── utils/ # Utility functions
|
||||
│ ├── views/ # Page components
|
||||
│ │ ├── OverviewView.vue
|
||||
│ │ ├── BuildingsView.vue
|
||||
│ │ ├── ResearchView.vue
|
||||
│ │ ├── ShipyardView.vue
|
||||
│ │ ├── DefenseView.vue
|
||||
│ │ ├── FleetView.vue
|
||||
│ │ ├── GalaxyView.vue
|
||||
│ │ ├── OfficersView.vue
|
||||
│ │ ├── BattleSimulatorView.vue
|
||||
│ │ ├── MessagesView.vue
|
||||
│ │ └── SettingsView.vue
|
||||
│ ├── App.vue # Root component
|
||||
│ ├── main.ts # Application entry point
|
||||
│ └── style.css # Global styles
|
||||
├── .github/
|
||||
│ └── ISSUE_TEMPLATE/ # GitHub issue templates
|
||||
├── CLAUDE.md # AI assistant instructions
|
||||
├── LICENSE # CC BY-NC 4.0 License
|
||||
├── package.json # Project dependencies
|
||||
├── tsconfig.json # TypeScript configuration
|
||||
└── vite.config.ts # Vite configuration
|
||||
```
|
||||
|
||||
## 🌐 Available Languages
|
||||
|
||||
- 🇺🇸 English
|
||||
- 🇨🇳 简体中文 (Simplified Chinese)
|
||||
- 🇹🇼 繁體中文 (Traditional Chinese)
|
||||
- 🇩🇪 Deutsch (German)
|
||||
- 🇷🇺 Русский (Russian)
|
||||
- 🇰🇷 한국어 (Korean)
|
||||
|
||||
## 🎮 Game Features
|
||||
|
||||
### Resource Management
|
||||
- **Metal** - Primary construction material
|
||||
- **Crystal** - Advanced technology component
|
||||
- **Deuterium** - Fuel and research resource
|
||||
- **Dark Matter** - Premium resource
|
||||
- **Energy** - Powers your facilities
|
||||
|
||||
### Building Types
|
||||
- **Resource Buildings** - Metal Mine, Crystal Mine, Deuterium Synthesizer, Solar Plant
|
||||
- **Facilities** - Robotics Factory, Shipyard, Research Lab, Storage facilities
|
||||
- **Special Buildings** - Nanite Factory, Terraformer, and more
|
||||
|
||||
### Technologies
|
||||
- **Energy Technology** - Improves energy efficiency
|
||||
- **Laser Technology** - Enhances weapon systems
|
||||
- **Ion Technology** - Advanced propulsion and weapons
|
||||
- **Hyperspace Technology** - Enables faster travel
|
||||
- **Plasma Technology** - Ultimate weapon systems
|
||||
- And many more...
|
||||
|
||||
### Ship Classes
|
||||
- **Civil Ships** - Small/Large Cargo, Colony Ship, Recycler
|
||||
- **Combat Ships** - Light/Heavy Fighter, Cruiser, Battleship, Bomber
|
||||
- **Special Ships** - Deathstar, Battlecruiser, Destroyer
|
||||
|
||||
### Defense Systems
|
||||
- Rocket Launcher, Light/Heavy Laser, Gauss Cannon
|
||||
- Ion Cannon, Plasma Turret
|
||||
- Small/Large Shield Dome
|
||||
|
||||
## 🔒 Data Security
|
||||
|
||||
All game data is automatically encrypted using AES encryption before being stored in your browser's local storage. Your game progress is secure and private.
|
||||
|
||||
## 🎨 Customization
|
||||
|
||||
The application supports full theme customization through Tailwind CSS variables defined in `src/style.css`. You can easily switch between light and dark modes.
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! Please feel free to submit issues or pull requests.
|
||||
|
||||
### Issue Templates
|
||||
We provide the following issue templates in both Chinese and English:
|
||||
- 🐛 Bug Report
|
||||
- ✨ Feature Request
|
||||
- 📚 Documentation Improvement
|
||||
- 💡 Feedback & Suggestion
|
||||
|
||||
## 📄 License
|
||||
|
||||
This work is licensed under the [Creative Commons Attribution-NonCommercial 4.0 International License](https://creativecommons.org/licenses/by-nc/4.0/).
|
||||
|
||||
### You are free to:
|
||||
- **Share** — copy and redistribute the material in any medium or format
|
||||
- **Adapt** — remix, transform, and build upon the material
|
||||
|
||||
### Under the following terms:
|
||||
- **Attribution** — You must give appropriate credit, provide a link to the license, and indicate if changes were made
|
||||
- **NonCommercial** — You may not use the material for commercial purposes
|
||||
|
||||
**Original Author:** Jun Qian (谦君)
|
||||
|
||||
## 👨💻 Author
|
||||
|
||||
- **GitHub:** [@setube](https://github.com/setube)
|
||||
- **Project:** [ogame-vue-ts](https://github.com/setube/ogame-vue-ts)
|
||||
|
||||
## 💬 Community
|
||||
|
||||
### Chinese Community
|
||||
- **QQ Group:** 920930589
|
||||
|
||||
### Global Community
|
||||
- **GitHub Issues:** [Report bugs or request features](https://github.com/setube/ogame-vue-ts/issues)
|
||||
- **GitHub Discussions:** [Join the conversation](https://github.com/setube/ogame-vue-ts/discussions)
|
||||
|
||||
## 🙏 Acknowledgments
|
||||
|
||||
This project is inspired by the original [OGame](https://ogame.org/) browser game. All game mechanics and design elements are reimplemented for educational and entertainment purposes.
|
||||
|
||||
## ⚠️ Disclaimer
|
||||
|
||||
This project is not affiliated with, endorsed by, or connected to Gameforge AG or the official OGame game. It is an independent fan project created for educational purposes and personal enjoyment.
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
Made with ❤️ by Jun Qian
|
||||
<br>
|
||||
© 2025 - All rights reserved (except those granted by CC BY-NC 4.0 License)
|
||||
</div>
|
||||
243
README.zh-CN.md
Normal file
243
README.zh-CN.md
Normal file
@@ -0,0 +1,243 @@
|
||||
<div align="center">
|
||||
<img src="public/logo.svg" alt="OGame Vue TS Logo" width="128" height="128">
|
||||
|
||||
# OGame Vue TS
|
||||
|
||||
一个基于 Vue 3 和 TypeScript 构建的现代化 OGame 太空策略游戏 Web 实现。
|
||||
|
||||
[](https://creativecommons.org/licenses/by-nc/4.0/)
|
||||
[](https://vuejs.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://vitejs.dev/)
|
||||
|
||||
[English](README.md) | 简体中文
|
||||
|
||||
</div>
|
||||
|
||||
## 📖 关于项目
|
||||
|
||||
OGame Vue TS 是一款受经典 OGame 游戏启发的单机版、基于浏览器的太空策略游戏。在银河系中建立你的帝国,研究科技,建造舰船,参与史诗般的太空战斗。本项目采用现代 Web 技术构建,完全在浏览器中运行,提供流畅且响应迅速的游戏体验,所有数据都存储在本地。
|
||||
|
||||
## ✨ 核心特性
|
||||
|
||||
- 🌍 **多语言支持** - 支持 6 种语言:英语、简体中文、繁体中文、德语、俄语和韩语
|
||||
- 🏗️ **建筑管理** - 在行星和月球上建造和升级各种建筑
|
||||
- 🔬 **科技研究** - 解锁先进科技来增强你的帝国
|
||||
- 🚀 **舰队管理** - 建造舰船、派遣任务、参与战术太空战斗
|
||||
- 🛡️ **防御系统** - 部署防御设施来保护你的殖民地
|
||||
- 👨✈️ **军官系统** - 招募军官以获得战略优势
|
||||
- ⚔️ **战斗模拟器** - 在投入资源前测试战斗场景
|
||||
- 🌌 **银河视图** - 探索宇宙并与其他星球互动
|
||||
- 💾 **本地数据持久化** - 所有游戏数据都经过加密并存储在浏览器本地
|
||||
- 🌓 **深色/浅色主题** - 选择你喜欢的视觉主题
|
||||
- 📊 **队列管理** - 管理多个建造和研究队列
|
||||
- 🌙 **月球生成** - 基于概率的月球从残骸场生成机制
|
||||
|
||||
## 🛠️ 技术栈
|
||||
|
||||
- **前端框架:** [Vue 3](https://vuejs.org/) + Composition API (`<script setup>` 语法)
|
||||
- **开发语言:** [TypeScript](https://www.typescriptlang.org/) (启用严格类型检查)
|
||||
- **构建工具:** [Vite](https://vitejs.dev/) (自定义 Rolldown-Vite 7.2.5)
|
||||
- **状态管理:** [Pinia](https://pinia.vuejs.org/) + 持久化插件
|
||||
- **路由管理:** [Vue Router 4](https://router.vuejs.org/)
|
||||
- **UI 组件:** [shadcn-vue](https://www.shadcn-vue.com/) (New York 风格)
|
||||
- **样式方案:** [Tailwind CSS v4](https://tailwindcss.com/) + CSS 变量
|
||||
- **图标库:** [Lucide Vue Next](https://lucide.dev/)
|
||||
- **动画效果:** [tw-animate-css](https://www.npmjs.com/package/tw-animate-css)
|
||||
- **国际化:** 自定义 i18n 实现
|
||||
- **数据加密:** [CryptoJS](https://cryptojs.gitbook.io/) 保障数据安全
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 环境要求
|
||||
|
||||
- [Node.js](https://nodejs.org/) (推荐 18 或更高版本)
|
||||
- [pnpm](https://pnpm.io/) (版本 10.13.1 或更高)
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
# 克隆仓库
|
||||
git clone https://github.com/setube/ogame-vue-ts.git
|
||||
|
||||
# 进入项目目录
|
||||
cd ogame-vue-ts
|
||||
|
||||
# 安装依赖
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 开发
|
||||
|
||||
```bash
|
||||
# 启动开发服务器 (运行在端口 25121)
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
在浏览器中访问 `http://localhost:25121`
|
||||
|
||||
### 生产构建
|
||||
|
||||
```bash
|
||||
# 构建应用
|
||||
pnpm build
|
||||
|
||||
# 预览生产构建
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
## 📁 项目结构
|
||||
|
||||
```
|
||||
ogame-vue-ts/
|
||||
├── public/ # 静态资源
|
||||
│ └── logo.svg # 应用图标
|
||||
├── src/
|
||||
│ ├── assets/ # 动态资源
|
||||
│ ├── components/ # Vue 组件
|
||||
│ │ └── ui/ # shadcn-vue UI 组件
|
||||
│ ├── composables/ # Vue 组合式函数
|
||||
│ ├── config/ # 游戏配置
|
||||
│ ├── lib/ # 工具库
|
||||
│ ├── locales/ # 国际化翻译文件
|
||||
│ ├── logic/ # 游戏逻辑模块
|
||||
│ │ ├── buildingLogic.ts # 建筑逻辑
|
||||
│ │ ├── buildingValidation.ts # 建筑验证
|
||||
│ │ ├── fleetLogic.ts # 舰队逻辑
|
||||
│ │ ├── moonLogic.ts # 月球逻辑
|
||||
│ │ ├── moonValidation.ts # 月球验证
|
||||
│ │ ├── researchLogic.ts # 研究逻辑
|
||||
│ │ ├── researchValidation.ts # 研究验证
|
||||
│ │ ├── shipLogic.ts # 舰船逻辑
|
||||
│ │ └── shipValidation.ts # 舰船验证
|
||||
│ ├── router/ # Vue Router 路由配置
|
||||
│ ├── stores/ # Pinia 状态存储
|
||||
│ ├── types/ # TypeScript 类型定义
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── views/ # 页面组件
|
||||
│ │ ├── OverviewView.vue # 概览页面
|
||||
│ │ ├── BuildingsView.vue # 建筑页面
|
||||
│ │ ├── ResearchView.vue # 研究页面
|
||||
│ │ ├── ShipyardView.vue # 船坞页面
|
||||
│ │ ├── DefenseView.vue # 防御页面
|
||||
│ │ ├── FleetView.vue # 舰队页面
|
||||
│ │ ├── GalaxyView.vue # 银河页面
|
||||
│ │ ├── OfficersView.vue # 军官页面
|
||||
│ │ ├── BattleSimulatorView.vue # 战斗模拟器
|
||||
│ │ ├── MessagesView.vue # 消息页面
|
||||
│ │ └── SettingsView.vue # 设置页面
|
||||
│ ├── App.vue # 根组件
|
||||
│ ├── main.ts # 应用入口
|
||||
│ └── style.css # 全局样式
|
||||
├── .github/
|
||||
│ └── ISSUE_TEMPLATE/ # GitHub issue 模板
|
||||
├── CLAUDE.md # AI 助手说明文档
|
||||
├── LICENSE # CC BY-NC 4.0 许可证
|
||||
├── package.json # 项目依赖
|
||||
├── tsconfig.json # TypeScript 配置
|
||||
└── vite.config.ts # Vite 配置
|
||||
```
|
||||
|
||||
## 🌐 支持的语言
|
||||
|
||||
- 🇺🇸 English (英语)
|
||||
- 🇨🇳 简体中文
|
||||
- 🇹🇼 繁體中文
|
||||
- 🇩🇪 Deutsch (德语)
|
||||
- 🇷🇺 Русский (俄语)
|
||||
- 🇰🇷 한국어 (韩语)
|
||||
|
||||
## 🎮 游戏特性
|
||||
|
||||
### 资源管理
|
||||
- **金属** - 主要建筑材料
|
||||
- **晶体** - 高级科技组件
|
||||
- **重氢** - 燃料和研究资源
|
||||
- **暗物质** - 高级资源
|
||||
- **能量** - 为设施供电
|
||||
|
||||
### 建筑类型
|
||||
- **资源建筑** - 金属矿、晶体矿、重氢合成器、太阳能发电厂
|
||||
- **设施建筑** - 机器人工厂、船坞、研究实验室、仓储设施
|
||||
- **特殊建筑** - 纳米机器人工厂、行星改造器等
|
||||
|
||||
### 科技系统
|
||||
- **能量技术** - 提高能量效率
|
||||
- **激光技术** - 增强武器系统
|
||||
- **离子技术** - 高级推进和武器
|
||||
- **超空间技术** - 实现更快的旅行
|
||||
- **等离子技术** - 终极武器系统
|
||||
- 还有更多...
|
||||
|
||||
### 舰船类别
|
||||
- **民用舰船** - 小型/大型货船、殖民船、回收船
|
||||
- **战斗舰船** - 轻型/重型战斗机、巡洋舰、战列舰、轰炸机
|
||||
- **特殊舰船** - 死星、战列巡洋舰、毁灭者
|
||||
|
||||
### 防御系统
|
||||
- 火箭发射器、轻型/重型激光炮、高斯炮
|
||||
- 离子炮、等离子炮塔
|
||||
- 小型/大型防护罩
|
||||
|
||||
## 🔒 数据安全
|
||||
|
||||
所有游戏数据在存储到浏览器的本地存储之前都会使用 AES 加密自动加密。您的游戏进度是安全且私密的。
|
||||
|
||||
## 🎨 自定义
|
||||
|
||||
应用支持通过 `src/style.css` 中定义的 Tailwind CSS 变量进行完整的主题自定义。您可以轻松地在浅色和深色模式之间切换。
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎贡献!请随时提交 issue 或 pull request。
|
||||
|
||||
### Issue 模板
|
||||
我们提供以下中英文 issue 模板:
|
||||
- 🐛 BUG反馈 / Bug Report
|
||||
- ✨ 功能请求 / Feature Request
|
||||
- 📚 文档改进 / Documentation Improvement
|
||||
- 💡 反馈建议 / Feedback & Suggestion
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本作品采用 [知识共享署名-非商业性使用 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc/4.0/) 进行许可。
|
||||
|
||||
### 您可以自由地:
|
||||
- **共享** — 在任何媒介以任何形式复制、发行本作品
|
||||
- **演绎** — 修改、转换或以本作品为基础进行创作
|
||||
|
||||
### 惟须遵守下列条件:
|
||||
- **署名** — 您必须给出适当的署名,提供指向本许可协议的链接,同时标明是否对原始作品作了修改
|
||||
- **非商业性使用** — 您不得将本作品用于商业目的
|
||||
|
||||
**原作者:** Jun Qian (谦君)
|
||||
|
||||
## 👨💻 作者
|
||||
|
||||
- **GitHub:** [@setube](https://github.com/setube)
|
||||
- **项目地址:** [ogame-vue-ts](https://github.com/setube/ogame-vue-ts)
|
||||
|
||||
## 💬 社区
|
||||
|
||||
### 中文社区
|
||||
- **QQ 群:** 920930589
|
||||
|
||||
### 国际社区
|
||||
- **GitHub Issues:** [报告 bug 或请求功能](https://github.com/setube/ogame-vue-ts/issues)
|
||||
- **GitHub Discussions:** [加入讨论](https://github.com/setube/ogame-vue-ts/discussions)
|
||||
|
||||
## 🙏 致谢
|
||||
|
||||
本项目受原版 [OGame](https://ogame.org/) 浏览器游戏启发。所有游戏机制和设计元素都是为了教育和娱乐目的而重新实现的。
|
||||
|
||||
## ⚠️ 免责声明
|
||||
|
||||
本项目与 Gameforge AG 或官方 OGame 游戏没有任何关联、认可或联系。这是一个独立的粉丝项目,创建目的仅用于教育和个人娱乐。
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
用 ❤️ 制作,作者:谦君
|
||||
<br>
|
||||
© 2025 - 保留所有权利(除 CC BY-NC 4.0 许可证授予的权利外)
|
||||
</div>
|
||||
21
components.json
Normal file
21
components.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"$schema": "https://shadcn-vue.com/schema.json",
|
||||
"style": "new-york",
|
||||
"typescript": true,
|
||||
"tailwind": {
|
||||
"config": "",
|
||||
"css": "src/style.css",
|
||||
"baseColor": "slate",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"iconLibrary": "lucide",
|
||||
"aliases": {
|
||||
"components": "@/components",
|
||||
"utils": "@/lib/utils",
|
||||
"ui": "@/components/ui",
|
||||
"lib": "@/lib",
|
||||
"composables": "@/composables"
|
||||
},
|
||||
"registries": {}
|
||||
}
|
||||
1
docs/assets/AlertDialog-BbpHh-RO.js
Normal file
1
docs/assets/AlertDialog-BbpHh-RO.js
Normal file
@@ -0,0 +1 @@
|
||||
import{An as e,Dn as t,Fn as n,Gn as r,Mn as i,Nn as a,Pn as o,Qn as s,bn as c,er as l,jn as u,kn as d,rr as f,sr as p,zt as m}from"./index-BpOElaf9.js";var h={key:0,class:`fixed inset-0 z-50 flex items-center justify-center`},g={class:`relative bg-card border rounded-lg shadow-lg p-6 max-w-md w-full mx-4 z-10`},_={class:`text-lg font-semibold mb-2`},v={class:`text-sm text-muted-foreground mb-6 whitespace-pre-line`},y={class:`flex justify-end gap-2`},b=n({__name:`AlertDialog`,setup(n,{expose:b}){let{t:x}=c(),S=l(!1),C=l(null),w=e=>{C.value=e,S.value=!0},T=()=>{C.value?.onConfirm&&C.value.onConfirm(),S.value=!1},E=()=>{S.value=!1};return b({show:w}),(n,c)=>(r(),e(t,{to:`body`},[S.value?(r(),i(`div`,h,[d(`div`,{class:`fixed inset-0 bg-black/50`,onClick:E}),d(`div`,g,[d(`h2`,_,p(C.value?.title),1),d(`p`,v,p(C.value?.message),1),d(`div`,y,[C.value?.onConfirm?(r(),e(f(m),{key:0,onClick:E,variant:`outline`},{default:s(()=>[a(p(f(x)(`common.cancel`)),1)]),_:1})):u(``,!0),o(f(m),{onClick:T,variant:`default`},{default:s(()=>[a(p(f(x)(`common.confirm`)),1)]),_:1})])])])):u(``,!0)]))}});export{b as t};
|
||||
1
docs/assets/BattleSimulatorView-DI2C9KJc.js
Normal file
1
docs/assets/BattleSimulatorView-DI2C9KJc.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/assets/BuildingsView-B00w_iux.js
Normal file
1
docs/assets/BuildingsView-B00w_iux.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/assets/CardDescription-Cb9v68Xa.js
Normal file
1
docs/assets/CardDescription-Cb9v68Xa.js
Normal file
@@ -0,0 +1 @@
|
||||
import{Bt as e,Fn as t,Gn as n,Jn as r,Mn as i,ir as a,rr as o}from"./index-BpOElaf9.js";var s=t({__name:`CardDescription`,props:{class:{}},setup(t){let s=t;return(t,c)=>(n(),i(`p`,{"data-slot":`card-description`,class:a(o(e)(`text-muted-foreground text-sm`,s.class))},[r(t.$slots,`default`)],2))}});export{s as t};
|
||||
2
docs/assets/CardUnlockOverlay-tiSNfyF3.js
Normal file
2
docs/assets/CardUnlockOverlay-tiSNfyF3.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import{n as e}from"./UnlockRequirement-DeOUxO_t.js";import{Fn as t,Gn as n,Mn as r,Nn as i,On as a,Pn as o,Qn as s,bn as c,ct as l,er as u,jn as d,kn as f,rr as p,rt as m,sr as h,w as g,xn as _,zt as v}from"./index-BpOElaf9.js";import{t as y}from"./useGameConfig-DqswvIth.js";import{t as b}from"./AlertDialog-BbpHh-RO.js";var x={key:0,class:`absolute inset-0 z-10 bg-background/70 backdrop-blur-[2px] rounded-lg flex items-center justify-center`},S={class:`text-center p-4 space-y-2`},C={class:`flex justify-center`},w={class:`rounded-full bg-muted p-2`},T={class:`text-xs font-medium text-muted-foreground`},E=t({__name:`CardUnlockOverlay`,props:{requirements:{}},setup(t){let E=t,D=_(),{t:O}=c(),{BUILDINGS:k,TECHNOLOGIES:A}=y(),j=u(null),M=a(()=>!E.requirements||!D.currentPlanet?!0:g(D.currentPlanet,D.player.technologies,E.requirements)),N=()=>{if(!E.requirements||!D.currentPlanet)return``;let e=[];for(let[t,n]of Object.entries(E.requirements))if(Object.values(m).includes(t)){let r=t,i=D.currentPlanet.buildings[r]||0,a=k.value[r]?.name||r,o=i>=n?`✓`:`✗`;e.push(`${o} ${a}: Lv ${n} (${O(`common.current`)}: Lv ${i})`)}else if(Object.values(l).includes(t)){let r=t,i=D.player.technologies[r]||0,a=A.value[r]?.name||r,o=i>=n?`✓`:`✗`;e.push(`${o} ${a}: Lv ${n} (${O(`common.current`)}: Lv ${i})`)}return e.join(`
|
||||
`)},P=()=>{j.value?.show({title:O(`common.requirementsNotMet`),message:N()})};return(t,a)=>M.value?d(``,!0):(n(),r(`div`,x,[f(`div`,S,[f(`div`,C,[f(`div`,w,[o(p(e),{size:20,class:`text-muted-foreground`})])]),f(`p`,T,h(p(O)(`common.locked`)),1),o(p(v),{variant:`outline`,size:`sm`,onClick:P,class:`text-xs`},{default:s(()=>[i(h(p(O)(`common.viewRequirements`)),1)]),_:1})]),o(b,{ref_key:`requirementsDialog`,ref:j},null,512)]))}});export{E as t};
|
||||
1
docs/assets/DefenseView-4OcM0vVE.js
Normal file
1
docs/assets/DefenseView-4OcM0vVE.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/assets/FleetView-DY7yR25i.js
Normal file
1
docs/assets/FleetView-DY7yR25i.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/assets/GalaxyView-C-Ue9wLo.js
Normal file
1
docs/assets/GalaxyView-C-Ue9wLo.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/assets/MessagesView-sKxpYFTS.js
Normal file
1
docs/assets/MessagesView-sKxpYFTS.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/assets/OfficersView-D9JTCZE2.js
Normal file
1
docs/assets/OfficersView-D9JTCZE2.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/assets/OverviewView-BBQfV6K0.js
Normal file
1
docs/assets/OverviewView-BBQfV6K0.js
Normal file
@@ -0,0 +1 @@
|
||||
import{t as e}from"./CardDescription-Cb9v68Xa.js";import{An as t,D as n,E as r,En as i,F as a,Fn as o,Gn as s,It as c,M as l,Mn as u,Nn as d,On as f,Pn as p,Pt as m,Qn as h,U as g,V as _,_t as v,bn as ee,dt as y,ft as b,gt as x,ht as S,ir as C,j as w,jn as T,kn as E,lt as D,mt as O,pt as te,qn as k,rr as A,sr as j,ut as M,vt as N,xn as P,zt as F}from"./index-BpOElaf9.js";import{t as I}from"./useGameConfig-DqswvIth.js";var L={key:0,class:`container mx-auto p-4 sm:p-6 space-y-4 sm:space-y-6`},R={class:`text-center`},z={class:`text-2xl sm:text-3xl font-bold mb-1 sm:mb-2 flex items-center justify-center gap-2`},B={class:`text-xs sm:text-sm text-muted-foreground`},V={key:0,class:`mt-2`},H={key:1,class:`mt-2`},U={class:`flex items-center gap-2`},W={class:`grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 sm:gap-4`},G={class:`text-xs sm:text-sm text-muted-foreground`},K={class:`text-lg sm:text-xl font-bold`},q=o({__name:`OverviewView`,setup(o){let q=P(),{t:J}=ee(),{SHIPS:Y}=I(),X=f(()=>q.currentPlanet),Z=f(()=>X.value?n(X.value,q.player.officers):null),Q=f(()=>X.value?r(X.value,q.player.officers):null),ne=f(()=>{if(!X.value)return 0;let e=a(q.player.officers,Date.now());return l(X.value,{energyProductionBonus:e.energyProductionBonus})}),re=f(()=>X.value?w(X.value):0),ie=[{key:`metal`},{key:`crystal`},{key:`deuterium`},{key:`darkMatter`},{key:`energy`}],$=f(()=>!X.value||X.value.isMoon?null:ae(X.value.id)),ae=e=>q.player.planets.find(t=>t.isMoon&&t.parentPlanetId===e)||null,oe=()=>{$.value&&(q.currentPlanetId=$.value.id)},se=()=>{X.value?.parentPlanetId&&(q.currentPlanetId=X.value.parentPlanetId)};return(n,r)=>X.value?(s(),u(`div`,L,[E(`div`,R,[E(`h1`,z,[d(j(X.value.name)+` `,1),X.value.isMoon?(s(),t(A(c),{key:0,variant:`secondary`},{default:h(()=>[d(j(A(J)(`planet.moon`)),1)]),_:1})):T(``,!0)]),E(`p`,B,j(A(J)(`planet.position`))+`: [`+j(X.value.position.galaxy)+`:`+j(X.value.position.system)+`:`+j(X.value.position.position)+`] `,1),!X.value.isMoon&&$.value?(s(),u(`div`,V,[p(A(F),{onClick:oe,variant:`outline`,size:`sm`},{default:h(()=>[r[0]||=E(`span`,{class:`mr-2`},`🌙`,-1),d(` `+j(A(J)(`planet.switchToMoon`)),1)]),_:1})])):T(``,!0),X.value.isMoon?(s(),u(`div`,H,[p(A(F),{onClick:se,variant:`outline`,size:`sm`},{default:h(()=>[d(j(A(J)(`planet.backToPlanet`)),1)]),_:1})])):T(``,!0)]),p(A(b),null,{default:h(()=>[p(A(M),null,{default:h(()=>[p(A(D),null,{default:h(()=>[d(j(A(J)(`overview.resourceOverview`)),1)]),_:1})]),_:1}),p(A(y),null,{default:h(()=>[p(A(N),null,{default:h(()=>[p(A(te),null,{default:h(()=>[p(A(S),null,{default:h(()=>[p(A(O),null,{default:h(()=>[d(j(A(J)(`common.resourceType`)),1)]),_:1}),p(A(O),{class:`text-right`},{default:h(()=>[d(j(A(J)(`resources.current`)),1)]),_:1}),p(A(O),{class:`text-right`},{default:h(()=>[d(j(A(J)(`resources.max`)),1)]),_:1}),p(A(O),{class:`text-right`},{default:h(()=>[d(j(A(J)(`resources.production`))+j(A(J)(`resources.perHour`)),1)]),_:1})]),_:1})]),_:1}),p(A(v),null,{default:h(()=>[(s(),u(i,null,k(ie,e=>p(A(S),{key:e.key},{default:h(()=>[p(A(x),{class:`font-medium`},{default:h(()=>[E(`div`,U,[p(m,{type:e.key,size:`sm`},null,8,[`type`]),d(` `+j(A(J)(`resources.${e.key}`)),1)])]),_:2},1024),e.key===`energy`?(s(),u(i,{key:0},[p(A(x),{class:C([`text-right`,X.value.resources[e.key]>=0?`text-green-600 dark:text-green-400`:`text-red-600 dark:text-red-400`])},{default:h(()=>[d(j(A(_)(X.value.resources[e.key])),1)]),_:2},1032,[`class`]),p(A(x),{class:`text-right text-muted-foreground`},{default:h(()=>[...r[1]||=[d(`-`,-1)]]),_:1}),p(A(x),{class:`text-right text-muted-foreground`},{default:h(()=>[d(j(A(_)(ne.value))+` / `+j(A(_)(re.value)),1)]),_:1})],64)):(s(),u(i,{key:1},[p(A(x),{class:C([`text-right`,A(g)(X.value.resources[e.key],Q.value?.[e.key]||1/0)])},{default:h(()=>[d(j(A(_)(X.value.resources[e.key])),1)]),_:2},1032,[`class`]),p(A(x),{class:`text-right text-muted-foreground`},{default:h(()=>[d(j(A(_)(Q.value?.[e.key]||0)),1)]),_:2},1024),p(A(x),{class:`text-right text-muted-foreground`},{default:h(()=>[d(j(A(_)(Z.value?.[e.key]||0)),1)]),_:2},1024)],64))]),_:2},1024)),64))]),_:1})]),_:1})]),_:1})]),_:1}),p(A(b),null,{default:h(()=>[p(A(M),null,{default:h(()=>[p(A(D),null,{default:h(()=>[d(j(A(J)(`overview.fleetInfo`)),1)]),_:1}),p(A(e),null,{default:h(()=>[d(j(A(J)(`overview.currentShips`)),1)]),_:1})]),_:1}),p(A(y),null,{default:h(()=>[E(`div`,W,[(s(!0),u(i,null,k(X.value.fleet,(e,t)=>(s(),u(`div`,{key:t},[E(`p`,G,j(A(Y)[t].name),1),E(`p`,K,j(e),1)]))),128))])]),_:1})]),_:1})])):T(``,!0)}});export{q as default};
|
||||
1
docs/assets/ResearchView-Do1WxfNH.js
Normal file
1
docs/assets/ResearchView-Do1WxfNH.js
Normal file
@@ -0,0 +1 @@
|
||||
import{t as e}from"./UnlockRequirement-DeOUxO_t.js";import{t}from"./CardDescription-Cb9v68Xa.js";import{An as n,En as r,Fn as i,Gn as a,It as o,Mn as s,Nn as c,On as l,Pn as u,Pt as d,Qn as f,V as p,W as m,Z as h,bn as ee,ct as te,dt as ne,er as re,ft as ie,ir as g,jn as _,kn as v,lt as ae,qn as oe,rr as y,rt as b,sr as x,ut as S,v as C,w,xn as T,y as E,yt as D,zt as O}from"./index-BpOElaf9.js";import{t as k}from"./useGameConfig-DqswvIth.js";import{t as A}from"./AlertDialog-BbpHh-RO.js";import{t as j}from"./CardUnlockOverlay-tiSNfyF3.js";var M={key:0,class:`container mx-auto p-4 sm:p-6`},N={class:`text-2xl sm:text-3xl font-bold mb-4 sm:mb-6`},P={class:`grid grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4`},F={class:`flex justify-between items-start gap-2`},I={class:`min-w-0 flex-1`},L={class:`space-y-2.5 sm:space-y-3`},se={class:`text-xs sm:text-sm space-y-1.5 sm:space-y-2`},R={class:`text-muted-foreground mb-1 sm:mb-2`},z={class:`space-y-1 sm:space-y-1.5`},B={class:`flex items-center gap-1.5 sm:gap-2`},V={class:`text-xs`},H={class:`flex items-center gap-1.5 sm:gap-2`},U={class:`text-xs`},W={class:`flex items-center gap-1.5 sm:gap-2`},G={class:`text-xs`},K=i({__name:`ResearchView`,setup(i){let K=T(),ce=D(),{t:q}=ee(),{TECHNOLOGIES:J}=k(),Y=l(()=>K.currentPlanet),X=l(()=>K.player),Z=re(null),le=e=>{if(!K.currentPlanet||!E(K.currentPlanet,e,K.player.technologies,K.player.researchQueue).valid)return!1;let t=K.player.technologies[e]||0,{queueItem:n}=C(K.currentPlanet,e,t,K.player.officers);return K.player.researchQueue.push(n),!0},ue=e=>{le(e)||Z.value?.show({title:q(`researchView.researchFailed`),message:q(`researchView.researchFailedMessage`)})},Q=e=>X.value.technologies[e]||0,de=e=>{if(!Y.value||X.value.researchQueue.length>0)return!1;let t=J.value[e],n=$(e,Q(e)+1);return w(Y.value,K.player.technologies,t.requirements)&&Y.value.resources.metal>=n.metal&&Y.value.resources.crystal>=n.crystal&&Y.value.resources.deuterium>=n.deuterium},$=(e,t)=>h(e,t);return(i,l)=>Y.value?(a(),s(`div`,M,[u(e,{"required-building":y(b).ResearchLab,"required-level":1},null,8,[`required-building`]),v(`h1`,N,x(y(q)(`researchView.title`)),1),v(`div`,P,[(a(!0),s(r,null,oe(Object.values(y(te)),e=>(a(),n(y(ie),{key:e,class:`relative`},{default:f(()=>[u(j,{requirements:y(J)[e].requirements},null,8,[`requirements`]),u(y(S),null,{default:f(()=>[v(`div`,F,[v(`div`,I,[u(y(ae),{class:`text-base sm:text-lg cursor-pointer hover:text-primary transition-colors`,onClick:t=>y(ce).openTechnology(e,Q(e))},{default:f(()=>[c(x(y(J)[e].name),1)]),_:2},1032,[`onClick`]),u(y(t),{class:`text-xs sm:text-sm`},{default:f(()=>[c(x(y(J)[e].description),1)]),_:2},1024)]),u(y(o),{variant:`secondary`,class:`text-xs whitespace-nowrap flex-shrink-0`},{default:f(()=>[c(`Lv `+x(Q(e)),1)]),_:2},1024)])]),_:2},1024),u(y(ne),null,{default:f(()=>[v(`div`,L,[v(`div`,se,[v(`p`,R,x(y(q)(`researchView.researchCost`))+`:`,1),v(`div`,z,[v(`div`,B,[u(d,{type:`metal`,size:`sm`}),v(`span`,V,x(y(q)(`resources.metal`))+`:`,1),v(`span`,{class:g([`font-medium text-xs sm:text-sm`,y(m)(Y.value.resources.metal,$(e,Q(e)+1).metal)])},x(y(p)($(e,Q(e)+1).metal)),3)]),v(`div`,H,[u(d,{type:`crystal`,size:`sm`}),v(`span`,U,x(y(q)(`resources.crystal`))+`:`,1),v(`span`,{class:g([`font-medium text-xs sm:text-sm`,y(m)(Y.value.resources.crystal,$(e,Q(e)+1).crystal)])},x(y(p)($(e,Q(e)+1).crystal)),3)]),v(`div`,W,[u(d,{type:`deuterium`,size:`sm`}),v(`span`,G,x(y(q)(`resources.deuterium`))+`:`,1),v(`span`,{class:g([`font-medium text-xs sm:text-sm`,y(m)(Y.value.resources.deuterium,$(e,Q(e)+1).deuterium)])},x(y(p)($(e,Q(e)+1).deuterium)),3)])])]),u(y(O),{onClick:t=>ue(e),disabled:!de(e),class:`w-full`},{default:f(()=>[c(x(y(q)(`researchView.research`)),1)]),_:1},8,[`onClick`,`disabled`])])]),_:2},1024)]),_:2},1024))),128))]),u(A,{ref_key:`alertDialog`,ref:Z},null,512)])):_(``,!0)}});export{K as default};
|
||||
1
docs/assets/SettingsView-IZn-5kW1.js
Normal file
1
docs/assets/SettingsView-IZn-5kW1.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/assets/ShipyardView-BGb4XhA2.js
Normal file
1
docs/assets/ShipyardView-BGb4XhA2.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/assets/UnlockRequirement-DeOUxO_t.js
Normal file
1
docs/assets/UnlockRequirement-DeOUxO_t.js
Normal file
@@ -0,0 +1 @@
|
||||
import{t as e}from"./CardDescription-Cb9v68Xa.js";import{At as t,Fn as n,Gn as r,It as i,Mn as a,Nn as o,On as s,Pn as c,Qn as l,bn as u,dt as d,ft as f,jn as p,jt as m,kn as h,lt as g,rr as _,sr as v,ut as y,wn as b,xn as x,zt as S}from"./index-BpOElaf9.js";import{t as C}from"./useGameConfig-DqswvIth.js";var w=m(`lock`,[[`rect`,{width:`18`,height:`11`,x:`3`,y:`11`,rx:`2`,ry:`2`,key:`1w4ew1`}],[`path`,{d:`M7 11V7a5 5 0 0 1 10 0v4`,key:`fwvmzm`}]]),T={key:0,class:`fixed inset-0 z-50 bg-background/80 backdrop-blur-sm flex items-center justify-center p-4`},E={class:`flex justify-center mb-4`},D={class:`rounded-full bg-muted p-4`},O={class:`p-4 bg-muted rounded-lg space-y-2`},k={class:`text-sm font-medium text-center`},A={class:`flex items-center justify-center gap-2`},j={class:`text-base sm:text-lg font-bold`},M={key:0,class:`text-xs text-center text-muted-foreground`},N={class:`flex gap-2`},P=n({__name:`UnlockRequirement`,props:{requiredBuilding:{},requiredLevel:{}},setup(n){let m=n,P=b(),F=x(),{t:I}=u(),{BUILDINGS:L}=C(),R=s(()=>L.value[m.requiredBuilding]?.name||m.requiredBuilding),z=s(()=>F.currentPlanet&&F.currentPlanet.buildings[m.requiredBuilding]||0),B=s(()=>z.value>=m.requiredLevel),V=()=>{P.push(`/buildings`)};return(s,u)=>B.value?p(``,!0):(r(),a(`div`,T,[c(_(f),{class:`max-w-md w-full`},{default:l(()=>[c(_(y),{class:`text-center`},{default:l(()=>[h(`div`,E,[h(`div`,D,[c(_(w),{size:48,class:`text-muted-foreground`})])]),c(_(g),{class:`text-xl sm:text-2xl`},{default:l(()=>[o(v(_(I)(`common.featureLocked`)),1)]),_:1}),c(_(e),{class:`text-sm sm:text-base`},{default:l(()=>[o(v(_(I)(`common.unlockRequired`)),1)]),_:1})]),_:1}),c(_(d),{class:`space-y-4`},{default:l(()=>[h(`div`,O,[h(`p`,k,v(_(I)(`common.requiredBuilding`))+`:`,1),h(`div`,A,[h(`span`,j,v(R.value),1),c(_(i),{variant:`default`},{default:l(()=>[o(`Lv `+v(n.requiredLevel),1)]),_:1})]),z.value===void 0?p(``,!0):(r(),a(`p`,M,v(_(I)(`common.currentLevel`))+`: Lv `+v(z.value),1))]),h(`div`,N,[c(_(S),{onClick:V,class:`flex-1`},{default:l(()=>[c(_(t),{size:16,class:`mr-2`}),o(` `+v(_(I)(`common.goToBuildings`)),1)]),_:1})])]),_:1})]),_:1})]))}});export{w as n,P as t};
|
||||
1
docs/assets/eye-BrZrKlSB.js
Normal file
1
docs/assets/eye-BrZrKlSB.js
Normal file
@@ -0,0 +1 @@
|
||||
import{jt as e}from"./index-BpOElaf9.js";var t=e(`eye`,[[`path`,{d:`M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0`,key:`1nclc0`}],[`circle`,{cx:`12`,cy:`12`,r:`3`,key:`1v7zrd`}]]);export{t};
|
||||
2
docs/assets/index-BXmZ_V9Y.css
Normal file
2
docs/assets/index-BXmZ_V9Y.css
Normal file
File diff suppressed because one or more lines are too long
53
docs/assets/index-BpOElaf9.js
Normal file
53
docs/assets/index-BpOElaf9.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/assets/logo-Cz0cNqhe.svg
Normal file
1
docs/assets/logo-Cz0cNqhe.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
1
docs/assets/shipValidation-BZIVAW1v.js
Normal file
1
docs/assets/shipValidation-BZIVAW1v.js
Normal file
@@ -0,0 +1 @@
|
||||
import{F as e,N as t,P as n,T as r,a as i,c as a,d as o,f as s,l as c,n as l,o as u,p as d,r as f,s as p,t as m,u as h}from"./index-BpOElaf9.js";const g=(e,n,r,i)=>{let a=u(n,r);return h(n,e.buildings,i)?t(e.resources,a)?{valid:!0}:{valid:!1,reason:`资源不足`}:{valid:!1,reason:`不满足前置条件`}},_=(t,r,a,o)=>{let c=u(r,a),l=i(r,a,e(o,Date.now()).buildingSpeedBonus);return n(t.resources,c),s(r,a,l)},v=(e,n,r,i)=>{let a=l(n,r);return p(n,e.buildings,i)?t(e.resources,a)?c(n,e.defense,r)?{valid:!0}:{valid:!1,reason:`护盾罩数量限制`}:{valid:!1,reason:`资源不足`}:{valid:!1,reason:`不满足前置条件`}},y=(t,r,i,a)=>{let s=l(r,i),c=m(r,i,e(a,Date.now()).buildingSpeedBonus);return n(t.resources,s),o(r,i,c)},b=(t,n,i,o,s=0)=>{let c=e(o,Date.now());if(s>=r(c.additionalFleetSlots))return{valid:!1,reason:`舰队任务槽位已满`};if(!a(t.fleet,n))return{valid:!1,reason:`舰队数量不足`};let l=f(n,c.fuelConsumptionReduction,i);return t.resources.deuterium<l?{valid:!1,reason:`燃料不足`,fuelNeeded:l}:{valid:!0,fuelNeeded:l}},x=(e,t,r,i,a)=>{d(e.fleet,t),e.resources.deuterium-=r,i&&n(e.resources,a)};export{b as a,v as i,x as n,g as o,_ as r,y as t};
|
||||
1
docs/assets/useGameConfig-DqswvIth.js
Normal file
1
docs/assets/useGameConfig-DqswvIth.js
Normal file
@@ -0,0 +1 @@
|
||||
import{$ as e,On as t,Q as n,bn as r,ct as i,et as a,it as o,nt as s,ot as c,rt as l,st as u,tt as d}from"./index-BpOElaf9.js";const f=()=>{let{t:f}=r(),p={[l.MetalMine]:`metalMine`,[l.CrystalMine]:`crystalMine`,[l.DeuteriumSynthesizer]:`deuteriumSynthesizer`,[l.SolarPlant]:`solarPlant`,[l.RoboticsFactory]:`roboticsFactory`,[l.NaniteFactory]:`naniteFactory`,[l.Shipyard]:`shipyard`,[l.ResearchLab]:`researchLab`,[l.MetalStorage]:`metalStorage`,[l.CrystalStorage]:`crystalStorage`,[l.DeuteriumTank]:`deuteriumTank`,[l.DarkMatterCollector]:`darkMatterCollector`,[l.LunarBase]:`lunarBase`,[l.SensorPhalanx]:`sensorPhalanx`,[l.JumpGate]:`jumpGate`},m={[u.LightFighter]:`lightFighter`,[u.HeavyFighter]:`heavyFighter`,[u.Cruiser]:`cruiser`,[u.Battleship]:`battleship`,[u.SmallCargo]:`smallCargo`,[u.LargeCargo]:`largeCargo`,[u.ColonyShip]:`colonyShip`,[u.Recycler]:`recycler`,[u.EspionageProbe]:`espionageProbe`,[u.DarkMatterHarvester]:`darkMatterHarvester`},h={[o.RocketLauncher]:`rocketLauncher`,[o.LightLaser]:`lightLaser`,[o.HeavyLaser]:`heavyLaser`,[o.GaussCannon]:`gaussCannon`,[o.IonCannon]:`ionCannon`,[o.PlasmaTurret]:`plasmaTurret`,[o.SmallShieldDome]:`smallShieldDome`,[o.LargeShieldDome]:`largeShieldDome`},g={[i.EnergyTechnology]:`energyTechnology`,[i.LaserTechnology]:`laserTechnology`,[i.IonTechnology]:`ionTechnology`,[i.HyperspaceTechnology]:`hyperspaceTechnology`,[i.PlasmaTechnology]:`plasmaTechnology`,[i.ComputerTechnology]:`computerTechnology`,[i.CombustionDrive]:`combustionDrive`,[i.ImpulseDrive]:`impulseDrive`,[i.HyperspaceDrive]:`hyperspaceDrive`,[i.DarkMatterTechnology]:`darkMatterTechnology`},_={[c.Commander]:`commander`,[c.Admiral]:`admiral`,[c.Engineer]:`engineer`,[c.Geologist]:`geologist`,[c.Technocrat]:`technocrat`,[c.DarkMatterSpecialist]:`darkMatterSpecialist`};return{BUILDINGS:t(()=>{let e={};for(let[t,r]of Object.entries(n)){let n=t,i=p[n];e[n]={...r,name:f(`buildings.${i}`),description:f(`buildingDescriptions.${i}`)}}return e}),SHIPS:t(()=>{let e={};for(let[t,n]of Object.entries(d)){let r=t,i=m[r];e[r]={...n,name:f(`ships.${i}`),description:f(`shipDescriptions.${i}`)}}return e}),DEFENSES:t(()=>{let t={};for(let[n,r]of Object.entries(e)){let e=n,i=h[e];t[e]={...r,name:f(`defenses.${i}`),description:f(`defenseDescriptions.${i}`)}}return t}),TECHNOLOGIES:t(()=>{let e={};for(let[t,n]of Object.entries(s)){let r=t,i=g[r];e[r]={...n,name:f(`technologies.${i}`),description:f(`technologyDescriptions.${i}`)}}return e}),OFFICERS:t(()=>{let e={};for(let[t,n]of Object.entries(a)){let r=t,i=_[r];e[r]={...n,name:f(`officers.${i}`),description:f(`officerDescriptions.${i}`)}}return e})}};export{f as t};
|
||||
17
docs/index.html
Normal file
17
docs/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="./logo.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>OGame-Vue-Ts</title>
|
||||
<script type="module" crossorigin src="./assets/index-BpOElaf9.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-BXmZ_V9Y.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
1
docs/logo.svg
Normal file
1
docs/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
20
index.html
Normal file
20
index.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-cmn-Hans">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
|
||||
<title>OGame-Vue-Ts</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<!-- 统计勿删 -->
|
||||
<script charset="UTF-8" id="LA_COLLECT" src="//sdk.51.la/js-sdk-pro.min.js"></script>
|
||||
<script>LA.init({ id: "L298GYqn6JhAO0VU", ck: "L298GYqn6JhAO0VU", autoTrack: true, hashMode: true })</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
48
package.json
Normal file
48
package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "ogame-vue-ts",
|
||||
"title": "OGame-Vue-Ts",
|
||||
"qq": "920930589",
|
||||
"id": "2zBlHPUA6E",
|
||||
"author": "setube",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --port 25121",
|
||||
"build": "vue-tsc -b && vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.17",
|
||||
"@tanstack/vue-table": "^8.21.3",
|
||||
"@vueuse/core": "^14.1.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-vue-next": "^0.556.0",
|
||||
"pinia": "^3.0.4",
|
||||
"pinia-plugin-persistedstate": "^4.7.1",
|
||||
"reka-ui": "^2.6.1",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"vue": "^3.5.24",
|
||||
"vue-router": "4",
|
||||
"vue-sonner": "^2.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/node": "^24.10.2",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vue/tsconfig": "^0.8.1",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "npm:rolldown-vite@7.2.5",
|
||||
"vue-tsc": "^3.1.4"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"vite": "npm:rolldown-vite@7.2.5"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.13.1+sha512.37ebf1a5c7a30d5fabe0c5df44ee8da4c965ca0c5af3dbab28c3a1681b70a256218d05c81c9c0dcf767ef6b8551eb5b960042b9ed4300c59242336377e01cfad"
|
||||
}
|
||||
1443
pnpm-lock.yaml
generated
Normal file
1443
pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,1443 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
vite: npm:rolldown-vite@7.2.5
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.17
|
||||
version: 4.1.17(rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1))
|
||||
'@tanstack/vue-table':
|
||||
specifier: ^8.21.3
|
||||
version: 8.21.3(vue@3.5.25(typescript@5.9.3))
|
||||
'@vueuse/core':
|
||||
specifier: ^14.1.0
|
||||
version: 14.1.0(vue@3.5.25(typescript@5.9.3))
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
clsx:
|
||||
specifier: ^2.1.1
|
||||
version: 2.1.1
|
||||
lucide-vue-next:
|
||||
specifier: ^0.556.0
|
||||
version: 0.556.0(vue@3.5.25(typescript@5.9.3))
|
||||
pinia:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))
|
||||
pinia-plugin-persistedstate:
|
||||
specifier: ^4.7.1
|
||||
version: 4.7.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3)))
|
||||
reka-ui:
|
||||
specifier: ^2.6.1
|
||||
version: 2.6.1(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))
|
||||
tailwind-merge:
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.0
|
||||
tailwindcss:
|
||||
specifier: ^4.1.17
|
||||
version: 4.1.17
|
||||
vue:
|
||||
specifier: ^3.5.24
|
||||
version: 3.5.25(typescript@5.9.3)
|
||||
vue-router:
|
||||
specifier: '4'
|
||||
version: 4.6.3(vue@3.5.25(typescript@5.9.3))
|
||||
vue-sonner:
|
||||
specifier: ^2.0.9
|
||||
version: 2.0.9
|
||||
devDependencies:
|
||||
'@types/crypto-js':
|
||||
specifier: ^4.2.2
|
||||
version: 4.2.2
|
||||
'@types/file-saver':
|
||||
specifier: ^2.0.7
|
||||
version: 2.0.7
|
||||
'@types/node':
|
||||
specifier: ^24.10.2
|
||||
version: 24.10.2
|
||||
'@vitejs/plugin-vue':
|
||||
specifier: ^6.0.1
|
||||
version: 6.0.2(rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1))(vue@3.5.25(typescript@5.9.3))
|
||||
'@vue/tsconfig':
|
||||
specifier: ^0.8.1
|
||||
version: 0.8.1(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))
|
||||
tw-animate-css:
|
||||
specifier: ^1.4.0
|
||||
version: 1.4.0
|
||||
typescript:
|
||||
specifier: ~5.9.3
|
||||
version: 5.9.3
|
||||
vite:
|
||||
specifier: npm:rolldown-vite@7.2.5
|
||||
version: rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1)
|
||||
vue-tsc:
|
||||
specifier: ^3.1.4
|
||||
version: 3.1.8(typescript@5.9.3)
|
||||
|
||||
packages:
|
||||
|
||||
'@babel/helper-string-parser@7.27.1':
|
||||
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/helper-validator-identifier@7.28.5':
|
||||
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@babel/parser@7.28.5':
|
||||
resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@babel/types@7.28.5':
|
||||
resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@emnapi/core@1.7.1':
|
||||
resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==}
|
||||
|
||||
'@emnapi/runtime@1.7.1':
|
||||
resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}
|
||||
|
||||
'@emnapi/wasi-threads@1.1.0':
|
||||
resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
|
||||
|
||||
'@floating-ui/core@1.7.3':
|
||||
resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
|
||||
|
||||
'@floating-ui/dom@1.7.4':
|
||||
resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
|
||||
|
||||
'@floating-ui/utils@0.2.10':
|
||||
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||
|
||||
'@floating-ui/vue@1.1.9':
|
||||
resolution: {integrity: sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==}
|
||||
|
||||
'@internationalized/date@3.10.0':
|
||||
resolution: {integrity: sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==}
|
||||
|
||||
'@internationalized/number@3.6.5':
|
||||
resolution: {integrity: sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==}
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@napi-rs/wasm-runtime@1.1.0':
|
||||
resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==}
|
||||
|
||||
'@oxc-project/runtime@0.97.0':
|
||||
resolution: {integrity: sha512-yH0zw7z+jEws4dZ4IUKoix5Lh3yhqIJWF9Dc8PWvhpo7U7O+lJrv7ZZL4BeRO0la8LBQFwcCewtLBnVV7hPe/w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
|
||||
'@oxc-project/types@0.97.0':
|
||||
resolution: {integrity: sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==}
|
||||
|
||||
'@rolldown/binding-android-arm64@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rolldown/binding-darwin-arm64@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-+JRqKJhoFlt5r9q+DecAGPLZ5PxeLva+wCMtAuoFMWPoZzgcYrr599KQ+Ix0jwll4B4HGP43avu9My8KtSOR+w==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rolldown/binding-darwin-x64@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-fFXDjXnuX7/gQZQm/1FoivVtRcyAzdjSik7Eo+9iwPQ9EgtA5/nB2+jmbzaKtMGG3q+BnZbdKHCtOacmNrkIDA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rolldown/binding-freebsd-x64@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-F1b6vARy49tjmT/hbloplzgJS7GIvwWZqt+tAHEstCh0JIh9sa8FAMVqEmYxDviqKBaAI8iVvUREm/Kh/PD26Q==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-U6cR76N8T8M6lHj7EZrQ3xunLPxSvYYxA8vJsBKZiFZkT8YV4kjgCO3KwMJL0NOjQCPGKyiXO07U+KmJzdPGRw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-ONgyjofCrrE3bnh5GZb8EINSFyR/hmwTzZ7oVuyUB170lboza1VMCnb8jgE6MsyyRgHYmN8Lb59i3NKGrxrYjw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rolldown/binding-linux-x64-musl@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rolldown/binding-openharmony-arm64@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@rolldown/binding-wasm32-wasi@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-nmCN0nIdeUnmgeDXiQ+2HU6FT162o+rxnF7WMkBm4M5Ds8qTU7Dzv2Wrf22bo4ftnlrb2hKK6FSwAJSAe2FWLg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [wasm32]
|
||||
|
||||
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-7kcNLi7Ua59JTTLvbe1dYb028QEPaJPJQHqkmSZ5q3tJueUeb6yjRtx8mw4uIqgWZcnQHAR3PrLN4XRJxvgIkA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-lL70VTNvSCdSZkDPPVMwWn/M2yQiYvSoXw9hTLgdIWdUfC3g72UaruezusR6ceRuwHCY1Ayu2LtKqXkBO5LIwg==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-4qU4x5DXWB4JPjyTne/wBNPqkbQU8J45bl21geERBKtEittleonioACBL1R0PsBu0Aq21SwMK5a9zdBkWSlQtQ==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.50':
|
||||
resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==}
|
||||
|
||||
'@swc/helpers@0.5.17':
|
||||
resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
|
||||
|
||||
'@tailwindcss/node@4.1.17':
|
||||
resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==}
|
||||
|
||||
'@tailwindcss/oxide-android-arm64@4.1.17':
|
||||
resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@tailwindcss/oxide-darwin-arm64@4.1.17':
|
||||
resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@tailwindcss/oxide-darwin-x64@4.1.17':
|
||||
resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@tailwindcss/oxide-freebsd-x64@4.1.17':
|
||||
resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17':
|
||||
resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-gnu@4.1.17':
|
||||
resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.17':
|
||||
resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.17':
|
||||
resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.17':
|
||||
resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.17':
|
||||
resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
cpu: [wasm32]
|
||||
bundledDependencies:
|
||||
- '@napi-rs/wasm-runtime'
|
||||
- '@emnapi/core'
|
||||
- '@emnapi/runtime'
|
||||
- '@tybys/wasm-util'
|
||||
- '@emnapi/wasi-threads'
|
||||
- tslib
|
||||
|
||||
'@tailwindcss/oxide-win32-arm64-msvc@4.1.17':
|
||||
resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@tailwindcss/oxide-win32-x64-msvc@4.1.17':
|
||||
resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@tailwindcss/oxide@4.1.17':
|
||||
resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@tailwindcss/vite@4.1.17':
|
||||
resolution: {integrity: sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA==}
|
||||
peerDependencies:
|
||||
vite: ^5.2.0 || ^6 || ^7
|
||||
|
||||
'@tanstack/table-core@8.21.3':
|
||||
resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@tanstack/virtual-core@3.13.13':
|
||||
resolution: {integrity: sha512-uQFoSdKKf5S8k51W5t7b2qpfkyIbdHMzAn+AMQvHPxKUPeo1SsGaA4JRISQT87jm28b7z8OEqPcg1IOZagQHcA==}
|
||||
|
||||
'@tanstack/vue-table@8.21.3':
|
||||
resolution: {integrity: sha512-rusRyd77c5tDPloPskctMyPLFEQUeBzxdQ+2Eow4F7gDPlPOB1UnnhzfpdvqZ8ZyX2rRNGmqNnQWm87OI2OQPw==}
|
||||
engines: {node: '>=12'}
|
||||
peerDependencies:
|
||||
vue: '>=3.2'
|
||||
|
||||
'@tanstack/vue-virtual@3.13.13':
|
||||
resolution: {integrity: sha512-Cf2xIEE8nWAfsX0N5nihkPYMeQRT+pHt4NEkuP8rNCn6lVnLDiV8rC8IeIxbKmQC0yPnj4SIBLwXYVf86xxKTQ==}
|
||||
peerDependencies:
|
||||
vue: ^2.7.0 || ^3.0.0
|
||||
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
|
||||
|
||||
'@types/crypto-js@4.2.2':
|
||||
resolution: {integrity: sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==}
|
||||
|
||||
'@types/file-saver@2.0.7':
|
||||
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
|
||||
|
||||
'@types/node@24.10.2':
|
||||
resolution: {integrity: sha512-WOhQTZ4G8xZ1tjJTvKOpyEVSGgOTvJAfDK3FNFgELyaTpzhdgHVHeqW8V+UJvzF5BT+/B54T/1S2K6gd9c7bbA==}
|
||||
|
||||
'@types/web-bluetooth@0.0.21':
|
||||
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
||||
|
||||
'@vitejs/plugin-vue@6.0.2':
|
||||
resolution: {integrity: sha512-iHmwV3QcVGGvSC1BG5bZ4z6iwa1SOpAPWmnjOErd4Ske+lZua5K9TtAVdx0gMBClJ28DViCbSmZitjWZsWO3LA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
peerDependencies:
|
||||
vite: ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||
vue: ^3.2.25
|
||||
|
||||
'@volar/language-core@2.4.26':
|
||||
resolution: {integrity: sha512-hH0SMitMxnB43OZpyF1IFPS9bgb2I3bpCh76m2WEK7BE0A0EzpYsRp0CCH2xNKshr7kacU5TQBLYn4zj7CG60A==}
|
||||
|
||||
'@volar/source-map@2.4.26':
|
||||
resolution: {integrity: sha512-JJw0Tt/kSFsIRmgTQF4JSt81AUSI1aEye5Zl65EeZ8H35JHnTvFGmpDOBn5iOxd48fyGE+ZvZBp5FcgAy/1Qhw==}
|
||||
|
||||
'@volar/typescript@2.4.26':
|
||||
resolution: {integrity: sha512-N87ecLD48Sp6zV9zID/5yuS1+5foj0DfuYGdQ6KHj/IbKvyKv1zNX6VCmnKYwtmHadEO6mFc2EKISiu3RDPAvA==}
|
||||
|
||||
'@vue/compiler-core@3.5.25':
|
||||
resolution: {integrity: sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==}
|
||||
|
||||
'@vue/compiler-dom@3.5.25':
|
||||
resolution: {integrity: sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==}
|
||||
|
||||
'@vue/compiler-sfc@3.5.25':
|
||||
resolution: {integrity: sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==}
|
||||
|
||||
'@vue/compiler-ssr@3.5.25':
|
||||
resolution: {integrity: sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==}
|
||||
|
||||
'@vue/devtools-api@6.6.4':
|
||||
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
|
||||
|
||||
'@vue/devtools-api@7.7.9':
|
||||
resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==}
|
||||
|
||||
'@vue/devtools-kit@7.7.9':
|
||||
resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==}
|
||||
|
||||
'@vue/devtools-shared@7.7.9':
|
||||
resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==}
|
||||
|
||||
'@vue/language-core@3.1.8':
|
||||
resolution: {integrity: sha512-PfwAW7BLopqaJbneChNL6cUOTL3GL+0l8paYP5shhgY5toBNidWnMXWM+qDwL7MC9+zDtzCF2enT8r6VPu64iw==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@vue/reactivity@3.5.25':
|
||||
resolution: {integrity: sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==}
|
||||
|
||||
'@vue/runtime-core@3.5.25':
|
||||
resolution: {integrity: sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==}
|
||||
|
||||
'@vue/runtime-dom@3.5.25':
|
||||
resolution: {integrity: sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==}
|
||||
|
||||
'@vue/server-renderer@3.5.25':
|
||||
resolution: {integrity: sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==}
|
||||
peerDependencies:
|
||||
vue: 3.5.25
|
||||
|
||||
'@vue/shared@3.5.25':
|
||||
resolution: {integrity: sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==}
|
||||
|
||||
'@vue/tsconfig@0.8.1':
|
||||
resolution: {integrity: sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==}
|
||||
peerDependencies:
|
||||
typescript: 5.x
|
||||
vue: ^3.4.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
vue:
|
||||
optional: true
|
||||
|
||||
'@vueuse/core@12.8.2':
|
||||
resolution: {integrity: sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==}
|
||||
|
||||
'@vueuse/core@14.1.0':
|
||||
resolution: {integrity: sha512-rgBinKs07hAYyPF834mDTigH7BtPqvZ3Pryuzt1SD/lg5wEcWqvwzXXYGEDb2/cP0Sj5zSvHl3WkmMELr5kfWw==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
'@vueuse/metadata@12.8.2':
|
||||
resolution: {integrity: sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==}
|
||||
|
||||
'@vueuse/metadata@14.1.0':
|
||||
resolution: {integrity: sha512-7hK4g015rWn2PhKcZ99NyT+ZD9sbwm7SGvp7k+k+rKGWnLjS/oQozoIZzWfCewSUeBmnJkIb+CNr7Zc/EyRnnA==}
|
||||
|
||||
'@vueuse/shared@12.8.2':
|
||||
resolution: {integrity: sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==}
|
||||
|
||||
'@vueuse/shared@14.1.0':
|
||||
resolution: {integrity: sha512-EcKxtYvn6gx1F8z9J5/rsg3+lTQnvOruQd8fUecW99DCK04BkWD7z5KQ/wTAx+DazyoEE9dJt/zV8OIEQbM6kw==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
alien-signals@3.1.1:
|
||||
resolution: {integrity: sha512-ogkIWbVrLwKtHY6oOAXaYkAxP+cTH7V5FZ5+Tm4NZFd8VDZ6uNMDrfzqctTZ42eTMCSR3ne3otpcxmqSnFfPYA==}
|
||||
|
||||
aria-hidden@1.2.6:
|
||||
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
birpc@2.9.0:
|
||||
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
|
||||
|
||||
class-variance-authority@0.7.1:
|
||||
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
|
||||
|
||||
clsx@2.1.1:
|
||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
copy-anything@4.0.5:
|
||||
resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
defu@6.1.4:
|
||||
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
|
||||
|
||||
detect-libc@2.1.2:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
enhanced-resolve@5.18.3:
|
||||
resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
entities@4.5.0:
|
||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||
engines: {node: '>=0.12'}
|
||||
|
||||
estree-walker@2.0.2:
|
||||
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
graceful-fs@4.2.11:
|
||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||
|
||||
hookable@5.5.3:
|
||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||
|
||||
is-what@5.5.0:
|
||||
resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
jiti@2.6.1:
|
||||
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
|
||||
hasBin: true
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
lightningcss-darwin-arm64@1.30.2:
|
||||
resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-darwin-x64@1.30.2:
|
||||
resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
lightningcss-freebsd-x64@1.30.2:
|
||||
resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.30.2:
|
||||
resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss-win32-x64-msvc@1.30.2:
|
||||
resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
lightningcss@1.30.2:
|
||||
resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
|
||||
lucide-vue-next@0.556.0:
|
||||
resolution: {integrity: sha512-JvdCM2smkWrMDhkfD/FpZiWekkbWD6MZLstIFx/FOVZgULrnMr5hegCB9LlTdgllEFnQYQs8hhHC1WYcAV9HTA==}
|
||||
peerDependencies:
|
||||
vue: '>=3.0.1'
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
mitt@3.0.1:
|
||||
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||
|
||||
muggle-string@0.4.1:
|
||||
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
|
||||
|
||||
nanoid@3.3.11:
|
||||
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
ohash@2.0.11:
|
||||
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
|
||||
|
||||
path-browserify@1.0.1:
|
||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
||||
|
||||
perfect-debounce@1.0.0:
|
||||
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
picomatch@4.0.3:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pinia-plugin-persistedstate@4.7.1:
|
||||
resolution: {integrity: sha512-WHOqh2esDlR3eAaknPbqXrkkj0D24h8shrDPqysgCFR6ghqP/fpFfJmMPJp0gETHsvrh9YNNg6dQfo2OEtDnIQ==}
|
||||
peerDependencies:
|
||||
'@nuxt/kit': '>=3.0.0'
|
||||
'@pinia/nuxt': '>=0.10.0'
|
||||
pinia: '>=3.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@nuxt/kit':
|
||||
optional: true
|
||||
'@pinia/nuxt':
|
||||
optional: true
|
||||
pinia:
|
||||
optional: true
|
||||
|
||||
pinia@3.0.4:
|
||||
resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==}
|
||||
peerDependencies:
|
||||
typescript: '>=4.5.0'
|
||||
vue: ^3.5.11
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
postcss@8.5.6:
|
||||
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
reka-ui@2.6.1:
|
||||
resolution: {integrity: sha512-XK7cJDQoNuGXfCNzBBo/81Yg/OgjPwvbabnlzXG2VsdSgNsT6iIkuPBPr+C0Shs+3bb0x0lbPvgQAhMSCKm5Ww==}
|
||||
peerDependencies:
|
||||
vue: '>= 3.2.0'
|
||||
|
||||
rfdc@1.4.1:
|
||||
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||
|
||||
rolldown-vite@7.2.5:
|
||||
resolution: {integrity: sha512-u09tdk/huMiN8xwoiBbig197jKdCamQTtOruSalOzbqGje3jdHiV0njQlAW0YvzoahkirFePNQ4RYlfnRQpXZA==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^20.19.0 || >=22.12.0
|
||||
esbuild: ^0.25.0
|
||||
jiti: '>=1.21.0'
|
||||
less: ^4.0.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
|
||||
esbuild:
|
||||
optional: true
|
||||
jiti:
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
sass-embedded:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
tsx:
|
||||
optional: true
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
rolldown@1.0.0-beta.50:
|
||||
resolution: {integrity: sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
speakingurl@14.0.1:
|
||||
resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
superjson@2.2.6:
|
||||
resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
tailwind-merge@3.4.0:
|
||||
resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
|
||||
|
||||
tailwindcss@4.1.17:
|
||||
resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==}
|
||||
|
||||
tapable@2.3.0:
|
||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tw-animate-css@1.4.0:
|
||||
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.16.0:
|
||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||
|
||||
vscode-uri@3.1.0:
|
||||
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
|
||||
|
||||
vue-demi@0.14.10:
|
||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.0.0-rc.1
|
||||
vue: ^3.0.0-0 || ^2.6.0
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
|
||||
vue-router@4.6.3:
|
||||
resolution: {integrity: sha512-ARBedLm9YlbvQomnmq91Os7ck6efydTSpRP3nuOKCvgJOHNrhRoJDSKtee8kcL1Vf7nz6U+PMBL+hTvR3bTVQg==}
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
vue-sonner@2.0.9:
|
||||
resolution: {integrity: sha512-i6BokNlNDL93fpzNxN/LZSn6D6MzlO+i3qXt6iVZne3x1k7R46d5HlFB4P8tYydhgqOrRbIZEsnRd3kG7qGXyw==}
|
||||
peerDependencies:
|
||||
'@nuxt/kit': ^4.0.3
|
||||
'@nuxt/schema': ^4.0.3
|
||||
nuxt: ^4.0.3
|
||||
peerDependenciesMeta:
|
||||
'@nuxt/kit':
|
||||
optional: true
|
||||
'@nuxt/schema':
|
||||
optional: true
|
||||
nuxt:
|
||||
optional: true
|
||||
|
||||
vue-tsc@3.1.8:
|
||||
resolution: {integrity: sha512-deKgwx6exIHeZwF601P1ktZKNF0bepaSN4jBU3AsbldPx9gylUc1JDxYppl82yxgkAgaz0Y0LCLOi+cXe9HMYA==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
typescript: '>=5.0.0'
|
||||
|
||||
vue@3.5.25:
|
||||
resolution: {integrity: sha512-YLVdgv2K13WJ6n+kD5owehKtEXwdwXuj2TTyJMsO7pSeKw2bfRNZGjhB7YzrpbMYj5b5QsUebHpOqR3R3ziy/g==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@babel/helper-string-parser@7.27.1': {}
|
||||
|
||||
'@babel/helper-validator-identifier@7.28.5': {}
|
||||
|
||||
'@babel/parser@7.28.5':
|
||||
dependencies:
|
||||
'@babel/types': 7.28.5
|
||||
|
||||
'@babel/types@7.28.5':
|
||||
dependencies:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@emnapi/core@1.7.1':
|
||||
dependencies:
|
||||
'@emnapi/wasi-threads': 1.1.0
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.7.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/wasi-threads@1.1.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@floating-ui/core@1.7.3':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/dom@1.7.4':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.3
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
|
||||
'@floating-ui/vue@1.1.9(vue@3.5.25(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.4
|
||||
'@floating-ui/utils': 0.2.10
|
||||
vue-demi: 0.14.10(vue@3.5.25(typescript@5.9.3))
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@internationalized/date@3.10.0':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.17
|
||||
|
||||
'@internationalized/number@3.6.5':
|
||||
dependencies:
|
||||
'@swc/helpers': 0.5.17
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/remapping@2.3.5':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@napi-rs/wasm-runtime@1.1.0':
|
||||
dependencies:
|
||||
'@emnapi/core': 1.7.1
|
||||
'@emnapi/runtime': 1.7.1
|
||||
'@tybys/wasm-util': 0.10.1
|
||||
optional: true
|
||||
|
||||
'@oxc-project/runtime@0.97.0': {}
|
||||
|
||||
'@oxc-project/types@0.97.0': {}
|
||||
|
||||
'@rolldown/binding-android-arm64@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-darwin-arm64@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-darwin-x64@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-freebsd-x64@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-arm64-gnu@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-arm64-musl@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-x64-gnu@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-linux-x64-musl@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-openharmony-arm64@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-wasm32-wasi@1.0.0-beta.50':
|
||||
dependencies:
|
||||
'@napi-rs/wasm-runtime': 1.1.0
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-win32-arm64-msvc@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-win32-ia32-msvc@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/binding-win32-x64-msvc@1.0.0-beta.50':
|
||||
optional: true
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.50': {}
|
||||
|
||||
'@swc/helpers@0.5.17':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@tailwindcss/node@4.1.17':
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
enhanced-resolve: 5.18.3
|
||||
jiti: 2.6.1
|
||||
lightningcss: 1.30.2
|
||||
magic-string: 0.30.21
|
||||
source-map-js: 1.2.1
|
||||
tailwindcss: 4.1.17
|
||||
|
||||
'@tailwindcss/oxide-android-arm64@4.1.17':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-darwin-arm64@4.1.17':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-darwin-x64@4.1.17':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-freebsd-x64@4.1.17':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-gnu@4.1.17':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.17':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.17':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.17':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.17':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-win32-arm64-msvc@4.1.17':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide-win32-x64-msvc@4.1.17':
|
||||
optional: true
|
||||
|
||||
'@tailwindcss/oxide@4.1.17':
|
||||
optionalDependencies:
|
||||
'@tailwindcss/oxide-android-arm64': 4.1.17
|
||||
'@tailwindcss/oxide-darwin-arm64': 4.1.17
|
||||
'@tailwindcss/oxide-darwin-x64': 4.1.17
|
||||
'@tailwindcss/oxide-freebsd-x64': 4.1.17
|
||||
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17
|
||||
'@tailwindcss/oxide-linux-arm64-gnu': 4.1.17
|
||||
'@tailwindcss/oxide-linux-arm64-musl': 4.1.17
|
||||
'@tailwindcss/oxide-linux-x64-gnu': 4.1.17
|
||||
'@tailwindcss/oxide-linux-x64-musl': 4.1.17
|
||||
'@tailwindcss/oxide-wasm32-wasi': 4.1.17
|
||||
'@tailwindcss/oxide-win32-arm64-msvc': 4.1.17
|
||||
'@tailwindcss/oxide-win32-x64-msvc': 4.1.17
|
||||
|
||||
'@tailwindcss/vite@4.1.17(rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.1.17
|
||||
'@tailwindcss/oxide': 4.1.17
|
||||
tailwindcss: 4.1.17
|
||||
vite: rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1)
|
||||
|
||||
'@tanstack/table-core@8.21.3': {}
|
||||
|
||||
'@tanstack/virtual-core@3.13.13': {}
|
||||
|
||||
'@tanstack/vue-table@8.21.3(vue@3.5.25(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@tanstack/table-core': 8.21.3
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
'@tanstack/vue-virtual@3.13.13(vue@3.5.25(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@tanstack/virtual-core': 3.13.13
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
'@tybys/wasm-util@0.10.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@types/crypto-js@4.2.2': {}
|
||||
|
||||
'@types/file-saver@2.0.7': {}
|
||||
|
||||
'@types/node@24.10.2':
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/web-bluetooth@0.0.21': {}
|
||||
|
||||
'@vitejs/plugin-vue@6.0.2(rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1))(vue@3.5.25(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@rolldown/pluginutils': 1.0.0-beta.50
|
||||
vite: rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1)
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
'@volar/language-core@2.4.26':
|
||||
dependencies:
|
||||
'@volar/source-map': 2.4.26
|
||||
|
||||
'@volar/source-map@2.4.26': {}
|
||||
|
||||
'@volar/typescript@2.4.26':
|
||||
dependencies:
|
||||
'@volar/language-core': 2.4.26
|
||||
path-browserify: 1.0.1
|
||||
vscode-uri: 3.1.0
|
||||
|
||||
'@vue/compiler-core@3.5.25':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.5
|
||||
'@vue/shared': 3.5.25
|
||||
entities: 4.5.0
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-dom@3.5.25':
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.5.25
|
||||
'@vue/shared': 3.5.25
|
||||
|
||||
'@vue/compiler-sfc@3.5.25':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.5
|
||||
'@vue/compiler-core': 3.5.25
|
||||
'@vue/compiler-dom': 3.5.25
|
||||
'@vue/compiler-ssr': 3.5.25
|
||||
'@vue/shared': 3.5.25
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.21
|
||||
postcss: 8.5.6
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-ssr@3.5.25':
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.25
|
||||
'@vue/shared': 3.5.25
|
||||
|
||||
'@vue/devtools-api@6.6.4': {}
|
||||
|
||||
'@vue/devtools-api@7.7.9':
|
||||
dependencies:
|
||||
'@vue/devtools-kit': 7.7.9
|
||||
|
||||
'@vue/devtools-kit@7.7.9':
|
||||
dependencies:
|
||||
'@vue/devtools-shared': 7.7.9
|
||||
birpc: 2.9.0
|
||||
hookable: 5.5.3
|
||||
mitt: 3.0.1
|
||||
perfect-debounce: 1.0.0
|
||||
speakingurl: 14.0.1
|
||||
superjson: 2.2.6
|
||||
|
||||
'@vue/devtools-shared@7.7.9':
|
||||
dependencies:
|
||||
rfdc: 1.4.1
|
||||
|
||||
'@vue/language-core@3.1.8(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@volar/language-core': 2.4.26
|
||||
'@vue/compiler-dom': 3.5.25
|
||||
'@vue/shared': 3.5.25
|
||||
alien-signals: 3.1.1
|
||||
muggle-string: 0.4.1
|
||||
path-browserify: 1.0.1
|
||||
picomatch: 4.0.3
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
'@vue/reactivity@3.5.25':
|
||||
dependencies:
|
||||
'@vue/shared': 3.5.25
|
||||
|
||||
'@vue/runtime-core@3.5.25':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.25
|
||||
'@vue/shared': 3.5.25
|
||||
|
||||
'@vue/runtime-dom@3.5.25':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.25
|
||||
'@vue/runtime-core': 3.5.25
|
||||
'@vue/shared': 3.5.25
|
||||
csstype: 3.2.3
|
||||
|
||||
'@vue/server-renderer@3.5.25(vue@3.5.25(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.5.25
|
||||
'@vue/shared': 3.5.25
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
'@vue/shared@3.5.25': {}
|
||||
|
||||
'@vue/tsconfig@0.8.1(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))':
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
'@vueuse/core@12.8.2(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.21
|
||||
'@vueuse/metadata': 12.8.2
|
||||
'@vueuse/shared': 12.8.2(typescript@5.9.3)
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@vueuse/core@14.1.0(vue@3.5.25(typescript@5.9.3))':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.21
|
||||
'@vueuse/metadata': 14.1.0
|
||||
'@vueuse/shared': 14.1.0(vue@3.5.25(typescript@5.9.3))
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
'@vueuse/metadata@12.8.2': {}
|
||||
|
||||
'@vueuse/metadata@14.1.0': {}
|
||||
|
||||
'@vueuse/shared@12.8.2(typescript@5.9.3)':
|
||||
dependencies:
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- typescript
|
||||
|
||||
'@vueuse/shared@14.1.0(vue@3.5.25(typescript@5.9.3))':
|
||||
dependencies:
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
alien-signals@3.1.1: {}
|
||||
|
||||
aria-hidden@1.2.6:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
birpc@2.9.0: {}
|
||||
|
||||
class-variance-authority@0.7.1:
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
|
||||
clsx@2.1.1: {}
|
||||
|
||||
copy-anything@4.0.5:
|
||||
dependencies:
|
||||
is-what: 5.5.0
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
defu@6.1.4: {}
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
enhanced-resolve@5.18.3:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
tapable: 2.3.0
|
||||
|
||||
entities@4.5.0: {}
|
||||
|
||||
estree-walker@2.0.2: {}
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
graceful-fs@4.2.11: {}
|
||||
|
||||
hookable@5.5.3: {}
|
||||
|
||||
is-what@5.5.0: {}
|
||||
|
||||
jiti@2.6.1: {}
|
||||
|
||||
lightningcss-android-arm64@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-arm64@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-darwin-x64@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-freebsd-x64@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm-gnueabihf@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss-win32-x64-msvc@1.30.2:
|
||||
optional: true
|
||||
|
||||
lightningcss@1.30.2:
|
||||
dependencies:
|
||||
detect-libc: 2.1.2
|
||||
optionalDependencies:
|
||||
lightningcss-android-arm64: 1.30.2
|
||||
lightningcss-darwin-arm64: 1.30.2
|
||||
lightningcss-darwin-x64: 1.30.2
|
||||
lightningcss-freebsd-x64: 1.30.2
|
||||
lightningcss-linux-arm-gnueabihf: 1.30.2
|
||||
lightningcss-linux-arm64-gnu: 1.30.2
|
||||
lightningcss-linux-arm64-musl: 1.30.2
|
||||
lightningcss-linux-x64-gnu: 1.30.2
|
||||
lightningcss-linux-x64-musl: 1.30.2
|
||||
lightningcss-win32-arm64-msvc: 1.30.2
|
||||
lightningcss-win32-x64-msvc: 1.30.2
|
||||
|
||||
lucide-vue-next@0.556.0(vue@3.5.25(typescript@5.9.3)):
|
||||
dependencies:
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
mitt@3.0.1: {}
|
||||
|
||||
muggle-string@0.4.1: {}
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
ohash@2.0.11: {}
|
||||
|
||||
path-browserify@1.0.1: {}
|
||||
|
||||
perfect-debounce@1.0.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
pinia-plugin-persistedstate@4.7.1(pinia@3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))):
|
||||
dependencies:
|
||||
defu: 6.1.4
|
||||
optionalDependencies:
|
||||
pinia: 3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3))
|
||||
|
||||
pinia@3.0.4(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@vue/devtools-api': 7.7.9
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
postcss@8.5.6:
|
||||
dependencies:
|
||||
nanoid: 3.3.11
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
reka-ui@2.6.1(typescript@5.9.3)(vue@3.5.25(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@floating-ui/dom': 1.7.4
|
||||
'@floating-ui/vue': 1.1.9(vue@3.5.25(typescript@5.9.3))
|
||||
'@internationalized/date': 3.10.0
|
||||
'@internationalized/number': 3.6.5
|
||||
'@tanstack/vue-virtual': 3.13.13(vue@3.5.25(typescript@5.9.3))
|
||||
'@vueuse/core': 12.8.2(typescript@5.9.3)
|
||||
'@vueuse/shared': 12.8.2(typescript@5.9.3)
|
||||
aria-hidden: 1.2.6
|
||||
defu: 6.1.4
|
||||
ohash: 2.0.11
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- typescript
|
||||
|
||||
rfdc@1.4.1: {}
|
||||
|
||||
rolldown-vite@7.2.5(@types/node@24.10.2)(jiti@2.6.1):
|
||||
dependencies:
|
||||
'@oxc-project/runtime': 0.97.0
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
lightningcss: 1.30.2
|
||||
picomatch: 4.0.3
|
||||
postcss: 8.5.6
|
||||
rolldown: 1.0.0-beta.50
|
||||
tinyglobby: 0.2.15
|
||||
optionalDependencies:
|
||||
'@types/node': 24.10.2
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.6.1
|
||||
|
||||
rolldown@1.0.0-beta.50:
|
||||
dependencies:
|
||||
'@oxc-project/types': 0.97.0
|
||||
'@rolldown/pluginutils': 1.0.0-beta.50
|
||||
optionalDependencies:
|
||||
'@rolldown/binding-android-arm64': 1.0.0-beta.50
|
||||
'@rolldown/binding-darwin-arm64': 1.0.0-beta.50
|
||||
'@rolldown/binding-darwin-x64': 1.0.0-beta.50
|
||||
'@rolldown/binding-freebsd-x64': 1.0.0-beta.50
|
||||
'@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.50
|
||||
'@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.50
|
||||
'@rolldown/binding-linux-arm64-musl': 1.0.0-beta.50
|
||||
'@rolldown/binding-linux-x64-gnu': 1.0.0-beta.50
|
||||
'@rolldown/binding-linux-x64-musl': 1.0.0-beta.50
|
||||
'@rolldown/binding-openharmony-arm64': 1.0.0-beta.50
|
||||
'@rolldown/binding-wasm32-wasi': 1.0.0-beta.50
|
||||
'@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.50
|
||||
'@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.50
|
||||
'@rolldown/binding-win32-x64-msvc': 1.0.0-beta.50
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
speakingurl@14.0.1: {}
|
||||
|
||||
superjson@2.2.6:
|
||||
dependencies:
|
||||
copy-anything: 4.0.5
|
||||
|
||||
tailwind-merge@3.4.0: {}
|
||||
|
||||
tailwindcss@4.1.17: {}
|
||||
|
||||
tapable@2.3.0: {}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tw-animate-css@1.4.0: {}
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@7.16.0: {}
|
||||
|
||||
vscode-uri@3.1.0: {}
|
||||
|
||||
vue-demi@0.14.10(vue@3.5.25(typescript@5.9.3)):
|
||||
dependencies:
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
vue-router@4.6.3(vue@3.5.25(typescript@5.9.3)):
|
||||
dependencies:
|
||||
'@vue/devtools-api': 6.6.4
|
||||
vue: 3.5.25(typescript@5.9.3)
|
||||
|
||||
vue-sonner@2.0.9: {}
|
||||
|
||||
vue-tsc@3.1.8(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@volar/typescript': 2.4.26
|
||||
'@vue/language-core': 3.1.8(typescript@5.9.3)
|
||||
typescript: 5.9.3
|
||||
|
||||
vue@3.5.25(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.25
|
||||
'@vue/compiler-sfc': 3.5.25
|
||||
'@vue/runtime-dom': 3.5.25
|
||||
'@vue/server-renderer': 3.5.25(vue@3.5.25(typescript@5.9.3))
|
||||
'@vue/shared': 3.5.25
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
1
public/logo.svg
Normal file
1
public/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
556
src/App.vue
Normal file
556
src/App.vue
Normal file
@@ -0,0 +1,556 @@
|
||||
<template>
|
||||
<div class="flex h-screen bg-background overflow-hidden">
|
||||
<!-- 遮罩层(移动端) -->
|
||||
<div v-if="!gameStore.sidebarCollapsed" class="fixed inset-0 bg-black/50 z-30 lg:hidden" @click="toggleSidebar" />
|
||||
|
||||
<!-- 侧边导航栏 -->
|
||||
<aside
|
||||
:class="[
|
||||
'border-r bg-card flex flex-col transition-all duration-300 ease-in-out shadow-lg z-40',
|
||||
'fixed lg:relative h-full',
|
||||
gameStore.sidebarCollapsed ? '-translate-x-full lg:translate-x-0 lg:w-16' : 'translate-x-0 w-64'
|
||||
]"
|
||||
>
|
||||
<!-- Logo -->
|
||||
<div class="p-4 border-b flex items-center justify-center">
|
||||
<h1 v-if="!gameStore.sidebarCollapsed" class="text-xl font-bold flex items-center gap-2">
|
||||
<span class="text-2xl">
|
||||
<img src="@/assets/logo.svg" class="w-10" />
|
||||
</span>
|
||||
{{ pkg.title }}
|
||||
</h1>
|
||||
<span v-else class="text-2xl">
|
||||
<img src="@/assets/logo.svg" class="w-10" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 星球信息 -->
|
||||
<div v-if="planet && !gameStore.sidebarCollapsed" class="p-4 border-b">
|
||||
<div class="text-sm space-y-2">
|
||||
<div>
|
||||
<p class="font-semibold mb-1">
|
||||
{{ planet.name }}
|
||||
<Badge v-if="planet.isMoon" variant="secondary" class="ml-1 text-xs">{{ t('planet.moon') }}</Badge>
|
||||
</p>
|
||||
<p class="text-muted-foreground text-xs">
|
||||
[{{ planet.position.galaxy }}:{{ planet.position.system }}:{{ planet.position.position }}]
|
||||
</p>
|
||||
</div>
|
||||
<!-- 玩家积分显示 -->
|
||||
<div class="bg-muted/50 rounded-lg p-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs text-muted-foreground">{{ t('player.points') }}</span>
|
||||
<span class="text-sm font-bold text-primary">{{ formatNumber(gameStore.player.points) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 月球切换按钮 -->
|
||||
<div v-if="hasMoon || planet.isMoon" class="flex gap-1">
|
||||
<Button v-if="planet.isMoon" @click="switchToParentPlanet" variant="outline" size="sm" class="w-full text-xs h-7">
|
||||
{{ t('planet.backToPlanet') }}
|
||||
</Button>
|
||||
<Button v-else-if="moon" @click="switchToMoon" variant="outline" size="sm" class="w-full text-xs h-7">
|
||||
{{ t('planet.switchToMoon') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导航菜单 -->
|
||||
<nav class="flex-1 p-2 space-y-1 overflow-y-auto">
|
||||
<RouterLink v-for="item in navItems" :key="item.path" :to="item.path" v-slot="{ isActive: routeActive }">
|
||||
<Button
|
||||
:variant="routeActive ? 'secondary' : 'ghost'"
|
||||
:class="['w-full transition-all', gameStore.sidebarCollapsed ? 'justify-center px-0' : 'justify-start']"
|
||||
:title="gameStore.sidebarCollapsed ? item.name.value : undefined"
|
||||
>
|
||||
<component :is="item.icon" :class="['h-4 w-4', !gameStore.sidebarCollapsed && 'mr-3']" />
|
||||
<span v-if="!gameStore.sidebarCollapsed">{{ item.name.value }}</span>
|
||||
</Button>
|
||||
</RouterLink>
|
||||
</nav>
|
||||
|
||||
<!-- 语言切换 -->
|
||||
<div class="p-2 border-t">
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="ghost" class="w-full" size="sm">
|
||||
<Languages class="h-4 w-4" />
|
||||
<span v-if="!gameStore.sidebarCollapsed" class="ml-2">{{ localeNames[gameStore.locale] }}</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-48 p-2" :align="gameStore.sidebarCollapsed ? 'start' : 'center'">
|
||||
<div class="space-y-1">
|
||||
<Button
|
||||
v-for="locale in locales"
|
||||
:key="locale"
|
||||
@click="gameStore.locale = locale"
|
||||
:variant="gameStore.locale === locale ? 'secondary' : 'ghost'"
|
||||
class="w-full justify-start"
|
||||
size="sm"
|
||||
>
|
||||
{{ localeNames[locale] }}
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<!-- 夜间模式切换 -->
|
||||
<div class="p-2 border-t">
|
||||
<Button @click="isDark = !isDark" variant="ghost" class="w-full" size="sm">
|
||||
<Sun v-if="isDark" class="h-4 w-4" />
|
||||
<Moon v-else class="h-4 w-4" />
|
||||
<span v-if="!gameStore.sidebarCollapsed" class="ml-2">{{ isDark ? t('sidebar.lightMode') : t('sidebar.darkMode') }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
<div class="p-2 border-t">
|
||||
<Button @click="toggleSidebar" variant="ghost" class="w-full" size="sm">
|
||||
<ChevronLeft v-if="!gameStore.sidebarCollapsed" class="h-4 w-4" />
|
||||
<ChevronRight v-else class="h-4 w-4" />
|
||||
<span v-if="!gameStore.sidebarCollapsed" class="ml-2">{{ t('sidebar.collapse') }}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<!-- 顶部资源栏 -->
|
||||
<header v-if="planet" class="bg-card border-b px-4 sm:px-6 py-4.5 shadow-md">
|
||||
<div class="flex items-center justify-between gap-3 sm:gap-6">
|
||||
<!-- 汉堡菜单(移动端)- 左侧占位 -->
|
||||
<div class="lg:flex-1">
|
||||
<Button @click="toggleSidebar" variant="ghost" size="icon" class="lg:hidden h-8 w-8">
|
||||
<component :is="gameStore.sidebarCollapsed ? Menu : X" class="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 资源显示 - PC端居中 -->
|
||||
<div class="flex items-center gap-3 sm:gap-6 flex-1 lg:flex-none overflow-x-auto lg:justify-center">
|
||||
<div v-for="resourceType in resourceTypes" :key="resourceType.key" class="flex items-center gap-1.5 sm:gap-2 flex-shrink-0">
|
||||
<ResourceIcon :type="resourceType.key" size="md" />
|
||||
<div class="min-w-0">
|
||||
<!-- 电量显示 -->
|
||||
<template v-if="resourceType.key === 'energy'">
|
||||
<p
|
||||
class="text-xs sm:text-sm font-medium truncate"
|
||||
:class="
|
||||
planet.resources[resourceType.key] >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'
|
||||
"
|
||||
>
|
||||
{{ formatNumber(planet.resources[resourceType.key]) }}
|
||||
</p>
|
||||
<p class="text-[10px] sm:text-xs text-muted-foreground truncate">
|
||||
{{ formatNumber(energyProduction || 0) }} / {{ formatNumber(energyConsumption || 0) }}
|
||||
</p>
|
||||
</template>
|
||||
<!-- 其他资源显示 -->
|
||||
<template v-else>
|
||||
<p
|
||||
class="text-xs sm:text-sm font-medium truncate"
|
||||
:class="getResourceColor(planet.resources[resourceType.key], capacity?.[resourceType.key] || Infinity)"
|
||||
>
|
||||
{{ formatNumber(planet.resources[resourceType.key]) }} / {{ formatNumber(capacity?.[resourceType.key] || 0) }}
|
||||
</p>
|
||||
<p class="text-[10px] sm:text-xs text-muted-foreground truncate">
|
||||
+{{ formatNumber(production?.[resourceType.key] || 0) }}/{{ t('resources.perHour') }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧状态 - 右侧占位 -->
|
||||
<div class="flex items-center gap-2 sm:gap-4 flex-shrink-0 lg:flex-1 lg:justify-end">
|
||||
<!-- 建造队列状态 -->
|
||||
<div v-if="planet.buildQueue.length > 0" class="flex items-center gap-1.5 sm:gap-2 text-xs sm:text-sm">
|
||||
<div class="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
|
||||
<span class="text-muted-foreground hidden sm:inline">{{ t('queue.building') }}</span>
|
||||
</div>
|
||||
<div v-if="gameStore.player.researchQueue.length > 0" class="flex items-center gap-1.5 sm:gap-2 text-xs sm:text-sm">
|
||||
<div class="h-2 w-2 rounded-full bg-blue-500 animate-pulse" />
|
||||
<span class="text-muted-foreground hidden sm:inline">{{ t('queue.researching') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 建造队列 -->
|
||||
<div
|
||||
v-if="planet && (planet.buildQueue.length > 0 || gameStore.player.researchQueue.length > 0)"
|
||||
class="bg-card border-b px-4 sm:px-6 py-4.5"
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<!-- 建造队列 -->
|
||||
<div v-for="item in planet.buildQueue" :key="item.id" class="space-y-1.5">
|
||||
<div class="flex items-center justify-between text-xs sm:text-sm gap-2">
|
||||
<div class="flex items-center gap-1.5 sm:gap-2 min-w-0 flex-1">
|
||||
<div class="h-2 w-2 rounded-full bg-green-500 animate-pulse flex-shrink-0" />
|
||||
<span class="font-medium truncate">{{ getItemName(item) }}</span>
|
||||
<span class="text-muted-foreground hidden sm:inline flex-shrink-0 text-[10px] sm:text-xs">
|
||||
→ {{ t('queue.level') }} {{ item.targetLevel }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||
<span class="text-muted-foreground text-[10px] sm:text-xs whitespace-nowrap">{{ formatTime(getRemainingTime(item)) }}</span>
|
||||
<Button
|
||||
@click="handleCancelBuild(item.id)"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-5 sm:h-6 px-1.5 sm:px-2 text-[10px] sm:text-xs"
|
||||
>
|
||||
{{ t('queue.cancel') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Progress :model-value="getQueueProgress(item)" class="h-1.5" />
|
||||
</div>
|
||||
<!-- 研究队列 -->
|
||||
<div v-for="item in gameStore.player.researchQueue" :key="item.id" class="space-y-1.5">
|
||||
<div class="flex items-center justify-between text-xs sm:text-sm gap-2">
|
||||
<div class="flex items-center gap-1.5 sm:gap-2 min-w-0 flex-1">
|
||||
<div class="h-2 w-2 rounded-full bg-blue-500 animate-pulse flex-shrink-0" />
|
||||
<span class="font-medium truncate">{{ getItemName(item) }}</span>
|
||||
<span class="text-muted-foreground hidden sm:inline flex-shrink-0 text-[10px] sm:text-xs">
|
||||
→ {{ t('queue.level') }} {{ item.targetLevel }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||
<span class="text-muted-foreground text-[10px] sm:text-xs whitespace-nowrap">{{ formatTime(getRemainingTime(item)) }}</span>
|
||||
<Button
|
||||
@click="handleCancelResearch(item.id)"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-5 sm:h-6 px-1.5 sm:px-2 text-[10px] sm:text-xs"
|
||||
>
|
||||
{{ t('queue.cancel') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Progress :model-value="getQueueProgress(item)" class="h-1.5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
<div class="animate-fade-in">
|
||||
<RouterView />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- 确认对话框 -->
|
||||
<ConfirmDialog ref="confirmDialog" />
|
||||
|
||||
<!-- 详情弹窗 -->
|
||||
<DetailDialog />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, computed, ref } from 'vue'
|
||||
import { RouterView, RouterLink } from 'vue-router'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { localeNames, detectBrowserLocale, type Locale } from '@/locales'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'
|
||||
import ResourceIcon from '@/components/ResourceIcon.vue'
|
||||
import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
||||
import DetailDialog from '@/components/DetailDialog.vue'
|
||||
import { BuildingType, TechnologyType, ShipType, DefenseType, MissionType } from '@/types/game'
|
||||
import type { BuildQueueItem, FleetMission } from '@/types/game'
|
||||
import { BUILDINGS, TECHNOLOGIES, SHIPS, DEFENSES } from '@/config/gameConfig'
|
||||
import { formatNumber, formatTime, getResourceColor } from '@/utils/format'
|
||||
import {
|
||||
Moon,
|
||||
Sun,
|
||||
Home,
|
||||
Building2,
|
||||
FlaskConical,
|
||||
Ship,
|
||||
Rocket,
|
||||
Shield,
|
||||
Mail,
|
||||
Globe,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Menu,
|
||||
X,
|
||||
Users,
|
||||
Swords,
|
||||
Languages,
|
||||
Settings
|
||||
} from 'lucide-vue-next'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
import * as planetLogic from '@/logic/planetLogic'
|
||||
import * as publicLogic from '@/logic/publicLogic'
|
||||
import * as officerLogic from '@/logic/officerLogic'
|
||||
import * as buildingValidation from '@/logic/buildingValidation'
|
||||
import * as resourceLogic from '@/logic/resourceLogic'
|
||||
import * as researchValidation from '@/logic/researchValidation'
|
||||
import * as fleetLogic from '@/logic/fleetLogic'
|
||||
import * as shipLogic from '@/logic/shipLogic'
|
||||
import pkg from '../package.json'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const { isDark } = useTheme()
|
||||
const { t } = useI18n()
|
||||
const confirmDialog = ref<InstanceType<typeof ConfirmDialog> | null>(null)
|
||||
|
||||
// 所有可用的语言选项
|
||||
const locales: Locale[] = ['zh-CN', 'zh-TW', 'en', 'de', 'ru', 'ko', 'ja']
|
||||
|
||||
const initGame = () => {
|
||||
const shouldInit = gameLogic.shouldInitializeGame(gameStore.player.planets)
|
||||
if (!shouldInit) {
|
||||
const now = Date.now()
|
||||
gameLogic.updatePlanetsLastUpdate(gameStore.player.planets, now)
|
||||
gameStore.player.planets.forEach(planet => {
|
||||
const key = gameLogic.generatePositionKey(planet.position.galaxy, planet.position.system, planet.position.position)
|
||||
gameStore.universePlanets[key] = planet
|
||||
})
|
||||
generateNPCPlanets()
|
||||
return
|
||||
}
|
||||
gameStore.player = gameLogic.initializePlayer(gameStore.player.id, t('common.playerName'))
|
||||
const initialPlanet = planetLogic.createInitialPlanet(gameStore.player.id, t('planet.homePlanet'))
|
||||
gameStore.player.planets = [initialPlanet]
|
||||
gameStore.currentPlanetId = initialPlanet.id
|
||||
const key = gameLogic.generatePositionKey(initialPlanet.position.galaxy, initialPlanet.position.system, initialPlanet.position.position)
|
||||
gameStore.universePlanets[key] = initialPlanet
|
||||
}
|
||||
|
||||
const generateNPCPlanets = () => {
|
||||
const npcCount = 200
|
||||
for (let i = 0; i < npcCount; i++) {
|
||||
const position = gameLogic.generateRandomPosition()
|
||||
const key = gameLogic.generatePositionKey(position.galaxy, position.system, position.position)
|
||||
if (gameStore.universePlanets[key]) continue
|
||||
const npcPlanet = planetLogic.createNPCPlanet(i, position, t('planet.planetPrefix'))
|
||||
gameStore.universePlanets[key] = npcPlanet
|
||||
}
|
||||
}
|
||||
|
||||
const updateGame = () => {
|
||||
if (gameStore.isPaused) return
|
||||
const now = Date.now()
|
||||
gameStore.gameTime = now
|
||||
gameLogic.checkOfficersExpiration(gameStore.player.officers, now)
|
||||
const result = gameLogic.processGameUpdate(gameStore.player, now)
|
||||
gameStore.player.researchQueue = result.updatedResearchQueue
|
||||
gameStore.player.fleetMissions.forEach(mission => {
|
||||
if (mission.status === 'outbound' && now >= mission.arrivalTime) {
|
||||
processMissionArrival(mission)
|
||||
} else if (mission.status === 'returning' && mission.returnTime && now >= mission.returnTime) {
|
||||
processMissionReturn(mission)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const processMissionArrival = (mission: FleetMission) => {
|
||||
const targetPlanet = gameStore.player.planets.find(
|
||||
p =>
|
||||
p.position.galaxy === mission.targetPosition.galaxy &&
|
||||
p.position.system === mission.targetPosition.system &&
|
||||
p.position.position === mission.targetPosition.position
|
||||
)
|
||||
|
||||
if (mission.missionType === MissionType.Transport) {
|
||||
fleetLogic.processTransportArrival(mission, targetPlanet)
|
||||
} else if (mission.missionType === MissionType.Attack) {
|
||||
const attackResult = fleetLogic.processAttackArrival(mission, targetPlanet, gameStore.player, null, gameStore.player.planets)
|
||||
if (attackResult) {
|
||||
gameStore.player.battleReports.push(attackResult.battleResult)
|
||||
if (attackResult.moon) {
|
||||
gameStore.player.planets.push(attackResult.moon)
|
||||
}
|
||||
}
|
||||
} else if (mission.missionType === MissionType.Colonize) {
|
||||
const newPlanet = fleetLogic.processColonizeArrival(mission, targetPlanet, gameStore.player.id, t('planet.colonyPrefix'))
|
||||
if (newPlanet) gameStore.player.planets.push(newPlanet)
|
||||
} else if (mission.missionType === MissionType.Spy) {
|
||||
const spyReport = fleetLogic.processSpyArrival(mission, targetPlanet, gameStore.player.id)
|
||||
if (spyReport) gameStore.player.spyReports.push(spyReport)
|
||||
} else if (mission.missionType === MissionType.Deploy) {
|
||||
const deployed = fleetLogic.processDeployArrival(mission, targetPlanet, gameStore.player.id)
|
||||
if (deployed) {
|
||||
const missionIndex = gameStore.player.fleetMissions.indexOf(mission)
|
||||
if (missionIndex > -1) gameStore.player.fleetMissions.splice(missionIndex, 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const processMissionReturn = (mission: FleetMission) => {
|
||||
const originPlanet = gameStore.player.planets.find(p => p.id === mission.originPlanetId)
|
||||
if (!originPlanet) return
|
||||
shipLogic.addFleet(originPlanet.fleet, mission.fleet)
|
||||
resourceLogic.addResources(originPlanet.resources, mission.cargo)
|
||||
const missionIndex = gameStore.player.fleetMissions.indexOf(mission)
|
||||
if (missionIndex > -1) gameStore.player.fleetMissions.splice(missionIndex, 1)
|
||||
}
|
||||
|
||||
// 初始化游戏
|
||||
onMounted(() => {
|
||||
// 如果是首次访问(没有星球数据),使用浏览器语言自动检测
|
||||
const isFirstVisit = gameStore.player.planets.length === 0
|
||||
if (isFirstVisit) {
|
||||
gameStore.locale = detectBrowserLocale()
|
||||
}
|
||||
|
||||
initGame()
|
||||
|
||||
// 启动游戏循环
|
||||
const gameLoop = setInterval(() => {
|
||||
updateGame()
|
||||
}, 1000) // 每秒更新一次
|
||||
|
||||
// 清理定时器
|
||||
onUnmounted(() => {
|
||||
clearInterval(gameLoop)
|
||||
})
|
||||
})
|
||||
|
||||
const navItems = [
|
||||
{ name: computed(() => t('nav.overview')), path: '/', icon: Home },
|
||||
{ name: computed(() => t('nav.buildings')), path: '/buildings', icon: Building2 },
|
||||
{ name: computed(() => t('nav.research')), path: '/research', icon: FlaskConical },
|
||||
{ name: computed(() => t('nav.shipyard')), path: '/shipyard', icon: Ship },
|
||||
{ name: computed(() => t('nav.defense')), path: '/defense', icon: Shield },
|
||||
{ name: computed(() => t('nav.fleet')), path: '/fleet', icon: Rocket },
|
||||
{ name: computed(() => t('nav.officers')), path: '/officers', icon: Users },
|
||||
{ name: computed(() => t('nav.simulator')), path: '/battle-simulator', icon: Swords },
|
||||
{ name: computed(() => t('nav.galaxy')), path: '/galaxy', icon: Globe },
|
||||
{ name: computed(() => t('nav.messages')), path: '/messages', icon: Mail },
|
||||
{ name: computed(() => t('nav.settings')), path: '/settings', icon: Settings }
|
||||
]
|
||||
|
||||
const planet = computed(() => gameStore.currentPlanet)
|
||||
const production = computed(() => (planet.value ? publicLogic.getResourceProduction(planet.value, gameStore.player.officers) : null))
|
||||
const capacity = computed(() => (planet.value ? publicLogic.getResourceCapacity(planet.value, gameStore.player.officers) : null))
|
||||
|
||||
// 电量产出和消耗
|
||||
const energyProduction = computed(() => {
|
||||
if (!planet.value) return 0
|
||||
const bonuses = officerLogic.calculateActiveBonuses(gameStore.player.officers, Date.now())
|
||||
return resourceLogic.calculateEnergyProduction(planet.value, { energyProductionBonus: bonuses.energyProductionBonus })
|
||||
})
|
||||
|
||||
const energyConsumption = computed(() => {
|
||||
if (!planet.value) return 0
|
||||
return resourceLogic.calculateEnergyConsumption(planet.value)
|
||||
})
|
||||
|
||||
// 资源类型配置
|
||||
const resourceTypes = [
|
||||
{ key: 'metal' as const },
|
||||
{ key: 'crystal' as const },
|
||||
{ key: 'deuterium' as const },
|
||||
{ key: 'energy' as const },
|
||||
{ key: 'darkMatter' as const }
|
||||
]
|
||||
|
||||
// 月球相关
|
||||
const moon = computed(() => {
|
||||
if (!planet.value || planet.value.isMoon) return null
|
||||
return gameStore.getMoonForPlanet(planet.value.id)
|
||||
})
|
||||
const hasMoon = computed(() => !!moon.value)
|
||||
|
||||
// 切换到月球
|
||||
const switchToMoon = () => {
|
||||
if (moon.value) {
|
||||
gameStore.currentPlanetId = moon.value.id
|
||||
}
|
||||
}
|
||||
|
||||
// 切换回母星
|
||||
const switchToParentPlanet = () => {
|
||||
if (planet.value?.parentPlanetId) {
|
||||
gameStore.currentPlanetId = planet.value.parentPlanetId
|
||||
}
|
||||
}
|
||||
|
||||
// 切换侧边栏
|
||||
const toggleSidebar = () => {
|
||||
gameStore.sidebarCollapsed = !gameStore.sidebarCollapsed
|
||||
}
|
||||
|
||||
// 获取队列项的名称
|
||||
const getItemName = (item: BuildQueueItem): string => {
|
||||
if (item.type === 'building' || item.type === 'demolish') {
|
||||
const buildingName = BUILDINGS[item.itemType as BuildingType]?.name || item.itemType
|
||||
return item.type === 'demolish' ? `${t('buildingsView.demolish')} - ${buildingName}` : buildingName
|
||||
} else if (item.type === 'technology') {
|
||||
return TECHNOLOGIES[item.itemType as TechnologyType]?.name || item.itemType
|
||||
} else if (item.type === 'ship') {
|
||||
return SHIPS[item.itemType as ShipType]?.name || item.itemType
|
||||
} else if (item.type === 'defense') {
|
||||
return DEFENSES[item.itemType as DefenseType]?.name || item.itemType
|
||||
}
|
||||
return item.itemType
|
||||
}
|
||||
|
||||
// 获取剩余时间
|
||||
const getRemainingTime = (item: BuildQueueItem): number => {
|
||||
const now = Date.now()
|
||||
return Math.max(0, Math.floor((item.endTime - now) / 1000))
|
||||
}
|
||||
|
||||
// 获取队列进度
|
||||
const getQueueProgress = (item: BuildQueueItem): number => {
|
||||
const now = Date.now()
|
||||
const total = item.endTime - item.startTime
|
||||
const elapsed = now - item.startTime
|
||||
return Math.min(100, Math.max(0, (elapsed / total) * 100))
|
||||
}
|
||||
|
||||
// 取消建造
|
||||
const handleCancelBuild = (queueId: string) => {
|
||||
confirmDialog.value?.show({
|
||||
title: t('queue.cancelBuild'),
|
||||
message: t('queue.confirmCancel'),
|
||||
onConfirm: () => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
const { item, index } = buildingValidation.findQueueItem(gameStore.currentPlanet.buildQueue, queueId)
|
||||
if (!item) return false
|
||||
if (item.type === 'building') {
|
||||
const refund = buildingValidation.cancelBuildingUpgrade(gameStore.currentPlanet, item)
|
||||
resourceLogic.addResources(gameStore.currentPlanet.resources, refund)
|
||||
}
|
||||
gameStore.currentPlanet.buildQueue.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 取消研究
|
||||
const handleCancelResearch = (queueId: string) => {
|
||||
confirmDialog.value?.show({
|
||||
title: t('queue.cancelResearch'),
|
||||
message: t('queue.confirmCancel'),
|
||||
onConfirm: () => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
const { item, index } = buildingValidation.findQueueItem(gameStore.player.researchQueue, queueId)
|
||||
if (!item) return false
|
||||
if (item.type === 'technology') {
|
||||
const refund = researchValidation.cancelTechnologyResearch(item)
|
||||
resourceLogic.addResources(gameStore.currentPlanet.resources, refund)
|
||||
}
|
||||
gameStore.player.researchQueue.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 平滑滚动 */
|
||||
main {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
</style>
|
||||
1
src/assets/logo.svg
Normal file
1
src/assets/logo.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 12 KiB |
51
src/components/AlertDialog.vue
Normal file
51
src/components/AlertDialog.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div class="fixed inset-0 bg-black/50" @click="handleClose" />
|
||||
<div class="relative bg-card border rounded-lg shadow-lg p-6 max-w-md w-full mx-4 z-10">
|
||||
<h2 class="text-lg font-semibold mb-2">{{ dialogProps?.title }}</h2>
|
||||
<p class="text-sm text-muted-foreground mb-6 whitespace-pre-line">{{ dialogProps?.message }}</p>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<Button v-if="dialogProps?.onConfirm" @click="handleClose" variant="outline">{{ t('common.cancel') }}</Button>
|
||||
<Button @click="handleConfirm" variant="default">{{ t('common.confirm') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
interface AlertDialogProps {
|
||||
title: string
|
||||
message: string
|
||||
onConfirm?: () => void
|
||||
}
|
||||
|
||||
const isOpen = ref(false)
|
||||
const dialogProps = ref<AlertDialogProps | null>(null)
|
||||
|
||||
const show = (props: AlertDialogProps) => {
|
||||
dialogProps.value = props
|
||||
isOpen.value = true
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (dialogProps.value?.onConfirm) {
|
||||
dialogProps.value.onConfirm()
|
||||
}
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
defineExpose({ show })
|
||||
</script>
|
||||
77
src/components/CardUnlockOverlay.vue
Normal file
77
src/components/CardUnlockOverlay.vue
Normal file
@@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<div v-if="!isUnlocked" class="absolute inset-0 z-10 bg-background/70 backdrop-blur-[2px] rounded-lg flex items-center justify-center">
|
||||
<div class="text-center p-4 space-y-2">
|
||||
<div class="flex justify-center">
|
||||
<div class="rounded-full bg-muted p-2">
|
||||
<Lock :size="20" class="text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs font-medium text-muted-foreground">{{ t('common.locked') }}</p>
|
||||
<Button variant="outline" size="sm" @click="showRequirements" class="text-xs">
|
||||
{{ t('common.viewRequirements') }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 前置条件详情对话框 -->
|
||||
<AlertDialog ref="requirementsDialog" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { useGameConfig } from '@/composables/useGameConfig'
|
||||
import { BuildingType, TechnologyType } from '@/types/game'
|
||||
import { Lock } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import AlertDialog from '@/components/AlertDialog.vue'
|
||||
import * as publicLogic from '@/logic/publicLogic'
|
||||
|
||||
interface Props {
|
||||
requirements?: Partial<Record<BuildingType | TechnologyType, number>>
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const gameStore = useGameStore()
|
||||
const { t } = useI18n()
|
||||
const { BUILDINGS, TECHNOLOGIES } = useGameConfig()
|
||||
const requirementsDialog = ref<InstanceType<typeof AlertDialog> | null>(null)
|
||||
|
||||
const isUnlocked = computed(() => {
|
||||
if (!props.requirements || !gameStore.currentPlanet) return true
|
||||
return publicLogic.checkRequirements(gameStore.currentPlanet, gameStore.player.technologies, props.requirements)
|
||||
})
|
||||
|
||||
const getRequirementsList = (): string => {
|
||||
if (!props.requirements || !gameStore.currentPlanet) return ''
|
||||
|
||||
const lines: string[] = []
|
||||
for (const [key, requiredLevel] of Object.entries(props.requirements)) {
|
||||
// 检查是否为建筑类型
|
||||
if (Object.values(BuildingType).includes(key as BuildingType)) {
|
||||
const buildingType = key as BuildingType
|
||||
const currentLevel = gameStore.currentPlanet.buildings[buildingType] || 0
|
||||
const name = BUILDINGS.value[buildingType]?.name || buildingType
|
||||
const status = currentLevel >= requiredLevel ? '✓' : '✗'
|
||||
lines.push(`${status} ${name}: Lv ${requiredLevel} (${t('common.current')}: Lv ${currentLevel})`)
|
||||
}
|
||||
// 检查是否为科技类型
|
||||
else if (Object.values(TechnologyType).includes(key as TechnologyType)) {
|
||||
const techType = key as TechnologyType
|
||||
const currentLevel = gameStore.player.technologies[techType] || 0
|
||||
const name = TECHNOLOGIES.value[techType]?.name || techType
|
||||
const status = currentLevel >= requiredLevel ? '✓' : '✗'
|
||||
lines.push(`${status} ${name}: Lv ${requiredLevel} (${t('common.current')}: Lv ${currentLevel})`)
|
||||
}
|
||||
}
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
const showRequirements = () => {
|
||||
requirementsDialog.value?.show({
|
||||
title: t('common.requirementsNotMet'),
|
||||
message: getRequirementsList()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
51
src/components/ConfirmDialog.vue
Normal file
51
src/components/ConfirmDialog.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="isOpen" class="fixed inset-0 z-50 flex items-center justify-center">
|
||||
<div class="fixed inset-0 bg-black/50" @click="handleCancel" />
|
||||
<div class="relative bg-card border rounded-lg shadow-lg p-6 max-w-md w-full mx-4 z-10">
|
||||
<h2 class="text-lg font-semibold mb-2">{{ dialogProps?.title }}</h2>
|
||||
<p class="text-sm text-muted-foreground mb-6">{{ dialogProps?.message }}</p>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<Button @click="handleCancel" variant="outline">{{ t('common.cancel') }}</Button>
|
||||
<Button @click="handleConfirm" variant="default">{{ t('common.confirm') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
interface ConfirmDialogProps {
|
||||
title: string
|
||||
message: string
|
||||
onConfirm: () => void
|
||||
}
|
||||
|
||||
const isOpen = ref(false)
|
||||
const dialogProps = ref<ConfirmDialogProps | null>(null)
|
||||
|
||||
const show = (props: ConfirmDialogProps) => {
|
||||
dialogProps.value = props
|
||||
isOpen.value = true
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (dialogProps.value) {
|
||||
dialogProps.value.onConfirm()
|
||||
}
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
defineExpose({ show })
|
||||
</script>
|
||||
84
src/components/DetailDialog.vue
Normal file
84
src/components/DetailDialog.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<Dialog :open="dialogStore.isOpen" @update:open="handleClose">
|
||||
<DialogContent class="max-w-[calc(100%-1rem)] sm:max-w-[90vw] md:max-w-3xl lg:max-w-4xl max-h-[90vh] flex flex-col p-0">
|
||||
<!-- 建筑详情 -->
|
||||
<template v-if="dialogStore.type === 'building' && dialogStore.itemType">
|
||||
<DialogHeader class="px-6 pt-6 pb-4 shrink-0">
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
{{ t(`buildings.${dialogStore.itemType}`) }}
|
||||
<Badge variant="outline">{{ t('common.currentLevel') }} {{ dialogStore.currentLevel || 0 }}</Badge>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{{ t(`buildingDescriptions.${dialogStore.itemType}`) }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="overflow-y-auto px-6 pb-6">
|
||||
<BuildingDetailView :buildingType="dialogStore.itemType as BuildingType" :currentLevel="dialogStore.currentLevel || 0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 科技详情 -->
|
||||
<template v-else-if="dialogStore.type === 'technology' && dialogStore.itemType">
|
||||
<DialogHeader class="px-6 pt-6 pb-4 shrink-0">
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
{{ t(`technologies.${dialogStore.itemType}`) }}
|
||||
<Badge variant="outline">{{ t('common.currentLevel') }} {{ dialogStore.currentLevel || 0 }}</Badge>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
{{ t(`technologyDescriptions.${dialogStore.itemType}`) }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="overflow-y-auto px-6 pb-6">
|
||||
<TechnologyDetailView :technologyType="dialogStore.itemType as TechnologyType" :currentLevel="dialogStore.currentLevel || 0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 舰船详情 -->
|
||||
<template v-else-if="dialogStore.type === 'ship' && dialogStore.itemType">
|
||||
<DialogHeader class="px-6 pt-6 pb-4 shrink-0">
|
||||
<DialogTitle>{{ t(`ships.${dialogStore.itemType}`) }}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{{ t(`shipDescriptions.${dialogStore.itemType}`) }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="overflow-y-auto px-6 pb-6">
|
||||
<ShipDetailView :shipType="dialogStore.itemType as ShipType" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 防御详情 -->
|
||||
<template v-else-if="dialogStore.type === 'defense' && dialogStore.itemType">
|
||||
<DialogHeader class="px-6 pt-6 pb-4 shrink-0">
|
||||
<DialogTitle>{{ t(`defenses.${dialogStore.itemType}`) }}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{{ t(`defenseDescriptions.${dialogStore.itemType}`) }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="overflow-y-auto px-6 pb-6">
|
||||
<DefenseDetailView :defenseType="dialogStore.itemType as DefenseType" />
|
||||
</div>
|
||||
</template>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { useDetailDialogStore } from '@/stores/detailDialogStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import type { BuildingType, TechnologyType, ShipType, DefenseType } from '@/types/game'
|
||||
import BuildingDetailView from './detail-views/BuildingDetailView.vue'
|
||||
import TechnologyDetailView from './detail-views/TechnologyDetailView.vue'
|
||||
import ShipDetailView from './detail-views/ShipDetailView.vue'
|
||||
import DefenseDetailView from './detail-views/DefenseDetailView.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const dialogStore = useDetailDialogStore()
|
||||
|
||||
const handleClose = (open: boolean) => {
|
||||
if (!open) {
|
||||
dialogStore.close()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
28
src/components/ResourceIcon.vue
Normal file
28
src/components/ResourceIcon.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div :class="[colors[type], sizes[size], 'rounded shadow-sm']" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
type: 'metal' | 'crystal' | 'deuterium' | 'darkMatter' | 'energy'
|
||||
size?: 'sm' | 'md' | 'lg'
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
size: 'md'
|
||||
})
|
||||
|
||||
const colors = {
|
||||
metal: 'bg-gradient-to-br from-slate-400 to-slate-600',
|
||||
crystal: 'bg-gradient-to-br from-cyan-400 to-blue-600',
|
||||
deuterium: 'bg-gradient-to-br from-green-400 to-emerald-600',
|
||||
darkMatter: 'bg-gradient-to-br from-purple-600 to-indigo-900',
|
||||
energy: 'bg-gradient-to-br from-yellow-400 to-orange-500'
|
||||
}
|
||||
|
||||
const sizes = {
|
||||
sm: 'w-3 h-3',
|
||||
md: 'w-4 h-4',
|
||||
lg: 'w-5 h-5'
|
||||
}
|
||||
</script>
|
||||
73
src/components/UnlockRequirement.vue
Normal file
73
src/components/UnlockRequirement.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div v-if="!isUnlocked" class="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm flex items-center justify-center p-4">
|
||||
<Card class="max-w-md w-full">
|
||||
<CardHeader class="text-center">
|
||||
<div class="flex justify-center mb-4">
|
||||
<div class="rounded-full bg-muted p-4">
|
||||
<Lock :size="48" class="text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
<CardTitle class="text-xl sm:text-2xl">{{ t('common.featureLocked') }}</CardTitle>
|
||||
<CardDescription class="text-sm sm:text-base">{{ t('common.unlockRequired') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="p-4 bg-muted rounded-lg space-y-2">
|
||||
<p class="text-sm font-medium text-center">{{ t('common.requiredBuilding') }}:</p>
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
<span class="text-base sm:text-lg font-bold">{{ buildingName }}</span>
|
||||
<Badge variant="default">Lv {{ requiredLevel }}</Badge>
|
||||
</div>
|
||||
<p v-if="currentLevel !== undefined" class="text-xs text-center text-muted-foreground">
|
||||
{{ t('common.currentLevel') }}: Lv {{ currentLevel }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<Button @click="goToBuildings" class="flex-1">
|
||||
<Building2 :size="16" class="mr-2" />
|
||||
{{ t('common.goToBuildings') }}
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { useGameConfig } from '@/composables/useGameConfig'
|
||||
import { BuildingType } from '@/types/game'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Lock, Building2 } from 'lucide-vue-next'
|
||||
|
||||
interface Props {
|
||||
requiredBuilding: BuildingType
|
||||
requiredLevel: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const router = useRouter()
|
||||
const gameStore = useGameStore()
|
||||
const { t } = useI18n()
|
||||
const { BUILDINGS } = useGameConfig()
|
||||
|
||||
const buildingName = computed(() => BUILDINGS.value[props.requiredBuilding]?.name || props.requiredBuilding)
|
||||
|
||||
const currentLevel = computed(() => {
|
||||
if (!gameStore.currentPlanet) return 0
|
||||
return gameStore.currentPlanet.buildings[props.requiredBuilding] || 0
|
||||
})
|
||||
|
||||
const isUnlocked = computed(() => {
|
||||
return currentLevel.value >= props.requiredLevel
|
||||
})
|
||||
|
||||
const goToBuildings = () => {
|
||||
router.push('/buildings')
|
||||
}
|
||||
</script>
|
||||
195
src/components/detail-views/BuildingDetailView.vue
Normal file
195
src/components/detail-views/BuildingDetailView.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 建筑等级范围表格 -->
|
||||
<div class="border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-20 text-center">{{ t('buildings.levelRange') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('resources.metal') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('resources.crystal') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('resources.deuterium') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('buildings.buildTime') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('buildings.production') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('buildings.consumption') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('player.points') }}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="level in levelRange" :key="level" :class="{ 'bg-muted/50': level === currentLevel }">
|
||||
<TableCell class="text-center font-medium">
|
||||
<Badge v-if="level === currentLevel" variant="default">{{ level }}</Badge>
|
||||
<span v-else>{{ level }}</span>
|
||||
</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatNumber(getLevelData(level).cost.metal) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatNumber(getLevelData(level).cost.crystal) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatNumber(getLevelData(level).cost.deuterium) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatTime(getLevelData(level).buildTime) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">
|
||||
<span v-if="getLevelData(level).production > 0" class="text-green-600 dark:text-green-400">
|
||||
+{{ formatNumber(getLevelData(level).production) }}/{{ t('resources.perHour') }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</TableCell>
|
||||
<TableCell class="text-center text-sm">
|
||||
<span v-if="getLevelData(level).consumption > 0" class="text-red-600 dark:text-red-400">
|
||||
-{{ formatNumber(getLevelData(level).consumption) }}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</TableCell>
|
||||
<TableCell class="text-center text-sm">
|
||||
<span class="text-primary font-medium">+{{ getLevelData(level).points }}</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<!-- 累积统计 -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm">{{ t('buildings.totalCost') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(totalStats.metal) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(totalStats.crystal) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(totalStats.deuterium) }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm">{{ t('buildings.totalPoints') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-3xl font-bold text-primary">{{ formatNumber(totalStats.points) }}</div>
|
||||
<p class="text-xs text-muted-foreground mt-1">
|
||||
{{ t('buildings.levelRange') }}: {{ Math.max(0, currentLevel - 10) }} - {{ Math.min(currentLevel + 10, currentLevel + 10) }}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import type { BuildingType } from '@/types/game'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import * as buildingLogic from '@/logic/buildingLogic'
|
||||
import * as pointsLogic from '@/logic/pointsLogic'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
buildingType: BuildingType
|
||||
currentLevel: number
|
||||
}>()
|
||||
|
||||
// 等级范围:当前等级 ±10
|
||||
const levelRange = computed(() => {
|
||||
const start = Math.max(0, props.currentLevel - 10)
|
||||
const end = props.currentLevel + 10
|
||||
const levels = []
|
||||
for (let i = start; i <= end; i++) {
|
||||
levels.push(i)
|
||||
}
|
||||
return levels
|
||||
})
|
||||
|
||||
// 获取某个等级的详细数据
|
||||
const getLevelData = (level: number) => {
|
||||
if (level === 0) {
|
||||
return {
|
||||
cost: { metal: 0, crystal: 0, deuterium: 0 },
|
||||
buildTime: 0,
|
||||
production: 0,
|
||||
consumption: 0,
|
||||
points: 0
|
||||
}
|
||||
}
|
||||
|
||||
const cost = buildingLogic.calculateBuildingCost(props.buildingType, level)
|
||||
const buildTime = buildingLogic.calculateBuildingTime(props.buildingType, level)
|
||||
|
||||
// 计算产量和消耗
|
||||
let production = 0
|
||||
let consumption = 0
|
||||
|
||||
// 资源矿产量
|
||||
if (props.buildingType === 'metalMine') {
|
||||
production = Math.floor(30 * level * Math.pow(1.1, level))
|
||||
} else if (props.buildingType === 'crystalMine') {
|
||||
production = Math.floor(20 * level * Math.pow(1.1, level))
|
||||
} else if (props.buildingType === 'deuteriumSynthesizer') {
|
||||
production = Math.floor(10 * level * Math.pow(1.1, level))
|
||||
}
|
||||
|
||||
// 能量产出
|
||||
if (props.buildingType === 'solarPlant') {
|
||||
production = Math.floor(20 * level * Math.pow(1.1, level))
|
||||
}
|
||||
|
||||
// 能量消耗(矿场和合成器)
|
||||
if (['metalMine', 'crystalMine', 'deuteriumSynthesizer'].includes(props.buildingType)) {
|
||||
consumption = Math.floor(10 * level * Math.pow(1.1, level))
|
||||
}
|
||||
|
||||
// 计算积分
|
||||
const points = pointsLogic.calculateBuildingPoints(props.buildingType, level - 1, level)
|
||||
|
||||
return {
|
||||
cost,
|
||||
buildTime,
|
||||
production,
|
||||
consumption,
|
||||
points
|
||||
}
|
||||
}
|
||||
|
||||
// 累积统计
|
||||
const totalStats = computed(() => {
|
||||
let metal = 0
|
||||
let crystal = 0
|
||||
let deuterium = 0
|
||||
let points = 0
|
||||
|
||||
for (const level of levelRange.value) {
|
||||
if (level === 0) continue
|
||||
const data = getLevelData(level)
|
||||
metal += data.cost.metal
|
||||
crystal += data.cost.crystal
|
||||
deuterium += data.cost.deuterium
|
||||
points += data.points
|
||||
}
|
||||
|
||||
return { metal, crystal, deuterium, points }
|
||||
})
|
||||
|
||||
const formatNumber = (num: number): string => {
|
||||
return num.toLocaleString()
|
||||
}
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
if (seconds < 60) return `${seconds}${t('common.timeSecond')}`
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
if (minutes < 60) return `${minutes}${t('common.timeMinute')}${secs}${t('common.timeSecond')}`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const mins = minutes % 60
|
||||
return `${hours}${t('common.timeHour')}${mins}${t('common.timeMinute')}`
|
||||
}
|
||||
</script>
|
||||
168
src/components/detail-views/DefenseDetailView.vue
Normal file
168
src/components/detail-views/DefenseDetailView.vue
Normal file
@@ -0,0 +1,168 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 防御基础信息 -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Sword class="h-4 w-4" />
|
||||
{{ t('defense.attack') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.attack) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Shield class="h-4 w-4" />
|
||||
{{ t('defense.shield') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.shield) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<ShieldCheck class="h-4 w-4" />
|
||||
{{ t('defense.armor') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.armor) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 建造成本和时间 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm">{{ t('defense.buildCost') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<div v-if="config.cost.metal > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(config.cost.metal) }}</span>
|
||||
</div>
|
||||
<div v-if="config.cost.crystal > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(config.cost.crystal) }}</span>
|
||||
</div>
|
||||
<div v-if="config.cost.deuterium > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(config.cost.deuterium) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm pt-2 border-t">
|
||||
<span class="text-muted-foreground">{{ t('player.points') }}:</span>
|
||||
<span class="font-bold text-primary">{{ pointsPerUnit }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm">{{ t('defense.buildTime') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-3xl font-bold">{{ formatTime(config.buildTime) }}</div>
|
||||
<p class="text-xs text-muted-foreground mt-2">{{ t('defense.perUnit') }}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 批量建造计算器 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm">{{ t('defense.batchCalculator') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<Label class="w-20">{{ t('defense.quantity') }}:</Label>
|
||||
<Input v-model.number="quantity" type="number" min="1" class="flex-1" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4 pt-4 border-t">
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">{{ t('defense.totalCost') }}:</p>
|
||||
<div class="space-y-1 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(batchCost.metal) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(batchCost.crystal) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(batchCost.deuterium) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">{{ t('defense.totalTime') }}:</p>
|
||||
<div class="text-xl font-bold">{{ formatTime(config.buildTime * quantity) }}</div>
|
||||
<p class="text-xs text-muted-foreground">{{ t('player.points') }}: +{{ formatNumber(batchPoints) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import type { DefenseType } from '@/types/game'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Sword, Shield, ShieldCheck } from 'lucide-vue-next'
|
||||
import * as pointsLogic from '@/logic/pointsLogic'
|
||||
import { DEFENSES } from '@/config/gameConfig'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
defenseType: DefenseType
|
||||
}>()
|
||||
|
||||
const config = computed(() => DEFENSES[props.defenseType])
|
||||
const quantity = ref(1)
|
||||
|
||||
// 单个防御的积分
|
||||
const pointsPerUnit = computed(() => {
|
||||
return pointsLogic.calculateDefensePoints(props.defenseType, 1)
|
||||
})
|
||||
|
||||
// 批量建造成本
|
||||
const batchCost = computed(() => ({
|
||||
metal: config.value.cost.metal * quantity.value,
|
||||
crystal: config.value.cost.crystal * quantity.value,
|
||||
deuterium: config.value.cost.deuterium * quantity.value
|
||||
}))
|
||||
|
||||
// 批量建造积分
|
||||
const batchPoints = computed(() => {
|
||||
return pointsLogic.calculateDefensePoints(props.defenseType, quantity.value)
|
||||
})
|
||||
|
||||
const formatNumber = (num: number): string => {
|
||||
return num.toLocaleString()
|
||||
}
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
if (seconds < 60) return `${seconds}${t('common.timeSecond')}`
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
if (minutes < 60) return `${minutes}${t('common.timeMinute')}${secs}${t('common.timeSecond')}`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const mins = minutes % 60
|
||||
return `${hours}${t('common.timeHour')}${mins}${t('common.timeMinute')}`
|
||||
}
|
||||
</script>
|
||||
204
src/components/detail-views/ShipDetailView.vue
Normal file
204
src/components/detail-views/ShipDetailView.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 舰船基础信息 -->
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Sword class="h-4 w-4" />
|
||||
{{ t('shipyard.attack') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.attack) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Shield class="h-4 w-4" />
|
||||
{{ t('shipyard.shield') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.shield) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<ShieldCheck class="h-4 w-4" />
|
||||
{{ t('shipyard.armor') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.armor) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Zap class="h-4 w-4" />
|
||||
{{ t('shipyard.speed') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.speed) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Package class="h-4 w-4" />
|
||||
{{ t('shipyard.cargoCapacity') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.cargoCapacity) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm flex items-center gap-2">
|
||||
<Fuel class="h-4 w-4" />
|
||||
{{ t('shipyard.fuelConsumption') }}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-2xl font-bold">{{ formatNumber(config.fuelConsumption) }}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 建造成本和时间 -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm">{{ t('shipyard.buildCost') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<div v-if="config.cost.metal > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(config.cost.metal) }}</span>
|
||||
</div>
|
||||
<div v-if="config.cost.crystal > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(config.cost.crystal) }}</span>
|
||||
</div>
|
||||
<div v-if="config.cost.deuterium > 0" class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(config.cost.deuterium) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm pt-2 border-t">
|
||||
<span class="text-muted-foreground">{{ t('player.points') }}:</span>
|
||||
<span class="font-bold text-primary">{{ pointsPerUnit }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm">{{ t('shipyard.buildTime') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-3xl font-bold">{{ formatTime(config.buildTime) }}</div>
|
||||
<p class="text-xs text-muted-foreground mt-2">{{ t('shipyard.perUnit') }}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 批量建造计算器 -->
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-sm">{{ t('shipyard.batchCalculator') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<Label class="w-20">{{ t('shipyard.quantity') }}:</Label>
|
||||
<Input v-model.number="quantity" type="number" min="1" class="flex-1" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4 pt-4 border-t">
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">{{ t('shipyard.totalCost') }}:</p>
|
||||
<div class="space-y-1 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(batchCost.metal) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(batchCost.crystal) }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(batchCost.deuterium) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">{{ t('shipyard.totalTime') }}:</p>
|
||||
<div class="text-xl font-bold">{{ formatTime(config.buildTime * quantity) }}</div>
|
||||
<p class="text-xs text-muted-foreground">{{ t('player.points') }}: +{{ formatNumber(batchPoints) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import type { ShipType } from '@/types/game'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Sword, Shield, ShieldCheck, Zap, Package, Fuel } from 'lucide-vue-next'
|
||||
import * as pointsLogic from '@/logic/pointsLogic'
|
||||
import { SHIPS } from '@/config/gameConfig'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
shipType: ShipType
|
||||
}>()
|
||||
|
||||
const config = computed(() => SHIPS[props.shipType])
|
||||
const quantity = ref(1)
|
||||
|
||||
// 单艘舰船的积分
|
||||
const pointsPerUnit = computed(() => {
|
||||
return pointsLogic.calculateShipPoints(props.shipType, 1)
|
||||
})
|
||||
|
||||
// 批量建造成本
|
||||
const batchCost = computed(() => ({
|
||||
metal: config.value.cost.metal * quantity.value,
|
||||
crystal: config.value.cost.crystal * quantity.value,
|
||||
deuterium: config.value.cost.deuterium * quantity.value
|
||||
}))
|
||||
|
||||
// 批量建造积分
|
||||
const batchPoints = computed(() => {
|
||||
return pointsLogic.calculateShipPoints(props.shipType, quantity.value)
|
||||
})
|
||||
|
||||
const formatNumber = (num: number): string => {
|
||||
return num.toLocaleString()
|
||||
}
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
if (seconds < 60) return `${seconds}${t('common.timeSecond')}`
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
if (minutes < 60) return `${minutes}${t('common.timeMinute')}${secs}${t('common.timeSecond')}`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const mins = minutes % 60
|
||||
return `${hours}${t('common.timeHour')}${mins}${t('common.timeMinute')}`
|
||||
}
|
||||
</script>
|
||||
154
src/components/detail-views/TechnologyDetailView.vue
Normal file
154
src/components/detail-views/TechnologyDetailView.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<!-- 科技等级范围表格 -->
|
||||
<div class="border rounded-lg overflow-hidden">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead class="w-20 text-center">{{ t('research.levelRange') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('resources.metal') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('resources.crystal') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('resources.deuterium') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('research.researchTime') }}</TableHead>
|
||||
<TableHead class="text-center">{{ t('player.points') }}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow v-for="level in levelRange" :key="level" :class="{ 'bg-muted/50': level === currentLevel }">
|
||||
<TableCell class="text-center font-medium">
|
||||
<Badge v-if="level === currentLevel" variant="default">{{ level }}</Badge>
|
||||
<span v-else>{{ level }}</span>
|
||||
</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatNumber(getLevelData(level).cost.metal) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatNumber(getLevelData(level).cost.crystal) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatNumber(getLevelData(level).cost.deuterium) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">{{ formatTime(getLevelData(level).researchTime) }}</TableCell>
|
||||
<TableCell class="text-center text-sm">
|
||||
<span class="text-primary font-medium">+{{ getLevelData(level).points }}</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<!-- 累积统计 -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm">{{ t('research.totalCost') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(totalStats.metal) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(totalStats.crystal) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium">{{ formatNumber(totalStats.deuterium) }}</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader class="pb-3">
|
||||
<CardTitle class="text-sm">{{ t('research.totalPoints') }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="text-3xl font-bold text-primary">{{ formatNumber(totalStats.points) }}</div>
|
||||
<p class="text-xs text-muted-foreground mt-1">
|
||||
{{ t('research.levelRange') }}: {{ Math.max(0, currentLevel - 10) }} - {{ Math.min(currentLevel + 10, currentLevel + 10) }}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import type { TechnologyType } from '@/types/game'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import * as researchLogic from '@/logic/researchLogic'
|
||||
import * as pointsLogic from '@/logic/pointsLogic'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
technologyType: TechnologyType
|
||||
currentLevel: number
|
||||
}>()
|
||||
|
||||
// 等级范围:当前等级 ±10
|
||||
const levelRange = computed(() => {
|
||||
const start = Math.max(0, props.currentLevel - 10)
|
||||
const end = props.currentLevel + 10
|
||||
const levels = []
|
||||
for (let i = start; i <= end; i++) {
|
||||
levels.push(i)
|
||||
}
|
||||
return levels
|
||||
})
|
||||
|
||||
// 获取某个等级的详细数据
|
||||
const getLevelData = (level: number) => {
|
||||
if (level === 0) {
|
||||
return {
|
||||
cost: { metal: 0, crystal: 0, deuterium: 0 },
|
||||
researchTime: 0,
|
||||
points: 0
|
||||
}
|
||||
}
|
||||
|
||||
const cost = researchLogic.calculateTechnologyCost(props.technologyType, level)
|
||||
const researchTime = researchLogic.calculateTechnologyTime(props.technologyType, level - 1)
|
||||
|
||||
// 计算积分
|
||||
const points = pointsLogic.calculateTechnologyPoints(props.technologyType, level - 1, level)
|
||||
|
||||
return {
|
||||
cost,
|
||||
researchTime,
|
||||
points
|
||||
}
|
||||
}
|
||||
|
||||
// 累积统计
|
||||
const totalStats = computed(() => {
|
||||
let metal = 0
|
||||
let crystal = 0
|
||||
let deuterium = 0
|
||||
let points = 0
|
||||
|
||||
for (const level of levelRange.value) {
|
||||
if (level === 0) continue
|
||||
const data = getLevelData(level)
|
||||
metal += data.cost.metal
|
||||
crystal += data.cost.crystal
|
||||
deuterium += data.cost.deuterium
|
||||
points += data.points
|
||||
}
|
||||
|
||||
return { metal, crystal, deuterium, points }
|
||||
})
|
||||
|
||||
const formatNumber = (num: number): string => {
|
||||
return num.toLocaleString()
|
||||
}
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
if (seconds < 60) return `${seconds}${t('common.timeSecond')}`
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
if (minutes < 60) return `${minutes}${t('common.timeMinute')}${secs}${t('common.timeSecond')}`
|
||||
const hours = Math.floor(minutes / 60)
|
||||
const mins = minutes % 60
|
||||
return `${hours}${t('common.timeHour')}${mins}${t('common.timeMinute')}`
|
||||
}
|
||||
</script>
|
||||
15
src/components/ui/alert-dialog/AlertDialog.vue
Normal file
15
src/components/ui/alert-dialog/AlertDialog.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<AlertDialogRoot v-slot="slotProps" data-slot="alert-dialog" v-bind="forwarded">
|
||||
<slot v-bind="slotProps" />
|
||||
</AlertDialogRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogEmits, AlertDialogProps } from 'reka-ui'
|
||||
import { AlertDialogRoot, useForwardPropsEmits } from 'reka-ui'
|
||||
|
||||
const props = defineProps<AlertDialogProps>()
|
||||
const emits = defineEmits<AlertDialogEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
18
src/components/ui/alert-dialog/AlertDialogAction.vue
Normal file
18
src/components/ui/alert-dialog/AlertDialogAction.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<AlertDialogAction v-bind="delegatedProps" :class="cn(buttonVariants(), props.class)">
|
||||
<slot />
|
||||
</AlertDialogAction>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogActionProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { AlertDialogAction } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
||||
const props = defineProps<AlertDialogActionProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
18
src/components/ui/alert-dialog/AlertDialogCancel.vue
Normal file
18
src/components/ui/alert-dialog/AlertDialogCancel.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<template>
|
||||
<AlertDialogCancel v-bind="delegatedProps" :class="cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', props.class)">
|
||||
<slot />
|
||||
</AlertDialogCancel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogCancelProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { AlertDialogCancel } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '@/components/ui/button'
|
||||
|
||||
const props = defineProps<AlertDialogCancelProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
39
src/components/ui/alert-dialog/AlertDialogContent.vue
Normal file
39
src/components/ui/alert-dialog/AlertDialogContent.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay
|
||||
data-slot="alert-dialog-overlay"
|
||||
class="data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80"
|
||||
/>
|
||||
<AlertDialogContent
|
||||
data-slot="alert-dialog-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</AlertDialogContent>
|
||||
</AlertDialogPortal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogContentEmits, AlertDialogContentProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { AlertDialogContent, AlertDialogOverlay, AlertDialogPortal, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const props = defineProps<AlertDialogContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<AlertDialogContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
21
src/components/ui/alert-dialog/AlertDialogDescription.vue
Normal file
21
src/components/ui/alert-dialog/AlertDialogDescription.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<AlertDialogDescription
|
||||
data-slot="alert-dialog-description"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('text-muted-foreground text-sm', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</AlertDialogDescription>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogDescriptionProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { AlertDialogDescription } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<AlertDialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
14
src/components/ui/alert-dialog/AlertDialogFooter.vue
Normal file
14
src/components/ui/alert-dialog/AlertDialogFooter.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="alert-dialog-footer" :class="cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/alert-dialog/AlertDialogHeader.vue
Normal file
14
src/components/ui/alert-dialog/AlertDialogHeader.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="alert-dialog-header" :class="cn('flex flex-col gap-2 text-center sm:text-left', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
17
src/components/ui/alert-dialog/AlertDialogTitle.vue
Normal file
17
src/components/ui/alert-dialog/AlertDialogTitle.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<AlertDialogTitle data-slot="alert-dialog-title" v-bind="delegatedProps" :class="cn('text-lg font-semibold', props.class)">
|
||||
<slot />
|
||||
</AlertDialogTitle>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogTitleProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { AlertDialogTitle } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<AlertDialogTitleProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
12
src/components/ui/alert-dialog/AlertDialogTrigger.vue
Normal file
12
src/components/ui/alert-dialog/AlertDialogTrigger.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<AlertDialogTrigger data-slot="alert-dialog-trigger" v-bind="props">
|
||||
<slot />
|
||||
</AlertDialogTrigger>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { AlertDialogTriggerProps } from 'reka-ui'
|
||||
import { AlertDialogTrigger } from 'reka-ui'
|
||||
|
||||
const props = defineProps<AlertDialogTriggerProps>()
|
||||
</script>
|
||||
9
src/components/ui/alert-dialog/index.ts
Normal file
9
src/components/ui/alert-dialog/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export { default as AlertDialog } from './AlertDialog.vue'
|
||||
export { default as AlertDialogAction } from './AlertDialogAction.vue'
|
||||
export { default as AlertDialogCancel } from './AlertDialogCancel.vue'
|
||||
export { default as AlertDialogContent } from './AlertDialogContent.vue'
|
||||
export { default as AlertDialogDescription } from './AlertDialogDescription.vue'
|
||||
export { default as AlertDialogFooter } from './AlertDialogFooter.vue'
|
||||
export { default as AlertDialogHeader } from './AlertDialogHeader.vue'
|
||||
export { default as AlertDialogTitle } from './AlertDialogTitle.vue'
|
||||
export { default as AlertDialogTrigger } from './AlertDialogTrigger.vue'
|
||||
24
src/components/ui/badge/Badge.vue
Normal file
24
src/components/ui/badge/Badge.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<Primitive data-slot="badge" :class="cn(badgeVariants({ variant }), props.class)" v-bind="delegatedProps">
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PrimitiveProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { BadgeVariants } from '.'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { badgeVariants } from '.'
|
||||
|
||||
const props = defineProps<
|
||||
PrimitiveProps & {
|
||||
variant?: BadgeVariants['variant']
|
||||
class?: HTMLAttributes['class']
|
||||
}
|
||||
>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
23
src/components/ui/badge/index.ts
Normal file
23
src/components/ui/badge/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
export { default as Badge } from './Badge.vue'
|
||||
|
||||
export const badgeVariants = cva(
|
||||
'inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90',
|
||||
secondary: 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90',
|
||||
destructive:
|
||||
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
outline: 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default'
|
||||
}
|
||||
}
|
||||
)
|
||||
export type BadgeVariants = VariantProps<typeof badgeVariants>
|
||||
24
src/components/ui/button/Button.vue
Normal file
24
src/components/ui/button/Button.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<Primitive data-slot="button" :as="as" :as-child="asChild" :class="cn(buttonVariants({ variant, size }), props.class)">
|
||||
<slot />
|
||||
</Primitive>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PrimitiveProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { ButtonVariants } from '.'
|
||||
import { Primitive } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { buttonVariants } from '.'
|
||||
|
||||
interface Props extends PrimitiveProps {
|
||||
variant?: ButtonVariants['variant']
|
||||
size?: ButtonVariants['size']
|
||||
class?: HTMLAttributes['class']
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
as: 'button'
|
||||
})
|
||||
</script>
|
||||
35
src/components/ui/button/index.ts
Normal file
35
src/components/ui/button/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
export { default as Button } from './Button.vue'
|
||||
|
||||
export const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60',
|
||||
outline:
|
||||
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
|
||||
link: 'text-primary underline-offset-4 hover:underline'
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
|
||||
sm: 'h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5',
|
||||
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
|
||||
icon: 'size-9',
|
||||
'icon-sm': 'size-8',
|
||||
'icon-lg': 'size-10'
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default'
|
||||
}
|
||||
}
|
||||
)
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>
|
||||
14
src/components/ui/card/Card.vue
Normal file
14
src/components/ui/card/Card.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="card" :class="cn('bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/card/CardAction.vue
Normal file
14
src/components/ui/card/CardAction.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="card-action" :class="cn('col-start-2 row-span-2 row-start-1 self-start justify-self-end', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/card/CardContent.vue
Normal file
14
src/components/ui/card/CardContent.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="card-content" :class="cn('px-6', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/card/CardDescription.vue
Normal file
14
src/components/ui/card/CardDescription.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<p data-slot="card-description" :class="cn('text-muted-foreground text-sm', props.class)">
|
||||
<slot />
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/card/CardFooter.vue
Normal file
14
src/components/ui/card/CardFooter.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="card-footer" :class="cn('flex items-center px-6 [.border-t]:pt-6', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
22
src/components/ui/card/CardHeader.vue
Normal file
22
src/components/ui/card/CardHeader.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div
|
||||
data-slot="card-header"
|
||||
:class="
|
||||
cn(
|
||||
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/card/CardTitle.vue
Normal file
14
src/components/ui/card/CardTitle.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<h3 data-slot="card-title" :class="cn('leading-none font-semibold', props.class)">
|
||||
<slot />
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
7
src/components/ui/card/index.ts
Normal file
7
src/components/ui/card/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export { default as Card } from './Card.vue'
|
||||
export { default as CardAction } from './CardAction.vue'
|
||||
export { default as CardContent } from './CardContent.vue'
|
||||
export { default as CardDescription } from './CardDescription.vue'
|
||||
export { default as CardFooter } from './CardFooter.vue'
|
||||
export { default as CardHeader } from './CardHeader.vue'
|
||||
export { default as CardTitle } from './CardTitle.vue'
|
||||
15
src/components/ui/dialog/Dialog.vue
Normal file
15
src/components/ui/dialog/Dialog.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<DialogRoot v-slot="slotProps" data-slot="dialog" v-bind="forwarded">
|
||||
<slot v-bind="slotProps" />
|
||||
</DialogRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogRootEmits, DialogRootProps } from 'reka-ui'
|
||||
import { DialogRoot, useForwardPropsEmits } from 'reka-ui'
|
||||
|
||||
const props = defineProps<DialogRootProps>()
|
||||
const emits = defineEmits<DialogRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
12
src/components/ui/dialog/DialogClose.vue
Normal file
12
src/components/ui/dialog/DialogClose.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<DialogClose data-slot="dialog-close" v-bind="props">
|
||||
<slot />
|
||||
</DialogClose>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogCloseProps } from 'reka-ui'
|
||||
import { DialogClose } from 'reka-ui'
|
||||
|
||||
const props = defineProps<DialogCloseProps>()
|
||||
</script>
|
||||
49
src/components/ui/dialog/DialogContent.vue
Normal file
49
src/components/ui/dialog/DialogContent.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogContent
|
||||
data-slot="dialog-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
|
||||
<DialogClose
|
||||
v-if="showCloseButton"
|
||||
data-slot="dialog-close"
|
||||
class="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||
>
|
||||
<X />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</DialogPortal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogContentEmits, DialogContentProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { X } from 'lucide-vue-next'
|
||||
import { DialogClose, DialogContent, DialogPortal, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import DialogOverlay from './DialogOverlay.vue'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<DialogContentProps & { class?: HTMLAttributes['class']; showCloseButton?: boolean }>(), {
|
||||
showCloseButton: true
|
||||
})
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
19
src/components/ui/dialog/DialogDescription.vue
Normal file
19
src/components/ui/dialog/DialogDescription.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<DialogDescription data-slot="dialog-description" v-bind="forwardedProps" :class="cn('text-muted-foreground text-sm', props.class)">
|
||||
<slot />
|
||||
</DialogDescription>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogDescriptionProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { DialogDescription, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogDescriptionProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
12
src/components/ui/dialog/DialogFooter.vue
Normal file
12
src/components/ui/dialog/DialogFooter.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div data-slot="dialog-footer" :class="cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{ class?: HTMLAttributes['class'] }>()
|
||||
</script>
|
||||
14
src/components/ui/dialog/DialogHeader.vue
Normal file
14
src/components/ui/dialog/DialogHeader.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="dialog-header" :class="cn('flex flex-col gap-2 text-center sm:text-left', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
26
src/components/ui/dialog/DialogOverlay.vue
Normal file
26
src/components/ui/dialog/DialogOverlay.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<DialogOverlay
|
||||
data-slot="dialog-overlay"
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</DialogOverlay>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogOverlayProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { DialogOverlay } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogOverlayProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
51
src/components/ui/dialog/DialogScrollContent.vue
Normal file
51
src/components/ui/dialog/DialogScrollContent.vue
Normal file
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<DialogPortal>
|
||||
<DialogOverlay
|
||||
class="fixed inset-0 z-50 grid place-items-center overflow-y-auto bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0"
|
||||
>
|
||||
<DialogContent
|
||||
:class="
|
||||
cn(
|
||||
'relative z-50 grid w-full max-w-lg my-8 gap-4 border border-border bg-background p-6 shadow-lg duration-200 sm:rounded-lg md:w-full',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
@pointer-down-outside="(event) => {
|
||||
const originalEvent = event.detail.originalEvent;
|
||||
const target = originalEvent.target as HTMLElement;
|
||||
if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}"
|
||||
>
|
||||
<slot />
|
||||
|
||||
<DialogClose class="absolute top-4 right-4 p-0.5 transition-colors rounded-md hover:bg-secondary">
|
||||
<X class="w-4 h-4" />
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogClose>
|
||||
</DialogContent>
|
||||
</DialogOverlay>
|
||||
</DialogPortal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogContentEmits, DialogContentProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { X } from 'lucide-vue-next'
|
||||
import { DialogClose, DialogContent, DialogOverlay, DialogPortal, useForwardPropsEmits } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
const props = defineProps<DialogContentProps & { class?: HTMLAttributes['class'] }>()
|
||||
const emits = defineEmits<DialogContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
19
src/components/ui/dialog/DialogTitle.vue
Normal file
19
src/components/ui/dialog/DialogTitle.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<DialogTitle data-slot="dialog-title" v-bind="forwardedProps" :class="cn('text-lg leading-none font-semibold', props.class)">
|
||||
<slot />
|
||||
</DialogTitle>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogTitleProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { DialogTitle, useForwardProps } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<DialogTitleProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps)
|
||||
</script>
|
||||
12
src/components/ui/dialog/DialogTrigger.vue
Normal file
12
src/components/ui/dialog/DialogTrigger.vue
Normal file
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<DialogTrigger data-slot="dialog-trigger" v-bind="props">
|
||||
<slot />
|
||||
</DialogTrigger>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { DialogTriggerProps } from 'reka-ui'
|
||||
import { DialogTrigger } from 'reka-ui'
|
||||
|
||||
const props = defineProps<DialogTriggerProps>()
|
||||
</script>
|
||||
10
src/components/ui/dialog/index.ts
Normal file
10
src/components/ui/dialog/index.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export { default as Dialog } from './Dialog.vue'
|
||||
export { default as DialogClose } from './DialogClose.vue'
|
||||
export { default as DialogContent } from './DialogContent.vue'
|
||||
export { default as DialogDescription } from './DialogDescription.vue'
|
||||
export { default as DialogFooter } from './DialogFooter.vue'
|
||||
export { default as DialogHeader } from './DialogHeader.vue'
|
||||
export { default as DialogOverlay } from './DialogOverlay.vue'
|
||||
export { default as DialogScrollContent } from './DialogScrollContent.vue'
|
||||
export { default as DialogTitle } from './DialogTitle.vue'
|
||||
export { default as DialogTrigger } from './DialogTrigger.vue'
|
||||
35
src/components/ui/input/Input.vue
Normal file
35
src/components/ui/input/Input.vue
Normal file
@@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<input
|
||||
v-model="modelValue"
|
||||
data-slot="input"
|
||||
:class="
|
||||
cn(
|
||||
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
|
||||
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
defaultValue?: string | number
|
||||
modelValue?: string | number
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'update:modelValue', payload: string | number): void
|
||||
}>()
|
||||
|
||||
const modelValue = useVModel(props, 'modelValue', emits, {
|
||||
passive: true,
|
||||
defaultValue: props.defaultValue
|
||||
})
|
||||
</script>
|
||||
1
src/components/ui/input/index.ts
Normal file
1
src/components/ui/input/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Input } from './Input.vue'
|
||||
26
src/components/ui/label/Label.vue
Normal file
26
src/components/ui/label/Label.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<Label
|
||||
data-slot="label"
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</Label>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { LabelProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { Label } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<LabelProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
1
src/components/ui/label/index.ts
Normal file
1
src/components/ui/label/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Label } from './Label.vue'
|
||||
19
src/components/ui/popover/Popover.vue
Normal file
19
src/components/ui/popover/Popover.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import type { PopoverRootEmits, PopoverRootProps } from "reka-ui"
|
||||
import { PopoverRoot, useForwardPropsEmits } from "reka-ui"
|
||||
|
||||
const props = defineProps<PopoverRootProps>()
|
||||
const emits = defineEmits<PopoverRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopoverRoot
|
||||
v-slot="slotProps"
|
||||
data-slot="popover"
|
||||
v-bind="forwarded"
|
||||
>
|
||||
<slot v-bind="slotProps" />
|
||||
</PopoverRoot>
|
||||
</template>
|
||||
15
src/components/ui/popover/PopoverAnchor.vue
Normal file
15
src/components/ui/popover/PopoverAnchor.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { PopoverAnchorProps } from "reka-ui"
|
||||
import { PopoverAnchor } from "reka-ui"
|
||||
|
||||
const props = defineProps<PopoverAnchorProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopoverAnchor
|
||||
data-slot="popover-anchor"
|
||||
v-bind="props"
|
||||
>
|
||||
<slot />
|
||||
</PopoverAnchor>
|
||||
</template>
|
||||
45
src/components/ui/popover/PopoverContent.vue
Normal file
45
src/components/ui/popover/PopoverContent.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<script setup lang="ts">
|
||||
import type { PopoverContentEmits, PopoverContentProps } from "reka-ui"
|
||||
import type { HTMLAttributes } from "vue"
|
||||
import { reactiveOmit } from "@vueuse/core"
|
||||
import {
|
||||
PopoverContent,
|
||||
PopoverPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "reka-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<PopoverContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||
{
|
||||
align: "center",
|
||||
sideOffset: 4,
|
||||
},
|
||||
)
|
||||
const emits = defineEmits<PopoverContentEmits>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class")
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopoverPortal>
|
||||
<PopoverContent
|
||||
data-slot="popover-content"
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 rounded-md border p-4 shadow-md origin-(--reka-popover-content-transform-origin) outline-hidden',
|
||||
props.class,
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</PopoverContent>
|
||||
</PopoverPortal>
|
||||
</template>
|
||||
15
src/components/ui/popover/PopoverTrigger.vue
Normal file
15
src/components/ui/popover/PopoverTrigger.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import type { PopoverTriggerProps } from "reka-ui"
|
||||
import { PopoverTrigger } from "reka-ui"
|
||||
|
||||
const props = defineProps<PopoverTriggerProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<PopoverTrigger
|
||||
data-slot="popover-trigger"
|
||||
v-bind="props"
|
||||
>
|
||||
<slot />
|
||||
</PopoverTrigger>
|
||||
</template>
|
||||
4
src/components/ui/popover/index.ts
Normal file
4
src/components/ui/popover/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as Popover } from "./Popover.vue"
|
||||
export { default as PopoverAnchor } from "./PopoverAnchor.vue"
|
||||
export { default as PopoverContent } from "./PopoverContent.vue"
|
||||
export { default as PopoverTrigger } from "./PopoverTrigger.vue"
|
||||
27
src/components/ui/progress/Progress.vue
Normal file
27
src/components/ui/progress/Progress.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<ProgressRoot
|
||||
data-slot="progress"
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('bg-primary/20 relative h-2 w-full overflow-hidden rounded-full', props.class)"
|
||||
>
|
||||
<ProgressIndicator
|
||||
data-slot="progress-indicator"
|
||||
class="bg-primary h-full w-full flex-1 transition-all"
|
||||
:style="`transform: translateX(-${100 - (props.modelValue ?? 0)}%);`"
|
||||
/>
|
||||
</ProgressRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ProgressRootProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ProgressIndicator, ProgressRoot } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = withDefaults(defineProps<ProgressRootProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
modelValue: 0
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
1
src/components/ui/progress/index.ts
Normal file
1
src/components/ui/progress/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Progress } from './Progress.vue'
|
||||
15
src/components/ui/select/Select.vue
Normal file
15
src/components/ui/select/Select.vue
Normal file
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<SelectRoot v-slot="slotProps" data-slot="select" v-bind="forwarded">
|
||||
<slot v-bind="slotProps" />
|
||||
</SelectRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SelectRootEmits, SelectRootProps } from 'reka-ui'
|
||||
import { SelectRoot, useForwardPropsEmits } from 'reka-ui'
|
||||
|
||||
const props = defineProps<SelectRootProps>()
|
||||
const emits = defineEmits<SelectRootEmits>()
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
</script>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user