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 @@ + + + \ 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 @@ + + + \ 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 @@ + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 @@ + + + + + \ 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 - 管理后台 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
- Logo - -
-

{{ oemSettings.siteName || 'Claude Relay Service' }}

-

管理后台

-
- -
-
- - -
- -
- - -
- - -
- -
- {{ loginError }} -
-
-
- - -
- -
-
-
-
- Logo - -
-
-
-

{{ oemSettings.siteName || 'Claude Relay Service' }}

- -
- v{{ versionInfo.current || '...' }} - - - - 新版本 - -
-
-

管理后台

-
-
- -
- - - -
- -
-
- 当前版本 - v{{ versionInfo.current || '...' }} -
-
-
- - 有新版本 - - v{{ versionInfo.latest }} -
- - 查看更新 - -
-
- 检查更新中... -
-
- - -
-

- 当前已是最新版本 -

-
- -
-
-
- - - -
- - -
-
-
-
- - -
- -
- -
- - -
- -
-
-
-
-

总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使用趋势

-
- -
- -
- - -
- - -
- - -
- - - 最多24小时 - -
- - - -
-
- -
- -
-

Token使用分布

-
- -
-
- - -
-

详细统计数据

-
-

暂无模型使用数据

-
-
- - - - - - - - - - - - - - - - - - - -
模型请求数总Token费用占比
{{ stat.model }}{{ formatNumber(stat.requests) }}{{ formatNumber(stat.allTokens) }}{{ stat.formatted ? stat.formatted.total : '$0.000000' }} - - {{ calculatePercentage(stat.allTokens, dashboardModelStats) }}% - -
-
-
-
-
- - -
-
-
- -
-
-
- - -
-
-
-

API Keys 使用趋势

- -
- - -
-
-
- - 共 {{ apiKeysTrendData.totalApiKeys }} 个 API Key,显示使用量前 10 个 - - - 共 {{ apiKeysTrendData.totalApiKeys }} 个 API Key - -
-
- -
-
-
-
- - -
-
-
-
-

API Keys 管理

-

管理和监控您的 API 密钥

-
-
- - - -
-
- -
-
-

正在加载 API Keys...

-
- -
-
- -
-

暂无 API Keys

-

点击上方按钮创建您的第一个 API Key

-
- -
- - - - - - - - - - - - - - - -
- 名称 - - - API Key - 状态 - - - - 使用统计 - - (费用 - - ) - - - 创建时间 - - - - 过期时间 - - - 操作
-
-
-
- - -
-
-
-
-

账户管理

-

管理您的 Claude 和 Gemini 账户及代理配置

-
-
- - -
-
- -
-
-

正在加载 Claude 账户...

-
- -
-
- -
-

暂无 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。

-
- - -
-
- -
-
- - -
- -
-

- 1 - 安装 Node.js 环境 -

-

Claude Code 需要 Node.js 环境才能运行。

- -
-
- - Windows 安装方法 -
-
-

方法一:官网下载(推荐)

-
    -
  1. 打开浏览器访问 https://nodejs.org/
  2. -
  3. 点击 "LTS" 版本进行下载(推荐长期支持版本)
  4. -
  5. 下载完成后双击 .msi 文件
  6. -
  7. 按照安装向导完成安装,保持默认设置即可
  8. -
-
-
-

方法二:使用包管理器

-

如果你安装了 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 -
-
    -
  1. 访问 https://git-scm.com/downloads/win
  2. -
  3. 点击 "Download for Windows" 下载安装包
  4. -
  5. 运行下载的 .exe 安装文件
  6. -
  7. 在安装过程中保持默认设置,直接点击 "Next" 完成安装
  8. -
-
-
安装完成后
-
    -
  • • 在任意文件夹右键可以看到 "Git Bash Here" 选项
  • -
  • • 也可以从开始菜单启动 "Git Bash"
  • -
  • • 只需要在 Git Bash 中运行 npm install 命令
  • -
  • • 后续的环境变量设置和使用都在 PowerShell/CMD 中
  • -
-
-
- - -
-
验证 Git Bash 安装
-

打开 Git Bash,输入以下命令验证:

-
-
git --version
-
-

如果显示 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 --version
-
-

如果显示版本号,恭喜你!Claude Code 已经成功安装了。

-
-
- - -
-

- 4 - 设置环境变量 -

- -
-
- - 配置 Claude Code 环境变量 -
-

为了让 Claude Code 连接到你的中转服务,需要设置两个环境变量:

- -
-
-
方法一:PowerShell 临时设置(推荐)
-

在 PowerShell 中运行以下命令:

-
-
$env:ANTHROPIC_BASE_URL = "{{ currentBaseUrl }}"
-
$env:ANTHROPIC_AUTH_TOKEN = "你的API密钥"
-
-

💡 记得将 "你的API密钥" 替换为在上方 "API Keys" 标签页中创建的实际密钥。

-
- -
-
方法二:系统环境变量(永久设置)
-
    -
  1. 右键"此电脑" → "属性" → "高级系统设置"
  2. -
  3. 点击"环境变量"按钮
  4. -
  5. 在"用户变量"或"系统变量"中点击"新建"
  6. -
  7. 添加以下两个变量:
  8. -
-
-
- 变量名: 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 了!

- -
-
-
启动 Claude Code
-
-
claude
-
-
- -
-
在特定项目中使用
-
-
# 进入你的项目目录
-
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
-
-
-
-

方法二:官网下载

-
    -
  1. 访问 https://nodejs.org/
  2. -
  3. 下载适合 macOS 的 LTS 版本
  4. -
  5. 打开下载的 .pkg 文件
  6. -
  7. 按照安装程序指引完成安装
  8. -
-
-
-
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 --version
-
-

如果显示版本号,恭喜你!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 了!

- -
-
-
启动 Claude Code
-
-
claude
-
-
- -
-
在特定项目中使用
-
-
# 进入你的项目目录
-
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 --version
-
-

如果显示版本号,恭喜你!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 了!

- -
-
-
启动 Claude Code
-
-
claude
-
-
- -
-
在特定项目中使用
-
-
# 进入你的项目目录
-
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 编程助手带来的便利了。

-

如果在使用过程中遇到任何问题,可以查看官方文档或社区讨论获取帮助。

-
-
-
- - -
-
-
-
-

其他设置

-

自定义网站名称和图标

-
-
- -
-
-

正在加载设置...

-
- -
- - - - - - - - - - - - - - - - - - - -
-
-
- -
-
-
网站名称
-
品牌标识
-
-
-
- -

将显示在浏览器标题和页面头部

-
-
-
- -
-
-
网站图标
-
Favicon
-
-
-
-
- -
- 图标预览 - 当前图标 - -
- -
- - - 支持 .ico, .png, .jpg, .svg 格式,最大 350KB -
-
-
-
-
- - - -
- -
- - 最后更新:{{ formatDateTime(oemSettings.updatedAt) }} -
-
-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
-
-
- -
-
-

{{ 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 统计 - - - - - - - - - - - - - - - - - - - - - -
- -
-
-
-
- Logo - -
-
-
-

{{ oemSettings.siteName || 'Claude Relay Service' }}

-
-

API Key 使用统计

-
-
- -
-
- - -
- -
-

- - 使用统计查询 -

-

查询您的 API Key 使用情况和统计数据

-
- - -
-
- -
- - -
- - -
- -
-
- - -
- - 您的 API Key 仅用于查询自己的统计数据,不会被存储或用于其他用途 -
-
-
- - -
-
- - {{ error }} -
-
- - -
- -
- -
-
-
- - 统计时间范围 -
-
- - -
-
-
- -
- -
-

- - 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 }} 个客户端 - - - - 允许所有客户端 - - -
-
-
-
- - -
-

- - 详细限制信息 -

- -
- -
-

- - 受限模型列表 -

-
-
- - {{ model }} -
-
-

- - 此 API Key 不能访问以上列出的模型 -

-
- - -
-

- - 允许的客户端 -

-
-
- - {{ client }} -
-
-

- - 此 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