Commit 26704c67 authored by Data Governance Dev's avatar Data Governance Dev

feat(web2): 初始化前端 + 后端完整骨架 + 数据库连接 popover

前端 (Vue 3.5 + Element Plus 2.9 + Vite 6, 端口 5174):
- 脚手架: package.json / vite.config.js / index.html / main.js / App.vue
- 路由 + 布局: router/index.js + layouts/MainLayout.vue + views/IdCardTool.vue
- 状态: stores/useDbConnection.js (响应式单例 + localStorage 持久化)
- UI: components/DbConnectionPopover.vue (数据库名 + 真实 logo + 测试/切换)
- 配置: config/dbPresets.js (后端不通兜底) + config/dbBranding.js (logo/icon/颜色)

后端 (FastAPI, 端口 8766):
- 入口: app.py + _logging.py (idempotent logger + RotatingFileHandler)
- API: routes/db.py (GET /api/db-defaults 读 web/configs/db_defaults.yaml,
  POST /api/connect/test 复用 web.core.db_adapter.test_connection)
- 启动: start.py (依赖检查 + uvicorn) / backend/start.bat

一键启动: web2/start.bat / web2/start.sh 同时拉起前后端两个窗口

数据流:
- 前端启动 → 异步 GET /api/db-defaults → 覆盖 presets (4s 超时 fallback)
- 点击测试 → POST /api/connect/test → 后端复用 web.core.db_adapter

安全策略:
- dbPresets.js 中 password 字段留空(真实密码不入库)
- 运行时由 GET /api/db-defaults 注入;后端不通时测试连接因密码空失败(预期)

配套改动:
- .gitignore: 忽略 web2/logs/ (start 脚本的 console 输出目录)
parent 53f6205d
......@@ -39,3 +39,4 @@ work-logs/
tags.lock
tags.temp
Session.vim
web2/logs/
"""web2 后端包
复用 web/ 的核心模块(db_adapter / models / configs),不重复实现。
"""
"""web2 后端统一日志配置
问题修复:
之前 start.py 和 app.py 都调 logging.basicConfig(...),
后调用的那次因为 root logger 已有 handler 被 basicConfig 幂等吞掉,
导致 web2.backend 的日志只写到 start.log、app.log 完全不更新。
解决:
抽出来一个 setup_logging(),检查 logger 是否有 handler,没有才配。
start.py / app.py 都从这里调 —— 重复调用幂等。
设计原则:
- 所有 web2.backend.* 日志同时写 stdout + app.log
- 不接管 start logger(start.py 自己管)
- 不接管 web.core.* / web.api.* logger(让它们 propagate 到 root,
由 root handler 输出,不重复写)
"""
from __future__ import annotations
import logging
from logging.handlers import RotatingFileHandler
from pathlib import Path
# ── 路径常量 ──
WEB2_DIR = Path(__file__).resolve().parent.parent
LOG_DIR = WEB2_DIR / "logs"
LOG_DIR.mkdir(parents=True, exist_ok=True)
APP_LOG_FILE = LOG_DIR / "app.log"
# 格式:与之前保持一致(用户已习惯)
_LOG_FORMAT = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
_DATE_FORMAT = None # 默认 ISO-ish
def setup_logging(level: str = "INFO") -> None:
"""初始化 web2.backend.* logger 的 handlers。
幂等:重复调用不会重复加 handler。
Args:
level: 日志级别(默认 INFO,可由环境变量 LOG_LEVEL 覆盖)
"""
web2_logger = logging.getLogger("web2.backend")
# 幂等:已经有 handler 就直接返回
if web2_logger.handlers:
return
web2_logger.setLevel(getattr(logging, level.upper(), logging.INFO))
# 不 propagate 到 root(避免重复写到 root 的 start.log)
web2_logger.propagate = False
formatter = logging.Formatter(_LOG_FORMAT, _DATE_FORMAT)
# 文件 handler(10MB 滚动,保留 5 份)
fh = RotatingFileHandler(
APP_LOG_FILE,
maxBytes=10 * 1024 * 1024,
backupCount=5,
encoding="utf-8",
)
fh.setFormatter(formatter)
web2_logger.addHandler(fh)
# 控制台 handler
ch = logging.StreamHandler()
ch.setFormatter(formatter)
web2_logger.addHandler(ch)
# 顺便确保 root 有基础配置(StreamHandler),让 web.core.* 能输出
root = logging.getLogger()
if not root.handlers:
logging.basicConfig(level=level, format=_LOG_FORMAT, datefmt=_DATE_FORMAT)
def get_logger(name: str) -> logging.Logger:
"""便捷:拿 web2.backend.* 子 logger,自动触发 setup_logging()"""
if not name.startswith("web2."):
name = f"web2.{name}"
return logging.getLogger(name)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""web2 后端入口(FastAPI)
启动方式:
python -m web2.backend.app
# 或
cd 数据治理 && python -m web2.backend.app
环境变量:
WEB2_PORT 端口(默认 8766,避免与 web 的 8765 冲突)
WEB2_HOST 主机(默认 0.0.0.0)
LOG_LEVEL 日志级别(默认 INFO)
设计原则:
- 复用 web/core/db_adapter.py(不重复实现 MySQL/达梦/Oracle 驱动)
- 复用 web/core/models.py 的 ConnectRequest/TestConnectionResponse
- 直接读 web/configs/db_defaults.yaml,不复制粘贴
- 当前阶段只暴露数据库相关 API;治理任务 API 后续按需加
"""
from __future__ import annotations
import os
import sys
from pathlib import Path
# ── 把项目根目录加进 sys.path,以便 import web.* ──
BACKEND_DIR = Path(__file__).resolve().parent
WEB2_DIR = BACKEND_DIR.parent
PROJECT_ROOT = WEB2_DIR.parent
sys.path.insert(0, str(PROJECT_ROOT))
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
# ── 日志(统一从 _logging 走,幂等,重复 import 不会重复加 handler) ──
from web2.backend._logging import setup_logging, get_logger # noqa: E402
setup_logging(os.environ.get("LOG_LEVEL", "INFO"))
logger = get_logger("backend") # → web2.backend
logger.info("=" * 60)
logger.info("web2 后端 · 进程启动")
logger.info(f"Python: {sys.version.split()[0]}, 平台: {sys.platform}")
logger.info(f"工作目录: {PROJECT_ROOT}")
from web2.backend._logging import APP_LOG_FILE # noqa: E402
logger.info(f"应用日志: {APP_LOG_FILE}")
# ── 创建 FastAPI 应用 ──
app = FastAPI(
title="数据治理 Web 工具 · web2 后端",
description="数据库连接管理(MySQL / 达梦 / Oracle)。前端在 web2/,由 Vite 5174 提供;本服务仅提供 API。",
version="0.1.0",
)
logger.info("FastAPI 应用已创建")
# CORS(开发模式:5174 是 Vite dev server)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
logger.debug("CORS 中间件已挂载(允许所有来源)")
# ── 注册路由 ──
from web2.backend.routes.db import router as db_router # noqa: E402
app.include_router(db_router, prefix="/api")
logger.info(f"已注册 {len(app.routes)} 个路由(含 db 路由)")
# ── 健康检查 ──
@app.get("/api/health")
async def health():
return {"status": "ok", "service": "web2-backend"}
# ── 静态资源(生产模式:构建后由 FastAPI 直接托管 dist/) ──
DIST_DIR = WEB2_DIR / "dist"
if DIST_DIR.exists():
# 挂载 assets 目录
app.mount("/assets", StaticFiles(directory=str(DIST_DIR / "assets")), name="assets")
@app.get("/", response_class=HTMLResponse)
async def root():
"""服务入口 HTML(仅当 dist/ 存在时生效;开发模式请用 Vite 5174)"""
index = DIST_DIR / "index.html"
if index.exists():
return HTMLResponse(index.read_text(encoding="utf-8"))
return HTMLResponse(
"<h1>web2 后端 API 服务</h1>"
"<p>开发模式请用 <code>npm run dev</code> 启动 Vite(端口 5174)。</p>"
"<p>API 文档:<a href='/docs'>/docs</a></p>"
)
# ── 启动 ──
def main():
import uvicorn
host = os.environ.get("WEB2_HOST", "0.0.0.0")
port = int(os.environ.get("WEB2_PORT", "8766"))
print("=" * 60)
print(f" web2 后端 · FastAPI")
print(f" API: http://localhost:{port}/api")
print(f" API 文档: http://localhost:{port}/docs")
print(f" 前端 dev: http://localhost:5174 (Vite 单独启动)")
print(f" 📋 应用日志: {LOG_DIR / 'app.log'}")
print("=" * 60)
uvicorn.run("web2.backend.app:app", host=host, port=port, reload=False, log_level="info")
if __name__ == "__main__":
main()
"""web2 后端路由包"""
"""数据库连接相关 API
端点:
GET /api/db-defaults 读取 web/configs/db_defaults.yaml,返回三套预设
POST /api/connect/test 测试连接是否可用(复用 web.core.db_adapter)
实现说明:
- 直接读 web/configs/db_defaults.yaml,不在 web2/ 复制一份(避免漂移)
- 测试连接复用 web.core.db_adapter.DBConfig + test_connection()
- 请求/响应模型复用 web.core.models(与 web 端一致)
日志策略:
- 每个请求开始打一行「分隔条 + 请求摘要」(password 打码)
- 测试连接打耗时(ms)
- 失败时用 logger.exception 拿全栈(不止一行 message)
- 日志同时进 stdout 和 web2/logs/app.log
"""
from __future__ import annotations
import time
from pathlib import Path
import yaml
from fastapi import APIRouter
# 复用 web/ 已有模块(不重复实现)
from web.core.db_adapter import DBConfig, test_connection
from web.core.models import TestConnectionRequest, TestConnectionResponse
from web2.backend._logging import get_logger
logger = get_logger("backend.routes.db") # → web2.backend.routes.db
router = APIRouter(tags=["database"])
# ── 路径常量 ────────────────────────────────────────────────
# web2/backend/routes/db.py → 往上 3 级到项目根 → web/configs/db_defaults.yaml
PROJECT_ROOT = Path(__file__).resolve().parents[3]
WEB_DEFAULTS_YAML = PROJECT_ROOT / "web" / "configs" / "db_defaults.yaml"
# ── 工具函数 ────────────────────────────────────────────────
def _mask_password(conn: dict) -> str:
"""连接信息脱敏:把 password 字段替换成 *** 用于日志输出"""
safe = {k: ("***" if k == "password" else v) for k, v in conn.items()}
return (
f"db_type={safe.get('db_type')} "
f"host={safe.get('host')}:{safe.get('port')} "
f"user={safe.get('user')} "
f"password={safe.get('password')} "
f"database={safe.get('database')} "
f"oracle_client_dir={'<set>' if safe.get('oracle_client_dir') else '<empty>'}"
)
# ── 端点 ────────────────────────────────────────────────────
@router.get("/db-defaults", summary="读取数据库连接预设")
async def db_defaults():
"""读取 web/configs/db_defaults.yaml,返回三套预设(mysql/dameng/oracle)
文件不存在或解析失败时返回空 dict,前端保留 JS 内置兜底值。
"""
logger.info("─" * 60)
logger.info("GET /api/db-defaults")
if not WEB_DEFAULTS_YAML.exists():
logger.warning(f"db_defaults.yaml 缺失: {WEB_DEFAULTS_YAML}")
logger.info("─" * 60)
return {}
try:
raw = yaml.safe_load(WEB_DEFAULTS_YAML.read_text(encoding="utf-8")) or {}
except Exception as e:
logger.exception(f"db_defaults.yaml 解析失败: {e}")
logger.info("─" * 60)
return {}
defaults = raw.get("db_defaults", {})
# 旧结构兼容:如果顶层 db_defaults 的 value 不是 dict,就当成「老格式单套」包成 {mysql: ...}
if not isinstance(defaults, dict) or any(
not isinstance(v, dict) for v in defaults.values()
):
logger.warning("db_defaults.yaml 为旧格式(单套配置),仅 mysql 可用")
logger.info(f"返回 keys: ['mysql'](旧格式兼容)")
logger.info("─" * 60)
return {"mysql": defaults}
# 防御性注入 db_type:yaml 里每个预设只有 host/port/user/password/database,
# 没有 db_type 字段(db_type 是 key 名)。前端有时会拿 conn.db_type 用,
# 后端注入后两边都安全(避免前端 JSON.stringify 漏掉 db_type → FastAPI 422)。
for db_type, preset in defaults.items():
if isinstance(preset, dict):
preset.setdefault("db_type", db_type)
logger.info(
f"返回 keys: {sorted(defaults.keys())}({sum(len(v) for v in defaults.values() if isinstance(v, dict))} 个字段)"
)
logger.info("─" * 60)
return defaults
@router.post("/connect/test", response_model=TestConnectionResponse, summary="测试数据库连接")
async def connect_test(req: TestConnectionRequest):
"""复用 web.core.db_adapter.test_connection,验证 db_type/host/port/user/password/database
返回:
TestConnectionResponse(ok, message, db_type, database_name, tables_count, ...)
失败时诊断信息:
- web2/logs/app.log 里有完整 traceback(用 logger.exception 打的)
- 响应 message 字段带异常摘要(前端弹窗显示)
"""
# ── 请求入口日志 ──
conn = req.model_dump()
logger.info("─" * 60)
logger.info(f"POST /api/connect/test {_mask_password(conn)}")
started = time.monotonic()
cfg = DBConfig(
db_type=req.db_type,
host=req.host,
port=req.port,
user=req.user,
password=req.password,
database=req.database,
charset=req.charset,
connect_timeout=req.connect_timeout,
oracle_client_dir=req.oracle_client_dir,
)
try:
ok, msg = test_connection(cfg)
except Exception as e:
elapsed_ms = (time.monotonic() - started) * 1000
# logger.exception 自动带全栈(不止一行 message)
logger.exception(
f"❌ 测试连接抛出未捕获异常(耗时 {elapsed_ms:.0f}ms): {type(e).__name__}: {e}"
)
logger.info("─" * 60)
return TestConnectionResponse(
ok=False,
message=f"测试连接异常: {type(e).__name__}: {e}",
db_type=req.db_type,
)
elapsed_ms = (time.monotonic() - started) * 1000
# ── 结果日志 ──
if not ok:
logger.warning(f"❌ 连接测试失败(耗时 {elapsed_ms:.0f}ms): {msg}")
# 常见原因分类提示(用户友好)
if "WinError 10061" in msg or "Connection refused" in msg:
logger.warning(
" 💡 诊断: 目标端口未监听(连接被拒)。可能原因:\n"
" 1) 主机/端口写错;\n"
" 2) 数据库服务没启;\n"
" 3) 防火墙拦截;\n"
" 4) 数据库不在 192.168.* 内网段(当前网络不可达)"
)
elif "timed out" in msg.lower() or "timeout" in msg.lower():
logger.warning(
" 💡 诊断: 连接超时。可能原因:\n"
" 1) 主机不可达(IP 错 / 不在同一网段);\n"
" 2) 防火墙丢包;\n"
" 3) 数据库服务卡死但不拒绝连接"
)
elif "Access denied" in msg or "1045" in msg:
logger.warning(" 💡 诊断: 用户名或密码错")
elif "Unknown database" in msg or "1049" in msg:
logger.warning(" 💡 诊断: 数据库名不存在")
logger.info("─" * 60)
return TestConnectionResponse(ok=False, message=msg, db_type=req.db_type)
logger.info(f"✅ 连接测试成功(耗时 {elapsed_ms:.0f}ms): {msg}")
logger.info("─" * 60)
return TestConnectionResponse(
ok=True,
message=msg,
db_type=req.db_type,
database_name=req.database,
)
@echo off
REM ============================================================
REM web2 Backend - Windows launcher
REM (REM lines are ASCII-only to avoid cmd.exe encoding issues)
REM ============================================================
REM Switch to project root (parent of web2/backend)
cd /d "%~dp0\..\.."
echo.
echo ============================================================
echo web2 Backend (FastAPI)
echo ============================================================
echo.
REM Check Python
python --version >nul 2>&1
if errorlevel 1 (
echo [ERROR] Python not found. Please install Python 3.10+ first.
pause
exit /b 1
)
REM Check fastapi; install minimal deps if missing
python -c "import fastapi" 2>nul
if errorlevel 1 (
echo [INFO] Installing minimal backend deps...
python -m pip install fastapi uvicorn pydantic pyyaml pymysql
if errorlevel 1 (
echo [ERROR] Failed to install dependencies.
pause
exit /b 1
)
)
REM Launch backend
python web2\backend\start.py
pause
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""web2 后端启动脚本(Windows / 跨平台通用)
用法:
python web2/backend/start.py
# 或
python -m web2.backend.start
环境变量(可选):
WEB2_HOST=0.0.0.0
WEB2_PORT=8766
LOG_LEVEL=INFO
"""
from __future__ import annotations
import logging
import os
import sys
from pathlib import Path
BACKEND_DIR = Path(__file__).resolve().parent
WEB2_DIR = BACKEND_DIR.parent
PROJECT_ROOT = WEB2_DIR.parent
sys.path.insert(0, str(PROJECT_ROOT))
_START_LOG = WEB2_DIR / "logs"
_START_LOG.mkdir(parents=True, exist_ok=True)
logging.basicConfig(
level=os.environ.get("LOG_LEVEL", "INFO"),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
handlers=[
logging.FileHandler(_START_LOG / "start.log", encoding="utf-8"),
logging.StreamHandler(),
],
)
logger = logging.getLogger("web2.backend.start")
def check_dependencies():
"""启动前检查关键依赖"""
missing = []
for pkg, name in [
("fastapi", "FastAPI"),
("uvicorn", "Uvicorn"),
("pydantic", "Pydantic"),
("yaml", "PyYAML"),
]:
try:
__import__(pkg)
except ImportError:
missing.append((pkg, name))
return missing
def main():
print("=" * 70)
print(" web2 后端 · 启动检查")
print("=" * 70)
logger.info("=" * 60)
logger.info("web2 后端 · 启动检查")
logger.info(f"Python: {sys.version.split()[0]}")
# 1. 依赖检查
missing = check_dependencies()
if missing:
msg = f"缺少关键依赖: {', '.join(n for _, n in missing)}"
print(f"\n✗ {msg}")
print(f"\n请执行: pip install fastapi uvicorn pydantic pyyaml pymysql")
logger.error(msg)
sys.exit(1)
print("✓ 关键依赖齐全")
logger.info("关键依赖齐全")
# 2. 配置
host = os.environ.get("WEB2_HOST", "0.0.0.0")
port = int(os.environ.get("WEB2_PORT", "8766"))
print(f"\n启动配置:")
print(f" · 主机: {host}")
print(f" · 端口: {port}")
logger.info(f"启动配置: host={host}, port={port}")
# 3. 启动
import uvicorn
app_log = WEB2_DIR / "logs" / "app.log"
start_log = WEB2_DIR / "logs" / "start.log"
print("\n" + "=" * 70)
print(f" web2 后端 · FastAPI")
print(f" API: http://localhost:{port}/api")
print(f" API 文档: http://localhost:{port}/docs")
print(f" 前端 dev: http://localhost:5174 (需另开终端跑 npm run dev)")
print(f" 按 Ctrl+C 停止")
print("=" * 70)
print(f" 📋 日志位置(连接失败时来这里看):")
print(f" 应用日志: {app_log}")
print(f" 启动日志: {start_log}")
print(f" 📖 实时查看: PowerShell Get-Content '{app_log}' -Wait")
print(f" CMD type \"{app_log}\"")
print(f" Git Bash tail -f \"{app_log}\"")
print("=" * 70 + "\n")
logger.info(f"启动 uvicorn: web2.backend.app:app @ http://{host}:{port}")
logger.info(f"应用日志文件: {app_log}")
uvicorn.run(
"web2.backend.app:app",
host=host,
port=port,
reload=False,
log_level=os.environ.get("LOG_LEVEL", "info"),
)
if __name__ == "__main__":
main()
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>数据治理工具</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
{
"name": "data-governance-web2",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "data-governance-web2",
"version": "0.1.0",
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"element-plus": "^2.9.1",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"vite": "^6.0.5"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
"integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.8"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/types": {
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
"integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.29.7",
"@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@ctrl/tinycolor": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz",
"integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==",
"license": "MIT",
"engines": {
"node": ">=14"
}
},
"node_modules/@element-plus/icons-vue": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz",
"integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
"integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
"integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
"integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
"integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
"integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
"integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
"integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
"integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
"integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
"integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
"integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
"integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
"integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
"integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
"integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
"integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
"integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
"integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
"integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
"integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
"integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
"integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
"integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
"integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
"integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
"integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@floating-ui/core": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
"integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.12"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz",
"integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.8.0",
"@floating-ui/utils": "^0.2.12"
}
},
"node_modules/@floating-ui/utils": {
"version": "0.2.12",
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz",
"integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
"license": "MIT"
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@napi-rs/lzma-linux-x64-gnu": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
"integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": "^22.20 || ^24.12 || >=25"
}
},
"node_modules/@popperjs/core": {
"name": "@sxzz/popperjs-es",
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz",
"integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz",
"integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz",
"integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz",
"integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz",
"integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz",
"integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz",
"integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz",
"integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==",
"cpu": [
"arm"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz",
"integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==",
"cpu": [
"arm"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz",
"integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz",
"integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz",
"integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==",
"cpu": [
"loong64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz",
"integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==",
"cpu": [
"loong64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz",
"integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz",
"integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz",
"integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz",
"integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==",
"cpu": [
"riscv64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz",
"integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz",
"integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz",
"integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz",
"integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz",
"integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz",
"integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz",
"integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz",
"integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz",
"integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.25",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz",
"integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==",
"license": "MIT"
},
"node_modules/@types/lodash-es": {
"version": "4.17.12",
"resolved": "https://registry.npmjs.org/@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.21",
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
"license": "MIT"
},
"node_modules/@vitejs/plugin-vue": {
"version": "5.2.4",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
"integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.0.0 || >=20.0.0"
},
"peerDependencies": {
"vite": "^5.0.0 || ^6.0.0",
"vue": "^3.2.25"
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.41",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.41.tgz",
"integrity": "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.8",
"@vue/shared": "3.5.41",
"entities": "^7.0.1",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.41",
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.41.tgz",
"integrity": "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.41",
"@vue/shared": "3.5.41"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.41",
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz",
"integrity": "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.8",
"@vue/compiler-core": "3.5.41",
"@vue/compiler-dom": "3.5.41",
"@vue/compiler-ssr": "3.5.41",
"@vue/shared": "3.5.41",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.21",
"postcss": "^8.5.19",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.41",
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz",
"integrity": "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.41",
"@vue/shared": "3.5.41"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
"license": "MIT"
},
"node_modules/@vue/reactivity": {
"version": "3.5.41",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.41.tgz",
"integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.41"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.41",
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.41.tgz",
"integrity": "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.41",
"@vue/shared": "3.5.41"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.41",
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.41.tgz",
"integrity": "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.41",
"@vue/runtime-core": "3.5.41",
"@vue/shared": "3.5.41",
"csstype": "^3.2.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.41",
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.41.tgz",
"integrity": "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.41",
"@vue/runtime-dom": "3.5.41",
"@vue/shared": "3.5.41"
}
},
"node_modules/@vue/shared": {
"version": "3.5.41",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz",
"integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==",
"license": "MIT"
},
"node_modules/@vueuse/core": {
"version": "14.3.0",
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.3.0.tgz",
"integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==",
"license": "MIT",
"dependencies": {
"@types/web-bluetooth": "^0.0.21",
"@vueuse/metadata": "14.3.0",
"@vueuse/shared": "14.3.0"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/@vueuse/metadata": {
"version": "14.3.0",
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.3.0.tgz",
"integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/@vueuse/shared": {
"version": "14.3.0",
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.3.0.tgz",
"integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
},
"peerDependencies": {
"vue": "^3.5.0"
}
},
"node_modules/async-validator": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
"integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==",
"license": "MIT"
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT"
},
"node_modules/dayjs": {
"version": "1.11.22",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.22.tgz",
"integrity": "sha512-1YRnxzt/AabP3GHxnaB9/b+ZScCKu5TeF+co+BWG+lnWVIwEcTFc1FVE0WLNmNO3sA6GGXL40i5qkHfbLzpwrg==",
"license": "MIT"
},
"node_modules/element-plus": {
"version": "2.14.4",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.14.4.tgz",
"integrity": "sha512-vMKR9tFcLeNrJgFXA3zhUn6YuRKUQW9d0btakBR8U1Iq8MfzkjMOGcgs5de2VgiLldHt69brmuBHpxc3bK4ZgQ==",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^4.2.0",
"@element-plus/icons-vue": "^2.3.2",
"@floating-ui/dom": "^1.8.0",
"@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.8",
"@types/lodash": "^4.17.24",
"@types/lodash-es": "^4.17.12",
"@vueuse/core": "14.3.0",
"async-validator": "^4.2.5",
"dayjs": "^1.11.20",
"lodash": "^4.18.1",
"lodash-es": "^4.18.1",
"lodash-unified": "^1.0.3",
"memoize-one": "^6.0.0",
"normalize-wheel-es": "^1.2.0",
"vue-component-type-helpers": "^3.3.9"
},
"peerDependencies": {
"vue": "^3.3.7"
}
},
"node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/esbuild": {
"version": "0.25.12",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.12",
"@esbuild/android-arm": "0.25.12",
"@esbuild/android-arm64": "0.25.12",
"@esbuild/android-x64": "0.25.12",
"@esbuild/darwin-arm64": "0.25.12",
"@esbuild/darwin-x64": "0.25.12",
"@esbuild/freebsd-arm64": "0.25.12",
"@esbuild/freebsd-x64": "0.25.12",
"@esbuild/linux-arm": "0.25.12",
"@esbuild/linux-arm64": "0.25.12",
"@esbuild/linux-ia32": "0.25.12",
"@esbuild/linux-loong64": "0.25.12",
"@esbuild/linux-mips64el": "0.25.12",
"@esbuild/linux-ppc64": "0.25.12",
"@esbuild/linux-riscv64": "0.25.12",
"@esbuild/linux-s390x": "0.25.12",
"@esbuild/linux-x64": "0.25.12",
"@esbuild/netbsd-arm64": "0.25.12",
"@esbuild/netbsd-x64": "0.25.12",
"@esbuild/openbsd-arm64": "0.25.12",
"@esbuild/openbsd-x64": "0.25.12",
"@esbuild/openharmony-arm64": "0.25.12",
"@esbuild/sunos-x64": "0.25.12",
"@esbuild/win32-arm64": "0.25.12",
"@esbuild/win32-ia32": "0.25.12",
"@esbuild/win32-x64": "0.25.12"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/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/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
"license": "MIT"
},
"node_modules/lodash-unified": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/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/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/memoize-one": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.18",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"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/normalize-wheel-es": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz",
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
"license": "BSD-3-Clause"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/postcss": {
"version": "8.5.26",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
"integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==",
"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.17",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/rollup": {
"version": "4.62.4",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz",
"integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.9"
},
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@napi-rs/lzma-linux-x64-gnu": "1.5.1",
"@rollup/rollup-android-arm-eabi": "4.62.4",
"@rollup/rollup-android-arm64": "4.62.4",
"@rollup/rollup-darwin-arm64": "4.62.4",
"@rollup/rollup-darwin-x64": "4.62.4",
"@rollup/rollup-freebsd-arm64": "4.62.4",
"@rollup/rollup-freebsd-x64": "4.62.4",
"@rollup/rollup-linux-arm-gnueabihf": "4.62.4",
"@rollup/rollup-linux-arm-musleabihf": "4.62.4",
"@rollup/rollup-linux-arm64-gnu": "4.62.4",
"@rollup/rollup-linux-arm64-musl": "4.62.4",
"@rollup/rollup-linux-loong64-gnu": "4.62.4",
"@rollup/rollup-linux-loong64-musl": "4.62.4",
"@rollup/rollup-linux-ppc64-gnu": "4.62.4",
"@rollup/rollup-linux-ppc64-musl": "4.62.4",
"@rollup/rollup-linux-riscv64-gnu": "4.62.4",
"@rollup/rollup-linux-riscv64-musl": "4.62.4",
"@rollup/rollup-linux-s390x-gnu": "4.62.4",
"@rollup/rollup-linux-x64-gnu": "4.62.4",
"@rollup/rollup-linux-x64-musl": "4.62.4",
"@rollup/rollup-openbsd-x64": "4.62.4",
"@rollup/rollup-openharmony-arm64": "4.62.4",
"@rollup/rollup-win32-arm64-msvc": "4.62.4",
"@rollup/rollup-win32-ia32-msvc": "4.62.4",
"@rollup/rollup-win32-x64-gnu": "4.62.4",
"@rollup/rollup-win32-x64-msvc": "4.62.4",
"fsevents": "~2.3.2"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/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/tinyglobby": {
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/vite": {
"version": "6.4.3",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
"integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
"picomatch": "^4.0.2",
"postcss": "^8.5.3",
"rollup": "^4.34.9",
"tinyglobby": "^0.2.13"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^18.0.0 || ^20.0.0 || >=22.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 || >=22.0.0",
"jiti": ">=1.21.0",
"less": "*",
"lightningcss": "^1.21.0",
"sass": "*",
"sass-embedded": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.16.0",
"tsx": "^4.8.1",
"yaml": "^2.4.2"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"jiti": {
"optional": true
},
"less": {
"optional": true
},
"lightningcss": {
"optional": true
},
"sass": {
"optional": true
},
"sass-embedded": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
},
"tsx": {
"optional": true
},
"yaml": {
"optional": true
}
}
},
"node_modules/vue": {
"version": "3.5.41",
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.41.tgz",
"integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.41",
"@vue/compiler-sfc": "3.5.41",
"@vue/runtime-dom": "3.5.41",
"@vue/server-renderer": "3.5.41",
"@vue/shared": "3.5.41"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/vue-component-type-helpers": {
"version": "3.3.10",
"resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.10.tgz",
"integrity": "sha512-t7IQivQ3oD4D01b7s7a9AWzHAcr3DGBIa/1jZREsFQJcFgSL92gqUkqNiHTYgzTt7QDuJa0I9wCeDwEtZICoBQ==",
"license": "MIT"
},
"node_modules/vue-router": {
"version": "4.6.4",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
"integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
"license": "MIT",
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.5.0"
}
}
}
}
{
"name": "data-governance-web2",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"element-plus": "^2.9.1",
"@element-plus/icons-vue": "^2.3.1"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
"vite": "^6.0.5"
}
}
<template>
<router-view />
</template>
<script setup>
// 根组件只负责挂载路由,由各 layout 决定具体结构
</script>
<style>
html,
body,
#app {
height: 100%;
margin: 0;
padding: 0;
}
</style>
<template>
<el-popover
:width="280"
placement="bottom-end"
trigger="click"
>
<!-- 触发器:Header 右上角按钮 -->
<template #reference>
<el-button
class="db-trigger"
:type="connected ? 'primary' : 'default'"
:loading="loading"
plain
round
>
<el-icon><Connection /></el-icon>
<span class="db-trigger__label">
{{ currentLabel }} · {{ currentConn.host }}:{{ currentConn.port }}
</span>
<el-icon class="db-trigger__arrow"><ArrowDown /></el-icon>
</el-button>
</template>
<!-- 弹出内容:极简列表 -->
<div class="db-popover">
<div class="db-popover__title">
<el-icon><Connection /></el-icon>
<span>数据库连接</span>
</div>
<ul class="db-popover__list">
<li
v-for="opt in dbTypes"
:key="opt.value"
:class="['db-popover__item', { 'is-active': opt.value === state.currentType }]"
>
<div class="db-popover__brand">
<img
v-if="brandOf(opt.value).url && !logoFailed[opt.value]"
:src="brandOf(opt.value).url"
:alt="opt.label"
class="db-popover__logo"
@error="onLogoError(opt.value)"
/>
<span
v-else
class="db-popover__logo db-popover__logo--fallback"
:style="{ background: brandOf(opt.value).color }"
>
<el-icon><component :is="brandOf(opt.value).fallbackIcon" /></el-icon>
</span>
<span class="db-popover__name">
{{ presets[opt.value]?.display || presets[opt.value]?.database || opt.label }}
</span>
</div>
<div class="db-popover__actions">
<el-button
link
size="small"
:loading="testing === opt.value"
:title="`测试 ${opt.label}`"
@click="onTest(opt.value)"
>
<el-icon><VideoPlay /></el-icon>
</el-button>
<el-button
type="primary"
link
size="small"
:disabled="opt.value === state.currentType"
:title="`切换到 ${opt.label}`"
@click="onSwitch(opt.value)"
>
<el-icon><Position /></el-icon>
</el-button>
</div>
</li>
</ul>
</div>
</el-popover>
</template>
<script setup>
import { computed, reactive, ref } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { DB_TYPES } from '@/config/dbPresets'
import { DB_LOGOS } from '@/config/dbBranding'
import { useDbConnection } from '@/stores/useDbConnection'
const {
state,
presets,
loading,
currentConnection,
setDbType,
} = useDbConnection()
const dbTypes = DB_TYPES
/** 当前正在测试的 db_type(null = 没在测试) */
const testing = ref(null)
/** 哪些 db_type 的 logo 加载失败过 → 切到 fallback icon */
const logoFailed = reactive({})
const currentConn = computed(() => currentConnection.value || {})
const currentLabel = computed(
() => dbTypes.find((d) => d.value === state.currentType)?.label || state.currentType,
)
const connected = computed(() => !!state.currentType)
function brandOf(dbType) {
return DB_LOGOS[dbType] || { url: null, color: '#909399', fallbackIcon: null }
}
function onLogoError(dbType) {
// 网络问题 / CDN 失效 → 切到 Element Plus icon 兜底,不再尝试
logoFailed[dbType] = true
}
/**
* 测试指定 db_type 的连接
*/
async function onTest(dbType) {
const conn = presets.value[dbType]
if (!conn) {
ElMessage.warning(`${dbType} 预设不存在`)
return
}
testing.value = dbType
try {
const r = await fetch('/api/connect/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
// db_type 用参数(永远有值),不从 conn 取(API YAML 没这字段)
db_type: dbType,
host: conn.host,
port: conn.port,
user: conn.user,
password: conn.password,
database: conn.database,
charset: 'utf8mb4',
connect_timeout: 10,
oracle_client_dir: conn.oracle_client_dir || null,
}),
})
if (!r.ok) {
let detail
try { detail = await r.json() } catch { detail = { detail: `HTTP ${r.status} ${r.statusText}` } }
const detailMsg = Array.isArray(detail.detail)
? detail.detail.map(d => `${d.loc?.join('.') || ''}: ${d.msg}`).join('\n')
: (detail.detail || JSON.stringify(detail))
ElMessageBox.alert(
`HTTP ${r.status}\n\n${detailMsg}`,
`❌ 测试失败(${dbType})`,
{ confirmButtonText: '好的', type: 'error' },
)
return
}
const data = await r.json()
if (data.ok) {
ElMessageBox.alert(
data.message || '连接成功',
`✅ ${dbType} 测试通过`,
{ confirmButtonText: '好的', type: 'success' },
)
} else {
ElMessageBox.alert(
data.message || '后端返回 ok=false 但无 message',
`❌ ${dbType} 测试失败`,
{ confirmButtonText: '好的', type: 'error' },
)
}
} catch (e) {
ElMessageBox.alert(
`请求失败: ${e.message}`,
`❌ ${dbType} 网络错误`,
{ confirmButtonText: '好的', type: 'error' },
)
} finally {
testing.value = null
}
}
/** 切换当前连接到指定 db_type */
function onSwitch(dbType) {
if (dbType === state.currentType) return
setDbType(dbType)
const label = dbTypes.find((d) => d.value === dbType)?.label || dbType
ElMessage.success(`已切换到 ${label}`)
document.activeElement?.blur?.()
}
</script>
<style scoped>
.db-trigger {
display: inline-flex;
align-items: center;
gap: 6px;
max-width: 320px;
font-weight: 500;
}
.db-trigger__label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.db-trigger__arrow {
font-size: 12px;
opacity: 0.7;
}
/* 弹框标题 */
.db-popover__title {
display: flex;
align-items: center;
gap: 6px;
font-size: 14px;
font-weight: 600;
color: #303133;
margin-bottom: 8px;
}
.db-popover__title .el-icon {
color: #409eff;
}
/* 列表 */
.db-popover__list {
list-style: none;
margin: 0;
padding: 0;
}
.db-popover__item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 10px;
border-radius: 6px;
transition: background 0.15s;
cursor: default;
}
.db-popover__item:hover {
background: #f5f7fa;
}
.db-popover__item.is-active {
background: #ecf5ff;
}
.db-popover__item.is-active .db-popover__name {
color: #409eff;
font-weight: 600;
}
/* logo + 数据库名(一行) */
.db-popover__brand {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
min-width: 0;
}
.db-popover__logo {
width: 20px;
height: 20px;
flex-shrink: 0;
object-fit: contain;
}
.db-popover__logo--fallback {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
color: #fff;
font-size: 12px;
}
.db-popover__logo--fallback .el-icon {
font-size: 14px;
}
.db-popover__name {
font-size: 14px;
color: #303133;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* 操作按钮 */
.db-popover__actions {
display: flex;
gap: 2px;
flex-shrink: 0;
}
.db-popover__actions .el-icon {
font-size: 16px;
}
</style>
/**
* 数据库品牌资源(logo / icon / 颜色)
*
* Logo 优先级:
* 1. 远程 SVG URL(devicon CDN,稳定 + 真实 logo)
* 2. URL 加载失败 → fallback 到 Element Plus icon(不依赖网络)
*
* 来源说明:
* - MySQL / Oracle → devicons/devicon(GitHub 11w+ stars,CDN 稳定)
* - 达梦 → 无公开 SVG logo CDN(国产数据库,体量小),用 Element Plus icon
* + 官方品牌色 #C8102E 兜底
*
* 颜色取自各厂商官方 brand color。
*/
import { DataLine, Coin } from '@element-plus/icons-vue'
export const DB_LOGOS = {
mysql: {
url: 'https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/mysql/mysql-original.svg',
color: '#00758F', // MySQL 官方蓝
fallbackIcon: DataLine,
},
dameng: {
url: null, // 达梦无公开 SVG CDN
color: '#C8102E', // 达梦官方红
fallbackIcon: DataLine,
},
oracle: {
url: 'https://cdn.jsdelivr.net/gh/devicons/devicon@latest/icons/oracle/oracle-original.svg',
color: '#C74634', // Oracle 官方红
fallbackIcon: Coin,
},
}
/**
* 数据库连接预设(前端本地兜底)
*
* 来源:web/configs/db_defaults.yaml(同步结构 + display 字段)
* - MySQL / 达梦 (DM) / Oracle 三套配置
* - 改这里 = 改 web/configs/db_defaults.yaml(保持两端结构一致)
*
* ⚠️ 安全:本文件已入库,password 字段必须留空字符串。
* 真实密码由运行时 GET /api/db-defaults 注入到 store.presets.password。
* 后端不通时 password 为空 → 测试连接会失败(这是预期行为,不是 bug)。
*
* 字段说明:
* host / port / user / database / display —— 可入库(无敏感信息)
* password —— 留空
* oracle_client_dir —— 可入库(路径,非凭据)
*/
export const DB_TYPES = [
{ value: 'mysql', label: 'MySQL' },
{ value: 'dameng', label: '达梦 (DM)' },
{ value: 'oracle', label: 'Oracle' },
]
export const DB_PRESETS = {
// ── MySQL ──
mysql: {
db_type: 'mysql',
host: '192.168.20.10',
port: 3306,
user: 'root',
password: '',
database: 'smart-build',
display: '智能建造',
oracle_client_dir: '',
},
// ── 达梦 (DM) ──
dameng: {
db_type: 'dameng',
host: '192.168.20.24',
port: 5236,
user: 'HGWXZJ',
password: '',
database: 'HGWXZJ',
display: '三金系统',
oracle_client_dir: '',
},
// ── Oracle ──
oracle: {
db_type: 'oracle',
host: '192.168.20.196',
port: 1521,
user: 'GJJ80',
password: '',
database: 'ZFGJJ',
display: '黄冈公积金',
oracle_client_dir:
'D:\\Oracle\\instantclient-basic-windows.x64-12.2.0.1.0\\instantclient_12_2',
},
}
<template>
<el-container class="layout-root">
<!-- 顶栏 -->
<el-header class="layout-header">
<div class="layout-header__brand">
<el-icon class="layout-header__logo"><DataAnalysis /></el-icon>
<span class="layout-header__title">数据治理工具</span>
</div>
<div class="layout-header__right">
<DbConnectionPopover />
<span class="layout-header__user">v0.1.0</span>
</div>
</el-header>
<el-container class="layout-body">
<!-- 左侧菜单 -->
<el-aside class="layout-aside" width="220px">
<el-menu
:default-active="activeMenu"
class="layout-menu"
background-color="#001529"
text-color="#bfcbd9"
active-text-color="#409eff"
router
>
<el-menu-item
v-for="item in menuItems"
:key="item.path"
:index="item.path"
:route="{ name: item.name }"
>
<el-icon><component :is="item.icon" /></el-icon>
<template #title>{{ item.title }}</template>
</el-menu-item>
</el-menu>
</el-aside>
<!-- 内容区 -->
<el-main class="layout-main">
<router-view v-slot="{ Component }">
<component :is="Component" />
</router-view>
</el-main>
</el-container>
</el-container>
</template>
<script setup>
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import DbConnectionPopover from '@/components/DbConnectionPopover.vue'
const route = useRoute()
// 目前只有一个菜单项,后续按需扩展
const menuItems = [
{
path: '/id-card',
name: 'IdCardTool',
title: '身份证治理工具',
icon: 'IdCard',
},
]
const activeMenu = computed(() => route.path)
</script>
<style scoped>
.layout-root {
height: 100vh;
}
/* 顶栏 */
.layout-header {
display: flex;
align-items: center;
justify-content: space-between;
background: #ffffff;
border-bottom: 1px solid #e6e8eb;
padding: 0 20px;
height: 56px;
}
.layout-header__brand {
display: flex;
align-items: center;
gap: 8px;
}
.layout-header__logo {
font-size: 22px;
color: #409eff;
}
.layout-header__title {
font-size: 18px;
font-weight: 600;
color: #303133;
}
.layout-header__right {
display: flex;
align-items: center;
gap: 12px;
}
.layout-header__user {
font-size: 13px;
color: #909399;
}
/* 侧边栏 */
.layout-aside {
background: #001529;
}
.layout-menu {
border-right: none;
height: 100%;
}
/* 内容区 */
.layout-main {
background: #f5f7fa;
padding: 20px;
}
</style>
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'
import { useDbConnection } from '@/stores/useDbConnection'
const app = createApp(App)
// 注册所有图标(全局可用)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus, { locale: zhCn })
app.use(router)
app.mount('#app')
// 启动时异步拉取后端 db-defaults(失败时 store 内部自动兜底到本地常量)
useDbConnection().loadPresetsFromApi()
import { createRouter, createWebHashHistory } from 'vue-router'
import MainLayout from '@/layouts/MainLayout.vue'
import IdCardTool from '@/views/IdCardTool.vue'
const routes = [
{
path: '/',
component: MainLayout,
redirect: '/id-card',
children: [
{
path: 'id-card',
name: 'IdCardTool',
component: IdCardTool,
meta: { title: '身份证治理工具', icon: 'IdCard' },
},
],
},
]
const router = createRouter({
history: createWebHashHistory(),
routes,
})
export default router
/**
* 全局数据库连接状态
*
* 数据流:
* 启动 → fetch('/api/db-defaults') → 替换 presets
* ↓ 失败
* 用 DB_PRESETS 常量兜底
*
* 状态:
* currentType 当前选中的 db_type(写到 localStorage)
* presets 三套预设(响应式;API 拉回来后会替换)
* loading 是否正在拉 API
*
* 设计取舍:
* - 不上 Pinia:当前只一个全局 db 状态,reactive 单例够用
* - 密码不进 localStorage:只存 db_type,需要连接时从 presets 实时取
*/
import { reactive, ref, watch, computed } from 'vue'
import { DB_PRESETS as FALLBACK_PRESETS } from '@/config/dbPresets'
const STORAGE_KEY = 'web2.dbConnection.type'
const API_TIMEOUT_MS = 4000
function _loadInitialType() {
try {
const saved = localStorage.getItem(STORAGE_KEY)
if (saved && FALLBACK_PRESETS[saved]) return saved
} catch (_) {
/* localStorage 不可用 */
}
return 'mysql'
}
const state = reactive({
currentType: _loadInitialType(),
/** 是否已经尝试过拉 API(true 后不再重试,避免启动时反复请求) */
apiLoaded: false,
})
/** 响应式 presets:API 拉回来后会被替换,组件 computed 自动更新 */
const presets = ref({ ...FALLBACK_PRESETS })
const loading = ref(false)
watch(
() => state.currentType,
(v) => {
try {
localStorage.setItem(STORAGE_KEY, v)
} catch (_) {
/* 静默 */
}
},
)
/** 当前选中的完整连接信息(响应式) */
const currentConnection = computed(() => presets.value[state.currentType])
/** 切换数据库类型 */
function setDbType(type) {
if (!presets.value[type]) {
console.warn(`[db] 未知数据库类型: ${type}`)
return
}
state.currentType = type
}
/**
* 从后端拉 db-defaults,覆盖本地 presets。
* 失败/超时 → 保留 FALLBACK_PRESETS 不变,apiLoaded=true 表示「不再重试」。
*/
async function loadPresetsFromApi() {
if (state.apiLoaded) return
loading.value = true
try {
const ctrl = new AbortController()
const timer = setTimeout(() => ctrl.abort(), API_TIMEOUT_MS)
const r = await fetch('/api/db-defaults', { signal: ctrl.signal })
clearTimeout(timer)
if (!r.ok) throw new Error(`HTTP ${r.status}`)
const data = await r.json()
if (data && typeof data === 'object' && Object.keys(data).length > 0) {
presets.value = data
console.log('[db] 已从后端加载 presets:', Object.keys(data))
} else {
console.warn('[db] 后端返回空,使用本地兜底预设')
}
} catch (e) {
console.warn(`[db] 拉取 /api/db-defaults 失败(${e.message}),保留本地兜底预设`)
} finally {
state.apiLoaded = true
loading.value = false
}
}
/**
* 测试当前选中的连接(不切换 currentType,只发请求)
* @returns {Promise<{ok: boolean, message: string}>}
*/
async function testCurrentConnection() {
const conn = currentConnection.value
if (!conn) {
return { ok: false, message: '当前连接不存在' }
}
try {
const r = await fetch('/api/connect/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
// db_type 必须用 state.currentType(已知 db_type 一定有值),
// 不能用 conn.db_type —— 后端 YAML 预设里没有这个字段。
db_type: state.currentType,
host: conn.host,
port: conn.port,
user: conn.user,
password: conn.password,
database: conn.database,
charset: 'utf8mb4',
connect_timeout: 10,
oracle_client_dir: conn.oracle_client_dir || null,
}),
})
const data = await r.json()
return { ok: !!data.ok, message: data.message || (data.ok ? '连接成功' : '连接失败') }
} catch (e) {
return { ok: false, message: `请求失败: ${e.message}` }
}
}
export function useDbConnection() {
return { state, presets, loading, currentConnection, setDbType, loadPresetsFromApi, testCurrentConnection }
}
<template>
<div class="idcard-page">
<el-card class="idcard-page__header" shadow="never">
<h2 class="idcard-page__title">身份证治理工具</h2>
<p class="idcard-page__desc">
占位页面。该工具将在后续迭代中实现:身份证号码识别、校验位计算、地区码归属地解析等。
</p>
</el-card>
<el-empty description="功能开发中……" />
</div>
</template>
<script setup>
// 暂时无业务逻辑
</script>
<style scoped>
.idcard-page {
max-width: 1200px;
margin: 0 auto;
}
.idcard-page__header {
margin-bottom: 20px;
}
.idcard-page__title {
margin: 0 0 8px;
font-size: 20px;
color: #303133;
}
.idcard-page__desc {
margin: 0;
color: #606266;
font-size: 14px;
line-height: 1.6;
}
</style>
@echo off
REM ============================================================
REM web2 - Windows one-click launcher (frontend + backend)
REM (REM lines are ASCII-only to avoid cmd.exe encoding issues)
REM
REM 启动后:
REM - 前端 Vite dev server http://localhost:5174
REM - 后端 FastAPI http://localhost:8766 /api/*
REM
REM 用两个独立 cmd 窗口分别跑前后端,互不干扰。
REM 关闭任一窗口 = 停止对应服务;想全停就关掉本窗口。
REM ============================================================
REM Switch to project root (parent of web2/)
cd /d "%~dp0\.."
echo.
echo ============================================================
echo web2 - Data Governance Tool (frontend + backend)
echo ============================================================
echo.
REM Check Node + npm
where npm >nul 2>&1
if errorlevel 1 (
echo [ERROR] npm not found. Please install Node.js 18+ first.
pause
exit /b 1
)
REM Check Python + fastapi
python --version >nul 2>&1
if errorlevel 1 (
echo [ERROR] Python not found. Please install Python 3.10+ first.
pause
exit /b 1
)
python -c "import fastapi" 2>nul
if errorlevel 1 (
echo [INFO] Installing minimal backend deps...
python -m pip install fastapi uvicorn pydantic pyyaml pymysql
if errorlevel 1 (
echo [ERROR] Failed to install dependencies.
pause
exit /b 1
)
)
REM Check if node_modules exists
if not exist "web2\node_modules" (
echo [INFO] Installing frontend deps...
cd web2 && npm install && cd ..
if errorlevel 1 (
echo [ERROR] npm install failed.
pause
exit /b 1
)
)
echo Starting backend in new window...
REM /k = keep window open after command ends. Title in quotes.
start "web2-backend" cmd /k "cd /d %CD%\web2\backend && python start.py"
echo Starting frontend in new window...
start "web2-frontend" cmd /k "cd /d %CD%\web2 && npm run dev"
echo.
echo ============================================================
echo Both services starting. Two new windows opened:
echo [web2-backend] http://localhost:8766 /api/*
echo [web2-frontend] http://localhost:5174
echo.
echo Open http://localhost:5174 in your browser.
echo.
echo Close this window or press any key to dismiss.
echo (Backend / frontend keep running in their own windows.)
echo ============================================================
echo.
pause
#!/usr/bin/env bash
# ============================================================
# web2 - macOS/Linux one-click launcher (frontend + backend)
# ============================================================
# 启动后:
# - 前端 Vite dev server http://localhost:5174
# - 后端 FastAPI http://localhost:8766 /api/*
# 用 trap + kill 在 Ctrl+C 时同时停掉两个。
# ============================================================
set -e
cd "$(dirname "$0")/.."
echo "============================================================"
echo " web2 - Data Governance Tool (frontend + backend)"
echo "============================================================"
# 检查 node
if ! command -v npm >/dev/null 2>&1; then
echo "[ERROR] npm not found. Please install Node.js 18+ first."
exit 1
fi
# 检查 python + fastapi
if ! command -v python >/dev/null 2>&1; then
echo "[ERROR] python not found. Please install Python 3.10+ first."
exit 1
fi
if ! python -c "import fastapi" 2>/dev/null; then
echo "[INFO] Installing minimal backend deps..."
pip install fastapi uvicorn pydantic pyyaml pymysql
fi
# 检查 node_modules
if [ ! -d "web2/node_modules" ]; then
echo "[INFO] Installing frontend deps..."
(cd web2 && npm install)
fi
echo "Starting backend..."
python web2/backend/start.py &
BACKEND_PID=$!
# 等后端起来再启动前端(避免前端启动时拉 /api 失败)
sleep 2
echo "Starting frontend..."
(cd web2 && npm run dev) &
FRONTEND_PID=$!
cleanup() {
echo ""
echo "Stopping..."
kill $BACKEND_PID 2>/dev/null || true
kill $FRONTEND_PID 2>/dev/null || true
exit 0
}
trap cleanup INT TERM
echo ""
echo "============================================================"
echo " Both services running."
echo " Backend: http://localhost:8766"
echo " Frontend: http://localhost:5174"
echo ""
echo " Press Ctrl+C to stop both."
echo "============================================================"
wait
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
server: {
port: 5174,
open: false,
// /api/* 反向代理到 web2 后端(FastAPI @ 8766),避免浏览器跨域
// 后端启动:python web2/backend/start.py
proxy: {
'/api': {
target: 'http://localhost:8766',
changeOrigin: true,
},
},
},
})
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment