diff --git a/src/app.js b/src/app.js
index 973b1e3d..534bf465 100644
--- a/src/app.js
+++ b/src/app.js
@@ -121,19 +121,70 @@ class Application {
this.app.set('trust proxy', 1);
}
+ // 🎨 新版管理界面静态文件服务(必须在其他路由之前)
+ const adminSpaPath = path.join(__dirname, '..', 'web', 'admin-spa', 'dist');
+ if (fs.existsSync(adminSpaPath)) {
+ // 处理不带斜杠的路径,重定向到带斜杠的路径
+ this.app.get('/admin-next', (req, res) => {
+ res.redirect(301, '/admin-next/');
+ });
+
+ // 安全的静态文件服务配置
+ this.app.use('/admin-next/', express.static(adminSpaPath, {
+ maxAge: '1d', // 缓存静态资源1天
+ etag: true,
+ lastModified: true,
+ index: 'index.html',
+ // 安全选项:禁止目录遍历
+ dotfiles: 'deny', // 拒绝访问点文件
+ redirect: false, // 禁止目录重定向
+ // 自定义错误处理
+ setHeaders: (res, path) => {
+ // 为不同类型的文件设置适当的缓存策略
+ if (path.endsWith('.js') || path.endsWith('.css')) {
+ res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); // 1年缓存
+ } else if (path.endsWith('.html')) {
+ res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
+ }
+ }
+ }));
+
+ // 处理SPA路由:所有未匹配的admin-next路径都返回index.html
+ this.app.get('/admin-next/*', (req, res, next) => {
+ // 安全检查:防止路径遍历攻击
+ const requestPath = req.path.replace('/admin-next/', '');
+ if (requestPath.includes('..') || requestPath.includes('//') || requestPath.includes('\\')) {
+ return res.status(400).json({ error: 'Invalid path' });
+ }
+
+ // 如果是静态资源请求但文件不存在,返回404
+ if (requestPath.match(/\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf)$/i)) {
+ return res.status(404).send('Not found');
+ }
+
+ // 其他路径返回index.html(SPA路由处理)
+ res.sendFile(path.join(adminSpaPath, 'index.html'));
+ });
+
+ logger.info('✅ Admin SPA (next) static files mounted at /admin-next/');
+ } else {
+ logger.warn('⚠️ Admin SPA dist directory not found, skipping /admin-next route');
+ }
+
// 🛣️ 路由
this.app.use('/api', apiRoutes);
this.app.use('/claude', apiRoutes); // /claude 路由别名,与 /api 功能相同
this.app.use('/admin', adminRoutes);
+ // 使用 web 路由(包含 auth 和页面重定向)
this.app.use('/web', webRoutes);
this.app.use('/apiStats', apiStatsRoutes);
this.app.use('/gemini', geminiRoutes);
this.app.use('/openai/gemini', openaiGeminiRoutes);
this.app.use('/openai/claude', openaiClaudeRoutes);
- // 🏠 根路径重定向到API统计页面
+ // 🏠 根路径重定向到新版管理界面
this.app.get('/', (req, res) => {
- res.redirect('/apiStats');
+ res.redirect('/admin-next/api-stats');
});
// 🏥 增强的健康检查端点
@@ -321,7 +372,7 @@ class Application {
this.server = this.app.listen(config.server.port, config.server.host, () => {
logger.start(`🚀 Claude Relay Service started on ${config.server.host}:${config.server.port}`);
- logger.info(`🌐 Web interface: http://${config.server.host}:${config.server.port}/web`);
+ logger.info(`🌐 Web interface: http://${config.server.host}:${config.server.port}/admin-next/api-stats`);
logger.info(`🔗 API endpoint: http://${config.server.host}:${config.server.port}/api/v1/messages`);
logger.info(`⚙️ Admin API: http://${config.server.host}:${config.server.port}/admin`);
logger.info(`🏥 Health check: http://${config.server.host}:${config.server.port}/health`);
diff --git a/src/routes/apiStats.js b/src/routes/apiStats.js
index d190285b..e4db6f4f 100644
--- a/src/routes/apiStats.js
+++ b/src/routes/apiStats.js
@@ -1,6 +1,4 @@
const express = require('express');
-const path = require('path');
-const fs = require('fs');
const redis = require('../models/redis');
const logger = require('../utils/logger');
const apiKeyService = require('../services/apiKeyService');
@@ -8,45 +6,9 @@ const CostCalculator = require('../utils/costCalculator');
const router = express.Router();
-// 🛡️ 安全文件服务函数
-function serveStaticFile(req, res, filename, contentType) {
- const filePath = path.join(__dirname, '../../web/apiStats', filename);
-
- try {
- // 检查文件是否存在
- if (!fs.existsSync(filePath)) {
- logger.error(`❌ API Stats file not found: ${filePath}`);
- return res.status(404).json({ error: 'File not found' });
- }
-
- // 读取并返回文件内容
- const content = fs.readFileSync(filePath, 'utf8');
- res.setHeader('Content-Type', contentType);
- res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
- res.setHeader('Pragma', 'no-cache');
- res.setHeader('Expires', '0');
- res.send(content);
-
- logger.info(`📄 Served API Stats file: ${filename}`);
- } catch (error) {
- logger.error(`❌ Error serving API Stats file ${filename}:`, error);
- res.status(500).json({ error: 'Internal server error' });
- }
-}
-
-// 🏠 API Stats 主页面
+// 🏠 重定向页面请求到新版 admin-spa
router.get('/', (req, res) => {
- serveStaticFile(req, res, 'index.html', 'text/html; charset=utf-8');
-});
-
-// 📱 JavaScript 文件
-router.get('/app.js', (req, res) => {
- serveStaticFile(req, res, 'app.js', 'application/javascript; charset=utf-8');
-});
-
-// 🎨 CSS 文件
-router.get('/style.css', (req, res) => {
- serveStaticFile(req, res, 'style.css', 'text/css; charset=utf-8');
+ res.redirect(301, '/admin-next/api-stats');
});
// 🔑 获取 API Key 对应的 ID
diff --git a/src/routes/web.js b/src/routes/web.js
index 7aacae26..4cd775bb 100644
--- a/src/routes/web.js
+++ b/src/routes/web.js
@@ -12,52 +12,10 @@ const router = express.Router();
// 🏠 服务静态文件
router.use('/assets', express.static(path.join(__dirname, '../../web/assets')));
-// 🔒 Web管理界面文件白名单 - 仅允许这些特定文件
-const ALLOWED_FILES = {
- 'index.html': {
- path: path.join(__dirname, '../../web/admin/index.html'),
- contentType: 'text/html; charset=utf-8'
- },
- 'app.js': {
- path: path.join(__dirname, '../../web/admin/app.js'),
- contentType: 'application/javascript; charset=utf-8'
- },
- 'style.css': {
- path: path.join(__dirname, '../../web/admin/style.css'),
- contentType: 'text/css; charset=utf-8'
- },
-};
-
-// 🛡️ 安全文件服务函数
-function serveWhitelistedFile(req, res, filename) {
- const fileConfig = ALLOWED_FILES[filename];
-
- if (!fileConfig) {
- logger.security(`🚨 Attempted access to non-whitelisted file: ${filename}`);
- return res.status(404).json({ error: 'File not found' });
- }
-
- try {
- // 检查文件是否存在
- if (!fs.existsSync(fileConfig.path)) {
- logger.error(`❌ Whitelisted file not found: ${fileConfig.path}`);
- return res.status(404).json({ error: 'File not found' });
- }
-
- // 读取并返回文件内容
- const content = fs.readFileSync(fileConfig.path, 'utf8');
- res.setHeader('Content-Type', fileConfig.contentType);
- res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
- res.setHeader('Pragma', 'no-cache');
- res.setHeader('Expires', '0');
- res.send(content);
-
- logger.info(`📄 Served whitelisted file: ${filename}`);
- } catch (error) {
- logger.error(`❌ Error serving file ${filename}:`, error);
- res.status(500).json({ error: 'Internal server error' });
- }
-}
+// 🌐 页面路由重定向到新版 admin-spa
+router.get('/', (req, res) => {
+ res.redirect(301, '/admin-next/api-stats');
+});
// 🔐 管理员登录
router.post('/auth/login', async (req, res) => {
@@ -387,22 +345,4 @@ router.post('/auth/refresh', async (req, res) => {
}
});
-// 🌐 Web管理界面路由 - 使用固定白名单
-router.get('/', (req, res) => {
- serveWhitelistedFile(req, res, 'index.html');
-});
-
-router.get('/app.js', (req, res) => {
- serveWhitelistedFile(req, res, 'app.js');
-});
-
-router.get('/style.css', (req, res) => {
- serveWhitelistedFile(req, res, 'style.css');
-});
-
-
-
-
-// 🔑 Gemini OAuth 回调页面
-
module.exports = router;
\ No newline at end of file
diff --git a/src/services/geminiAccountService.js b/src/services/geminiAccountService.js
index bd829f56..7ffe286b 100644
--- a/src/services/geminiAccountService.js
+++ b/src/services/geminiAccountService.js
@@ -654,7 +654,7 @@ async function refreshAccountToken(accountId) {
errorMessage: error.message
});
} catch (updateError) {
- logger.error(`Failed to update account status after refresh error:`, updateError);
+ logger.error('Failed to update account status after refresh error:', updateError);
}
}
diff --git a/web/admin-spa/.env.example b/web/admin-spa/.env.example
new file mode 100644
index 00000000..3d306c8c
--- /dev/null
+++ b/web/admin-spa/.env.example
@@ -0,0 +1,29 @@
+# ========== 基础配置 ==========
+
+# 应用基础路径
+# 用于配置路由和资源的基础路径
+# 开发环境默认:/admin/
+# 生产环境默认:/admin-next/
+# 如果使用默认值,可以注释掉此行
+#VITE_APP_BASE_URL=/admin/
+
+# 应用标题
+# 显示在浏览器标签页和页面头部
+VITE_APP_TITLE=Claude Relay Service - 管理后台
+
+# ========== 开发环境配置 ==========
+
+# API 代理目标地址
+# 开发环境下,所有 /webapi 前缀的请求会被代理到这个地址
+# 默认值:http://localhost:3000
+VITE_API_TARGET=http://localhost:3000
+
+# HTTP 代理配置(可选)
+# 如果需要通过代理访问后端服务器,请取消注释并配置
+# 格式:http://proxy-host:port
+#VITE_HTTP_PROXY=http://127.0.0.1:7890
+
+# ========== 使用说明 ==========
+# 1. 复制此文件为 .env.local 进行本地配置
+# 2. .env.local 文件不会被提交到版本控制
+# 3. 详细说明请查看 ENV_CONFIG.md
\ No newline at end of file
diff --git a/web/admin-spa/.eslintrc.cjs b/web/admin-spa/.eslintrc.cjs
new file mode 100644
index 00000000..349b7420
--- /dev/null
+++ b/web/admin-spa/.eslintrc.cjs
@@ -0,0 +1,22 @@
+module.exports = {
+ root: true,
+ env: {
+ node: true,
+ browser: true,
+ es2021: true
+ },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:vue/vue3-recommended'
+ ],
+ parserOptions: {
+ ecmaVersion: 2021,
+ sourceType: 'module'
+ },
+ rules: {
+ 'vue/multi-word-component-names': 'off',
+ 'vue/no-v-html': 'off',
+ 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
+ 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
+ }
+}
\ No newline at end of file
diff --git a/web/admin-spa/.gitignore b/web/admin-spa/.gitignore
new file mode 100644
index 00000000..12e7eba2
--- /dev/null
+++ b/web/admin-spa/.gitignore
@@ -0,0 +1,33 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+# Dependencies
+node_modules
+
+# Production build files
+dist
+dist-ssr
+
+# Local env files
+*.local
+.env.local
+.env.*.local
+.env
+vite.config.js.timestamp-*.mjs
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
\ No newline at end of file
diff --git a/web/admin-spa/README.md b/web/admin-spa/README.md
new file mode 100644
index 00000000..c6674429
--- /dev/null
+++ b/web/admin-spa/README.md
@@ -0,0 +1,147 @@
+# Claude Relay Service 管理后台 SPA
+
+这是 Claude Relay Service 管理后台的 Vue3 SPA 重构版本。
+
+## 开发环境要求
+
+- Node.js >= 16
+- npm >= 7
+
+## 安装和运行
+
+### 1. 安装依赖
+
+```bash
+cd web/admin-spa
+npm install
+```
+
+### 2. 开发模式运行
+
+```bash
+npm run dev
+```
+
+**重要提示:**
+- 开发服务器启动后,会自动在浏览器中打开
+- 必须访问完整路径:http://localhost:3001/web/admin/
+- 不要访问 http://localhost:3001/ (会显示404)
+- 首次访问会自动跳转到登录页面
+
+### 3. 生产构建
+
+```bash
+npm run build
+```
+
+构建产物将输出到 `dist` 目录。
+
+### 4. 预览生产构建
+
+```bash
+npm run preview
+```
+
+## 项目结构
+
+```
+web/admin-spa/
+├── public/ # 静态资源
+├── src/
+│ ├── api/ # API 接口封装
+│ ├── assets/ # 资源文件
+│ ├── components/ # 组件
+│ ├── composables/ # 组合式函数
+│ ├── router/ # 路由配置
+│ ├── stores/ # Pinia 状态管理
+│ ├── utils/ # 工具函数
+│ ├── views/ # 页面视图
+│ ├── App.vue # 根组件
+│ └── main.js # 入口文件
+├── package.json
+└── vite.config.js
+```
+
+## 功能模块
+
+- ✅ 登录认证
+- ✅ 仪表板(系统统计、使用趋势、模型分布)
+- 🚧 API Keys 管理
+- 🚧 账户管理(Claude/Gemini)
+- 🚧 使用教程
+- 🚧 系统设置
+
+## 技术栈
+
+- Vue 3.3.4
+- Vue Router 4
+- Pinia(状态管理)
+- Element Plus 2.4.4
+- Tailwind CSS
+- Chart.js 4.4.0
+- Vite 5
+
+## 开发注意事项
+
+1. 所有 API 请求都通过 `/api` 目录下的模块进行封装
+2. 状态管理使用 Pinia,存放在 `/stores` 目录
+3. 组件按功能模块组织在 `/components` 目录下
+4. 保持与原版页面的功能和样式一致性
+
+## 代理配置
+
+如果你的后端服务器需要通过代理访问(例如服务器在国外),可以配置 HTTP 代理:
+
+### 方法一:使用环境变量文件(推荐)
+
+创建 `.env.development.local` 文件:
+
+```bash
+# 后端服务器地址
+VITE_API_TARGET=http://74.48.134.98:3000
+
+# HTTP 代理配置
+VITE_HTTP_PROXY=http://127.0.0.1:7890
+```
+
+### 方法二:使用系统环境变量
+
+```bash
+# Linux/Mac
+export VITE_HTTP_PROXY=http://127.0.0.1:7890
+npm run dev
+
+# Windows
+set VITE_HTTP_PROXY=http://127.0.0.1:7890
+npm run dev
+```
+
+注意:`.env.development.local` 文件不会被提交到版本控制,适合存放本地特定的配置。
+
+## 部署
+
+构建后的文件需要部署到 Claude Relay Service 的 `web/admin/` 路径下。
+
+## 常见问题
+
+### Q: 访问 localhost:3001 显示 404?
+A: 这是正常的。应用配置在 `/web/admin/` 路径下,必须访问完整路径:http://localhost:3001/web/admin/
+
+### Q: 登录时 API 请求失败(500错误)?
+A:
+1. **确保主服务运行**:Claude Relay Service 必须运行在 http://localhost:3000
+2. **检查代理配置**:Vite 会自动代理 `/admin` 和 `/api` 请求到 3000 端口
+3. **重启开发服务器**:如果修改了配置,需要重启 `npm run dev`
+4. **测试代理**:运行 `node test-proxy.js` 检查代理是否正常工作
+
+### Q: 如何处理开发和生产环境的 API 配置?
+A:
+- **开发环境**:使用 Vite 代理,自动转发请求到 localhost:3000
+- **生产环境**:直接使用相对路径 `/admin`,无需配置
+- 两种环境都使用相同的 API 路径,通过环境变量自动切换
+
+### Q: 如何部署到生产环境?
+A:
+1. 运行 `npm run build` 构建项目
+2. 将 `dist` 目录内容复制到服务器的 `/web/admin/` 路径
+3. 确保服务器配置了 SPA 路由回退规则
\ No newline at end of file
diff --git a/web/admin-spa/components/.gitkeep b/web/admin-spa/components/.gitkeep
new file mode 100644
index 00000000..0661b78e
--- /dev/null
+++ b/web/admin-spa/components/.gitkeep
@@ -0,0 +1 @@
+# This file keeps the empty directory in git
\ No newline at end of file
diff --git a/web/admin-spa/index.html b/web/admin-spa/index.html
new file mode 100644
index 00000000..789db5a2
--- /dev/null
+++ b/web/admin-spa/index.html
@@ -0,0 +1,26 @@
+
+
+
+
+
+ Claude Relay Service - 管理后台
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/package-lock.json b/web/admin-spa/package-lock.json
new file mode 100644
index 00000000..d73151b8
--- /dev/null
+++ b/web/admin-spa/package-lock.json
@@ -0,0 +1,4760 @@
+{
+ "name": "claude-relay-admin-spa",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "claude-relay-admin-spa",
+ "version": "1.0.0",
+ "dependencies": {
+ "@fortawesome/fontawesome-free": "^6.5.1",
+ "axios": "^1.6.2",
+ "chart.js": "^4.4.0",
+ "dayjs": "^1.11.9",
+ "element-plus": "^2.4.4",
+ "pinia": "^2.1.7",
+ "vue": "^3.3.4",
+ "vue-router": "^4.2.5"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^4.5.2",
+ "autoprefixer": "^10.4.16",
+ "eslint": "^8.55.0",
+ "eslint-plugin-vue": "^9.19.2",
+ "postcss": "^8.4.32",
+ "prettier": "^3.1.1",
+ "tailwindcss": "^3.3.6",
+ "unplugin-auto-import": "^0.17.2",
+ "unplugin-element-plus": "^0.8.0",
+ "unplugin-vue-components": "^0.26.0",
+ "vite": "^5.0.8"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@antfu/utils": {
+ "version": "0.7.10",
+ "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz",
+ "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
+ "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.0.tgz",
+ "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.2",
+ "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.2.tgz",
+ "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@ctrl/tinycolor": {
+ "version": "3.6.1",
+ "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz",
+ "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@element-plus/icons-vue": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz",
+ "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz",
+ "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.1",
+ "resolved": "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
+ "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmmirror.com/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.2.tgz",
+ "integrity": "sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.2.tgz",
+ "integrity": "sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.2",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT"
+ },
+ "node_modules/@fortawesome/fontawesome-free": {
+ "version": "6.7.2",
+ "resolved": "https://registry.npmmirror.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz",
+ "integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==",
+ "license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.12",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz",
+ "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz",
+ "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.29",
+ "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz",
+ "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmmirror.com/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+ "license": "MIT"
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@popperjs/core": {
+ "name": "@sxzz/popperjs-es",
+ "version": "2.11.7",
+ "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz",
+ "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.2.0.tgz",
+ "integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^4.0.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.1.tgz",
+ "integrity": "sha512-oENme6QxtLCqjChRUUo3S6X8hjCXnWmJWnedD7VbGML5GUtaOtAyx+fEEXnBXVf0CBZApMQU0Idwi0FmyxzQhw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.1.tgz",
+ "integrity": "sha512-OikvNT3qYTl9+4qQ9Bpn6+XHM+ogtFadRLuT2EXiFQMiNkXFLQfNVppi5o28wvYdHL2s3fM0D/MZJ8UkNFZWsw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.1.tgz",
+ "integrity": "sha512-EFYNNGij2WllnzljQDQnlFTXzSJw87cpAs4TVBAWLdkvic5Uh5tISrIL6NRcxoh/b2EFBG/TK8hgRrGx94zD4A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.1.tgz",
+ "integrity": "sha512-ZaNH06O1KeTug9WI2+GRBE5Ujt9kZw4a1+OIwnBHal92I8PxSsl5KpsrPvthRynkhMck4XPdvY0z26Cym/b7oA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.1.tgz",
+ "integrity": "sha512-n4SLVebZP8uUlJ2r04+g2U/xFeiQlw09Me5UFqny8HGbARl503LNH5CqFTb5U5jNxTouhRjai6qPT0CR5c/Iig==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.1.tgz",
+ "integrity": "sha512-8vu9c02F16heTqpvo3yeiu7Vi1REDEC/yES/dIfq3tSXe6mLndiwvYr3AAvd1tMNUqE9yeGYa5w7PRbI5QUV+w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.1.tgz",
+ "integrity": "sha512-K4ncpWl7sQuyp6rWiGUvb6Q18ba8mzM0rjWJ5JgYKlIXAau1db7hZnR0ldJvqKWWJDxqzSLwGUhA4jp+KqgDtQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.1.tgz",
+ "integrity": "sha512-YykPnXsjUjmXE6j6k2QBBGAn1YsJUix7pYaPLK3RVE0bQL2jfdbfykPxfF8AgBlqtYbfEnYHmLXNa6QETjdOjQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.1.tgz",
+ "integrity": "sha512-kKvqBGbZ8i9pCGW3a1FH3HNIVg49dXXTsChGFsHGXQaVJPLA4f/O+XmTxfklhccxdF5FefUn2hvkoGJH0ScWOA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.1.tgz",
+ "integrity": "sha512-zzX5nTw1N1plmqC9RGC9vZHFuiM7ZP7oSWQGqpbmfjK7p947D518cVK1/MQudsBdcD84t6k70WNczJOct6+hdg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.1.tgz",
+ "integrity": "sha512-O8CwgSBo6ewPpktFfSDgB6SJN9XDcPSvuwxfejiddbIC/hn9Tg6Ai0f0eYDf3XvB/+PIWzOQL+7+TZoB8p9Yuw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.1.tgz",
+ "integrity": "sha512-JnCfFVEKeq6G3h3z8e60kAp8Rd7QVnWCtPm7cxx+5OtP80g/3nmPtfdCXbVl063e3KsRnGSKDHUQMydmzc/wBA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.1.tgz",
+ "integrity": "sha512-dVxuDqS237eQXkbYzQQfdf/njgeNw6LZuVyEdUaWwRpKHhsLI+y4H/NJV8xJGU19vnOJCVwaBFgr936FHOnJsQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.1.tgz",
+ "integrity": "sha512-CvvgNl2hrZrTR9jXK1ye0Go0HQRT6ohQdDfWR47/KFKiLd5oN5T14jRdUVGF4tnsN8y9oSfMOqH6RuHh+ck8+w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.1.tgz",
+ "integrity": "sha512-x7ANt2VOg2565oGHJ6rIuuAon+A8sfe1IeUx25IKqi49OjSr/K3awoNqr9gCwGEJo9OuXlOn+H2p1VJKx1psxA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.1.tgz",
+ "integrity": "sha512-9OADZYryz/7E8/qt0vnaHQgmia2Y0wrjSSn1V/uL+zw/i7NUhxbX4cHXdEQ7dnJgzYDS81d8+tf6nbIdRFZQoQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.1.tgz",
+ "integrity": "sha512-NuvSCbXEKY+NGWHyivzbjSVJi68Xfq1VnIvGmsuXs6TCtveeoDRKutI5vf2ntmNnVq64Q4zInet0UDQ+yMB6tA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.1.tgz",
+ "integrity": "sha512-mWz+6FSRb82xuUMMV1X3NGiaPFqbLN9aIueHleTZCc46cJvwTlvIh7reQLk4p97dv0nddyewBhwzryBHH7wtPw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.1.tgz",
+ "integrity": "sha512-7Thzy9TMXDw9AU4f4vsLNBxh7/VOKuXi73VH3d/kHGr0tZ3x/ewgL9uC7ojUKmH1/zvmZe2tLapYcZllk3SO8Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash": {
+ "version": "4.17.20",
+ "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash-es": {
+ "version": "4.17.12",
+ "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+ "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.16",
+ "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz",
+ "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==",
+ "license": "MIT"
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz",
+ "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.0.0 || ^5.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.18.tgz",
+ "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.0",
+ "@vue/shared": "3.5.18",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz",
+ "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.18",
+ "@vue/shared": "3.5.18"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz",
+ "integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.0",
+ "@vue/compiler-core": "3.5.18",
+ "@vue/compiler-dom": "3.5.18",
+ "@vue/compiler-ssr": "3.5.18",
+ "@vue/shared": "3.5.18",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.17",
+ "postcss": "^8.5.6",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz",
+ "integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.18",
+ "@vue/shared": "3.5.18"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
+ "license": "MIT"
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.18.tgz",
+ "integrity": "sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.18"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.18.tgz",
+ "integrity": "sha512-DUpHa1HpeOQEt6+3nheUfqVXRog2kivkXHUhoqJiKR33SO4x+a5uNOMkV487WPerQkL0vUuRvq/7JhRgLW3S+w==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.18",
+ "@vue/shared": "3.5.18"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.18.tgz",
+ "integrity": "sha512-YwDj71iV05j4RnzZnZtGaXwPoUWeRsqinblgVJwR8XTXYZ9D5PbahHQgsbmzUvCWNF6x7siQ89HgnX5eWkr3mw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.18",
+ "@vue/runtime-core": "3.5.18",
+ "@vue/shared": "3.5.18",
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.18.tgz",
+ "integrity": "sha512-PvIHLUoWgSbDG7zLHqSqaCoZvHi6NNmfVFOqO+OnwvqMz/tqQr3FuGWS8ufluNddk7ZLBJYMrjcw1c6XzR12mA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.18",
+ "@vue/shared": "3.5.18"
+ },
+ "peerDependencies": {
+ "vue": "3.5.18"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.18.tgz",
+ "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==",
+ "license": "MIT"
+ },
+ "node_modules/@vueuse/core": {
+ "version": "9.13.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz",
+ "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.16",
+ "@vueuse/metadata": "9.13.0",
+ "@vueuse/shared": "9.13.0",
+ "vue-demi": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "9.13.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz",
+ "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "9.13.0",
+ "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz",
+ "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==",
+ "license": "MIT",
+ "dependencies": {
+ "vue-demi": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/async-validator": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz",
+ "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
+ "license": "MIT"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.21",
+ "resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.21.tgz",
+ "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.24.4",
+ "caniuse-lite": "^1.0.30001702",
+ "fraction.js": "^4.3.7",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmmirror.com/axios/-/axios-1.11.0.tgz",
+ "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.25.1",
+ "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.25.1.tgz",
+ "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001726",
+ "electron-to-chromium": "^1.5.173",
+ "node-releases": "^2.0.19",
+ "update-browserslist-db": "^1.1.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001727",
+ "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz",
+ "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chart.js": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmmirror.com/chart.js/-/chart.js-4.5.0.tgz",
+ "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/confbox": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz",
+ "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.13",
+ "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz",
+ "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz",
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.191",
+ "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz",
+ "integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/element-plus": {
+ "version": "2.10.4",
+ "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.10.4.tgz",
+ "integrity": "sha512-UD4elWHrCnp1xlPhbXmVcaKFLCRaRAY6WWRwemGfGW3ceIjXm9fSYc9RNH3AiOEA6Ds1p9ZvhCs76CR9J8Vd+A==",
+ "license": "MIT",
+ "dependencies": {
+ "@ctrl/tinycolor": "^3.4.1",
+ "@element-plus/icons-vue": "^2.3.1",
+ "@floating-ui/dom": "^1.0.1",
+ "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7",
+ "@types/lodash": "^4.14.182",
+ "@types/lodash-es": "^4.17.6",
+ "@vueuse/core": "^9.1.0",
+ "async-validator": "^4.2.5",
+ "dayjs": "^1.11.13",
+ "escape-html": "^1.0.3",
+ "lodash": "^4.17.21",
+ "lodash-es": "^4.17.21",
+ "lodash-unified": "^1.0.2",
+ "memoize-one": "^6.0.0",
+ "normalize-wheel-es": "^1.2.0"
+ },
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/esbuild/node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmmirror.com/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-plugin-vue": {
+ "version": "9.33.0",
+ "resolved": "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz",
+ "integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "globals": "^13.24.0",
+ "natural-compare": "^1.4.0",
+ "nth-check": "^2.1.1",
+ "postcss-selector-parser": "^6.0.15",
+ "semver": "^7.6.3",
+ "vue-eslint-parser": "^9.4.3",
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmmirror.com/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT"
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/exsolve": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.7.tgz",
+ "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz",
+ "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz",
+ "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmmirror.com/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz",
+ "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/local-pkg": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.1.tgz",
+ "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.7.3",
+ "pkg-types": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash-unified": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz",
+ "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/lodash-es": "*",
+ "lodash": "*",
+ "lodash-es": "*"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.17",
+ "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz",
+ "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/memoize-one": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz",
+ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/mlly": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.7.4.tgz",
+ "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.14.0",
+ "pathe": "^2.0.1",
+ "pkg-types": "^1.3.0",
+ "ufo": "^1.5.4"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.19",
+ "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz",
+ "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-wheel-es": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
+ "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmmirror.com/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pinia": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.3.1.tgz",
+ "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.3",
+ "vue-demi": "^0.14.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.4.4",
+ "vue": "^2.7.0 || ^3.5.11"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz",
+ "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
+ "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.0.0",
+ "yaml": "^2.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ },
+ "peerDependencies": {
+ "postcss": ">=8.0.9",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "postcss": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmmirror.com/prettier/-/prettier-3.6.2.tgz",
+ "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/quansync": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.10.tgz",
+ "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/antfu"
+ },
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/sxzz"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.10",
+ "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz",
+ "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.46.1.tgz",
+ "integrity": "sha512-33xGNBsDJAkzt0PvninskHlWnTIPgDtTwhg0U38CUoNP/7H6wI2Cz6dUeoNPbjdTdsYTGuiFFASuUOWovH0SyQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.46.1",
+ "@rollup/rollup-android-arm64": "4.46.1",
+ "@rollup/rollup-darwin-arm64": "4.46.1",
+ "@rollup/rollup-darwin-x64": "4.46.1",
+ "@rollup/rollup-freebsd-arm64": "4.46.1",
+ "@rollup/rollup-freebsd-x64": "4.46.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.46.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.46.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.46.1",
+ "@rollup/rollup-linux-arm64-musl": "4.46.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.46.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.46.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.46.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.46.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.46.1",
+ "@rollup/rollup-linux-x64-gnu": "4.46.1",
+ "@rollup/rollup-linux-x64-musl": "4.46.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.46.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.46.1",
+ "@rollup/rollup-win32-x64-msvc": "4.46.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.46.1",
+ "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.1.tgz",
+ "integrity": "sha512-7GVB4luhFmGUNXXJhH2jJwZCFB3pIOixv2E3s17GQHBFUOQaISlt7aGcQgqvCaDSxTZJUzlK/QJ1FN8S94MrzQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/scule": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
+ "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "7.7.2",
+ "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz",
+ "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-literal": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-2.1.1.tgz",
+ "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/sucrase/node_modules/glob": {
+ "version": "10.4.5",
+ "resolved": "https://registry.npmmirror.com/glob/-/glob-10.4.5.tgz",
+ "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/sucrase/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.17",
+ "resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.17.tgz",
+ "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.6",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz",
+ "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unimport": {
+ "version": "3.14.6",
+ "resolved": "https://registry.npmmirror.com/unimport/-/unimport-3.14.6.tgz",
+ "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.1.4",
+ "acorn": "^8.14.0",
+ "escape-string-regexp": "^5.0.0",
+ "estree-walker": "^3.0.3",
+ "fast-glob": "^3.3.3",
+ "local-pkg": "^1.0.0",
+ "magic-string": "^0.30.17",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1",
+ "picomatch": "^4.0.2",
+ "pkg-types": "^1.3.0",
+ "scule": "^1.3.0",
+ "strip-literal": "^2.1.1",
+ "unplugin": "^1.16.1"
+ }
+ },
+ "node_modules/unimport/node_modules/confbox": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz",
+ "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unimport/node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/unimport/node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/unimport/node_modules/local-pkg": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.1.tgz",
+ "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.7.4",
+ "pkg-types": "^2.0.1",
+ "quansync": "^0.2.8"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.2.0.tgz",
+ "integrity": "sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.2",
+ "exsolve": "^1.0.7",
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/unimport/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/unplugin": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.16.1.tgz",
+ "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.14.0",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/unplugin-auto-import": {
+ "version": "0.17.8",
+ "resolved": "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.17.8.tgz",
+ "integrity": "sha512-CHryj6HzJ+n4ASjzwHruD8arhbdl+UXvhuAIlHDs15Y/IMecG3wrf7FVg4pVH/DIysbq/n0phIjNHAjl7TG7Iw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@antfu/utils": "^0.7.10",
+ "@rollup/pluginutils": "^5.1.0",
+ "fast-glob": "^3.3.2",
+ "local-pkg": "^0.5.0",
+ "magic-string": "^0.30.10",
+ "minimatch": "^9.0.4",
+ "unimport": "^3.7.2",
+ "unplugin": "^1.11.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@nuxt/kit": "^3.2.2",
+ "@vueuse/core": "*"
+ },
+ "peerDependenciesMeta": {
+ "@nuxt/kit": {
+ "optional": true
+ },
+ "@vueuse/core": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/unplugin-auto-import/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/unplugin-auto-import/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/unplugin-element-plus": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmmirror.com/unplugin-element-plus/-/unplugin-element-plus-0.8.0.tgz",
+ "integrity": "sha512-jByUGY3FG2B8RJKFryqxx4eNtSTj+Hjlo8edcOdJymewndDQjThZ1pRUQHRjQsbKhTV2jEctJV7t7RJ405UL4g==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.2",
+ "es-module-lexer": "^1.3.0",
+ "magic-string": "^0.30.1",
+ "unplugin": "^1.3.2"
+ },
+ "engines": {
+ "node": ">=14.19.0"
+ }
+ },
+ "node_modules/unplugin-vue-components": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.26.0.tgz",
+ "integrity": "sha512-s7IdPDlnOvPamjunVxw8kNgKNK8A5KM1YpK5j/p97jEKTjlPNrA0nZBiSfAKKlK1gWZuyWXlKL5dk3EDw874LQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@antfu/utils": "^0.7.6",
+ "@rollup/pluginutils": "^5.0.4",
+ "chokidar": "^3.5.3",
+ "debug": "^4.3.4",
+ "fast-glob": "^3.3.1",
+ "local-pkg": "^0.4.3",
+ "magic-string": "^0.30.3",
+ "minimatch": "^9.0.3",
+ "resolve": "^1.22.4",
+ "unplugin": "^1.4.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@babel/parser": "^7.15.8",
+ "@nuxt/kit": "^3.2.2",
+ "vue": "2 || 3"
+ },
+ "peerDependenciesMeta": {
+ "@babel/parser": {
+ "optional": true
+ },
+ "@nuxt/kit": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/unplugin-vue-components/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/unplugin-vue-components/node_modules/local-pkg": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz",
+ "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/unplugin-vue-components/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+ "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "5.4.19",
+ "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.19.tgz",
+ "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.5.18",
+ "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.18.tgz",
+ "integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.18",
+ "@vue/compiler-sfc": "3.5.18",
+ "@vue/runtime-dom": "3.5.18",
+ "@vue/server-renderer": "3.5.18",
+ "@vue/shared": "3.5.18"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-eslint-parser": {
+ "version": "9.4.3",
+ "resolved": "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
+ "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "eslint-scope": "^7.1.1",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.3.1",
+ "esquery": "^1.4.0",
+ "lodash": "^4.17.21",
+ "semver": "^7.3.6"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=6.0.0"
+ }
+ },
+ "node_modules/vue-router": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz",
+ "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/webpack-virtual-modules": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.1.0.tgz",
+ "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz",
+ "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz",
+ "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yaml": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.0.tgz",
+ "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/web/admin-spa/package.json b/web/admin-spa/package.json
new file mode 100644
index 00000000..a7429805
--- /dev/null
+++ b/web/admin-spa/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "claude-relay-admin-spa",
+ "version": "1.0.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "lint": "eslint src --ext .js,.vue",
+ "format": "prettier --write \"src/**/*.{js,vue,css}\""
+ },
+ "dependencies": {
+ "@fortawesome/fontawesome-free": "^6.5.1",
+ "axios": "^1.6.2",
+ "chart.js": "^4.4.0",
+ "dayjs": "^1.11.9",
+ "element-plus": "^2.4.4",
+ "pinia": "^2.1.7",
+ "vue": "^3.3.4",
+ "vue-router": "^4.2.5"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-vue": "^4.5.2",
+ "autoprefixer": "^10.4.16",
+ "eslint": "^8.55.0",
+ "eslint-plugin-vue": "^9.19.2",
+ "postcss": "^8.4.32",
+ "prettier": "^3.1.1",
+ "tailwindcss": "^3.3.6",
+ "unplugin-auto-import": "^0.17.2",
+ "unplugin-element-plus": "^0.8.0",
+ "unplugin-vue-components": "^0.26.0",
+ "vite": "^5.0.8"
+ }
+}
diff --git a/web/admin-spa/postcss.config.js b/web/admin-spa/postcss.config.js
new file mode 100644
index 00000000..e99ebc2c
--- /dev/null
+++ b/web/admin-spa/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
\ No newline at end of file
diff --git a/web/admin-spa/src/App.vue b/web/admin-spa/src/App.vue
new file mode 100644
index 00000000..07432554
--- /dev/null
+++ b/web/admin-spa/src/App.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/assets/styles/components.css b/web/admin-spa/src/assets/styles/components.css
new file mode 100644
index 00000000..a2c83973
--- /dev/null
+++ b/web/admin-spa/src/assets/styles/components.css
@@ -0,0 +1,352 @@
+/* Glass效果 */
+.glass {
+ background: var(--glass-color);
+ backdrop-filter: blur(20px);
+ border: 1px solid var(--border-color);
+ box-shadow:
+ 0 20px 25px -5px rgba(0, 0, 0, 0.1),
+ 0 10px 10px -5px rgba(0, 0, 0, 0.04),
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
+}
+
+.glass-strong {
+ background: var(--surface-color);
+ backdrop-filter: blur(25px);
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ box-shadow:
+ 0 25px 50px -12px rgba(0, 0, 0, 0.25),
+ 0 0 0 1px rgba(255, 255, 255, 0.05),
+ inset 0 1px 0 rgba(255, 255, 255, 0.1);
+}
+
+/* 标签按钮 */
+.tab-btn {
+ position: relative;
+ overflow: hidden;
+ border-radius: 12px;
+ font-weight: 500;
+ letter-spacing: 0.025em;
+}
+
+.tab-btn::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
+ transition: left 0.5s;
+}
+
+.tab-btn:hover::before {
+ left: 100%;
+}
+
+.tab-btn.active {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
+ color: white;
+ box-shadow:
+ 0 10px 15px -3px rgba(102, 126, 234, 0.3),
+ 0 4px 6px -2px rgba(102, 126, 234, 0.05);
+ transform: translateY(-1px);
+}
+
+/* 卡片 */
+.card {
+ background: var(--surface-color);
+ border-radius: 16px;
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ box-shadow:
+ 0 10px 15px -3px rgba(0, 0, 0, 0.1),
+ 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+ overflow: hidden;
+ position: relative;
+}
+
+.card::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 1px;
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5), transparent);
+}
+
+/* 统计卡片 */
+.stat-card {
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.8) 100%);
+ border-radius: 20px;
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ padding: 24px;
+ position: relative;
+ overflow: hidden;
+ transition: all 0.3s ease;
+}
+
+.stat-card::before {
+ content: '';
+ position: absolute;
+ top: -50%;
+ left: -50%;
+ width: 200%;
+ height: 200%;
+ background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
+ opacity: 0;
+ transition: opacity 0.3s ease;
+}
+
+.stat-card:hover {
+ transform: translateY(-4px);
+ box-shadow:
+ 0 20px 25px -5px rgba(0, 0, 0, 0.1),
+ 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+}
+
+.stat-card:hover::before {
+ opacity: 1;
+}
+
+.stat-icon {
+ width: 56px;
+ height: 56px;
+ border-radius: 16px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 24px;
+ color: white;
+ box-shadow:
+ 0 10px 15px -3px rgba(0, 0, 0, 0.1),
+ 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+}
+
+/* 按钮 */
+.btn {
+ font-weight: 500;
+ border-radius: 12px;
+ border: none;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+ letter-spacing: 0.025em;
+}
+
+.btn::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 50%;
+ transform: translate(-50%, -50%);
+ transition: width 0.3s ease, height 0.3s ease;
+}
+
+.btn:active::before {
+ width: 300px;
+ height: 300px;
+}
+
+.btn-primary {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
+ color: white;
+ box-shadow:
+ 0 10px 15px -3px rgba(102, 126, 234, 0.3),
+ 0 4px 6px -2px rgba(102, 126, 234, 0.05);
+}
+
+.btn-primary:hover {
+ transform: translateY(-1px);
+ box-shadow:
+ 0 20px 25px -5px rgba(102, 126, 234, 0.3),
+ 0 10px 10px -5px rgba(102, 126, 234, 0.1);
+}
+
+.btn-success {
+ background: linear-gradient(135deg, var(--success-color) 0%, #059669 100%);
+ color: white;
+ box-shadow:
+ 0 10px 15px -3px rgba(16, 185, 129, 0.3),
+ 0 4px 6px -2px rgba(16, 185, 129, 0.05);
+}
+
+.btn-success:hover {
+ transform: translateY(-1px);
+ box-shadow:
+ 0 20px 25px -5px rgba(16, 185, 129, 0.3),
+ 0 10px 10px -5px rgba(16, 185, 129, 0.1);
+}
+
+.btn-danger {
+ background: linear-gradient(135deg, var(--error-color) 0%, #dc2626 100%);
+ color: white;
+ box-shadow:
+ 0 10px 15px -3px rgba(239, 68, 68, 0.3),
+ 0 4px 6px -2px rgba(239, 68, 68, 0.05);
+}
+
+.btn-danger:hover {
+ transform: translateY(-1px);
+ box-shadow:
+ 0 20px 25px -5px rgba(239, 68, 68, 0.3),
+ 0 10px 10px -5px rgba(239, 68, 68, 0.1);
+}
+
+/* 表单输入 */
+.form-input {
+ background: rgba(255, 255, 255, 0.9);
+ border: 2px solid rgba(255, 255, 255, 0.3);
+ border-radius: 12px;
+ padding: 8px 12px;
+ font-size: 14px;
+ transition: all 0.3s ease;
+ backdrop-filter: blur(10px);
+}
+
+.form-input:focus {
+ outline: none;
+ border-color: var(--primary-color);
+ box-shadow:
+ 0 0 0 3px rgba(102, 126, 234, 0.1),
+ 0 10px 15px -3px rgba(0, 0, 0, 0.1);
+ background: rgba(255, 255, 255, 0.95);
+}
+
+/* 表格容器 */
+.table-container {
+ background: rgba(255, 255, 255, 0.95);
+ border-radius: 16px;
+ overflow: hidden;
+ box-shadow:
+ 0 10px 15px -3px rgba(0, 0, 0, 0.1),
+ 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+}
+
+.table-row {
+ transition: all 0.2s ease;
+}
+
+.table-row:hover {
+ background: rgba(102, 126, 234, 0.05);
+ transform: scale(1.005);
+}
+
+/* 模态框 */
+.modal {
+ backdrop-filter: blur(8px);
+ background: rgba(0, 0, 0, 0.4);
+}
+
+.modal-content {
+ background: rgba(255, 255, 255, 0.95);
+ border-radius: 24px;
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ box-shadow:
+ 0 25px 50px -12px rgba(0, 0, 0, 0.25),
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
+ backdrop-filter: blur(20px);
+}
+
+/* 弹窗滚动内容样式 */
+.modal-scroll-content {
+ max-height: calc(90vh - 160px);
+ overflow-y: auto;
+ padding-right: 8px;
+}
+
+/* 标题渐变 */
+.header-title {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+ font-weight: 700;
+ letter-spacing: -0.025em;
+}
+
+/* 加载动画 */
+.loading-spinner {
+ display: inline-block;
+ width: 20px;
+ height: 20px;
+ border: 2px solid rgba(255, 255, 255, 0.3);
+ border-radius: 50%;
+ border-top: 2px solid white;
+ animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+/* Toast通知 */
+.toast {
+ position: fixed;
+ top: 80px;
+ right: 20px;
+ z-index: 1000;
+ min-width: 320px;
+ max-width: 500px;
+ transform: translateX(100%);
+ transition: transform 0.3s ease-in-out;
+}
+
+.toast.show {
+ transform: translateX(0);
+}
+
+.toast-success {
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+ color: white;
+ border: 1px solid rgba(16, 185, 129, 0.3);
+}
+
+.toast-error {
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
+ color: white;
+ border: 1px solid rgba(239, 68, 68, 0.3);
+}
+
+.toast-info {
+ background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
+ color: white;
+ border: 1px solid rgba(59, 130, 246, 0.3);
+}
+
+.toast-warning {
+ background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
+ color: white;
+ border: 1px solid rgba(245, 158, 11, 0.3);
+}
+
+/* 版本更新提醒动画 */
+@keyframes pulse {
+ 0% {
+ transform: scale(1);
+ opacity: 1;
+ }
+ 50% {
+ transform: scale(1.1);
+ opacity: 0.8;
+ }
+ 100% {
+ transform: scale(1);
+ opacity: 1;
+ }
+}
+
+.animate-pulse {
+ animation: pulse 2s infinite;
+}
+
+/* 用户菜单下拉框优化 */
+.user-menu-dropdown {
+ min-width: 240px;
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+}
\ No newline at end of file
diff --git a/web/admin/style.css b/web/admin-spa/src/assets/styles/global.css
similarity index 80%
rename from web/admin/style.css
rename to web/admin-spa/src/assets/styles/global.css
index 809e9593..2c7f55c7 100644
--- a/web/admin/style.css
+++ b/web/admin-spa/src/assets/styles/global.css
@@ -1,3 +1,4 @@
+/* 从原始 style.css 复制的全局样式 */
:root {
--primary-color: #667eea;
--secondary-color: #764ba2;
@@ -93,6 +94,101 @@ body::before {
transform: translateY(-1px);
}
+/* 按钮样式 */
+.btn {
+ font-weight: 500;
+ border-radius: 12px;
+ border: none;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ position: relative;
+ overflow: hidden;
+ letter-spacing: 0.025em;
+}
+
+.btn::before {
+ content: '';
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 0;
+ height: 0;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 50%;
+ transform: translate(-50%, -50%);
+ transition: width 0.3s ease, height 0.3s ease;
+}
+
+.btn:active::before {
+ width: 300px;
+ height: 300px;
+}
+
+.btn-primary {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
+ color: white;
+ box-shadow:
+ 0 10px 15px -3px rgba(102, 126, 234, 0.3),
+ 0 4px 6px -2px rgba(102, 126, 234, 0.05);
+}
+
+.btn-primary:hover {
+ transform: translateY(-1px);
+ box-shadow:
+ 0 20px 25px -5px rgba(102, 126, 234, 0.3),
+ 0 10px 10px -5px rgba(102, 126, 234, 0.1);
+}
+
+.btn-success {
+ background: linear-gradient(135deg, var(--success-color) 0%, #059669 100%);
+ color: white;
+ box-shadow:
+ 0 10px 15px -3px rgba(16, 185, 129, 0.3),
+ 0 4px 6px -2px rgba(16, 185, 129, 0.05);
+}
+
+.btn-success:hover {
+ transform: translateY(-1px);
+ box-shadow:
+ 0 20px 25px -5px rgba(16, 185, 129, 0.3),
+ 0 10px 10px -5px rgba(16, 185, 129, 0.1);
+}
+
+.btn-danger {
+ background: linear-gradient(135deg, var(--error-color) 0%, #dc2626 100%);
+ color: white;
+ box-shadow:
+ 0 10px 15px -3px rgba(239, 68, 68, 0.3),
+ 0 4px 6px -2px rgba(239, 68, 68, 0.05);
+}
+
+.btn-danger:hover {
+ transform: translateY(-1px);
+ box-shadow:
+ 0 20px 25px -5px rgba(239, 68, 68, 0.3),
+ 0 10px 10px -5px rgba(239, 68, 68, 0.1);
+}
+
+/* 表单输入框样式 */
+.form-input {
+ background: rgba(255, 255, 255, 0.9);
+ border: 2px solid rgba(255, 255, 255, 0.3);
+ border-radius: 12px;
+ padding: 16px;
+ font-size: 16px;
+ transition: all 0.3s ease;
+ backdrop-filter: blur(10px);
+}
+
+.form-input:focus {
+ outline: none;
+ border-color: var(--primary-color);
+ box-shadow:
+ 0 0 0 3px rgba(102, 126, 234, 0.1),
+ 0 10px 15px -3px rgba(0, 0, 0, 0.1);
+ background: rgba(255, 255, 255, 0.95);
+}
+
.card {
background: var(--surface-color);
border-radius: 16px;
@@ -376,8 +472,6 @@ body::before {
display: none;
}
-
-
/* 自定义滚动条样式 */
.custom-scrollbar {
scrollbar-width: thin;
@@ -459,4 +553,20 @@ body::before {
.user-menu-dropdown {
min-width: 240px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+}
+
+/* Tab 内容区域样式 */
+.tab-content {
+ animation: fadeIn 0.3s ease-in-out;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
\ No newline at end of file
diff --git a/web/admin-spa/src/assets/styles/main.css b/web/admin-spa/src/assets/styles/main.css
new file mode 100644
index 00000000..b2c5f0eb
--- /dev/null
+++ b/web/admin-spa/src/assets/styles/main.css
@@ -0,0 +1,124 @@
+@import 'tailwindcss/base';
+@import 'tailwindcss/components';
+@import 'tailwindcss/utilities';
+@import './variables.css';
+@import './components.css';
+
+/* Font Awesome 图标 */
+@import '@fortawesome/fontawesome-free/css/all.css';
+
+/* 全局样式 */
+body {
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 50%, var(--accent-color) 100%);
+ background-attachment: fixed;
+ min-height: 100vh;
+ margin: 0;
+ overflow-x: hidden;
+}
+
+body::before {
+ content: '';
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background:
+ radial-gradient(circle at 20% 80%, rgba(240, 147, 251, 0.2) 0%, transparent 50%),
+ radial-gradient(circle at 80% 20%, rgba(102, 126, 234, 0.2) 0%, transparent 50%),
+ radial-gradient(circle at 40% 40%, rgba(118, 75, 162, 0.1) 0%, transparent 50%);
+ pointer-events: none;
+ z-index: -1;
+}
+
+/* 通用transition - 仅应用于特定元素 */
+body, div, button, input, select, textarea, table, tr, td, th, span, p, h1, h2, h3, h4, h5, h6 {
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+/* Element Plus 主题覆盖 */
+.el-button--primary {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
+ border-color: transparent;
+}
+
+.el-button--primary:hover,
+.el-button--primary:focus {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
+ opacity: 0.9;
+}
+
+/* 自定义滚动条样式 */
+.custom-scrollbar {
+ scrollbar-width: thin;
+ scrollbar-color: rgba(102, 126, 234, 0.3) rgba(102, 126, 234, 0.05);
+}
+
+.custom-scrollbar::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-track {
+ background: rgba(102, 126, 234, 0.05);
+ border-radius: 10px;
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb {
+ background: linear-gradient(135deg, rgba(102, 126, 234, 0.4) 0%, rgba(118, 75, 162, 0.4) 100%);
+ border-radius: 10px;
+ transition: background 0.3s ease;
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb:hover {
+ background: linear-gradient(135deg, rgba(102, 126, 234, 0.6) 0%, rgba(118, 75, 162, 0.6) 100%);
+}
+
+.custom-scrollbar::-webkit-scrollbar-thumb:active {
+ background: linear-gradient(135deg, rgba(102, 126, 234, 0.8) 0%, rgba(118, 75, 162, 0.8) 100%);
+}
+
+/* Vue过渡动画 */
+.fade-enter-active, .fade-leave-active {
+ transition: opacity 0.3s ease;
+}
+
+.fade-enter-from, .fade-leave-to {
+ opacity: 0;
+}
+
+.slide-up-enter-active, .slide-up-leave-active {
+ transition: all 0.3s ease;
+}
+
+.slide-up-enter-from {
+ opacity: 0;
+ transform: translateY(30px);
+}
+
+.slide-up-leave-to {
+ opacity: 0;
+ transform: translateY(-30px);
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+ .glass, .glass-strong {
+ margin: 16px;
+ border-radius: 20px;
+ }
+
+ .stat-card {
+ padding: 16px;
+ }
+
+ .tab-btn {
+ font-size: 14px;
+ padding: 12px 8px;
+ }
+
+ .modal-scroll-content {
+ max-height: calc(85vh - 120px);
+ }
+}
\ No newline at end of file
diff --git a/web/admin-spa/src/assets/styles/variables.css b/web/admin-spa/src/assets/styles/variables.css
new file mode 100644
index 00000000..cac6b842
--- /dev/null
+++ b/web/admin-spa/src/assets/styles/variables.css
@@ -0,0 +1,13 @@
+:root {
+ --primary-color: #667eea;
+ --secondary-color: #764ba2;
+ --accent-color: #f093fb;
+ --success-color: #10b981;
+ --warning-color: #f59e0b;
+ --error-color: #ef4444;
+ --surface-color: rgba(255, 255, 255, 0.95);
+ --glass-color: rgba(255, 255, 255, 0.1);
+ --text-primary: #1f2937;
+ --text-secondary: #6b7280;
+ --border-color: rgba(255, 255, 255, 0.2);
+}
\ No newline at end of file
diff --git a/web/admin-spa/src/components/accounts/AccountForm.vue b/web/admin-spa/src/components/accounts/AccountForm.vue
new file mode 100644
index 00000000..dbaec8f9
--- /dev/null
+++ b/web/admin-spa/src/components/accounts/AccountForm.vue
@@ -0,0 +1,646 @@
+
+
+
+
+
+
+
+
+
+
{{ isEdit ? '编辑账户' : '添加账户' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 账户名称
+
+
+
+
+ 描述 (可选)
+
+
+
+
+
账户类型
+
+
+
+ 共享账户
+
+
+
+ 专属账户
+
+
+
+ 共享账户:供所有API Key使用;专属账户:仅供特定API Key使用
+
+
+
+
+
+
项目编号 (可选)
+
+
+
+
+
+
Google Cloud/Workspace 账号需要提供项目编号
+
某些 Google 账号(特别是绑定了 Google Cloud 的账号)会被识别为 Workspace 账号,需要提供额外的项目编号。
+
+
如何获取项目编号:
+
+ 访问 Google Cloud Console
+ 复制项目编号(Project Number) ,通常是12位纯数字
+ ⚠️ 注意:不要复制项目ID(Project ID),要复制项目编号!
+
+
+
提示: 如果您的账号是普通个人账号(未绑定 Google Cloud),请留空此字段。
+
+
+
+
+
+
+
+
+
+
+
+
+
手动输入 Token
+
+ 请输入有效的 Claude Access Token。如果您有 Refresh Token,建议也一并填写以支持自动刷新。
+
+
+ 请输入有效的 Gemini Access Token。如果您有 Refresh Token,建议也一并填写以支持自动刷新。
+
+
+
+
+ 获取 Access Token 的方法:
+
+
+ 请从已登录 Claude Code 的机器上获取 ~/.claude/.credentials.json 文件中的凭证,
+ 请勿使用 Claude 官网 API Keys 页面的密钥。
+
+
+ 请从已登录 Gemini CLI 的机器上获取 ~/.config/gemini/credentials.json 文件中的凭证。
+
+
+
💡 如果未填写 Refresh Token,Token 过期后需要手动更新。
+
+
+
+
+ Access Token *
+
+
+
+
+ Refresh Token (可选)
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+ 下一步
+
+
+
+ {{ loading ? '创建中...' : '创建' }}
+
+
+
+
+
+
+
+
+
+
+
+
+ 账户名称
+
+
+
+
+ 描述 (可选)
+
+
+
+
+
账户类型
+
+
+
+ 共享账户
+
+
+
+ 专属账户
+
+
+
+ 共享账户:供所有API Key使用;专属账户:仅供特定API Key使用
+
+
+
+
+
+
项目编号 (可选)
+
+
+ Google Cloud/Workspace 账号可能需要提供项目编号
+
+
+
+
+
+
+
+
+
+
+
更新 Token
+
可以更新 Access Token 和 Refresh Token。为了安全起见,不会显示当前的 Token 值。
+
💡 留空表示不更新该字段。
+
+
+
+
+
+ 新的 Access Token
+
+
+
+
+ 新的 Refresh Token
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+
+ {{ loading ? '更新中...' : '更新' }}
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/accounts/OAuthFlow.vue b/web/admin-spa/src/components/accounts/OAuthFlow.vue
new file mode 100644
index 00000000..30384d25
--- /dev/null
+++ b/web/admin-spa/src/components/accounts/OAuthFlow.vue
@@ -0,0 +1,377 @@
+
+
+
+
+
+
+
+
+
+
+
Claude 账户授权
+
+ 请按照以下步骤完成 Claude 账户的授权:
+
+
+
+
+
+
+
1
+
+
点击下方按钮生成授权链接
+
+
+
+ {{ loading ? '生成中...' : '生成授权链接' }}
+
+
+
+
+
+
+
+
+
+
2
+
+
在浏览器中打开链接并完成授权
+
+ 请在新标签页中打开授权链接,登录您的 Claude 账户并授权。
+
+
+
+
+ 注意: 如果您设置了代理,请确保浏览器也使用相同的代理访问授权页面。
+
+
+
+
+
+
+
+
+
+
3
+
+
输入 Authorization Code
+
+ 授权完成后,页面会显示一个 Authorization Code ,请将其复制并粘贴到下方输入框:
+
+
+
+
+ Authorization Code
+
+
+
+
+
+ 请粘贴从Claude页面复制的Authorization Code
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Gemini 账户授权
+
+ 请按照以下步骤完成 Gemini 账户的授权:
+
+
+
+
+
+
+
1
+
+
点击下方按钮生成授权链接
+
+
+
+ {{ loading ? '生成中...' : '生成授权链接' }}
+
+
+
+
+
+
+
+
+
+
2
+
+
在浏览器中打开链接并完成授权
+
+ 点击上方的授权链接,在新页面中完成Google账号登录
+ 点击“登录”按钮后可能会加载很慢(这是正常的)
+ 如果超过1分钟还在加载,请按 F5 刷新页面
+ 授权完成后会跳转到 http://localhost:45462 (可能显示无法访问)
+
+
+
+
+ 提示: 如果页面一直无法跳转,可以打开浏览器开发者工具(F12),F5刷新一下授权页再点击页面的登录按钮,在“网络”标签中找到以 localhost:45462 开头的请求,复制其完整URL。
+
+
+
+
+
+
+
+
+
+
3
+
+
复制oauth后的链接
+
+ 复制浏览器地址栏的完整链接并粘贴到下方输入框:
+
+
+
+
+ 复制oauth后的链接
+
+
+
+
+
+
+ 支持粘贴完整链接,系统会自动提取授权码
+
+
+
+ 也可以直接粘贴授权码(code参数的值)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 上一步
+
+
+
+ {{ exchanging ? '验证中...' : '完成授权' }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/accounts/ProxyConfig.vue b/web/admin-spa/src/components/accounts/ProxyConfig.vue
new file mode 100644
index 00000000..d88c1016
--- /dev/null
+++ b/web/admin-spa/src/components/accounts/ProxyConfig.vue
@@ -0,0 +1,166 @@
+
+
+
+
代理设置 (可选)
+
+
+ 启用代理
+
+
+
+
+
+
+
+
+
+
+ 配置代理以访问受限的网络资源。支持 SOCKS5 和 HTTP 代理。
+
+
+ 请确保代理服务器稳定可用,否则会影响账户的正常使用。
+
+
+
+
+
+ 代理类型
+
+ SOCKS5
+ HTTP
+ HTTPS
+
+
+
+
+
+
+
+
+
+
+ 提示: 代理设置将用于所有与此账户相关的API请求。请确保代理服务器支持HTTPS流量转发。
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/apikeys/CreateApiKeyModal.vue b/web/admin-spa/src/components/apikeys/CreateApiKeyModal.vue
new file mode 100644
index 00000000..502bfd34
--- /dev/null
+++ b/web/admin-spa/src/components/apikeys/CreateApiKeyModal.vue
@@ -0,0 +1,534 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue b/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue
new file mode 100644
index 00000000..44144f81
--- /dev/null
+++ b/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue
@@ -0,0 +1,447 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
速率限制设置
+
控制 API Key 的使用频率和资源消耗
+
+
+
+
+
时间窗口 (分钟)
+
+
设置一个时间段(以分钟为单位),用于计算速率限制
+
+
+
+
时间窗口内的请求次数限制
+
+
在时间窗口内允许的最大请求次数(需要先设置时间窗口)
+
+
+
+
时间窗口内的 Token 使用量限制
+
+
在时间窗口内允许消耗的最大 Token 数量(需要先设置时间窗口),0 或留空表示无限制
+
+
+
+
+
💡 使用示例
+
+
示例1: 时间窗口=60,请求次数限制=100
+
→ 每60分钟内最多允许100次请求
+
示例2: 时间窗口=10,Token限制=50000
+
→ 每10分钟内最多消耗50,000个Token
+
示例3: 时间窗口=30,请求次数限制=50,Token限制=100000
+
→ 每30分钟内最多50次请求且总Token不超过100,000
+
+
+
+
+
+
每日费用限制 (美元)
+
+
+ $50
+ $100
+ $200
+ 自定义
+
+
+
设置此 API Key 每日的费用限制,超过限制将拒绝请求,0 或留空表示无限制
+
+
+
+
+
并发限制
+
+
设置此 API Key 可同时处理的最大请求数
+
+
+
+
+
+
专属账号绑定
+
+
+ Claude 专属账号
+
+ 使用共享账号池
+
+ {{ account.name }} ({{ account.status === 'active' ? '正常' : '异常' }})
+
+
+
+
+ Gemini 专属账号
+
+ 使用共享账号池
+
+ {{ account.name }} ({{ account.status === 'active' ? '正常' : '异常' }})
+
+
+
+
+
修改绑定账号将影响此API Key的请求路由
+
+
+
+
+
+
+ 启用模型限制
+
+
+
+
+
+
限制的模型列表
+
+
+ {{ model }}
+
+
+
+
+
+ 暂无限制的模型
+
+
+
+
+
+
+
+
+
设置此API Key无法访问的模型,例如:claude-opus-4-20250514
+
+
+
+
+
+
+
+
+
+ 启用客户端限制
+
+
+
+
+
+
允许的客户端
+
勾选允许使用此API Key的客户端
+
+
+
+
+ {{ client.name }}
+ {{ client.description }}
+
+
+
+
+
+
+
+
+
+ 取消
+
+
+
+
+ {{ loading ? '保存中...' : '保存修改' }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/apikeys/NewApiKeyModal.vue b/web/admin-spa/src/components/apikeys/NewApiKeyModal.vue
new file mode 100644
index 00000000..7d884d1a
--- /dev/null
+++ b/web/admin-spa/src/components/apikeys/NewApiKeyModal.vue
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
请妥善保管您的 API Key
+
API Key 只会显示一次,关闭此窗口后将无法再次查看完整密钥。请立即复制并保存到安全的地方。
+
+
+
+
+
+
+
+
名称
+
{{ apiKey.name }}
+
+
+
+
描述
+
{{ apiKey.description }}
+
+
+
+
API Key
+
+
+
+
+
+
+
+
+ {{ copied ? '已复制' : '复制' }}
+
+
+
+
+
+
+
+
+
+ 使用说明
+
+
+
1. 在 HTTP 请求头中添加:
+
Authorization: Bearer {{ apiKey.key }}
+
+
2. 请求示例:
+
curl -X POST {{ currentBaseUrl }}v1/messages \
+ -H "Authorization: Bearer {{ apiKey.key }}" \
+ -H "Content-Type: application/json" \
+ -d '{"model": "claude-3-opus-20240229", "messages": [...]}'
+
+
+
+
+
+ 我已保存
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/apikeys/RenewApiKeyModal.vue b/web/admin-spa/src/components/apikeys/RenewApiKeyModal.vue
new file mode 100644
index 00000000..6551b0e8
--- /dev/null
+++ b/web/admin-spa/src/components/apikeys/RenewApiKeyModal.vue
@@ -0,0 +1,224 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
API Key 信息
+
{{ apiKey.name }}
+
+ 当前过期时间:{{ apiKey.expiresAt ? formatExpireDate(apiKey.expiresAt) : '永不过期' }}
+
+
+
+
+
+
+
续期时长
+
+ 延长 7 天
+ 延长 30 天
+ 延长 90 天
+ 延长 180 天
+ 延长 365 天
+ 自定义日期
+ 设为永不过期
+
+
+
+
+
+ 新的过期时间:{{ formatExpireDate(form.newExpiresAt) }}
+
+
+
+
+
+
+ 取消
+
+
+
+
+ {{ loading ? '续期中...' : '确认续期' }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/apistats/ApiKeyInput.vue b/web/admin-spa/src/components/apistats/ApiKeyInput.vue
new file mode 100644
index 00000000..d799dd09
--- /dev/null
+++ b/web/admin-spa/src/components/apistats/ApiKeyInput.vue
@@ -0,0 +1,269 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/apistats/LimitConfig.vue b/web/admin-spa/src/components/apistats/LimitConfig.vue
new file mode 100644
index 00000000..0926c85d
--- /dev/null
+++ b/web/admin-spa/src/components/apistats/LimitConfig.vue
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+ 限制配置
+
+
+
+ Token 限制
+ {{ statsData.limits.tokenLimit > 0 ? formatNumber(statsData.limits.tokenLimit) : '无限制' }}
+
+
+ 并发限制
+ {{ statsData.limits.concurrencyLimit > 0 ? statsData.limits.concurrencyLimit : '无限制' }}
+
+
+ 速率限制
+
+ {{ statsData.limits.rateLimitRequests > 0 && statsData.limits.rateLimitWindow > 0
+ ? `${statsData.limits.rateLimitRequests}次/${statsData.limits.rateLimitWindow}分钟`
+ : '无限制' }}
+
+
+
+ 每日费用限制
+ {{ statsData.limits.dailyCostLimit > 0 ? '$' + statsData.limits.dailyCostLimit : '无限制' }}
+
+
+ 模型限制
+
+
+
+ 限制 {{ statsData.restrictions.restrictedModels.length }} 个模型
+
+
+
+ 允许所有模型
+
+
+
+
+ 客户端限制
+
+
+
+ 限制 {{ statsData.restrictions.allowedClients.length }} 个客户端
+
+
+
+ 允许所有客户端
+
+
+
+
+
+
+
+
+
+
+ 详细限制信息
+
+
+
+
+
+
+
+ 受限模型列表
+
+
+
+
+ 此 API Key 不能访问以上列出的模型
+
+
+
+
+
+
+
+ 允许的客户端
+
+
+
+
+ 此 API Key 只能被以上列出的客户端使用
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/apistats/ModelUsageStats.vue b/web/admin-spa/src/components/apistats/ModelUsageStats.vue
new file mode 100644
index 00000000..f161f4b9
--- /dev/null
+++ b/web/admin-spa/src/components/apistats/ModelUsageStats.vue
@@ -0,0 +1,173 @@
+
+
+
+
+
+ 模型使用统计 ({{ statsPeriod === 'daily' ? '今日' : '本月' }})
+
+
+
+
+
+
+
+
+
+
+
+
{{ model.model }}
+
{{ model.requests }} 次请求
+
+
+
{{ model.formatted?.total || '$0.000000' }}
+
总费用
+
+
+
+
+
+
输入 Token
+
{{ formatNumber(model.inputTokens) }}
+
+
+
输出 Token
+
{{ formatNumber(model.outputTokens) }}
+
+
+
缓存创建
+
{{ formatNumber(model.cacheCreateTokens) }}
+
+
+
缓存读取
+
{{ formatNumber(model.cacheReadTokens) }}
+
+
+
+
+
+
+
+
+
暂无{{ statsPeriod === 'daily' ? '今日' : '本月' }}模型使用数据
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/apistats/StatsOverview.vue b/web/admin-spa/src/components/apistats/StatsOverview.vue
new file mode 100644
index 00000000..efe8c666
--- /dev/null
+++ b/web/admin-spa/src/components/apistats/StatsOverview.vue
@@ -0,0 +1,255 @@
+
+
+
+
+
+
+ API Key 信息
+
+
+
+ 名称
+ {{ statsData.name }}
+
+
+ 状态
+
+
+ {{ statsData.isActive ? '活跃' : '已停用' }}
+
+
+
+ 权限
+ {{ formatPermissions(statsData.permissions) }}
+
+
+ 创建时间
+ {{ formatDate(statsData.createdAt) }}
+
+
+
过期时间
+
+
+
+ 已过期
+
+
+
+ {{ formatExpireDate(statsData.expiresAt) }}
+
+
+ {{ formatExpireDate(statsData.expiresAt) }}
+
+
+
+
+ 永不过期
+
+
+
+
+
+
+
+
+
+ 使用统计概览 ({{ statsPeriod === 'daily' ? '今日' : '本月' }})
+
+
+
+
{{ formatNumber(currentPeriodData.requests) }}
+
{{ statsPeriod === 'daily' ? '今日' : '本月' }}请求数
+
+
+
{{ formatNumber(currentPeriodData.allTokens) }}
+
{{ statsPeriod === 'daily' ? '今日' : '本月' }}Token数
+
+
+
{{ currentPeriodData.formattedCost || '$0.000000' }}
+
{{ statsPeriod === 'daily' ? '今日' : '本月' }}费用
+
+
+
{{ formatNumber(currentPeriodData.inputTokens) }}
+
{{ statsPeriod === 'daily' ? '今日' : '本月' }}输入Token
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/apistats/TokenDistribution.vue b/web/admin-spa/src/components/apistats/TokenDistribution.vue
new file mode 100644
index 00000000..525ffec9
--- /dev/null
+++ b/web/admin-spa/src/components/apistats/TokenDistribution.vue
@@ -0,0 +1,102 @@
+
+
+
+
+ Token 使用分布 ({{ statsPeriod === 'daily' ? '今日' : '本月' }})
+
+
+
+
+
+ 输入 Token
+
+ {{ formatNumber(currentPeriodData.inputTokens) }}
+
+
+
+
+ 输出 Token
+
+ {{ formatNumber(currentPeriodData.outputTokens) }}
+
+
+
+
+ 缓存创建 Token
+
+ {{ formatNumber(currentPeriodData.cacheCreateTokens) }}
+
+
+
+
+ 缓存读取 Token
+
+ {{ formatNumber(currentPeriodData.cacheReadTokens) }}
+
+
+
+
+ {{ statsPeriod === 'daily' ? '今日' : '本月' }}总计
+ {{ formatNumber(currentPeriodData.allTokens) }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/common/ConfirmDialog.vue b/web/admin-spa/src/components/common/ConfirmDialog.vue
new file mode 100644
index 00000000..94105384
--- /dev/null
+++ b/web/admin-spa/src/components/common/ConfirmDialog.vue
@@ -0,0 +1,193 @@
+
+
+
+
+
+
+
+
+
+
+
{{ title }}
+
{{ message }}
+
+
+
+
+
+ {{ cancelText }}
+
+
+
+ {{ confirmText }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/common/ConfirmModal.vue b/web/admin-spa/src/components/common/ConfirmModal.vue
new file mode 100644
index 00000000..48c63b00
--- /dev/null
+++ b/web/admin-spa/src/components/common/ConfirmModal.vue
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
{{ title }}
+
{{ message }}
+
+
+
+
+
+ {{ cancelText }}
+
+
+ {{ confirmText }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/common/LogoTitle.vue b/web/admin-spa/src/components/common/LogoTitle.vue
new file mode 100644
index 00000000..e7a4c8c0
--- /dev/null
+++ b/web/admin-spa/src/components/common/LogoTitle.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/common/StatCard.vue b/web/admin-spa/src/components/common/StatCard.vue
new file mode 100644
index 00000000..3ef7860c
--- /dev/null
+++ b/web/admin-spa/src/components/common/StatCard.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
{{ title }}
+
{{ value }}
+
{{ subtitle }}
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/common/ToastNotification.vue b/web/admin-spa/src/components/common/ToastNotification.vue
new file mode 100644
index 00000000..94b664e4
--- /dev/null
+++ b/web/admin-spa/src/components/common/ToastNotification.vue
@@ -0,0 +1,373 @@
+
+
+
+
+
+
+
+
+
+
{{ toast.title }}
+
{{ toast.message }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/dashboard/ModelDistribution.vue b/web/admin-spa/src/components/dashboard/ModelDistribution.vue
new file mode 100644
index 00000000..7608d091
--- /dev/null
+++ b/web/admin-spa/src/components/dashboard/ModelDistribution.vue
@@ -0,0 +1,134 @@
+
+
+
+
+
+ 模型使用分布
+
+
+
+ 今日
+ 累计
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ formatNumber(stat.requests) }} 请求
+
{{ formatNumber(stat.totalTokens) }} tokens
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/dashboard/UsageTrend.vue b/web/admin-spa/src/components/dashboard/UsageTrend.vue
new file mode 100644
index 00000000..682a3605
--- /dev/null
+++ b/web/admin-spa/src/components/dashboard/UsageTrend.vue
@@ -0,0 +1,167 @@
+
+
+
+
+
+ 使用趋势
+
+
+
+
+ 按天
+ 按小时
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/layout/AppHeader.vue b/web/admin-spa/src/components/layout/AppHeader.vue
new file mode 100644
index 00000000..ef16a409
--- /dev/null
+++ b/web/admin-spa/src/components/layout/AppHeader.vue
@@ -0,0 +1,412 @@
+
+
+
+
+
+
+
+
+
v{{ versionInfo.current || '...' }}
+
+
+
+ 新版本
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
当前用户名
+
+
当前用户名,输入新用户名以修改
+
+
+
+
+
+ 当前密码
+
+
+
+
+
+
+ 确认新密码
+
+
+
+
+
+ 取消
+
+
+
+
+ {{ changePasswordLoading ? '保存中...' : '保存修改' }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/layout/MainLayout.vue b/web/admin-spa/src/components/layout/MainLayout.vue
new file mode 100644
index 00000000..a12e450b
--- /dev/null
+++ b/web/admin-spa/src/components/layout/MainLayout.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/components/layout/TabBar.vue b/web/admin-spa/src/components/layout/TabBar.vue
new file mode 100644
index 00000000..d2d1ffff
--- /dev/null
+++ b/web/admin-spa/src/components/layout/TabBar.vue
@@ -0,0 +1,38 @@
+
+
+
+ {{ tab.name }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/composables/useChartConfig.js b/web/admin-spa/src/composables/useChartConfig.js
new file mode 100644
index 00000000..022e5fca
--- /dev/null
+++ b/web/admin-spa/src/composables/useChartConfig.js
@@ -0,0 +1,101 @@
+import { Chart } from 'chart.js/auto'
+
+export function useChartConfig() {
+ // 设置Chart.js默认配置
+ Chart.defaults.font.family = "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
+ Chart.defaults.color = '#6b7280'
+ Chart.defaults.plugins.tooltip.backgroundColor = 'rgba(0, 0, 0, 0.8)'
+ Chart.defaults.plugins.tooltip.padding = 12
+ Chart.defaults.plugins.tooltip.cornerRadius = 8
+ Chart.defaults.plugins.tooltip.titleFont.size = 14
+ Chart.defaults.plugins.tooltip.bodyFont.size = 12
+
+ // 创建渐变色
+ const getGradient = (ctx, color, opacity = 0.2) => {
+ const gradient = ctx.createLinearGradient(0, 0, 0, 300)
+ gradient.addColorStop(0, `${color}${Math.round(opacity * 255).toString(16).padStart(2, '0')}`)
+ gradient.addColorStop(1, `${color}00`)
+ return gradient
+ }
+
+ // 通用图表选项
+ const commonOptions = {
+ responsive: true,
+ maintainAspectRatio: false,
+ plugins: {
+ legend: {
+ display: true,
+ position: 'top',
+ labels: {
+ usePointStyle: true,
+ padding: 20,
+ font: {
+ size: 12,
+ weight: '500'
+ }
+ }
+ },
+ tooltip: {
+ mode: 'index',
+ intersect: false,
+ callbacks: {
+ label: function(context) {
+ let label = context.dataset.label || ''
+ if (label) {
+ label += ': '
+ }
+ if (context.parsed.y !== null) {
+ label += new Intl.NumberFormat('zh-CN').format(context.parsed.y)
+ }
+ return label
+ }
+ }
+ }
+ },
+ scales: {
+ x: {
+ grid: {
+ display: false
+ },
+ ticks: {
+ font: {
+ size: 11
+ }
+ }
+ },
+ y: {
+ grid: {
+ color: 'rgba(0, 0, 0, 0.05)',
+ drawBorder: false
+ },
+ ticks: {
+ font: {
+ size: 11
+ },
+ callback: function(value) {
+ if (value >= 1000000) {
+ return (value / 1000000).toFixed(1) + 'M'
+ } else if (value >= 1000) {
+ return (value / 1000).toFixed(1) + 'K'
+ }
+ return value
+ }
+ }
+ }
+ }
+ }
+
+ // 颜色方案
+ const colorSchemes = {
+ primary: ['#667eea', '#764ba2', '#f093fb', '#4facfe', '#00f2fe'],
+ success: ['#10b981', '#059669', '#34d399', '#6ee7b7', '#a7f3d0'],
+ warning: ['#f59e0b', '#d97706', '#fbbf24', '#fcd34d', '#fde68a'],
+ danger: ['#ef4444', '#dc2626', '#f87171', '#fca5a5', '#fecaca']
+ }
+
+ return {
+ getGradient,
+ commonOptions,
+ colorSchemes
+ }
+}
\ No newline at end of file
diff --git a/web/admin-spa/src/composables/useConfirm.js b/web/admin-spa/src/composables/useConfirm.js
new file mode 100644
index 00000000..b5f2800a
--- /dev/null
+++ b/web/admin-spa/src/composables/useConfirm.js
@@ -0,0 +1,49 @@
+import { ref } from 'vue'
+
+const showConfirmModal = ref(false)
+const confirmOptions = ref({
+ title: '',
+ message: '',
+ confirmText: '继续',
+ cancelText: '取消'
+})
+const confirmResolve = ref(null)
+
+export function useConfirm() {
+ const showConfirm = (title, message, confirmText = '继续', cancelText = '取消') => {
+ return new Promise((resolve) => {
+ confirmOptions.value = {
+ title,
+ message,
+ confirmText,
+ cancelText
+ }
+ confirmResolve.value = resolve
+ showConfirmModal.value = true
+ })
+ }
+
+ const handleConfirm = () => {
+ showConfirmModal.value = false
+ if (confirmResolve.value) {
+ confirmResolve.value(true)
+ confirmResolve.value = null
+ }
+ }
+
+ const handleCancel = () => {
+ showConfirmModal.value = false
+ if (confirmResolve.value) {
+ confirmResolve.value(false)
+ confirmResolve.value = null
+ }
+ }
+
+ return {
+ showConfirmModal,
+ confirmOptions,
+ showConfirm,
+ handleConfirm,
+ handleCancel
+ }
+}
\ No newline at end of file
diff --git a/web/admin-spa/src/config/api.js b/web/admin-spa/src/config/api.js
new file mode 100644
index 00000000..68508011
--- /dev/null
+++ b/web/admin-spa/src/config/api.js
@@ -0,0 +1,173 @@
+// API 配置
+import { APP_CONFIG, getLoginUrl } from './app'
+
+const isDev = import.meta.env.DEV
+
+// 开发环境使用 /webapi 前缀,生产环境不使用前缀
+export const API_PREFIX = APP_CONFIG.apiPrefix
+
+// 创建完整的 API URL
+export function createApiUrl(path) {
+ // 确保路径以 / 开头
+ if (!path.startsWith('/')) {
+ path = '/' + path
+ }
+ return API_PREFIX + path
+}
+
+// API 请求的基础配置
+export function getRequestConfig(token) {
+ const config = {
+ headers: {
+ 'Content-Type': 'application/json'
+ }
+ }
+
+ if (token) {
+ config.headers['Authorization'] = `Bearer ${token}`
+ }
+
+ return config
+}
+
+// 统一的 API 请求类
+class ApiClient {
+ constructor() {
+ this.baseURL = API_PREFIX
+ }
+
+ // 获取认证 token
+ getAuthToken() {
+ const authToken = localStorage.getItem('authToken')
+ return authToken || null
+ }
+
+ // 构建请求配置
+ buildConfig(options = {}) {
+ const config = {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers
+ },
+ ...options
+ }
+
+ // 添加认证 token
+ const token = this.getAuthToken()
+ if (token) {
+ config.headers['Authorization'] = `Bearer ${token}`
+ }
+
+ return config
+ }
+
+ // 处理响应
+ async handleResponse(response) {
+ // 401 未授权,需要重新登录
+ if (response.status === 401) {
+ // 如果当前已经在登录页面,不要再次跳转
+ const currentPath = window.location.pathname + window.location.hash
+ const isLoginPage = currentPath.includes('/login') || currentPath.endsWith('/')
+
+ if (!isLoginPage) {
+ localStorage.removeItem('authToken')
+ // 使用统一的登录URL
+ window.location.href = getLoginUrl()
+ }
+ throw new Error('Unauthorized')
+ }
+
+ // 尝试解析 JSON
+ const contentType = response.headers.get('content-type')
+ if (contentType && contentType.includes('application/json')) {
+ const data = await response.json()
+
+ // 如果响应不成功,抛出错误
+ if (!response.ok) {
+ throw new Error(data.message || `HTTP ${response.status}`)
+ }
+
+ return data
+ }
+
+ // 非 JSON 响应
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
+ }
+
+ return response
+ }
+
+ // GET 请求
+ async get(url, options = {}) {
+ const fullUrl = createApiUrl(url)
+ const config = this.buildConfig({
+ ...options,
+ method: 'GET'
+ })
+
+ try {
+ const response = await fetch(fullUrl, config)
+ return await this.handleResponse(response)
+ } catch (error) {
+ console.error('API GET Error:', error)
+ throw error
+ }
+ }
+
+ // POST 请求
+ async post(url, data = null, options = {}) {
+ const fullUrl = createApiUrl(url)
+ const config = this.buildConfig({
+ ...options,
+ method: 'POST',
+ body: data ? JSON.stringify(data) : undefined
+ })
+
+ try {
+ const response = await fetch(fullUrl, config)
+ return await this.handleResponse(response)
+ } catch (error) {
+ console.error('API POST Error:', error)
+ throw error
+ }
+ }
+
+ // PUT 请求
+ async put(url, data = null, options = {}) {
+ const fullUrl = createApiUrl(url)
+ const config = this.buildConfig({
+ ...options,
+ method: 'PUT',
+ body: data ? JSON.stringify(data) : undefined
+ })
+
+ try {
+ const response = await fetch(fullUrl, config)
+ return await this.handleResponse(response)
+ } catch (error) {
+ console.error('API PUT Error:', error)
+ throw error
+ }
+ }
+
+ // DELETE 请求
+ async delete(url, options = {}) {
+ const fullUrl = createApiUrl(url)
+ const config = this.buildConfig({
+ ...options,
+ method: 'DELETE'
+ })
+
+ try {
+ const response = await fetch(fullUrl, config)
+ return await this.handleResponse(response)
+ } catch (error) {
+ console.error('API DELETE Error:', error)
+ throw error
+ }
+ }
+}
+
+// 导出单例实例
+export const apiClient = new ApiClient()
\ No newline at end of file
diff --git a/web/admin-spa/src/config/apiStats.js b/web/admin-spa/src/config/apiStats.js
new file mode 100644
index 00000000..5eac7794
--- /dev/null
+++ b/web/admin-spa/src/config/apiStats.js
@@ -0,0 +1,81 @@
+// API Stats 专用 API 客户端
+// 与管理员 API 隔离,不需要认证
+
+class ApiStatsClient {
+ constructor() {
+ this.baseURL = window.location.origin
+ // 开发环境需要为 admin 路径添加 /webapi 前缀
+ this.isDev = import.meta.env.DEV
+ }
+
+ async request(url, options = {}) {
+ try {
+ // 在开发环境中,为 /admin 路径添加 /webapi 前缀
+ if (this.isDev && url.startsWith('/admin')) {
+ url = '/webapi' + url
+ }
+
+ const response = await fetch(`${this.baseURL}${url}`, {
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options.headers
+ },
+ ...options
+ })
+
+ const data = await response.json()
+
+ if (!response.ok) {
+ throw new Error(data.message || `请求失败: ${response.status}`)
+ }
+
+ return data
+ } catch (error) {
+ console.error('API Stats request error:', error)
+ throw error
+ }
+ }
+
+ // 获取 API Key ID
+ async getKeyId(apiKey) {
+ return this.request('/apiStats/api/get-key-id', {
+ method: 'POST',
+ body: JSON.stringify({ apiKey })
+ })
+ }
+
+ // 获取用户统计数据
+ async getUserStats(apiId) {
+ return this.request('/apiStats/api/user-stats', {
+ method: 'POST',
+ body: JSON.stringify({ apiId })
+ })
+ }
+
+ // 获取模型使用统计
+ async getUserModelStats(apiId, period = 'daily') {
+ return this.request('/apiStats/api/user-model-stats', {
+ method: 'POST',
+ body: JSON.stringify({ apiId, period })
+ })
+ }
+
+ // 获取 OEM 设置(用于网站名称和图标)
+ async getOemSettings() {
+ try {
+ return await this.request('/admin/oem-settings')
+ } catch (error) {
+ console.error('Failed to load OEM settings:', error)
+ return {
+ success: true,
+ data: {
+ siteName: 'Claude Relay Service',
+ siteIcon: '',
+ siteIconData: ''
+ }
+ }
+ }
+ }
+}
+
+export const apiStatsClient = new ApiStatsClient()
\ No newline at end of file
diff --git a/web/admin-spa/src/config/app.js b/web/admin-spa/src/config/app.js
new file mode 100644
index 00000000..56493bc3
--- /dev/null
+++ b/web/admin-spa/src/config/app.js
@@ -0,0 +1,28 @@
+// 应用配置
+export const APP_CONFIG = {
+ // 应用基础路径
+ basePath: import.meta.env.VITE_APP_BASE_URL || (import.meta.env.DEV ? '/admin/' : '/web/admin/'),
+
+ // 应用标题
+ title: import.meta.env.VITE_APP_TITLE || 'Claude Relay Service - 管理后台',
+
+ // 是否为开发环境
+ isDev: import.meta.env.DEV,
+
+ // API 前缀
+ apiPrefix: import.meta.env.DEV ? '/webapi' : ''
+}
+
+// 获取完整的应用URL
+export function getAppUrl(path = '') {
+ // 确保路径以 / 开头
+ if (path && !path.startsWith('/')) {
+ path = '/' + path
+ }
+ return APP_CONFIG.basePath + (path.startsWith('#') ? path : '#' + path)
+}
+
+// 获取登录页面URL
+export function getLoginUrl() {
+ return getAppUrl('/login')
+}
\ No newline at end of file
diff --git a/web/admin-spa/src/main.js b/web/admin-spa/src/main.js
new file mode 100644
index 00000000..16c1bbd3
--- /dev/null
+++ b/web/admin-spa/src/main.js
@@ -0,0 +1,27 @@
+import { createApp } from 'vue'
+import { createPinia } from 'pinia'
+import ElementPlus from 'element-plus'
+import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
+import 'element-plus/dist/index.css'
+import App from './App.vue'
+import router from './router'
+import './assets/styles/main.css'
+import './assets/styles/global.css'
+
+// 创建Vue应用
+const app = createApp(App)
+
+// 使用Pinia状态管理
+const pinia = createPinia()
+app.use(pinia)
+
+// 使用路由
+app.use(router)
+
+// 使用Element Plus
+app.use(ElementPlus, {
+ locale: zhCn,
+})
+
+// 挂载应用
+app.mount('#app')
\ No newline at end of file
diff --git a/web/admin-spa/src/router/index.js b/web/admin-spa/src/router/index.js
new file mode 100644
index 00000000..89c44e77
--- /dev/null
+++ b/web/admin-spa/src/router/index.js
@@ -0,0 +1,122 @@
+import { createRouter, createWebHistory } from 'vue-router'
+import { useAuthStore } from '@/stores/auth'
+import { APP_CONFIG } from '@/config/app'
+
+// 路由懒加载
+const LoginView = () => import('@/views/LoginView.vue')
+const MainLayout = () => import('@/components/layout/MainLayout.vue')
+const DashboardView = () => import('@/views/DashboardView.vue')
+const ApiKeysView = () => import('@/views/ApiKeysView.vue')
+const AccountsView = () => import('@/views/AccountsView.vue')
+const TutorialView = () => import('@/views/TutorialView.vue')
+const SettingsView = () => import('@/views/SettingsView.vue')
+const ApiStatsView = () => import('@/views/ApiStatsView.vue')
+
+const routes = [
+ {
+ path: '/',
+ redirect: '/api-stats'
+ },
+ {
+ path: '/login',
+ name: 'Login',
+ component: LoginView,
+ meta: { requiresAuth: false }
+ },
+ {
+ path: '/api-stats',
+ name: 'ApiStats',
+ component: ApiStatsView,
+ meta: { requiresAuth: false }
+ },
+ {
+ path: '/dashboard',
+ component: MainLayout,
+ meta: { requiresAuth: true },
+ children: [
+ {
+ path: '',
+ name: 'Dashboard',
+ component: DashboardView
+ }
+ ]
+ },
+ {
+ path: '/api-keys',
+ component: MainLayout,
+ meta: { requiresAuth: true },
+ children: [
+ {
+ path: '',
+ name: 'ApiKeys',
+ component: ApiKeysView
+ }
+ ]
+ },
+ {
+ path: '/accounts',
+ component: MainLayout,
+ meta: { requiresAuth: true },
+ children: [
+ {
+ path: '',
+ name: 'Accounts',
+ component: AccountsView
+ }
+ ]
+ },
+ {
+ path: '/tutorial',
+ component: MainLayout,
+ meta: { requiresAuth: true },
+ children: [
+ {
+ path: '',
+ name: 'Tutorial',
+ component: TutorialView
+ }
+ ]
+ },
+ {
+ path: '/settings',
+ component: MainLayout,
+ meta: { requiresAuth: true },
+ children: [
+ {
+ path: '',
+ name: 'Settings',
+ component: SettingsView
+ }
+ ]
+ }
+]
+
+const router = createRouter({
+ history: createWebHistory(APP_CONFIG.basePath),
+ routes
+})
+
+// 路由守卫
+router.beforeEach((to, from, next) => {
+ const authStore = useAuthStore()
+
+ console.log('路由导航:', {
+ to: to.path,
+ from: from.path,
+ requiresAuth: to.meta.requiresAuth,
+ isAuthenticated: authStore.isAuthenticated
+ })
+
+ // API Stats 页面不需要认证,直接放行
+ if (to.path === '/api-stats' || to.path.startsWith('/api-stats')) {
+ next()
+ } else if (to.meta.requiresAuth && !authStore.isAuthenticated) {
+ next('/login')
+ } else if (to.path === '/login' && authStore.isAuthenticated) {
+ next('/dashboard')
+ } else {
+ next()
+ }
+})
+
+export default router
\ No newline at end of file
diff --git a/web/admin-spa/src/stores/accounts.js b/web/admin-spa/src/stores/accounts.js
new file mode 100644
index 00000000..cf0b8af3
--- /dev/null
+++ b/web/admin-spa/src/stores/accounts.js
@@ -0,0 +1,334 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import { apiClient } from '@/config/api'
+
+export const useAccountsStore = defineStore('accounts', () => {
+ // 状态
+ const claudeAccounts = ref([])
+ const geminiAccounts = ref([])
+ const loading = ref(false)
+ const error = ref(null)
+ const sortBy = ref('')
+ const sortOrder = ref('asc')
+
+ // Actions
+
+ // 获取Claude账户列表
+ const fetchClaudeAccounts = async () => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.get('/admin/claude-accounts')
+ if (response.success) {
+ claudeAccounts.value = response.data || []
+ } else {
+ throw new Error(response.message || '获取Claude账户失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 获取Gemini账户列表
+ const fetchGeminiAccounts = async () => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.get('/admin/gemini-accounts')
+ if (response.success) {
+ geminiAccounts.value = response.data || []
+ } else {
+ throw new Error(response.message || '获取Gemini账户失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 获取所有账户
+ const fetchAllAccounts = async () => {
+ loading.value = true
+ error.value = null
+ try {
+ await Promise.all([
+ fetchClaudeAccounts(),
+ fetchGeminiAccounts()
+ ])
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 创建Claude账户
+ const createClaudeAccount = async (data) => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.post('/admin/claude-accounts', data)
+ if (response.success) {
+ await fetchClaudeAccounts()
+ return response.data
+ } else {
+ throw new Error(response.message || '创建Claude账户失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 创建Gemini账户
+ const createGeminiAccount = async (data) => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.post('/admin/gemini-accounts', data)
+ if (response.success) {
+ await fetchGeminiAccounts()
+ return response.data
+ } else {
+ throw new Error(response.message || '创建Gemini账户失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 更新Claude账户
+ const updateClaudeAccount = async (id, data) => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.put(`/admin/claude-accounts/${id}`, data)
+ if (response.success) {
+ await fetchClaudeAccounts()
+ return response
+ } else {
+ throw new Error(response.message || '更新Claude账户失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 更新Gemini账户
+ const updateGeminiAccount = async (id, data) => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.put(`/admin/gemini-accounts/${id}`, data)
+ if (response.success) {
+ await fetchGeminiAccounts()
+ return response
+ } else {
+ throw new Error(response.message || '更新Gemini账户失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 切换账户状态
+ const toggleAccount = async (platform, id) => {
+ loading.value = true
+ error.value = null
+ try {
+ const endpoint = platform === 'claude'
+ ? `/admin/claude-accounts/${id}/toggle`
+ : `/admin/gemini-accounts/${id}/toggle`
+
+ const response = await apiClient.put(endpoint)
+ if (response.success) {
+ if (platform === 'claude') {
+ await fetchClaudeAccounts()
+ } else {
+ await fetchGeminiAccounts()
+ }
+ return response
+ } else {
+ throw new Error(response.message || '切换状态失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 删除账户
+ const deleteAccount = async (platform, id) => {
+ loading.value = true
+ error.value = null
+ try {
+ const endpoint = platform === 'claude'
+ ? `/admin/claude-accounts/${id}`
+ : `/admin/gemini-accounts/${id}`
+
+ const response = await apiClient.delete(endpoint)
+ if (response.success) {
+ if (platform === 'claude') {
+ await fetchClaudeAccounts()
+ } else {
+ await fetchGeminiAccounts()
+ }
+ return response
+ } else {
+ throw new Error(response.message || '删除失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 刷新Claude Token
+ const refreshClaudeToken = async (id) => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.post(`/admin/claude-accounts/${id}/refresh`)
+ if (response.success) {
+ await fetchClaudeAccounts()
+ return response
+ } else {
+ throw new Error(response.message || 'Token刷新失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 生成Claude OAuth URL
+ const generateClaudeAuthUrl = async (proxyConfig) => {
+ try {
+ const response = await apiClient.post('/admin/claude-accounts/generate-auth-url', proxyConfig)
+ if (response.success) {
+ return response.data.authUrl // 返回authUrl字符串而不是整个对象
+ } else {
+ throw new Error(response.message || '生成授权URL失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ }
+ }
+
+ // 交换Claude OAuth Code
+ const exchangeClaudeCode = async (data) => {
+ try {
+ const response = await apiClient.post('/admin/claude-accounts/exchange-code', data)
+ if (response.success) {
+ return response.data
+ } else {
+ throw new Error(response.message || '交换授权码失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ }
+ }
+
+ // 生成Gemini OAuth URL
+ const generateGeminiAuthUrl = async (proxyConfig) => {
+ try {
+ const response = await apiClient.post('/admin/gemini-accounts/generate-auth-url', proxyConfig)
+ if (response.success) {
+ return response.data.authUrl // 返回authUrl字符串而不是整个对象
+ } else {
+ throw new Error(response.message || '生成授权URL失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ }
+ }
+
+ // 交换Gemini OAuth Code
+ const exchangeGeminiCode = async (data) => {
+ try {
+ const response = await apiClient.post('/admin/gemini-accounts/exchange-code', data)
+ if (response.success) {
+ return response.data
+ } else {
+ throw new Error(response.message || '交换授权码失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ }
+ }
+
+ // 排序账户
+ const sortAccounts = (field) => {
+ if (sortBy.value === field) {
+ sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
+ } else {
+ sortBy.value = field
+ sortOrder.value = 'asc'
+ }
+ }
+
+ // 重置store
+ const reset = () => {
+ claudeAccounts.value = []
+ geminiAccounts.value = []
+ loading.value = false
+ error.value = null
+ sortBy.value = ''
+ sortOrder.value = 'asc'
+ }
+
+ return {
+ // State
+ claudeAccounts,
+ geminiAccounts,
+ loading,
+ error,
+ sortBy,
+ sortOrder,
+
+ // Actions
+ fetchClaudeAccounts,
+ fetchGeminiAccounts,
+ fetchAllAccounts,
+ createClaudeAccount,
+ createGeminiAccount,
+ updateClaudeAccount,
+ updateGeminiAccount,
+ toggleAccount,
+ deleteAccount,
+ refreshClaudeToken,
+ generateClaudeAuthUrl,
+ exchangeClaudeCode,
+ generateGeminiAuthUrl,
+ exchangeGeminiCode,
+ sortAccounts,
+ reset
+ }
+})
\ No newline at end of file
diff --git a/web/admin-spa/src/stores/apiKeys.js b/web/admin-spa/src/stores/apiKeys.js
new file mode 100644
index 00000000..70478d70
--- /dev/null
+++ b/web/admin-spa/src/stores/apiKeys.js
@@ -0,0 +1,192 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import { apiClient } from '@/config/api'
+
+export const useApiKeysStore = defineStore('apiKeys', () => {
+ // 状态
+ const apiKeys = ref([])
+ const loading = ref(false)
+ const error = ref(null)
+ const statsTimeRange = ref('all')
+ const sortBy = ref('')
+ const sortOrder = ref('asc')
+
+ // Actions
+
+ // 获取API Keys列表
+ const fetchApiKeys = async () => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.get('/admin/api-keys')
+ if (response.success) {
+ apiKeys.value = response.data || []
+ } else {
+ throw new Error(response.message || '获取API Keys失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 创建API Key
+ const createApiKey = async (data) => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.post('/admin/api-keys', data)
+ if (response.success) {
+ await fetchApiKeys()
+ return response.data
+ } else {
+ throw new Error(response.message || '创建API Key失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 更新API Key
+ const updateApiKey = async (id, data) => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.put(`/admin/api-keys/${id}`, data)
+ if (response.success) {
+ await fetchApiKeys()
+ return response
+ } else {
+ throw new Error(response.message || '更新API Key失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 切换API Key状态
+ const toggleApiKey = async (id) => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.put(`/admin/api-keys/${id}/toggle`)
+ if (response.success) {
+ await fetchApiKeys()
+ return response
+ } else {
+ throw new Error(response.message || '切换状态失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 续期API Key
+ const renewApiKey = async (id, data) => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.put(`/admin/api-keys/${id}/renew`, data)
+ if (response.success) {
+ await fetchApiKeys()
+ return response
+ } else {
+ throw new Error(response.message || '续期失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 删除API Key
+ const deleteApiKey = async (id) => {
+ loading.value = true
+ error.value = null
+ try {
+ const response = await apiClient.delete(`/admin/api-keys/${id}`)
+ if (response.success) {
+ await fetchApiKeys()
+ return response
+ } else {
+ throw new Error(response.message || '删除失败')
+ }
+ } catch (err) {
+ error.value = err.message
+ throw err
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 获取API Key统计
+ const fetchApiKeyStats = async (id, timeRange = 'all') => {
+ try {
+ const response = await apiClient.get(`/admin/api-keys/${id}/stats`, {
+ params: { timeRange }
+ })
+ if (response.success) {
+ return response.stats
+ } else {
+ throw new Error(response.message || '获取统计失败')
+ }
+ } catch (err) {
+ console.error('获取API Key统计失败:', err)
+ return null
+ }
+ }
+
+ // 排序API Keys
+ const sortApiKeys = (field) => {
+ if (sortBy.value === field) {
+ sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
+ } else {
+ sortBy.value = field
+ sortOrder.value = 'asc'
+ }
+ }
+
+ // 重置store
+ const reset = () => {
+ apiKeys.value = []
+ loading.value = false
+ error.value = null
+ statsTimeRange.value = 'all'
+ sortBy.value = ''
+ sortOrder.value = 'asc'
+ }
+
+ return {
+ // State
+ apiKeys,
+ loading,
+ error,
+ statsTimeRange,
+ sortBy,
+ sortOrder,
+
+ // Actions
+ fetchApiKeys,
+ createApiKey,
+ updateApiKey,
+ toggleApiKey,
+ renewApiKey,
+ deleteApiKey,
+ fetchApiKeyStats,
+ sortApiKeys,
+ reset
+ }
+})
\ No newline at end of file
diff --git a/web/admin-spa/src/stores/apistats.js b/web/admin-spa/src/stores/apistats.js
new file mode 100644
index 00000000..819728b1
--- /dev/null
+++ b/web/admin-spa/src/stores/apistats.js
@@ -0,0 +1,343 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+import { apiStatsClient } from '@/config/apiStats'
+
+export const useApiStatsStore = defineStore('apistats', () => {
+ // 状态
+ const apiKey = ref('')
+ const apiId = ref(null)
+ const loading = ref(false)
+ const modelStatsLoading = ref(false)
+ const oemLoading = ref(true)
+ const error = ref('')
+ const statsPeriod = ref('daily')
+ const statsData = ref(null)
+ const modelStats = ref([])
+ const dailyStats = ref(null)
+ const monthlyStats = ref(null)
+ const oemSettings = ref({
+ siteName: '',
+ siteIcon: '',
+ siteIconData: ''
+ })
+
+ // 计算属性
+ const currentPeriodData = computed(() => {
+ const defaultData = {
+ requests: 0,
+ inputTokens: 0,
+ outputTokens: 0,
+ cacheCreateTokens: 0,
+ cacheReadTokens: 0,
+ allTokens: 0,
+ cost: 0,
+ formattedCost: '$0.000000'
+ }
+
+ if (statsPeriod.value === 'daily') {
+ return dailyStats.value || defaultData
+ } else {
+ return monthlyStats.value || defaultData
+ }
+ })
+
+ const usagePercentages = computed(() => {
+ if (!statsData.value || !currentPeriodData.value) {
+ return {
+ tokenUsage: 0,
+ costUsage: 0,
+ requestUsage: 0
+ }
+ }
+
+ const current = currentPeriodData.value
+ const limits = statsData.value.limits
+
+ return {
+ tokenUsage: limits.tokenLimit > 0 ? Math.min((current.allTokens / limits.tokenLimit) * 100, 100) : 0,
+ costUsage: limits.dailyCostLimit > 0 ? Math.min((current.cost / limits.dailyCostLimit) * 100, 100) : 0,
+ requestUsage: limits.rateLimitRequests > 0 ? Math.min((current.requests / limits.rateLimitRequests) * 100, 100) : 0
+ }
+ })
+
+ // Actions
+
+ // 查询统计数据
+ async function queryStats() {
+ if (!apiKey.value.trim()) {
+ error.value = '请输入 API Key'
+ return
+ }
+
+ loading.value = true
+ error.value = ''
+ statsData.value = null
+ modelStats.value = []
+ apiId.value = null
+
+ try {
+ // 获取 API Key ID
+ const idResult = await apiStatsClient.getKeyId(apiKey.value)
+
+ if (idResult.success) {
+ apiId.value = idResult.data.id
+
+ // 使用 apiId 查询统计数据
+ const statsResult = await apiStatsClient.getUserStats(apiId.value)
+
+ if (statsResult.success) {
+ statsData.value = statsResult.data
+
+ // 同时加载今日和本月的统计数据
+ await loadAllPeriodStats()
+
+ // 清除错误信息
+ error.value = ''
+
+ // 更新 URL
+ updateURL()
+ } else {
+ throw new Error(statsResult.message || '查询失败')
+ }
+ } else {
+ throw new Error(idResult.message || '获取 API Key ID 失败')
+ }
+ } catch (err) {
+ console.error('Query stats error:', err)
+ error.value = err.message || '查询统计数据失败,请检查您的 API Key 是否正确'
+ statsData.value = null
+ modelStats.value = []
+ apiId.value = null
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 加载所有时间段的统计数据
+ async function loadAllPeriodStats() {
+ if (!apiId.value) return
+
+ // 并行加载今日和本月的数据
+ await Promise.all([
+ loadPeriodStats('daily'),
+ loadPeriodStats('monthly')
+ ])
+
+ // 加载当前选择时间段的模型统计
+ await loadModelStats(statsPeriod.value)
+ }
+
+ // 加载指定时间段的统计数据
+ async function loadPeriodStats(period) {
+ try {
+ const result = await apiStatsClient.getUserModelStats(apiId.value, period)
+
+ if (result.success) {
+ // 计算汇总数据
+ const modelData = result.data || []
+ const summary = {
+ requests: 0,
+ inputTokens: 0,
+ outputTokens: 0,
+ cacheCreateTokens: 0,
+ cacheReadTokens: 0,
+ allTokens: 0,
+ cost: 0,
+ formattedCost: '$0.000000'
+ }
+
+ modelData.forEach(model => {
+ summary.requests += model.requests || 0
+ summary.inputTokens += model.inputTokens || 0
+ summary.outputTokens += model.outputTokens || 0
+ summary.cacheCreateTokens += model.cacheCreateTokens || 0
+ summary.cacheReadTokens += model.cacheReadTokens || 0
+ summary.allTokens += model.allTokens || 0
+ summary.cost += model.costs?.total || 0
+ })
+
+ summary.formattedCost = formatCost(summary.cost)
+
+ // 存储到对应的时间段数据
+ if (period === 'daily') {
+ dailyStats.value = summary
+ } else {
+ monthlyStats.value = summary
+ }
+ } else {
+ console.warn(`Failed to load ${period} stats:`, result.message)
+ }
+ } catch (err) {
+ console.error(`Load ${period} stats error:`, err)
+ }
+ }
+
+ // 加载模型统计数据
+ async function loadModelStats(period = 'daily') {
+ if (!apiId.value) return
+
+ modelStatsLoading.value = true
+
+ try {
+ const result = await apiStatsClient.getUserModelStats(apiId.value, period)
+
+ if (result.success) {
+ modelStats.value = result.data || []
+ } else {
+ throw new Error(result.message || '加载模型统计失败')
+ }
+ } catch (err) {
+ console.error('Load model stats error:', err)
+ modelStats.value = []
+ } finally {
+ modelStatsLoading.value = false
+ }
+ }
+
+ // 切换时间范围
+ async function switchPeriod(period) {
+ if (statsPeriod.value === period || modelStatsLoading.value) {
+ return
+ }
+
+ statsPeriod.value = period
+
+ // 如果对应时间段的数据还没有加载,则加载它
+ if ((period === 'daily' && !dailyStats.value) ||
+ (period === 'monthly' && !monthlyStats.value)) {
+ await loadPeriodStats(period)
+ }
+
+ // 加载对应的模型统计
+ await loadModelStats(period)
+ }
+
+ // 使用 apiId 直接加载数据
+ async function loadStatsWithApiId() {
+ if (!apiId.value) return
+
+ loading.value = true
+ error.value = ''
+ statsData.value = null
+ modelStats.value = []
+
+ try {
+ const result = await apiStatsClient.getUserStats(apiId.value)
+
+ if (result.success) {
+ statsData.value = result.data
+
+ // 同时加载今日和本月的统计数据
+ await loadAllPeriodStats()
+
+ // 清除错误信息
+ error.value = ''
+ } else {
+ throw new Error(result.message || '查询失败')
+ }
+ } catch (err) {
+ console.error('Load stats with apiId error:', err)
+ error.value = err.message || '查询统计数据失败'
+ statsData.value = null
+ modelStats.value = []
+ } finally {
+ loading.value = false
+ }
+ }
+
+ // 加载 OEM 设置
+ async function loadOemSettings() {
+ oemLoading.value = true
+ try {
+ const result = await apiStatsClient.getOemSettings()
+ if (result && result.success && result.data) {
+ oemSettings.value = { ...oemSettings.value, ...result.data }
+ }
+ } catch (err) {
+ console.error('Error loading OEM settings:', err)
+ // 失败时使用默认值
+ oemSettings.value = {
+ siteName: 'Claude Relay Service',
+ siteIcon: '',
+ siteIconData: ''
+ }
+ } finally {
+ oemLoading.value = false
+ }
+ }
+
+ // 工具函数
+
+ // 格式化费用
+ function formatCost(cost) {
+ if (typeof cost !== 'number' || cost === 0) {
+ return '$0.000000'
+ }
+
+ // 根据数值大小选择精度
+ if (cost >= 1) {
+ return '$' + cost.toFixed(2)
+ } else if (cost >= 0.01) {
+ return '$' + cost.toFixed(4)
+ } else {
+ return '$' + cost.toFixed(6)
+ }
+ }
+
+ // 更新 URL
+ function updateURL() {
+ if (apiId.value) {
+ const url = new URL(window.location)
+ url.searchParams.set('apiId', apiId.value)
+ window.history.pushState({}, '', url)
+ }
+ }
+
+ // 清除数据
+ function clearData() {
+ statsData.value = null
+ modelStats.value = []
+ dailyStats.value = null
+ monthlyStats.value = null
+ error.value = ''
+ statsPeriod.value = 'daily'
+ apiId.value = null
+ }
+
+ // 重置
+ function reset() {
+ apiKey.value = ''
+ clearData()
+ }
+
+ return {
+ // State
+ apiKey,
+ apiId,
+ loading,
+ modelStatsLoading,
+ oemLoading,
+ error,
+ statsPeriod,
+ statsData,
+ modelStats,
+ dailyStats,
+ monthlyStats,
+ oemSettings,
+
+ // Computed
+ currentPeriodData,
+ usagePercentages,
+
+ // Actions
+ queryStats,
+ loadAllPeriodStats,
+ loadPeriodStats,
+ loadModelStats,
+ switchPeriod,
+ loadStatsWithApiId,
+ loadOemSettings,
+ clearData,
+ reset
+ }
+})
\ No newline at end of file
diff --git a/web/admin-spa/src/stores/auth.js b/web/admin-spa/src/stores/auth.js
new file mode 100644
index 00000000..bcd308b9
--- /dev/null
+++ b/web/admin-spa/src/stores/auth.js
@@ -0,0 +1,130 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+import router from '@/router'
+import { apiClient } from '@/config/api'
+
+export const useAuthStore = defineStore('auth', () => {
+ // 状态
+ const isLoggedIn = ref(false)
+ const authToken = ref(localStorage.getItem('authToken') || '')
+ const username = ref('')
+ const loginError = ref('')
+ const loginLoading = ref(false)
+ const oemSettings = ref({
+ siteName: 'Claude Relay Service',
+ siteIcon: '',
+ siteIconData: '',
+ faviconData: ''
+ })
+ const oemLoading = ref(true)
+
+ // 计算属性
+ const isAuthenticated = computed(() => !!authToken.value && isLoggedIn.value)
+ const token = computed(() => authToken.value)
+ const user = computed(() => ({ username: username.value }))
+
+ // 方法
+ async function login(credentials) {
+ loginLoading.value = true
+ loginError.value = ''
+
+ try {
+ const result = await apiClient.post('/web/auth/login', credentials)
+
+ if (result.success) {
+ authToken.value = result.token
+ username.value = credentials.username
+ isLoggedIn.value = true
+ localStorage.setItem('authToken', result.token)
+
+ await router.push('/dashboard')
+ } else {
+ loginError.value = result.message || '登录失败'
+ }
+ } catch (error) {
+ loginError.value = error.message || '登录失败,请检查用户名和密码'
+ } finally {
+ loginLoading.value = false
+ }
+ }
+
+ function logout() {
+ isLoggedIn.value = false
+ authToken.value = ''
+ username.value = ''
+ localStorage.removeItem('authToken')
+ router.push('/login')
+ }
+
+ function checkAuth() {
+ if (authToken.value) {
+ isLoggedIn.value = true
+ // 验证token有效性
+ verifyToken()
+ }
+ }
+
+ async function verifyToken() {
+ try {
+ // 使用 dashboard 端点来验证 token
+ // 如果 token 无效,会抛出错误
+ const result = await apiClient.get('/admin/dashboard')
+ if (!result.success) {
+ logout()
+ }
+ } catch (error) {
+ // token 无效,需要重新登录
+ logout()
+ }
+ }
+
+ async function loadOemSettings() {
+ oemLoading.value = true
+ try {
+ const result = await apiClient.get('/admin/oem-settings')
+ if (result.success && result.data) {
+ oemSettings.value = { ...oemSettings.value, ...result.data }
+
+ // 设置favicon
+ if (result.data.faviconData) {
+ const link = document.querySelector("link[rel*='icon']") || document.createElement('link')
+ link.type = 'image/x-icon'
+ link.rel = 'shortcut icon'
+ link.href = result.data.faviconData
+ document.getElementsByTagName('head')[0].appendChild(link)
+ }
+
+ // 设置页面标题
+ if (result.data.siteName) {
+ document.title = `${result.data.siteName} - 管理后台`
+ }
+ }
+ } catch (error) {
+ console.error('加载OEM设置失败:', error)
+ } finally {
+ oemLoading.value = false
+ }
+ }
+
+ return {
+ // 状态
+ isLoggedIn,
+ authToken,
+ username,
+ loginError,
+ loginLoading,
+ oemSettings,
+ oemLoading,
+
+ // 计算属性
+ isAuthenticated,
+ token,
+ user,
+
+ // 方法
+ login,
+ logout,
+ checkAuth,
+ loadOemSettings
+ }
+})
\ No newline at end of file
diff --git a/web/admin-spa/src/stores/clients.js b/web/admin-spa/src/stores/clients.js
new file mode 100644
index 00000000..7945154b
--- /dev/null
+++ b/web/admin-spa/src/stores/clients.js
@@ -0,0 +1,41 @@
+import { defineStore } from 'pinia'
+import { apiClient } from '@/config/api'
+
+export const useClientsStore = defineStore('clients', {
+ state: () => ({
+ supportedClients: [],
+ loading: false,
+ error: null
+ }),
+
+ actions: {
+ async loadSupportedClients() {
+ if (this.supportedClients.length > 0) {
+ // 如果已经加载过,不重复加载
+ return this.supportedClients
+ }
+
+ this.loading = true
+ this.error = null
+
+ try {
+ const response = await apiClient.get('/admin/supported-clients')
+
+ if (response.success) {
+ this.supportedClients = response.data || []
+ } else {
+ this.error = response.message || '加载支持的客户端失败'
+ console.error('Failed to load supported clients:', this.error)
+ }
+
+ return this.supportedClients
+ } catch (error) {
+ this.error = error.message || '加载支持的客户端失败'
+ console.error('Error loading supported clients:', error)
+ return []
+ } finally {
+ this.loading = false
+ }
+ }
+ }
+})
\ No newline at end of file
diff --git a/web/admin-spa/src/stores/dashboard.js b/web/admin-spa/src/stores/dashboard.js
new file mode 100644
index 00000000..4535b548
--- /dev/null
+++ b/web/admin-spa/src/stores/dashboard.js
@@ -0,0 +1,450 @@
+import { defineStore } from 'pinia'
+import { ref, computed } from 'vue'
+import { apiClient } from '@/config/api'
+import { showToast } from '@/utils/toast'
+
+export const useDashboardStore = defineStore('dashboard', () => {
+ // 状态
+ const loading = ref(false)
+ const dashboardData = ref({
+ totalApiKeys: 0,
+ activeApiKeys: 0,
+ totalAccounts: 0,
+ activeAccounts: 0,
+ rateLimitedAccounts: 0,
+ todayRequests: 0,
+ totalRequests: 0,
+ todayTokens: 0,
+ todayInputTokens: 0,
+ todayOutputTokens: 0,
+ totalTokens: 0,
+ totalInputTokens: 0,
+ totalOutputTokens: 0,
+ totalCacheCreateTokens: 0,
+ totalCacheReadTokens: 0,
+ todayCacheCreateTokens: 0,
+ todayCacheReadTokens: 0,
+ systemRPM: 0,
+ systemTPM: 0,
+ systemStatus: '正常',
+ uptime: 0
+ })
+
+ const costsData = ref({
+ todayCosts: { totalCost: 0, formatted: { totalCost: '$0.000000' } },
+ totalCosts: { totalCost: 0, formatted: { totalCost: '$0.000000' } }
+ })
+
+ const modelStats = ref([])
+ const trendData = ref([])
+ const dashboardModelStats = ref([])
+ const apiKeysTrendData = ref({
+ data: [],
+ topApiKeys: [],
+ totalApiKeys: 0
+ })
+
+ // 日期筛选
+ const dateFilter = ref({
+ type: 'preset', // preset 或 custom
+ preset: '7days', // today, 7days, 30days
+ customStart: '',
+ customEnd: '',
+ customRange: null,
+ presetOptions: [
+ { value: 'today', label: '今日', days: 1 },
+ { value: '7days', label: '7天', days: 7 },
+ { value: '30days', label: '30天', days: 30 }
+ ]
+ })
+
+ // 趋势图粒度
+ const trendGranularity = ref('day') // 'day' 或 'hour'
+ const apiKeysTrendMetric = ref('requests') // 'requests' 或 'tokens'
+
+ // 默认时间
+ const defaultTime = ref([new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 2, 1, 23, 59, 59)])
+
+ // 计算属性
+ const formattedUptime = computed(() => {
+ const seconds = dashboardData.value.uptime
+ const days = Math.floor(seconds / 86400)
+ const hours = Math.floor((seconds % 86400) / 3600)
+ const minutes = Math.floor((seconds % 3600) / 60)
+
+ if (days > 0) {
+ return `${days}天 ${hours}小时`
+ } else if (hours > 0) {
+ return `${hours}小时 ${minutes}分钟`
+ } else {
+ return `${minutes}分钟`
+ }
+ })
+
+ // 方法
+ async function loadDashboardData() {
+ loading.value = true
+ try {
+ const [dashboardResponse, todayCostsResponse, totalCostsResponse] = await Promise.all([
+ apiClient.get('/admin/dashboard'),
+ apiClient.get('/admin/usage-costs?period=today'),
+ apiClient.get('/admin/usage-costs?period=all')
+ ])
+
+ if (dashboardResponse.success) {
+ const overview = dashboardResponse.data.overview || {}
+ const recentActivity = dashboardResponse.data.recentActivity || {}
+ const systemAverages = dashboardResponse.data.systemAverages || {}
+ const systemHealth = dashboardResponse.data.systemHealth || {}
+
+ dashboardData.value = {
+ totalApiKeys: overview.totalApiKeys || 0,
+ activeApiKeys: overview.activeApiKeys || 0,
+ totalAccounts: overview.totalClaudeAccounts || 0,
+ activeAccounts: overview.activeClaudeAccounts || 0,
+ rateLimitedAccounts: overview.rateLimitedClaudeAccounts || 0,
+ todayRequests: recentActivity.requestsToday || 0,
+ totalRequests: overview.totalRequestsUsed || 0,
+ todayTokens: recentActivity.tokensToday || 0,
+ todayInputTokens: recentActivity.inputTokensToday || 0,
+ todayOutputTokens: recentActivity.outputTokensToday || 0,
+ totalTokens: overview.totalTokensUsed || 0,
+ totalInputTokens: overview.totalInputTokensUsed || 0,
+ totalOutputTokens: overview.totalOutputTokensUsed || 0,
+ totalCacheCreateTokens: overview.totalCacheCreateTokensUsed || 0,
+ totalCacheReadTokens: overview.totalCacheReadTokensUsed || 0,
+ todayCacheCreateTokens: recentActivity.cacheCreateTokensToday || 0,
+ todayCacheReadTokens: recentActivity.cacheReadTokensToday || 0,
+ systemRPM: systemAverages.rpm || 0,
+ systemTPM: systemAverages.tpm || 0,
+ systemStatus: systemHealth.redisConnected ? '正常' : '异常',
+ uptime: systemHealth.uptime || 0
+ }
+ }
+
+ // 更新费用数据
+ if (todayCostsResponse.success && totalCostsResponse.success) {
+ costsData.value = {
+ todayCosts: todayCostsResponse.data.totalCosts || { totalCost: 0, formatted: { totalCost: '$0.000000' } },
+ totalCosts: totalCostsResponse.data.totalCosts || { totalCost: 0, formatted: { totalCost: '$0.000000' } }
+ }
+ }
+ } catch (error) {
+ console.error('加载仪表板数据失败:', error)
+ } finally {
+ loading.value = false
+ }
+ }
+
+ async function loadUsageTrend(days = 7, granularity = 'day') {
+ try {
+ let url = '/admin/usage-trend?'
+
+ if (granularity === 'hour') {
+ // 小时粒度,传递开始和结束时间
+ url += `granularity=hour`
+ if (dateFilter.value.customRange && dateFilter.value.customRange.length === 2) {
+ url += `&startDate=${encodeURIComponent(dateFilter.value.customRange[0])}`
+ url += `&endDate=${encodeURIComponent(dateFilter.value.customRange[1])}`
+ }
+ } else {
+ // 天粒度,传递天数
+ url += `granularity=day&days=${days}`
+ }
+
+ const response = await apiClient.get(url)
+ if (response.success) {
+ trendData.value = response.data
+ }
+ } catch (error) {
+ console.error('加载使用趋势失败:', error)
+ }
+ }
+
+ async function loadModelStats(period = 'daily') {
+ try {
+ const response = await apiClient.get(`/admin/model-stats?period=${period}`)
+ if (response.success) {
+ dashboardModelStats.value = response.data
+ }
+ } catch (error) {
+ console.error('加载模型统计失败:', error)
+ }
+ }
+
+ async function loadApiKeysTrend(metric = 'requests') {
+ try {
+ let url = '/admin/api-keys-usage-trend?'
+
+ if (trendGranularity.value === 'hour') {
+ // 小时粒度,传递开始和结束时间
+ url += `granularity=hour`
+ if (dateFilter.value.customRange && dateFilter.value.customRange.length === 2) {
+ url += `&startDate=${encodeURIComponent(dateFilter.value.customRange[0])}`
+ url += `&endDate=${encodeURIComponent(dateFilter.value.customRange[1])}`
+ }
+ } else {
+ // 天粒度,传递天数
+ const days = dateFilter.value.type === 'preset'
+ ? (dateFilter.value.preset === 'today' ? 1 : dateFilter.value.preset === '7days' ? 7 : 30)
+ : calculateDaysBetween(dateFilter.value.customStart, dateFilter.value.customEnd)
+ url += `granularity=day&days=${days}`
+ }
+
+ url += `&metric=${metric}`
+
+ const response = await apiClient.get(url)
+ if (response.success) {
+ apiKeysTrendData.value = {
+ data: response.data || [],
+ topApiKeys: response.topApiKeys || [],
+ totalApiKeys: response.totalApiKeys || 0
+ }
+ }
+ } catch (error) {
+ console.error('加载API Keys趋势失败:', error)
+ }
+ }
+
+ // 日期筛选相关方法
+ function setDateFilterPreset(preset) {
+ dateFilter.value.type = 'preset'
+ dateFilter.value.preset = preset
+
+ // 根据预设计算并设置具体的日期范围
+ const option = dateFilter.value.presetOptions.find(opt => opt.value === preset)
+ if (option) {
+ const now = new Date()
+ let startDate, endDate
+
+ if (trendGranularity.value === 'hour') {
+ // 小时粒度的预设
+ switch (preset) {
+ case 'last24h':
+ startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000)
+ endDate = now
+ break
+ case 'yesterday':
+ startDate = new Date(now)
+ startDate.setDate(now.getDate() - 1)
+ startDate.setHours(0, 0, 0, 0)
+ endDate = new Date(startDate)
+ endDate.setHours(23, 59, 59, 999)
+ break
+ case 'dayBefore':
+ startDate = new Date(now)
+ startDate.setDate(now.getDate() - 2)
+ startDate.setHours(0, 0, 0, 0)
+ endDate = new Date(startDate)
+ endDate.setHours(23, 59, 59, 999)
+ break
+ }
+ } else {
+ // 天粒度的预设
+ startDate = new Date(now)
+ endDate = new Date(now)
+
+ if (preset === 'today') {
+ // 今日:从凌晨开始
+ startDate.setHours(0, 0, 0, 0)
+ endDate.setHours(23, 59, 59, 999)
+ } else {
+ // 其他预设:按天数计算
+ startDate.setDate(now.getDate() - (option.days - 1))
+ startDate.setHours(0, 0, 0, 0)
+ endDate.setHours(23, 59, 59, 999)
+ }
+ }
+
+ dateFilter.value.customStart = startDate.toISOString().split('T')[0]
+ dateFilter.value.customEnd = endDate.toISOString().split('T')[0]
+
+ // 设置 customRange 为 Element Plus 需要的格式
+ const formatDate = (date) => {
+ const year = date.getFullYear()
+ const month = String(date.getMonth() + 1).padStart(2, '0')
+ const day = String(date.getDate()).padStart(2, '0')
+ const hours = String(date.getHours()).padStart(2, '0')
+ const minutes = String(date.getMinutes()).padStart(2, '0')
+ const seconds = String(date.getSeconds()).padStart(2, '0')
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
+ }
+
+ dateFilter.value.customRange = [
+ formatDate(startDate),
+ formatDate(endDate)
+ ]
+ }
+
+ // 触发数据刷新
+ refreshChartsData()
+ }
+
+ function onCustomDateRangeChange(value) {
+ if (value && value.length === 2) {
+ dateFilter.value.type = 'custom'
+ dateFilter.value.preset = '' // 清除预设选择
+ dateFilter.value.customRange = value
+ dateFilter.value.customStart = value[0].split(' ')[0]
+ dateFilter.value.customEnd = value[1].split(' ')[0]
+
+ // 检查日期范围限制
+ const start = new Date(value[0])
+ const end = new Date(value[1])
+
+ if (trendGranularity.value === 'hour') {
+ // 小时粒度:限制 24 小时
+ const hoursDiff = (end - start) / (1000 * 60 * 60)
+ if (hoursDiff > 24) {
+ showToast('小时粒度下日期范围不能超过24小时', 'warning')
+ return
+ }
+ } else {
+ // 天粒度:限制 31 天
+ const daysDiff = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1
+ if (daysDiff > 31) {
+ showToast('日期范围不能超过 31 天', 'warning')
+ return
+ }
+ }
+
+ // 触发数据刷新
+ refreshChartsData()
+ } else if (value === null) {
+ // 清空时恢复默认
+ setDateFilterPreset(trendGranularity.value === 'hour' ? '7days' : '7days')
+ }
+ }
+
+ function setTrendGranularity(granularity) {
+ trendGranularity.value = granularity
+
+ // 根据粒度更新预设选项
+ if (granularity === 'hour') {
+ dateFilter.value.presetOptions = [
+ { value: 'last24h', label: '近24小时', hours: 24 },
+ { value: 'yesterday', label: '昨天', hours: 24 },
+ { value: 'dayBefore', label: '前天', hours: 24 }
+ ]
+
+ // 检查当前自定义日期范围是否超过24小时
+ if (dateFilter.value.type === 'custom' && dateFilter.value.customRange && dateFilter.value.customRange.length === 2) {
+ const start = new Date(dateFilter.value.customRange[0])
+ const end = new Date(dateFilter.value.customRange[1])
+ const hoursDiff = (end - start) / (1000 * 60 * 60)
+ if (hoursDiff > 24) {
+ showToast('小时粒度下日期范围不能超过24小时,已切换到近24小时', 'warning')
+ setDateFilterPreset('last24h')
+ return
+ }
+ }
+
+ // 如果当前是天粒度的预设,切换到小时粒度的默认预设
+ if (['today', '7days', '30days'].includes(dateFilter.value.preset)) {
+ setDateFilterPreset('last24h')
+ return
+ }
+ } else {
+ // 天粒度
+ dateFilter.value.presetOptions = [
+ { value: 'today', label: '今日', days: 1 },
+ { value: '7days', label: '7天', days: 7 },
+ { value: '30days', label: '30天', days: 30 }
+ ]
+
+ // 如果当前是小时粒度的预设,切换到天粒度的默认预设
+ if (['last24h', 'yesterday', 'dayBefore'].includes(dateFilter.value.preset)) {
+ setDateFilterPreset('7days')
+ return
+ }
+ }
+
+ // 触发数据刷新
+ refreshChartsData()
+ }
+
+ async function refreshChartsData() {
+ // 根据当前筛选条件刷新数据
+ let days
+ let modelPeriod = 'monthly'
+
+ if (dateFilter.value.type === 'preset') {
+ const option = dateFilter.value.presetOptions.find(opt => opt.value === dateFilter.value.preset)
+
+ if (trendGranularity.value === 'hour') {
+ // 小时粒度
+ days = 1 // 小时粒度默认查看1天的数据
+ modelPeriod = 'daily' // 小时粒度使用日统计
+ } else {
+ // 天粒度
+ days = option ? option.days : 7
+ // 设置模型统计期间
+ if (dateFilter.value.preset === 'today') {
+ modelPeriod = 'daily'
+ } else {
+ modelPeriod = 'monthly'
+ }
+ }
+ } else {
+ // 自定义日期范围
+ if (trendGranularity.value === 'hour') {
+ // 小时粒度下的自定义范围,计算小时数
+ const start = new Date(dateFilter.value.customRange[0])
+ const end = new Date(dateFilter.value.customRange[1])
+ const hoursDiff = Math.ceil((end - start) / (1000 * 60 * 60))
+ days = Math.ceil(hoursDiff / 24) || 1
+ } else {
+ days = calculateDaysBetween(dateFilter.value.customStart, dateFilter.value.customEnd)
+ }
+ modelPeriod = 'daily' // 自定义范围使用日统计
+ }
+
+ await Promise.all([
+ loadUsageTrend(days, trendGranularity.value),
+ loadModelStats(modelPeriod),
+ loadApiKeysTrend(apiKeysTrendMetric.value)
+ ])
+ }
+
+ function calculateDaysBetween(start, end) {
+ if (!start || !end) return 7
+ const startDate = new Date(start)
+ const endDate = new Date(end)
+ const diffTime = Math.abs(endDate - startDate)
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
+ return diffDays || 7
+ }
+
+ function disabledDate(date) {
+ return date > new Date()
+ }
+
+ return {
+ // 状态
+ loading,
+ dashboardData,
+ costsData,
+ modelStats,
+ trendData,
+ dashboardModelStats,
+ apiKeysTrendData,
+ dateFilter,
+ trendGranularity,
+ apiKeysTrendMetric,
+ defaultTime,
+
+ // 计算属性
+ formattedUptime,
+
+ // 方法
+ loadDashboardData,
+ loadUsageTrend,
+ loadModelStats,
+ loadApiKeysTrend,
+ setDateFilterPreset,
+ onCustomDateRangeChange,
+ setTrendGranularity,
+ refreshChartsData,
+ disabledDate
+ }
+})
\ No newline at end of file
diff --git a/web/admin-spa/src/stores/settings.js b/web/admin-spa/src/stores/settings.js
new file mode 100644
index 00000000..879bfbdb
--- /dev/null
+++ b/web/admin-spa/src/stores/settings.js
@@ -0,0 +1,151 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+import { apiClient } from '@/config/api'
+
+export const useSettingsStore = defineStore('settings', () => {
+ // 状态
+ const oemSettings = ref({
+ siteName: 'Claude Relay Service',
+ siteIcon: '',
+ siteIconData: '',
+ updatedAt: null
+ })
+
+ const loading = ref(false)
+ const saving = ref(false)
+
+ // 移除自定义API请求方法,使用统一的apiClient
+
+ // Actions
+ const loadOemSettings = async () => {
+ loading.value = true
+ try {
+ const result = await apiClient.get('/admin/oem-settings')
+
+ if (result && result.success) {
+ oemSettings.value = { ...oemSettings.value, ...result.data }
+
+ // 应用设置到页面
+ applyOemSettings()
+ }
+
+ return result
+ } catch (error) {
+ console.error('Failed to load OEM settings:', error)
+ throw error
+ } finally {
+ loading.value = false
+ }
+ }
+
+ const saveOemSettings = async (settings) => {
+ saving.value = true
+ try {
+ const result = await apiClient.put('/admin/oem-settings', settings)
+
+ if (result && result.success) {
+ oemSettings.value = { ...oemSettings.value, ...result.data }
+
+ // 应用设置到页面
+ applyOemSettings()
+ }
+
+ return result
+ } catch (error) {
+ console.error('Failed to save OEM settings:', error)
+ throw error
+ } finally {
+ saving.value = false
+ }
+ }
+
+ const resetOemSettings = async () => {
+ const defaultSettings = {
+ siteName: 'Claude Relay Service',
+ siteIcon: '',
+ siteIconData: '',
+ updatedAt: null
+ }
+
+ oemSettings.value = { ...defaultSettings }
+ return await saveOemSettings(defaultSettings)
+ }
+
+ // 应用OEM设置到页面
+ const applyOemSettings = () => {
+ // 更新页面标题
+ if (oemSettings.value.siteName) {
+ document.title = `${oemSettings.value.siteName} - 管理后台`
+ }
+
+ // 更新favicon
+ if (oemSettings.value.siteIconData || oemSettings.value.siteIcon) {
+ const favicon = document.querySelector('link[rel="icon"]') || document.createElement('link')
+ favicon.rel = 'icon'
+ favicon.href = oemSettings.value.siteIconData || oemSettings.value.siteIcon
+ if (!document.querySelector('link[rel="icon"]')) {
+ document.head.appendChild(favicon)
+ }
+ }
+ }
+
+ // 格式化日期时间
+ const formatDateTime = (dateString) => {
+ if (!dateString) return ''
+ return new Date(dateString).toLocaleString('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit'
+ })
+ }
+
+ // 验证文件上传
+ const validateIconFile = (file) => {
+ const errors = []
+
+ // 检查文件大小 (350KB)
+ if (file.size > 350 * 1024) {
+ errors.push('图标文件大小不能超过 350KB')
+ }
+
+ // 检查文件类型
+ const allowedTypes = ['image/x-icon', 'image/png', 'image/jpeg', 'image/jpg', 'image/svg+xml']
+ if (!allowedTypes.includes(file.type)) {
+ errors.push('不支持的文件类型,请选择 .ico, .png, .jpg 或 .svg 文件')
+ }
+
+ return {
+ isValid: errors.length === 0,
+ errors
+ }
+ }
+
+ // 将文件转换为Base64
+ const fileToBase64 = (file) => {
+ return new Promise((resolve, reject) => {
+ const reader = new FileReader()
+ reader.onload = (e) => resolve(e.target.result)
+ reader.onerror = reject
+ reader.readAsDataURL(file)
+ })
+ }
+
+ return {
+ // State
+ oemSettings,
+ loading,
+ saving,
+
+ // Actions
+ loadOemSettings,
+ saveOemSettings,
+ resetOemSettings,
+ applyOemSettings,
+ formatDateTime,
+ validateIconFile,
+ fileToBase64
+ }
+})
\ No newline at end of file
diff --git a/web/admin-spa/src/utils/format.js b/web/admin-spa/src/utils/format.js
new file mode 100644
index 00000000..1d86a44a
--- /dev/null
+++ b/web/admin-spa/src/utils/format.js
@@ -0,0 +1,74 @@
+// 数字格式化函数
+export function formatNumber(num) {
+ if (num === null || num === undefined) return '0'
+
+ const absNum = Math.abs(num)
+
+ if (absNum >= 1e9) {
+ return (num / 1e9).toFixed(2) + 'B'
+ } else if (absNum >= 1e6) {
+ return (num / 1e6).toFixed(2) + 'M'
+ } else if (absNum >= 1e3) {
+ return (num / 1e3).toFixed(1) + 'K'
+ }
+
+ return num.toLocaleString()
+}
+
+// 日期格式化函数
+export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') {
+ if (!date) return ''
+
+ const d = new Date(date)
+
+ const year = d.getFullYear()
+ const month = String(d.getMonth() + 1).padStart(2, '0')
+ const day = String(d.getDate()).padStart(2, '0')
+ const hours = String(d.getHours()).padStart(2, '0')
+ const minutes = String(d.getMinutes()).padStart(2, '0')
+ const seconds = String(d.getSeconds()).padStart(2, '0')
+
+ return format
+ .replace('YYYY', year)
+ .replace('MM', month)
+ .replace('DD', day)
+ .replace('HH', hours)
+ .replace('mm', minutes)
+ .replace('ss', seconds)
+}
+
+// 相对时间格式化
+export function formatRelativeTime(date) {
+ if (!date) return ''
+
+ const now = new Date()
+ const past = new Date(date)
+ const diffMs = now - past
+ const diffSecs = Math.floor(diffMs / 1000)
+ const diffMins = Math.floor(diffSecs / 60)
+ const diffHours = Math.floor(diffMins / 60)
+ const diffDays = Math.floor(diffHours / 24)
+
+ if (diffDays > 0) {
+ return `${diffDays}天前`
+ } else if (diffHours > 0) {
+ return `${diffHours}小时前`
+ } else if (diffMins > 0) {
+ return `${diffMins}分钟前`
+ } else {
+ return '刚刚'
+ }
+}
+
+// 字节格式化
+export function formatBytes(bytes, decimals = 2) {
+ if (bytes === 0) return '0 Bytes'
+
+ const k = 1024
+ const dm = decimals < 0 ? 0 : decimals
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
+
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
+
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]
+}
\ No newline at end of file
diff --git a/web/admin-spa/src/utils/toast.js b/web/admin-spa/src/utils/toast.js
new file mode 100644
index 00000000..5b76bc45
--- /dev/null
+++ b/web/admin-spa/src/utils/toast.js
@@ -0,0 +1,68 @@
+// Toast 通知管理
+let toastContainer = null
+let toastId = 0
+
+export function showToast(message, type = 'info', title = '', duration = 3000) {
+ // 创建容器
+ if (!toastContainer) {
+ toastContainer = document.createElement('div')
+ toastContainer.id = 'toast-container'
+ toastContainer.style.cssText = 'position: fixed; top: 20px; right: 20px; z-index: 10000;'
+ document.body.appendChild(toastContainer)
+ }
+
+ // 创建 toast
+ const id = ++toastId
+ const toast = document.createElement('div')
+ toast.className = `toast rounded-2xl p-4 shadow-2xl backdrop-blur-sm toast-${type}`
+ toast.style.cssText = `
+ position: relative;
+ min-width: 320px;
+ max-width: 500px;
+ margin-bottom: 16px;
+ transform: translateX(100%);
+ transition: transform 0.3s ease-in-out;
+ `
+
+ const iconMap = {
+ success: 'fas fa-check-circle',
+ error: 'fas fa-times-circle',
+ warning: 'fas fa-exclamation-triangle',
+ info: 'fas fa-info-circle'
+ }
+
+ toast.innerHTML = `
+
+
+
+
+
+ ${title ? `
${title} ` : ''}
+
${message}
+
+
+
+
+
+ `
+
+ toastContainer.appendChild(toast)
+
+ // 触发动画
+ setTimeout(() => {
+ toast.style.transform = 'translateX(0)'
+ }, 10)
+
+ // 自动移除
+ if (duration > 0) {
+ setTimeout(() => {
+ toast.style.transform = 'translateX(100%)'
+ setTimeout(() => {
+ toast.remove()
+ }, 300)
+ }, duration)
+ }
+
+ return id
+}
\ No newline at end of file
diff --git a/web/admin-spa/src/views/ApiKeysView.vue b/web/admin-spa/src/views/ApiKeysView.vue
new file mode 100644
index 00000000..dbbd83e0
--- /dev/null
+++ b/web/admin-spa/src/views/ApiKeysView.vue
@@ -0,0 +1,923 @@
+
+
+
+
+
+
API Keys 管理
+
管理和监控您的 API 密钥
+
+
+
+
+ 今日
+ 最近7天
+ 本月
+ 全部时间
+
+
+ 创建新 Key
+
+
+
+
+
+
+
+
+
+
+
暂无 API Keys
+
点击上方按钮创建您的第一个 API Key
+
+
+
+
+
+
+
+ 名称
+
+
+
+ API Key
+
+ 状态
+
+
+
+
+ 使用统计
+
+ (费用
+
+ )
+
+
+
+ 创建时间
+
+
+
+
+ 过期时间
+
+
+
+ 操作
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ key.name }}
+
{{ key.id }}
+
+
+
+ 绑定: {{ getBoundAccountName(key.claudeAccountId) }}
+
+
+
+ 使用共享池
+
+
+
+
+
+
+
+ {{ (key.apiKey || '').substring(0, 20) }}...
+
+
+
+
+
+ {{ key.isActive ? '活跃' : '禁用' }}
+
+
+
+
+
+
+ 请求数:
+ {{ formatNumber((key.usage && key.usage.total && key.usage.total.requests) || 0) }}
+
+
+
+ Token:
+ {{ formatNumber((key.usage && key.usage.total && key.usage.total.tokens) || 0) }}
+
+
+
+ 费用:
+ {{ calculateApiKeyCost(key.usage) }}
+
+
+
+ 今日费用:
+
+ ${{ (key.dailyCost || 0).toFixed(2) }} / ${{ key.dailyCostLimit.toFixed(2) }}
+
+
+
+
+ 并发限制:
+ {{ key.concurrencyLimit > 0 ? key.concurrencyLimit : '无限制' }}
+
+
+
+ 当前并发:
+
+ {{ key.currentConcurrency || 0 }}
+ / {{ key.concurrencyLimit }}
+
+
+
+
+ 时间窗口:
+ {{ key.rateLimitWindow }} 分钟
+
+
+
+ 请求限制:
+ {{ key.rateLimitRequests }} 次/窗口
+
+
+
+ 输入: {{ formatNumber((key.usage && key.usage.total && key.usage.total.inputTokens) || 0) }}
+ 输出: {{ formatNumber((key.usage && key.usage.total && key.usage.total.outputTokens) || 0) }}
+
+
+
+ 缓存创建: {{ formatNumber((key.usage && key.usage.total && key.usage.total.cacheCreateTokens) || 0) }}
+ 缓存读取: {{ formatNumber((key.usage && key.usage.total && key.usage.total.cacheReadTokens) || 0) }}
+
+
+
+ RPM: {{ (key.usage && key.usage.averages && key.usage.averages.rpm) || 0 }}
+ TPM: {{ (key.usage && key.usage.averages && key.usage.averages.tpm) || 0 }}
+
+
+
+
+ 今日: {{ formatNumber((key.usage && key.usage.daily && key.usage.daily.requests) || 0) }}次
+ {{ formatNumber((key.usage && key.usage.daily && key.usage.daily.tokens) || 0) }}T
+
+
+
+
+
+
+ 模型使用分布
+
+
+
+
+
+ {{ new Date(key.createdAt).toLocaleDateString() }}
+
+
+
+
+
+ 已过期
+
+
+
+ {{ formatExpireDate(key.expiresAt) }}
+
+
+ {{ formatExpireDate(key.expiresAt) }}
+
+
+
+
+ 永不过期
+
+
+
+
+
+ 统计
+
+
+ 编辑
+
+
+ 续期
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 模型使用分布
+
+
+
+ {{ apiKeyModelStats[key.id].length }} 个模型
+
+
+
+
+
+
+
+ {{ option.label }}
+
+
+
+
+
onApiKeyCustomDateRangeChange(key.id, value)"
+ type="datetimerange"
+ range-separator="至"
+ start-placeholder="开始日期"
+ end-placeholder="结束日期"
+ format="YYYY-MM-DD HH:mm:ss"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ :disabled-date="disabledDate"
+ :default-time="defaultTime"
+ size="small"
+ style="width: 280px;"
+ class="api-key-date-picker"
+ :clearable="true"
+ :unlink-panels="false"
+ >
+
+
+
+
+
+
+
+
+
暂无模型使用数据
+
+
+ 刷新
+
+
+
尝试调整时间范围或点击刷新重新加载数据
+
+
+
+
+
+ {{ stat.model }}
+ {{ stat.requests }} 次请求
+
+
+
+
+
+
+
+ 总Token:
+
+ {{ formatNumber(stat.allTokens) }}
+
+
+
+
+ 费用:
+
+ {{ calculateModelCost(stat) }}
+
+
+
+
+
+ 输入:
+
+ {{ formatNumber(stat.inputTokens) }}
+
+
+
+
+ 输出:
+
+ {{ formatNumber(stat.outputTokens) }}
+
+
+
+
+ 缓存创建:
+
+ {{ formatNumber(stat.cacheCreateTokens) }}
+
+
+
+
+ 缓存读取:
+
+ {{ formatNumber(stat.cacheReadTokens) }}
+
+
+
+
+
+
+
+
+ {{ calculateApiKeyModelPercentage(stat.allTokens, apiKeyModelStats[key.id]) }}%
+
+
+
+
+
+
+
+
+
+
+ 总计统计
+
+
+
+ 总请求: {{ apiKeyModelStats[key.id].reduce((sum, stat) => sum + stat.requests, 0) }}
+
+
+ 总Token: {{ formatNumber(apiKeyModelStats[key.id].reduce((sum, stat) => sum + stat.allTokens, 0)) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/views/ApiStatsView.vue b/web/admin-spa/src/views/ApiStatsView.vue
new file mode 100644
index 00000000..c83074bd
--- /dev/null
+++ b/web/admin-spa/src/views/ApiStatsView.vue
@@ -0,0 +1,384 @@
+
+
+
+
+
+
+
+
+
+
+
+ 统计查询
+
+
+
+ 使用教程
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 统计时间范围
+
+
+
+
+ 今日
+
+
+
+ 本月
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/views/DashboardView.vue b/web/admin-spa/src/views/DashboardView.vue
new file mode 100644
index 00000000..38cc0e2d
--- /dev/null
+++ b/web/admin-spa/src/views/DashboardView.vue
@@ -0,0 +1,744 @@
+
+
+
+
+
+
+
+
总API Keys
+
{{ dashboardData.totalApiKeys }}
+
活跃: {{ dashboardData.activeApiKeys || 0 }}
+
+
+
+
+
+
+
+
+
+
+
服务账户
+
{{ dashboardData.totalAccounts }}
+
+ 活跃: {{ dashboardData.activeAccounts || 0 }}
+
+ | 限流: {{ dashboardData.rateLimitedAccounts }}
+
+
+
+
+
+
+
+
+
+
+
+
+
今日请求
+
{{ dashboardData.todayRequests }}
+
总请求: {{ formatNumber(dashboardData.totalRequests || 0) }}
+
+
+
+
+
+
+
+
+
+
+
系统状态
+
{{ dashboardData.systemStatus }}
+
运行时间: {{ formattedUptime }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
今日Token
+
+
{{ formatNumber((dashboardData.todayInputTokens || 0) + (dashboardData.todayOutputTokens || 0) + (dashboardData.todayCacheCreateTokens || 0) + (dashboardData.todayCacheReadTokens || 0)) }}
+
/ {{ costsData.todayCosts.formatted.totalCost }}
+
+
+
+ 输入: {{ formatNumber(dashboardData.todayInputTokens || 0) }}
+ 输出: {{ formatNumber(dashboardData.todayOutputTokens || 0) }}
+ 缓存创建: {{ formatNumber(dashboardData.todayCacheCreateTokens || 0) }}
+ 缓存读取: {{ formatNumber(dashboardData.todayCacheReadTokens || 0) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
总Token消耗
+
+
{{ formatNumber((dashboardData.totalInputTokens || 0) + (dashboardData.totalOutputTokens || 0) + (dashboardData.totalCacheCreateTokens || 0) + (dashboardData.totalCacheReadTokens || 0)) }}
+
/ {{ costsData.totalCosts.formatted.totalCost }}
+
+
+
+ 输入: {{ formatNumber(dashboardData.totalInputTokens || 0) }}
+ 输出: {{ formatNumber(dashboardData.totalOutputTokens || 0) }}
+ 缓存创建: {{ formatNumber(dashboardData.totalCacheCreateTokens || 0) }}
+ 缓存读取: {{ formatNumber(dashboardData.totalCacheReadTokens || 0) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
平均RPM
+
{{ dashboardData.systemRPM || 0 }}
+
每分钟请求数
+
+
+
+
+
+
+
+
+
+
+
平均TPM
+
{{ dashboardData.systemTPM || 0 }}
+
每分钟Token数
+
+
+
+
+
+
+
+
+
+
+
+
模型使用分布与Token使用趋势
+
+
+
+
+ {{ option.label }}
+
+
+
+
+
+
+ 按天
+
+
+ 按小时
+
+
+
+
+
+
+
+ 最多24小时
+
+
+
+
+ 刷新
+
+
+
+
+
+
+
+
+
+
+
详细统计数据
+
+
+
+
+
+ 模型
+ 请求数
+ 总Token
+ 费用
+ 占比
+
+
+
+
+ {{ stat.model }}
+ {{ formatNumber(stat.requests) }}
+ {{ formatNumber(stat.allTokens) }}
+ {{ stat.formatted ? stat.formatted.total : '$0.000000' }}
+
+
+ {{ calculatePercentage(stat.allTokens, dashboardModelStats) }}%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
API Keys 使用趋势
+
+
+
+ 请求次数
+
+
+ Token 数量
+
+
+
+
+
+ 共 {{ apiKeysTrendData.totalApiKeys }} 个 API Key,显示使用量前 10 个
+
+
+ 共 {{ apiKeysTrendData.totalApiKeys }} 个 API Key
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/views/LoginView.vue b/web/admin-spa/src/views/LoginView.vue
new file mode 100644
index 00000000..8753a62b
--- /dev/null
+++ b/web/admin-spa/src/views/LoginView.vue
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+ e.target.style.display = 'none'">
+
+
+
+
+
+
+
+
+
管理后台
+
+
+
+
+ 用户名
+
+
+
+
+ 密码
+
+
+
+
+
+
+ {{ authStore.loginLoading ? '登录中...' : '登录' }}
+
+
+
+
+ {{ authStore.loginError }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/views/SettingsView.vue b/web/admin-spa/src/views/SettingsView.vue
new file mode 100644
index 00000000..a5685f1f
--- /dev/null
+++ b/web/admin-spa/src/views/SettingsView.vue
@@ -0,0 +1,279 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 将显示在浏览器标题和页面头部
+
+
+
+
+
+
+
+
+
+
+
+
+
+
当前图标
+
+ 删除
+
+
+
+
+
+
+
+
+ 上传图标
+
+ 支持 .ico, .png, .jpg, .svg 格式,最大 350KB
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ saving ? '保存中...' : '保存设置' }}
+
+
+
+
+ 重置为默认
+
+
+
+
+
+ 最后更新:{{ formatDateTime(oemSettings.updatedAt) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/src/views/TutorialView.vue b/web/admin-spa/src/views/TutorialView.vue
new file mode 100644
index 00000000..f402c94b
--- /dev/null
+++ b/web/admin-spa/src/views/TutorialView.vue
@@ -0,0 +1,828 @@
+
+
+
+
+
+ Claude Code 使用教程
+
+
跟着这个教程,你可以轻松在自己的电脑上安装并使用 Claude Code。
+
+
+
+
+
+
+
+ {{ system.name }}
+
+
+
+
+
+
+
+
+
+ 1
+ 安装 Node.js 环境
+
+
Claude Code 需要 Node.js 环境才能运行。
+
+
+
+
+ Windows 安装方法
+
+
+
方法一:官网下载(推荐)
+
+ 打开浏览器访问 https://nodejs.org/
+ 点击 "LTS" 版本进行下载(推荐长期支持版本)
+ 下载完成后双击 .msi 文件
+ 按照安装向导完成安装,保持默认设置即可
+
+
+
+
方法二:使用包管理器
+
如果你安装了 Chocolatey 或 Scoop,可以使用命令行安装:
+
+
# 使用 Chocolatey
+
choco install nodejs
+
# 或使用 Scoop
+
scoop install nodejs
+
+
+
+
Windows 注意事项
+
+ • 建议使用 PowerShell 而不是 CMD
+ • 如果遇到权限问题,尝试以管理员身份运行
+ • 某些杀毒软件可能会误报,需要添加白名单
+
+
+
+
+
+
+
验证安装是否成功
+
安装完成后,打开 PowerShell 或 CMD,输入以下命令:
+
+
node --version
+
npm --version
+
+
如果显示版本号,说明安装成功了!
+
+
+
+
+
+
+ 2
+ 安装 Git Bash
+
+
Windows 环境下需要使用 Git Bash 安装Claude code。安装完成后,环境变量设置和使用 Claude Code 仍然在普通的 PowerShell 或 CMD 中进行。
+
+
+
+
+ 下载并安装 Git for Windows
+
+
+ 访问 https://git-scm.com/downloads/win
+ 点击 "Download for Windows" 下载安装包
+ 运行下载的 .exe 安装文件
+ 在安装过程中保持默认设置,直接点击 "Next" 完成安装
+
+
+
安装完成后
+
+ • 在任意文件夹右键可以看到 "Git Bash Here" 选项
+ • 也可以从开始菜单启动 "Git Bash"
+ • 只需要在 Git Bash 中运行 npm install 命令
+ • 后续的环境变量设置和使用都在 PowerShell/CMD 中
+
+
+
+
+
+
+
验证 Git Bash 安装
+
打开 Git Bash,输入以下命令验证:
+
+
如果显示 Git 版本号,说明安装成功!
+
+
+
+
+
+
+ 3
+ 安装 Claude Code
+
+
+
+
+
+ 安装 Claude Code
+
+
打开 Git Bash(重要:不要使用 PowerShell),运行以下命令:
+
+
# 在 Git Bash 中全局安装 Claude Code
+
npm install -g @anthropic-ai/claude-code
+
+
这个命令会从 npm 官方仓库下载并安装最新版本的 Claude Code。
+
+
+
重要提醒
+
+ • 必须在 Git Bash 中运行,不要在 PowerShell 中运行
+ • 如果遇到权限问题,可以尝试在 Git Bash 中使用 sudo 命令
+
+
+
+
+
+
+
验证 Claude Code 安装
+
安装完成后,输入以下命令检查是否安装成功:
+
+
如果显示版本号,恭喜你!Claude Code 已经成功安装了。
+
+
+
+
+
+
+ 4
+ 设置环境变量
+
+
+
+
+
+ 配置 Claude Code 环境变量
+
+
为了让 Claude Code 连接到你的中转服务,需要设置两个环境变量:
+
+
+
+
方法一:PowerShell 临时设置(推荐)
+
在 PowerShell 中运行以下命令:
+
+
$env:ANTHROPIC_BASE_URL = "{{ currentBaseUrl }}"
+
$env:ANTHROPIC_AUTH_TOKEN = "你的API密钥"
+
+
💡 记得将 "你的API密钥" 替换为在上方 "API Keys" 标签页中创建的实际密钥。
+
+
+
+
方法二:系统环境变量(永久设置)
+
+ 右键"此电脑" → "属性" → "高级系统设置"
+ 点击"环境变量"按钮
+ 在"用户变量"或"系统变量"中点击"新建"
+ 添加以下两个变量:
+
+
+
+ 变量名: ANTHROPIC_BASE_URL
+ 变量值: {{ currentBaseUrl }}
+
+
+ 变量名: ANTHROPIC_AUTH_TOKEN
+ 变量值: 你的API密钥
+
+
+
+
+
+
+
+
+
验证环境变量设置
+
设置完环境变量后,可以通过以下命令验证是否设置成功:
+
+
+
+
在 PowerShell 中验证:
+
+
echo $env:ANTHROPIC_BASE_URL
+
echo $env:ANTHROPIC_AUTH_TOKEN
+
+
+
+
+
在 CMD 中验证:
+
+
echo %ANTHROPIC_BASE_URL%
+
echo %ANTHROPIC_AUTH_TOKEN%
+
+
+
+
+
+
+ 预期输出示例:
+
+
+
{{ currentBaseUrl }}
+
cr_xxxxxxxxxxxxxxxxxx
+
+
+ 💡 如果输出为空或显示变量名本身,说明环境变量设置失败,请重新设置。
+
+
+
+
+
+
+
+
+ 5
+ 开始使用 Claude Code
+
+
+
现在你可以开始使用 Claude Code 了!
+
+
+
+
+
+
在特定项目中使用
+
+
# 进入你的项目目录
+
cd C:\path\to\your\project
+
# 启动 Claude Code
+
claude
+
+
+
+
+
+
+
+
+
+
+ Windows 常见问题解决
+
+
+
+
+ 安装时提示 "permission denied" 错误
+
+
+
这通常是权限问题,尝试以下解决方法:
+
+ 以管理员身份运行 PowerShell
+ 或者配置 npm 使用用户目录:npm config set prefix %APPDATA%\npm
+
+
+
+
+
+
+ PowerShell 执行策略错误
+
+
+
如果遇到执行策略限制,运行:
+
+
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
+
+
+
+
+
+
+ 环境变量设置后不生效
+
+
+
设置永久环境变量后需要:
+
+ 重新启动 PowerShell 或 CMD
+ 或者注销并重新登录 Windows
+ 验证设置:echo $env:ANTHROPIC_BASE_URL
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 安装 Node.js 环境
+
+
Claude Code 需要 Node.js 环境才能运行。
+
+
+
+
+ macOS 安装方法
+
+
+
方法一:使用 Homebrew(推荐)
+
如果你已经安装了 Homebrew,使用它安装 Node.js 会更方便:
+
+
# 更新 Homebrew
+
brew update
+
# 安装 Node.js
+
brew install node
+
+
+
+
方法二:官网下载
+
+ 访问 https://nodejs.org/
+ 下载适合 macOS 的 LTS 版本
+ 打开下载的 .pkg 文件
+ 按照安装程序指引完成安装
+
+
+
+
macOS 注意事项
+
+ • 如果遇到权限问题,可能需要使用 sudo
+ • 首次运行可能需要在系统偏好设置中允许
+ • 建议使用 Terminal 或 iTerm2
+
+
+
+
+
+
+
验证安装是否成功
+
安装完成后,打开 Terminal,输入以下命令:
+
+
node --version
+
npm --version
+
+
如果显示版本号,说明安装成功了!
+
+
+
+
+
+
+ 2
+ 安装 Claude Code
+
+
+
+
+
+ 安装 Claude Code
+
+
打开 Terminal,运行以下命令:
+
+
# 全局安装 Claude Code
+
npm install -g @anthropic-ai/claude-code
+
+
如果遇到权限问题,可以使用 sudo:
+
+
sudo npm install -g @anthropic-ai/claude-code
+
+
+
+
+
+
验证 Claude Code 安装
+
安装完成后,输入以下命令检查是否安装成功:
+
+
如果显示版本号,恭喜你!Claude Code 已经成功安装了。
+
+
+
+
+
+
+ 3
+ 设置环境变量
+
+
+
+
+
+ 配置 Claude Code 环境变量
+
+
为了让 Claude Code 连接到你的中转服务,需要设置两个环境变量:
+
+
+
+
方法一:临时设置(当前会话)
+
在 Terminal 中运行以下命令:
+
+
export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"
+
export ANTHROPIC_AUTH_TOKEN="你的API密钥"
+
+
💡 记得将 "你的API密钥" 替换为在上方 "API Keys" 标签页中创建的实际密钥。
+
+
+
+
方法二:永久设置
+
编辑你的 shell 配置文件(根据你使用的 shell):
+
+
# 对于 zsh (默认)
+
echo 'export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"' >> ~/.zshrc
+
echo 'export ANTHROPIC_AUTH_TOKEN="你的API密钥"' >> ~/.zshrc
+
source ~/.zshrc
+
+
+
# 对于 bash
+
echo 'export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"' >> ~/.bash_profile
+
echo 'export ANTHROPIC_AUTH_TOKEN="你的API密钥"' >> ~/.bash_profile
+
source ~/.bash_profile
+
+
+
+
+
+
+
+
+
+ 4
+ 开始使用 Claude Code
+
+
+
现在你可以开始使用 Claude Code 了!
+
+
+
+
+
+
在特定项目中使用
+
+
# 进入你的项目目录
+
cd /path/to/your/project
+
# 启动 Claude Code
+
claude
+
+
+
+
+
+
+
+
+
+
+ macOS 常见问题解决
+
+
+
+
+ 安装时提示权限错误
+
+
+
尝试以下解决方法:
+
+ 使用 sudo 安装:sudo npm install -g @anthropic-ai/claude-code
+ 或者配置 npm 使用用户目录:npm config set prefix ~/.npm-global
+
+
+
+
+
+
+ macOS 安全设置阻止运行
+
+
+
如果系统阻止运行 Claude Code:
+
+ 打开"系统偏好设置" → "安全性与隐私"
+ 点击"仍要打开"或"允许"
+ 或者在 Terminal 中运行:sudo spctl --master-disable
+
+
+
+
+
+
+ 环境变量不生效
+
+
+
检查以下几点:
+
+ 确认修改了正确的配置文件(.zshrc 或 .bash_profile)
+ 重新启动 Terminal
+ 验证设置:echo $ANTHROPIC_BASE_URL
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 安装 Node.js 环境
+
+
Claude Code 需要 Node.js 环境才能运行。
+
+
+
+
+ Linux 安装方法
+
+
+
方法一:使用官方仓库(推荐)
+
+
# 添加 NodeSource 仓库
+
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
+
# 安装 Node.js
+
sudo apt-get install -y nodejs
+
+
+
+
方法二:使用系统包管理器
+
虽然版本可能不是最新的,但对于基本使用已经足够:
+
+
# Ubuntu/Debian
+
sudo apt update
+
sudo apt install nodejs npm
+
# CentOS/RHEL/Fedora
+
sudo dnf install nodejs npm
+
+
+
+
Linux 注意事项
+
+ • 某些发行版可能需要安装额外的依赖
+ • 如果遇到权限问题,使用 sudo
+ • 确保你的用户在 npm 的全局目录有写权限
+
+
+
+
+
+
+
验证安装是否成功
+
安装完成后,打开终端,输入以下命令:
+
+
node --version
+
npm --version
+
+
如果显示版本号,说明安装成功了!
+
+
+
+
+
+
+ 2
+ 安装 Claude Code
+
+
+
+
+
+ 安装 Claude Code
+
+
打开终端,运行以下命令:
+
+
# 全局安装 Claude Code
+
npm install -g @anthropic-ai/claude-code
+
+
如果遇到权限问题,可以使用 sudo:
+
+
sudo npm install -g @anthropic-ai/claude-code
+
+
+
+
+
+
验证 Claude Code 安装
+
安装完成后,输入以下命令检查是否安装成功:
+
+
如果显示版本号,恭喜你!Claude Code 已经成功安装了。
+
+
+
+
+
+
+ 3
+ 设置环境变量
+
+
+
+
+
+ 配置 Claude Code 环境变量
+
+
为了让 Claude Code 连接到你的中转服务,需要设置两个环境变量:
+
+
+
+
方法一:临时设置(当前会话)
+
在终端中运行以下命令:
+
+
export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"
+
export ANTHROPIC_AUTH_TOKEN="你的API密钥"
+
+
💡 记得将 "你的API密钥" 替换为在上方 "API Keys" 标签页中创建的实际密钥。
+
+
+
+
方法二:永久设置
+
编辑你的 shell 配置文件:
+
+
# 对于 bash (默认)
+
echo 'export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"' >> ~/.bashrc
+
echo 'export ANTHROPIC_AUTH_TOKEN="你的API密钥"' >> ~/.bashrc
+
source ~/.bashrc
+
+
+
# 对于 zsh
+
echo 'export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"' >> ~/.zshrc
+
echo 'export ANTHROPIC_AUTH_TOKEN="你的API密钥"' >> ~/.zshrc
+
source ~/.zshrc
+
+
+
+
+
+
+
+
+
+ 4
+ 开始使用 Claude Code
+
+
+
现在你可以开始使用 Claude Code 了!
+
+
+
+
+
+
在特定项目中使用
+
+
# 进入你的项目目录
+
cd /path/to/your/project
+
# 启动 Claude Code
+
claude
+
+
+
+
+
+
+
+
+
+
+ Linux 常见问题解决
+
+
+
+
+ 安装时提示权限错误
+
+
+
尝试以下解决方法:
+
+ 使用 sudo 安装:sudo npm install -g @anthropic-ai/claude-code
+ 或者配置 npm 使用用户目录:npm config set prefix ~/.npm-global
+ 然后添加到 PATH:export PATH=~/.npm-global/bin:$PATH
+
+
+
+
+
+
+ 缺少依赖库
+
+
+
某些 Linux 发行版需要安装额外依赖:
+
+
# Ubuntu/Debian
+
sudo apt install build-essential
+
# CentOS/RHEL
+
sudo dnf groupinstall "Development Tools"
+
+
+
+
+
+
+ 环境变量不生效
+
+
+
检查以下几点:
+
+ 确认修改了正确的配置文件(.bashrc 或 .zshrc)
+ 重新启动终端或运行 source ~/.bashrc
+ 验证设置:echo $ANTHROPIC_BASE_URL
+
+
+
+
+
+
+
+
+
+
🎉 恭喜你!
+
你已经成功安装并配置了 Claude Code,现在可以开始享受 AI 编程助手带来的便利了。
+
如果在使用过程中遇到任何问题,可以查看官方文档或社区讨论获取帮助。
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/admin-spa/tailwind.config.js b/web/admin-spa/tailwind.config.js
new file mode 100644
index 00000000..dcd84952
--- /dev/null
+++ b/web/admin-spa/tailwind.config.js
@@ -0,0 +1,38 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: [
+ "./index.html",
+ "./src/**/*.{vue,js,ts,jsx,tsx}",
+ ],
+ theme: {
+ extend: {
+ animation: {
+ 'gradient': 'gradient 8s ease infinite',
+ 'float': 'float 6s ease-in-out infinite',
+ 'float-delayed': 'float 6s ease-in-out infinite 2s',
+ 'pulse-glow': 'pulse-glow 2s ease-in-out infinite',
+ },
+ keyframes: {
+ gradient: {
+ '0%, 100%': {
+ 'background-size': '200% 200%',
+ 'background-position': 'left center'
+ },
+ '50%': {
+ 'background-size': '200% 200%',
+ 'background-position': 'right center'
+ }
+ },
+ float: {
+ '0%, 100%': { transform: 'translateY(0px)' },
+ '50%': { transform: 'translateY(-10px)' }
+ },
+ 'pulse-glow': {
+ '0%, 100%': { opacity: 1 },
+ '50%': { opacity: 0.8 }
+ }
+ }
+ },
+ },
+ plugins: [],
+}
\ No newline at end of file
diff --git a/web/admin-spa/vite.config.js b/web/admin-spa/vite.config.js
new file mode 100644
index 00000000..0a3a08ec
--- /dev/null
+++ b/web/admin-spa/vite.config.js
@@ -0,0 +1,104 @@
+import { defineConfig, loadEnv } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import AutoImport from 'unplugin-auto-import/vite'
+import Components from 'unplugin-vue-components/vite'
+import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
+import { fileURLToPath, URL } from 'node:url'
+
+export default defineConfig(({ mode }) => {
+ // 加载环境变量
+ const env = loadEnv(mode, process.cwd(), '')
+ const apiTarget = env.VITE_API_TARGET || 'http://localhost:3000'
+ const httpProxy = env.VITE_HTTP_PROXY || env.HTTP_PROXY || env.http_proxy
+ // 使用环境变量配置基础路径,如果未设置则使用默认值
+ const basePath = env.VITE_APP_BASE_URL || (mode === 'development' ? '/admin/' : '/admin-next/')
+
+ // 创建代理配置
+ const proxyConfig = {
+ target: apiTarget,
+ changeOrigin: true,
+ secure: false
+ }
+
+ // 如果设置了代理,动态导入并配置 agent(仅在开发模式下)
+ if (httpProxy && mode === 'development') {
+ console.log(`Using HTTP proxy: ${httpProxy}`)
+ // Vite 的 proxy 使用 http-proxy,它支持通过环境变量自动使用代理
+ // 设置环境变量让 http-proxy 使用代理
+ process.env.HTTP_PROXY = httpProxy
+ process.env.HTTPS_PROXY = httpProxy
+ }
+
+ console.log(`${mode === 'development' ? 'Starting dev server' : 'Building'} with base path: ${basePath}`)
+
+ return {
+ base: basePath,
+ plugins: [
+ vue(),
+ AutoImport({
+ resolvers: [ElementPlusResolver()],
+ imports: ['vue', 'vue-router', 'pinia']
+ }),
+ Components({
+ resolvers: [ElementPlusResolver()]
+ })
+ ],
+ resolve: {
+ alias: {
+ '@': fileURLToPath(new URL('./src', import.meta.url))
+ }
+ },
+ server: {
+ port: 3001,
+ host: true,
+ open: true,
+ proxy: {
+ // 统一的 API 代理规则 - 开发环境所有 API 请求都加 /webapi 前缀
+ '/webapi': {
+ ...proxyConfig,
+ rewrite: (path) => path.replace(/^\/webapi/, ''), // 转发时去掉 /webapi 前缀
+ configure: (proxy, options) => {
+ proxy.on('proxyReq', (proxyReq, req, res) => {
+ console.log('Proxying:', req.method, req.url, '->', options.target + req.url.replace(/^\/webapi/, ''))
+ })
+ proxy.on('error', (err, req, res) => {
+ console.log('Proxy error:', err)
+ })
+ }
+ },
+ // API Stats 专用代理规则
+ '/apiStats': {
+ ...proxyConfig,
+ configure: (proxy, options) => {
+ proxy.on('proxyReq', (proxyReq, req, res) => {
+ console.log('API Stats Proxying:', req.method, req.url, '->', options.target + req.url)
+ })
+ }
+ }
+ }
+ },
+ build: {
+ outDir: 'dist',
+ assetsDir: 'assets',
+ rollupOptions: {
+ output: {
+ manualChunks(id) {
+ // 将 vue 相关的库打包到一起
+ if (id.includes('node_modules')) {
+ if (id.includes('element-plus')) {
+ return 'element-plus'
+ }
+ if (id.includes('chart.js')) {
+ return 'chart'
+ }
+ if (id.includes('vue') || id.includes('pinia') || id.includes('vue-router')) {
+ return 'vue-vendor'
+ }
+ return 'vendor'
+ }
+ }
+ }
+ }
+ }
+ }
+})
\ No newline at end of file
diff --git a/web/admin-spa/yarn.lock b/web/admin-spa/yarn.lock
new file mode 100644
index 00000000..19eef885
--- /dev/null
+++ b/web/admin-spa/yarn.lock
@@ -0,0 +1,2136 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@alloc/quick-lru@^5.2.0":
+ version "5.2.0"
+ resolved "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz"
+ integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==
+
+"@antfu/utils@^0.7.10", "@antfu/utils@^0.7.6":
+ version "0.7.10"
+ resolved "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz"
+ integrity sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==
+
+"@babel/helper-string-parser@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz"
+ integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
+
+"@babel/helper-validator-identifier@^7.27.1":
+ version "7.27.1"
+ resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz"
+ integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
+
+"@babel/parser@^7.15.8", "@babel/parser@^7.28.0":
+ version "7.28.0"
+ resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.0.tgz"
+ integrity sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==
+ dependencies:
+ "@babel/types" "^7.28.0"
+
+"@babel/types@^7.28.0":
+ version "7.28.2"
+ resolved "https://registry.npmmirror.com/@babel/types/-/types-7.28.2.tgz"
+ integrity sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==
+ dependencies:
+ "@babel/helper-string-parser" "^7.27.1"
+ "@babel/helper-validator-identifier" "^7.27.1"
+
+"@ctrl/tinycolor@^3.4.1":
+ version "3.6.1"
+ resolved "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz"
+ integrity sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==
+
+"@element-plus/icons-vue@^2.3.1":
+ version "2.3.1"
+ resolved "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz"
+ integrity sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==
+
+"@esbuild/linux-x64@0.21.5":
+ version "0.21.5"
+ resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz"
+ integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
+
+"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
+ version "4.7.0"
+ resolved "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz"
+ integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==
+ dependencies:
+ eslint-visitor-keys "^3.4.3"
+
+"@eslint-community/regexpp@^4.6.1":
+ version "4.12.1"
+ resolved "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz"
+ integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
+
+"@eslint/eslintrc@^2.1.4":
+ version "2.1.4"
+ resolved "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz"
+ integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
+ dependencies:
+ ajv "^6.12.4"
+ debug "^4.3.2"
+ espree "^9.6.0"
+ globals "^13.19.0"
+ ignore "^5.2.0"
+ import-fresh "^3.2.1"
+ js-yaml "^4.1.0"
+ minimatch "^3.1.2"
+ strip-json-comments "^3.1.1"
+
+"@eslint/js@8.57.1":
+ version "8.57.1"
+ resolved "https://registry.npmmirror.com/@eslint/js/-/js-8.57.1.tgz"
+ integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
+
+"@floating-ui/core@^1.7.2":
+ version "1.7.2"
+ resolved "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.2.tgz"
+ integrity sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==
+ dependencies:
+ "@floating-ui/utils" "^0.2.10"
+
+"@floating-ui/dom@^1.0.1":
+ version "1.7.2"
+ resolved "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.2.tgz"
+ integrity sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==
+ dependencies:
+ "@floating-ui/core" "^1.7.2"
+ "@floating-ui/utils" "^0.2.10"
+
+"@floating-ui/utils@^0.2.10":
+ version "0.2.10"
+ resolved "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.10.tgz"
+ integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==
+
+"@fortawesome/fontawesome-free@^6.5.1":
+ version "6.7.2"
+ resolved "https://registry.npmmirror.com/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz"
+ integrity sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==
+
+"@humanwhocodes/config-array@^0.13.0":
+ version "0.13.0"
+ resolved "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz"
+ integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==
+ dependencies:
+ "@humanwhocodes/object-schema" "^2.0.3"
+ debug "^4.3.1"
+ minimatch "^3.0.5"
+
+"@humanwhocodes/module-importer@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz"
+ integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
+
+"@humanwhocodes/object-schema@^2.0.3":
+ version "2.0.3"
+ resolved "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz"
+ integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
+
+"@isaacs/cliui@^8.0.2":
+ version "8.0.2"
+ resolved "https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz"
+ integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==
+ dependencies:
+ string-width "^5.1.2"
+ string-width-cjs "npm:string-width@^4.2.0"
+ strip-ansi "^7.0.1"
+ strip-ansi-cjs "npm:strip-ansi@^6.0.1"
+ wrap-ansi "^8.1.0"
+ wrap-ansi-cjs "npm:wrap-ansi@^7.0.0"
+
+"@jridgewell/gen-mapping@^0.3.2":
+ version "0.3.12"
+ resolved "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz"
+ integrity sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==
+ dependencies:
+ "@jridgewell/sourcemap-codec" "^1.5.0"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.2"
+ resolved "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz"
+ integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==
+
+"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
+ version "1.5.4"
+ resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz"
+ integrity sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==
+
+"@jridgewell/trace-mapping@^0.3.24":
+ version "0.3.29"
+ resolved "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz"
+ integrity sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
+"@kurkle/color@^0.3.0":
+ version "0.3.4"
+ resolved "https://registry.npmmirror.com/@kurkle/color/-/color-0.3.4.tgz"
+ integrity sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==
+
+"@nodelib/fs.scandir@2.1.5":
+ version "2.1.5"
+ resolved "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
+ integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+ dependencies:
+ "@nodelib/fs.stat" "2.0.5"
+ run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
+ version "2.0.5"
+ resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
+ integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
+"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
+ version "1.2.8"
+ resolved "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz"
+ integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
+ dependencies:
+ "@nodelib/fs.scandir" "2.1.5"
+ fastq "^1.6.0"
+
+"@pkgjs/parseargs@^0.11.0":
+ version "0.11.0"
+ resolved "https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz"
+ integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
+
+"@popperjs/core@npm:@sxzz/popperjs-es@^2.11.7":
+ version "2.11.7"
+ resolved "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz"
+ integrity sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==
+
+"@rollup/pluginutils@^5.0.2", "@rollup/pluginutils@^5.0.4", "@rollup/pluginutils@^5.1.0", "@rollup/pluginutils@^5.1.4":
+ version "5.2.0"
+ resolved "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.2.0.tgz"
+ integrity sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==
+ dependencies:
+ "@types/estree" "^1.0.0"
+ estree-walker "^2.0.2"
+ picomatch "^4.0.2"
+
+"@rollup/rollup-linux-x64-gnu@4.46.1":
+ version "4.46.1"
+ resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.1.tgz"
+ integrity sha512-9OADZYryz/7E8/qt0vnaHQgmia2Y0wrjSSn1V/uL+zw/i7NUhxbX4cHXdEQ7dnJgzYDS81d8+tf6nbIdRFZQoQ==
+
+"@rollup/rollup-linux-x64-musl@4.46.1":
+ version "4.46.1"
+ resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.1.tgz"
+ integrity sha512-NuvSCbXEKY+NGWHyivzbjSVJi68Xfq1VnIvGmsuXs6TCtveeoDRKutI5vf2ntmNnVq64Q4zInet0UDQ+yMB6tA==
+
+"@types/estree@^1.0.0", "@types/estree@1.0.8":
+ version "1.0.8"
+ resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz"
+ integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
+
+"@types/lodash-es@*", "@types/lodash-es@^4.17.6":
+ version "4.17.12"
+ resolved "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz"
+ integrity sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==
+ dependencies:
+ "@types/lodash" "*"
+
+"@types/lodash@*", "@types/lodash@^4.14.182":
+ version "4.17.20"
+ resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.20.tgz"
+ integrity sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==
+
+"@types/web-bluetooth@^0.0.16":
+ version "0.0.16"
+ resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz"
+ integrity sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==
+
+"@ungap/structured-clone@^1.2.0":
+ version "1.3.0"
+ resolved "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz"
+ integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
+
+"@vitejs/plugin-vue@^4.5.2":
+ version "4.6.2"
+ resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz"
+ integrity sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==
+
+"@vue/compiler-core@3.5.18":
+ version "3.5.18"
+ resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.18.tgz"
+ integrity sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==
+ dependencies:
+ "@babel/parser" "^7.28.0"
+ "@vue/shared" "3.5.18"
+ entities "^4.5.0"
+ estree-walker "^2.0.2"
+ source-map-js "^1.2.1"
+
+"@vue/compiler-dom@3.5.18":
+ version "3.5.18"
+ resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz"
+ integrity sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==
+ dependencies:
+ "@vue/compiler-core" "3.5.18"
+ "@vue/shared" "3.5.18"
+
+"@vue/compiler-sfc@3.5.18":
+ version "3.5.18"
+ resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz"
+ integrity sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==
+ dependencies:
+ "@babel/parser" "^7.28.0"
+ "@vue/compiler-core" "3.5.18"
+ "@vue/compiler-dom" "3.5.18"
+ "@vue/compiler-ssr" "3.5.18"
+ "@vue/shared" "3.5.18"
+ estree-walker "^2.0.2"
+ magic-string "^0.30.17"
+ postcss "^8.5.6"
+ source-map-js "^1.2.1"
+
+"@vue/compiler-ssr@3.5.18":
+ version "3.5.18"
+ resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz"
+ integrity sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==
+ dependencies:
+ "@vue/compiler-dom" "3.5.18"
+ "@vue/shared" "3.5.18"
+
+"@vue/devtools-api@^6.6.3", "@vue/devtools-api@^6.6.4":
+ version "6.6.4"
+ resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz"
+ integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==
+
+"@vue/reactivity@3.5.18":
+ version "3.5.18"
+ resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.18.tgz"
+ integrity sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==
+ dependencies:
+ "@vue/shared" "3.5.18"
+
+"@vue/runtime-core@3.5.18":
+ version "3.5.18"
+ resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.18.tgz"
+ integrity sha512-DUpHa1HpeOQEt6+3nheUfqVXRog2kivkXHUhoqJiKR33SO4x+a5uNOMkV487WPerQkL0vUuRvq/7JhRgLW3S+w==
+ dependencies:
+ "@vue/reactivity" "3.5.18"
+ "@vue/shared" "3.5.18"
+
+"@vue/runtime-dom@3.5.18":
+ version "3.5.18"
+ resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.18.tgz"
+ integrity sha512-YwDj71iV05j4RnzZnZtGaXwPoUWeRsqinblgVJwR8XTXYZ9D5PbahHQgsbmzUvCWNF6x7siQ89HgnX5eWkr3mw==
+ dependencies:
+ "@vue/reactivity" "3.5.18"
+ "@vue/runtime-core" "3.5.18"
+ "@vue/shared" "3.5.18"
+ csstype "^3.1.3"
+
+"@vue/server-renderer@3.5.18":
+ version "3.5.18"
+ resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.18.tgz"
+ integrity sha512-PvIHLUoWgSbDG7zLHqSqaCoZvHi6NNmfVFOqO+OnwvqMz/tqQr3FuGWS8ufluNddk7ZLBJYMrjcw1c6XzR12mA==
+ dependencies:
+ "@vue/compiler-ssr" "3.5.18"
+ "@vue/shared" "3.5.18"
+
+"@vue/shared@3.5.18":
+ version "3.5.18"
+ resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.18.tgz"
+ integrity sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==
+
+"@vueuse/core@*", "@vueuse/core@^9.1.0":
+ version "9.13.0"
+ resolved "https://registry.npmmirror.com/@vueuse/core/-/core-9.13.0.tgz"
+ integrity sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==
+ dependencies:
+ "@types/web-bluetooth" "^0.0.16"
+ "@vueuse/metadata" "9.13.0"
+ "@vueuse/shared" "9.13.0"
+ vue-demi "*"
+
+"@vueuse/metadata@9.13.0":
+ version "9.13.0"
+ resolved "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-9.13.0.tgz"
+ integrity sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==
+
+"@vueuse/shared@9.13.0":
+ version "9.13.0"
+ resolved "https://registry.npmmirror.com/@vueuse/shared/-/shared-9.13.0.tgz"
+ integrity sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==
+ dependencies:
+ vue-demi "*"
+
+acorn-jsx@^5.3.2:
+ version "5.3.2"
+ resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
+ integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
+"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.14.0, acorn@^8.9.0:
+ version "8.15.0"
+ resolved "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz"
+ integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
+
+ajv@^6.12.4:
+ version "6.12.6"
+ resolved "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz"
+ integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+ dependencies:
+ fast-deep-equal "^3.1.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
+ansi-regex@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz"
+ integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-regex@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.1.0.tgz"
+ integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==
+
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+ version "4.3.0"
+ resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz"
+ integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+ dependencies:
+ color-convert "^2.0.1"
+
+ansi-styles@^6.1.0:
+ version "6.2.1"
+ resolved "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.1.tgz"
+ integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==
+
+any-promise@^1.0.0:
+ version "1.3.0"
+ resolved "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz"
+ integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==
+
+anymatch@~3.1.2:
+ version "3.1.3"
+ resolved "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz"
+ integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
+arg@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz"
+ integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==
+
+argparse@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz"
+ integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+async-validator@^4.2.5:
+ version "4.2.5"
+ resolved "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz"
+ integrity sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==
+
+asynckit@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz"
+ integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
+
+autoprefixer@^10.4.16:
+ version "10.4.21"
+ resolved "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.21.tgz"
+ integrity sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==
+ dependencies:
+ browserslist "^4.24.4"
+ caniuse-lite "^1.0.30001702"
+ fraction.js "^4.3.7"
+ normalize-range "^0.1.2"
+ picocolors "^1.1.1"
+ postcss-value-parser "^4.2.0"
+
+axios@^1.6.2:
+ version "1.11.0"
+ resolved "https://registry.npmmirror.com/axios/-/axios-1.11.0.tgz"
+ integrity sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==
+ dependencies:
+ follow-redirects "^1.15.6"
+ form-data "^4.0.4"
+ proxy-from-env "^1.1.0"
+
+balanced-match@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz"
+ integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+binary-extensions@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz"
+ integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
+
+boolbase@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz"
+ integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
+
+brace-expansion@^1.1.7:
+ version "1.1.12"
+ resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz"
+ integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+brace-expansion@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz"
+ integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==
+ dependencies:
+ balanced-match "^1.0.0"
+
+braces@^3.0.3, braces@~3.0.2:
+ version "3.0.3"
+ resolved "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz"
+ integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==
+ dependencies:
+ fill-range "^7.1.1"
+
+browserslist@^4.24.4, "browserslist@>= 4.21.0":
+ version "4.25.1"
+ resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.25.1.tgz"
+ integrity sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==
+ dependencies:
+ caniuse-lite "^1.0.30001726"
+ electron-to-chromium "^1.5.173"
+ node-releases "^2.0.19"
+ update-browserslist-db "^1.1.3"
+
+call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz"
+ integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==
+ dependencies:
+ es-errors "^1.3.0"
+ function-bind "^1.1.2"
+
+callsites@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz"
+ integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
+camelcase-css@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz"
+ integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
+
+caniuse-lite@^1.0.30001702, caniuse-lite@^1.0.30001726:
+ version "1.0.30001727"
+ resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz"
+ integrity sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==
+
+chalk@^4.0.0:
+ version "4.1.2"
+ resolved "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz"
+ integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+ dependencies:
+ ansi-styles "^4.1.0"
+ supports-color "^7.1.0"
+
+chart.js@^4.4.0:
+ version "4.5.0"
+ resolved "https://registry.npmmirror.com/chart.js/-/chart.js-4.5.0.tgz"
+ integrity sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==
+ dependencies:
+ "@kurkle/color" "^0.3.0"
+
+chokidar@^3.5.3, chokidar@^3.6.0:
+ version "3.6.0"
+ resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz"
+ integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
+ dependencies:
+ anymatch "~3.1.2"
+ braces "~3.0.2"
+ glob-parent "~5.1.2"
+ is-binary-path "~2.1.0"
+ is-glob "~4.0.1"
+ normalize-path "~3.0.0"
+ readdirp "~3.6.0"
+ optionalDependencies:
+ fsevents "~2.3.2"
+
+color-convert@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz"
+ integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+ dependencies:
+ color-name "~1.1.4"
+
+color-name@~1.1.4:
+ version "1.1.4"
+ resolved "https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz"
+ integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+combined-stream@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz"
+ integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+ dependencies:
+ delayed-stream "~1.0.0"
+
+commander@^4.0.0:
+ version "4.1.1"
+ resolved "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz"
+ integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz"
+ integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+confbox@^0.1.8:
+ version "0.1.8"
+ resolved "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz"
+ integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==
+
+confbox@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz"
+ integrity sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==
+
+cross-spawn@^7.0.2, cross-spawn@^7.0.6:
+ version "7.0.6"
+ resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz"
+ integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
+ dependencies:
+ path-key "^3.1.0"
+ shebang-command "^2.0.0"
+ which "^2.0.1"
+
+cssesc@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz"
+ integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
+
+csstype@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz"
+ integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
+
+dayjs@^1.11.13, dayjs@^1.11.9:
+ version "1.11.13"
+ resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz"
+ integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
+
+debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
+ version "4.4.1"
+ resolved "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz"
+ integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
+ dependencies:
+ ms "^2.1.3"
+
+deep-is@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz"
+ integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
+
+delayed-stream@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz"
+ integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+
+didyoumean@^1.2.2:
+ version "1.2.2"
+ resolved "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz"
+ integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==
+
+dlv@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz"
+ integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
+
+doctrine@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz"
+ integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
+ dependencies:
+ esutils "^2.0.2"
+
+dunder-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz"
+ integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==
+ dependencies:
+ call-bind-apply-helpers "^1.0.1"
+ es-errors "^1.3.0"
+ gopd "^1.2.0"
+
+eastasianwidth@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
+ integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
+
+electron-to-chromium@^1.5.173:
+ version "1.5.191"
+ resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz"
+ integrity sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==
+
+element-plus@^2.4.4:
+ version "2.10.4"
+ resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.10.4.tgz"
+ integrity sha512-UD4elWHrCnp1xlPhbXmVcaKFLCRaRAY6WWRwemGfGW3ceIjXm9fSYc9RNH3AiOEA6Ds1p9ZvhCs76CR9J8Vd+A==
+ dependencies:
+ "@ctrl/tinycolor" "^3.4.1"
+ "@element-plus/icons-vue" "^2.3.1"
+ "@floating-ui/dom" "^1.0.1"
+ "@popperjs/core" "npm:@sxzz/popperjs-es@^2.11.7"
+ "@types/lodash" "^4.14.182"
+ "@types/lodash-es" "^4.17.6"
+ "@vueuse/core" "^9.1.0"
+ async-validator "^4.2.5"
+ dayjs "^1.11.13"
+ escape-html "^1.0.3"
+ lodash "^4.17.21"
+ lodash-es "^4.17.21"
+ lodash-unified "^1.0.2"
+ memoize-one "^6.0.0"
+ normalize-wheel-es "^1.2.0"
+
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+emoji-regex@^9.2.2:
+ version "9.2.2"
+ resolved "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz"
+ integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
+
+entities@^4.5.0:
+ version "4.5.0"
+ resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz"
+ integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==
+
+es-define-property@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz"
+ integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==
+
+es-errors@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz"
+ integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
+
+es-module-lexer@^1.3.0:
+ version "1.7.0"
+ resolved "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz"
+ integrity sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==
+
+es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz"
+ integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==
+ dependencies:
+ es-errors "^1.3.0"
+
+es-set-tostringtag@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz"
+ integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==
+ dependencies:
+ es-errors "^1.3.0"
+ get-intrinsic "^1.2.6"
+ has-tostringtag "^1.0.2"
+ hasown "^2.0.2"
+
+esbuild@^0.21.3:
+ version "0.21.5"
+ resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz"
+ integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
+ optionalDependencies:
+ "@esbuild/aix-ppc64" "0.21.5"
+ "@esbuild/android-arm" "0.21.5"
+ "@esbuild/android-arm64" "0.21.5"
+ "@esbuild/android-x64" "0.21.5"
+ "@esbuild/darwin-arm64" "0.21.5"
+ "@esbuild/darwin-x64" "0.21.5"
+ "@esbuild/freebsd-arm64" "0.21.5"
+ "@esbuild/freebsd-x64" "0.21.5"
+ "@esbuild/linux-arm" "0.21.5"
+ "@esbuild/linux-arm64" "0.21.5"
+ "@esbuild/linux-ia32" "0.21.5"
+ "@esbuild/linux-loong64" "0.21.5"
+ "@esbuild/linux-mips64el" "0.21.5"
+ "@esbuild/linux-ppc64" "0.21.5"
+ "@esbuild/linux-riscv64" "0.21.5"
+ "@esbuild/linux-s390x" "0.21.5"
+ "@esbuild/linux-x64" "0.21.5"
+ "@esbuild/netbsd-x64" "0.21.5"
+ "@esbuild/openbsd-x64" "0.21.5"
+ "@esbuild/sunos-x64" "0.21.5"
+ "@esbuild/win32-arm64" "0.21.5"
+ "@esbuild/win32-ia32" "0.21.5"
+ "@esbuild/win32-x64" "0.21.5"
+
+escalade@^3.2.0:
+ version "3.2.0"
+ resolved "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz"
+ integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
+
+escape-html@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz"
+ integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
+
+escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+escape-string-regexp@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz"
+ integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
+
+eslint-plugin-vue@^9.19.2:
+ version "9.33.0"
+ resolved "https://registry.npmmirror.com/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz"
+ integrity sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.4.0"
+ globals "^13.24.0"
+ natural-compare "^1.4.0"
+ nth-check "^2.1.1"
+ postcss-selector-parser "^6.0.15"
+ semver "^7.6.3"
+ vue-eslint-parser "^9.4.3"
+ xml-name-validator "^4.0.0"
+
+eslint-scope@^7.1.1, eslint-scope@^7.2.2:
+ version "7.2.2"
+ resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.2.tgz"
+ integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
+ dependencies:
+ esrecurse "^4.3.0"
+ estraverse "^5.2.0"
+
+eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
+ version "3.4.3"
+ resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
+ integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
+
+"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", "eslint@^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", eslint@^8.55.0, eslint@>=6.0.0:
+ version "8.57.1"
+ resolved "https://registry.npmmirror.com/eslint/-/eslint-8.57.1.tgz"
+ integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
+ dependencies:
+ "@eslint-community/eslint-utils" "^4.2.0"
+ "@eslint-community/regexpp" "^4.6.1"
+ "@eslint/eslintrc" "^2.1.4"
+ "@eslint/js" "8.57.1"
+ "@humanwhocodes/config-array" "^0.13.0"
+ "@humanwhocodes/module-importer" "^1.0.1"
+ "@nodelib/fs.walk" "^1.2.8"
+ "@ungap/structured-clone" "^1.2.0"
+ ajv "^6.12.4"
+ chalk "^4.0.0"
+ cross-spawn "^7.0.2"
+ debug "^4.3.2"
+ doctrine "^3.0.0"
+ escape-string-regexp "^4.0.0"
+ eslint-scope "^7.2.2"
+ eslint-visitor-keys "^3.4.3"
+ espree "^9.6.1"
+ esquery "^1.4.2"
+ esutils "^2.0.2"
+ fast-deep-equal "^3.1.3"
+ file-entry-cache "^6.0.1"
+ find-up "^5.0.0"
+ glob-parent "^6.0.2"
+ globals "^13.19.0"
+ graphemer "^1.4.0"
+ ignore "^5.2.0"
+ imurmurhash "^0.1.4"
+ is-glob "^4.0.0"
+ is-path-inside "^3.0.3"
+ js-yaml "^4.1.0"
+ json-stable-stringify-without-jsonify "^1.0.1"
+ levn "^0.4.1"
+ lodash.merge "^4.6.2"
+ minimatch "^3.1.2"
+ natural-compare "^1.4.0"
+ optionator "^0.9.3"
+ strip-ansi "^6.0.1"
+ text-table "^0.2.0"
+
+espree@^9.3.1, espree@^9.6.0, espree@^9.6.1:
+ version "9.6.1"
+ resolved "https://registry.npmmirror.com/espree/-/espree-9.6.1.tgz"
+ integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
+ dependencies:
+ acorn "^8.9.0"
+ acorn-jsx "^5.3.2"
+ eslint-visitor-keys "^3.4.1"
+
+esquery@^1.4.0, esquery@^1.4.2:
+ version "1.6.0"
+ resolved "https://registry.npmmirror.com/esquery/-/esquery-1.6.0.tgz"
+ integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
+ dependencies:
+ estraverse "^5.1.0"
+
+esrecurse@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz"
+ integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+ dependencies:
+ estraverse "^5.2.0"
+
+estraverse@^5.1.0, estraverse@^5.2.0:
+ version "5.3.0"
+ resolved "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz"
+ integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+estree-walker@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz"
+ integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
+
+estree-walker@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz"
+ integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==
+ dependencies:
+ "@types/estree" "^1.0.0"
+
+esutils@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz"
+ integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+exsolve@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.7.tgz"
+ integrity sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
+ integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-glob@^3.3.1, fast-glob@^3.3.2, fast-glob@^3.3.3:
+ version "3.3.3"
+ resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz"
+ integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==
+ dependencies:
+ "@nodelib/fs.stat" "^2.0.2"
+ "@nodelib/fs.walk" "^1.2.3"
+ glob-parent "^5.1.2"
+ merge2 "^1.3.0"
+ micromatch "^4.0.8"
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
+ integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fast-levenshtein@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
+ integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+
+fastq@^1.6.0:
+ version "1.19.1"
+ resolved "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz"
+ integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==
+ dependencies:
+ reusify "^1.0.4"
+
+file-entry-cache@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz"
+ integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
+ dependencies:
+ flat-cache "^3.0.4"
+
+fill-range@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz"
+ integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
+ dependencies:
+ to-regex-range "^5.0.1"
+
+find-up@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz"
+ integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+ dependencies:
+ locate-path "^6.0.0"
+ path-exists "^4.0.0"
+
+flat-cache@^3.0.4:
+ version "3.2.0"
+ resolved "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.2.0.tgz"
+ integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==
+ dependencies:
+ flatted "^3.2.9"
+ keyv "^4.5.3"
+ rimraf "^3.0.2"
+
+flatted@^3.2.9:
+ version "3.3.3"
+ resolved "https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz"
+ integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
+
+follow-redirects@^1.15.6:
+ version "1.15.9"
+ resolved "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.9.tgz"
+ integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
+
+foreground-child@^3.1.0:
+ version "3.3.1"
+ resolved "https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz"
+ integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==
+ dependencies:
+ cross-spawn "^7.0.6"
+ signal-exit "^4.0.1"
+
+form-data@^4.0.4:
+ version "4.0.4"
+ resolved "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz"
+ integrity sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ es-set-tostringtag "^2.1.0"
+ hasown "^2.0.2"
+ mime-types "^2.1.12"
+
+fraction.js@^4.3.7:
+ version "4.3.7"
+ resolved "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz"
+ integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz"
+ integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
+function-bind@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz"
+ integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
+
+get-intrinsic@^1.2.6:
+ version "1.3.0"
+ resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz"
+ integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
+ dependencies:
+ call-bind-apply-helpers "^1.0.2"
+ es-define-property "^1.0.1"
+ es-errors "^1.3.0"
+ es-object-atoms "^1.1.1"
+ function-bind "^1.1.2"
+ get-proto "^1.0.1"
+ gopd "^1.2.0"
+ has-symbols "^1.1.0"
+ hasown "^2.0.2"
+ math-intrinsics "^1.1.0"
+
+get-proto@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz"
+ integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==
+ dependencies:
+ dunder-proto "^1.0.1"
+ es-object-atoms "^1.0.0"
+
+glob-parent@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob-parent@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz"
+ integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+ dependencies:
+ is-glob "^4.0.3"
+
+glob-parent@~5.1.2:
+ version "5.1.2"
+ resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz"
+ integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob@^10.3.10:
+ version "10.4.5"
+ resolved "https://registry.npmmirror.com/glob/-/glob-10.4.5.tgz"
+ integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==
+ dependencies:
+ foreground-child "^3.1.0"
+ jackspeak "^3.1.2"
+ minimatch "^9.0.4"
+ minipass "^7.1.2"
+ package-json-from-dist "^1.0.0"
+ path-scurry "^1.11.1"
+
+glob@^7.1.3:
+ version "7.2.3"
+ resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz"
+ integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.1.1"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+globals@^13.19.0, globals@^13.24.0:
+ version "13.24.0"
+ resolved "https://registry.npmmirror.com/globals/-/globals-13.24.0.tgz"
+ integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==
+ dependencies:
+ type-fest "^0.20.2"
+
+gopd@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz"
+ integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==
+
+graphemer@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz"
+ integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+
+has-flag@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz"
+ integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+has-symbols@^1.0.3, has-symbols@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz"
+ integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==
+
+has-tostringtag@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz"
+ integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==
+ dependencies:
+ has-symbols "^1.0.3"
+
+hasown@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz"
+ integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==
+ dependencies:
+ function-bind "^1.1.2"
+
+ignore@^5.2.0:
+ version "5.3.2"
+ resolved "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz"
+ integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
+
+import-fresh@^3.2.1:
+ version "3.3.1"
+ resolved "https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz"
+ integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
+ dependencies:
+ parent-module "^1.0.0"
+ resolve-from "^4.0.0"
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz"
+ integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz"
+ integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2:
+ version "2.0.4"
+ resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+is-binary-path@~2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz"
+ integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+ dependencies:
+ binary-extensions "^2.0.0"
+
+is-core-module@^2.16.0:
+ version "2.16.1"
+ resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz"
+ integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==
+ dependencies:
+ hasown "^2.0.2"
+
+is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz"
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
+is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
+ version "4.0.3"
+ resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz"
+ integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-path-inside@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz"
+ integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz"
+ integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+jackspeak@^3.1.2:
+ version "3.4.3"
+ resolved "https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz"
+ integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==
+ dependencies:
+ "@isaacs/cliui" "^8.0.2"
+ optionalDependencies:
+ "@pkgjs/parseargs" "^0.11.0"
+
+jiti@^1.21.6:
+ version "1.21.7"
+ resolved "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz"
+ integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==
+
+js-tokens@^9.0.1:
+ version "9.0.1"
+ resolved "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz"
+ integrity sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==
+
+js-yaml@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz"
+ integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+ dependencies:
+ argparse "^2.0.1"
+
+json-buffer@3.0.1:
+ version "3.0.1"
+ resolved "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz"
+ integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
+
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-stable-stringify-without-jsonify@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
+ integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+
+keyv@^4.5.3:
+ version "4.5.4"
+ resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz"
+ integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
+ dependencies:
+ json-buffer "3.0.1"
+
+levn@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz"
+ integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+ dependencies:
+ prelude-ls "^1.2.1"
+ type-check "~0.4.0"
+
+lilconfig@^3.0.0, lilconfig@^3.1.3:
+ version "3.1.3"
+ resolved "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz"
+ integrity sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==
+
+lines-and-columns@^1.1.6:
+ version "1.2.4"
+ resolved "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz"
+ integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
+
+local-pkg@^0.4.3:
+ version "0.4.3"
+ resolved "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.4.3.tgz"
+ integrity sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==
+
+local-pkg@^0.5.0:
+ version "0.5.1"
+ resolved "https://registry.npmmirror.com/local-pkg/-/local-pkg-0.5.1.tgz"
+ integrity sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==
+ dependencies:
+ mlly "^1.7.3"
+ pkg-types "^1.2.1"
+
+local-pkg@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.1.tgz"
+ integrity sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==
+ dependencies:
+ mlly "^1.7.4"
+ pkg-types "^2.0.1"
+ quansync "^0.2.8"
+
+locate-path@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz"
+ integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+ dependencies:
+ p-locate "^5.0.0"
+
+lodash-es@*, lodash-es@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz"
+ integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==
+
+lodash-unified@^1.0.2:
+ version "1.0.3"
+ resolved "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz"
+ integrity sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==
+
+lodash.merge@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz"
+ integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
+lodash@*, lodash@^4.17.21:
+ version "4.17.21"
+ resolved "https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz"
+ integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+lru-cache@^10.2.0:
+ version "10.4.3"
+ resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz"
+ integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
+
+magic-string@^0.30.1, magic-string@^0.30.10, magic-string@^0.30.17, magic-string@^0.30.3:
+ version "0.30.17"
+ resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz"
+ integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==
+ dependencies:
+ "@jridgewell/sourcemap-codec" "^1.5.0"
+
+math-intrinsics@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz"
+ integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
+
+memoize-one@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz"
+ integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
+
+merge2@^1.3.0:
+ version "1.4.1"
+ resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz"
+ integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
+micromatch@^4.0.8:
+ version "4.0.8"
+ resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz"
+ integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
+ dependencies:
+ braces "^3.0.3"
+ picomatch "^2.3.1"
+
+mime-db@1.52.0:
+ version "1.52.0"
+ resolved "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz"
+ integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12:
+ version "2.1.35"
+ resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz"
+ integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+ dependencies:
+ mime-db "1.52.0"
+
+minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
+ version "3.1.2"
+ resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz"
+ integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimatch@^9.0.3:
+ version "9.0.5"
+ resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz"
+ integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+minimatch@^9.0.4:
+ version "9.0.5"
+ resolved "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz"
+ integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
+ dependencies:
+ brace-expansion "^2.0.1"
+
+"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2:
+ version "7.1.2"
+ resolved "https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz"
+ integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
+
+mlly@^1.7.3, mlly@^1.7.4:
+ version "1.7.4"
+ resolved "https://registry.npmmirror.com/mlly/-/mlly-1.7.4.tgz"
+ integrity sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==
+ dependencies:
+ acorn "^8.14.0"
+ pathe "^2.0.1"
+ pkg-types "^1.3.0"
+ ufo "^1.5.4"
+
+ms@^2.1.3:
+ version "2.1.3"
+ resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+mz@^2.7.0:
+ version "2.7.0"
+ resolved "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz"
+ integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==
+ dependencies:
+ any-promise "^1.0.0"
+ object-assign "^4.0.1"
+ thenify-all "^1.0.0"
+
+nanoid@^3.3.11:
+ version "3.3.11"
+ resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz"
+ integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
+
+natural-compare@^1.4.0:
+ version "1.4.0"
+ resolved "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz"
+ integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
+
+node-releases@^2.0.19:
+ version "2.0.19"
+ resolved "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz"
+ integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==
+
+normalize-path@^3.0.0, normalize-path@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+normalize-range@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz"
+ integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
+
+normalize-wheel-es@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz"
+ integrity sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==
+
+nth-check@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz"
+ integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
+ dependencies:
+ boolbase "^1.0.0"
+
+object-assign@^4.0.1:
+ version "4.1.1"
+ resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz"
+ integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
+
+object-hash@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz"
+ integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz"
+ integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+ dependencies:
+ wrappy "1"
+
+optionator@^0.9.3:
+ version "0.9.4"
+ resolved "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz"
+ integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
+ dependencies:
+ deep-is "^0.1.3"
+ fast-levenshtein "^2.0.6"
+ levn "^0.4.1"
+ prelude-ls "^1.2.1"
+ type-check "^0.4.0"
+ word-wrap "^1.2.5"
+
+p-limit@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz"
+ integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+ dependencies:
+ yocto-queue "^0.1.0"
+
+p-locate@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz"
+ integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+ dependencies:
+ p-limit "^3.0.2"
+
+package-json-from-dist@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz"
+ integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
+
+parent-module@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz"
+ integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+ dependencies:
+ callsites "^3.0.0"
+
+path-exists@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz"
+ integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
+ integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+
+path-key@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz"
+ integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-parse@^1.0.7:
+ version "1.0.7"
+ resolved "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz"
+ integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+path-scurry@^1.11.1:
+ version "1.11.1"
+ resolved "https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz"
+ integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
+ dependencies:
+ lru-cache "^10.2.0"
+ minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
+
+pathe@^2.0.1, pathe@^2.0.3:
+ version "2.0.3"
+ resolved "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz"
+ integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
+
+picocolors@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz"
+ integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
+
+picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz"
+ integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+picomatch@^4.0.2:
+ version "4.0.3"
+ resolved "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz"
+ integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
+
+pify@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz"
+ integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==
+
+pinia@^2.1.7:
+ version "2.3.1"
+ resolved "https://registry.npmmirror.com/pinia/-/pinia-2.3.1.tgz"
+ integrity sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==
+ dependencies:
+ "@vue/devtools-api" "^6.6.3"
+ vue-demi "^0.14.10"
+
+pirates@^4.0.1:
+ version "4.0.7"
+ resolved "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz"
+ integrity sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==
+
+pkg-types@^1.2.1, pkg-types@^1.3.0:
+ version "1.3.1"
+ resolved "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz"
+ integrity sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==
+ dependencies:
+ confbox "^0.1.8"
+ mlly "^1.7.4"
+ pathe "^2.0.1"
+
+pkg-types@^2.0.1:
+ version "2.2.0"
+ resolved "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.2.0.tgz"
+ integrity sha512-2SM/GZGAEkPp3KWORxQZns4M+WSeXbC2HEvmOIJe3Cmiv6ieAJvdVhDldtHqM5J1Y7MrR1XhkBT/rMlhh9FdqQ==
+ dependencies:
+ confbox "^0.2.2"
+ exsolve "^1.0.7"
+ pathe "^2.0.3"
+
+postcss-import@^15.1.0:
+ version "15.1.0"
+ resolved "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz"
+ integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==
+ dependencies:
+ postcss-value-parser "^4.0.0"
+ read-cache "^1.0.0"
+ resolve "^1.1.7"
+
+postcss-js@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.0.1.tgz"
+ integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==
+ dependencies:
+ camelcase-css "^2.0.1"
+
+postcss-load-config@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz"
+ integrity sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==
+ dependencies:
+ lilconfig "^3.0.0"
+ yaml "^2.3.4"
+
+postcss-nested@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz"
+ integrity sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==
+ dependencies:
+ postcss-selector-parser "^6.1.1"
+
+postcss-selector-parser@^6.0.15, postcss-selector-parser@^6.1.1, postcss-selector-parser@^6.1.2:
+ version "6.1.2"
+ resolved "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz"
+ integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
+ dependencies:
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
+
+postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
+ integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
+
+postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4.21, postcss@^8.4.32, postcss@^8.4.43, postcss@^8.4.47, postcss@^8.5.6, postcss@>=8.0.9:
+ version "8.5.6"
+ resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz"
+ integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
+ dependencies:
+ nanoid "^3.3.11"
+ picocolors "^1.1.1"
+ source-map-js "^1.2.1"
+
+prelude-ls@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz"
+ integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
+prettier@^3.1.1:
+ version "3.6.2"
+ resolved "https://registry.npmmirror.com/prettier/-/prettier-3.6.2.tgz"
+ integrity sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==
+
+proxy-from-env@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
+ integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
+
+punycode@^2.1.0:
+ version "2.3.1"
+ resolved "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz"
+ integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
+
+quansync@^0.2.8:
+ version "0.2.10"
+ resolved "https://registry.npmmirror.com/quansync/-/quansync-0.2.10.tgz"
+ integrity sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==
+
+queue-microtask@^1.2.2:
+ version "1.2.3"
+ resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz"
+ integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+
+read-cache@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz"
+ integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==
+ dependencies:
+ pify "^2.3.0"
+
+readdirp@~3.6.0:
+ version "3.6.0"
+ resolved "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz"
+ integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
+ dependencies:
+ picomatch "^2.2.1"
+
+resolve-from@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz"
+ integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+resolve@^1.1.7, resolve@^1.22.4, resolve@^1.22.8:
+ version "1.22.10"
+ resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz"
+ integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==
+ dependencies:
+ is-core-module "^2.16.0"
+ path-parse "^1.0.7"
+ supports-preserve-symlinks-flag "^1.0.0"
+
+reusify@^1.0.4:
+ version "1.1.0"
+ resolved "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz"
+ integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
+
+rimraf@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
+rollup@^1.20.0||^2.0.0||^3.0.0||^4.0.0, rollup@^4.20.0:
+ version "4.46.1"
+ resolved "https://registry.npmmirror.com/rollup/-/rollup-4.46.1.tgz"
+ integrity sha512-33xGNBsDJAkzt0PvninskHlWnTIPgDtTwhg0U38CUoNP/7H6wI2Cz6dUeoNPbjdTdsYTGuiFFASuUOWovH0SyQ==
+ dependencies:
+ "@types/estree" "1.0.8"
+ optionalDependencies:
+ "@rollup/rollup-android-arm-eabi" "4.46.1"
+ "@rollup/rollup-android-arm64" "4.46.1"
+ "@rollup/rollup-darwin-arm64" "4.46.1"
+ "@rollup/rollup-darwin-x64" "4.46.1"
+ "@rollup/rollup-freebsd-arm64" "4.46.1"
+ "@rollup/rollup-freebsd-x64" "4.46.1"
+ "@rollup/rollup-linux-arm-gnueabihf" "4.46.1"
+ "@rollup/rollup-linux-arm-musleabihf" "4.46.1"
+ "@rollup/rollup-linux-arm64-gnu" "4.46.1"
+ "@rollup/rollup-linux-arm64-musl" "4.46.1"
+ "@rollup/rollup-linux-loongarch64-gnu" "4.46.1"
+ "@rollup/rollup-linux-ppc64-gnu" "4.46.1"
+ "@rollup/rollup-linux-riscv64-gnu" "4.46.1"
+ "@rollup/rollup-linux-riscv64-musl" "4.46.1"
+ "@rollup/rollup-linux-s390x-gnu" "4.46.1"
+ "@rollup/rollup-linux-x64-gnu" "4.46.1"
+ "@rollup/rollup-linux-x64-musl" "4.46.1"
+ "@rollup/rollup-win32-arm64-msvc" "4.46.1"
+ "@rollup/rollup-win32-ia32-msvc" "4.46.1"
+ "@rollup/rollup-win32-x64-msvc" "4.46.1"
+ fsevents "~2.3.2"
+
+run-parallel@^1.1.9:
+ version "1.2.0"
+ resolved "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz"
+ integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
+ dependencies:
+ queue-microtask "^1.2.2"
+
+scule@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz"
+ integrity sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==
+
+semver@^7.3.6, semver@^7.6.3:
+ version "7.7.2"
+ resolved "https://registry.npmmirror.com/semver/-/semver-7.7.2.tgz"
+ integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
+
+shebang-command@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz"
+ integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+ dependencies:
+ shebang-regex "^3.0.0"
+
+shebang-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz"
+ integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+signal-exit@^4.0.1:
+ version "4.1.0"
+ resolved "https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz"
+ integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==
+
+source-map-js@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz"
+ integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
+
+"string-width-cjs@npm:string-width@^4.2.0":
+ version "4.2.3"
+ resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^4.1.0:
+ version "4.2.3"
+ resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^5.0.1, string-width@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz"
+ integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==
+ dependencies:
+ eastasianwidth "^0.2.0"
+ emoji-regex "^9.2.2"
+ strip-ansi "^7.0.1"
+
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^7.0.1:
+ version "7.1.0"
+ resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.0.tgz"
+ integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==
+ dependencies:
+ ansi-regex "^6.0.1"
+
+strip-json-comments@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
+ integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+strip-literal@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.npmmirror.com/strip-literal/-/strip-literal-2.1.1.tgz"
+ integrity sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==
+ dependencies:
+ js-tokens "^9.0.1"
+
+sucrase@^3.35.0:
+ version "3.35.0"
+ resolved "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.0.tgz"
+ integrity sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.2"
+ commander "^4.0.0"
+ glob "^10.3.10"
+ lines-and-columns "^1.1.6"
+ mz "^2.7.0"
+ pirates "^4.0.1"
+ ts-interface-checker "^0.1.9"
+
+supports-color@^7.1.0:
+ version "7.2.0"
+ resolved "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz"
+ integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+ dependencies:
+ has-flag "^4.0.0"
+
+supports-preserve-symlinks-flag@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz"
+ integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+tailwindcss@^3.3.6:
+ version "3.4.17"
+ resolved "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.17.tgz"
+ integrity sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==
+ dependencies:
+ "@alloc/quick-lru" "^5.2.0"
+ arg "^5.0.2"
+ chokidar "^3.6.0"
+ didyoumean "^1.2.2"
+ dlv "^1.1.3"
+ fast-glob "^3.3.2"
+ glob-parent "^6.0.2"
+ is-glob "^4.0.3"
+ jiti "^1.21.6"
+ lilconfig "^3.1.3"
+ micromatch "^4.0.8"
+ normalize-path "^3.0.0"
+ object-hash "^3.0.0"
+ picocolors "^1.1.1"
+ postcss "^8.4.47"
+ postcss-import "^15.1.0"
+ postcss-js "^4.0.1"
+ postcss-load-config "^4.0.2"
+ postcss-nested "^6.2.0"
+ postcss-selector-parser "^6.1.2"
+ resolve "^1.22.8"
+ sucrase "^3.35.0"
+
+text-table@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz"
+ integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
+
+thenify-all@^1.0.0:
+ version "1.6.0"
+ resolved "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz"
+ integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==
+ dependencies:
+ thenify ">= 3.1.0 < 4"
+
+"thenify@>= 3.1.0 < 4":
+ version "3.3.1"
+ resolved "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz"
+ integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==
+ dependencies:
+ any-promise "^1.0.0"
+
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
+ts-interface-checker@^0.1.9:
+ version "0.1.13"
+ resolved "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"
+ integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
+
+type-check@^0.4.0, type-check@~0.4.0:
+ version "0.4.0"
+ resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz"
+ integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+ dependencies:
+ prelude-ls "^1.2.1"
+
+type-fest@^0.20.2:
+ version "0.20.2"
+ resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz"
+ integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+
+ufo@^1.5.4:
+ version "1.6.1"
+ resolved "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz"
+ integrity sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==
+
+unimport@^3.7.2:
+ version "3.14.6"
+ resolved "https://registry.npmmirror.com/unimport/-/unimport-3.14.6.tgz"
+ integrity sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==
+ dependencies:
+ "@rollup/pluginutils" "^5.1.4"
+ acorn "^8.14.0"
+ escape-string-regexp "^5.0.0"
+ estree-walker "^3.0.3"
+ fast-glob "^3.3.3"
+ local-pkg "^1.0.0"
+ magic-string "^0.30.17"
+ mlly "^1.7.4"
+ pathe "^2.0.1"
+ picomatch "^4.0.2"
+ pkg-types "^1.3.0"
+ scule "^1.3.0"
+ strip-literal "^2.1.1"
+ unplugin "^1.16.1"
+
+unplugin-auto-import@^0.17.2:
+ version "0.17.8"
+ resolved "https://registry.npmmirror.com/unplugin-auto-import/-/unplugin-auto-import-0.17.8.tgz"
+ integrity sha512-CHryj6HzJ+n4ASjzwHruD8arhbdl+UXvhuAIlHDs15Y/IMecG3wrf7FVg4pVH/DIysbq/n0phIjNHAjl7TG7Iw==
+ dependencies:
+ "@antfu/utils" "^0.7.10"
+ "@rollup/pluginutils" "^5.1.0"
+ fast-glob "^3.3.2"
+ local-pkg "^0.5.0"
+ magic-string "^0.30.10"
+ minimatch "^9.0.4"
+ unimport "^3.7.2"
+ unplugin "^1.11.0"
+
+unplugin-element-plus@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.npmmirror.com/unplugin-element-plus/-/unplugin-element-plus-0.8.0.tgz"
+ integrity sha512-jByUGY3FG2B8RJKFryqxx4eNtSTj+Hjlo8edcOdJymewndDQjThZ1pRUQHRjQsbKhTV2jEctJV7t7RJ405UL4g==
+ dependencies:
+ "@rollup/pluginutils" "^5.0.2"
+ es-module-lexer "^1.3.0"
+ magic-string "^0.30.1"
+ unplugin "^1.3.2"
+
+unplugin-vue-components@^0.26.0:
+ version "0.26.0"
+ resolved "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-0.26.0.tgz"
+ integrity sha512-s7IdPDlnOvPamjunVxw8kNgKNK8A5KM1YpK5j/p97jEKTjlPNrA0nZBiSfAKKlK1gWZuyWXlKL5dk3EDw874LQ==
+ dependencies:
+ "@antfu/utils" "^0.7.6"
+ "@rollup/pluginutils" "^5.0.4"
+ chokidar "^3.5.3"
+ debug "^4.3.4"
+ fast-glob "^3.3.1"
+ local-pkg "^0.4.3"
+ magic-string "^0.30.3"
+ minimatch "^9.0.3"
+ resolve "^1.22.4"
+ unplugin "^1.4.0"
+
+unplugin@^1.11.0, unplugin@^1.16.1, unplugin@^1.3.2, unplugin@^1.4.0:
+ version "1.16.1"
+ resolved "https://registry.npmmirror.com/unplugin/-/unplugin-1.16.1.tgz"
+ integrity sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==
+ dependencies:
+ acorn "^8.14.0"
+ webpack-virtual-modules "^0.6.2"
+
+update-browserslist-db@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz"
+ integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==
+ dependencies:
+ escalade "^3.2.0"
+ picocolors "^1.1.1"
+
+uri-js@^4.2.2:
+ version "4.4.1"
+ resolved "https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz"
+ integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+ dependencies:
+ punycode "^2.1.0"
+
+util-deprecate@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz"
+ integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
+
+"vite@^4.0.0 || ^5.0.0", vite@^5.0.8:
+ version "5.4.19"
+ resolved "https://registry.npmmirror.com/vite/-/vite-5.4.19.tgz"
+ integrity sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==
+ dependencies:
+ esbuild "^0.21.3"
+ postcss "^8.4.43"
+ rollup "^4.20.0"
+ optionalDependencies:
+ fsevents "~2.3.3"
+
+vue-demi@*, vue-demi@^0.14.10:
+ version "0.14.10"
+ resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz"
+ integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==
+
+vue-eslint-parser@^9.4.3:
+ version "9.4.3"
+ resolved "https://registry.npmmirror.com/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz"
+ integrity sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==
+ dependencies:
+ debug "^4.3.4"
+ eslint-scope "^7.1.1"
+ eslint-visitor-keys "^3.3.0"
+ espree "^9.3.1"
+ esquery "^1.4.0"
+ lodash "^4.17.21"
+ semver "^7.3.6"
+
+vue-router@^4.2.5:
+ version "4.5.1"
+ resolved "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz"
+ integrity sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==
+ dependencies:
+ "@vue/devtools-api" "^6.6.4"
+
+"vue@^2.7.0 || ^3.5.11", "vue@^3.0.0-0 || ^2.6.0", vue@^3.2.0, vue@^3.2.25, vue@^3.3.4, "vue@2 || 3", vue@3.5.18:
+ version "3.5.18"
+ resolved "https://registry.npmmirror.com/vue/-/vue-3.5.18.tgz"
+ integrity sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==
+ dependencies:
+ "@vue/compiler-dom" "3.5.18"
+ "@vue/compiler-sfc" "3.5.18"
+ "@vue/runtime-dom" "3.5.18"
+ "@vue/server-renderer" "3.5.18"
+ "@vue/shared" "3.5.18"
+
+webpack-virtual-modules@^0.6.2:
+ version "0.6.2"
+ resolved "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz"
+ integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==
+
+which@^2.0.1:
+ version "2.0.2"
+ resolved "https://registry.npmmirror.com/which/-/which-2.0.2.tgz"
+ integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+ dependencies:
+ isexe "^2.0.0"
+
+word-wrap@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz"
+ integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
+
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
+wrap-ansi@^8.1.0:
+ version "8.1.0"
+ resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz"
+ integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==
+ dependencies:
+ ansi-styles "^6.1.0"
+ string-width "^5.0.1"
+ strip-ansi "^7.0.1"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz"
+ integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+xml-name-validator@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz"
+ integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
+
+yaml@^2.3.4:
+ version "2.8.0"
+ resolved "https://registry.npmmirror.com/yaml/-/yaml-2.8.0.tgz"
+ integrity sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==
+
+yocto-queue@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz"
+ integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/web/admin/app.js b/web/admin/app.js
deleted file mode 100644
index d1493215..00000000
--- a/web/admin/app.js
+++ /dev/null
@@ -1,4220 +0,0 @@
-/* global Vue, Chart, ElementPlus, ElementPlusLocaleZhCn, FileReader, document, localStorage, location, navigator, window */
-const { createApp } = Vue;
-
-const app = createApp({
- data() {
- return {
- isLoggedIn: false,
- authToken: localStorage.getItem('authToken'),
- activeTab: 'dashboard',
-
- // Toast 通知
- toasts: [],
- toastIdCounter: 0,
-
- // 登录相关
- loginForm: {
- username: '',
- password: ''
- },
- loginLoading: false,
- loginError: '',
-
- // 标签页
- tabs: [
- { key: 'dashboard', name: '仪表板', icon: 'fas fa-tachometer-alt' },
- { key: 'apiKeys', name: 'API Keys', icon: 'fas fa-key' },
- { key: 'accounts', name: '账户管理', icon: 'fas fa-user-circle' },
- { key: 'tutorial', name: '使用教程', icon: 'fas fa-graduation-cap' },
- { key: 'settings', name: '其他设置', icon: 'fas fa-cogs' }
- ],
-
- // 教程系统选择
- activeTutorialSystem: 'windows',
- tutorialSystems: [
- { key: 'windows', name: 'Windows', icon: 'fab fa-windows' },
- { key: 'macos', name: 'macOS', icon: 'fab fa-apple' },
- { key: 'linux', name: 'Linux / WSL2', icon: 'fab fa-ubuntu' }
- ],
-
- // 模型统计
- modelStats: [],
- modelStatsLoading: false,
- modelStatsPeriod: 'daily',
-
- // 数据
- dashboardData: {
- totalApiKeys: 0,
- activeApiKeys: 0,
- totalAccounts: 0,
- activeAccounts: 0,
- todayRequests: 0,
- totalRequests: 0,
- todayTokens: 0,
- todayInputTokens: 0,
- todayOutputTokens: 0,
- totalTokens: 0,
- totalInputTokens: 0,
- totalOutputTokens: 0,
- totalCacheCreateTokens: 0,
- totalCacheReadTokens: 0,
- todayCacheCreateTokens: 0,
- todayCacheReadTokens: 0,
- systemRPM: 0,
- systemTPM: 0,
- systemStatus: '正常',
- uptime: 0
- },
-
- // 价格数据
- costsData: {
- todayCosts: { totalCost: 0, formatted: { totalCost: '$0.000000' } },
- totalCosts: { totalCost: 0, formatted: { totalCost: '$0.000000' } }
- },
-
- // 仪表盘模型统计
- dashboardModelStats: [],
- dashboardModelPeriod: 'daily',
- modelUsageChart: null,
- usageTrendChart: null,
- trendPeriod: 7,
- trendData: [],
- trendGranularity: 'day', // 新增:趋势图粒度(day/hour)
-
- // API Keys 使用趋势
- apiKeysUsageTrendChart: null,
- apiKeysTrendData: {
- data: [],
- topApiKeys: [],
- totalApiKeys: 0
- },
- apiKeysTrendMetric: 'requests', // 'requests' 或 'tokens' - 默认显示请求次数
-
- // 统一的日期筛选
- dateFilter: {
- type: 'preset', // preset 或 custom
- preset: '7days', // today, 7days, 30days
- customStart: '',
- customEnd: '',
- customRange: null, // Element Plus日期范围选择器的值
- presetOptions: [
- { value: 'today', label: '今天', days: 1 },
- { value: '7days', label: '近7天', days: 7 },
- { value: '30days', label: '近30天', days: 30 }
- ]
- },
- defaultTime: [
- new Date(2000, 1, 1, 0, 0, 0),
- new Date(2000, 2, 1, 23, 59, 59),
- ],
- showDateRangePicker: false, // 日期范围选择器显示状态
- dateRangeInputValue: '', // 日期范围显示文本
-
- // API Keys
- apiKeys: [],
- apiKeysLoading: false,
- apiKeyStatsTimeRange: 'all', // API Key统计时间范围:all, 7days, monthly
- apiKeysSortBy: '', // 当前排序字段
- apiKeysSortOrder: 'asc', // 排序顺序 'asc' 或 'desc'
- showCreateApiKeyModal: false,
- createApiKeyLoading: false,
- apiKeyForm: {
- name: '',
- tokenLimit: '',
- description: '',
- concurrencyLimit: '',
- rateLimitWindow: '',
- rateLimitRequests: '',
- claudeAccountId: '',
- geminiAccountId: '',
- permissions: 'all', // 'claude', 'gemini', 'all'
- enableModelRestriction: false,
- restrictedModels: [],
- modelInput: '',
- enableClientRestriction: false,
- allowedClients: [],
- expireDuration: '', // 过期时长选择
- customExpireDate: '', // 自定义过期日期
- expiresAt: null, // 实际的过期时间戳
- dailyCostLimit: '' // 每日费用限制
- },
- apiKeyModelStats: {}, // 存储每个key的模型统计数据
- expandedApiKeys: {}, // 跟踪展开的API Keys
- apiKeyModelPeriod: 'monthly', // API Key模型统计期间
-
- // API Keys的日期筛选(每个API Key独立)
- apiKeyDateFilters: {}, // 存储每个API Key的独立日期筛选状态
- apiKeyDateFilterDefaults: {
- type: 'preset', // preset 或 custom
- preset: '7days', // today, 7days, 30days
- customStart: '',
- customEnd: '',
- customRange: null, // Element Plus日期范围选择器的值
- presetOptions: [
- { value: 'today', label: '今天', days: 1 },
- { value: '7days', label: '近7天', days: 7 },
- { value: '30days', label: '近30天', days: 30 }
- ]
- },
-
- // 新创建的API Key展示弹窗
- showNewApiKeyModal: false,
- newApiKey: {
- key: '',
- name: '',
- description: '',
- showFullKey: false
- },
-
- // API Key续期
- showRenewApiKeyModal: false,
- renewApiKeyLoading: false,
- renewApiKeyForm: {
- id: '',
- name: '',
- currentExpiresAt: null,
- renewDuration: '30d',
- customExpireDate: '',
- newExpiresAt: null
- },
-
- // 编辑API Key
- showEditApiKeyModal: false,
- editApiKeyLoading: false,
- editApiKeyForm: {
- id: '',
- name: '',
- tokenLimit: '',
- concurrencyLimit: '',
- rateLimitWindow: '',
- rateLimitRequests: '',
- claudeAccountId: '',
- geminiAccountId: '',
- permissions: 'all',
- enableModelRestriction: false,
- restrictedModels: [],
- modelInput: '',
- enableClientRestriction: false,
- allowedClients: [],
- dailyCostLimit: ''
- },
-
- // 支持的客户端列表
- supportedClients: [],
-
- // 账户
- accounts: [],
- accountsLoading: false,
- accountSortBy: 'dailyTokens', // 默认按今日Token排序
- accountsSortOrder: 'asc', // 排序顺序 'asc' 或 'desc'
- showCreateAccountModal: false,
- createAccountLoading: false,
- accountForm: {
- platform: 'claude', // 'claude' 或 'gemini'
- name: '',
- description: '',
- addType: 'oauth', // 'oauth' 或 'manual'
- accountType: 'shared', // 'shared' 或 'dedicated'
- accessToken: '',
- refreshToken: '',
- proxyType: '',
- proxyHost: '',
- proxyPort: '',
- proxyUsername: '',
- proxyPassword: '',
- projectId: '' // Gemini 项目编号
- },
-
- // 编辑账户相关
- showEditAccountModal: false,
- editAccountLoading: false,
- editAccountForm: {
- id: '',
- platform: 'claude',
- name: '',
- description: '',
- accountType: 'shared',
- originalAccountType: 'shared',
- accessToken: '',
- refreshToken: '',
- proxyType: '',
- proxyHost: '',
- proxyPort: '',
- proxyUsername: '',
- proxyPassword: '',
- projectId: '' // Gemini 项目编号
- },
-
- // OAuth 相关
- oauthStep: 1,
- authUrlLoading: false,
- oauthData: {
- sessionId: '',
- authUrl: '',
- callbackUrl: ''
- },
-
- // Gemini OAuth 相关
- geminiOauthPolling: false,
- geminiOauthInterval: null,
- geminiOauthData: {
- sessionId: '',
- authUrl: '',
- code: ''
- },
-
- // 用户菜单和账户修改相关
- userMenuOpen: false,
- currentUser: {
- username: ''
- },
- showChangePasswordModal: false,
- changePasswordLoading: false,
- changePasswordForm: {
- newUsername: '',
- currentPassword: '',
- newPassword: '',
- confirmPassword: ''
- },
-
- // 确认弹窗相关
- showConfirmModal: false,
- confirmModal: {
- title: '',
- message: '',
- confirmText: '继续',
- cancelText: '取消',
- onConfirm: null,
- onCancel: null
- },
-
- // 版本管理相关
- versionInfo: {
- current: '', // 当前版本
- latest: '', // 最新版本
- hasUpdate: false, // 是否有更新
- checkingUpdate: false, // 正在检查更新
- lastChecked: null, // 上次检查时间
- releaseInfo: null, // 最新版本的发布信息
- githubRepo: 'wei-shaw/claude-relay-service', // GitHub仓库
- showReleaseNotes: false, // 是否显示发布说明
- autoCheckInterval: null, // 自动检查定时器
- noUpdateMessage: false // 显示"已是最新版"提醒
- },
-
- // OEM设置相关
- oemSettings: {
- siteName: 'Claude Relay Service',
- siteIcon: '',
- siteIconData: '', // Base64图标数据
- updatedAt: null
- },
- oemSettingsLoading: false,
- oemSettingsSaving: false
- }
- },
-
- computed: {
- // 动态计算BASE_URL
- currentBaseUrl() {
- return `${window.location.protocol}//${window.location.host}/api/`;
- },
-
- // 排序后的账户列表
- sortedAccounts() {
- if (!this.accountsSortBy) {
- return this.accounts;
- }
-
- return [...this.accounts].sort((a, b) => {
- let aValue = a[this.accountsSortBy];
- let bValue = b[this.accountsSortBy];
-
- // 特殊处理状态字段
- if (this.accountsSortBy === 'status') {
- aValue = a.isActive ? 1 : 0;
- bValue = b.isActive ? 1 : 0;
- }
-
- // 处理字符串比较
- if (typeof aValue === 'string' && typeof bValue === 'string') {
- aValue = aValue.toLowerCase();
- bValue = bValue.toLowerCase();
- }
-
- // 排序
- if (this.accountsSortOrder === 'asc') {
- return aValue > bValue ? 1 : aValue < bValue ? -1 : 0;
- } else {
- return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
- }
- });
- },
-
- // 排序后的API Keys列表
- sortedApiKeys() {
- if (!this.apiKeysSortBy) {
- return this.apiKeys;
- }
-
- return [...this.apiKeys].sort((a, b) => {
- let aValue, bValue;
-
- // 特殊处理不同字段
- switch (this.apiKeysSortBy) {
- case 'status':
- aValue = a.isActive ? 1 : 0;
- bValue = b.isActive ? 1 : 0;
- break;
- case 'cost':
- // 计算费用,转换为数字比较
- aValue = this.calculateApiKeyCostNumber(a.usage);
- bValue = this.calculateApiKeyCostNumber(b.usage);
- break;
- case 'createdAt':
- case 'expiresAt':
- // 日期比较
- aValue = a[this.apiKeysSortBy] ? new Date(a[this.apiKeysSortBy]).getTime() : 0;
- bValue = b[this.apiKeysSortBy] ? new Date(b[this.apiKeysSortBy]).getTime() : 0;
- break;
- default:
- aValue = a[this.apiKeysSortBy];
- bValue = b[this.apiKeysSortBy];
-
- // 处理字符串比较
- if (typeof aValue === 'string' && typeof bValue === 'string') {
- aValue = aValue.toLowerCase();
- bValue = bValue.toLowerCase();
- }
- }
-
- // 排序
- if (this.apiKeysSortOrder === 'asc') {
- return aValue > bValue ? 1 : aValue < bValue ? -1 : 0;
- } else {
- return aValue < bValue ? 1 : aValue > bValue ? -1 : 0;
- }
- });
- },
-
- // 获取专属账号列表
- dedicatedAccounts() {
- return this.accounts.filter(account =>
- account.accountType === 'dedicated' && account.isActive === true
- );
- },
-
- // 计算最小日期时间(当前时间)
- minDateTime() {
- const now = new Date();
- now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
- return now.toISOString().slice(0, 16);
- }
- },
-
- mounted() {
- console.log('Vue app mounted, authToken:', !!this.authToken, 'activeTab:', this.activeTab);
-
- // 从URL参数中读取tab信息
- this.initializeTabFromUrl();
-
- // 初始化防抖函数
- this.setTrendPeriod = this.debounce(this._setTrendPeriod, 300);
-
- // 添加全局点击事件监听器,用于关闭用户菜单
- document.addEventListener('click', (event) => {
- // 检查点击是否在用户菜单区域外部
- const isClickInsideUserMenu = event.target.closest('.user-menu-container');
-
- if (!isClickInsideUserMenu) {
- this.userMenuOpen = false;
- }
- });
-
- // 监听浏览器前进后退按钮事件
- window.addEventListener('popstate', () => {
- this.initializeTabFromUrl();
- });
-
- if (this.authToken) {
- this.isLoggedIn = true;
-
- // 加载当前用户信息
- this.loadCurrentUser();
-
- // 加载版本信息
- this.loadCurrentVersion();
-
- // 初始化日期筛选器和图表数据
- this.initializeDateFilter();
-
- // 预加载账号列表、API Keys和支持的客户端,以便正确显示绑定关系
- Promise.all([
- this.loadAccounts(),
- this.loadApiKeys(),
- this.loadSupportedClients()
- ]).then(() => {
- // 根据当前活跃标签页加载数据
- this.loadCurrentTabData();
- });
-
- // 如果在仪表盘,等待Chart.js加载后初始化图表
- if (this.activeTab === 'dashboard') {
- this.waitForChartJS().then(() => {
- this.loadDashboardModelStats();
- this.loadUsageTrend();
- this.loadApiKeysUsageTrend();
- });
- }
- } else {
- console.log('No auth token found, user needs to login');
- }
-
- // 始终加载OEM设置,无论登录状态
- this.loadOemSettings();
- },
-
- beforeUnmount() {
- this.cleanupCharts();
- // 清理版本检查定时器
- if (this.versionInfo.autoCheckInterval) {
- clearInterval(this.versionInfo.autoCheckInterval);
- }
- },
-
- watch: {
- activeTab: {
- handler(newTab, oldTab) {
- console.log('Tab changed from:', oldTab, 'to:', newTab);
-
- // 如果离开仪表盘标签页,清理图表
- if (oldTab === 'dashboard' && newTab !== 'dashboard') {
- this.cleanupCharts();
- }
-
- this.loadCurrentTabData();
- },
- immediate: false
- },
- 'geminiOauthData.code': {
- handler(newValue) {
- if (newValue) {
- this.handleGeminiAuthCodeInput(newValue);
- }
- }
- }
- },
-
- methods: {
- // 账户列表排序
- sortAccounts(field) {
- if (this.accountsSortBy === field) {
- // 如果点击的是当前排序字段,切换排序顺序
- this.accountsSortOrder = this.accountsSortOrder === 'asc' ? 'desc' : 'asc';
- } else {
- // 如果点击的是新字段,设置为升序
- this.accountsSortBy = field;
- this.accountsSortOrder = 'asc';
- }
- },
-
- // API Keys列表排序
- sortApiKeys(field) {
- if (this.apiKeysSortBy === field) {
- // 如果点击的是当前排序字段,切换排序顺序
- this.apiKeysSortOrder = this.apiKeysSortOrder === 'asc' ? 'desc' : 'asc';
- } else {
- // 如果点击的是新字段,设置为升序
- this.apiKeysSortBy = field;
- this.apiKeysSortOrder = 'asc';
- }
- },
-
- // 从URL读取tab参数并设置activeTab
- initializeTabFromUrl() {
- const urlParams = new URLSearchParams(window.location.search);
- const tabParam = urlParams.get('tab');
-
- // 检查tab参数是否有效
- const validTabs = this.tabs.map(tab => tab.key);
- if (tabParam && validTabs.includes(tabParam)) {
- this.activeTab = tabParam;
- }
- },
-
- // 切换tab并更新URL
- switchTab(tabKey) {
- if (this.activeTab !== tabKey) {
- this.activeTab = tabKey;
- this.updateUrlTab(tabKey);
- }
- },
-
- // 更新URL中的tab参数
- updateUrlTab(tabKey) {
- const url = new URL(window.location.href);
- if (tabKey === 'dashboard') {
- // 如果是默认的dashboard标签,移除tab参数
- url.searchParams.delete('tab');
- } else {
- url.searchParams.set('tab', tabKey);
- }
-
- // 使用pushState更新URL但不刷新页面
- window.history.pushState({}, '', url.toString());
- },
-
- // 统一的API请求方法,处理token过期等错误
- async apiRequest(url, options = {}) {
- try {
- const defaultOptions = {
- headers: {
- 'Authorization': 'Bearer ' + this.authToken,
- 'Content-Type': 'application/json',
- ...options.headers
- },
- ...options
- };
-
- const response = await fetch(url, defaultOptions);
- const data = await response.json();
-
- // 检查是否是token过期错误
- if (!response.ok && (response.status === 401 ||
- (data.error === 'Invalid admin token' ||
- data.message === 'Invalid or expired admin session'))) {
- // 清理本地存储并刷新页面
- localStorage.removeItem('authToken');
- this.authToken = null;
- this.isLoggedIn = false;
- location.reload();
- return null;
- }
-
- return data;
- } catch (error) {
- console.error('API request error:', error);
- throw error;
- }
- },
-
- // 显示确认弹窗
- showConfirm(title, message, confirmText = '继续', cancelText = '取消') {
- return new Promise((resolve) => {
- this.confirmModal = {
- title,
- message,
- confirmText,
- cancelText,
- onConfirm: () => {
- this.showConfirmModal = false;
- resolve(true);
- },
- onCancel: () => {
- this.showConfirmModal = false;
- resolve(false);
- }
- };
- this.showConfirmModal = true;
- });
- },
-
- // 处理确认弹窗确定按钮
- handleConfirmOk() {
- if (this.confirmModal.onConfirm) {
- this.confirmModal.onConfirm();
- }
- },
-
- // 处理确认弹窗取消按钮
- handleConfirmCancel() {
- if (this.confirmModal.onCancel) {
- this.confirmModal.onCancel();
- }
- },
-
- // 获取绑定账号名称
- getBoundAccountName(accountId) {
- const account = this.accounts.find(acc => acc.id === accountId);
- return account ? account.name : '未知账号';
- },
-
- // 获取绑定到特定账号的API Key数量
- getBoundApiKeysCount(accountId) {
- return this.apiKeys.filter(key =>
- key.claudeAccountId === accountId || key.geminiAccountId === accountId
- ).length;
- },
-
- // 添加限制模型
- addRestrictedModel(form) {
- const model = form.modelInput.trim();
- if (model && !form.restrictedModels.includes(model)) {
- form.restrictedModels.push(model);
- form.modelInput = '';
- }
- },
-
- // 移除限制模型
- removeRestrictedModel(form, index) {
- form.restrictedModels.splice(index, 1);
- },
-
- // Toast 通知方法
- showToast(message, type = 'info', title = null, duration = 5000) {
- const id = ++this.toastIdCounter;
- const toast = {
- id,
- message,
- type,
- title,
- show: false
- };
-
- this.toasts.push(toast);
-
- // 延迟显示动画
- setTimeout(() => {
- const toastIndex = this.toasts.findIndex(t => t.id === id);
- if (toastIndex !== -1) {
- this.toasts[toastIndex].show = true;
- }
- }, 100);
-
- // 自动移除
- if (duration > 0) {
- setTimeout(() => {
- this.removeToast(id);
- }, duration);
- }
- },
-
- removeToast(id) {
- const index = this.toasts.findIndex(t => t.id === id);
- if (index !== -1) {
- this.toasts[index].show = false;
- setTimeout(() => {
- const currentIndex = this.toasts.findIndex(t => t.id === id);
- if (currentIndex !== -1) {
- this.toasts.splice(currentIndex, 1);
- }
- }, 300);
- }
- },
-
- getToastIcon(type) {
- switch (type) {
- case 'success': return 'fas fa-check-circle';
- case 'error': return 'fas fa-exclamation-circle';
- case 'warning': return 'fas fa-exclamation-triangle';
- case 'info': return 'fas fa-info-circle';
- default: return 'fas fa-info-circle';
- }
- },
-
- // 打开创建API Key模态框
- openCreateApiKeyModal() {
- console.log('Opening API Key modal...');
- // 先关闭所有其他模态框
- this.showCreateAccountModal = false;
- // 使用nextTick确保状态更新
- this.$nextTick(() => {
- this.showCreateApiKeyModal = true;
- });
- },
-
- // 更新过期时间
- updateExpireAt() {
- const duration = this.apiKeyForm.expireDuration;
- if (!duration) {
- this.apiKeyForm.expiresAt = null;
- return;
- }
-
- if (duration === 'custom') {
- // 自定义日期需要用户选择
- return;
- }
-
- const now = new Date();
- const durationMap = {
- '1d': 1,
- '7d': 7,
- '30d': 30,
- '90d': 90,
- '180d': 180,
- '365d': 365
- };
-
- const days = durationMap[duration];
- if (days) {
- const expireDate = new Date(now.getTime() + days * 24 * 60 * 60 * 1000);
- this.apiKeyForm.expiresAt = expireDate.toISOString();
- }
- },
-
- // 更新自定义过期时间
- updateCustomExpireAt() {
- if (this.apiKeyForm.customExpireDate) {
- const expireDate = new Date(this.apiKeyForm.customExpireDate);
- this.apiKeyForm.expiresAt = expireDate.toISOString();
- }
- },
-
- // 格式化过期日期
- formatExpireDate(dateString) {
- if (!dateString) return '';
- const date = new Date(dateString);
- return date.toLocaleString('zh-CN', {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit'
- });
- },
-
- // 格式化日期时间
- formatDateTime(dateString) {
- if (!dateString) return '';
- const date = new Date(dateString);
- return date.toLocaleString('zh-CN', {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit'
- });
- },
-
- // 检查 API Key 是否已过期
- isApiKeyExpired(expiresAt) {
- if (!expiresAt) return false;
- return new Date(expiresAt) < new Date();
- },
-
- // 检查 API Key 是否即将过期(7天内)
- isApiKeyExpiringSoon(expiresAt) {
- if (!expiresAt) return false;
- const expireDate = new Date(expiresAt);
- const now = new Date();
- const daysUntilExpire = (expireDate - now) / (1000 * 60 * 60 * 24);
- return daysUntilExpire > 0 && daysUntilExpire <= 7;
- },
-
- // 打开创建账户模态框
- openCreateAccountModal() {
- console.log('Opening Account modal...');
- // 先关闭所有其他模态框
- this.showCreateApiKeyModal = false;
- // 使用nextTick确保状态更新
- this.$nextTick(() => {
- this.showCreateAccountModal = true;
- this.resetAccountForm();
- });
- },
-
- // 关闭创建账户模态框
- closeCreateAccountModal() {
- this.showCreateAccountModal = false;
- this.resetAccountForm();
- },
-
- // 打开编辑账户模态框
- openEditAccountModal(account) {
- this.editAccountForm = {
- id: account.id,
- platform: account.platform || 'claude',
- name: account.name,
- description: account.description || '',
- accountType: account.accountType || 'shared',
- originalAccountType: account.accountType || 'shared',
- accessToken: '',
- refreshToken: '',
- proxyType: account.proxy ? account.proxy.type : '',
- proxyHost: account.proxy ? account.proxy.host : '',
- proxyPort: account.proxy ? account.proxy.port : '',
- proxyUsername: account.proxy ? account.proxy.username : '',
- proxyPassword: account.proxy ? account.proxy.password : '',
- projectId: account.projectId || '' // 添加项目编号
- };
- this.showEditAccountModal = true;
- },
-
- // 关闭编辑账户模态框
- closeEditAccountModal() {
- this.showEditAccountModal = false;
- this.editAccountForm = {
- id: '',
- platform: 'claude',
- name: '',
- description: '',
- accountType: 'shared',
- originalAccountType: 'shared',
- accessToken: '',
- refreshToken: '',
- proxyType: '',
- proxyHost: '',
- proxyPort: '',
- proxyUsername: '',
- proxyPassword: '',
- projectId: '' // 重置项目编号
- };
- },
-
- // 更新账户
- async updateAccount() {
- // 对于Gemini账户,检查项目编号
- if (this.editAccountForm.platform === 'gemini') {
- if (!this.editAccountForm.projectId || this.editAccountForm.projectId.trim() === '') {
- // 使用自定义确认弹窗
- const confirmed = await this.showConfirm(
- '项目编号未填写',
- '您尚未填写项目编号。\n\n如果您的Google账号绑定了Google Cloud或被识别为Workspace账号,需要提供项目编号。\n如果您使用的是普通个人账号,可以继续不填写。',
- '继续保存',
- '返回填写'
- );
- if (!confirmed) {
- return;
- }
- }
- }
-
- this.editAccountLoading = true;
- try {
- // 验证账户类型切换
- if (this.editAccountForm.accountType === 'shared' &&
- this.editAccountForm.originalAccountType === 'dedicated') {
- // 确保API Keys数据已加载,以便正确计算绑定数量
- if (this.apiKeys.length === 0) {
- await this.loadApiKeys();
- }
- const boundKeysCount = this.getBoundApiKeysCount(this.editAccountForm.id);
- if (boundKeysCount > 0) {
- this.showToast(`无法切换到共享账户,该账户绑定了 ${boundKeysCount} 个API Key,请先解绑所有API Key`, 'error', '切换失败');
- return;
- }
- }
-
- // 构建更新数据
- let updateData = {
- name: this.editAccountForm.name,
- description: this.editAccountForm.description,
- accountType: this.editAccountForm.accountType,
- projectId: this.editAccountForm.projectId || '' // 添加项目编号
- };
-
- // 只在有值时才更新 token
- if (this.editAccountForm.accessToken.trim()) {
- if (this.editAccountForm.platform === 'gemini') {
- // Gemini OAuth 数据格式
- // 如果有 Refresh Token,设置10分钟过期;否则设置1年
- const expiresInMs = this.editAccountForm.refreshToken
- ? (10 * 60 * 1000) // 10分钟
- : (365 * 24 * 60 * 60 * 1000); // 1年
-
- const newOauthData = {
- access_token: this.editAccountForm.accessToken,
- refresh_token: this.editAccountForm.refreshToken || '',
- scope: 'https://www.googleapis.com/auth/cloud-platform',
- token_type: 'Bearer',
- expiry_date: Date.now() + expiresInMs
- };
- updateData.geminiOauth = newOauthData;
- } else {
- // Claude OAuth 数据格式
- // 如果有 Refresh Token,设置10分钟过期;否则设置1年
- const expiresInMs = this.editAccountForm.refreshToken
- ? (10 * 60 * 1000) // 10分钟
- : (365 * 24 * 60 * 60 * 1000); // 1年
-
- const newOauthData = {
- accessToken: this.editAccountForm.accessToken,
- refreshToken: this.editAccountForm.refreshToken || '',
- expiresAt: Date.now() + expiresInMs,
- scopes: ['user:inference']
- };
- updateData.claudeAiOauth = newOauthData;
- }
- }
-
- // 更新代理配置
- if (this.editAccountForm.proxyType) {
- updateData.proxy = {
- type: this.editAccountForm.proxyType,
- host: this.editAccountForm.proxyHost,
- port: parseInt(this.editAccountForm.proxyPort),
- username: this.editAccountForm.proxyUsername || null,
- password: this.editAccountForm.proxyPassword || null
- };
- } else {
- updateData.proxy = null;
- }
-
- // 根据平台选择端点
- const endpoint = this.editAccountForm.platform === 'gemini'
- ? `/admin/gemini-accounts/${this.editAccountForm.id}`
- : `/admin/claude-accounts/${this.editAccountForm.id}`;
-
- const response = await fetch(endpoint, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': 'Bearer ' + this.authToken
- },
- body: JSON.stringify(updateData)
- });
-
- const data = await response.json();
-
- if (data.success) {
- this.showToast('账户更新成功!', 'success', '更新成功');
- this.closeEditAccountModal();
- await this.loadAccounts();
- } else {
- this.showToast(data.message || 'Account update failed', 'error', 'Update Failed');
- }
- } catch (error) {
- console.error('Error updating account:', error);
-
- let errorMessage = '更新失败,请检查网络连接';
-
- if (error.response) {
- try {
- const errorData = await error.response.json();
- errorMessage = errorData.message || errorMessage;
- } catch (parseError) {
- console.error('Failed to parse error response:', parseError);
- }
- } else if (error.message) {
- errorMessage = error.message;
- }
-
- this.showToast(errorMessage, 'error', '网络错误', 8000);
- } finally {
- this.editAccountLoading = false;
- }
- },
-
- // 重置账户表单
- resetAccountForm() {
- this.accountForm = {
- platform: 'claude',
- name: '',
- description: '',
- addType: 'oauth',
- accountType: 'shared',
- accessToken: '',
- refreshToken: '',
- proxyType: '',
- proxyHost: '',
- proxyPort: '',
- proxyUsername: '',
- proxyPassword: '',
- projectId: '' // 重置项目编号
- };
- this.oauthStep = 1;
- this.oauthData = {
- sessionId: '',
- authUrl: '',
- callbackUrl: ''
- };
- this.geminiOauthData = {
- sessionId: '',
- authUrl: '',
- code: ''
- };
- // 停止 Gemini OAuth 轮询
- if (this.geminiOauthInterval) {
- clearInterval(this.geminiOauthInterval);
- this.geminiOauthInterval = null;
- }
- this.geminiOauthPolling = false;
- },
-
- // OAuth步骤前进
- async nextOAuthStep() {
- // 对于Gemini账户,检查项目编号
- if (this.accountForm.platform === 'gemini' && this.oauthStep === 1 && this.accountForm.addType === 'oauth') {
- if (!this.accountForm.projectId || this.accountForm.projectId.trim() === '') {
- // 使用自定义确认弹窗
- const confirmed = await this.showConfirm(
- '项目编号未填写',
- '您尚未填写项目编号。\n\n如果您的Google账号绑定了Google Cloud或被识别为Workspace账号,需要提供项目编号。\n如果您使用的是普通个人账号,可以继续不填写。',
- '继续',
- '返回填写'
- );
- if (!confirmed) {
- return;
- }
- }
- }
-
- if (this.oauthStep < 3) {
- this.oauthStep++;
- }
- },
-
- // 生成OAuth授权URL
- async generateAuthUrl() {
- this.authUrlLoading = true;
- try {
- // Build proxy configuration
- let proxy = null;
- if (this.accountForm.proxyType) {
- proxy = {
- type: this.accountForm.proxyType,
- host: this.accountForm.proxyHost,
- port: parseInt(this.accountForm.proxyPort),
- username: this.accountForm.proxyUsername || null,
- password: this.accountForm.proxyPassword || null
- };
- }
-
- const endpoint = this.accountForm.platform === 'gemini'
- ? '/admin/gemini-accounts/generate-auth-url'
- : '/admin/claude-accounts/generate-auth-url';
-
- const data = await this.apiRequest(endpoint, {
- method: 'POST',
- body: JSON.stringify({
- proxy: proxy
- })
- });
-
- if (!data) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (data.success) {
- if (this.accountForm.platform === 'gemini') {
- this.geminiOauthData.authUrl = data.data.authUrl;
- this.geminiOauthData.sessionId = data.data.sessionId;
- // 不再自动开始轮询,改为手动输入授权码
- } else {
- this.oauthData.authUrl = data.data.authUrl;
- this.oauthData.sessionId = data.data.sessionId;
- }
- this.showToast('授权链接生成成功!', 'success', '生成成功');
- } else {
- this.showToast(data.message || '生成失败', 'error', '生成失败');
- }
- } catch (error) {
- console.error('Error generating auth URL:', error);
- this.showToast('生成失败,请检查网络连接', 'error', '网络错误');
- } finally {
- this.authUrlLoading = false;
- }
- },
-
- // 复制到剪贴板
- async copyToClipboard(text) {
- try {
- await navigator.clipboard.writeText(text);
- this.showToast('已复制到剪贴板', 'success', '复制成功');
- } catch (error) {
- console.error('Copy failed:', error);
- this.showToast('复制失败', 'error', '复制失败');
- }
- },
-
- // 创建OAuth账户
- async createOAuthAccount() {
- // 如果是 Gemini,不应该调用这个方法
- if (this.accountForm.platform === 'gemini') {
- console.error('createOAuthAccount should not be called for Gemini');
- return;
- }
-
- this.createAccountLoading = true;
- try {
- // 首先交换authorization code获取token
- const exchangeData = await this.apiRequest('/admin/claude-accounts/exchange-code', {
- method: 'POST',
- body: JSON.stringify({
- sessionId: this.oauthData.sessionId,
- callbackUrl: this.oauthData.callbackUrl
- })
- });
-
- if (!exchangeData) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (!exchangeData.success) {
- // Display detailed error information
- const errorMsg = exchangeData.message || 'Token exchange failed';
- this.showToast('Authorization failed: ' + errorMsg, 'error', 'Authorization Failed', 8000);
- console.error('OAuth exchange failed:', exchangeData);
- return;
- }
-
- // Build proxy configuration
- let proxy = null;
- if (this.accountForm.proxyType) {
- proxy = {
- type: this.accountForm.proxyType,
- host: this.accountForm.proxyHost,
- port: parseInt(this.accountForm.proxyPort),
- username: this.accountForm.proxyUsername || null,
- password: this.accountForm.proxyPassword || null
- };
- }
-
- // 创建账户
- const createData = await this.apiRequest('/admin/claude-accounts', {
- method: 'POST',
- body: JSON.stringify({
- name: this.accountForm.name,
- description: this.accountForm.description,
- claudeAiOauth: exchangeData.data.claudeAiOauth,
- proxy: proxy,
- accountType: this.accountForm.accountType
- })
- });
-
- if (!createData) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (createData.success) {
- this.showToast('OAuth账户创建成功!', 'success', '账户创建成功');
- this.closeCreateAccountModal();
- await this.loadAccounts();
- } else {
- this.showToast(createData.message || 'Account creation failed', 'error', 'Creation Failed');
- }
- } catch (error) {
- console.error('Error creating OAuth account:', error);
-
- // 尝试从错误响应中提取更详细的信息
- let errorMessage = '创建失败,请检查网络连接';
-
- if (error.response) {
- try {
- const errorData = await error.response.json();
- errorMessage = errorData.message || errorMessage;
- } catch (parseError) {
- // 如果无法解析JSON,使用默认消息
- console.error('Failed to parse error response:', parseError);
- }
- } else if (error.message) {
- errorMessage = error.message;
- }
-
- this.showToast(errorMessage, 'error', '网络错误', 8000);
- } finally {
- this.createAccountLoading = false;
- }
- },
-
- // 创建手动账户
- async createManualAccount() {
- this.createAccountLoading = true;
- try {
- // 构建代理配置
- let proxy = null;
- if (this.accountForm.proxyType) {
- proxy = {
- type: this.accountForm.proxyType,
- host: this.accountForm.proxyHost,
- port: parseInt(this.accountForm.proxyPort),
- username: this.accountForm.proxyUsername || null,
- password: this.accountForm.proxyPassword || null
- };
- }
-
- // 根据平台构建 OAuth 数据
- let endpoint, bodyData;
-
- if (this.accountForm.platform === 'gemini') {
- // Gemini 账户
- // 如果有 Refresh Token,设置10分钟过期;否则设置1年
- const expiresInMs = this.accountForm.refreshToken
- ? (10 * 60 * 1000) // 10分钟
- : (365 * 24 * 60 * 60 * 1000); // 1年
-
- const geminiOauthData = {
- access_token: this.accountForm.accessToken,
- refresh_token: this.accountForm.refreshToken || '',
- scope: 'https://www.googleapis.com/auth/cloud-platform',
- token_type: 'Bearer',
- expiry_date: Date.now() + expiresInMs
- };
-
- endpoint = '/admin/gemini-accounts';
- bodyData = {
- name: this.accountForm.name,
- description: this.accountForm.description,
- geminiOauth: geminiOauthData,
- proxy: proxy,
- accountType: this.accountForm.accountType,
- projectId: this.accountForm.projectId || '' // 添加项目编号
- };
- } else {
- // Claude 账户
- // 如果有 Refresh Token,设置10分钟过期;否则设置1年
- const expiresInMs = this.accountForm.refreshToken
- ? (10 * 60 * 1000) // 10分钟
- : (365 * 24 * 60 * 60 * 1000); // 1年
-
- const manualOauthData = {
- accessToken: this.accountForm.accessToken,
- refreshToken: this.accountForm.refreshToken || '',
- expiresAt: Date.now() + expiresInMs,
- scopes: ['user:inference'] // 默认权限
- };
-
- endpoint = '/admin/claude-accounts';
- bodyData = {
- name: this.accountForm.name,
- description: this.accountForm.description,
- claudeAiOauth: manualOauthData,
- proxy: proxy,
- accountType: this.accountForm.accountType
- };
- }
-
- // 创建账户
- const createResponse = await fetch(endpoint, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': 'Bearer ' + this.authToken
- },
- body: JSON.stringify(bodyData)
- });
-
- const createData = await createResponse.json();
-
- if (createData.success) {
- this.showToast('手动账户创建成功!', 'success', '账户创建成功');
- this.closeCreateAccountModal();
- await this.loadAccounts();
- } else {
- this.showToast(createData.message || 'Account creation failed', 'error', 'Creation Failed');
- }
- } catch (error) {
- console.error('Error creating manual account:', error);
-
- let errorMessage = '创建失败,请检查网络连接';
-
- if (error.response) {
- try {
- const errorData = await error.response.json();
- errorMessage = errorData.message || errorMessage;
- } catch (parseError) {
- console.error('Failed to parse error response:', parseError);
- }
- } else if (error.message) {
- errorMessage = error.message;
- }
-
- this.showToast(errorMessage, 'error', '网络错误', 8000);
- } finally {
- this.createAccountLoading = false;
- }
- },
-
- // Gemini OAuth 轮询
- async startGeminiOAuthPolling() {
- if (this.geminiOauthPolling) return;
-
- this.geminiOauthPolling = true;
- let attempts = 0;
- const maxAttempts = 30; // 最多轮询 30 次(60秒)
-
- this.geminiOauthInterval = setInterval(async () => {
- attempts++;
-
- try {
- const data = await this.apiRequest('/admin/gemini-accounts/poll-auth-status', {
- method: 'POST',
- body: JSON.stringify({
- sessionId: this.geminiOauthData.sessionId
- })
- });
-
- if (!data) {
- // 如果token过期,apiRequest会返回null并刷新页面
- this.stopGeminiOAuthPolling();
- return;
- }
-
- if (data.success) {
- // 授权成功
- this.stopGeminiOAuthPolling();
- this.geminiOauthData.code = 'authorized';
-
- // 自动创建账户
- await this.createGeminiOAuthAccount(data.data.tokens);
- } else if (data.error === 'Authorization timeout' || attempts >= maxAttempts) {
- // 超时
- this.stopGeminiOAuthPolling();
- this.showToast('授权超时,请重试', 'error', '授权超时');
- }
- } catch (error) {
- console.error('Polling error:', error);
- if (attempts >= maxAttempts) {
- this.stopGeminiOAuthPolling();
- this.showToast('轮询失败,请检查网络连接', 'error', '网络错误');
- }
- }
- }, 2000); // 每2秒轮询一次
- },
-
- stopGeminiOAuthPolling() {
- if (this.geminiOauthInterval) {
- clearInterval(this.geminiOauthInterval);
- this.geminiOauthInterval = null;
- }
- this.geminiOauthPolling = false;
- },
-
- // 创建 Gemini OAuth 账户
- async createGeminiOAuthAccount() {
- this.createAccountLoading = true;
- try {
- // 首先交换授权码获取 tokens
- const tokenData = await this.apiRequest('/admin/gemini-accounts/exchange-code', {
- method: 'POST',
- body: JSON.stringify({
- code: this.geminiOauthData.code,
- sessionId: this.geminiOauthData.sessionId
- })
- });
-
- if (!tokenData) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (!tokenData.success) {
- this.showToast(tokenData.message || '授权码交换失败', 'error', '交换失败');
- return;
- }
-
- // 构建代理配置
- let proxy = null;
- if (this.accountForm.proxyType) {
- proxy = {
- type: this.accountForm.proxyType,
- host: this.accountForm.proxyHost,
- port: parseInt(this.accountForm.proxyPort),
- username: this.accountForm.proxyUsername || null,
- password: this.accountForm.proxyPassword || null
- };
- }
-
- // 创建账户
- const data = await this.apiRequest('/admin/gemini-accounts', {
- method: 'POST',
- body: JSON.stringify({
- name: this.accountForm.name,
- description: this.accountForm.description,
- geminiOauth: tokenData.data.tokens,
- proxy: proxy,
- accountType: this.accountForm.accountType,
- projectId: this.accountForm.projectId || '' // 添加项目编号
- })
- });
-
- if (!data) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (data.success) {
- this.showToast('Gemini OAuth账户创建成功!', 'success', '账户创建成功');
- this.closeCreateAccountModal();
- await this.loadAccounts();
- } else {
- this.showToast(data.message || 'Account creation failed', 'error', 'Creation Failed');
- }
- } catch (error) {
- console.error('Error creating Gemini OAuth account:', error);
- this.showToast('创建失败,请检查网络连接', 'error', '网络错误', 8000);
- } finally {
- this.createAccountLoading = false;
- }
- },
-
- // 处理 Gemini OAuth 授权码输入
- handleGeminiAuthCodeInput(value, isUserTyping = false) {
- if (!value || typeof value !== 'string') return;
-
- const trimmedValue = value.trim();
-
- // 如果内容为空,不处理
- if (!trimmedValue) return;
-
- // 检查是否是 URL 格式(包含 http:// 或 https://)
- const isUrl = trimmedValue.startsWith('http://') || trimmedValue.startsWith('https://');
-
- // 如果是 URL 格式
- if (isUrl) {
- // 检查是否是正确的 localhost:45462 开头的 URL
- if (trimmedValue.startsWith('http://localhost:45462')) {
- try {
- const url = new URL(trimmedValue);
- const code = url.searchParams.get('code');
-
- if (code) {
- // 成功提取授权码
- this.geminiOauthData.code = code;
- this.showToast('成功提取授权码!', 'success', '提取成功');
- console.log('Successfully extracted authorization code from URL');
- } else {
- // URL 中没有 code 参数
- this.showToast('URL 中未找到授权码参数,请检查链接是否正确', 'error', '提取失败');
- }
- } catch (error) {
- // URL 解析失败
- console.error('Failed to parse URL:', error);
- this.showToast('链接格式错误,请检查是否为完整的 URL', 'error', '解析失败');
- }
- } else {
- // 错误的 URL(不是 localhost:45462 开头)
- this.showToast('请粘贴以 http://localhost:45462 开头的链接', 'error', '链接错误');
- }
- }
- // 如果不是 URL,保持原值(兼容直接输入授权码)
- },
-
- // 根据当前标签页加载数据
- loadCurrentTabData() {
- console.log('Loading current tab data for:', this.activeTab);
- switch (this.activeTab) {
- case 'dashboard':
- this.loadDashboard();
- // 加载图表数据,等待Chart.js
- this.waitForChartJS().then(() => {
- this.loadDashboardModelStats();
- this.loadUsageTrend();
- this.loadApiKeysUsageTrend();
- });
- break;
- case 'apiKeys':
- // 加载API Keys时同时加载账号列表,以便显示绑定账号名称
- Promise.all([
- this.loadApiKeys(),
- this.loadAccounts()
- ]);
- break;
- case 'accounts':
- // 加载账户时同时加载API Keys,以便正确计算绑定数量
- Promise.all([
- this.loadAccounts(),
- this.loadApiKeys()
- ]);
- break;
- case 'models':
- this.loadModelStats();
- break;
- case 'tutorial':
- // 教程页面不需要加载数据
- break;
- case 'settings':
- // OEM 设置已在 mounted 时加载,避免重复加载
- if (!this.oemSettings.siteName && !this.oemSettings.siteIcon && !this.oemSettings.siteIconData) {
- this.loadOemSettings();
- }
- break;
- }
- },
-
- // 等待Chart.js加载完成
- waitForChartJS() {
- return new Promise((resolve) => {
- const checkChart = () => {
- if (typeof Chart !== 'undefined') {
- resolve();
- } else {
- setTimeout(checkChart, 100);
- }
- };
- checkChart();
- });
- },
-
- // 清理所有图表实例
- cleanupCharts() {
-
- // 清理模型使用图表
- if (this.modelUsageChart) {
- try {
- // 先停止所有动画
- this.modelUsageChart.stop();
- // 再销毁图表
- this.modelUsageChart.destroy();
- } catch (error) {
- console.warn('Error destroying model usage chart:', error);
- }
- this.modelUsageChart = null;
- }
-
- // 清理使用趋势图表
- if (this.usageTrendChart) {
- try {
- // 先停止所有动画
- this.usageTrendChart.stop();
- // 再销毁图表
- this.usageTrendChart.destroy();
- } catch (error) {
- console.warn('Error destroying usage trend chart:', error);
- }
- this.usageTrendChart = null;
- }
-
- // 清理API Keys使用趋势图表
- if (this.apiKeysUsageTrendChart) {
- try {
- // 先停止所有动画
- this.apiKeysUsageTrendChart.stop();
- // 再销毁图表
- this.apiKeysUsageTrendChart.destroy();
- } catch (error) {
- console.warn('Error destroying API keys usage trend chart:', error);
- }
- this.apiKeysUsageTrendChart = null;
- }
- },
-
- // 检查DOM元素是否存在且有效
- isElementValid(elementId) {
- const element = document.getElementById(elementId);
- return element && element.isConnected && element.ownerDocument && element.parentNode;
- },
-
- // 防抖函数,防止快速点击
- debounce(func, wait) {
- let timeout;
- return function executedFunction(...args) {
- const later = () => {
- clearTimeout(timeout);
- func(...args);
- };
- clearTimeout(timeout);
- timeout = setTimeout(later, wait);
- };
- },
-
- async login() {
- this.loginLoading = true;
- this.loginError = '';
-
- try {
- const response = await fetch('/web/auth/login', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(this.loginForm)
- });
-
- const data = await response.json();
-
- if (data.success) {
- this.authToken = data.token;
- localStorage.setItem('authToken', this.authToken);
- this.isLoggedIn = true;
-
- // 记录当前用户名(使用服务器返回的真实用户名)
- this.currentUser.username = data.username;
-
- // 登录成功后刷新页面以重新加载所有数据
- location.reload();
- } else {
- this.loginError = data.message;
- }
- } catch (error) {
- console.error('Login error:', error);
- this.loginError = '登录失败,请检查网络连接';
- } finally {
- this.loginLoading = false;
- }
- },
-
- // 加载当前用户信息
- async loadCurrentUser() {
- try {
- const data = await this.apiRequest('/web/auth/user');
-
- if (!data) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (data.success) {
- this.currentUser.username = data.user.username;
- console.log('Loaded current user:', data.user.username);
- } else {
- console.warn('Failed to load current user:', data.message);
- }
- } catch (error) {
- console.error('Error loading current user:', error);
- }
- },
-
- // 版本管理相关方法
- async loadCurrentVersion() {
- try {
- const response = await fetch('/health');
- const data = await response.json();
-
- if (data.version) {
- // 从健康检查端点获取当前版本
- this.versionInfo.current = data.version;
-
- // 检查更新
- await this.checkForUpdates();
-
- // 设置自动检查更新(每小时检查一次)
- this.versionInfo.autoCheckInterval = setInterval(() => {
- this.checkForUpdates();
- }, 3600000); // 1小时
- }
- } catch (error) {
- console.error('Error loading current version:', error);
- this.versionInfo.current = '未知';
- }
- },
-
- async checkForUpdates() {
- if (this.versionInfo.checkingUpdate) {
- return;
- }
-
- this.versionInfo.checkingUpdate = true;
-
- try {
- // 使用后端接口检查更新
- const result = await this.apiRequest('/admin/check-updates');
-
- if (!result) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (result.success) {
- const data = result.data;
-
- this.versionInfo.current = data.current;
- this.versionInfo.latest = data.latest;
- this.versionInfo.hasUpdate = data.hasUpdate;
- this.versionInfo.releaseInfo = data.releaseInfo;
- this.versionInfo.lastChecked = new Date();
-
- // 保存到localStorage
- localStorage.setItem('versionInfo', JSON.stringify({
- current: data.current,
- latest: data.latest,
- lastChecked: this.versionInfo.lastChecked,
- hasUpdate: data.hasUpdate,
- releaseInfo: data.releaseInfo
- }));
-
- // 如果没有更新,显示提醒
- if (!data.hasUpdate) {
- this.versionInfo.noUpdateMessage = true;
- // 3秒后自动隐藏提醒
- setTimeout(() => {
- this.versionInfo.noUpdateMessage = false;
- }, 3000);
- }
-
- if (data.cached && data.warning) {
- console.warn('Version check warning:', data.warning);
- }
- } else {
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
- }
- } catch (error) {
- console.error('Error checking for updates:', error);
-
- // 尝试从localStorage读取缓存的版本信息
- const cached = localStorage.getItem('versionInfo');
- if (cached) {
- const cachedInfo = JSON.parse(cached);
- this.versionInfo.current = cachedInfo.current || this.versionInfo.current;
- this.versionInfo.latest = cachedInfo.latest;
- this.versionInfo.hasUpdate = cachedInfo.hasUpdate;
- this.versionInfo.releaseInfo = cachedInfo.releaseInfo;
- this.versionInfo.lastChecked = new Date(cachedInfo.lastChecked);
- }
- } finally {
- this.versionInfo.checkingUpdate = false;
- }
- },
-
- compareVersions(current, latest) {
- // 比较语义化版本号
- const parseVersion = (v) => {
- const parts = v.split('.').map(Number);
- return {
- major: parts[0] || 0,
- minor: parts[1] || 0,
- patch: parts[2] || 0
- };
- };
-
- const currentV = parseVersion(current);
- const latestV = parseVersion(latest);
-
- if (currentV.major !== latestV.major) {
- return currentV.major - latestV.major;
- }
- if (currentV.minor !== latestV.minor) {
- return currentV.minor - latestV.minor;
- }
- return currentV.patch - latestV.patch;
- },
-
- formatVersionDate(dateString) {
- if (!dateString) return '';
- const date = new Date(dateString);
- return date.toLocaleDateString('zh-CN', {
- year: 'numeric',
- month: 'long',
- day: 'numeric'
- });
- },
-
- // 用户菜单相关方法
- openChangePasswordModal() {
- this.userMenuOpen = false;
- this.showChangePasswordModal = true;
- },
-
- closeChangePasswordModal() {
- this.showChangePasswordModal = false;
- this.changePasswordForm = {
- newUsername: '',
- currentPassword: '',
- newPassword: '',
- confirmPassword: ''
- };
- },
-
- async changePassword() {
- // 验证表单
- if (this.changePasswordForm.newPassword !== this.changePasswordForm.confirmPassword) {
- this.showToast('新密码和确认密码不一致', 'error');
- return;
- }
-
- if (this.changePasswordForm.newPassword.length < 8) {
- this.showToast('新密码长度至少8位', 'error');
- return;
- }
-
- this.changePasswordLoading = true;
- try {
- const result = await this.apiRequest('/web/auth/change-password', {
- method: 'POST',
- body: JSON.stringify({
- newUsername: this.changePasswordForm.newUsername || undefined,
- currentPassword: this.changePasswordForm.currentPassword,
- newPassword: this.changePasswordForm.newPassword
- })
- });
-
- if (!result) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (result.success) {
- this.showToast('账户信息修改成功,即将退出登录', 'success');
- this.closeChangePasswordModal();
-
- // 将新的用户名更新到本地状态
- if (this.changePasswordForm.newUsername) {
- this.currentUser.username = this.changePasswordForm.newUsername;
- }
-
- // 延迟2秒后自动退出登录
- setTimeout(() => {
- this.logout();
- }, 2000);
- } else {
- this.showToast(result.message || '修改失败', 'error');
- }
- } catch (error) {
- console.error('Change password error:', error);
- this.showToast('网络错误,请稍后再试', 'error');
- } finally {
- this.changePasswordLoading = false;
- }
- },
-
- async logout() {
- if (this.authToken) {
- try {
- await fetch('/web/auth/logout', {
- method: 'POST',
- headers: {
- 'Authorization': 'Bearer ' + this.authToken
- }
- });
- } catch (error) {
- console.error('Logout error:', error);
- }
- }
-
- this.authToken = null;
- localStorage.removeItem('authToken');
- this.isLoggedIn = false;
- this.loginForm = { username: '', password: '' };
- this.loginError = '';
- },
-
- async loadDashboard() {
- try {
- const [dashboardData, todayCostsData, totalCostsData] = await Promise.all([
- this.apiRequest('/admin/dashboard'),
- this.apiRequest('/admin/usage-costs?period=today'),
- this.apiRequest('/admin/usage-costs?period=all')
- ]);
-
- if (!dashboardData || !todayCostsData || !totalCostsData) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (dashboardData.success) {
- const overview = dashboardData.data.overview || {};
- const recentActivity = dashboardData.data.recentActivity || {};
- const systemAverages = dashboardData.data.systemAverages || {};
- const systemHealth = dashboardData.data.systemHealth || {};
-
- this.dashboardData = {
- totalApiKeys: overview.totalApiKeys || 0,
- activeApiKeys: overview.activeApiKeys || 0,
- totalAccounts: overview.totalClaudeAccounts || 0,
- activeAccounts: overview.activeClaudeAccounts || 0,
- rateLimitedAccounts: overview.rateLimitedClaudeAccounts || 0,
- todayRequests: recentActivity.requestsToday || 0,
- totalRequests: overview.totalRequestsUsed || 0,
- todayTokens: recentActivity.tokensToday || 0,
- todayInputTokens: recentActivity.inputTokensToday || 0,
- todayOutputTokens: recentActivity.outputTokensToday || 0,
- totalTokens: overview.totalTokensUsed || 0,
- totalInputTokens: overview.totalInputTokensUsed || 0,
- totalOutputTokens: overview.totalOutputTokensUsed || 0,
- totalCacheCreateTokens: overview.totalCacheCreateTokensUsed || 0,
- totalCacheReadTokens: overview.totalCacheReadTokensUsed || 0,
- todayCacheCreateTokens: recentActivity.cacheCreateTokensToday || 0,
- todayCacheReadTokens: recentActivity.cacheReadTokensToday || 0,
- systemRPM: systemAverages.rpm || 0,
- systemTPM: systemAverages.tpm || 0,
- systemStatus: systemHealth.redisConnected ? '正常' : '异常',
- uptime: systemHealth.uptime || 0
- };
- }
-
- // 更新费用数据
- if (todayCostsData.success && totalCostsData.success) {
- this.costsData = {
- todayCosts: todayCostsData.data.totalCosts || { totalCost: 0, formatted: { totalCost: '$0.000000' } },
- totalCosts: totalCostsData.data.totalCosts || { totalCost: 0, formatted: { totalCost: '$0.000000' } }
- };
- }
- } catch (error) {
- console.error('Failed to load dashboard:', error);
- }
- },
-
- async loadSupportedClients() {
- try {
- const data = await this.apiRequest('/admin/supported-clients');
- if (data && data.success) {
- this.supportedClients = data.data || [];
- console.log('Loaded supported clients:', this.supportedClients);
- }
- } catch (error) {
- console.error('Failed to load supported clients:', error);
- }
- },
-
- async loadApiKeys() {
- this.apiKeysLoading = true;
- console.log('Loading API Keys with time range:', this.apiKeyStatsTimeRange);
- try {
- const data = await this.apiRequest(`/admin/api-keys?timeRange=${this.apiKeyStatsTimeRange}`);
-
- if (!data) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- console.log('API Keys response:', data);
-
- if (data.success) {
- // 确保每个 API key 都有必要的属性
- this.apiKeys = (data.data || []).map(key => {
- const processedKey = {
- ...key,
- apiKey: key.apiKey || '',
- name: key.name || 'Unknown',
- id: key.id || '',
- isActive: key.isActive !== undefined ? key.isActive : true,
- usage: key.usage || { tokensUsed: 0 },
- tokenLimit: key.tokenLimit || null,
- createdAt: key.createdAt || new Date().toISOString()
- };
-
- // 为每个API Key初始化独立的日期筛选状态
- if (!this.apiKeyDateFilters[processedKey.id]) {
- this.initApiKeyDateFilter(processedKey.id);
- }
-
- return processedKey;
- });
- console.log('Processed API Keys:', this.apiKeys);
- } else {
- console.error('API Keys load failed:', data.message);
- this.apiKeys = [];
- }
- } catch (error) {
- console.error('Failed to load API keys:', error);
- this.apiKeys = [];
- } finally {
- this.apiKeysLoading = false;
- }
- },
-
- async loadAccounts() {
- this.accountsLoading = true;
- try {
- // 并行加载 Claude 和 Gemini 账户
- const [claudeData, geminiData] = await Promise.all([
- this.apiRequest('/admin/claude-accounts'),
- this.apiRequest('/admin/gemini-accounts')
- ]);
-
- if (!claudeData || !geminiData) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- // 合并账户数据
- const allAccounts = [];
-
- if (claudeData.success) {
- const claudeAccounts = (claudeData.data || []).map(acc => ({
- ...acc,
- platform: 'claude'
- }));
- allAccounts.push(...claudeAccounts);
- }
-
- if (geminiData.success) {
- const geminiAccounts = (geminiData.data || []).map(acc => ({
- ...acc,
- platform: 'gemini'
- }));
- allAccounts.push(...geminiAccounts);
- }
-
- this.accounts = allAccounts;
-
- // 为每个账号计算绑定的API Key数量
- this.accounts.forEach(account => {
- if (account.platform === 'claude') {
- account.boundApiKeysCount = this.apiKeys.filter(key => key.claudeAccountId === account.id).length;
- } else {
- account.boundApiKeysCount = this.apiKeys.filter(key => key.geminiAccountId === account.id).length;
- }
- });
-
- // 加载完成后自动排序
- this.sortAccounts();
- } catch (error) {
- console.error('Failed to load accounts:', error);
- } finally {
- this.accountsLoading = false;
- }
- },
-
- // 账户排序
- sortAccounts() {
- if (!this.accounts || this.accounts.length === 0) return;
-
- this.accounts.sort((a, b) => {
- switch (this.accountSortBy) {
- case 'name':
- return a.name.localeCompare(b.name);
- case 'dailyTokens':
- const aTokens = (a.usage && a.usage.daily && a.usage.daily.allTokens) || 0;
- const bTokens = (b.usage && b.usage.daily && b.usage.daily.allTokens) || 0;
- return bTokens - aTokens; // 降序
- case 'dailyRequests':
- const aRequests = (a.usage && a.usage.daily && a.usage.daily.requests) || 0;
- const bRequests = (b.usage && b.usage.daily && b.usage.daily.requests) || 0;
- return bRequests - aRequests; // 降序
- case 'totalTokens':
- const aTotalTokens = (a.usage && a.usage.total && a.usage.total.allTokens) || 0;
- const bTotalTokens = (b.usage && b.usage.total && b.usage.total.allTokens) || 0;
- return bTotalTokens - aTotalTokens; // 降序
- case 'lastUsed':
- const aLastUsed = a.lastUsedAt ? new Date(a.lastUsedAt) : new Date(0);
- const bLastUsed = b.lastUsedAt ? new Date(b.lastUsedAt) : new Date(0);
- return bLastUsed - aLastUsed; // 降序(最近使用的在前)
- default:
- return 0;
- }
- });
- },
-
- async loadModelStats() {
- this.modelStatsLoading = true;
- try {
- const data = await this.apiRequest('/admin/model-stats?period=' + this.modelStatsPeriod);
-
- if (!data) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (data.success) {
- this.modelStats = data.data || [];
- } else {
- this.modelStats = [];
- }
- } catch (error) {
- console.error('Failed to load model stats:', error);
- this.modelStats = [];
- } finally {
- this.modelStatsLoading = false;
- }
- },
-
- async createApiKey() {
- this.createApiKeyLoading = true;
- try {
- const data = await this.apiRequest('/admin/api-keys', {
- method: 'POST',
- body: JSON.stringify({
- name: this.apiKeyForm.name,
- tokenLimit: this.apiKeyForm.tokenLimit && this.apiKeyForm.tokenLimit.toString().trim() ? parseInt(this.apiKeyForm.tokenLimit) : null,
- description: this.apiKeyForm.description || '',
- concurrencyLimit: this.apiKeyForm.concurrencyLimit && this.apiKeyForm.concurrencyLimit.toString().trim() ? parseInt(this.apiKeyForm.concurrencyLimit) : 0,
- rateLimitWindow: this.apiKeyForm.rateLimitWindow && this.apiKeyForm.rateLimitWindow.toString().trim() ? parseInt(this.apiKeyForm.rateLimitWindow) : null,
- rateLimitRequests: this.apiKeyForm.rateLimitRequests && this.apiKeyForm.rateLimitRequests.toString().trim() ? parseInt(this.apiKeyForm.rateLimitRequests) : null,
- claudeAccountId: this.apiKeyForm.claudeAccountId || null,
- geminiAccountId: this.apiKeyForm.geminiAccountId || null,
- permissions: this.apiKeyForm.permissions || 'all',
- enableModelRestriction: this.apiKeyForm.enableModelRestriction,
- restrictedModels: this.apiKeyForm.restrictedModels,
- enableClientRestriction: this.apiKeyForm.enableClientRestriction,
- allowedClients: this.apiKeyForm.allowedClients,
- expiresAt: this.apiKeyForm.expiresAt,
- dailyCostLimit: this.apiKeyForm.dailyCostLimit && this.apiKeyForm.dailyCostLimit.toString().trim() ? parseFloat(this.apiKeyForm.dailyCostLimit) : 0
- })
- });
-
- if (!data) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (data.success) {
- // 设置新API Key数据并显示弹窗
- this.newApiKey = {
- key: data.data.apiKey,
- name: data.data.name,
- description: data.data.description || '无描述',
- showFullKey: false
- };
- this.showNewApiKeyModal = true;
-
- // 关闭创建弹窗并清理表单
- this.showCreateApiKeyModal = false;
- this.apiKeyForm = {
- name: '',
- tokenLimit: '',
- description: '',
- concurrencyLimit: '',
- rateLimitWindow: '',
- rateLimitRequests: '',
- claudeAccountId: '',
- geminiAccountId: '',
- permissions: 'all',
- enableModelRestriction: false,
- restrictedModels: [],
- modelInput: '',
- enableClientRestriction: false,
- allowedClients: [],
- expireDuration: '',
- customExpireDate: '',
- expiresAt: null,
- dailyCostLimit: ''
- };
-
- // 重新加载API Keys列表
- await this.loadApiKeys();
- } else {
- this.showToast(data.message || '创建失败', 'error', '创建失败');
- }
- } catch (error) {
- console.error('Error creating API key:', error);
- this.showToast('创建失败,请检查网络连接', 'error', '网络错误');
- } finally {
- this.createApiKeyLoading = false;
- }
- },
-
- async deleteApiKey(keyId) {
- const confirmed = await this.showConfirm(
- '删除 API Key',
- '确定要删除这个 API Key 吗?\n\n此操作不可撤销,删除后将无法恢复。',
- '确认删除',
- '取消'
- );
- if (!confirmed) return;
-
- try {
- const data = await this.apiRequest('/admin/api-keys/' + keyId, {
- method: 'DELETE'
- });
-
- if (!data) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (data.success) {
- this.showToast('API Key 删除成功', 'success', '删除成功');
- await this.loadApiKeys();
- } else {
- this.showToast(data.message || '删除失败', 'error', '删除失败');
- }
- } catch (error) {
- console.error('Error deleting API key:', error);
- this.showToast('删除失败,请检查网络连接', 'error', '网络错误');
- }
- },
-
- // 打开续期弹窗
- openRenewApiKeyModal(key) {
- this.renewApiKeyForm = {
- id: key.id,
- name: key.name,
- currentExpiresAt: key.expiresAt,
- renewDuration: '30d',
- customExpireDate: '',
- newExpiresAt: null
- };
- this.showRenewApiKeyModal = true;
- // 立即计算新的过期时间
- this.updateRenewExpireAt();
- },
-
- // 关闭续期弹窗
- closeRenewApiKeyModal() {
- this.showRenewApiKeyModal = false;
- this.renewApiKeyForm = {
- id: '',
- name: '',
- currentExpiresAt: null,
- renewDuration: '30d',
- customExpireDate: '',
- newExpiresAt: null
- };
- },
-
- // 更新续期过期时间
- updateRenewExpireAt() {
- const duration = this.renewApiKeyForm.renewDuration;
-
- if (duration === 'permanent') {
- this.renewApiKeyForm.newExpiresAt = null;
- return;
- }
-
- if (duration === 'custom') {
- // 自定义日期需要用户选择
- return;
- }
-
- // 计算新的过期时间
- const baseTime = this.renewApiKeyForm.currentExpiresAt
- ? new Date(this.renewApiKeyForm.currentExpiresAt)
- : new Date();
-
- // 如果当前已过期,从现在开始计算
- if (baseTime < new Date()) {
- baseTime.setTime(new Date().getTime());
- }
-
- const durationMap = {
- '7d': 7,
- '30d': 30,
- '90d': 90,
- '180d': 180,
- '365d': 365
- };
-
- const days = durationMap[duration];
- if (days) {
- const expireDate = new Date(baseTime.getTime() + days * 24 * 60 * 60 * 1000);
- this.renewApiKeyForm.newExpiresAt = expireDate.toISOString();
- }
- },
-
- // 更新自定义续期时间
- updateCustomRenewExpireAt() {
- if (this.renewApiKeyForm.customExpireDate) {
- const expireDate = new Date(this.renewApiKeyForm.customExpireDate);
- this.renewApiKeyForm.newExpiresAt = expireDate.toISOString();
- }
- },
-
- // 执行续期操作
- async renewApiKey() {
- this.renewApiKeyLoading = true;
- try {
- const data = await this.apiRequest('/admin/api-keys/' + this.renewApiKeyForm.id, {
- method: 'PUT',
- body: JSON.stringify({
- expiresAt: this.renewApiKeyForm.newExpiresAt
- })
- });
-
- if (!data) {
- return;
- }
-
- if (data.success) {
- this.showToast('API Key 续期成功', 'success');
- this.closeRenewApiKeyModal();
- await this.loadApiKeys();
- } else {
- this.showToast(data.message || '续期失败', 'error');
- }
- } catch (error) {
- console.error('Error renewing API key:', error);
- this.showToast('续期失败,请检查网络连接', 'error');
- } finally {
- this.renewApiKeyLoading = false;
- }
- },
-
- openEditApiKeyModal(key) {
- this.editApiKeyForm = {
- id: key.id,
- name: key.name,
- tokenLimit: key.tokenLimit || '',
- concurrencyLimit: key.concurrencyLimit || '',
- rateLimitWindow: key.rateLimitWindow || '',
- rateLimitRequests: key.rateLimitRequests || '',
- claudeAccountId: key.claudeAccountId || '',
- geminiAccountId: key.geminiAccountId || '',
- permissions: key.permissions || 'all',
- enableModelRestriction: key.enableModelRestriction || false,
- restrictedModels: key.restrictedModels ? [...key.restrictedModels] : [],
- modelInput: '',
- enableClientRestriction: key.enableClientRestriction || false,
- allowedClients: key.allowedClients ? [...key.allowedClients] : [],
- dailyCostLimit: key.dailyCostLimit || ''
- };
- this.showEditApiKeyModal = true;
- },
-
- closeEditApiKeyModal() {
- this.showEditApiKeyModal = false;
- this.editApiKeyForm = {
- id: '',
- name: '',
- tokenLimit: '',
- concurrencyLimit: '',
- rateLimitWindow: '',
- rateLimitRequests: '',
- claudeAccountId: '',
- geminiAccountId: '',
- permissions: 'all',
- enableModelRestriction: false,
- restrictedModels: [],
- modelInput: '',
- enableClientRestriction: false,
- allowedClients: [],
- dailyCostLimit: ''
- };
- },
-
- async updateApiKey() {
- this.editApiKeyLoading = true;
- try {
- const data = await this.apiRequest('/admin/api-keys/' + this.editApiKeyForm.id, {
- method: 'PUT',
- body: JSON.stringify({
- tokenLimit: this.editApiKeyForm.tokenLimit && this.editApiKeyForm.tokenLimit.toString().trim() !== '' ? parseInt(this.editApiKeyForm.tokenLimit) : 0,
- concurrencyLimit: this.editApiKeyForm.concurrencyLimit && this.editApiKeyForm.concurrencyLimit.toString().trim() !== '' ? parseInt(this.editApiKeyForm.concurrencyLimit) : 0,
- rateLimitWindow: this.editApiKeyForm.rateLimitWindow && this.editApiKeyForm.rateLimitWindow.toString().trim() !== '' ? parseInt(this.editApiKeyForm.rateLimitWindow) : 0,
- rateLimitRequests: this.editApiKeyForm.rateLimitRequests && this.editApiKeyForm.rateLimitRequests.toString().trim() !== '' ? parseInt(this.editApiKeyForm.rateLimitRequests) : 0,
- claudeAccountId: this.editApiKeyForm.claudeAccountId || null,
- geminiAccountId: this.editApiKeyForm.geminiAccountId || null,
- permissions: this.editApiKeyForm.permissions || 'all',
- enableModelRestriction: this.editApiKeyForm.enableModelRestriction,
- restrictedModels: this.editApiKeyForm.restrictedModels,
- enableClientRestriction: this.editApiKeyForm.enableClientRestriction,
- allowedClients: this.editApiKeyForm.allowedClients,
- dailyCostLimit: this.editApiKeyForm.dailyCostLimit && this.editApiKeyForm.dailyCostLimit.toString().trim() !== '' ? parseFloat(this.editApiKeyForm.dailyCostLimit) : 0
- })
- });
-
- if (!data) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- if (data.success) {
- this.showToast('API Key 更新成功', 'success', '更新成功');
- this.closeEditApiKeyModal();
- await this.loadApiKeys();
- } else {
- this.showToast(data.message || '更新失败', 'error', '更新失败');
- }
- } catch (error) {
- console.error('Error updating API key:', error);
- this.showToast('更新失败,请检查网络连接', 'error', '网络错误');
- } finally {
- this.editApiKeyLoading = false;
- }
- },
-
- async deleteAccount(accountId) {
- // 确保API Keys数据已加载,以便正确计算绑定数量
- if (this.apiKeys.length === 0) {
- await this.loadApiKeys();
- }
-
- // 查找账户以确定平台类型
- const account = this.accounts.find(acc => acc.id === accountId);
- if (!account) {
- this.showToast('账户不存在', 'error', '删除失败');
- return;
- }
-
- // 检查是否有API Key绑定到此账号
- const boundKeysCount = this.getBoundApiKeysCount(accountId);
- if (boundKeysCount > 0) {
- this.showToast(`无法删除此账号,有 ${boundKeysCount} 个API Key绑定到此账号,请先解绑所有API Key`, 'error', '删除失败');
- return;
- }
-
- const platformName = account.platform === 'gemini' ? 'Gemini' : 'Claude';
- const confirmed = await this.showConfirm(
- `删除 ${platformName} 账户`,
- `确定要删除这个 ${platformName} 账户吗?\n\n账户名称:${account.name}\n此操作不可撤销,删除后将无法恢复。`,
- '确认删除',
- '取消'
- );
- if (!confirmed) return;
-
- // 根据平台选择端点
- const endpoint = account.platform === 'gemini'
- ? `/admin/gemini-accounts/${accountId}`
- : `/admin/claude-accounts/${accountId}`;
-
- try {
- const response = await fetch(endpoint, {
- method: 'DELETE',
- headers: { 'Authorization': 'Bearer ' + this.authToken }
- });
-
- const data = await response.json();
-
- if (data.success) {
- this.showToast('Claude 账户删除成功', 'success', '删除成功');
- await this.loadAccounts();
- } else {
- this.showToast(data.message || '删除失败', 'error', '删除失败');
- }
- } catch (error) {
- console.error('Error deleting account:', error);
- this.showToast('删除失败,请检查网络连接', 'error', '网络错误');
- }
- },
-
- // 刷新账户 Token
- async refreshAccountToken(accountId) {
- const account = this.accounts.find(acc => acc.id === accountId);
- if (!account) {
- this.showToast('账户不存在', 'error', '刷新失败');
- return;
- }
-
- // 根据平台选择端点
- const endpoint = account.platform === 'gemini'
- ? `/admin/gemini-accounts/${accountId}/refresh`
- : `/admin/claude-accounts/${accountId}/refresh`;
-
- try {
- const response = await fetch(endpoint, {
- method: 'POST',
- headers: { 'Authorization': 'Bearer ' + this.authToken }
- });
-
- const data = await response.json();
-
- if (data.success) {
- const platformName = account.platform === 'gemini' ? 'Gemini' : 'Claude';
- this.showToast(`${platformName} Token 刷新成功`, 'success', '刷新成功');
- await this.loadAccounts();
- } else {
- this.showToast(data.message || '刷新失败', 'error', '刷新失败');
- }
- } catch (error) {
- console.error('Error refreshing token:', error);
- this.showToast('刷新失败,请检查网络连接', 'error', '网络错误');
- }
- },
-
- // API Key 展示相关方法
- toggleApiKeyVisibility() {
- this.newApiKey.showFullKey = !this.newApiKey.showFullKey;
- },
-
- getDisplayedApiKey() {
- if (this.newApiKey.showFullKey) {
- return this.newApiKey.key;
- } else {
- // 显示前8个字符和后4个字符,中间用*代替
- const key = this.newApiKey.key;
- if (key.length <= 12) return key;
- return key.substring(0, 8) + '●'.repeat(Math.max(0, key.length - 12)) + key.substring(key.length - 4);
- }
- },
-
- async copyApiKeyToClipboard() {
- try {
- await navigator.clipboard.writeText(this.newApiKey.key);
- this.showToast('API Key 已复制到剪贴板', 'success', '复制成功');
- } catch (error) {
- console.error('Failed to copy:', error);
- // 降级方案:创建一个临时文本区域
- const textArea = document.createElement('textarea');
- textArea.value = this.newApiKey.key;
- document.body.appendChild(textArea);
- textArea.select();
- try {
- document.execCommand('copy');
- this.showToast('API Key 已复制到剪贴板', 'success', '复制成功');
- } catch (fallbackError) {
- this.showToast('复制失败,请手动复制', 'error', '复制失败');
- }
- document.body.removeChild(textArea);
- }
- },
-
- async closeNewApiKeyModal() {
- // 显示确认提示
- const confirmed = await this.showConfirm(
- '关闭 API Key',
- '关闭后将无法再次查看完整的API Key,请确保已经妥善保存。\n\n确定要关闭吗?',
- '我已保存',
- '取消'
- );
- if (confirmed) {
- this.showNewApiKeyModal = false;
- this.newApiKey = { key: '', name: '', description: '', showFullKey: false };
- }
- },
-
-
- // 格式化数字,添加千分符
- formatNumber(num) {
- if (num === null || num === undefined) return '0';
- const number = Number(num);
- if (number >= 1000000) {
- return Math.floor(number / 1000000).toLocaleString() + 'M';
- }
- return number.toLocaleString();
- },
-
- // 格式化会话窗口时间
- formatSessionWindow(windowStart, windowEnd) {
- if (!windowStart || !windowEnd) return '--';
-
- const start = new Date(windowStart);
- const end = new Date(windowEnd);
-
- const startHour = start.getHours().toString().padStart(2, '0');
- const startMin = start.getMinutes().toString().padStart(2, '0');
- const endHour = end.getHours().toString().padStart(2, '0');
- const endMin = end.getMinutes().toString().padStart(2, '0');
-
- return `${startHour}:${startMin} - ${endHour}:${endMin}`;
- },
-
- // 格式化剩余时间
- formatRemainingTime(minutes) {
- if (!minutes || minutes <= 0) return '已结束';
-
- const hours = Math.floor(minutes / 60);
- const mins = minutes % 60;
-
- if (hours > 0) {
- return `${hours}小时${mins}分钟`;
- }
- return `${mins}分钟`;
- },
-
- // 格式化运行时间
- formatUptime(seconds) {
- if (!seconds) return '0s';
-
- const days = Math.floor(seconds / 86400);
- const hours = Math.floor((seconds % 86400) / 3600);
- const mins = Math.floor((seconds % 3600) / 60);
-
- if (days > 0) {
- return days + '天' + hours + '时';
- } else if (hours > 0) {
- return hours + '时' + mins + '分';
- } else {
- return mins + '分';
- }
- },
-
- // 计算百分比
- calculatePercentage(value, stats) {
- const total = stats.reduce((sum, stat) => sum + (stat.allTokens || 0), 0);
- if (total === 0) return 0;
- return ((value / total) * 100).toFixed(1);
- },
-
- // 加载仪表盘模型统计
- async loadDashboardModelStats() {
- console.log('Loading dashboard model stats, period:', this.dashboardModelPeriod, 'authToken:', !!this.authToken);
- try {
- const data = await this.apiRequest('/admin/model-stats?period=' + this.dashboardModelPeriod);
-
- if (!data) {
- // 如果token过期,apiRequest会返回null并刷新页面
- return;
- }
-
- console.log('Model stats response data:', data);
-
- if (data.success) {
- this.dashboardModelStats = data.data || [];
- console.log('Loaded model stats:', this.dashboardModelStats.length, 'items');
- this.updateModelUsageChart();
- } else {
- console.warn('Model stats API returned success=false:', data);
- this.dashboardModelStats = [];
- }
- } catch (error) {
- console.error('Failed to load dashboard model stats:', error);
- this.dashboardModelStats = [];
- }
- },
-
- // 更新模型使用饼图
- updateModelUsageChart() {
-
- if (!this.dashboardModelStats.length) {
- console.warn('No dashboard model stats data, skipping chart update');
- return;
- }
-
- // 检查Chart.js是否已加载
- if (typeof Chart === 'undefined') {
- console.warn('Chart.js not loaded yet, retrying...');
- setTimeout(() => this.updateModelUsageChart(), 500);
- return;
- }
-
- // 严格检查DOM元素是否有效
- if (!this.isElementValid('modelUsageChart')) {
- console.error('Model usage chart canvas element not found or invalid');
- return;
- }
-
- const ctx = document.getElementById('modelUsageChart');
-
- // 安全销毁现有图表
- if (this.modelUsageChart) {
- try {
- this.modelUsageChart.destroy();
- } catch (error) {
- console.warn('Error destroying model usage chart:', error);
- }
- this.modelUsageChart = null;
- }
-
- // 再次验证元素在销毁后仍然有效
- if (!this.isElementValid('modelUsageChart')) {
- console.error('Model usage chart canvas element became invalid after cleanup');
- return;
- }
-
- const labels = this.dashboardModelStats.map(stat => stat.model);
- const data = this.dashboardModelStats.map(stat => stat.allTokens || 0);
-
-
- // 生成渐变色
- const colors = [
- 'rgba(102, 126, 234, 0.8)',
- 'rgba(118, 75, 162, 0.8)',
- 'rgba(240, 147, 251, 0.8)',
- 'rgba(16, 185, 129, 0.8)',
- 'rgba(245, 158, 11, 0.8)',
- 'rgba(239, 68, 68, 0.8)'
- ];
-
- try {
- // 最后一次检查元素有效性
- if (!this.isElementValid('modelUsageChart')) {
- throw new Error('Canvas element is not valid for chart creation');
- }
-
- this.modelUsageChart = new Chart(ctx, {
- type: 'doughnut',
- data: {
- labels: labels,
- datasets: [{
- data: data,
- backgroundColor: colors,
- borderColor: 'rgba(255, 255, 255, 1)',
- borderWidth: 2
- }]
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- animation: false, // 禁用动画防止异步渲染问题
- plugins: {
- legend: {
- position: 'bottom',
- labels: {
- padding: 15,
- font: {
- size: 12
- }
- }
- },
- tooltip: {
- callbacks: {
- label: function(context) {
- const label = context.label || '';
- const value = context.parsed || 0;
- const total = context.dataset.data.reduce((a, b) => a + b, 0);
- const percentage = ((value / total) * 100).toFixed(1);
- return label + ': ' + value.toLocaleString() + ' (' + percentage + '%)';
- }
- }
- }
- }
- }
- });
- } catch (error) {
- console.error('Error creating model usage chart:', error);
- this.modelUsageChart = null;
- }
- },
-
- // 设置趋势图周期(添加防抖)
- setTrendPeriod: null, // 将在mounted中初始化为防抖函数
-
- // 实际的设置趋势图周期方法
- async _setTrendPeriod(days) {
- console.log('Setting trend period to:', days);
-
- // 先清理现有图表,防止竞态条件
- if (this.usageTrendChart) {
- try {
- this.usageTrendChart.stop();
- this.usageTrendChart.destroy();
- } catch (error) {
- console.warn('Error cleaning trend chart:', error);
- }
- this.usageTrendChart = null;
- }
-
- this.trendPeriod = days;
- await this.loadUsageTrend();
- },
-
- // 加载API Keys使用趋势数据
- async loadApiKeysUsageTrend() {
- console.log('Loading API keys usage trend data, granularity:', this.trendGranularity);
- try {
- let url = '/admin/api-keys-usage-trend?';
-
- if (this.trendGranularity === 'hour') {
- // 小时粒度,传递开始和结束时间
- url += `granularity=hour`;
- if (this.dateFilter.customRange && this.dateFilter.customRange.length === 2) {
- url += `&startDate=${encodeURIComponent(this.dateFilter.customRange[0])}`;
- url += `&endDate=${encodeURIComponent(this.dateFilter.customRange[1])}`;
- }
- } else {
- // 天粒度,传递天数
- url += `granularity=day&days=${this.trendPeriod}`;
- }
-
- const response = await fetch(url, {
- headers: { 'Authorization': 'Bearer ' + this.authToken }
- });
-
- if (!response.ok) {
- console.error('API keys usage trend API error:', response.status, response.statusText);
- return;
- }
-
- const data = await response.json();
-
- if (data.success) {
- this.apiKeysTrendData = {
- data: data.data || [],
- topApiKeys: data.topApiKeys || [],
- totalApiKeys: data.totalApiKeys || 0
- };
- console.log('Loaded API keys trend data:', this.apiKeysTrendData);
- this.updateApiKeysUsageTrendChart();
- }
- } catch (error) {
- console.error('Failed to load API keys usage trend:', error);
- }
- },
-
- // 加载使用趋势数据
- async loadUsageTrend() {
- console.log('Loading usage trend data, period:', this.trendPeriod, 'granularity:', this.trendGranularity, 'authToken:', !!this.authToken);
- try {
- let url = '/admin/usage-trend?';
-
- if (this.trendGranularity === 'hour') {
- // 小时粒度,传递开始和结束时间
- url += `granularity=hour`;
- if (this.dateFilter.customRange && this.dateFilter.customRange.length === 2) {
- url += `&startDate=${encodeURIComponent(this.dateFilter.customRange[0])}`;
- url += `&endDate=${encodeURIComponent(this.dateFilter.customRange[1])}`;
- }
- } else {
- // 天粒度,传递天数
- url += `granularity=day&days=${this.trendPeriod}`;
- }
-
- const response = await fetch(url, {
- headers: { 'Authorization': 'Bearer ' + this.authToken }
- });
-
- console.log('Usage trend response status:', response.status);
-
- if (!response.ok) {
- console.error('Usage trend API error:', response.status, response.statusText);
- const errorText = await response.text();
- console.error('Error response:', errorText);
- return;
- }
-
- const data = await response.json();
- console.log('Usage trend response data:', data);
-
- if (data.success) {
- this.trendData = data.data || [];
- console.log('Loaded trend data:', this.trendData.length, 'items');
- this.updateUsageTrendChart();
- } else {
- console.warn('Usage trend API returned success=false:', data);
- }
- } catch (error) {
- console.error('Failed to load usage trend:', error);
- }
- },
-
- // 更新使用趋势图
- updateUsageTrendChart() {
-
- // 检查Chart.js是否已加载
- if (typeof Chart === 'undefined') {
- console.warn('Chart.js not loaded yet, retrying...');
- setTimeout(() => this.updateUsageTrendChart(), 500);
- return;
- }
-
- // 严格检查DOM元素是否有效
- if (!this.isElementValid('usageTrendChart')) {
- console.error('Usage trend chart canvas element not found or invalid');
- return;
- }
-
- const ctx = document.getElementById('usageTrendChart');
-
- // 安全销毁现有图表
- if (this.usageTrendChart) {
- try {
- this.usageTrendChart.destroy();
- } catch (error) {
- console.warn('Error destroying usage trend chart:', error);
- }
- this.usageTrendChart = null;
- }
-
- // 如果没有数据,不创建图表
- if (!this.trendData || this.trendData.length === 0) {
- console.warn('No trend data available, skipping chart creation');
- return;
- }
-
- // 再次验证元素在销毁后仍然有效
- if (!this.isElementValid('usageTrendChart')) {
- console.error('Usage trend chart canvas element became invalid after cleanup');
- return;
- }
-
- // 根据粒度格式化标签
- const labels = this.trendData.map(item => {
- if (this.trendGranularity === 'hour') {
- // 小时粒度:从hour字段提取时间
- if (item.hour) {
- const date = new Date(item.hour);
- return `${String(date.getHours()).padStart(2, '0')}:00`;
- }
- // 后备方案:从date字段解析
- const [, time] = item.date.split(':');
- return `${time}:00`;
- } else {
- // 天粒度:显示日期
- return item.date;
- }
- });
-
- const inputData = this.trendData.map(item => item.inputTokens || 0);
- const outputData = this.trendData.map(item => item.outputTokens || 0);
- const cacheCreateData = this.trendData.map(item => item.cacheCreateTokens || 0);
- const cacheReadData = this.trendData.map(item => item.cacheReadTokens || 0);
- const requestsData = this.trendData.map(item => item.requests || 0);
- const costData = this.trendData.map(item => item.cost || 0);
-
-
- try {
- // 最后一次检查元素有效性
- if (!this.isElementValid('usageTrendChart')) {
- throw new Error('Canvas element is not valid for chart creation');
- }
-
- this.usageTrendChart = new Chart(ctx, {
- type: 'line',
- data: {
- labels: labels,
- datasets: [
- {
- label: '输入Token',
- data: inputData,
- borderColor: 'rgb(102, 126, 234)',
- backgroundColor: 'rgba(102, 126, 234, 0.1)',
- tension: 0.3
- },
- {
- label: '输出Token',
- data: outputData,
- borderColor: 'rgb(240, 147, 251)',
- backgroundColor: 'rgba(240, 147, 251, 0.1)',
- tension: 0.3
- },
- {
- label: '缓存创建Token',
- data: cacheCreateData,
- borderColor: 'rgb(59, 130, 246)',
- backgroundColor: 'rgba(59, 130, 246, 0.1)',
- tension: 0.3
- },
- {
- label: '缓存读取Token',
- data: cacheReadData,
- borderColor: 'rgb(147, 51, 234)',
- backgroundColor: 'rgba(147, 51, 234, 0.1)',
- tension: 0.3
- },
- {
- label: '费用 (USD)',
- data: costData,
- borderColor: 'rgb(34, 197, 94)',
- backgroundColor: 'rgba(34, 197, 94, 0.1)',
- tension: 0.3,
- yAxisID: 'y2'
- },
- {
- label: '请求数',
- data: requestsData,
- borderColor: 'rgb(16, 185, 129)',
- backgroundColor: 'rgba(16, 185, 129, 0.1)',
- tension: 0.3,
- yAxisID: 'y1'
- }
- ]
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- animation: false, // 禁用动画防止异步渲染问题
- interaction: {
- mode: 'index',
- intersect: false,
- },
- scales: {
- x: {
- type: 'category',
- display: true,
- title: {
- display: true,
- text: this.trendGranularity === 'hour' ? '时间' : '日期'
- },
- ticks: {
- autoSkip: true,
- maxRotation: this.trendGranularity === 'hour' ? 45 : 0,
- minRotation: this.trendGranularity === 'hour' ? 45 : 0
- }
- },
- y: {
- type: 'linear',
- display: true,
- position: 'left',
- title: {
- display: true,
- text: 'Token数量'
- }
- },
- y1: {
- type: 'linear',
- display: true,
- position: 'right',
- title: {
- display: true,
- text: '请求数'
- },
- grid: {
- drawOnChartArea: false,
- }
- },
- y2: {
- type: 'linear',
- display: false, // 隐藏费用轴,在tooltip中显示
- position: 'right'
- }
- },
- plugins: {
- legend: {
- position: 'top',
- },
- tooltip: {
- mode: 'index',
- intersect: false,
- callbacks: {
- title: (tooltipItems) => {
- if (tooltipItems.length === 0) return '';
- const index = tooltipItems[0].dataIndex;
- const item = this.trendData[index];
-
- if (this.trendGranularity === 'hour' && item.hour) {
- // 小时粒度:显示完整的日期时间
- const date = new Date(item.hour);
- return date.toLocaleString('zh-CN', {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit'
- });
- }
- // 天粒度:保持原有标签
- return tooltipItems[0].label;
- },
- label: function(context) {
- const label = context.dataset.label || '';
- let value = context.parsed.y;
-
- if (label === '费用 (USD)') {
- // 格式化费用显示
- if (value < 0.01) {
- return label + ': $' + value.toFixed(6);
- } else {
- return label + ': $' + value.toFixed(4);
- }
- } else if (label === '请求数') {
- return label + ': ' + value.toLocaleString();
- } else {
- return label + ': ' + value.toLocaleString() + ' tokens';
- }
- }
- }
- }
- }
- }
- });
- } catch (error) {
- console.error('Error creating usage trend chart:', error);
- this.usageTrendChart = null;
- }
- },
-
- // 更新API Keys使用趋势图
- updateApiKeysUsageTrendChart() {
- // 检查Chart.js是否已加载
- if (typeof Chart === 'undefined') {
- console.warn('Chart.js not loaded yet, retrying...');
- setTimeout(() => this.updateApiKeysUsageTrendChart(), 500);
- return;
- }
-
- // 严格检查DOM元素是否有效
- if (!this.isElementValid('apiKeysUsageTrendChart')) {
- console.error('API keys usage trend chart canvas element not found or invalid');
- return;
- }
-
- const ctx = document.getElementById('apiKeysUsageTrendChart');
-
- // 安全销毁现有图表
- if (this.apiKeysUsageTrendChart) {
- try {
- this.apiKeysUsageTrendChart.destroy();
- } catch (error) {
- console.warn('Error destroying API keys usage trend chart:', error);
- }
- this.apiKeysUsageTrendChart = null;
- }
-
- // 如果没有数据,不创建图表
- if (!this.apiKeysTrendData.data || this.apiKeysTrendData.data.length === 0) {
- console.warn('No API keys trend data available, skipping chart creation');
- return;
- }
-
- // 准备数据
- const labels = this.apiKeysTrendData.data.map(item => {
- if (this.trendGranularity === 'hour') {
- const date = new Date(item.hour);
- return `${String(date.getHours()).padStart(2, '0')}:00`;
- }
- return item.date;
- });
-
- // 获取所有API Key的数据集
- const datasets = [];
- const colors = [
- 'rgb(102, 126, 234)',
- 'rgb(240, 147, 251)',
- 'rgb(59, 130, 246)',
- 'rgb(147, 51, 234)',
- 'rgb(34, 197, 94)',
- 'rgb(251, 146, 60)',
- 'rgb(239, 68, 68)',
- 'rgb(16, 185, 129)',
- 'rgb(245, 158, 11)',
- 'rgb(236, 72, 153)'
- ];
-
- // 只显示前10个使用量最多的API Key
- this.apiKeysTrendData.topApiKeys.forEach((apiKeyId, index) => {
- const data = this.apiKeysTrendData.data.map(item => {
- if (!item.apiKeys[apiKeyId]) return 0;
- return this.apiKeysTrendMetric === 'tokens'
- ? item.apiKeys[apiKeyId].tokens
- : item.apiKeys[apiKeyId].requests || 0;
- });
-
- // 获取API Key名称
- const apiKeyName = this.apiKeysTrendData.data.find(item =>
- item.apiKeys[apiKeyId]
- )?.apiKeys[apiKeyId]?.name || `API Key ${apiKeyId}`;
-
- datasets.push({
- label: apiKeyName,
- data: data,
- borderColor: colors[index % colors.length],
- backgroundColor: colors[index % colors.length] + '20',
- tension: 0.3,
- fill: false
- });
- });
-
- try {
- // 最后一次检查元素有效性
- if (!this.isElementValid('apiKeysUsageTrendChart')) {
- throw new Error('Canvas element is not valid for chart creation');
- }
-
- this.apiKeysUsageTrendChart = new Chart(ctx, {
- type: 'line',
- data: {
- labels: labels,
- datasets: datasets
- },
- options: {
- responsive: true,
- maintainAspectRatio: false,
- animation: false, // 禁用动画防止异步渲染问题
- interaction: {
- mode: 'index',
- intersect: false,
- },
- scales: {
- x: {
- type: 'category',
- display: true,
- title: {
- display: true,
- text: this.trendGranularity === 'hour' ? '时间' : '日期'
- },
- ticks: {
- autoSkip: true,
- maxRotation: this.trendGranularity === 'hour' ? 45 : 0,
- minRotation: this.trendGranularity === 'hour' ? 45 : 0
- }
- },
- y: {
- type: 'linear',
- display: true,
- position: 'left',
- title: {
- display: true,
- text: this.apiKeysTrendMetric === 'tokens' ? 'Token 数量' : '请求次数'
- },
- ticks: {
- callback: function(value) {
- return value.toLocaleString();
- }
- }
- }
- },
- plugins: {
- legend: {
- position: 'top',
- labels: {
- usePointStyle: true,
- padding: 15
- }
- },
- tooltip: {
- mode: 'index',
- intersect: false,
- callbacks: {
- title: (tooltipItems) => {
- if (tooltipItems.length === 0) return '';
- const index = tooltipItems[0].dataIndex;
- const item = this.apiKeysTrendData.data[index];
-
- if (this.trendGranularity === 'hour' && item.hour) {
- const date = new Date(item.hour);
- return date.toLocaleString('zh-CN', {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit'
- });
- }
- return tooltipItems[0].label;
- },
- label: (context) => {
- const label = context.dataset.label || '';
- const value = context.parsed.y;
- const unit = this.apiKeysTrendMetric === 'tokens' ? ' tokens' : ' 次';
- return label + ': ' + value.toLocaleString() + unit;
- }
- }
- }
- }
- }
- });
- } catch (error) {
- console.error('Error creating API keys usage trend chart:', error);
- this.apiKeysUsageTrendChart = null;
- }
- },
-
- // 切换API Key模型统计展开状态
- toggleApiKeyModelStats(keyId) {
- if (!keyId) {
- console.warn('toggleApiKeyModelStats: keyId is null or undefined');
- return;
- }
-
- console.log('Toggling API key model stats for:', keyId, 'current state:', this.expandedApiKeys[keyId]);
-
- if (this.expandedApiKeys[keyId]) {
- // 收起展开
- this.expandedApiKeys = {
- ...this.expandedApiKeys
- };
- delete this.expandedApiKeys[keyId];
- } else {
- // 展开并加载数据
- this.expandedApiKeys = {
- ...this.expandedApiKeys,
- [keyId]: true
- };
- console.log('Expanded keys after toggle:', this.expandedApiKeys);
- this.loadApiKeyModelStats(keyId);
- }
- },
-
- // 加载API Key的模型统计
- async loadApiKeyModelStats(keyId, forceReload = false) {
- if (!keyId) {
- console.warn('loadApiKeyModelStats: keyId is null or undefined');
- return;
- }
-
- // 如果已经有数据且不为空,且不是强制重新加载,则跳过加载
- if (!forceReload && this.apiKeyModelStats[keyId] && this.apiKeyModelStats[keyId].length > 0) {
- console.log('API key model stats already loaded for:', keyId);
- return;
- }
-
- const filter = this.getApiKeyDateFilter(keyId);
- console.log('Loading API key model stats for:', keyId, 'period:', this.apiKeyModelPeriod, 'forceReload:', forceReload, 'authToken:', !!this.authToken);
- console.log('API Key date filter:', filter);
-
- // 清除现有数据以显示加载状态
- if (forceReload) {
- const newStats = { ...this.apiKeyModelStats };
- delete newStats[keyId];
- this.apiKeyModelStats = newStats;
- }
-
- try {
- // 构建API请求URL,根据筛选类型传递不同参数
- let url = '/admin/api-keys/' + keyId + '/model-stats';
- const params = new URLSearchParams();
-
- // 检查是否有具体的日期范围设置(包括快捷按钮设置的日期)
- if (filter.customStart && filter.customEnd) {
- // 有具体日期范围,使用自定义时间范围方式
- params.append('startDate', filter.customStart);
- params.append('endDate', filter.customEnd);
- params.append('period', 'custom');
- console.log('Using custom date range:', filter.customStart, 'to', filter.customEnd);
- } else {
- // 没有具体日期范围,使用预设期间(目前只有 today 会走这里)
- const period = filter.preset === 'today' ? 'daily' : 'monthly';
- params.append('period', period);
- console.log('Using preset period:', period);
- }
-
- url += '?' + params.toString();
- console.log('API request URL:', url);
-
- const response = await fetch(url, {
- headers: { 'Authorization': 'Bearer ' + this.authToken }
- });
-
- console.log('API key model stats response status:', response.status);
-
- if (!response.ok) {
- console.error('API key model stats API error:', response.status, response.statusText);
- const errorText = await response.text();
- console.error('Error response:', errorText);
- return;
- }
-
- const data = await response.json();
- console.log('API key model stats response data:', data);
-
- if (data.success) {
- console.log('API response success, data:', data.data);
- console.log('Setting apiKeyModelStats for keyId:', keyId);
-
- // 确保响应式更新 - 创建新对象
- const newStats = { ...this.apiKeyModelStats };
- newStats[keyId] = data.data || [];
- this.apiKeyModelStats = newStats;
-
- console.log('Updated apiKeyModelStats:', this.apiKeyModelStats);
- console.log('Data for keyId', keyId, ':', this.apiKeyModelStats[keyId]);
- console.log('Data length:', this.apiKeyModelStats[keyId] ? this.apiKeyModelStats[keyId].length : 'undefined');
-
- // 确保Vue知道数据已经更新
- this.$nextTick(() => {
- console.log('Vue nextTick - stats should be visible now');
- });
- } else {
- console.warn('API key model stats API returned success=false:', data);
- }
- } catch (error) {
- console.error('Failed to load API key model stats:', error);
- }
- },
-
- // 计算API Key模型使用百分比
- calculateApiKeyModelPercentage(value, stats) {
- const total = stats.reduce((sum, stat) => sum + (stat.allTokens || 0), 0);
- if (total === 0) return 0;
- return Math.round((value / total) * 100);
- },
-
- // 计算单个模型费用
- calculateModelCost(stat) {
- // 优先使用后端返回的费用数据
- if (stat.formatted && stat.formatted.total) {
- return stat.formatted.total;
- }
-
- // 如果后端没有返回费用数据,则使用简单估算(备用方案)
- const inputTokens = stat.inputTokens || 0;
- const outputTokens = stat.outputTokens || 0;
- const cacheCreateTokens = stat.cacheCreateTokens || 0;
- const cacheReadTokens = stat.cacheReadTokens || 0;
-
- // 使用通用估算价格(Claude 3.5 Sonnet价格作为默认)
- const inputCost = (inputTokens / 1000000) * 3.00;
- const outputCost = (outputTokens / 1000000) * 15.00;
- const cacheCreateCost = (cacheCreateTokens / 1000000) * 3.75;
- const cacheReadCost = (cacheReadTokens / 1000000) * 0.30;
-
- const totalCost = inputCost + outputCost + cacheCreateCost + cacheReadCost;
-
- if (totalCost < 0.000001) return '$0.000000';
- if (totalCost < 0.01) return '$' + totalCost.toFixed(6);
- return '$' + totalCost.toFixed(4);
- },
-
- // 计算API Key费用
- calculateApiKeyCost(usage) {
- if (!usage || !usage.total) return '$0.000000';
-
- // 使用后端返回的准确费用数据
- if (usage.total.formattedCost) {
- return usage.total.formattedCost;
- }
-
- // 如果没有后端费用数据,返回默认值
- return '$0.000000';
- },
-
- // 计算API Key费用数值(用于排序)
- calculateApiKeyCostNumber(usage) {
- if (!usage || !usage.total) return 0;
-
- // 使用后端返回的准确费用数据
- if (usage.total.cost) {
- return usage.total.cost;
- }
-
- // 如果没有后端费用数据,返回0
- return 0;
- },
-
- // 初始化日期筛选器
- initializeDateFilter() {
- console.log('Initializing date filter, default preset:', this.dateFilter.preset);
-
- // 根据默认的日期筛选设置正确的 dashboardModelPeriod
- if (this.dateFilter.preset === 'today') {
- this.dashboardModelPeriod = 'daily';
- } else {
- this.dashboardModelPeriod = 'monthly';
- }
-
- console.log('Set dashboardModelPeriod to:', this.dashboardModelPeriod);
- },
-
- // 日期筛选方法
- setDateFilterPreset(preset) {
- this.dateFilter.type = 'preset';
- this.dateFilter.preset = preset;
- // 清除自定义日期范围
- this.dateFilter.customStart = '';
- this.dateFilter.customEnd = '';
-
- // 根据预设计算并设置自定义时间框的值
- const option = this.dateFilter.presetOptions.find(opt => opt.value === preset);
- if (option) {
- const now = new Date();
- let startDate, endDate;
-
- if (this.trendGranularity === 'hour') {
- // 小时粒度的预设处理
- if (preset === 'last24h') {
- endDate = new Date(now);
- startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000);
- } else if (preset === 'yesterday') {
- // 昨天的00:00到23:59
- startDate = new Date(now);
- startDate.setDate(startDate.getDate() - 1);
- startDate.setHours(0, 0, 0, 0);
- endDate = new Date(startDate);
- endDate.setHours(23, 59, 59, 999);
- } else if (preset === 'dayBefore') {
- // 前天的00:00到23:59
- startDate = new Date(now);
- startDate.setDate(startDate.getDate() - 2);
- startDate.setHours(0, 0, 0, 0);
- endDate = new Date(startDate);
- endDate.setHours(23, 59, 59, 999);
- }
- } else {
- // 天粒度的预设处理(保持原有逻辑)
- endDate = new Date(now);
- startDate = new Date(now);
- startDate.setDate(now.getDate() - (option.days - 1));
- startDate.setHours(0, 0, 0, 0);
- endDate.setHours(23, 59, 59, 999);
- }
-
- // 格式化为 Element Plus 需要的格式
- const formatDate = (date) => {
- return date.getFullYear() + '-' +
- String(date.getMonth() + 1).padStart(2, '0') + '-' +
- String(date.getDate()).padStart(2, '0') + ' ' +
- String(date.getHours()).padStart(2, '0') + ':' +
- String(date.getMinutes()).padStart(2, '0') + ':' +
- String(date.getSeconds()).padStart(2, '0');
- };
-
- this.dateFilter.customRange = [
- formatDate(startDate),
- formatDate(endDate)
- ];
- }
-
- this.refreshChartsData();
- },
-
- // 获取今日日期字符串
- getTodayDate() {
- return new Date().toISOString().split('T')[0];
- },
-
- // 获取自定义范围天数
- getCustomRangeDays() {
- if (!this.dateFilter.customStart || !this.dateFilter.customEnd) return 0;
- const start = new Date(this.dateFilter.customStart);
- const end = new Date(this.dateFilter.customEnd);
- return Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1;
- },
-
- // 验证并设置自定义日期范围
- validateAndSetCustomRange() {
- if (!this.dateFilter.customStart || !this.dateFilter.customEnd) return;
-
- const start = new Date(this.dateFilter.customStart);
- const end = new Date(this.dateFilter.customEnd);
- const today = new Date();
-
- // 确保结束日期不晚于今天
- if (end > today) {
- this.dateFilter.customEnd = this.getTodayDate();
- end.setTime(today.getTime());
- }
-
- // 确保开始日期不晚于结束日期
- if (start > end) {
- this.dateFilter.customStart = this.dateFilter.customEnd;
- start.setTime(end.getTime());
- }
-
- // 限制最大31天
- const daysDiff = this.getCustomRangeDays();
- if (daysDiff > 31) {
- // 自动调整开始日期,保持31天范围
- const newStart = new Date(end);
- newStart.setDate(end.getDate() - 30); // 31天范围
- this.dateFilter.customStart = newStart.toISOString().split('T')[0];
-
- this.showToast('日期范围已自动调整为最大31天', 'warning', '范围限制');
- }
-
- // 只有在都有效时才更新
- if (this.dateFilter.customStart && this.dateFilter.customEnd) {
- this.dateFilter.type = 'custom';
- this.refreshChartsData();
- }
- },
-
- setDateFilterCustom() {
- this.validateAndSetCustomRange();
- },
-
- // 一体化日期范围选择器相关方法
- toggleDateRangePicker() {
- this.showDateRangePicker = !this.showDateRangePicker;
- },
-
- getDateRangeDisplayText() {
- if (this.dateFilter.type === 'preset') {
- const option = this.dateFilter.presetOptions.find(opt => opt.value === this.dateFilter.preset);
- return option ? option.label : '自定义范围';
- } else if (this.dateFilter.customStart && this.dateFilter.customEnd) {
- const start = new Date(this.dateFilter.customStart).toLocaleDateString('zh-CN', {month: 'short', day: 'numeric'});
- const end = new Date(this.dateFilter.customEnd).toLocaleDateString('zh-CN', {month: 'short', day: 'numeric'});
- return start + ' - ' + end + ' (' + this.getCustomRangeDays() + '天)';
- }
- return '选择日期范围';
- },
-
- getCustomDateRangeText() {
- if (this.dateFilter.type === 'custom' && this.dateFilter.customStart && this.dateFilter.customEnd) {
- const start = new Date(this.dateFilter.customStart).toLocaleDateString('zh-CN', {month: 'short', day: 'numeric'});
- const end = new Date(this.dateFilter.customEnd).toLocaleDateString('zh-CN', {month: 'short', day: 'numeric'});
- return start + ' - ' + end;
- }
- return '自定义范围';
- },
-
- onDateRangeChange() {
- // 实时验证日期范围
- if (this.dateFilter.customStart && this.dateFilter.customEnd) {
- const start = new Date(this.dateFilter.customStart);
- const end = new Date(this.dateFilter.customEnd);
- const today = new Date();
-
- // 确保结束日期不晚于今天
- if (end > today) {
- this.dateFilter.customEnd = this.getTodayDate();
- }
-
- // 确保开始日期不晚于结束日期
- if (start > end) {
- this.dateFilter.customStart = this.dateFilter.customEnd;
- }
-
- // 限制最大31天
- const daysDiff = this.getCustomRangeDays();
- if (daysDiff > 31) {
- const newStart = new Date(end);
- newStart.setDate(end.getDate() - 30);
- this.dateFilter.customStart = newStart.toISOString().split('T')[0];
- }
- }
- },
-
- clearDateRange() {
- this.dateFilter.customStart = '';
- this.dateFilter.customEnd = '';
- this.dateFilter.type = 'preset';
- this.dateFilter.preset = '7days'; // 恢复默认
- },
-
- applyDateRange() {
- if (this.dateFilter.customStart && this.dateFilter.customEnd) {
- this.dateFilter.type = 'custom';
- this.dateFilter.preset = ''; // 清除预设选择
- this.showDateRangePicker = false;
- this.refreshChartsData();
- } else {
- this.showToast('请选择完整的日期范围', 'warning', '日期范围');
- }
- },
-
- refreshChartsData() {
- // 根据当前日期筛选设置更新数据
- let days;
- if (this.dateFilter.type === 'preset') {
- const option = this.dateFilter.presetOptions.find(opt => opt.value === this.dateFilter.preset);
- days = option ? option.days : 7;
-
- // 设置模型统计期间
- if (this.dateFilter.preset === 'today') {
- this.dashboardModelPeriod = 'daily';
- } else {
- this.dashboardModelPeriod = 'monthly';
- }
- } else {
- // 自定义日期范围
- const start = new Date(this.dateFilter.customStart);
- const end = new Date(this.dateFilter.customEnd);
- days = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1;
- this.dashboardModelPeriod = 'daily'; // 自定义范围使用日统计
- }
-
- this.trendPeriod = days;
-
- // 重新加载数据
- this.loadDashboardModelStats();
- this.loadUsageTrend();
- this.loadApiKeysUsageTrend();
- },
-
- // 设置趋势图粒度
- setTrendGranularity(granularity) {
- console.log('Setting trend granularity to:', granularity);
- this.trendGranularity = granularity;
-
- // 根据粒度更新预设选项
- if (granularity === 'hour') {
- this.dateFilter.presetOptions = [
- { value: 'last24h', label: '近24小时', hours: 24 },
- { value: 'yesterday', label: '昨天', hours: 24 },
- { value: 'dayBefore', label: '前天', hours: 24 }
- ];
-
- // 检查当前自定义日期范围是否超过24小时
- if (this.dateFilter.type === 'custom' && this.dateFilter.customRange && this.dateFilter.customRange.length === 2) {
- const start = new Date(this.dateFilter.customRange[0]);
- const end = new Date(this.dateFilter.customRange[1]);
- const hoursDiff = (end - start) / (1000 * 60 * 60);
-
- if (hoursDiff > 24) {
- this.showToast('切换到小时粒度,日期范围已调整为近24小时', 'info');
- this.dateFilter.preset = 'last24h';
- this.setDateFilterPreset('last24h');
- }
- } else if (['today', '7days', '30days'].includes(this.dateFilter.preset)) {
- // 预设不兼容,切换到近24小时
- this.dateFilter.preset = 'last24h';
- this.setDateFilterPreset('last24h');
- }
- } else {
- // 恢复天粒度的选项
- this.dateFilter.presetOptions = [
- { value: 'today', label: '今天', days: 1 },
- { value: '7days', label: '近7天', days: 7 },
- { value: '30days', label: '近30天', days: 30 }
- ];
-
- // 如果当前是小时粒度的预设,切换到天粒度的默认预设
- if (['last24h', 'yesterday', 'dayBefore'].includes(this.dateFilter.preset)) {
- this.dateFilter.preset = '7days';
- this.setDateFilterPreset('7days');
- } else if (this.dateFilter.type === 'custom') {
- // 自定义日期范围在天粒度下通常不需要调整,因为24小时肯定在31天内
- // 只需要重新加载数据
- this.refreshChartsData();
- return;
- }
- }
-
- // 重新加载数据
- this.loadUsageTrend();
- this.loadApiKeysUsageTrend();
- },
-
- // API Keys 日期筛选方法
- setApiKeyDateFilterPreset(preset, keyId) {
- console.log('Setting API Key date filter preset:', preset, 'for keyId:', keyId);
-
- const filter = this.getApiKeyDateFilter(keyId);
- console.log('Before preset change - type:', filter.type, 'preset:', filter.preset);
-
- filter.type = 'preset';
- filter.preset = preset;
-
- // 根据预设计算并设置具体的日期范围
- const option = filter.presetOptions.find(opt => opt.value === preset);
- if (option) {
- const today = new Date();
- const startDate = new Date(today);
- startDate.setDate(today.getDate() - (option.days - 1));
-
- // 设置为日期字符串格式 YYYY-MM-DD
- filter.customStart = startDate.toISOString().split('T')[0];
- filter.customEnd = today.toISOString().split('T')[0];
-
- // 同时设置customRange,让日期选择器显示当前选中的范围
- // 格式化为 Element Plus 需要的格式
- const formatDate = (date) => {
- return date.getFullYear() + '-' +
- String(date.getMonth() + 1).padStart(2, '0') + '-' +
- String(date.getDate()).padStart(2, '0') + ' 00:00:00';
- };
-
- filter.customRange = [
- formatDate(startDate),
- formatDate(today)
- ];
-
- console.log('Set customStart to:', filter.customStart);
- console.log('Set customEnd to:', filter.customEnd);
- console.log('Set customRange to:', filter.customRange);
- }
-
- console.log('After preset change - type:', filter.type, 'preset:', filter.preset);
-
- // 立即加载数据
- this.loadApiKeyModelStats(keyId, true);
- },
-
- validateAndSetApiKeyCustomRange(keyId) {
- const filter = this.getApiKeyDateFilter(keyId);
-
- if (!filter.customStart || !filter.customEnd) return;
-
- const start = new Date(filter.customStart);
- const end = new Date(filter.customEnd);
- const today = new Date();
-
- // 确保结束日期不晚于今天
- if (end > today) {
- filter.customEnd = this.getTodayDate();
- end.setTime(today.getTime());
- }
-
- // 确保开始日期不晚于结束日期
- if (start > end) {
- filter.customStart = filter.customEnd;
- start.setTime(end.getTime());
- }
-
- // 限制最大31天
- const daysDiff = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1;
- if (daysDiff > 31) {
- // 自动调整开始日期,保持31天范围
- const newStart = new Date(end);
- newStart.setDate(end.getDate() - 30); // 31天范围
- filter.customStart = newStart.toISOString().split('T')[0];
-
- this.showToast('日期范围已自动调整为最大31天', 'warning', '范围限制');
- }
-
- // 只有在都有效时才更新
- if (filter.customStart && filter.customEnd) {
- filter.type = 'custom';
- this.apiKeyModelPeriod = 'daily'; // 自定义范围使用日统计
-
- // 强制重新加载该API Key的数据
- this.loadApiKeyModelStats(keyId, true);
- }
- },
-
- // API Keys 一体化日期范围选择器相关方法
- toggleApiKeyDateRangePicker() {
- this.showApiKeyDateRangePicker = !this.showApiKeyDateRangePicker;
- },
-
- getApiKeyDateRangeDisplayText() {
- if (this.apiKeyDateFilter.type === 'preset') {
- const option = this.apiKeyDateFilter.presetOptions.find(opt => opt.value === this.apiKeyDateFilter.preset);
- return option ? option.label : '自定义';
- } else if (this.apiKeyDateFilter.customStart && this.apiKeyDateFilter.customEnd) {
- const start = new Date(this.apiKeyDateFilter.customStart).toLocaleDateString('zh-CN', {month: 'short', day: 'numeric'});
- const end = new Date(this.apiKeyDateFilter.customEnd).toLocaleDateString('zh-CN', {month: 'short', day: 'numeric'});
- return start + ' - ' + end;
- }
- return '自定义';
- },
-
- getApiKeyCustomDateRangeText() {
- if (this.apiKeyDateFilter.type === 'custom' && this.apiKeyDateFilter.customStart && this.apiKeyDateFilter.customEnd) {
- const start = new Date(this.apiKeyDateFilter.customStart).toLocaleDateString('zh-CN', {month: 'short', day: 'numeric'});
- const end = new Date(this.apiKeyDateFilter.customEnd).toLocaleDateString('zh-CN', {month: 'short', day: 'numeric'});
- return start + ' - ' + end;
- }
- return '自定义范围';
- },
-
- getApiKeyCustomRangeDays() {
- if (!this.apiKeyDateFilter.customStart || !this.apiKeyDateFilter.customEnd) return 0;
- const start = new Date(this.apiKeyDateFilter.customStart);
- const end = new Date(this.apiKeyDateFilter.customEnd);
- return Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1;
- },
-
- onApiKeyDateRangeChange() {
- if (this.apiKeyDateFilter.customStart && this.apiKeyDateFilter.customEnd) {
- const start = new Date(this.apiKeyDateFilter.customStart);
- const end = new Date(this.apiKeyDateFilter.customEnd);
- const today = new Date();
-
- // 确保结束日期不晚于今天
- if (end > today) {
- this.apiKeyDateFilter.customEnd = this.getTodayDate();
- }
-
- // 确保开始日期不晚于结束日期
- if (start > end) {
- this.apiKeyDateFilter.customStart = this.apiKeyDateFilter.customEnd;
- }
-
- // 限制最大31天
- const daysDiff = this.getApiKeyCustomRangeDays();
- if (daysDiff > 31) {
- const newStart = new Date(end);
- newStart.setDate(end.getDate() - 30);
- this.apiKeyDateFilter.customStart = newStart.toISOString().split('T')[0];
- }
- }
- },
-
- clearApiKeyDateRange() {
- this.apiKeyDateFilter.customStart = '';
- this.apiKeyDateFilter.customEnd = '';
- this.apiKeyDateFilter.type = 'preset';
- this.apiKeyDateFilter.preset = '7days'; // 恢复默认
- },
-
- applyApiKeyDateRange(keyId) {
- if (this.apiKeyDateFilter.customStart && this.apiKeyDateFilter.customEnd) {
- this.apiKeyDateFilter.type = 'custom';
- this.apiKeyDateFilter.preset = ''; // 清除预设选择
- this.apiKeyModelPeriod = 'daily'; // 自定义范围使用日统计
- this.showApiKeyDateRangePicker = false;
-
- // 强制重新加载该API Key的数据
- this.loadApiKeyModelStats(keyId, true);
- } else {
- this.showToast('请选择完整的日期范围', 'warning', '日期范围');
- }
- },
-
- // Element Plus 日期选择器相关方法
-
- // 禁用未来日期
- disabledDate(date) {
- return date > new Date();
- },
-
- // 仪表盘自定义日期范围变化处理
- onCustomDateRangeChange(value) {
- if (value && value.length === 2) {
- // 清除快捷选择的焦点状态
- this.dateFilter.type = 'custom';
- this.dateFilter.preset = '';
- this.dateFilter.customStart = value[0].split(' ')[0];
- this.dateFilter.customEnd = value[1].split(' ')[0];
-
- // 检查日期范围限制
- const start = new Date(value[0]);
- const end = new Date(value[1]);
-
- if (this.trendGranularity === 'hour') {
- // 小时粒度:限制24小时
- const hoursDiff = (end - start) / (1000 * 60 * 60);
- if (hoursDiff > 24) {
- this.showToast('小时粒度下日期范围不能超过24小时', 'warning', '范围限制');
- // 调整结束时间为开始时间后24小时
- const newEnd = new Date(start.getTime() + 24 * 60 * 60 * 1000);
- const formatDate = (date) => {
- return date.getFullYear() + '-' +
- String(date.getMonth() + 1).padStart(2, '0') + '-' +
- String(date.getDate()).padStart(2, '0') + ' ' +
- String(date.getHours()).padStart(2, '0') + ':' +
- String(date.getMinutes()).padStart(2, '0') + ':' +
- String(date.getSeconds()).padStart(2, '0');
- };
- this.dateFilter.customRange = [
- formatDate(start),
- formatDate(newEnd)
- ];
- this.dateFilter.customEnd = newEnd.toISOString().split('T')[0];
- return;
- }
- } else {
- // 天粒度:限制31天
- const daysDiff = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1;
- if (daysDiff > 31) {
- this.showToast('日期范围不能超过31天', 'warning', '范围限制');
- // 重置为默认7天
- this.dateFilter.customRange = null;
- this.dateFilter.type = 'preset';
- this.dateFilter.preset = '7days';
- return;
- }
- }
-
- this.refreshChartsData();
- } else if (value === null) {
- // 清空时恢复默认
- this.dateFilter.type = 'preset';
- this.dateFilter.preset = this.trendGranularity === 'hour' ? 'last24h' : '7days';
- this.dateFilter.customStart = '';
- this.dateFilter.customEnd = '';
- this.refreshChartsData();
- }
- },
-
- // API Keys自定义日期范围变化处理
- onApiKeyCustomDateRangeChange(keyId) {
- return (value) => {
- const filter = this.getApiKeyDateFilter(keyId);
- console.log('API Key custom date range change:', value, 'for keyId:', keyId);
- console.log('Before change - type:', filter.type, 'preset:', filter.preset);
-
- // 更新 customRange 值
- filter.customRange = value;
-
- if (value && value.length === 2) {
- // 清除快捷选择的焦点状态
- filter.type = 'custom';
- filter.preset = ''; // 清空preset确保快捷按钮失去焦点
- filter.customStart = value[0].split(' ')[0];
- filter.customEnd = value[1].split(' ')[0];
-
- console.log('After change - type:', filter.type, 'preset:', filter.preset);
- console.log('Set customStart to:', filter.customStart);
- console.log('Set customEnd to:', filter.customEnd);
-
- // 检查日期范围限制
- const start = new Date(value[0]);
- const end = new Date(value[1]);
- const daysDiff = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1;
-
- if (daysDiff > 31) {
- this.showToast('日期范围不能超过31天', 'warning', '范围限制');
- // 重置到默认7天
- this.resetApiKeyDateFilterToDefault(keyId);
- return;
- }
-
- // 立即加载数据
- console.log('Loading model stats after date range selection');
- this.loadApiKeyModelStats(keyId, true);
- } else if (value === null || value === undefined) {
- // 清空时恢复默认7天
- this.resetApiKeyDateFilterToDefault(keyId);
-
- console.log('Cleared - type:', filter.type, 'preset:', filter.preset);
-
- // 加载数据
- this.loadApiKeyModelStats(keyId, true);
- }
- };
- },
-
- // 初始化API Key的日期筛选器
- initApiKeyDateFilter(keyId) {
- const today = new Date();
- const startDate = new Date(today);
- startDate.setDate(today.getDate() - 6); // 7天前
-
- // Vue 3 直接赋值即可,不需要 $set
- this.apiKeyDateFilters[keyId] = {
- type: 'preset',
- preset: '7days',
- customStart: startDate.toISOString().split('T')[0],
- customEnd: today.toISOString().split('T')[0],
- customRange: null,
- presetOptions: this.apiKeyDateFilterDefaults.presetOptions
- };
- },
-
- // 获取API Key的日期筛选器状态
- getApiKeyDateFilter(keyId) {
- if (!this.apiKeyDateFilters[keyId]) {
- this.initApiKeyDateFilter(keyId);
- }
- return this.apiKeyDateFilters[keyId];
- },
-
- // 重置API Key日期筛选器为默认值(内部使用)
- resetApiKeyDateFilterToDefault(keyId) {
- const filter = this.getApiKeyDateFilter(keyId);
-
- // 重置为默认的7天预设
- filter.type = 'preset';
- filter.preset = '7days';
- filter.customRange = null;
-
- // 计算7天的具体日期范围
- const today = new Date();
- const startDate = new Date(today);
- startDate.setDate(today.getDate() - 6); // 7天前
-
- filter.customStart = startDate.toISOString().split('T')[0];
- filter.customEnd = today.toISOString().split('T')[0];
-
- console.log(`Reset API Key ${keyId} to default 7 days range:`, filter.customStart, 'to', filter.customEnd);
- },
-
- // 重置API Key日期筛选器并刷新
- resetApiKeyDateFilter(keyId) {
- console.log('Resetting API Key date filter for keyId:', keyId);
-
- this.resetApiKeyDateFilterToDefault(keyId);
-
- // 使用nextTick确保状态更新后再加载数据
- this.$nextTick(() => {
- this.loadApiKeyModelStats(keyId, true);
- });
-
- this.showToast('已重置筛选条件并刷新数据', 'info', '重置成功');
- },
-
- // OEM设置相关方法
- async loadOemSettings() {
- this.oemSettingsLoading = true;
- try {
- const result = await this.apiRequest('/admin/oem-settings');
- if (result && result.success) {
- this.oemSettings = { ...this.oemSettings, ...result.data };
-
- // 应用设置到页面
- this.applyOemSettings();
- } else {
- // 如果请求失败但不是因为认证问题,使用默认值
- console.warn('Failed to load OEM settings, using defaults');
- this.applyOemSettings();
- }
- } catch (error) {
- console.error('Error loading OEM settings:', error);
- // 加载失败时也应用默认值,确保页面正常显示
- this.applyOemSettings();
- } finally {
- this.oemSettingsLoading = false;
- }
- },
-
- async saveOemSettings() {
- // 验证输入
- if (!this.oemSettings.siteName || this.oemSettings.siteName.trim() === '') {
- this.showToast('网站名称不能为空', 'error', '验证失败');
- return;
- }
-
- if (this.oemSettings.siteName.length > 100) {
- this.showToast('网站名称不能超过100个字符', 'error', '验证失败');
- return;
- }
-
- this.oemSettingsSaving = true;
- try {
- const result = await this.apiRequest('/admin/oem-settings', {
- method: 'PUT',
- body: JSON.stringify({
- siteName: this.oemSettings.siteName.trim(),
- siteIcon: this.oemSettings.siteIcon.trim(),
- siteIconData: this.oemSettings.siteIconData.trim()
- })
- });
-
- if (result && result.success) {
- this.oemSettings = { ...this.oemSettings, ...result.data };
- this.showToast('OEM设置保存成功', 'success', '保存成功');
-
- // 应用设置到页面
- this.applyOemSettings();
- } else {
- this.showToast(result?.message || '保存失败', 'error', '保存失败');
- }
- } catch (error) {
- console.error('Error saving OEM settings:', error);
- this.showToast('保存OEM设置失败', 'error', '保存失败');
- } finally {
- this.oemSettingsSaving = false;
- }
- },
-
- applyOemSettings() {
- // 更新网站标题
- document.title = `${this.oemSettings.siteName} - 管理后台`;
-
- // 更新页面中的所有网站名称
- const titleElements = document.querySelectorAll('.header-title');
- titleElements.forEach(el => {
- el.textContent = this.oemSettings.siteName;
- });
-
- // 应用自定义CSS
- this.applyCustomCss();
-
- // 应用网站图标
- this.applyFavicon();
- },
-
- applyCustomCss() {
- // 移除之前的自定义CSS
- const existingStyle = document.getElementById('custom-oem-css');
- if (existingStyle) {
- existingStyle.remove();
- }
- },
-
- applyFavicon() {
- const iconData = this.oemSettings.siteIconData || this.oemSettings.siteIcon;
- if (iconData && iconData.trim()) {
- // 移除现有的favicon
- const existingFavicons = document.querySelectorAll('link[rel*="icon"]');
- existingFavicons.forEach(link => link.remove());
-
- // 添加新的favicon
- const link = document.createElement('link');
- link.rel = 'icon';
-
- // 根据数据类型设置适当的type
- if (iconData.startsWith('data:')) {
- // Base64数据
- link.href = iconData;
- } else {
- // URL
- link.type = 'image/x-icon';
- link.href = iconData;
- }
-
- document.head.appendChild(link);
- }
- },
-
- resetOemSettings() {
- this.oemSettings = {
- siteName: 'Claude Relay Service',
- siteIcon: '',
- siteIconData: '',
- updatedAt: null
- };
- },
-
- // 处理图标文件上传
- async handleIconUpload(event) {
- const file = event.target.files[0];
- if (!file) return;
-
- // 验证文件大小
- if (file.size > 350 * 1024) { // 350KB
- this.showToast('图标文件大小不能超过350KB', 'error', '文件太大');
- return;
- }
-
- // 验证文件类型
- const allowedTypes = ['image/x-icon', 'image/png', 'image/jpeg', 'image/svg+xml'];
- if (!allowedTypes.includes(file.type) && !file.name.endsWith('.ico')) {
- this.showToast('请选择有效的图标文件格式 (.ico, .png, .jpg, .svg)', 'error', '格式错误');
- return;
- }
-
- try {
- // 读取文件为Base64
- const reader = new FileReader();
- reader.onload = (e) => {
- this.oemSettings.siteIconData = e.target.result;
- this.oemSettings.siteIcon = ''; // 清空URL
- this.showToast('图标上传成功', 'success', '上传成功');
- };
- reader.onerror = () => {
- this.showToast('图标文件读取失败', 'error', '读取失败');
- };
- reader.readAsDataURL(file);
- } catch (error) {
- console.error('Icon upload error:', error);
- this.showToast('图标上传过程中出现错误', 'error', '上传失败');
- }
- },
-
- // 移除图标
- removeIcon() {
- this.oemSettings.siteIcon = '';
- this.oemSettings.siteIconData = '';
- if (this.$refs.iconFileInput) {
- this.$refs.iconFileInput.value = '';
- }
- },
-
- // 处理图标加载错误
- handleIconError(event) {
- console.error('Icon load error');
- event.target.style.display = 'none';
- }
- }
-});
-
-// 使用Element Plus,确保正确的语言包配置
-if (typeof ElementPlus !== 'undefined') {
- app.use(ElementPlus, {
- locale: typeof ElementPlusLocaleZhCn !== 'undefined' ? ElementPlusLocaleZhCn : undefined
- });
-} else {
- console.warn('Element Plus 未正确加载');
-}
-
-// 挂载应用
-app.mount('#app');
\ No newline at end of file
diff --git a/web/admin/index.html b/web/admin/index.html
deleted file mode 100644
index 44327661..00000000
--- a/web/admin/index.html
+++ /dev/null
@@ -1,3928 +0,0 @@
-
-
-
-
-
- Claude Relay Service - 管理后台
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
e.target.style.display = 'none'">
-
-
-
-
管理后台
-
-
-
-
- 用户名
-
-
-
-
- 密码
-
-
-
-
-
-
- {{ loginLoading ? '登录中...' : '登录' }}
-
-
-
-
- {{ loginError }}
-
-
-
-
-
-
-
-
-
-
-
-
e.target.style.display = 'none'">
-
-
-
-
-
-
-
-
v{{ versionInfo.current || '...' }}
-
-
-
- 新版本
-
-
-
-
管理后台
-
-
-
-
-
-
-
-
-
-
-
-
- {{ tab.name }}
-
-
-
-
-
-
-
-
-
-
-
总API Keys
-
{{ dashboardData.totalApiKeys }}
-
活跃: {{ dashboardData.activeApiKeys || 0 }}
-
-
-
-
-
-
-
-
-
-
-
服务账户
-
{{ dashboardData.totalAccounts }}
-
- 活跃: {{ dashboardData.activeAccounts || 0 }}
-
- | 限流: {{ dashboardData.rateLimitedAccounts }}
-
-
-
-
-
-
-
-
-
-
-
-
-
今日请求
-
{{ dashboardData.todayRequests }}
-
总请求: {{ formatNumber(dashboardData.totalRequests || 0) }}
-
-
-
-
-
-
-
-
-
-
-
系统状态
-
{{ dashboardData.systemStatus }}
-
运行时间: {{ formatUptime(dashboardData.uptime) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
今日Token
-
-
{{ formatNumber((dashboardData.todayInputTokens || 0) + (dashboardData.todayOutputTokens || 0) + (dashboardData.todayCacheCreateTokens || 0) + (dashboardData.todayCacheReadTokens || 0)) }}
-
/ {{ costsData.todayCosts.formatted.totalCost }}
-
-
-
- 输入: {{ formatNumber(dashboardData.todayInputTokens || 0) }}
- 输出: {{ formatNumber(dashboardData.todayOutputTokens || 0) }}
- 缓存创建: {{ formatNumber(dashboardData.todayCacheCreateTokens || 0) }}
- 缓存读取: {{ formatNumber(dashboardData.todayCacheReadTokens || 0) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
总Token消耗
-
-
{{ formatNumber((dashboardData.totalInputTokens || 0) + (dashboardData.totalOutputTokens || 0) + (dashboardData.totalCacheCreateTokens || 0) + (dashboardData.totalCacheReadTokens || 0)) }}
-
/ {{ costsData.totalCosts.formatted.totalCost }}
-
-
-
- 输入: {{ formatNumber(dashboardData.totalInputTokens || 0) }}
- 输出: {{ formatNumber(dashboardData.totalOutputTokens || 0) }}
- 缓存创建: {{ formatNumber(dashboardData.totalCacheCreateTokens || 0) }}
- 缓存读取: {{ formatNumber(dashboardData.totalCacheReadTokens || 0) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
平均RPM
-
{{ dashboardData.systemRPM || 0 }}
-
每分钟请求数
-
-
-
-
-
-
-
-
-
-
-
平均TPM
-
{{ dashboardData.systemTPM || 0 }}
-
每分钟Token数
-
-
-
-
-
-
-
-
-
-
-
-
模型使用分布与Token使用趋势
-
-
-
-
- {{ option.label }}
-
-
-
-
-
-
- 按天
-
-
- 按小时
-
-
-
-
-
-
-
- 最多24小时
-
-
-
-
- 刷新
-
-
-
-
-
-
-
-
-
-
-
-
详细统计数据
-
-
-
-
-
- 模型
- 请求数
- 总Token
- 费用
- 占比
-
-
-
-
- {{ stat.model }}
- {{ formatNumber(stat.requests) }}
- {{ formatNumber(stat.allTokens) }}
- {{ stat.formatted ? stat.formatted.total : '$0.000000' }}
-
-
- {{ calculatePercentage(stat.allTokens, dashboardModelStats) }}%
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
API Keys 使用趋势
-
-
-
- 请求次数
-
-
- Token 数量
-
-
-
-
-
- 共 {{ apiKeysTrendData.totalApiKeys }} 个 API Key,显示使用量前 10 个
-
-
- 共 {{ apiKeysTrendData.totalApiKeys }} 个 API Key
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
API Keys 管理
-
管理和监控您的 API 密钥
-
-
-
-
- 今日
- 最近7天
- 本月
- 全部时间
-
-
- 创建新 Key
-
-
-
-
-
-
-
-
-
-
-
暂无 API Keys
-
点击上方按钮创建您的第一个 API Key
-
-
-
-
-
-
-
- 名称
-
-
-
- API Key
-
- 状态
-
-
-
-
- 使用统计
-
- (费用
-
- )
-
-
-
- 创建时间
-
-
-
-
- 过期时间
-
-
-
- 操作
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ key.name }}
-
{{ key.id }}
-
-
-
- 绑定: {{ getBoundAccountName(key.claudeAccountId) }}
-
-
-
- 使用共享池
-
-
-
-
-
-
-
- {{ (key.apiKey || '').substring(0, 20) }}...
-
-
-
-
-
- {{ key.isActive ? '活跃' : '禁用' }}
-
-
-
-
-
-
- 请求数:
- {{ formatNumber((key.usage && key.usage.total && key.usage.total.requests) || 0) }}
-
-
-
- Token:
- {{ formatNumber((key.usage && key.usage.total && key.usage.total.tokens) || 0) }}
-
-
-
- 费用:
- {{ calculateApiKeyCost(key.usage) }}
-
-
-
- 今日费用:
-
- ${{ (key.dailyCost || 0).toFixed(2) }} / ${{ key.dailyCostLimit.toFixed(2) }}
-
-
-
-
- 并发限制:
- {{ key.concurrencyLimit > 0 ? key.concurrencyLimit : '无限制' }}
-
-
-
- 当前并发:
-
- {{ key.currentConcurrency || 0 }}
- / {{ key.concurrencyLimit }}
-
-
-
-
- 时间窗口:
- {{ key.rateLimitWindow }} 分钟
-
-
-
- 请求限制:
- {{ key.rateLimitRequests }} 次/窗口
-
-
-
- 输入: {{ formatNumber((key.usage && key.usage.total && key.usage.total.inputTokens) || 0) }}
- 输出: {{ formatNumber((key.usage && key.usage.total && key.usage.total.outputTokens) || 0) }}
-
-
-
- 缓存创建: {{ formatNumber((key.usage && key.usage.total && key.usage.total.cacheCreateTokens) || 0) }}
- 缓存读取: {{ formatNumber((key.usage && key.usage.total && key.usage.total.cacheReadTokens) || 0) }}
-
-
-
- RPM: {{ (key.usage && key.usage.averages && key.usage.averages.rpm) || 0 }}
- TPM: {{ (key.usage && key.usage.averages && key.usage.averages.tpm) || 0 }}
-
-
-
-
- 今日: {{ formatNumber((key.usage && key.usage.daily && key.usage.daily.requests) || 0) }}次
- {{ formatNumber((key.usage && key.usage.daily && key.usage.daily.tokens) || 0) }}T
-
-
-
-
-
-
- 模型使用分布
-
-
-
-
-
- {{ new Date(key.createdAt).toLocaleDateString() }}
-
-
-
-
-
- 已过期
-
-
-
- {{ formatExpireDate(key.expiresAt) }}
-
-
- {{ formatExpireDate(key.expiresAt) }}
-
-
-
-
- 永不过期
-
-
-
-
-
- 编辑
-
-
- 续期
-
-
- 删除
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 模型使用分布
-
-
-
- {{ apiKeyModelStats[key.id].length }} 个模型
-
-
-
-
-
-
-
- {{ option.label }}
-
-
-
-
-
onApiKeyCustomDateRangeChange(key.id)(value)"
- type="datetimerange"
- range-separator="至"
- start-placeholder="开始日期"
- end-placeholder="结束日期"
- format="YYYY-MM-DD HH:mm:ss"
- value-format="YYYY-MM-DD HH:mm:ss"
- :disabled-date="disabledDate"
- size="small"
- style="width: 280px;"
- class="api-key-date-picker"
- :clearable="true"
- :unlink-panels="false"
- @visible-change="(visible) => !visible && $forceUpdate()"
- >
-
-
-
-
-
-
-
-
-
暂无模型使用数据
-
-
- 刷新
-
-
-
尝试调整时间范围或点击刷新重新加载数据
-
-
-
-
-
- {{ stat.model }}
- {{ stat.requests }} 次请求
-
-
-
-
-
-
-
- 总Token:
-
- {{ formatNumber(stat.allTokens) }}
-
-
-
-
- 费用:
-
- {{ calculateModelCost(stat) }}
-
-
-
-
- 输入:
-
- {{ formatNumber(stat.inputTokens) }}
-
-
-
-
- 输出:
-
- {{ formatNumber(stat.outputTokens) }}
-
-
-
-
-
- 缓存创建:
-
- {{ formatNumber(stat.cacheCreateTokens) }}
-
-
-
-
- 缓存读取:
-
- {{ formatNumber(stat.cacheReadTokens) }}
-
-
-
-
-
-
-
-
- {{ calculateApiKeyModelPercentage(stat.allTokens, apiKeyModelStats[key.id]) }}%
-
-
-
-
-
-
-
-
-
-
- 总计统计
-
-
-
- 总请求: {{ apiKeyModelStats[key.id].reduce((sum, stat) => sum + stat.requests, 0) }}
-
-
- 总Token: {{ formatNumber(apiKeyModelStats[key.id].reduce((sum, stat) => sum + stat.allTokens, 0)) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
账户管理
-
管理您的 Claude 和 Gemini 账户及代理配置
-
-
-
- 按名称排序
- 按今日Token排序
- 按今日请求数排序
- 按总Token排序
- 按最后使用排序
-
-
- 添加账户
-
-
-
-
-
-
-
-
-
-
-
暂无 Claude 账户
-
点击上方按钮添加您的第一个账户
-
-
-
-
-
-
-
- 名称
-
-
-
-
- 平台
-
-
-
-
- 类型
-
-
-
-
- 状态
-
-
-
- 代理
- 今日使用
- 会话窗口
- 最后使用
- 操作
-
-
-
-
-
-
-
-
-
-
-
-
{{ account.name }}
-
- 专属
-
-
- 共享
-
-
-
{{ account.id }}
-
-
-
-
-
- Gemini
-
-
- Claude
-
-
-
-
- OAuth
-
-
- 传统
-
-
-
-
-
-
- {{ account.isActive ? '正常' : '异常' }}
-
-
-
- 限流中 ({{ account.rateLimitStatus.minutesRemaining }}分钟)
-
-
- 绑定: {{ account.boundApiKeysCount || 0 }} 个API Key
-
-
-
-
-
- {{ account.proxy.type }}://{{ account.proxy.host }}:{{ account.proxy.port }}
-
- 无代理
-
-
-
-
-
-
{{ account.usage.daily.requests || 0 }} 次
-
-
-
-
{{ formatNumber(account.usage.daily.allTokens || 0) }} tokens
-
-
- 平均 {{ account.usage.averages.rpm.toFixed(2) }} RPM
-
-
- 暂无数据
-
-
-
-
-
-
- {{ account.sessionWindow.progress }}%
-
-
-
-
{{ formatSessionWindow(account.sessionWindow.windowStart, account.sessionWindow.windowEnd) }}
-
- 剩余 {{ formatRemainingTime(account.sessionWindow.remainingTime) }}
-
-
-
-
-
-
-
- N/A
-
-
-
- {{ account.lastUsedAt ? new Date(account.lastUsedAt).toLocaleDateString() : '从未使用' }}
-
-
-
- 编辑
-
-
- 删除
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
模型使用统计
-
查看不同模型的使用量和费用统计
-
-
-
- 今日
- 本月
-
-
- 刷新
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ stat.model }}
-
{{ stat.period === 'daily' ? '今日使用' : '本月使用' }}
-
-
-
{{ (stat.formatted && stat.formatted.total) || '$0.000000' }}
-
总费用
-
-
-
-
-
-
{{ formatNumber((stat.usage && stat.usage.requests) || 0) }}
-
请求数
-
-
-
{{ formatNumber((stat.usage && stat.usage.inputTokens) || 0) }}
-
输入Token
-
-
-
{{ formatNumber((stat.usage && stat.usage.outputTokens) || 0) }}
-
输出Token
-
-
-
{{ formatNumber((stat.usage && stat.usage.totalTokens) || 0) }}
-
总Token
-
-
-
-
-
-
-
{{ formatNumber((stat.usage && stat.usage.cacheCreateTokens) || 0) }}
-
缓存创建
-
-
-
{{ formatNumber((stat.usage && stat.usage.cacheReadTokens) || 0) }}
-
缓存读取
-
-
-
-
-
-
-
{{ (stat.formatted && stat.formatted.input) || '$0.000000' }}
-
输入费用
-
-
-
{{ (stat.formatted && stat.formatted.output) || '$0.000000' }}
-
输出费用
-
-
-
{{ (stat.formatted && stat.formatted.cacheWrite) || '$0.000000' }}
-
缓存写入
-
-
-
{{ (stat.formatted && stat.formatted.cacheRead) || '$0.000000' }}
-
缓存读取
-
-
-
-
-
-
定价信息 (USD per 1M tokens):
-
-
输入: ${{ (stat.pricing && stat.pricing.input) || 0 }}
-
输出: ${{ (stat.pricing && stat.pricing.output) || 0 }}
-
缓存写: ${{ (stat.pricing && stat.pricing.cacheWrite) || 0 }}
-
缓存读: ${{ (stat.pricing && stat.pricing.cacheRead) || 0 }}
-
-
-
-
-
-
-
-
-
-
-
- Claude Code 使用教程
-
-
跟着这个教程,你可以轻松在自己的电脑上安装并使用 Claude Code。
-
-
-
-
-
-
-
- {{ system.name }}
-
-
-
-
-
-
-
-
-
- 1
- 安装 Node.js 环境
-
-
Claude Code 需要 Node.js 环境才能运行。
-
-
-
-
- Windows 安装方法
-
-
-
方法一:官网下载(推荐)
-
- 打开浏览器访问 https://nodejs.org/
- 点击 "LTS" 版本进行下载(推荐长期支持版本)
- 下载完成后双击 .msi 文件
- 按照安装向导完成安装,保持默认设置即可
-
-
-
-
方法二:使用包管理器
-
如果你安装了 Chocolatey 或 Scoop,可以使用命令行安装:
-
-
# 使用 Chocolatey
-
choco install nodejs
-
# 或使用 Scoop
-
scoop install nodejs
-
-
-
-
Windows 注意事项
-
- • 建议使用 PowerShell 而不是 CMD
- • 如果遇到权限问题,尝试以管理员身份运行
- • 某些杀毒软件可能会误报,需要添加白名单
-
-
-
-
-
-
-
验证安装是否成功
-
安装完成后,打开 PowerShell 或 CMD,输入以下命令:
-
-
node --version
-
npm --version
-
-
如果显示版本号,说明安装成功了!
-
-
-
-
-
-
- 2
- 安装 Git Bash
-
-
Windows 环境下需要使用 Git Bash 安装Claude code。安装完成后,环境变量设置和使用 Claude Code 仍然在普通的 PowerShell 或 CMD 中进行。
-
-
-
-
- 下载并安装 Git for Windows
-
-
- 访问 https://git-scm.com/downloads/win
- 点击 "Download for Windows" 下载安装包
- 运行下载的 .exe 安装文件
- 在安装过程中保持默认设置,直接点击 "Next" 完成安装
-
-
-
安装完成后
-
- • 在任意文件夹右键可以看到 "Git Bash Here" 选项
- • 也可以从开始菜单启动 "Git Bash"
- • 只需要在 Git Bash 中运行 npm install 命令
- • 后续的环境变量设置和使用都在 PowerShell/CMD 中
-
-
-
-
-
-
-
验证 Git Bash 安装
-
打开 Git Bash,输入以下命令验证:
-
-
如果显示 Git 版本号,说明安装成功!
-
-
-
-
-
-
- 3
- 安装 Claude Code
-
-
-
-
-
- 安装 Claude Code
-
-
打开 Git Bash(重要:不要使用 PowerShell),运行以下命令:
-
-
# 在 Git Bash 中全局安装 Claude Code
-
npm install -g @anthropic-ai/claude-code
-
-
这个命令会从 npm 官方仓库下载并安装最新版本的 Claude Code。
-
-
-
重要提醒
-
- • 必须在 Git Bash 中运行,不要在 PowerShell 中运行
- • 如果遇到权限问题,可以尝试在 Git Bash 中使用 sudo 命令
-
-
-
-
-
-
-
验证 Claude Code 安装
-
安装完成后,输入以下命令检查是否安装成功:
-
-
如果显示版本号,恭喜你!Claude Code 已经成功安装了。
-
-
-
-
-
-
- 4
- 设置环境变量
-
-
-
-
-
- 配置 Claude Code 环境变量
-
-
为了让 Claude Code 连接到你的中转服务,需要设置两个环境变量:
-
-
-
-
方法一:PowerShell 临时设置(推荐)
-
在 PowerShell 中运行以下命令:
-
-
$env:ANTHROPIC_BASE_URL = "{{ currentBaseUrl }}"
-
$env:ANTHROPIC_AUTH_TOKEN = "你的API密钥"
-
-
💡 记得将 "你的API密钥" 替换为在上方 "API Keys" 标签页中创建的实际密钥。
-
-
-
-
方法二:系统环境变量(永久设置)
-
- 右键"此电脑" → "属性" → "高级系统设置"
- 点击"环境变量"按钮
- 在"用户变量"或"系统变量"中点击"新建"
- 添加以下两个变量:
-
-
-
- 变量名: ANTHROPIC_BASE_URL
- 变量值: {{ currentBaseUrl }}
-
-
- 变量名: ANTHROPIC_AUTH_TOKEN
- 变量值: 你的API密钥
-
-
-
-
-
-
-
-
-
验证环境变量设置
-
设置完环境变量后,可以通过以下命令验证是否设置成功:
-
-
-
-
在 PowerShell 中验证:
-
-
echo $env:ANTHROPIC_BASE_URL
-
echo $env:ANTHROPIC_AUTH_TOKEN
-
-
-
-
-
在 CMD 中验证:
-
-
echo %ANTHROPIC_BASE_URL%
-
echo %ANTHROPIC_AUTH_TOKEN%
-
-
-
-
-
-
- 预期输出示例:
-
-
-
{{ currentBaseUrl }}
-
cr_xxxxxxxxxxxxxxxxxx
-
-
- 💡 如果输出为空或显示变量名本身,说明环境变量设置失败,请重新设置。
-
-
-
-
-
-
-
-
- 5
- 开始使用 Claude Code
-
-
-
现在你可以开始使用 Claude Code 了!
-
-
-
-
-
-
在特定项目中使用
-
-
# 进入你的项目目录
-
cd C:\path\to\your\project
-
# 启动 Claude Code
-
claude
-
-
-
-
-
-
-
-
-
-
- Windows 常见问题解决
-
-
-
-
- 安装时提示 "permission denied" 错误
-
-
-
这通常是权限问题,尝试以下解决方法:
-
- 以管理员身份运行 PowerShell
- 或者配置 npm 使用用户目录:npm config set prefix %APPDATA%\npm
-
-
-
-
-
-
- PowerShell 执行策略错误
-
-
-
如果遇到执行策略限制,运行:
-
-
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
-
-
-
-
-
-
- 环境变量设置后不生效
-
-
-
设置永久环境变量后需要:
-
- 重新启动 PowerShell 或 CMD
- 或者注销并重新登录 Windows
- 验证设置:echo $env:ANTHROPIC_BASE_URL
-
-
-
-
-
-
-
-
-
-
-
-
- 1
- 安装 Node.js 环境
-
-
Claude Code 需要 Node.js 环境才能运行。
-
-
-
-
- macOS 安装方法
-
-
-
方法一:使用 Homebrew(推荐)
-
如果你已经安装了 Homebrew,使用它安装 Node.js 会更方便:
-
-
# 更新 Homebrew
-
brew update
-
# 安装 Node.js
-
brew install node
-
-
-
-
方法二:官网下载
-
- 访问 https://nodejs.org/
- 下载适合 macOS 的 LTS 版本
- 打开下载的 .pkg 文件
- 按照安装程序指引完成安装
-
-
-
-
macOS 注意事项
-
- • 如果遇到权限问题,可能需要使用 sudo
- • 首次运行可能需要在系统偏好设置中允许
- • 建议使用 Terminal 或 iTerm2
-
-
-
-
-
-
-
验证安装是否成功
-
安装完成后,打开 Terminal,输入以下命令:
-
-
node --version
-
npm --version
-
-
如果显示版本号,说明安装成功了!
-
-
-
-
-
-
- 2
- 安装 Claude Code
-
-
-
-
-
- 安装 Claude Code
-
-
打开 Terminal,运行以下命令:
-
-
# 全局安装 Claude Code
-
npm install -g @anthropic-ai/claude-code
-
-
如果遇到权限问题,可以使用 sudo:
-
-
sudo npm install -g @anthropic-ai/claude-code
-
-
-
-
-
-
验证 Claude Code 安装
-
安装完成后,输入以下命令检查是否安装成功:
-
-
如果显示版本号,恭喜你!Claude Code 已经成功安装了。
-
-
-
-
-
-
- 3
- 设置环境变量
-
-
-
-
-
- 配置 Claude Code 环境变量
-
-
为了让 Claude Code 连接到你的中转服务,需要设置两个环境变量:
-
-
-
-
方法一:临时设置(当前会话)
-
在 Terminal 中运行以下命令:
-
-
export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"
-
export ANTHROPIC_AUTH_TOKEN="你的API密钥"
-
-
💡 记得将 "你的API密钥" 替换为在上方 "API Keys" 标签页中创建的实际密钥。
-
-
-
-
方法二:永久设置
-
编辑你的 shell 配置文件(根据你使用的 shell):
-
-
# 对于 zsh (默认)
-
echo 'export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"' >> ~/.zshrc
-
echo 'export ANTHROPIC_AUTH_TOKEN="你的API密钥"' >> ~/.zshrc
-
source ~/.zshrc
-
-
-
# 对于 bash
-
echo 'export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"' >> ~/.bash_profile
-
echo 'export ANTHROPIC_AUTH_TOKEN="你的API密钥"' >> ~/.bash_profile
-
source ~/.bash_profile
-
-
-
-
-
-
-
-
-
- 4
- 开始使用 Claude Code
-
-
-
现在你可以开始使用 Claude Code 了!
-
-
-
-
-
-
在特定项目中使用
-
-
# 进入你的项目目录
-
cd /path/to/your/project
-
# 启动 Claude Code
-
claude
-
-
-
-
-
-
-
-
-
-
- macOS 常见问题解决
-
-
-
-
- 安装时提示权限错误
-
-
-
尝试以下解决方法:
-
- 使用 sudo 安装:sudo npm install -g @anthropic-ai/claude-code
- 或者配置 npm 使用用户目录:npm config set prefix ~/.npm-global
-
-
-
-
-
-
- macOS 安全设置阻止运行
-
-
-
如果系统阻止运行 Claude Code:
-
- 打开"系统偏好设置" → "安全性与隐私"
- 点击"仍要打开"或"允许"
- 或者在 Terminal 中运行:sudo spctl --master-disable
-
-
-
-
-
-
- 环境变量不生效
-
-
-
检查以下几点:
-
- 确认修改了正确的配置文件(.zshrc 或 .bash_profile)
- 重新启动 Terminal
- 验证设置:echo $ANTHROPIC_BASE_URL
-
-
-
-
-
-
-
-
-
-
-
-
- 1
- 安装 Node.js 环境
-
-
Claude Code 需要 Node.js 环境才能运行。
-
-
-
-
- Linux 安装方法
-
-
-
方法一:使用官方仓库(推荐)
-
-
# 添加 NodeSource 仓库
-
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
-
# 安装 Node.js
-
sudo apt-get install -y nodejs
-
-
-
-
方法二:使用系统包管理器
-
虽然版本可能不是最新的,但对于基本使用已经足够:
-
-
# Ubuntu/Debian
-
sudo apt update
-
sudo apt install nodejs npm
-
# CentOS/RHEL/Fedora
-
sudo dnf install nodejs npm
-
-
-
-
Linux 注意事项
-
- • 某些发行版可能需要安装额外的依赖
- • 如果遇到权限问题,使用 sudo
- • 确保你的用户在 npm 的全局目录有写权限
-
-
-
-
-
-
-
验证安装是否成功
-
安装完成后,打开终端,输入以下命令:
-
-
node --version
-
npm --version
-
-
如果显示版本号,说明安装成功了!
-
-
-
-
-
-
- 2
- 安装 Claude Code
-
-
-
-
-
- 安装 Claude Code
-
-
打开终端,运行以下命令:
-
-
# 全局安装 Claude Code
-
npm install -g @anthropic-ai/claude-code
-
-
如果遇到权限问题,可以使用 sudo:
-
-
sudo npm install -g @anthropic-ai/claude-code
-
-
-
-
-
-
验证 Claude Code 安装
-
安装完成后,输入以下命令检查是否安装成功:
-
-
如果显示版本号,恭喜你!Claude Code 已经成功安装了。
-
-
-
-
-
-
- 3
- 设置环境变量
-
-
-
-
-
- 配置 Claude Code 环境变量
-
-
为了让 Claude Code 连接到你的中转服务,需要设置两个环境变量:
-
-
-
-
方法一:临时设置(当前会话)
-
在终端中运行以下命令:
-
-
export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"
-
export ANTHROPIC_AUTH_TOKEN="你的API密钥"
-
-
💡 记得将 "你的API密钥" 替换为在上方 "API Keys" 标签页中创建的实际密钥。
-
-
-
-
方法二:永久设置
-
编辑你的 shell 配置文件:
-
-
# 对于 bash (默认)
-
echo 'export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"' >> ~/.bashrc
-
echo 'export ANTHROPIC_AUTH_TOKEN="你的API密钥"' >> ~/.bashrc
-
source ~/.bashrc
-
-
-
# 对于 zsh
-
echo 'export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"' >> ~/.zshrc
-
echo 'export ANTHROPIC_AUTH_TOKEN="你的API密钥"' >> ~/.zshrc
-
source ~/.zshrc
-
-
-
-
-
-
-
-
-
- 4
- 开始使用 Claude Code
-
-
-
现在你可以开始使用 Claude Code 了!
-
-
-
-
-
-
在特定项目中使用
-
-
# 进入你的项目目录
-
cd /path/to/your/project
-
# 启动 Claude Code
-
claude
-
-
-
-
-
-
-
-
-
-
- Linux 常见问题解决
-
-
-
-
- 安装时提示权限错误
-
-
-
尝试以下解决方法:
-
- 使用 sudo 安装:sudo npm install -g @anthropic-ai/claude-code
- 或者配置 npm 使用用户目录:npm config set prefix ~/.npm-global
- 然后添加到 PATH:export PATH=~/.npm-global/bin:$PATH
-
-
-
-
-
-
- 缺少依赖库
-
-
-
某些 Linux 发行版需要安装额外依赖:
-
-
# Ubuntu/Debian
-
sudo apt install build-essential
-
# CentOS/RHEL
-
sudo dnf groupinstall "Development Tools"
-
-
-
-
-
-
- 环境变量不生效
-
-
-
检查以下几点:
-
- 确认修改了正确的配置文件(.bashrc 或 .zshrc)
- 重新启动终端或运行 source ~/.bashrc
- 验证设置:echo $ANTHROPIC_BASE_URL
-
-
-
-
-
-
-
-
-
-
🎉 恭喜你!
-
你已经成功安装并配置了 Claude Code,现在可以开始享受 AI 编程助手带来的便利了。
-
如果在使用过程中遇到任何问题,可以查看官方文档或社区讨论获取帮助。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 将显示在浏览器标题和页面头部
-
-
-
-
-
-
-
-
-
-
-
-
-
-
当前图标
-
- 删除
-
-
-
-
-
-
-
- 上传图标
-
- 支持 .ico, .png, .jpg, .svg 格式,最大 350KB
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ oemSettingsSaving ? '保存中...' : '保存设置' }}
-
-
-
-
- 重置为默认
-
-
-
-
-
- 最后更新:{{ formatDateTime(oemSettings.updatedAt) }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 名称
-
-
-
-
-
-
-
-
-
-
-
速率限制设置 (可选)
-
控制 API Key 的使用频率和资源消耗
-
-
-
-
-
时间窗口 (分钟)
-
-
设置一个时间段(以分钟为单位),用于计算速率限制
-
-
-
-
时间窗口内的请求次数限制
-
-
在时间窗口内允许的最大请求次数(需要先设置时间窗口)
-
-
-
-
时间窗口内的 Token 使用量限制
-
-
在时间窗口内允许消耗的最大 Token 数量(需要先设置时间窗口)
-
-
-
-
-
💡 使用示例
-
-
示例1: 时间窗口=60,请求次数限制=100
-
→ 每60分钟内最多允许100次请求
-
示例2: 时间窗口=10,Token限制=50000
-
→ 每10分钟内最多消耗50,000个Token
-
示例3: 时间窗口=30,请求次数限制=50,Token限制=100000
-
→ 每30分钟内最多50次请求且总Token不超过100,000
-
-
-
-
-
-
每日费用限制 (美元)
-
-
- $50
- $100
- $200
- 自定义
-
-
-
设置此 API Key 每日的费用限制,超过限制将拒绝请求,0 或留空表示无限制
-
-
-
-
-
并发限制 (可选)
-
-
设置此 API Key 可同时处理的最大请求数,0 或留空表示无限制
-
-
-
- 备注 (可选)
-
-
-
-
-
有效期限
-
- 永不过期
- 1 天
- 7 天
- 30 天
- 90 天
- 180 天
- 365 天
- 自定义日期
-
-
-
-
-
- 将于 {{ formatExpireDate(apiKeyForm.expiresAt) }} 过期
-
-
-
-
-
-
-
专属账号绑定 (可选)
-
-
- Claude 专属账号
-
- 使用共享账号池
-
- {{ account.name }} ({{ account.status === 'active' ? '正常' : '异常' }})
-
-
-
-
- Gemini 专属账号
-
- 使用共享账号池
-
- {{ account.name }} ({{ account.status === 'active' ? '正常' : '异常' }})
-
-
-
-
-
选择专属账号后,此API Key将只使用该账号,不选择则使用共享账号池
-
-
-
-
-
-
- 启用模型限制
-
-
-
-
-
-
限制的模型列表
-
-
- {{ model }}
-
-
-
-
-
- 暂无限制的模型
-
-
-
-
-
-
-
-
-
设置此API Key无法访问的模型,例如:claude-opus-4-20250514
-
-
-
-
-
-
-
-
-
- 启用客户端限制
-
-
-
-
-
-
允许的客户端
-
勾选允许使用此API Key的客户端
-
-
-
-
- {{ client.name }}
- {{ client.description }}
-
-
-
-
-
-
-
-
-
- 取消
-
-
-
-
- {{ createApiKeyLoading ? '创建中...' : '创建' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
速率限制设置
-
控制 API Key 的使用频率和资源消耗
-
-
-
-
-
时间窗口 (分钟)
-
-
设置一个时间段(以分钟为单位),用于计算速率限制
-
-
-
-
时间窗口内的请求次数限制
-
-
在时间窗口内允许的最大请求次数(需要先设置时间窗口)
-
-
-
-
时间窗口内的 Token 使用量限制
-
-
在时间窗口内允许消耗的最大 Token 数量(需要先设置时间窗口),0 或留空表示无限制
-
-
-
-
-
💡 使用示例
-
-
示例1: 时间窗口=60,请求次数限制=100
-
→ 每60分钟内最多允许100次请求
-
示例2: 时间窗口=10,Token限制=50000
-
→ 每10分钟内最多消耗50,000个Token
-
示例3: 时间窗口=30,请求次数限制=50,Token限制=100000
-
→ 每30分钟内最多50次请求且总Token不超过100,000
-
-
-
-
-
-
每日费用限制 (美元)
-
-
- $50
- $100
- $200
- 自定义
-
-
-
设置此 API Key 每日的费用限制,超过限制将拒绝请求,0 或留空表示无限制
-
-
-
-
-
并发限制
-
-
设置此 API Key 可同时处理的最大请求数,0 或留空表示无限制
-
-
-
-
-
-
专属账号绑定
-
-
- Claude 专属账号
-
- 使用共享账号池
-
- {{ account.name }} ({{ account.status === 'active' ? '正常' : '异常' }})
-
-
-
-
- Gemini 专属账号
-
- 使用共享账号池
-
- {{ account.name }} ({{ account.status === 'active' ? '正常' : '异常' }})
-
-
-
-
-
修改绑定账号将影响此API Key的请求路由
-
-
-
-
-
-
- 启用模型限制
-
-
-
-
-
-
限制的模型列表
-
-
- {{ model }}
-
-
-
-
-
- 暂无限制的模型
-
-
-
-
-
-
-
-
-
设置此API Key无法访问的模型,例如:claude-opus-4-20250514
-
-
-
-
-
-
-
-
-
- 启用客户端限制
-
-
-
-
-
-
允许的客户端
-
勾选允许使用此API Key的客户端
-
-
-
-
- {{ client.name }}
- {{ client.description }}
-
-
-
-
-
-
-
-
-
- 取消
-
-
-
-
- {{ editApiKeyLoading ? '保存中...' : '保存修改' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
API Key 信息
-
{{ renewApiKeyForm.name }}
-
- 当前过期时间:{{ renewApiKeyForm.currentExpiresAt ? formatExpireDate(renewApiKeyForm.currentExpiresAt) : '永不过期' }}
-
-
-
-
-
-
-
续期时长
-
- 延长 7 天
- 延长 30 天
- 延长 90 天
- 延长 180 天
- 延长 365 天
- 自定义日期
- 设为永不过期
-
-
-
-
-
- 新的过期时间:{{ formatExpireDate(renewApiKeyForm.newExpiresAt) }}
-
-
-
-
-
-
- 取消
-
-
-
-
- {{ renewApiKeyLoading ? '续期中...' : '确认续期' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
API Key 创建成功
-
请妥善保存您的 API Key
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
重要提醒
-
- 这是您唯一能看到完整 API Key 的机会。关闭此窗口后,系统将不再显示完整的 API Key。请立即复制并妥善保存。
-
-
-
-
-
-
-
-
-
API Key 名称
-
- {{ newApiKey.name }}
-
-
-
-
-
备注
-
- {{ newApiKey.description }}
-
-
-
-
-
API Key
-
-
- {{ getDisplayedApiKey() }}
-
-
-
-
-
-
-
-
-
- 点击眼睛图标切换显示模式,使用下方按钮复制完整 API Key
-
-
-
-
-
-
-
-
- 复制 API Key
-
-
- 我已保存
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 账户名称
-
-
-
-
- 描述 (可选)
-
-
-
-
-
账户类型
-
-
-
- 共享账户
-
-
-
- 专属账户
-
-
-
- 共享账户:供所有API Key使用;专属账户:仅供特定API Key使用
-
-
-
-
-
-
项目编号 (可选)
-
-
-
-
-
-
Google Cloud/Workspace 账号需要提供项目编号
-
某些 Google 账号(特别是绑定了 Google Cloud 的账号)会被识别为 Workspace 账号,需要提供额外的项目编号。
-
-
如何获取项目编号:
-
- 访问 Google Cloud Console
- 复制项目编号(Project Number) ,通常是12位纯数字
- ⚠️ 注意:不要复制项目ID(Project ID),要复制项目编号!
-
-
-
提示: 如果您的账号是普通个人账号(未绑定 Google Cloud),请留空此字段。
-
-
-
-
-
-
-
-
-
-
-
-
-
手动输入 Token
-
- 请输入有效的 Claude Access Token。如果您有 Refresh Token,建议也一并填写以支持自动刷新。
-
-
- 请输入有效的 Gemini Access Token。如果您有 Refresh Token,建议也一并填写以支持自动刷新。
-
-
-
-
- 获取 Access Token 的方法:
-
-
- 请从已登录 Claude Code 的机器上获取 ~/.claude/.credentials.json 文件中的凭证,
- 请勿使用 Claude 官网 API Keys 页面的密钥。
-
-
- 请从已登录 Gemini CLI 的机器上获取 ~/.config/gemini/credentials.json 文件中的凭证。
-
-
-
💡 如果未填写 Refresh Token,Token 过期后需要手动更新。
-
-
-
-
- Access Token *
-
-
-
-
-
Refresh Token (可选)
-
-
如果有 Refresh Token,填写后系统可以自动刷新过期的 Access Token
-
-
-
-
-
代理设置 (可选)
-
- 如果需要使用代理访问Claude服务,请配置代理信息。OAuth授权也将通过此代理进行。
- 如果需要使用代理访问Gemini服务,请配置代理信息。OAuth授权也将通过此代理进行。
-
-
- 不使用代理
- SOCKS5 代理
- HTTP 代理
-
-
-
-
-
-
-
-
- 取消
-
-
- 下一步
-
-
-
-
- {{ createAccountLoading ? '创建中...' : '创建账户' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
获取授权链接
-
点击下方按钮生成OAuth授权链接
-
-
-
- {{ authUrlLoading ? '生成中...' : '生成授权链接' }}
-
-
-
-
-
-
-
-
-
-
-
-
操作说明
-
- 点击下方的授权链接,在新页面中完成Claude Code登录
- 点击"授权"按钮同意应用权限
- 页面会显示一个 Authorization Code
- 复制这个 Authorization Code 并粘贴到下方输入框
- 💡 提示: 请直接粘贴显示的Authorization Code
-
-
-
-
-
-
-
-
-
-
- Authorization Code
-
-
-
-
- 请粘贴从Claude页面复制的Authorization Code
-
-
-
-
-
-
-
-
-
-
-
-
-
-
获取授权链接
-
点击下方按钮生成Gemini OAuth授权链接
-
-
-
- {{ authUrlLoading ? '生成中...' : '生成授权链接' }}
-
-
-
-
-
-
-
-
-
-
-
-
操作说明
-
- 点击下方的授权链接,在新页面中完成Google账号登录
- 点击"登录"按钮后可能会加载很慢(这是正常的)
- 如果超过1分钟还在加载,请按 F5 刷新页面
- 授权完成后会跳转到 http://localhost:45462 (可能显示无法访问)
- 复制浏览器地址栏的完整链接并粘贴到下方输入框
-
-
-
- 提示: 如果页面一直无法跳转,可以打开浏览器开发者工具(F12),F5刷新一下授权页再点击页面的登录按钮,在"网络"标签中找到以 localhost:45462 开头的请求,复制其完整URL。
-
-
-
-
-
-
-
-
-
-
-
- 复制oauth后的链接
-
-
-
-
-
- 支持粘贴完整链接,系统会自动提取授权码
-
-
-
- 也可以直接粘贴授权码(code参数的值)
-
-
-
-
-
-
-
-
-
- 上一步
-
-
-
-
-
- {{ createAccountLoading ? '创建中...' : '完成创建' }}
-
-
-
-
-
- {{ createAccountLoading ? '创建中...' : '使用授权码创建账户' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
- 账户名称
-
-
-
-
- 描述 (可选)
-
-
-
-
-
账户类型
-
-
-
- 共享账户
-
-
-
- 专属账户
-
-
-
-
-
-
-
切换到共享账户需要验证
-
当前账户绑定了 {{ getBoundApiKeysCount(editAccountForm.id) }} 个API Key,需要先解绑所有API Key才能切换到共享账户。
-
-
-
-
- 共享账户:供所有API Key使用;专属账户:仅供特定API Key使用
-
-
-
-
-
-
-
项目编号 (可选)
-
-
-
-
-
-
Google Cloud/Workspace 账号需要提供项目编号
-
如果您的账号被识别为 Workspace 账号,请提供项目编号。留空将尝试自动检测。
-
-
如何获取项目编号:
-
- 访问 Google Cloud Console
- 复制项目编号(Project Number) ,通常是12位纯数字
- ⚠️ 注意:不要复制项目ID(Project ID),要复制项目编号!
-
-
-
提示: 如果您的账号是普通个人账号(未绑定 Google Cloud),请留空此字段。
-
-
-
-
-
-
-
-
-
-
-
-
更新 Token
-
可以更新 Access Token 和 Refresh Token。为了安全起见,不会显示当前的 Token 值。
-
💡 留空表示不更新该字段。
-
-
-
-
-
- Access Token
-
-
-
-
-
Refresh Token
-
-
如果有 Refresh Token,填写后系统可以自动刷新过期的 Access Token
-
-
-
-
-
-
-
代理设置 (可选)
-
如果需要修改代理设置,请更新代理信息。
-
- 不使用代理
- SOCKS5 代理
- HTTP 代理
-
-
-
-
-
-
-
-
- 取消
-
-
-
-
- {{ editAccountLoading ? '更新中...' : '保存修改' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
当前用户名
-
-
当前用户名,输入新用户名以修改
-
-
-
-
-
- 当前密码
-
-
-
-
-
-
- 确认新密码
-
-
-
-
-
- 取消
-
-
-
-
- {{ changePasswordLoading ? '保存中...' : '保存修改' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ confirmModal.title }}
-
{{ confirmModal.message }}
-
-
-
-
-
- {{ confirmModal.cancelText || '取消' }}
-
-
- {{ confirmModal.confirmText || '继续' }}
-
-
-
-
-
-
-
-
-
-
-
-
-
{{ toast.title }}
-
{{ toast.message }}
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/web/apiStats/app.js b/web/apiStats/app.js
deleted file mode 100644
index d4761f23..00000000
--- a/web/apiStats/app.js
+++ /dev/null
@@ -1,689 +0,0 @@
-// 初始化 dayjs 插件
-dayjs.extend(dayjs_plugin_relativeTime);
-dayjs.extend(dayjs_plugin_timezone);
-dayjs.extend(dayjs_plugin_utc);
-
-const { createApp } = Vue;
-
-const app = createApp({
- data() {
- return {
- // 用户输入
- apiKey: '',
- apiId: null, // 存储 API Key 对应的 ID
-
- // 状态控制
- loading: false,
- modelStatsLoading: false,
- error: '',
- showAdminButton: true, // 控制管理后端按钮显示
-
- // 时间范围控制
- statsPeriod: 'daily', // 默认今日
-
- // 数据
- statsData: null,
- modelStats: [],
-
- // 分时间段的统计数据
- dailyStats: null,
- monthlyStats: null,
-
- // OEM设置
- oemSettings: {
- siteName: 'Claude Relay Service',
- siteIcon: '',
- siteIconData: ''
- }
- };
- },
-
- methods: {
- // 🔍 查询统计数据
- async queryStats() {
- if (!this.apiKey.trim()) {
- this.error = '请输入 API Key';
- return;
- }
-
- this.loading = true;
- this.error = '';
- this.statsData = null;
- this.modelStats = [];
- this.apiId = null;
-
- try {
- // 首先获取 API Key 对应的 ID
- const idResponse = await fetch('/apiStats/api/get-key-id', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- apiKey: this.apiKey
- })
- });
-
- const idResult = await idResponse.json();
-
- if (!idResponse.ok) {
- throw new Error(idResult.message || '获取 API Key ID 失败');
- }
-
- if (idResult.success) {
- this.apiId = idResult.data.id;
-
- // 使用 apiId 查询统计数据
- const response = await fetch('/apiStats/api/user-stats', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- apiId: this.apiId
- })
- });
-
- const result = await response.json();
-
- if (!response.ok) {
- throw new Error(result.message || '查询失败');
- }
-
- if (result.success) {
- this.statsData = result.data;
-
- // 同时加载今日和本月的统计数据
- await this.loadAllPeriodStats();
-
- // 清除错误信息
- this.error = '';
-
- // 更新 URL
- this.updateURL();
- } else {
- throw new Error(result.message || '查询失败');
- }
- } else {
- throw new Error(idResult.message || '获取 API Key ID 失败');
- }
-
- } catch (error) {
- console.error('Query stats error:', error);
- this.error = error.message || '查询统计数据失败,请检查您的 API Key 是否正确';
- this.statsData = null;
- this.modelStats = [];
- this.apiId = null;
- } finally {
- this.loading = false;
- }
- },
-
- // 📊 加载所有时间段的统计数据
- async loadAllPeriodStats() {
- if (!this.apiId) {
- return;
- }
-
- // 并行加载今日和本月的数据
- await Promise.all([
- this.loadPeriodStats('daily'),
- this.loadPeriodStats('monthly')
- ]);
-
- // 加载当前选择时间段的模型统计
- await this.loadModelStats(this.statsPeriod);
- },
-
- // 📊 加载指定时间段的统计数据
- async loadPeriodStats(period) {
- try {
- const response = await fetch('/apiStats/api/user-model-stats', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- apiId: this.apiId,
- period: period
- })
- });
-
- const result = await response.json();
-
- if (response.ok && result.success) {
- // 计算汇总数据
- const modelData = result.data || [];
- const summary = {
- requests: 0,
- inputTokens: 0,
- outputTokens: 0,
- cacheCreateTokens: 0,
- cacheReadTokens: 0,
- allTokens: 0,
- cost: 0,
- formattedCost: '$0.000000'
- };
-
- modelData.forEach(model => {
- summary.requests += model.requests || 0;
- summary.inputTokens += model.inputTokens || 0;
- summary.outputTokens += model.outputTokens || 0;
- summary.cacheCreateTokens += model.cacheCreateTokens || 0;
- summary.cacheReadTokens += model.cacheReadTokens || 0;
- summary.allTokens += model.allTokens || 0;
- summary.cost += model.costs?.total || 0;
- });
-
- summary.formattedCost = this.formatCost(summary.cost);
-
- // 存储到对应的时间段数据
- if (period === 'daily') {
- this.dailyStats = summary;
- } else {
- this.monthlyStats = summary;
- }
- } else {
- console.warn(`Failed to load ${period} stats:`, result.message);
- }
-
- } catch (error) {
- console.error(`Load ${period} stats error:`, error);
- }
- },
-
- // 📊 加载模型统计数据
- async loadModelStats(period = 'daily') {
- if (!this.apiId) {
- return;
- }
-
- this.modelStatsLoading = true;
-
- try {
- const response = await fetch('/apiStats/api/user-model-stats', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- apiId: this.apiId,
- period: period
- })
- });
-
- const result = await response.json();
-
- if (!response.ok) {
- throw new Error(result.message || '加载模型统计失败');
- }
-
- if (result.success) {
- this.modelStats = result.data || [];
- } else {
- throw new Error(result.message || '加载模型统计失败');
- }
-
- } catch (error) {
- console.error('Load model stats error:', error);
- this.modelStats = [];
- // 不显示错误,因为模型统计是可选的
- } finally {
- this.modelStatsLoading = false;
- }
- },
-
- // 🔄 切换时间范围
- async switchPeriod(period) {
- if (this.statsPeriod === period || this.modelStatsLoading) {
- return;
- }
-
- this.statsPeriod = period;
-
- // 如果对应时间段的数据还没有加载,则加载它
- if ((period === 'daily' && !this.dailyStats) ||
- (period === 'monthly' && !this.monthlyStats)) {
- await this.loadPeriodStats(period);
- }
-
- // 加载对应的模型统计
- await this.loadModelStats(period);
- },
-
- // 📅 格式化日期
- formatDate(dateString) {
- if (!dateString) return '无';
-
- try {
- // 使用 dayjs 格式化日期
- const date = dayjs(dateString);
- return date.format('YYYY年MM月DD日 HH:mm');
- } catch (error) {
- return '格式错误';
- }
- },
-
- // 📅 格式化过期日期
- formatExpireDate(dateString) {
- if (!dateString) return '';
- const date = new Date(dateString);
- return date.toLocaleString('zh-CN', {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit'
- });
- },
-
- // 🔍 检查 API Key 是否已过期
- isApiKeyExpired(expiresAt) {
- if (!expiresAt) return false;
- return new Date(expiresAt) < new Date();
- },
-
- // ⏰ 检查 API Key 是否即将过期(7天内)
- isApiKeyExpiringSoon(expiresAt) {
- if (!expiresAt) return false;
- const expireDate = new Date(expiresAt);
- const now = new Date();
- const daysUntilExpire = (expireDate - now) / (1000 * 60 * 60 * 24);
- return daysUntilExpire > 0 && daysUntilExpire <= 7;
- },
-
- // 🔢 格式化数字
- formatNumber(num) {
- if (typeof num !== 'number') {
- num = parseInt(num) || 0;
- }
-
- if (num === 0) return '0';
-
- // 大数字使用简化格式
- if (num >= 1000000) {
- return (num / 1000000).toFixed(1) + 'M';
- } else if (num >= 1000) {
- return (num / 1000).toFixed(1) + 'K';
- } else {
- return num.toLocaleString();
- }
- },
-
- // 💰 格式化费用
- formatCost(cost) {
- if (typeof cost !== 'number' || cost === 0) {
- return '$0.000000';
- }
-
- // 根据数值大小选择精度
- if (cost >= 1) {
- return '$' + cost.toFixed(2);
- } else if (cost >= 0.01) {
- return '$' + cost.toFixed(4);
- } else {
- return '$' + cost.toFixed(6);
- }
- },
-
- // 🔐 格式化权限
- formatPermissions(permissions) {
- const permissionMap = {
- 'claude': 'Claude',
- 'gemini': 'Gemini',
- 'all': '全部模型'
- };
-
- return permissionMap[permissions] || permissions || '未知';
- },
-
- // 💾 处理错误
- handleError(error, defaultMessage = '操作失败') {
- console.error('Error:', error);
-
- let errorMessage = defaultMessage;
-
- if (error.response) {
- // HTTP 错误响应
- if (error.response.data && error.response.data.message) {
- errorMessage = error.response.data.message;
- } else if (error.response.status === 401) {
- errorMessage = 'API Key 无效或已过期';
- } else if (error.response.status === 403) {
- errorMessage = '没有权限访问该数据';
- } else if (error.response.status === 429) {
- errorMessage = '请求过于频繁,请稍后再试';
- } else if (error.response.status >= 500) {
- errorMessage = '服务器内部错误,请稍后再试';
- }
- } else if (error.message) {
- errorMessage = error.message;
- }
-
- this.error = errorMessage;
- },
-
- // 📋 复制到剪贴板
- async copyToClipboard(text) {
- try {
- await navigator.clipboard.writeText(text);
- this.showToast('已复制到剪贴板', 'success');
- } catch (error) {
- console.error('Copy failed:', error);
- this.showToast('复制失败', 'error');
- }
- },
-
- // 🍞 显示 Toast 通知
- showToast(message, type = 'info') {
- // 简单的 toast 实现
- const toast = document.createElement('div');
- toast.className = `fixed top-4 right-4 z-50 px-6 py-3 rounded-lg shadow-lg text-white transform transition-all duration-300 ${
- type === 'success' ? 'bg-green-500' :
- type === 'error' ? 'bg-red-500' :
- 'bg-blue-500'
- }`;
- toast.textContent = message;
-
- document.body.appendChild(toast);
-
- // 显示动画
- setTimeout(() => {
- toast.style.transform = 'translateX(0)';
- toast.style.opacity = '1';
- }, 100);
-
- // 自动隐藏
- setTimeout(() => {
- toast.style.transform = 'translateX(100%)';
- toast.style.opacity = '0';
- setTimeout(() => {
- document.body.removeChild(toast);
- }, 300);
- }, 3000);
- },
-
- // 🧹 清除数据
- clearData() {
- this.statsData = null;
- this.modelStats = [];
- this.dailyStats = null;
- this.monthlyStats = null;
- this.error = '';
- this.statsPeriod = 'daily'; // 重置为默认值
- this.apiId = null;
- },
-
- // 加载OEM设置
- async loadOemSettings() {
- try {
- const response = await fetch('/admin/oem-settings', {
- headers: {
- 'Content-Type': 'application/json'
- }
- });
-
- if (response.ok) {
- const result = await response.json();
- if (result && result.success && result.data) {
- this.oemSettings = { ...this.oemSettings, ...result.data };
-
- // 应用设置到页面
- this.applyOemSettings();
- }
- }
- } catch (error) {
- console.error('Error loading OEM settings:', error);
- // 静默失败,使用默认值
- }
- },
-
- // 应用OEM设置
- applyOemSettings() {
- // 更新网站标题
- document.title = `API Key 统计 - ${this.oemSettings.siteName}`;
-
- // 应用网站图标
- const iconData = this.oemSettings.siteIconData || this.oemSettings.siteIcon;
- if (iconData && iconData.trim()) {
- // 移除现有的favicon
- const existingFavicons = document.querySelectorAll('link[rel*="icon"]');
- existingFavicons.forEach(link => link.remove());
-
- // 添加新的favicon
- const link = document.createElement('link');
- link.rel = 'icon';
-
- // 根据数据类型设置适当的type
- if (iconData.startsWith('data:')) {
- // Base64数据
- link.href = iconData;
- } else {
- // URL
- link.type = 'image/x-icon';
- link.href = iconData;
- }
-
- document.head.appendChild(link);
- }
- },
-
- // 🔄 刷新数据
- async refreshData() {
- if (this.statsData && this.apiKey) {
- await this.queryStats();
- }
- },
-
- // 📊 刷新当前时间段数据
- async refreshCurrentPeriod() {
- if (this.apiId) {
- await this.loadPeriodStats(this.statsPeriod);
- await this.loadModelStats(this.statsPeriod);
- }
- },
-
- // 🔄 更新 URL
- updateURL() {
- if (this.apiId) {
- const url = new URL(window.location);
- url.searchParams.set('apiId', this.apiId);
- window.history.pushState({}, '', url);
- }
- },
-
- // 📊 使用 apiId 直接加载数据
- async loadStatsWithApiId() {
- if (!this.apiId) {
- return;
- }
-
- this.loading = true;
- this.error = '';
- this.statsData = null;
- this.modelStats = [];
-
- try {
- // 使用 apiId 查询统计数据
- const response = await fetch('/apiStats/api/user-stats', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- apiId: this.apiId
- })
- });
-
- const result = await response.json();
-
- if (!response.ok) {
- throw new Error(result.message || '查询失败');
- }
-
- if (result.success) {
- this.statsData = result.data;
-
- // 同时加载今日和本月的统计数据
- await this.loadAllPeriodStats();
-
- // 清除错误信息
- this.error = '';
- } else {
- throw new Error(result.message || '查询失败');
- }
-
- } catch (error) {
- console.error('Load stats with apiId error:', error);
- this.error = error.message || '查询统计数据失败';
- this.statsData = null;
- this.modelStats = [];
- } finally {
- this.loading = false;
- }
- }
- },
-
- computed: {
- // 📊 当前时间段的数据
- currentPeriodData() {
- if (this.statsPeriod === 'daily') {
- return this.dailyStats || {
- requests: 0,
- inputTokens: 0,
- outputTokens: 0,
- cacheCreateTokens: 0,
- cacheReadTokens: 0,
- allTokens: 0,
- cost: 0,
- formattedCost: '$0.000000'
- };
- } else {
- return this.monthlyStats || {
- requests: 0,
- inputTokens: 0,
- outputTokens: 0,
- cacheCreateTokens: 0,
- cacheReadTokens: 0,
- allTokens: 0,
- cost: 0,
- formattedCost: '$0.000000'
- };
- }
- },
-
- // 📊 使用率计算(基于当前时间段)
- usagePercentages() {
- if (!this.statsData || !this.currentPeriodData) {
- return {
- tokenUsage: 0,
- costUsage: 0,
- requestUsage: 0
- };
- }
-
- const current = this.currentPeriodData;
- const limits = this.statsData.limits;
-
- return {
- tokenUsage: limits.tokenLimit > 0 ? Math.min((current.allTokens / limits.tokenLimit) * 100, 100) : 0,
- costUsage: limits.dailyCostLimit > 0 ? Math.min((current.cost / limits.dailyCostLimit) * 100, 100) : 0,
- requestUsage: limits.rateLimitRequests > 0 ? Math.min((current.requests / limits.rateLimitRequests) * 100, 100) : 0
- };
- },
-
- // 📈 统计摘要(基于当前时间段)
- statsSummary() {
- if (!this.statsData || !this.currentPeriodData) return null;
-
- const current = this.currentPeriodData;
-
- return {
- totalRequests: current.requests || 0,
- totalTokens: current.allTokens || 0,
- totalCost: current.cost || 0,
- formattedCost: current.formattedCost || '$0.000000',
- inputTokens: current.inputTokens || 0,
- outputTokens: current.outputTokens || 0,
- cacheCreateTokens: current.cacheCreateTokens || 0,
- cacheReadTokens: current.cacheReadTokens || 0
- };
- }
- },
-
- watch: {
- // 监听 API Key 变化
- apiKey(newValue) {
- if (!newValue) {
- this.clearData();
- }
- // 清除之前的错误
- if (this.error) {
- this.error = '';
- }
- }
- },
-
- mounted() {
- // 页面加载完成后的初始化
- console.log('User Stats Page loaded');
-
- // 加载OEM设置
- this.loadOemSettings();
-
- // 检查 URL 参数是否有预填的 API Key(用于开发测试)
- const urlParams = new URLSearchParams(window.location.search);
- const presetApiId = urlParams.get('apiId');
- const presetApiKey = urlParams.get('apiKey');
-
- if (presetApiId && presetApiId.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i)) {
- // 如果 URL 中有 apiId,直接使用 apiId 加载数据
- this.apiId = presetApiId;
- this.showAdminButton = false; // 隐藏管理后端按钮
- this.loadStatsWithApiId();
- } else if (presetApiKey && presetApiKey.length > 10) {
- // 向后兼容,支持 apiKey 参数
- this.apiKey = presetApiKey;
- }
-
- // 添加键盘快捷键
- document.addEventListener('keydown', (event) => {
- // Ctrl/Cmd + Enter 查询
- if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
- if (!this.loading && this.apiKey.trim()) {
- this.queryStats();
- }
- event.preventDefault();
- }
-
- // ESC 清除数据
- if (event.key === 'Escape') {
- this.clearData();
- this.apiKey = '';
- }
- });
-
- // 定期清理无效的 toast 元素
- setInterval(() => {
- const toasts = document.querySelectorAll('[class*="fixed top-4 right-4"]');
- toasts.forEach(toast => {
- if (toast.style.opacity === '0') {
- try {
- document.body.removeChild(toast);
- } catch (e) {
- // 忽略已经被移除的元素
- }
- }
- });
- }, 5000);
- },
-
- // 组件销毁前清理
- beforeUnmount() {
- // 清理事件监听器
- document.removeEventListener('keydown', this.handleKeyDown);
- }
-});
-
-// 挂载应用
-app.mount('#app');
\ No newline at end of file
diff --git a/web/apiStats/index.html b/web/apiStats/index.html
deleted file mode 100644
index 0030289a..00000000
--- a/web/apiStats/index.html
+++ /dev/null
@@ -1,497 +0,0 @@
-
-
-
-
-
-
API Key 统计
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
e.target.style.display = 'none'">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 统计时间范围
-
-
-
-
- 今日
-
-
-
- 本月
-
-
-
-
-
-
-
-
-
-
- API Key 信息
-
-
-
- 名称
- {{ statsData.name }}
-
-
- 状态
-
-
- {{ statsData.isActive ? '活跃' : '已停用' }}
-
-
-
- 权限
- {{ formatPermissions(statsData.permissions) }}
-
-
- 创建时间
- {{ formatDate(statsData.createdAt) }}
-
-
-
过期时间
-
-
-
- 已过期
-
-
-
- {{ formatExpireDate(statsData.expiresAt) }}
-
-
- {{ formatExpireDate(statsData.expiresAt) }}
-
-
-
-
- 永不过期
-
-
-
-
-
-
-
-
-
- 使用统计概览 ({{ statsPeriod === 'daily' ? '今日' : '本月' }})
-
-
-
-
{{ formatNumber(currentPeriodData.requests) }}
-
{{ statsPeriod === 'daily' ? '今日' : '本月' }}请求数
-
-
-
{{ formatNumber(currentPeriodData.allTokens) }}
-
{{ statsPeriod === 'daily' ? '今日' : '本月' }}Token数
-
-
-
{{ currentPeriodData.formattedCost || '$0.000000' }}
-
{{ statsPeriod === 'daily' ? '今日' : '本月' }}费用
-
-
-
{{ formatNumber(currentPeriodData.inputTokens) }}
-
{{ statsPeriod === 'daily' ? '今日' : '本月' }}输入Token
-
-
-
-
-
-
-
-
-
-
-
- Token 使用分布 ({{ statsPeriod === 'daily' ? '今日' : '本月' }})
-
-
-
-
-
- 输入 Token
-
- {{ formatNumber(currentPeriodData.inputTokens) }}
-
-
-
-
- 输出 Token
-
- {{ formatNumber(currentPeriodData.outputTokens) }}
-
-
-
-
- 缓存创建 Token
-
- {{ formatNumber(currentPeriodData.cacheCreateTokens) }}
-
-
-
-
- 缓存读取 Token
-
- {{ formatNumber(currentPeriodData.cacheReadTokens) }}
-
-
-
-
- {{ statsPeriod === 'daily' ? '今日' : '本月' }}总计
- {{ formatNumber(currentPeriodData.allTokens) }}
-
-
-
-
-
-
-
-
- 限制配置
-
-
-
- Token 限制
- {{ statsData.limits.tokenLimit > 0 ? formatNumber(statsData.limits.tokenLimit) : '无限制' }}
-
-
- 并发限制
- {{ statsData.limits.concurrencyLimit > 0 ? statsData.limits.concurrencyLimit : '无限制' }}
-
-
- 速率限制
-
- {{ statsData.limits.rateLimitRequests > 0 && statsData.limits.rateLimitWindow > 0
- ? `${statsData.limits.rateLimitRequests}次/${statsData.limits.rateLimitWindow}分钟`
- : '无限制' }}
-
-
-
- 每日费用限制
- {{ statsData.limits.dailyCostLimit > 0 ? '$' + statsData.limits.dailyCostLimit : '无限制' }}
-
-
- 模型限制
-
-
-
- 限制 {{ statsData.restrictions.restrictedModels.length }} 个模型
-
-
-
- 允许所有模型
-
-
-
-
- 客户端限制
-
-
-
- 限制 {{ statsData.restrictions.allowedClients.length }} 个客户端
-
-
-
- 允许所有客户端
-
-
-
-
-
-
-
-
-
-
-
- 详细限制信息
-
-
-
-
-
-
-
- 受限模型列表
-
-
-
-
- 此 API Key 不能访问以上列出的模型
-
-
-
-
-
-
-
- 允许的客户端
-
-
-
-
- 此 API Key 只能被以上列出的客户端使用
-
-
-
-
-
-
-
-
-
-
- 模型使用统计 ({{ statsPeriod === 'daily' ? '今日' : '本月' }})
-
-
-
-
-
-
-
-
-
-
-
-
{{ model.model }}
-
{{ model.requests }} 次请求
-
-
-
{{ model.formatted?.total || '$0.000000' }}
-
总费用
-
-
-
-
-
-
输入 Token
-
{{ formatNumber(model.inputTokens) }}
-
-
-
输出 Token
-
{{ formatNumber(model.outputTokens) }}
-
-
-
缓存创建
-
{{ formatNumber(model.cacheCreateTokens) }}
-
-
-
缓存读取
-
{{ formatNumber(model.cacheReadTokens) }}
-
-
-
-
-
-
-
-
-
暂无{{ statsPeriod === 'daily' ? '今日' : '本月' }}模型使用数据
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/web/apiStats/style.css b/web/apiStats/style.css
deleted file mode 100644
index 6bfce507..00000000
--- a/web/apiStats/style.css
+++ /dev/null
@@ -1,870 +0,0 @@
-/* 🎨 用户统计页面自定义样式 - 与管理页面保持一致 */
-
-/* CSS 变量 - 与管理页面保持一致 */
-:root {
- --primary-color: #667eea;
- --secondary-color: #764ba2;
- --accent-color: #f093fb;
- --success-color: #10b981;
- --warning-color: #f59e0b;
- --error-color: #ef4444;
- --surface-color: rgba(255, 255, 255, 0.95);
- --glass-color: rgba(255, 255, 255, 0.1);
- --text-primary: #1f2937;
- --text-secondary: #6b7280;
- --border-color: rgba(255, 255, 255, 0.2);
-}
-
-/* 📱 响应式布局优化 */
-@media (max-width: 768px) {
- .container {
- padding-left: 1rem;
- padding-right: 1rem;
- }
-
- .card {
- margin-bottom: 1rem;
- }
-
- .grid {
- grid-template-columns: 1fr;
- gap: 1rem;
- }
-
- .stat-card {
- padding: 0.75rem;
- }
-
- .stat-card .text-2xl {
- font-size: 1.5rem;
- }
-
- .model-usage-item .grid {
- grid-template-columns: 1fr 1fr;
- gap: 0.5rem;
- }
-
- .text-4xl {
- font-size: 2rem;
- }
-
- .input-field, .btn-primary {
- padding: 0.75rem 1rem;
- }
-}
-
-@media (max-width: 480px) {
- .container {
- padding-left: 0.5rem;
- padding-right: 0.5rem;
- }
-
- .text-4xl {
- font-size: 1.75rem;
- }
-
- .text-lg {
- font-size: 1rem;
- }
-
- .card {
- padding: 1rem;
- }
-
- .stat-card {
- padding: 0.5rem;
- }
-
- .stat-card .text-2xl {
- font-size: 1.25rem;
- }
-
- .stat-card .text-sm {
- font-size: 0.75rem;
- }
-
- .model-usage-item .grid {
- grid-template-columns: 1fr;
- }
-
- .flex.gap-3 {
- flex-direction: column;
- gap: 0.75rem;
- }
-
- .btn-primary {
- width: 100%;
- justify-content: center;
- }
-}
-
-/* 🌈 渐变背景 - 与管理页面一致 */
-body {
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
- background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 50%, var(--accent-color) 100%);
- background-attachment: fixed;
- min-height: 100vh;
- margin: 0;
- overflow-x: hidden;
-}
-
-body::before {
- content: '';
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background:
- radial-gradient(circle at 20% 80%, rgba(240, 147, 251, 0.2) 0%, transparent 50%),
- radial-gradient(circle at 80% 20%, rgba(102, 126, 234, 0.2) 0%, transparent 50%),
- radial-gradient(circle at 40% 40%, rgba(118, 75, 162, 0.1) 0%, transparent 50%);
- pointer-events: none;
- z-index: -1;
-}
-
-.gradient-bg {
- /* 移除原有的渐变,使用body的背景 */
-}
-
-/* ✨ 卡片样式 - 与管理页面一致 */
-.glass {
- background: var(--glass-color);
- backdrop-filter: blur(20px);
- border: 1px solid var(--border-color);
- box-shadow:
- 0 20px 25px -5px rgba(0, 0, 0, 0.1),
- 0 10px 10px -5px rgba(0, 0, 0, 0.04),
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
-}
-
-.glass-strong {
- background: var(--surface-color);
- backdrop-filter: blur(25px);
- border: 1px solid rgba(255, 255, 255, 0.3);
- box-shadow:
- 0 25px 50px -12px rgba(0, 0, 0, 0.25),
- 0 0 0 1px rgba(255, 255, 255, 0.05),
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
-}
-
-.card {
- background: var(--surface-color);
- border-radius: 16px;
- border: 1px solid rgba(255, 255, 255, 0.2);
- box-shadow:
- 0 10px 15px -3px rgba(0, 0, 0, 0.1),
- 0 4px 6px -2px rgba(0, 0, 0, 0.05);
- overflow: hidden;
- position: relative;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-.card::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 1px;
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5), transparent);
-}
-
-.card:hover {
- transform: translateY(-2px);
- box-shadow:
- 0 20px 25px -5px rgba(0, 0, 0, 0.15),
- 0 10px 10px -5px rgba(0, 0, 0, 0.08);
-}
-
-/* 🎯 统计卡片样式 - 与管理页面一致 */
-.stat-card {
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.8) 100%);
- border-radius: 20px;
- border: 1px solid rgba(255, 255, 255, 0.3);
- padding: 24px;
- position: relative;
- overflow: hidden;
- transition: all 0.3s ease;
-}
-
-.stat-card::before {
- content: '';
- position: absolute;
- top: -50%;
- left: -50%;
- width: 200%;
- height: 200%;
- background: radial-gradient(circle, rgba(255, 255, 255, 0.1) 0%, transparent 70%);
- opacity: 0;
- transition: opacity 0.3s ease;
-}
-
-.stat-card:hover {
- transform: translateY(-4px);
- box-shadow:
- 0 20px 25px -5px rgba(0, 0, 0, 0.1),
- 0 10px 10px -5px rgba(0, 0, 0, 0.04);
-}
-
-.stat-card:hover::before {
- opacity: 1;
-}
-
-/* 🔍 输入框样式 - 与管理页面一致 */
-.form-input {
- background: rgba(255, 255, 255, 0.9);
- border: 2px solid rgba(255, 255, 255, 0.3);
- border-radius: 12px;
- padding: 16px;
- font-size: 16px;
- transition: all 0.3s ease;
- backdrop-filter: blur(10px);
- color: var(--text-primary);
-}
-
-.form-input::placeholder {
- color: var(--text-secondary);
-}
-
-.form-input:focus {
- outline: none;
- border-color: var(--primary-color);
- box-shadow:
- 0 0 0 3px rgba(102, 126, 234, 0.1),
- 0 10px 15px -3px rgba(0, 0, 0, 0.1);
- background: rgba(255, 255, 255, 0.95);
-}
-
-/* 兼容旧的 input-field 类名 */
-.input-field {
- background: rgba(255, 255, 255, 0.9);
- border: 2px solid rgba(255, 255, 255, 0.3);
- border-radius: 12px;
- padding: 16px;
- font-size: 16px;
- transition: all 0.3s ease;
- backdrop-filter: blur(10px);
- color: var(--text-primary);
-}
-
-.input-field::placeholder {
- color: var(--text-secondary);
-}
-
-.input-field:focus {
- outline: none;
- border-color: var(--primary-color);
- box-shadow:
- 0 0 0 3px rgba(102, 126, 234, 0.1),
- 0 10px 15px -3px rgba(0, 0, 0, 0.1);
- background: rgba(255, 255, 255, 0.95);
-}
-
-/* ====== 系统标题样式 ====== */
-.header-title {
- background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- background-clip: text;
- font-weight: 700;
- letter-spacing: -0.025em;
-}
-
-/* ====== 玻璃按钮样式 ====== */
-.glass-button {
- background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%) !important;
- border: 1px solid rgba(255, 255, 255, 0.2) !important;
- backdrop-filter: blur(10px) !important;
- -webkit-backdrop-filter: blur(10px) !important;
- border-radius: 12px !important;
- transition: all 0.3s ease !important;
- color: white !important;
- text-decoration: none !important;
- box-shadow:
- 0 10px 15px -3px rgba(102, 126, 234, 0.3),
- 0 4px 6px -2px rgba(102, 126, 234, 0.05) !important;
- position: relative;
- overflow: hidden;
-}
-
-.glass-button::before {
- content: '';
- position: absolute;
- top: 0;
- left: -100%;
- width: 100%;
- height: 100%;
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
- transition: left 0.5s;
-}
-
-.glass-button:hover::before {
- left: 100%;
-}
-
-.glass-button:hover {
- background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%) !important;
- border-color: rgba(255, 255, 255, 0.3) !important;
- transform: translateY(-1px) !important;
- box-shadow:
- 0 20px 25px -5px rgba(102, 126, 234, 0.3),
- 0 10px 10px -5px rgba(102, 126, 234, 0.1) !important;
- color: white !important;
- text-decoration: none !important;
-}
-
-/* 🎨 按钮样式 - 与管理页面一致 */
-.btn {
- font-weight: 500;
- border-radius: 12px;
- border: none;
- cursor: pointer;
- transition: all 0.3s ease;
- position: relative;
- overflow: hidden;
- letter-spacing: 0.025em;
-}
-
-.btn::before {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- width: 0;
- height: 0;
- background: rgba(255, 255, 255, 0.2);
- border-radius: 50%;
- transform: translate(-50%, -50%);
- transition: width 0.3s ease, height 0.3s ease;
-}
-
-.btn:active::before {
- width: 300px;
- height: 300px;
-}
-
-.btn-primary {
- background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
- color: white;
- box-shadow:
- 0 10px 15px -3px rgba(102, 126, 234, 0.3),
- 0 4px 6px -2px rgba(102, 126, 234, 0.05);
-}
-
-.btn-primary:hover {
- transform: translateY(-1px);
- box-shadow:
- 0 20px 25px -5px rgba(102, 126, 234, 0.3),
- 0 10px 10px -5px rgba(102, 126, 234, 0.1);
-}
-
-.btn-primary:disabled {
- opacity: 0.6;
- cursor: not-allowed;
- transform: none;
-}
-
-/* 🎯 修复时间范围按钮样式 */
-.btn-primary {
- border-radius: 12px !important;
-}
-
-.btn {
- border-radius: 12px !important;
-}
-
-/* 🎯 时间范围按钮 - 与管理页面 tab-btn 样式一致 */
-.period-btn {
- position: relative;
- overflow: hidden;
- border-radius: 12px;
- font-weight: 500;
- letter-spacing: 0.025em;
- transition: all 0.3s ease;
- border: none;
- cursor: pointer;
-}
-
-.period-btn::before {
- content: '';
- position: absolute;
- top: 0;
- left: -100%;
- width: 100%;
- height: 100%;
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
- transition: left 0.5s;
-}
-
-.period-btn:hover::before {
- left: 100%;
-}
-
-.period-btn.active {
- background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%);
- color: white;
- box-shadow:
- 0 10px 15px -3px rgba(102, 126, 234, 0.3),
- 0 4px 6px -2px rgba(102, 126, 234, 0.05);
- transform: translateY(-1px);
-}
-
-.period-btn:not(.active) {
- color: #374151;
- background: transparent;
-}
-
-.period-btn:not(.active):hover {
- background: rgba(255, 255, 255, 0.1);
- color: #1f2937;
-}
-
-/* 📊 模型使用项样式 - 与管理页面保持一致 */
-.model-usage-item {
- background: rgba(255, 255, 255, 0.95);
- border: 1px solid rgba(255, 255, 255, 0.2);
- border-radius: 12px;
- padding: 16px;
- transition: all 0.3s ease;
- position: relative;
- overflow: hidden;
-}
-
-.model-usage-item::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- height: 1px;
- background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.5), transparent);
-}
-
-.model-usage-item:hover {
- transform: translateY(-2px);
- box-shadow:
- 0 10px 15px -3px rgba(0, 0, 0, 0.1),
- 0 4px 6px -2px rgba(0, 0, 0, 0.05);
- border-color: rgba(255, 255, 255, 0.3);
-}
-
-/* 🔄 加载动画增强 */
-.loading-spinner {
- animation: spin 1s linear infinite;
- filter: drop-shadow(0 0 4px rgba(255, 255, 255, 0.5));
-}
-
-@keyframes spin {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
-}
-
-/* 🌟 动画效果 */
-.fade-in {
- animation: fadeIn 0.6s ease-out;
-}
-
-@keyframes fadeIn {
- from {
- opacity: 0;
- transform: translateY(30px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-.slide-in {
- animation: slideIn 0.4s ease-out;
-}
-
-@keyframes slideIn {
- from {
- opacity: 0;
- transform: translateX(-30px);
- }
- to {
- opacity: 1;
- transform: translateX(0);
- }
-}
-
-/* 🎯 焦点样式增强 */
-.input-field:focus-visible,
-.btn-primary:focus-visible {
- outline: 2px solid rgba(255, 255, 255, 0.5);
- outline-offset: 2px;
-}
-
-/* 📱 滚动条样式 */
-::-webkit-scrollbar {
- width: 8px;
-}
-
-::-webkit-scrollbar-track {
- background: rgba(255, 255, 255, 0.1);
- border-radius: 4px;
-}
-
-::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.3);
- border-radius: 4px;
- transition: background 0.3s ease;
-}
-
-::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.5);
-}
-
-/* 🚨 错误状态样式 */
-.error-border {
- border-color: #ef4444 !important;
- box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1);
-}
-
-/* 🎉 成功状态样式 */
-.success-border {
- border-color: #10b981 !important;
- box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1);
-}
-
-/* 🌙 深色模式适配 */
-@media (prefers-color-scheme: dark) {
- .card {
- background: rgba(0, 0, 0, 0.2);
- border-color: rgba(255, 255, 255, 0.15);
- }
-
- .stat-card {
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.05) 100%);
- }
-
- .input-field {
- background: rgba(0, 0, 0, 0.3);
- border-color: rgba(255, 255, 255, 0.3);
- }
-}
-
-/* 🔍 高对比度模式支持 */
-@media (prefers-contrast: high) {
- .card {
- border-width: 2px;
- border-color: rgba(255, 255, 255, 0.5);
- }
-
- .input-field {
- border-width: 2px;
- border-color: rgba(255, 255, 255, 0.6);
- }
-
- .btn-primary {
- border: 2px solid rgba(255, 255, 255, 0.5);
- }
-}
-
-/* 📊 数据可视化增强 */
-.chart-container {
- position: relative;
- background: rgba(255, 255, 255, 0.05);
- border-radius: 12px;
- padding: 20px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- backdrop-filter: blur(10px);
-}
-
-/* 🎨 图标动画 */
-.fas {
- transition: transform 0.3s ease;
-}
-
-.card:hover .fas {
- transform: scale(1.1);
-}
-
-/* 💫 悬浮效果 */
-.hover-lift {
- transition: transform 0.3s ease, box-shadow 0.3s ease;
-}
-
-.hover-lift:hover {
- transform: translateY(-4px);
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
-}
-
-/* 🎯 选中状态 */
-.selected {
- background: rgba(255, 255, 255, 0.2) !important;
- border-color: rgba(255, 255, 255, 0.4) !important;
- transform: scale(1.02);
-}
-
-/* 🌈 彩虹边框效果 */
-.rainbow-border {
- position: relative;
- background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #feca57);
- background-size: 400% 400%;
- animation: gradientBG 15s ease infinite;
- padding: 2px;
- border-radius: 12px;
-}
-
-.rainbow-border > * {
- background: rgba(0, 0, 0, 0.8);
- border-radius: 10px;
-}
-
-@keyframes gradientBG {
- 0% { background-position: 0% 50%; }
- 50% { background-position: 100% 50%; }
- 100% { background-position: 0% 50%; }
-}
-
-/* 🎯 单层宽卡片样式优化 */
-.api-input-wide-card {
- background: var(--surface-color);
- backdrop-filter: blur(25px);
- border: 1px solid rgba(255, 255, 255, 0.3);
- box-shadow:
- 0 25px 50px -12px rgba(0, 0, 0, 0.25),
- 0 0 0 1px rgba(255, 255, 255, 0.05),
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-.api-input-wide-card:hover {
- box-shadow:
- 0 32px 64px -12px rgba(0, 0, 0, 0.35),
- 0 0 0 1px rgba(255, 255, 255, 0.08),
- inset 0 1px 0 rgba(255, 255, 255, 0.15);
- transform: translateY(-1px);
-}
-
-/* 🎯 宽卡片内标题样式 */
-.wide-card-title h2 {
- color: #1f2937 !important;
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
- font-weight: 700;
-}
-
-.wide-card-title p {
- color: #4b5563 !important;
- text-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
-}
-
-.wide-card-title .fas.fa-chart-line {
- color: #3b82f6 !important;
- text-shadow: 0 1px 2px rgba(59, 130, 246, 0.2);
-}
-
-/* 🎯 网格布局优化 */
-.api-input-grid {
- align-items: end;
- gap: 1rem;
-}
-
-@media (min-width: 1024px) {
- .api-input-grid {
- grid-template-columns: 3fr 1fr;
- gap: 1.5rem;
- }
-}
-
-/* 🎯 输入框在宽卡片中的样式调整 */
-.wide-card-input {
- background: rgba(255, 255, 255, 0.95);
- border: 2px solid rgba(255, 255, 255, 0.4);
- border-radius: 12px;
- padding: 14px 16px;
- font-size: 16px;
- transition: all 0.3s ease;
- backdrop-filter: blur(10px);
- color: var(--text-primary);
- box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
-}
-
-.wide-card-input::placeholder {
- color: #9ca3af;
-}
-
-.wide-card-input:focus {
- outline: none;
- border-color: #60a5fa;
- box-shadow:
- 0 0 0 3px rgba(96, 165, 250, 0.2),
- 0 10px 15px -3px rgba(0, 0, 0, 0.1);
- background: white;
-}
-
-/* 🎯 安全提示样式优化 */
-.security-notice {
- background: rgba(255, 255, 255, 0.15);
- border: 1px solid rgba(255, 255, 255, 0.25);
- backdrop-filter: blur(10px);
- border-radius: 8px;
- padding: 12px 16px;
- color: #374151;
- font-size: 0.875rem;
- transition: all 0.3s ease;
-}
-
-.security-notice:hover {
- background: rgba(255, 255, 255, 0.2);
- border-color: rgba(255, 255, 255, 0.35);
-}
-
-.security-notice .fas.fa-shield-alt {
- color: #10b981 !important;
- text-shadow: 0 1px 2px rgba(16, 185, 129, 0.2);
-}
-
-/* 🎯 时间范围选择器在卡片内的样式优化 */
-.time-range-section {
- background: rgba(255, 255, 255, 0.05);
- border-radius: 12px;
- padding: 16px;
- margin-bottom: 24px;
- border: 1px solid rgba(255, 255, 255, 0.1);
- transition: all 0.3s ease;
-}
-
-.time-range-section:hover {
- background: rgba(255, 255, 255, 0.08);
- border-color: rgba(255, 255, 255, 0.15);
-}
-
-/* 📱 响应式优化 - 宽卡片布局 */
-@media (max-width: 768px) {
- .api-input-wide-card {
- padding: 1.25rem !important;
- margin-left: 1rem;
- margin-right: 1rem;
- }
-
- .wide-card-title {
- margin-bottom: 1.25rem !important;
- }
-
- .wide-card-title h2 {
- font-size: 1.5rem !important;
- }
-
- .wide-card-title p {
- font-size: 0.875rem !important;
- }
-
- .api-input-grid {
- grid-template-columns: 1fr !important;
- gap: 1rem !important;
- }
-
- .wide-card-input {
- padding: 12px 14px !important;
- font-size: 16px !important;
- }
-
- .security-notice {
- padding: 10px 14px !important;
- font-size: 0.8rem !important;
- }
-}
-
-@media (max-width: 480px) {
- .api-input-wide-card {
- padding: 1rem !important;
- margin-left: 0.5rem;
- margin-right: 0.5rem;
- }
-
- .wide-card-title h2 {
- font-size: 1.25rem !important;
- }
-
- .wide-card-title p {
- font-size: 0.8rem !important;
- }
-}
-
-/* 📱 响应式优化 - 时间范围选择器 */
-@media (max-width: 768px) {
- .time-range-section .flex {
- flex-direction: column;
- align-items: flex-start !important;
- gap: 1rem;
- }
-
- .time-range-section .flex .flex {
- width: 100%;
- justify-content: center;
- }
-
- .period-btn {
- flex: 1;
- justify-content: center;
- }
-}
-
-/* 📱 触摸设备优化 */
-@media (hover: none) and (pointer: coarse) {
- .card:hover {
- transform: none;
- box-shadow: 0 8px 32px rgba(31, 38, 135, 0.37);
- }
-
- .btn-primary:hover {
- transform: none;
- background-position: 0% 0;
- }
-
- .model-usage-item:hover {
- transform: none;
- background: rgba(255, 255, 255, 0.05);
- }
-
- .time-range-section:hover {
- background: rgba(255, 255, 255, 0.05);
- border-color: rgba(255, 255, 255, 0.1);
- }
-
- .query-title-section:hover {
- background: linear-gradient(135deg, rgba(59, 130, 246, 0.05) 0%, rgba(99, 102, 241, 0.05) 100%);
- border-color: rgba(59, 130, 246, 0.1);
- }
-
- .api-input-wide-card:hover {
- transform: none;
- box-shadow:
- 0 25px 50px -12px rgba(0, 0, 0, 0.25),
- 0 0 0 1px rgba(255, 255, 255, 0.05),
- inset 0 1px 0 rgba(255, 255, 255, 0.1);
- }
-
- .security-notice:hover {
- background: rgba(255, 255, 255, 0.15);
- border-color: rgba(255, 255, 255, 0.25);
- }
-}
-
-/* 🎯 打印样式 */
-@media print {
- .gradient-bg {
- background: white !important;
- color: black !important;
- }
-
- .card {
- border: 1px solid #ccc !important;
- background: white !important;
- box-shadow: none !important;
- }
-
- .btn-primary {
- display: none !important;
- }
-
- .input-field {
- border: 1px solid #ccc !important;
- background: white !important;
- }
-}
\ No newline at end of file