commit 1d5c97904ff57deb148f76ae8ec887ae8ef79ad6 Author: dekun Date: Thu May 28 21:43:23 2026 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..deeef8e --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Python +backend/venv/ +__pycache__/ +*.py[cod] +*.egg-info/ +.pytest_cache/ + +# Node +frontend/node_modules/ +frontend/dist/ + +# 数据库(生产环境数据保留在服务器,不提交) +backend/data/*.db + +# IDE / OS +.vscode/ +.idea/ +*.swp +.DS_Store +Thumbs.db + +# PM2 +.pm2/ +logs/ diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..42521b9 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,193 @@ +# 部署文档 + +> 代码仓库:[https://git.bz121.com/dekun/crypto-pre-trade-system.git](https://git.bz121.com/dekun/crypto-pre-trade-system.git) + +本文档说明如何在 **Ubuntu 服务器**上使用 **PM2** 守护进程部署本系统。 + +--- + +## 部署环境要求 + +| 项目 | 要求 | +|------|------| +| 操作系统 | Ubuntu 20.04 / 22.04 / 24.04 | +| 运行用户 | root | +| 安装路径 | `/opt/crypto-pre-trade-system` | +| 进程守护 | PM2 | +| Python | 3.10+(虚拟环境) | +| Node.js | 18+(仅构建前端时使用) | +| 访问端口 | **1125** | + +--- + +## 一键部署(推荐) + +以 **root** 用户登录服务器,执行: + +```bash +# 首次部署:克隆仓库并运行安装脚本 +git clone https://git.bz121.com/dekun/crypto-pre-trade-system.git /opt/crypto-pre-trade-system +cd /opt/crypto-pre-trade-system +bash deploy/install.sh +``` + +脚本会自动完成: + +1. 安装系统依赖(git、python3、nodejs、pm2) +2. 创建 Python 虚拟环境并安装后端依赖 +3. 构建前端静态资源 +4. 通过 PM2 启动服务并设置开机自启 +5. 执行健康检查 + +部署完成后访问:**http://\<服务器IP\>:1125** + +--- + +## 更新部署 + +代码有更新时,在服务器上重新执行安装脚本即可: + +```bash +cd /opt/crypto-pre-trade-system +bash deploy/install.sh +``` + +脚本会自动 `git pull`、重新构建前端、重启 PM2 进程。 + +--- + +## 手动部署步骤 + +若需手动逐步部署,按以下步骤操作: + +### 1. 克隆仓库 + +```bash +git clone https://git.bz121.com/dekun/crypto-pre-trade-system.git /opt/crypto-pre-trade-system +cd /opt/crypto-pre-trade-system +``` + +### 2. 安装系统依赖 + +```bash +apt-get update +apt-get install -y git python3 python3-venv python3-pip curl + +# Node.js 20 +curl -fsSL https://deb.nodesource.com/setup_20.x | bash - +apt-get install -y nodejs + +# PM2 +npm install -g pm2 +``` + +### 3. 后端虚拟环境 + +```bash +cd /opt/crypto-pre-trade-system/backend +python3 -m venv venv +venv/bin/pip install -r requirements.txt +mkdir -p data +``` + +### 4. 构建前端 + +```bash +cd /opt/crypto-pre-trade-system/frontend +npm install +npm run build +``` + +### 5. PM2 启动 + +```bash +cd /opt/crypto-pre-trade-system +mkdir -p logs +pm2 start ecosystem.config.cjs +pm2 save +pm2 startup systemd -u root --hp /root +``` + +--- + +## PM2 常用命令 + +```bash +pm2 status # 查看进程状态 +pm2 logs crypto-pre-trade # 查看实时日志 +pm2 restart crypto-pre-trade # 重启服务 +pm2 stop crypto-pre-trade # 停止服务 +pm2 delete crypto-pre-trade # 删除进程 +``` + +日志文件位置: + +- 标准输出:`/opt/crypto-pre-trade-system/logs/pm2-out.log` +- 错误输出:`/opt/crypto-pre-trade-system/logs/pm2-error.log` + +--- + +## 架构说明 + +生产环境采用 **单进程单端口** 模式: + +``` +PM2 → uvicorn (0.0.0.0:1125) + ├── /api/* → FastAPI 后端接口 + └── /* → Vue3 前端静态资源(frontend/dist) +``` + +- 前端构建产物由 FastAPI 直接托管,无需额外 Nginx +- SQLite 数据库文件:`/opt/crypto-pre-trade-system/backend/data/pretrade.db` +- 重启服务不丢失数据 + +--- + +## 防火墙 + +若服务器开启了防火墙,需放行 1125 端口: + +```bash +# ufw +ufw allow 1125/tcp + +# firewalld +firewall-cmd --permanent --add-port=1125/tcp +firewall-cmd --reload +``` + +--- + +## 重置数据库 + +```bash +pm2 stop crypto-pre-trade +rm /opt/crypto-pre-trade-system/backend/data/pretrade.db +pm2 start crypto-pre-trade +``` + +重启后系统自动重建表结构并写入默认数据。 + +--- + +## 故障排查 + +| 现象 | 排查方式 | +|------|---------| +| 无法访问 | `pm2 status` 确认进程 online;`curl http://127.0.0.1:1125/api/health` | +| 502 / 连接拒绝 | 检查防火墙是否放行 1125 端口 | +| 前端白屏 | 确认 `frontend/dist` 存在;重新 `npm run build` | +| API 报错 | `pm2 logs crypto-pre-trade --lines 50` | +| 数据库问题 | 检查 `backend/data/` 目录权限 | + +--- + +## 本地开发 vs 生产部署 + +| | 本地开发 | 生产部署 | +|---|---------|---------| +| 后端 | `uvicorn --reload --port 8000` | PM2 + uvicorn `:1125` | +| 前端 | `npm run dev :1125`(代理到 8000) | `npm run build`,由 FastAPI 托管 | +| 访问 | http://localhost:1125 | http://\<服务器IP\>:1125 | + +本地开发说明见 [README.md](./README.md)。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..4fe4f7e --- /dev/null +++ b/README.md @@ -0,0 +1,195 @@ +# 加密货币大盘-周期-阶段-强弱-方向-账户-策略前置匹配系统 + +纯本地部署的前置策略匹配工具。前后端分离,SQLite 持久化,黑色极简主题,无登录。 + +> **代码仓库**:[https://git.bz121.com/dekun/crypto-pre-trade-system.git](https://git.bz121.com/dekun/crypto-pre-trade-system.git) +> **生产部署**:见 [DEPLOY.md](./DEPLOY.md)(Ubuntu + PM2 一键部署) + +> **本系统仅做前置策略匹配**,不处理币种、K线识别、箱体判断、点位计算、突破校验、自动下单或交易所对接。 +> 所有箱体细节、触碰次数、突破确认条件、顺势/逆势箱体,全部由人工手动筛选输入。 + +--- + +## 技术栈 + +| 层级 | 技术 | +|------|------| +| 后端 | Python 3.10+ / FastAPI / SQLAlchemy / SQLite | +| 前端 | Vue 3 / Vite / TailwindCSS / Axios | +| 数据库 | SQLite(`backend/data/pretrade.db`) | + +--- + +## 项目结构 + +``` +crypto-pre-trade-system/ +├── backend/ +│ ├── app/ +│ │ ├── main.py # FastAPI 入口 +│ │ ├── database.py # 数据库连接 + 默认数据初始化 +│ │ ├── models.py # ORM 模型 +│ │ ├── schemas.py # Pydantic 模型 +│ │ ├── crud.py # CRUD 操作 +│ │ ├── matcher.py # 前置匹配核心逻辑 +│ │ └── routes/ # API 路由 +│ ├── init_db.sql # 建表 SQL(参考) +│ ├── requirements.txt +│ └── data/ # SQLite 数据库(自动创建) +├── frontend/ +│ ├── src/ +│ │ ├── views/ +│ │ │ ├── DailyMatch.vue # 日常使用页(核心) +│ │ │ └── ConfigCenter.vue # 系统配置中心 +│ │ ├── api/index.js # API 封装 +│ │ └── ... +│ └── package.json +├── deploy/ +│ └── install.sh # Ubuntu 一键部署脚本 +├── ecosystem.config.cjs # PM2 进程配置 +├── DEPLOY.md # 部署文档 +└── README.md +``` + +--- + +## 快速启动 + +### 1. 启动后端 + +```bash +cd backend + +# 创建虚拟环境(推荐) +python -m venv venv +# Windows: +venv\Scripts\activate +# macOS/Linux: +# source venv/bin/activate + +pip install -r requirements.txt + +# 启动 API 服务(首次启动自动建表 + 写入默认数据) +uvicorn app.main:app --reload --host 127.0.0.1 --port 8000 +``` + +后端启动后访问: +- API 文档:http://127.0.0.1:8000/docs +- 健康检查:http://127.0.0.1:8000/api/health + +### 2. 启动前端 + +```bash +cd frontend + +npm install +npm run dev +``` + +前端访问:http://localhost:1125 + +--- + +## 使用流程 + +### 日常使用(核心页面) + +1. **人工选择**大盘周期(日线 / 4H / 1H) +2. **人工选择**大盘阶段(8 个固定阶段) +3. **人工选择**趋势强弱(强 / 弱 / 震荡) +4. 点击「执行前置匹配」 +5. 系统自动输出: + - 交易类型(顺势 / 反转 / 观望) + - 允许开仓方向 + - 可用账户列表 + - 可用策略列表(含规则文本,仅展示) + +### 系统配置中心 + +- **大盘管理**:增删改 8 个大盘阶段及交易方向 +- **账户管理**:动态管理账户(本金、周期、风险比) +- **策略管理**:增删改策略及规则文本 +- **匹配配置**:绑定「大盘周期 + 阶段 + 强弱」→ 账户 + 策略 + 方向 + +--- + +## 匹配逻辑(默认规则) + +| 大盘阶段 | 趋势强弱 | 匹配策略 | 方向 | +|---------|---------|---------|------| +| 上涨初期/中期 | 强 | 箱体顺势突破 | 做多 | +| 上涨初期/中期 | 弱 | 斐波回调 | 做多 | +| 上涨末期 | 强/弱 | 手工主观 | 做空 | +| 下跌初期/中期 | 强 | 箱体顺势突破 | 做空 | +| 下跌初期/中期 | 弱 | 斐波回调 | 做空 | +| 下跌末期 | 强/弱 | 手工主观 | 做多 | +| 宽幅震荡末期 | 强/弱 | 收敛结构突破 | 多空均可 | +| 宽幅震荡 | 任意 | **全部禁用** | 禁止 | +| 任意阶段 | 震荡 | **全部禁用** | 观望 | + +--- + +## API 接口 + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `/api/regimes` | 获取大盘阶段列表 | +| POST/PUT/DELETE | `/api/regimes` | 增删改大盘阶段 | +| GET | `/api/accounts` | 获取账户列表 | +| POST/PUT/DELETE | `/api/accounts` | 增删改账户 | +| GET | `/api/strategies` | 获取策略列表 | +| POST/PUT/DELETE | `/api/strategies` | 增删改策略 | +| GET | `/api/matches` | 获取匹配配置 | +| POST/PUT/DELETE | `/api/matches` | 增删改匹配配置 | +| GET | `/api/match` | **前置匹配**(核心接口) | + +匹配接口参数: +``` +GET /api/match?market_cycle=日线&market_regime_id=1&trend_strength=强 +``` + +--- + +## 数据库表 + +| 表名 | 说明 | +|------|------| +| `market_regime` | 大盘阶段(名称、交易类型、允许方向) | +| `account` | 账户(名称、本金、周期、风险比、启用状态) | +| `strategy` | 策略(名称、适用周期/强弱、规则文本) | +| `regime_match` | 匹配绑定(阶段+周期+强弱 → 账户+策略+方向) | + +建表 SQL 见 `backend/init_db.sql`,也可通过后端启动自动创建。 + +--- + +## 约束说明 + +1. **黑色极简** UI,左右分栏布局 +2. **SQLite 持久化**,重启不丢数据 +3. **无登录**,纯本地部署 +4. **账户/策略/匹配规则** 全部可动态增删改 +5. **严格禁止**:自动 K 线识别、自动箱体判断、自动下单、交易所对接、币种输入、点位计算、突破条件校验 +6. 策略规则文本**仅作展示**,系统不做任何自动校验 + +--- + +## 重置数据库 + +删除 `backend/data/pretrade.db` 后重启后端,系统将自动重建并写入默认数据。 + +--- + +## 生产部署 + +服务器部署(Ubuntu + PM2 + root + `/opt`)详见 **[DEPLOY.md](./DEPLOY.md)**。 + +一键部署: + +```bash +git clone https://git.bz121.com/dekun/crypto-pre-trade-system.git /opt/crypto-pre-trade-system +cd /opt/crypto-pre-trade-system +bash deploy/install.sh +``` + +部署完成后访问:**http://\<服务器IP\>:1125** diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/crud.py b/backend/app/crud.py new file mode 100644 index 0000000..d4a1ca2 --- /dev/null +++ b/backend/app/crud.py @@ -0,0 +1,173 @@ +"""数据库 CRUD 操作""" + +from typing import List, Optional +from sqlalchemy.orm import Session, joinedload + +from app.models import MarketRegime, Account, Strategy, RegimeMatch +from app.schemas import ( + MarketRegimeCreate, MarketRegimeUpdate, + AccountCreate, AccountUpdate, + StrategyCreate, StrategyUpdate, + RegimeMatchCreate, RegimeMatchUpdate, +) + + +# ── 大盘阶段 ── + +def get_regimes(db: Session) -> List[MarketRegime]: + return db.query(MarketRegime).order_by(MarketRegime.id).all() + + +def get_regime(db: Session, regime_id: int) -> Optional[MarketRegime]: + return db.query(MarketRegime).filter(MarketRegime.id == regime_id).first() + + +def create_regime(db: Session, data: MarketRegimeCreate) -> MarketRegime: + obj = MarketRegime(**data.model_dump()) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_regime(db: Session, regime_id: int, data: MarketRegimeUpdate) -> Optional[MarketRegime]: + obj = get_regime(db, regime_id) + if not obj: + return None + for k, v in data.model_dump(exclude_unset=True).items(): + setattr(obj, k, v) + db.commit() + db.refresh(obj) + return obj + + +def delete_regime(db: Session, regime_id: int) -> bool: + obj = get_regime(db, regime_id) + if not obj: + return False + db.delete(obj) + db.commit() + return True + + +# ── 账户 ── + +def get_accounts(db: Session) -> List[Account]: + return db.query(Account).order_by(Account.id).all() + + +def get_account(db: Session, account_id: int) -> Optional[Account]: + return db.query(Account).filter(Account.id == account_id).first() + + +def create_account(db: Session, data: AccountCreate) -> Account: + obj = Account(**data.model_dump()) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_account(db: Session, account_id: int, data: AccountUpdate) -> Optional[Account]: + obj = get_account(db, account_id) + if not obj: + return None + for k, v in data.model_dump(exclude_unset=True).items(): + setattr(obj, k, v) + db.commit() + db.refresh(obj) + return obj + + +def delete_account(db: Session, account_id: int) -> bool: + obj = get_account(db, account_id) + if not obj: + return False + db.delete(obj) + db.commit() + return True + + +# ── 策略 ── + +def get_strategies(db: Session) -> List[Strategy]: + return db.query(Strategy).order_by(Strategy.id).all() + + +def get_strategy(db: Session, strategy_id: int) -> Optional[Strategy]: + return db.query(Strategy).filter(Strategy.id == strategy_id).first() + + +def create_strategy(db: Session, data: StrategyCreate) -> Strategy: + obj = Strategy(**data.model_dump()) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_strategy(db: Session, strategy_id: int, data: StrategyUpdate) -> Optional[Strategy]: + obj = get_strategy(db, strategy_id) + if not obj: + return None + for k, v in data.model_dump(exclude_unset=True).items(): + setattr(obj, k, v) + db.commit() + db.refresh(obj) + return obj + + +def delete_strategy(db: Session, strategy_id: int) -> bool: + obj = get_strategy(db, strategy_id) + if not obj: + return False + db.delete(obj) + db.commit() + return True + + +# ── 匹配绑定 ── + +def get_matches(db: Session) -> List[RegimeMatch]: + return ( + db.query(RegimeMatch) + .options( + joinedload(RegimeMatch.regime), + joinedload(RegimeMatch.account), + joinedload(RegimeMatch.strategy), + ) + .order_by(RegimeMatch.id) + .all() + ) + + +def get_match(db: Session, match_id: int) -> Optional[RegimeMatch]: + return db.query(RegimeMatch).filter(RegimeMatch.id == match_id).first() + + +def create_match(db: Session, data: RegimeMatchCreate) -> RegimeMatch: + obj = RegimeMatch(**data.model_dump()) + db.add(obj) + db.commit() + db.refresh(obj) + return obj + + +def update_match(db: Session, match_id: int, data: RegimeMatchUpdate) -> Optional[RegimeMatch]: + obj = get_match(db, match_id) + if not obj: + return None + for k, v in data.model_dump(exclude_unset=True).items(): + setattr(obj, k, v) + db.commit() + db.refresh(obj) + return obj + + +def delete_match(db: Session, match_id: int) -> bool: + obj = get_match(db, match_id) + if not obj: + return False + db.delete(obj) + db.commit() + return True diff --git a/backend/app/database.py b/backend/app/database.py new file mode 100644 index 0000000..efa4b02 --- /dev/null +++ b/backend/app/database.py @@ -0,0 +1,175 @@ +"""数据库连接与初始化""" + +import os +from pathlib import Path + +from sqlalchemy import create_engine, text +from sqlalchemy.orm import sessionmaker, declarative_base + +# 数据库文件路径 +BASE_DIR = Path(__file__).resolve().parent.parent +DB_PATH = BASE_DIR / "data" / "pretrade.db" +DB_PATH.parent.mkdir(parents=True, exist_ok=True) + +DATABASE_URL = f"sqlite:///{DB_PATH}" + +engine = create_engine( + DATABASE_URL, + connect_args={"check_same_thread": False}, +) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() + + +def get_db(): + """FastAPI 依赖:获取数据库会话""" + db = SessionLocal() + try: + yield db + finally: + db.close() + + +def init_database(): + """执行建表 SQL 并写入默认数据""" + from app.models import MarketRegime, Account, Strategy, RegimeMatch # noqa: F401 + + # 建表 + Base.metadata.create_all(bind=engine) + + # 若已有数据则跳过初始化 + db = SessionLocal() + try: + if db.query(MarketRegime).count() > 0: + return + + _seed_default_data(db) + finally: + db.close() + + +def _seed_default_data(db): + """写入默认大盘阶段、账户、策略及匹配规则""" + from app.models import MarketRegime, Account, Strategy, RegimeMatch + + # ── 大盘阶段(8 个固定) ── + regimes = [ + MarketRegime(name="上涨初期", trade_type="顺势", allow_direction="做多", remark="顺势做多,禁止做空"), + MarketRegime(name="上涨中期", trade_type="顺势", allow_direction="做多", remark="顺势做多,禁止做空"), + MarketRegime(name="上涨末期", trade_type="反转", allow_direction="做空", remark="反转做空"), + MarketRegime(name="宽幅震荡", trade_type="观望", allow_direction="禁止", remark="观望,禁止交易"), + MarketRegime(name="宽幅震荡末期", trade_type="反转", allow_direction="多空均可", remark="反转交易(收敛突破)"), + MarketRegime(name="下跌初期", trade_type="顺势", allow_direction="做空", remark="顺势做空,禁止做多"), + MarketRegime(name="下跌中期", trade_type="顺势", allow_direction="做空", remark="顺势做空,禁止做多"), + MarketRegime(name="下跌末期", trade_type="反转", allow_direction="做多", remark="反转做多"), + ] + db.add_all(regimes) + db.flush() + + regime_map = {r.name: r.id for r in regimes} + + # ── 账户(默认本金 100U) ── + accounts = [ + Account(account_name="账户1-斐波回调", total_capital=100, trade_cycle="4H/1H", risk_ratio="5%~10%", remark="斐波回调专用"), + Account(account_name="账户2-箱体突破", total_capital=100, trade_cycle="日内", risk_ratio="0.5%~1%", remark="箱体顺势突破专用"), + Account(account_name="账户3-收敛突破", total_capital=100, trade_cycle="日内", risk_ratio="0.5%~1%", remark="收敛结构突破专用"), + Account(account_name="账户4-手工主观", total_capital=100, trade_cycle="灵活", risk_ratio="2%", remark="手工主观策略专用"), + ] + db.add_all(accounts) + db.flush() + + acc_map = {a.account_name: a.id for a in accounts} + + # ── 策略(4 个内置) ── + strategies = [ + Strategy( + strategy_name="斐波回调", + fit_cycle="4H/1H", + fit_trend_strength="弱", + trade_type="顺势", + strategy_rule=( + "入场:仅 0.618 / 0.786\n" + "适用:通道式上涨/下跌\n" + "适用:弱趋势\n" + "周期:4H/1H\n" + "注意:点位、触碰次数、突破条件均由人工手动筛选输入" + ), + ), + Strategy( + strategy_name="箱体顺势突破", + fit_cycle="日内(5分钟K线)", + fit_trend_strength="强", + trade_type="顺势", + strategy_rule=( + "箱体时长要求:≥4小时(48根5分钟K线),优先8小时以上\n" + "成立条件:人工手动判断触碰上下沿次数、顺势/逆势箱体、突破确认条件\n" + "系统不校验,仅展示该策略可用\n" + "适用:强趋势顺势阶段" + ), + ), + Strategy( + strategy_name="收敛结构突破", + fit_cycle="日内", + fit_trend_strength="强", + trade_type="反转", + strategy_rule=( + "适用:宽幅震荡末期收敛三角\n" + "人工确认结构,系统仅匹配可用\n" + "注意:结构细节、突破条件均由人工手动确认" + ), + ), + Strategy( + strategy_name="手工主观", + fit_cycle="灵活", + fit_trend_strength="全部", + trade_type="全部", + strategy_rule="全场景人工自主判断,系统仅做前置匹配", + ), + ] + db.add_all(strategies) + db.flush() + + strat_map = {s.strategy_name: s.id for s in strategies} + + # ── 匹配绑定规则 ── + cycles = ["日线", "4H", "1H"] + matches = [] + + # 顺势阶段(上涨初/中期、下跌初/中期):强→箱体,弱→斐波 + shunshi_regimes = ["上涨初期", "上涨中期", "下跌初期", "下跌中期"] + for rname in shunshi_regimes: + rid = regime_map[rname] + for cycle in cycles: + matches.append(RegimeMatch( + market_regime_id=rid, market_cycle=cycle, trend_strength="强", + account_id=acc_map["账户2-箱体突破"], strategy_id=strat_map["箱体顺势突破"], + )) + matches.append(RegimeMatch( + market_regime_id=rid, market_cycle=cycle, trend_strength="弱", + account_id=acc_map["账户1-斐波回调"], strategy_id=strat_map["斐波回调"], + )) + + # 反转阶段(上涨末期、下跌末期):手工主观 + fanzhuan_regimes = ["上涨末期", "下跌末期"] + for rname in fanzhuan_regimes: + rid = regime_map[rname] + for cycle in cycles: + for strength in ["强", "弱"]: + matches.append(RegimeMatch( + market_regime_id=rid, market_cycle=cycle, trend_strength=strength, + account_id=acc_map["账户4-手工主观"], strategy_id=strat_map["手工主观"], + )) + + # 宽幅震荡末期:收敛突破 + rid = regime_map["宽幅震荡末期"] + for cycle in cycles: + for strength in ["强", "弱"]: + matches.append(RegimeMatch( + market_regime_id=rid, market_cycle=cycle, trend_strength=strength, + account_id=acc_map["账户3-收敛突破"], strategy_id=strat_map["收敛结构突破"], + )) + + # 宽幅震荡:不写入匹配(系统自动禁用) + + db.add_all(matches) + db.commit() diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..ee3da35 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,74 @@ +"""FastAPI 应用入口""" + +from pathlib import Path + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import FileResponse +from fastapi.staticfiles import StaticFiles + +from app.database import init_database +from app.routes import regimes, accounts, strategies, matches, match + +# 前端构建产物目录(生产部署时由 PM2 单端口托管) +FRONTEND_DIST = Path(__file__).resolve().parent.parent.parent / "frontend" / "dist" + +app = FastAPI( + title="加密货币前置匹配系统", + description="大盘-周期-阶段-强弱-方向-账户-策略前置匹配(纯本地,无登录)", + version="1.0.0", +) + +# 允许前端跨域(本地开发) +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# 注册 API 路由 +app.include_router(regimes.router) +app.include_router(accounts.router) +app.include_router(strategies.router) +app.include_router(matches.router) +app.include_router(match.router) + + +@app.on_event("startup") +def on_startup(): + """启动时初始化数据库""" + init_database() + + +@app.get("/api/health") +def health(): + return {"status": "ok", "message": "加密货币前置匹配系统运行中"} + + +def _mount_frontend(): + """生产环境:托管 Vue 前端静态资源,支持 SPA 路由""" + if not FRONTEND_DIST.exists(): + return + + assets_dir = FRONTEND_DIST / "assets" + if assets_dir.exists(): + app.mount("/assets", StaticFiles(directory=assets_dir), name="assets") + + @app.get("/") + async def serve_index(): + return FileResponse(FRONTEND_DIST / "index.html") + + @app.get("/{path:path}") + async def serve_spa(path: str): + # API 路径不走 SPA 回退 + if path.startswith("api"): + return {"detail": "Not Found"} + file = FRONTEND_DIST / path + if file.is_file(): + return FileResponse(file) + return FileResponse(FRONTEND_DIST / "index.html") + + +_mount_frontend() diff --git a/backend/app/matcher.py b/backend/app/matcher.py new file mode 100644 index 0000000..c5a4875 --- /dev/null +++ b/backend/app/matcher.py @@ -0,0 +1,138 @@ +"""前置策略匹配核心逻辑 + +本模块仅根据人工选择的大盘周期、阶段、趋势强弱, +查询匹配绑定表并输出可用账户与策略。 +不做任何 K 线识别、箱体判断、点位计算或突破校验。 +""" + +from sqlalchemy.orm import Session, joinedload + +from app.models import MarketRegime, RegimeMatch +from app.schemas import MatchRequest, MatchResult, MatchAccountOut, MatchStrategyOut + + +def run_match(db: Session, req: MatchRequest) -> MatchResult: + """执行前置匹配""" + regime = db.query(MarketRegime).filter(MarketRegime.id == req.market_regime_id).first() + if not regime: + return MatchResult( + market_cycle=req.market_cycle, + regime_name="未知", + trade_type="", + allow_direction="", + trend_strength=req.trend_strength, + status="disabled", + message="大盘阶段不存在", + ) + + base = dict( + market_cycle=req.market_cycle, + regime_name=regime.name, + trade_type=regime.trade_type, + allow_direction=regime.allow_direction, + trend_strength=req.trend_strength, + ) + + # 震荡 → 全部禁用 + if req.trend_strength == "震荡": + return MatchResult( + **base, + status="watch", + message="趋势强弱为「震荡」,全部策略禁用,建议观望", + ) + + # 宽幅震荡阶段 → 观望 + if regime.trade_type == "观望" or regime.allow_direction == "禁止": + return MatchResult( + **base, + status="watch", + message=f"大盘阶段「{regime.name}」为观望阶段,禁止交易", + ) + + # 查询匹配绑定 + matches = ( + db.query(RegimeMatch) + .options( + joinedload(RegimeMatch.account), + joinedload(RegimeMatch.strategy), + ) + .filter( + RegimeMatch.market_regime_id == req.market_regime_id, + RegimeMatch.market_cycle == req.market_cycle, + RegimeMatch.trend_strength == req.trend_strength, + ) + .all() + ) + + if not matches: + return MatchResult( + **base, + status="disabled", + message="未找到匹配的账户/策略绑定,请在配置中心添加匹配规则", + ) + + # 过滤:仅保留已启用账户 + accounts_out = [] + strategies_out = [] + seen_acc = set() + seen_strat = set() + + for m in matches: + if not m.account or m.account.enable != 1: + continue + + # 趋势强弱过滤(双重保险) + strat = m.strategy + if not strat: + continue + if not _strength_compatible(req.trend_strength, strat.fit_trend_strength): + continue + + direction = m.force_direction or regime.allow_direction + + if m.account_id not in seen_acc: + seen_acc.add(m.account_id) + accounts_out.append(MatchAccountOut( + id=m.account.id, + account_name=m.account.account_name, + total_capital=m.account.total_capital, + trade_cycle=m.account.trade_cycle, + risk_ratio=m.account.risk_ratio, + force_direction=direction, + )) + + if m.strategy_id not in seen_strat: + seen_strat.add(m.strategy_id) + strategies_out.append(MatchStrategyOut( + id=strat.id, + strategy_name=strat.strategy_name, + fit_cycle=strat.fit_cycle, + strategy_rule=strat.strategy_rule, + force_direction=direction, + )) + + if not accounts_out: + return MatchResult( + **base, + status="disabled", + message="无可用账户(可能全部被禁用或不匹配当前趋势强弱)", + ) + + return MatchResult( + **base, + status="ok", + message="匹配成功,以下为前置策略匹配结果(箱体/点位/突破条件需人工确认)", + accounts=accounts_out, + strategies=strategies_out, + ) + + +def _strength_compatible(selected: str, fit: str) -> bool: + """判断趋势强弱是否与策略适配""" + if fit == "全部": + return True + if selected == "强" and fit == "强": + return True + if selected == "弱" and fit == "弱": + return True + return False diff --git a/backend/app/models.py b/backend/app/models.py new file mode 100644 index 0000000..b728c9b --- /dev/null +++ b/backend/app/models.py @@ -0,0 +1,66 @@ +"""SQLAlchemy ORM 模型""" + +from sqlalchemy import Column, Integer, String, Float, ForeignKey, Text +from sqlalchemy.orm import relationship + +from app.database import Base + + +class MarketRegime(Base): + """大盘阶段""" + __tablename__ = "market_regime" + + id = Column(Integer, primary_key=True, autoincrement=True) + name = Column(String, nullable=False, unique=True) + trade_type = Column(String, nullable=False) # 顺势 / 反转 / 观望 + allow_direction = Column(String, nullable=False) # 做多 / 做空 / 禁止 / 多空均可 + remark = Column(Text, default="") + + matches = relationship("RegimeMatch", back_populates="regime", cascade="all, delete-orphan") + + +class Account(Base): + """交易账户""" + __tablename__ = "account" + + id = Column(Integer, primary_key=True, autoincrement=True) + account_name = Column(String, nullable=False) + total_capital = Column(Float, nullable=False, default=100) + trade_cycle = Column(String, nullable=False) + risk_ratio = Column(String, nullable=False) + enable = Column(Integer, nullable=False, default=1) + remark = Column(Text, default="") + + matches = relationship("RegimeMatch", back_populates="account", cascade="all, delete-orphan") + + +class Strategy(Base): + """交易策略(规则文本仅展示,系统不做校验)""" + __tablename__ = "strategy" + + id = Column(Integer, primary_key=True, autoincrement=True) + strategy_name = Column(String, nullable=False) + fit_cycle = Column(String, nullable=False) + fit_trend_strength = Column(String, nullable=False) # 强 / 弱 / 全部 + trade_type = Column(String, nullable=False) # 顺势 / 反转 / 全部 + strategy_rule = Column(Text, nullable=False) + remark = Column(Text, default="") + + matches = relationship("RegimeMatch", back_populates="strategy", cascade="all, delete-orphan") + + +class RegimeMatch(Base): + """匹配绑定:大盘周期+阶段+强弱 → 账户+策略+方向""" + __tablename__ = "regime_match" + + id = Column(Integer, primary_key=True, autoincrement=True) + market_regime_id = Column(Integer, ForeignKey("market_regime.id", ondelete="CASCADE"), nullable=False) + market_cycle = Column(String, nullable=False) # 日线 / 4H / 1H + trend_strength = Column(String, nullable=False) # 强 / 弱 / 震荡 + account_id = Column(Integer, ForeignKey("account.id", ondelete="CASCADE"), nullable=False) + strategy_id = Column(Integer, ForeignKey("strategy.id", ondelete="CASCADE"), nullable=False) + force_direction = Column(String, default="") # 做多 / 做空 / 空=跟随大盘 + + regime = relationship("MarketRegime", back_populates="matches") + account = relationship("Account", back_populates="matches") + strategy = relationship("Strategy", back_populates="matches") diff --git a/backend/app/routes/__init__.py b/backend/app/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/app/routes/accounts.py b/backend/app/routes/accounts.py new file mode 100644 index 0000000..e73ff2e --- /dev/null +++ b/backend/app/routes/accounts.py @@ -0,0 +1,35 @@ +"""API 路由:账户""" + +from typing import List +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from app.database import get_db +from app import crud, schemas + +router = APIRouter(prefix="/api/accounts", tags=["账户"]) + + +@router.get("", response_model=List[schemas.AccountOut]) +def list_accounts(db: Session = Depends(get_db)): + return crud.get_accounts(db) + + +@router.post("", response_model=schemas.AccountOut, status_code=201) +def create_account(data: schemas.AccountCreate, db: Session = Depends(get_db)): + return crud.create_account(db, data) + + +@router.put("/{account_id}", response_model=schemas.AccountOut) +def update_account(account_id: int, data: schemas.AccountUpdate, db: Session = Depends(get_db)): + obj = crud.update_account(db, account_id, data) + if not obj: + raise HTTPException(404, "账户不存在") + return obj + + +@router.delete("/{account_id}") +def delete_account(account_id: int, db: Session = Depends(get_db)): + if not crud.delete_account(db, account_id): + raise HTTPException(404, "账户不存在") + return {"ok": True} diff --git a/backend/app/routes/match.py b/backend/app/routes/match.py new file mode 100644 index 0000000..b5766d3 --- /dev/null +++ b/backend/app/routes/match.py @@ -0,0 +1,31 @@ +"""API 路由:日常使用 - 前置匹配""" + +from fastapi import APIRouter, Depends, Query +from sqlalchemy.orm import Session + +from app.database import get_db +from app import schemas +from app.matcher import run_match + +router = APIRouter(prefix="/api/match", tags=["前置匹配"]) + + +@router.get("", response_model=schemas.MatchResult) +def do_match( + market_cycle: str = Query(..., description="大盘周期:日线/4H/1H"), + market_regime_id: int = Query(..., description="大盘阶段 ID"), + trend_strength: str = Query(..., description="趋势强弱:强/弱/震荡"), + db: Session = Depends(get_db), +): + """根据人工选择的三项参数,输出前置匹配结果""" + req = schemas.MatchRequest( + market_cycle=market_cycle, + market_regime_id=market_regime_id, + trend_strength=trend_strength, + ) + return run_match(db, req) + + +@router.post("", response_model=schemas.MatchResult) +def do_match_post(req: schemas.MatchRequest, db: Session = Depends(get_db)): + return run_match(db, req) diff --git a/backend/app/routes/matches.py b/backend/app/routes/matches.py new file mode 100644 index 0000000..b48bacf --- /dev/null +++ b/backend/app/routes/matches.py @@ -0,0 +1,61 @@ +"""API 路由:匹配绑定""" + +from typing import List +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from app.database import get_db +from app import crud, schemas + +router = APIRouter(prefix="/api/matches", tags=["匹配配置"]) + + +def _enrich_match(m) -> schemas.RegimeMatchOut: + """填充关联名称""" + return schemas.RegimeMatchOut( + id=m.id, + market_regime_id=m.market_regime_id, + market_cycle=m.market_cycle, + trend_strength=m.trend_strength, + account_id=m.account_id, + strategy_id=m.strategy_id, + force_direction=m.force_direction or "", + regime_name=m.regime.name if m.regime else None, + account_name=m.account.account_name if m.account else None, + strategy_name=m.strategy.strategy_name if m.strategy else None, + ) + + +@router.get("", response_model=List[schemas.RegimeMatchOut]) +def list_matches(db: Session = Depends(get_db)): + return [_enrich_match(m) for m in crud.get_matches(db)] + + +@router.post("", response_model=schemas.RegimeMatchOut, status_code=201) +def create_match(data: schemas.RegimeMatchCreate, db: Session = Depends(get_db)): + obj = crud.create_match(db, data) + # 重新加载关联 + matches = crud.get_matches(db) + for m in matches: + if m.id == obj.id: + return _enrich_match(m) + return schemas.RegimeMatchOut(id=obj.id, **data.model_dump()) + + +@router.put("/{match_id}", response_model=schemas.RegimeMatchOut) +def update_match(match_id: int, data: schemas.RegimeMatchUpdate, db: Session = Depends(get_db)): + obj = crud.update_match(db, match_id, data) + if not obj: + raise HTTPException(404, "匹配规则不存在") + matches = crud.get_matches(db) + for m in matches: + if m.id == match_id: + return _enrich_match(m) + raise HTTPException(404, "匹配规则不存在") + + +@router.delete("/{match_id}") +def delete_match(match_id: int, db: Session = Depends(get_db)): + if not crud.delete_match(db, match_id): + raise HTTPException(404, "匹配规则不存在") + return {"ok": True} diff --git a/backend/app/routes/regimes.py b/backend/app/routes/regimes.py new file mode 100644 index 0000000..bab5547 --- /dev/null +++ b/backend/app/routes/regimes.py @@ -0,0 +1,35 @@ +"""API 路由:大盘阶段""" + +from typing import List +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from app.database import get_db +from app import crud, schemas + +router = APIRouter(prefix="/api/regimes", tags=["大盘阶段"]) + + +@router.get("", response_model=List[schemas.MarketRegimeOut]) +def list_regimes(db: Session = Depends(get_db)): + return crud.get_regimes(db) + + +@router.post("", response_model=schemas.MarketRegimeOut, status_code=201) +def create_regime(data: schemas.MarketRegimeCreate, db: Session = Depends(get_db)): + return crud.create_regime(db, data) + + +@router.put("/{regime_id}", response_model=schemas.MarketRegimeOut) +def update_regime(regime_id: int, data: schemas.MarketRegimeUpdate, db: Session = Depends(get_db)): + obj = crud.update_regime(db, regime_id, data) + if not obj: + raise HTTPException(404, "大盘阶段不存在") + return obj + + +@router.delete("/{regime_id}") +def delete_regime(regime_id: int, db: Session = Depends(get_db)): + if not crud.delete_regime(db, regime_id): + raise HTTPException(404, "大盘阶段不存在") + return {"ok": True} diff --git a/backend/app/routes/strategies.py b/backend/app/routes/strategies.py new file mode 100644 index 0000000..6d5b01b --- /dev/null +++ b/backend/app/routes/strategies.py @@ -0,0 +1,35 @@ +"""API 路由:策略""" + +from typing import List +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session + +from app.database import get_db +from app import crud, schemas + +router = APIRouter(prefix="/api/strategies", tags=["策略"]) + + +@router.get("", response_model=List[schemas.StrategyOut]) +def list_strategies(db: Session = Depends(get_db)): + return crud.get_strategies(db) + + +@router.post("", response_model=schemas.StrategyOut, status_code=201) +def create_strategy(data: schemas.StrategyCreate, db: Session = Depends(get_db)): + return crud.create_strategy(db, data) + + +@router.put("/{strategy_id}", response_model=schemas.StrategyOut) +def update_strategy(strategy_id: int, data: schemas.StrategyUpdate, db: Session = Depends(get_db)): + obj = crud.update_strategy(db, strategy_id, data) + if not obj: + raise HTTPException(404, "策略不存在") + return obj + + +@router.delete("/{strategy_id}") +def delete_strategy(strategy_id: int, db: Session = Depends(get_db)): + if not crud.delete_strategy(db, strategy_id): + raise HTTPException(404, "策略不存在") + return {"ok": True} diff --git a/backend/app/schemas.py b/backend/app/schemas.py new file mode 100644 index 0000000..570237d --- /dev/null +++ b/backend/app/schemas.py @@ -0,0 +1,171 @@ +"""Pydantic 请求/响应模型""" + +from typing import Optional, List +from pydantic import BaseModel, Field + + +# ── 大盘阶段 ── + +class MarketRegimeBase(BaseModel): + name: str + trade_type: str + allow_direction: str + remark: str = "" + + +class MarketRegimeCreate(MarketRegimeBase): + pass + + +class MarketRegimeUpdate(BaseModel): + name: Optional[str] = None + trade_type: Optional[str] = None + allow_direction: Optional[str] = None + remark: Optional[str] = None + + +class MarketRegimeOut(MarketRegimeBase): + id: int + + class Config: + from_attributes = True + + +# ── 账户 ── + +class AccountBase(BaseModel): + account_name: str + total_capital: float = 100 + trade_cycle: str + risk_ratio: str + enable: int = 1 + remark: str = "" + + +class AccountCreate(AccountBase): + pass + + +class AccountUpdate(BaseModel): + account_name: Optional[str] = None + total_capital: Optional[float] = None + trade_cycle: Optional[str] = None + risk_ratio: Optional[str] = None + enable: Optional[int] = None + remark: Optional[str] = None + + +class AccountOut(AccountBase): + id: int + + class Config: + from_attributes = True + + +# ── 策略 ── + +class StrategyBase(BaseModel): + strategy_name: str + fit_cycle: str + fit_trend_strength: str + trade_type: str + strategy_rule: str + remark: str = "" + + +class StrategyCreate(StrategyBase): + pass + + +class StrategyUpdate(BaseModel): + strategy_name: Optional[str] = None + fit_cycle: Optional[str] = None + fit_trend_strength: Optional[str] = None + trade_type: Optional[str] = None + strategy_rule: Optional[str] = None + remark: Optional[str] = None + + +class StrategyOut(StrategyBase): + id: int + + class Config: + from_attributes = True + + +# ── 匹配绑定 ── + +class RegimeMatchBase(BaseModel): + market_regime_id: int + market_cycle: str + trend_strength: str + account_id: int + strategy_id: int + force_direction: str = "" + + +class RegimeMatchCreate(RegimeMatchBase): + pass + + +class RegimeMatchUpdate(BaseModel): + market_regime_id: Optional[int] = None + market_cycle: Optional[str] = None + trend_strength: Optional[str] = None + account_id: Optional[int] = None + strategy_id: Optional[int] = None + force_direction: Optional[str] = None + + +class RegimeMatchOut(RegimeMatchBase): + id: int + regime_name: Optional[str] = None + account_name: Optional[str] = None + strategy_name: Optional[str] = None + + class Config: + from_attributes = True + + +# ── 日常使用:匹配查询 ── + +class MatchRequest(BaseModel): + market_cycle: str = Field(..., description="大盘周期:日线/4H/1H") + market_regime_id: int = Field(..., description="大盘阶段 ID") + trend_strength: str = Field(..., description="趋势强弱:强/弱/震荡") + + +class MatchAccountOut(BaseModel): + id: int + account_name: str + total_capital: float + trade_cycle: str + risk_ratio: str + force_direction: str = "" + + class Config: + from_attributes = True + + +class MatchStrategyOut(BaseModel): + id: int + strategy_name: str + fit_cycle: str + strategy_rule: str + force_direction: str = "" + + class Config: + from_attributes = True + + +class MatchResult(BaseModel): + """匹配结果:系统仅做前置策略匹配,不做任何自动校验""" + market_cycle: str + regime_name: str + trade_type: str + allow_direction: str + trend_strength: str + status: str # ok / watch / disabled + message: str = "" + accounts: List[MatchAccountOut] = [] + strategies: List[MatchStrategyOut] = [] diff --git a/backend/init_db.sql b/backend/init_db.sql new file mode 100644 index 0000000..b2f5bb8 --- /dev/null +++ b/backend/init_db.sql @@ -0,0 +1,50 @@ +-- 加密货币前置匹配系统 - SQLite 建表脚本 +-- 本系统仅做前置策略匹配,不处理币种、点位、箱体细节 + +-- 1. 大盘阶段表 +CREATE TABLE IF NOT EXISTS market_regime ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + trade_type TEXT NOT NULL, -- 顺势 / 反转 / 观望 + allow_direction TEXT NOT NULL, -- 做多 / 做空 / 禁止 / 多空均可 + remark TEXT DEFAULT '' +); + +-- 2. 账户表 +CREATE TABLE IF NOT EXISTS account ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + account_name TEXT NOT NULL, + total_capital REAL NOT NULL DEFAULT 100, + trade_cycle TEXT NOT NULL, -- 如 4H/1H、日内、灵活 + risk_ratio TEXT NOT NULL, -- 如 5%~10% + enable INTEGER NOT NULL DEFAULT 1, + remark TEXT DEFAULT '' +); + +-- 3. 策略表 +CREATE TABLE IF NOT EXISTS strategy ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + strategy_name TEXT NOT NULL, + fit_cycle TEXT NOT NULL, -- 适用周期 + fit_trend_strength TEXT NOT NULL, -- 强 / 弱 / 全部 + trade_type TEXT NOT NULL, -- 顺势 / 反转 / 全部 + strategy_rule TEXT NOT NULL, -- 策略规则文本(仅展示,不做校验) + remark TEXT DEFAULT '' +); + +-- 4. 匹配绑定表 +CREATE TABLE IF NOT EXISTS regime_match ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + market_regime_id INTEGER NOT NULL, + market_cycle TEXT NOT NULL, -- 日线 / 4H / 1H + trend_strength TEXT NOT NULL, -- 强 / 弱 / 震荡 + account_id INTEGER NOT NULL, + strategy_id INTEGER NOT NULL, + force_direction TEXT DEFAULT '', -- 强制方向:做多 / 做空 / 空=跟随大盘 + FOREIGN KEY (market_regime_id) REFERENCES market_regime(id) ON DELETE CASCADE, + FOREIGN KEY (account_id) REFERENCES account(id) ON DELETE CASCADE, + FOREIGN KEY (strategy_id) REFERENCES strategy(id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS idx_regime_match_lookup + ON regime_match(market_regime_id, market_cycle, trend_strength); diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..6407af7 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.115.6 +uvicorn[standard]==0.34.0 +sqlalchemy==2.0.36 +pydantic==2.10.4 diff --git a/deploy/install.sh b/deploy/install.sh new file mode 100644 index 0000000..0c45ac3 --- /dev/null +++ b/deploy/install.sh @@ -0,0 +1,113 @@ +#!/bin/bash +# ============================================================ +# 加密货币前置匹配系统 — Ubuntu 一键部署脚本 +# 仓库:https://git.bz121.com/dekun/crypto-pre-trade-system.git +# 部署路径:/opt/crypto-pre-trade-system +# 运行用户:root +# 进程守护:PM2 +# 访问端口:1125 +# ============================================================ + +set -euo pipefail + +REPO_URL="https://git.bz121.com/dekun/crypto-pre-trade-system.git" +INSTALL_DIR="/opt/crypto-pre-trade-system" +PORT=1125 + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +log() { echo -e "${GREEN}[INFO]${NC} $*"; } +warn() { echo -e "${YELLOW}[WARN]${NC} $*"; } +err() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; } + +# ── 检查 root 权限 ── +if [ "$(id -u)" -ne 0 ]; then + err "请使用 root 用户运行:sudo bash deploy/install.sh" +fi + +log "========== 开始部署 crypto-pre-trade-system ==========" + +# ── 1. 安装系统依赖 ── +log "安装系统依赖..." +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq +apt-get install -y -qq git curl python3 python3-venv python3-pip + +# Node.js 18+(若未安装) +if ! command -v node &>/dev/null; then + log "安装 Node.js 20 LTS..." + curl -fsSL https://deb.nodesource.com/setup_20.x | bash - + apt-get install -y -qq nodejs +fi + +# PM2(若未安装) +if ! command -v pm2 &>/dev/null; then + log "安装 PM2..." + npm install -g pm2 +fi + +log "Node $(node -v) | Python $(python3 --version) | PM2 $(pm2 -v)" + +# ── 2. 拉取代码 ── +if [ -d "$INSTALL_DIR/.git" ]; then + log "更新代码..." + cd "$INSTALL_DIR" + git pull origin main 2>/dev/null || git pull origin master 2>/dev/null || git pull +else + log "克隆仓库..." + git clone "$REPO_URL" "$INSTALL_DIR" + cd "$INSTALL_DIR" +fi + +# ── 3. 创建日志目录 ── +mkdir -p "$INSTALL_DIR/logs" +mkdir -p "$INSTALL_DIR/backend/data" + +# ── 4. Python 虚拟环境 + 依赖 ── +log "配置 Python 虚拟环境..." +cd "$INSTALL_DIR/backend" +if [ ! -d "venv" ]; then + python3 -m venv venv +fi +venv/bin/pip install -q --upgrade pip +venv/bin/pip install -q -r requirements.txt + +# ── 5. 构建前端 ── +log "构建前端..." +cd "$INSTALL_DIR/frontend" +npm install --silent +npm run build + +# ── 6. PM2 启动 / 重启 ── +log "启动 PM2 守护进程..." +cd "$INSTALL_DIR" +pm2 delete crypto-pre-trade 2>/dev/null || true +pm2 start ecosystem.config.cjs +pm2 save + +# 设置 PM2 开机自启(已配置则跳过) +pm2 startup systemd -u root --hp /root 2>/dev/null | tail -1 | bash 2>/dev/null || true + +# ── 7. 健康检查 ── +log "等待服务启动..." +sleep 3 +if curl -sf "http://127.0.0.1:${PORT}/api/health" > /dev/null; then + log "健康检查通过 ✓" +else + warn "健康检查未通过,请查看日志:pm2 logs crypto-pre-trade" +fi + +echo "" +log "========== 部署完成 ==========" +echo -e " 访问地址:${GREEN}http://<服务器IP>:${PORT}${NC}" +echo -e " API 文档:${GREEN}http://<服务器IP>:${PORT}/docs${NC}" +echo -e " 安装目录:${INSTALL_DIR}" +echo -e " 常用命令:" +echo -e " pm2 status # 查看状态" +echo -e " pm2 logs crypto-pre-trade # 查看日志" +echo -e " pm2 restart crypto-pre-trade # 重启服务" +echo -e " bash deploy/install.sh # 更新并重新部署" +echo "" diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs new file mode 100644 index 0000000..e0a0c00 --- /dev/null +++ b/ecosystem.config.cjs @@ -0,0 +1,26 @@ +/** + * PM2 进程配置 + * 部署路径:/opt/crypto-pre-trade-system + * 运行用户:root + * 访问端口:1125 + */ +module.exports = { + apps: [ + { + name: 'crypto-pre-trade', + cwd: '/opt/crypto-pre-trade-system/backend', + script: 'venv/bin/uvicorn', + args: 'app.main:app --host 0.0.0.0 --port 1125', + interpreter: 'none', + autorestart: true, + watch: false, + max_memory_restart: '300M', + env: { + NODE_ENV: 'production', + }, + error_file: '/opt/crypto-pre-trade-system/logs/pm2-error.log', + out_file: '/opt/crypto-pre-trade-system/logs/pm2-out.log', + log_date_format: 'YYYY-MM-DD HH:mm:ss', + }, + ], +}; diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..19ef11a --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + 加密货币前置匹配系统 + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..655dd4b --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2773 @@ +{ + "name": "crypto-pre-trade-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "crypto-pre-trade-frontend", + "version": "1.0.0", + "dependencies": { + "axios": "^1.7.9", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "vite": "^6.0.5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@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/@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.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.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/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@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.npmjs.org/@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.npmjs.org/@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/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "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.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.35.tgz", + "integrity": "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/shared": "3.5.35", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.35.tgz", + "integrity": "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.35.tgz", + "integrity": "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/compiler-core": "3.5.35", + "@vue/compiler-dom": "3.5.35", + "@vue/compiler-ssr": "3.5.35", + "@vue/shared": "3.5.35", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.35.tgz", + "integrity": "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "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.35", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.35.tgz", + "integrity": "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.35.tgz", + "integrity": "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.35", + "@vue/shared": "3.5.35" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.35.tgz", + "integrity": "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.35", + "@vue/runtime-core": "3.5.35", + "@vue/shared": "3.5.35", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.35.tgz", + "integrity": "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.35", + "@vue/shared": "3.5.35" + }, + "peerDependencies": { + "vue": "3.5.35" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.35.tgz", + "integrity": "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==", + "license": "MIT" + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "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.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "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.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/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/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/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.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.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.npmjs.org/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/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/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.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/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.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/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.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.363", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.363.tgz", + "integrity": "sha512-VjUKPyWzGnT1fujlkEGC/BvN70Hh70KXtAqcmniXviYlJC/ivcT+BWGPyxWVbJZLfvtKR6dqg1L7T7pgAMBtWA==", + "dev": true, + "license": "ISC" + }, + "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/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/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.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/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/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/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "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/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/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/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/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.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/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.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/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/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/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.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "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/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.npmjs.org/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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/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.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "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": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "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.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "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.12", + "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.npmjs.org/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.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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.npmjs.org/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.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "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/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/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/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "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.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/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/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/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/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.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "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.7", + "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 || ^5.0 || ^6.0", + "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/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "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/tinyglobby/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/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/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.npmjs.org/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/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "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/vite/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/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vue": { + "version": "3.5.35", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.35.tgz", + "integrity": "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.35", + "@vue/compiler-sfc": "3.5.35", + "@vue/runtime-dom": "3.5.35", + "@vue/server-renderer": "3.5.35", + "@vue/shared": "3.5.35" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..3a05d5b --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,23 @@ +{ + "name": "crypto-pre-trade-frontend", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.5.13", + "vue-router": "^4.5.0", + "axios": "^1.7.9" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.2.1", + "autoprefixer": "^10.4.20", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.17", + "vite": "^6.0.5" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..5eec88d --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..b679b60 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,37 @@ + + + diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js new file mode 100644 index 0000000..85eaff0 --- /dev/null +++ b/frontend/src/api/index.js @@ -0,0 +1,35 @@ +import axios from 'axios' + +const api = axios.create({ + baseURL: '/api', + timeout: 10000, +}) + +// ── 大盘阶段 ── +export const getRegimes = () => api.get('/regimes') +export const createRegime = (data) => api.post('/regimes', data) +export const updateRegime = (id, data) => api.put(`/regimes/${id}`, data) +export const deleteRegime = (id) => api.delete(`/regimes/${id}`) + +// ── 账户 ── +export const getAccounts = () => api.get('/accounts') +export const createAccount = (data) => api.post('/accounts', data) +export const updateAccount = (id, data) => api.put(`/accounts/${id}`, data) +export const deleteAccount = (id) => api.delete(`/accounts/${id}`) + +// ── 策略 ── +export const getStrategies = () => api.get('/strategies') +export const createStrategy = (data) => api.post('/strategies', data) +export const updateStrategy = (id, data) => api.put(`/strategies/${id}`, data) +export const deleteStrategy = (id) => api.delete(`/strategies/${id}`) + +// ── 匹配配置 ── +export const getMatches = () => api.get('/matches') +export const createMatch = (data) => api.post('/matches', data) +export const updateMatch = (id, data) => api.put(`/matches/${id}`, data) +export const deleteMatch = (id) => api.delete(`/matches/${id}`) + +// ── 前置匹配 ── +export const doMatch = (params) => api.get('/match', { params }) + +export default api diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..ce2c9e1 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,6 @@ +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' +import './style.css' + +createApp(App).use(router).mount('#app') diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..6fa07b6 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,13 @@ +import { createRouter, createWebHistory } from 'vue-router' +import DailyMatch from '../views/DailyMatch.vue' +import ConfigCenter from '../views/ConfigCenter.vue' + +const routes = [ + { path: '/', name: 'DailyMatch', component: DailyMatch }, + { path: '/config', name: 'ConfigCenter', component: ConfigCenter }, +] + +export default createRouter({ + history: createWebHistory(), + routes, +}) diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..150ab79 --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,51 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; + -webkit-font-smoothing: antialiased; +} + +/* 滚动条样式 */ +::-webkit-scrollbar { width: 6px; height: 6px; } +::-webkit-scrollbar-track { background: #141414; } +::-webkit-scrollbar-thumb { background: #404040; border-radius: 3px; } +::-webkit-scrollbar-thumb:hover { background: #525252; } + +/* 通用卡片 */ +.card { + @apply bg-dark-card border border-dark-border rounded-lg p-4; +} + +/* 通用按钮 */ +.btn { + @apply px-4 py-2 rounded-md text-sm font-medium transition-colors; +} +.btn-primary { + @apply btn bg-dark-accent text-white hover:bg-blue-600; +} +.btn-danger { + @apply btn bg-dark-danger text-white hover:bg-red-600; +} +.btn-ghost { + @apply btn bg-dark-hover text-dark-text hover:bg-dark-border; +} + +/* 表单元素 */ +.input { + @apply w-full bg-dark-bg border border-dark-border rounded-md px-3 py-2 text-sm + text-dark-text focus:outline-none focus:border-dark-accent; +} +.select { + @apply input appearance-none cursor-pointer; +} +.textarea { + @apply input resize-y min-h-[80px]; +} + +/* 标签页 */ +.tab-active { + @apply border-b-2 border-dark-accent text-dark-accent; +} diff --git a/frontend/src/views/ConfigCenter.vue b/frontend/src/views/ConfigCenter.vue new file mode 100644 index 0000000..2f4c40c --- /dev/null +++ b/frontend/src/views/ConfigCenter.vue @@ -0,0 +1,367 @@ + + + diff --git a/frontend/src/views/DailyMatch.vue b/frontend/src/views/DailyMatch.vue new file mode 100644 index 0000000..eed6214 --- /dev/null +++ b/frontend/src/views/DailyMatch.vue @@ -0,0 +1,290 @@ + + + diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..a155590 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,23 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{vue,js}'], + theme: { + extend: { + colors: { + dark: { + bg: '#0a0a0a', + card: '#141414', + border: '#262626', + hover: '#1f1f1f', + text: '#e5e5e5', + muted: '#737373', + accent: '#3b82f6', + success: '#22c55e', + warning: '#eab308', + danger: '#ef4444', + }, + }, + }, + }, + plugins: [], +} diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..f3a03e9 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + port: 1125, + proxy: { + '/api': { + target: 'http://127.0.0.1:8000', + changeOrigin: true, + }, + }, + }, +})