commit 332f4dddd69ad77143aece8b3cb7bdc2605631c6 Author: Mars Date: Fri Jun 13 15:50:11 2025 +0800 Initial commit: Project setup with frontend and backend implementation diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8b07892 --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +venv/ +ENV/ + +# Node +node_modules/ +dist/ +dist-ssr/ +*.local +.env + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Project specific +*.log +*.json +!package.json +!package-lock.json \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a6a5eee --- /dev/null +++ b/README.md @@ -0,0 +1,171 @@ +# 彩票数据分析系统 + +## 项目概述 +本项目是一个彩票数据分析系统,支持双色球和大乐透的数据管理、统计分析和智能选号功能。系统采用前后端分离架构,提供直观的用户界面和强大的数据分析能力。 + +## 技术栈 +### 后端 +- Python 3.8+ +- FastAPI +- SQLAlchemy +- PostgreSQL +- Pydantic + +### 前端 +- Vue 3 +- Vite +- Element Plus +- ECharts +- Pinia +- Vue Router + +## 系统功能 +1. 数据管理 + - 双色球和大乐透历史数据导入 + - 数据查询和筛选 + - 数据导出 + - 手动数据录入 + +2. 统计分析 + - 号码出现频率统计 + - 热门号码分析 + - 冷门号码分析 + - 数据可视化展示 + +3. 智能选号 + - 随机选号 + - 频率选号 + - 热门号码选号 + - 冷门号码选号 + +## 项目结构 +``` +lottery/ +├── backend/ # 后端代码 +│ ├── app/ +│ │ ├── api/ # API 路由 +│ │ ├── core/ # 核心配置 +│ │ ├── models/ # 数据模型 +│ │ ├── schemas/ # 数据验证 +│ │ └── services/ # 业务逻辑 +│ ├── requirements.txt # 依赖包 +│ └── main.py # 入口文件 +├── frontend/ # 前端代码 +│ ├── src/ +│ │ ├── api/ # API 接口 +│ │ ├── assets/ # 静态资源 +│ │ ├── components/ # 组件 +│ │ ├── router/ # 路由配置 +│ │ ├── views/ # 页面 +│ │ ├── App.vue # 根组件 +│ │ └── main.js # 入口文件 +│ ├── package.json # 依赖配置 +│ └── vite.config.js # Vite 配置 +└── README.md # 项目文档 +``` + +## 开发环境要求 +- Python 3.8+ +- Node.js 16+ +- PostgreSQL 12+ +- npm 或 yarn + +## 安装和运行 + +### 后端 +1. 创建虚拟环境 +```bash +python -m venv venv +source venv/bin/activate # Linux/Mac +venv\Scripts\activate # Windows +``` + +2. 安装依赖 +```bash +cd backend +pip install -r requirements.txt +``` + +3. 配置数据库 +- 创建 PostgreSQL 数据库 +- 修改 `backend/app/core/database.py` 中的数据库连接配置 + +4. 启动服务 +```bash +uvicorn main:app --reload --host 0.0.0.0 --port 8000 +``` + +### 前端 +1. 安装依赖 +```bash +cd frontend +npm install +``` + +2. 启动开发服务器 +```bash +npm run dev +``` + +3. 构建生产版本 +```bash +npm run build +``` + +## 使用说明 + +### 数据导入 +1. 在双色球或大乐透页面点击"导入数据"按钮 +2. 选择对应的 JSON 数据文件 +3. 等待导入完成 + +### 数据查询 +1. 在查询表单中输入查询条件 +2. 点击"查询"按钮 +3. 查看查询结果 + +### 统计分析 +1. 进入统计分析页面 +2. 选择要分析的彩票类型 +3. 查看统计图表 + +### 智能选号 +1. 进入智能选号页面 +2. 选择彩票类型和选号策略 +3. 设置生成注数 +4. 点击"生成号码"按钮 + +## API 文档 +启动后端服务后,访问以下地址查看 API 文档: +- Swagger UI: http://localhost:8000/docs +- ReDoc: http://localhost:8000/redoc + +## 常见问题 +1. 数据库连接失败 + - 检查数据库服务是否启动 + - 验证数据库连接配置是否正确 + +2. 前端无法连接后端 + - 确认后端服务是否正常运行 + - 检查前端环境配置中的 API 地址是否正确 + +3. 数据导入失败 + - 检查 JSON 文件格式是否正确 + - 确认数据库表结构是否完整 + +## 开发计划 +- [ ] 添加用户认证功能 +- [ ] 实现数据备份和恢复 +- [ ] 优化数据导入性能 +- [ ] 添加更多统计分析功能 +- [ ] 实现自定义选号策略 + +## 贡献指南 +1. Fork 项目 +2. 创建特性分支 +3. 提交更改 +4. 推送到分支 +5. 创建 Pull Request + +## 许可证 +MIT License \ No newline at end of file diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..1cc9c60 --- /dev/null +++ b/backend/__init__.py @@ -0,0 +1,3 @@ +""" +Lottery backend package +""" diff --git a/backend/app/__init__.py b/backend/app/__init__.py new file mode 100644 index 0000000..a9b1d5a --- /dev/null +++ b/backend/app/__init__.py @@ -0,0 +1,3 @@ +""" +Lottery application package +""" diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py new file mode 100644 index 0000000..c14c65e --- /dev/null +++ b/backend/app/api/__init__.py @@ -0,0 +1,3 @@ +""" +API package +""" diff --git a/backend/app/api/endpoints/__init__.py b/backend/app/api/endpoints/__init__.py new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/backend/app/api/endpoints/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/app/api/endpoints/lottery.py b/backend/app/api/endpoints/lottery.py new file mode 100644 index 0000000..27316a4 --- /dev/null +++ b/backend/app/api/endpoints/lottery.py @@ -0,0 +1,107 @@ +from fastapi import APIRouter, Depends, HTTPException, UploadFile, File +from sqlalchemy.orm import Session +from typing import List +import os +from ...core.database import get_db +from ...schemas.lottery import ( + SSQLottery, SSQLotteryCreate, + DLTLottery, DLTLotteryCreate, + LotteryQuery +) +from ...services.lottery import LotteryService + +router = APIRouter() + + +@router.post("/ssq/", response_model=SSQLottery) +def create_ssq_lottery( + lottery: SSQLotteryCreate, + db: Session = Depends(get_db) +): + return LotteryService.create_ssq_lottery(db, lottery) + + +@router.post("/dlt/", response_model=DLTLottery) +def create_dlt_lottery( + lottery: DLTLotteryCreate, + db: Session = Depends(get_db) +): + return LotteryService.create_dlt_lottery(db, lottery) + + +@router.get("/ssq/", response_model=List[SSQLottery]) +def get_ssq_lotteries( + query: LotteryQuery = Depends(), + db: Session = Depends(get_db) +): + lotteries, total = LotteryService.get_ssq_lotteries(db, query) + return lotteries + + +@router.get("/dlt/", response_model=List[DLTLottery]) +def get_dlt_lotteries( + query: LotteryQuery = Depends(), + db: Session = Depends(get_db) +): + lotteries, total = LotteryService.get_dlt_lotteries(db, query) + return lotteries + + +@router.get("/ssq/statistics") +def get_ssq_statistics(db: Session = Depends(get_db)): + return LotteryService.get_ssq_statistics(db) + + +@router.get("/dlt/statistics") +def get_dlt_statistics(db: Session = Depends(get_db)): + return LotteryService.get_dlt_statistics(db) + + +@router.post("/ssq/import") +async def import_ssq_data( + file: UploadFile = File(...), + db: Session = Depends(get_db) +): + if not file.filename.endswith('.json'): + raise HTTPException( + status_code=400, detail="Only JSON files are allowed") + + # 保存上传的文件 + file_path = f"temp_{file.filename}" + try: + with open(file_path, "wb") as buffer: + content = await file.read() + buffer.write(content) + + # 导入数据 + count = LotteryService.import_ssq_data(db, file_path) + return {"message": f"Successfully imported {count} records"} + finally: + # 清理临时文件 + if os.path.exists(file_path): + os.remove(file_path) + + +@router.post("/dlt/import") +async def import_dlt_data( + file: UploadFile = File(...), + db: Session = Depends(get_db) +): + if not file.filename.endswith('.json'): + raise HTTPException( + status_code=400, detail="Only JSON files are allowed") + + # 保存上传的文件 + file_path = f"temp_{file.filename}" + try: + with open(file_path, "wb") as buffer: + content = await file.read() + buffer.write(content) + + # 导入数据 + count = LotteryService.import_dlt_data(db, file_path) + return {"message": f"Successfully imported {count} records"} + finally: + # 清理临时文件 + if os.path.exists(file_path): + os.remove(file_path) diff --git a/backend/app/api/v1/__init__.py b/backend/app/api/v1/__init__.py new file mode 100644 index 0000000..7fcbc4a --- /dev/null +++ b/backend/app/api/v1/__init__.py @@ -0,0 +1,3 @@ +""" +API v1 package +""" diff --git a/backend/app/api/v1/lottery.py b/backend/app/api/v1/lottery.py new file mode 100644 index 0000000..c5753d9 --- /dev/null +++ b/backend/app/api/v1/lottery.py @@ -0,0 +1,112 @@ +from fastapi import APIRouter, Depends, Query +from sqlalchemy.orm import Session +from typing import List, Optional +from datetime import date +from app.core.database import get_db +from app.services.lottery import LotteryService +from app.schemas.lottery import SSQLottery, DLTLottery, LotteryQuery +import random + +router = APIRouter() + + +@router.get("/ssq/latest", response_model=SSQLottery) +def get_latest_ssq(db: Session = Depends(get_db)): + """获取最新一期双色球开奖记录""" + return LotteryService.get_latest_ssq(db) + + +@router.get("/dlt/latest", response_model=DLTLottery) +def get_latest_dlt(db: Session = Depends(get_db)): + """获取最新一期大乐透开奖记录""" + return LotteryService.get_latest_dlt(db) + + +@router.get("/ssq/", response_model=List[SSQLottery]) +def get_ssq_lotteries( + db: Session = Depends(get_db), + issue: Optional[str] = Query(None, description="期号"), + start_date: Optional[str] = Query(None, description="开始日期 (YYYY-MM-DD)"), + end_date: Optional[str] = Query(None, description="结束日期 (YYYY-MM-DD)"), + page: int = Query(1, ge=1, description="页码"), + page_size: int = Query(20, ge=1, le=100, description="每页记录数") +): + """获取双色球开奖记录列表""" + query = LotteryQuery( + issue=issue, + start_date=start_date, + end_date=end_date, + page=page, + page_size=page_size + ) + lotteries, _ = LotteryService.get_ssq_lotteries(db, query) + return lotteries + + +@router.get("/dlt/", response_model=List[DLTLottery]) +def get_dlt_lotteries( + db: Session = Depends(get_db), + issue: Optional[str] = Query(None, description="期号"), + start_date: Optional[str] = Query(None, description="开始日期 (YYYY-MM-DD)"), + end_date: Optional[str] = Query(None, description="结束日期 (YYYY-MM-DD)"), + page: int = Query(1, ge=1, description="页码"), + page_size: int = Query(20, ge=1, le=100, description="每页记录数") +): + """获取大乐透开奖记录列表""" + query = LotteryQuery( + issue=issue, + start_date=start_date, + end_date=end_date, + page=page, + page_size=page_size + ) + lotteries, _ = LotteryService.get_dlt_lotteries(db, query) + return lotteries + + +@router.get("/ssq/statistics") +def get_ssq_statistics(db: Session = Depends(get_db)): + """获取双色球统计数据""" + return LotteryService.get_ssq_statistics(db) + + +@router.get("/dlt/statistics") +def get_dlt_statistics(db: Session = Depends(get_db)): + """获取大乐透统计数据""" + return LotteryService.get_dlt_statistics(db) + + +@router.get("/ssq/generate") +def generate_ssq_numbers(strategy: str = Query("random"), count: int = Query(1)): + """ + 智能生成双色球号码 + - strategy: 选号策略(目前仅支持 random) + - count: 生成注数 + """ + results = [] + for _ in range(count): + red_balls = sorted(random.sample(range(1, 34), 6)) + blue_ball = random.randint(1, 16) + results.append({ + "red_balls": red_balls, + "blue_ball": blue_ball + }) + return {"results": results} + + +@router.get("/dlt/generate") +def generate_dlt_numbers(strategy: str = Query("random"), count: int = Query(1)): + """ + 智能生成大乐透号码 + - strategy: 选号策略(目前仅支持 random) + - count: 生成注数 + """ + results = [] + for _ in range(count): + front_balls = sorted(random.sample(range(1, 36), 5)) + back_balls = sorted(random.sample(range(1, 13), 2)) + results.append({ + "front_balls": front_balls, + "back_balls": back_balls + }) + return {"results": results} diff --git a/backend/app/core/__init__.py b/backend/app/core/__init__.py new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/backend/app/core/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/app/core/config.py b/backend/app/core/config.py new file mode 100644 index 0000000..1ae7d35 --- /dev/null +++ b/backend/app/core/config.py @@ -0,0 +1,7 @@ +class Settings: + PROJECT_NAME = "彩票数据分析系统" + API_V1_STR = "/api/v1" + BACKEND_CORS_ORIGINS = ["*"] + + +settings = Settings() diff --git a/backend/app/core/database.py b/backend/app/core/database.py new file mode 100644 index 0000000..74d7e7c --- /dev/null +++ b/backend/app/core/database.py @@ -0,0 +1,31 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + +# 远程MySQL数据库配置 +SQLALCHEMY_DATABASE_URL = "mysql+pymysql://root:911Forever@119.28.86.234:3306/lottery" + +# 创建数据库引擎 +engine = create_engine( + SQLALCHEMY_DATABASE_URL, + pool_pre_ping=True, + pool_recycle=3600, + pool_size=5, + max_overflow=10 +) + +# 创建会话工厂 +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# 创建基类 +Base = declarative_base() + +# 获取数据库会话 + + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/backend/app/core/init_db.py b/backend/app/core/init_db.py new file mode 100644 index 0000000..061ed40 --- /dev/null +++ b/backend/app/core/init_db.py @@ -0,0 +1,29 @@ +import pymysql +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from ..models.lottery import Base +from .database import SQLALCHEMY_DATABASE_URL + + +def init_database(): + # 创建数据库 + conn = pymysql.connect( + host='127.0.0.1', + user='root', + password='911!Dswybs-1024' + ) + try: + with conn.cursor() as cursor: + cursor.execute('CREATE DATABASE IF NOT EXISTS lottery') + conn.commit() + finally: + conn.close() + + # 创建表 + engine = create_engine(SQLALCHEMY_DATABASE_URL) + Base.metadata.create_all(bind=engine) + + +if __name__ == "__main__": + init_database() + print("数据库初始化完成!") diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..c024dad --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,30 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from app.core.config import settings +from app.api.v1.lottery import router as lottery_router +from app.core.database import Base, engine + +# 创建数据库表 +Base.metadata.create_all(bind=engine) + +app = FastAPI( + title=settings.PROJECT_NAME, + openapi_url=f"{settings.API_V1_STR}/openapi.json" +) + +# 配置CORS +app.add_middleware( + CORSMiddleware, + allow_origins=settings.BACKEND_CORS_ORIGINS, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# 注册路由 +app.include_router( + lottery_router, prefix=f"{settings.API_V1_STR}/lottery", tags=["lottery"]) + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/backend/app/models/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/app/models/lottery.py b/backend/app/models/lottery.py new file mode 100644 index 0000000..b8a2af1 --- /dev/null +++ b/backend/app/models/lottery.py @@ -0,0 +1,38 @@ +from sqlalchemy import Column, Integer, String, Date, TIMESTAMP, func +from ..core.database import Base + + +class SSQLottery(Base): + __tablename__ = "ssq_lottery" + + id = Column(Integer, primary_key=True, index=True, autoincrement=True) + issue = Column(String(10), unique=True, nullable=False, comment="期号") + open_time = Column(Date, nullable=False, comment="开奖日期") + red_ball_1 = Column(Integer, nullable=False, comment="红球1") + red_ball_2 = Column(Integer, nullable=False, comment="红球2") + red_ball_3 = Column(Integer, nullable=False, comment="红球3") + red_ball_4 = Column(Integer, nullable=False, comment="红球4") + red_ball_5 = Column(Integer, nullable=False, comment="红球5") + red_ball_6 = Column(Integer, nullable=False, comment="红球6") + blue_ball = Column(Integer, nullable=False, comment="蓝球") + created_at = Column(TIMESTAMP, server_default=func.now(), comment="创建时间") + updated_at = Column(TIMESTAMP, server_default=func.now(), + onupdate=func.now(), comment="更新时间") + + +class DLTLottery(Base): + __tablename__ = "dlt_lottery" + + id = Column(Integer, primary_key=True, index=True, autoincrement=True) + issue = Column(String(10), unique=True, nullable=False, comment="期号") + open_time = Column(Date, nullable=False, comment="开奖日期") + front_ball_1 = Column(Integer, nullable=False, comment="前区球1") + front_ball_2 = Column(Integer, nullable=False, comment="前区球2") + front_ball_3 = Column(Integer, nullable=False, comment="前区球3") + front_ball_4 = Column(Integer, nullable=False, comment="前区球4") + front_ball_5 = Column(Integer, nullable=False, comment="前区球5") + back_ball_1 = Column(Integer, nullable=False, comment="后区球1") + back_ball_2 = Column(Integer, nullable=False, comment="后区球2") + created_at = Column(TIMESTAMP, server_default=func.now(), comment="创建时间") + updated_at = Column(TIMESTAMP, server_default=func.now(), + onupdate=func.now(), comment="更新时间") diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/backend/app/schemas/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/app/schemas/lottery.py b/backend/app/schemas/lottery.py new file mode 100644 index 0000000..c919fcf --- /dev/null +++ b/backend/app/schemas/lottery.py @@ -0,0 +1,61 @@ +from pydantic import BaseModel, Field +from datetime import date, datetime +from typing import Optional + + +class SSQLotteryBase(BaseModel): + issue: str = Field(..., description="期号") + open_time: date = Field(..., description="开奖日期") + red_ball_1: int = Field(..., ge=1, le=33, description="红球1") + red_ball_2: int = Field(..., ge=1, le=33, description="红球2") + red_ball_3: int = Field(..., ge=1, le=33, description="红球3") + red_ball_4: int = Field(..., ge=1, le=33, description="红球4") + red_ball_5: int = Field(..., ge=1, le=33, description="红球5") + red_ball_6: int = Field(..., ge=1, le=33, description="红球6") + blue_ball: int = Field(..., ge=1, le=16, description="蓝球") + + +class SSQLotteryCreate(SSQLotteryBase): + pass + + +class SSQLottery(SSQLotteryBase): + id: int + created_at: datetime + updated_at: datetime + + class Config: + from_attributes = True + + +class DLTLotteryBase(BaseModel): + issue: str = Field(..., description="期号") + open_time: date = Field(..., description="开奖日期") + front_ball_1: int = Field(..., ge=1, le=35, description="前区球1") + front_ball_2: int = Field(..., ge=1, le=35, description="前区球2") + front_ball_3: int = Field(..., ge=1, le=35, description="前区球3") + front_ball_4: int = Field(..., ge=1, le=35, description="前区球4") + front_ball_5: int = Field(..., ge=1, le=35, description="前区球5") + back_ball_1: int = Field(..., ge=1, le=12, description="后区球1") + back_ball_2: int = Field(..., ge=1, le=12, description="后区球2") + + +class DLTLotteryCreate(DLTLotteryBase): + pass + + +class DLTLottery(DLTLotteryBase): + id: int + created_at: datetime + updated_at: datetime + + class Config: + from_attributes = True + + +class LotteryQuery(BaseModel): + start_date: Optional[str] = None + end_date: Optional[str] = None + issue: Optional[str] = None + page: int = 1 + page_size: int = 20 diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/backend/app/services/__init__.py @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/backend/app/services/lottery.py b/backend/app/services/lottery.py new file mode 100644 index 0000000..c8e1a5b --- /dev/null +++ b/backend/app/services/lottery.py @@ -0,0 +1,245 @@ +from sqlalchemy.orm import Session +from sqlalchemy import func, desc +from typing import List, Optional, Dict, Any, Tuple +from datetime import date, datetime +import pandas as pd +from app.models.lottery import SSQLottery, DLTLottery +from app.schemas.lottery import SSQLotteryCreate, DLTLotteryCreate, LotteryQuery + + +class LotteryService: + @staticmethod + def create_ssq_lottery(db: Session, lottery: SSQLotteryCreate) -> SSQLottery: + db_lottery = SSQLottery(**lottery.model_dump()) + db.add(db_lottery) + db.commit() + db.refresh(db_lottery) + return db_lottery + + @staticmethod + def create_dlt_lottery(db: Session, lottery: DLTLotteryCreate) -> DLTLottery: + db_lottery = DLTLottery(**lottery.model_dump()) + db.add(db_lottery) + db.commit() + db.refresh(db_lottery) + return db_lottery + + @staticmethod + def get_ssq_lotteries( + db: Session, + query: LotteryQuery + ) -> Tuple[List[SSQLottery], int]: + """获取双色球开奖记录""" + db_query = db.query(SSQLottery) + + # 应用过滤条件 + if query.issue: + db_query = db_query.filter(SSQLottery.issue == query.issue) + if query.start_date: + try: + start_date = datetime.strptime( + query.start_date, "%Y-%m-%d").date() + db_query = db_query.filter(SSQLottery.open_time >= start_date) + except ValueError: + pass + if query.end_date: + try: + end_date = datetime.strptime(query.end_date, "%Y-%m-%d").date() + db_query = db_query.filter(SSQLottery.open_time <= end_date) + except ValueError: + pass + + # 获取总记录数 + total = db_query.count() + + # 应用分页 + page = query.page or 1 + page_size = query.page_size or 20 + db_query = db_query.order_by(desc(SSQLottery.open_time)) \ + .offset((page - 1) * page_size) \ + .limit(page_size) + + return db_query.all(), total + + @staticmethod + def get_dlt_lotteries( + db: Session, + query: LotteryQuery + ) -> Tuple[List[DLTLottery], int]: + """获取大乐透开奖记录""" + db_query = db.query(DLTLottery) + + # 应用过滤条件 + if query.issue: + db_query = db_query.filter(DLTLottery.issue == query.issue) + if query.start_date: + try: + start_date = datetime.strptime( + query.start_date, "%Y-%m-%d").date() + db_query = db_query.filter(DLTLottery.open_time >= start_date) + except ValueError: + pass + if query.end_date: + try: + end_date = datetime.strptime(query.end_date, "%Y-%m-%d").date() + db_query = db_query.filter(DLTLottery.open_time <= end_date) + except ValueError: + pass + + # 获取总记录数 + total = db_query.count() + + # 应用分页 + page = query.page or 1 + page_size = query.page_size or 20 + db_query = db_query.order_by(desc(DLTLottery.open_time)) \ + .offset((page - 1) * page_size) \ + .limit(page_size) + + return db_query.all(), total + + @staticmethod + def get_ssq_statistics(db: Session): + # 红球统计 + red_freq = [] + for i in range(1, 7): + col = getattr(SSQLottery, f"red_ball_{i}") + result = db.query(col, func.count().label( + 'count')).group_by(col).all() + for number, count in result: + red_freq.append((number, count)) + # 汇总红球频率 + red_counter = {} + for number, count in red_freq: + red_counter[number] = red_counter.get(number, 0) + count + red_balls = sorted([(k, v) + for k, v in red_counter.items()], key=lambda x: x[0]) + + # 蓝球统计 + blue_freq = db.query(SSQLottery.blue_ball, func.count().label( + 'count')).group_by(SSQLottery.blue_ball).all() + blue_balls = sorted([(k, v) for k, v in blue_freq], key=lambda x: x[0]) + + return { + "red_balls": red_balls, + "blue_balls": blue_balls + } + + @staticmethod + def get_dlt_statistics(db: Session): + # 前区统计 + front_freq = [] + for i in range(1, 6): + col = getattr(DLTLottery, f"front_ball_{i}") + result = db.query(col, func.count().label( + 'count')).group_by(col).all() + for number, count in result: + front_freq.append((number, count)) + front_counter = {} + for number, count in front_freq: + front_counter[number] = front_counter.get(number, 0) + count + front_balls = sorted( + [(k, v) for k, v in front_counter.items()], key=lambda x: x[0]) + + # 后区统计 + back_freq = [] + for i in range(1, 3): + col = getattr(DLTLottery, f"back_ball_{i}") + result = db.query(col, func.count().label( + 'count')).group_by(col).all() + for number, count in result: + back_freq.append((number, count)) + back_counter = {} + for number, count in back_freq: + back_counter[number] = back_counter.get(number, 0) + count + back_balls = sorted( + [(k, v) for k, v in back_counter.items()], key=lambda x: x[0]) + + return { + "front_balls": front_balls, + "back_balls": back_balls + } + + @staticmethod + def import_ssq_data(db: Session, file_path: str) -> int: + import pandas as pd + from app.models.lottery import SSQLottery + + df = pd.read_json(file_path) + # 先查出所有已存在的期号 + existing_issues = set( + i[0] for i in db.query(SSQLottery.issue).filter(SSQLottery.issue.in_(df['issue'].astype(str).tolist())).all() + ) + # pandas 端彻底去重 + new_rows = df[~df['issue'].astype(str).isin( + existing_issues)].drop_duplicates(subset=['issue']) + + objs = [ + SSQLottery( + issue=str(row['issue']), + open_time=row['open_time'], + red_ball_1=int(row['red_ball_1']), + red_ball_2=int(row['red_ball_2']), + red_ball_3=int(row['red_ball_3']), + red_ball_4=int(row['red_ball_4']), + red_ball_5=int(row['red_ball_5']), + red_ball_6=int(row['red_ball_6']), + blue_ball=int(row['blue_ball']) + ) + for _, row in new_rows.iterrows() + ] + if objs: + try: + db.bulk_save_objects(objs) + db.commit() + except Exception as e: + db.rollback() + print(f"Bulk insert error: {e}") + return len(objs) + + @staticmethod + def import_dlt_data(db: Session, file_path: str) -> int: + import pandas as pd + from app.models.lottery import DLTLottery + + df = pd.read_json(file_path) + # 先查出所有已存在的期号 + existing_issues = set( + i[0] for i in db.query(DLTLottery.issue).filter(DLTLottery.issue.in_(df['issue'].astype(str).tolist())).all() + ) + # pandas 端彻底去重 + new_rows = df[~df['issue'].astype(str).isin( + existing_issues)].drop_duplicates(subset=['issue']) + + objs = [ + DLTLottery( + issue=str(row['issue']), + open_time=row['open_time'], + front_ball_1=int(row['front_ball_1']), + front_ball_2=int(row['front_ball_2']), + front_ball_3=int(row['front_ball_3']), + front_ball_4=int(row['front_ball_4']), + front_ball_5=int(row['front_ball_5']), + back_ball_1=int(row['back_ball_1']), + back_ball_2=int(row['back_ball_2']) + ) + for _, row in new_rows.iterrows() + ] + if objs: + try: + db.bulk_save_objects(objs) + db.commit() + except Exception as e: + db.rollback() + print(f"Bulk insert error: {e}") + return len(objs) + + @staticmethod + def get_latest_ssq(db: Session) -> Optional[SSQLottery]: + """获取最新一期双色球开奖记录""" + return db.query(SSQLottery).order_by(desc(SSQLottery.issue)).first() + + @staticmethod + def get_latest_dlt(db: Session) -> Optional[DLTLottery]: + """获取最新一期大乐透开奖记录""" + return db.query(DLTLottery).order_by(desc(DLTLottery.issue)).first() diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..71c98cf --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,13 @@ +fastapi==0.104.1 +uvicorn==0.24.0 +sqlalchemy==2.0.23 +pydantic==2.5.2 +python-multipart==0.0.6 +python-jose==3.3.0 +passlib==1.7.4 +bcrypt==4.0.1 +pymysql==1.1.0 +cryptography==41.0.5 +python-dotenv==1.0.0 +pandas==2.1.3 +aiofiles==23.2.1 \ No newline at end of file diff --git a/clean_dlt.py b/clean_dlt.py new file mode 100644 index 0000000..7c253f2 --- /dev/null +++ b/clean_dlt.py @@ -0,0 +1,37 @@ +import pandas as pd + +# 读取原始 JSON +df = pd.read_json('dlt_all_data.json') + + +def parse_front_balls(s): + return [int(x) for x in str(s).split()[:5]] + + +def parse_back_balls(s): + return [int(x) for x in str(s).split()[:2]] + + +records = [] +for _, row in df.iterrows(): + try: + fronts = parse_front_balls(row['frontWinningNum']) + backs = parse_back_balls(row['backWinningNum']) + records.append({ + 'issue': str(row['issue']), + 'open_time': str(row['openTime']), + 'front_ball_1': fronts[0], + 'front_ball_2': fronts[1], + 'front_ball_3': fronts[2], + 'front_ball_4': fronts[3], + 'front_ball_5': fronts[4], + 'back_ball_1': backs[0], + 'back_ball_2': backs[1] + }) + except Exception as e: + print(f"Error parsing row: {row['issue']}, error: {e}") + +# 保存为新 JSON +pd.DataFrame(records).to_json('dlt_clean.json', + orient='records', force_ascii=False) +print('精简后的数据已保存为 dlt_clean.json') diff --git a/clean_ssq.py b/clean_ssq.py new file mode 100644 index 0000000..a637064 --- /dev/null +++ b/clean_ssq.py @@ -0,0 +1,37 @@ +import pandas as pd + +# 读取原始 JSON +df = pd.read_json('ssq_all_data.json') + + +def parse_red_balls(s): + return [int(x) for x in str(s).split()[:6]] + + +def parse_blue_ball(s): + return int(str(s).split()[0]) + + +records = [] +for _, row in df.iterrows(): + try: + reds = parse_red_balls(row['frontWinningNum']) + blue = parse_blue_ball(row['backWinningNum']) + records.append({ + 'issue': str(row['issue']), + 'open_time': str(row['openTime']), + 'red_ball_1': reds[0], + 'red_ball_2': reds[1], + 'red_ball_3': reds[2], + 'red_ball_4': reds[3], + 'red_ball_5': reds[4], + 'red_ball_6': reds[5], + 'blue_ball': blue + }) + except Exception as e: + print(f"Error parsing row: {row['issue']}, error: {e}") + +# 保存为新 JSON +pd.DataFrame(records).to_json('ssq_clean.json', + orient='records', force_ascii=False) +print('精简后的数据已保存为 ssq_clean.json') diff --git a/frontend/favicon.ico b/frontend/favicon.ico new file mode 100644 index 0000000..a60141c --- /dev/null +++ b/frontend/favicon.ico @@ -0,0 +1 @@ +"" diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..7cb0920 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 彩票数据分析系统 + + +
+ + + \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..777b56b --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1867 @@ +{ + "name": "lottery-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lottery-frontend", + "version": "0.1.0", + "dependencies": { + "axios": "^1.5.0", + "dayjs": "^1.11.9", + "echarts": "^5.4.3", + "element-plus": "^2.10.1", + "pinia": "^2.1.6", + "vue": "^3.3.4", + "vue-router": "^4.2.4" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.3.4", + "sass": "^1.66.1", + "vite": "^4.4.9" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", + "integrity": "sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.1.tgz", + "integrity": "sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz", + "integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz", + "integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.1", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz", + "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz", + "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.17.tgz", + "integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.16", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.16.tgz", + "integrity": "sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.6.2.tgz", + "integrity": "sha512-kqf7SGFoG+80aZG6Pf+gsZIVvGSCKE98JbiWqcCV9cThtg91Jav0yvYFC9Zb+jKetNGF6ZKeoaxgZfND21fWKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0 || ^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.16.tgz", + "integrity": "sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.2", + "@vue/shared": "3.5.16", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz", + "integrity": "sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.16", + "@vue/shared": "3.5.16" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz", + "integrity": "sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.2", + "@vue/compiler-core": "3.5.16", + "@vue/compiler-dom": "3.5.16", + "@vue/compiler-ssr": "3.5.16", + "@vue/shared": "3.5.16", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.17", + "postcss": "^8.5.3", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz", + "integrity": "sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.16", + "@vue/shared": "3.5.16" + } + }, + "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.16", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.16.tgz", + "integrity": "sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.16" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.16.tgz", + "integrity": "sha512-bw5Ykq6+JFHYxrQa7Tjr+VSzw7Dj4ldR/udyBZbq73fCdJmyy5MPIFR9IX/M5Qs+TtTjuyUTCnmK3lWWwpAcFQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.16", + "@vue/shared": "3.5.16" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.16.tgz", + "integrity": "sha512-T1qqYJsG2xMGhImRUV9y/RseB9d0eCYZQ4CWca9ztCuiPj/XWNNN+lkNBuzVbia5z4/cgxdL28NoQCvC0Xcfww==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.16", + "@vue/runtime-core": "3.5.16", + "@vue/shared": "3.5.16", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.16.tgz", + "integrity": "sha512-BrX0qLiv/WugguGsnQUJiYOE0Fe5mZTwi6b7X/ybGB0vfrPH9z0gD/Y6WOR1sGCgX4gc25L1RYS5eYQKDMoNIg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.16", + "@vue/shared": "3.5.16" + }, + "peerDependencies": { + "vue": "3.5.16" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", + "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-9.13.0.tgz", + "integrity": "sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.16", + "@vueuse/metadata": "9.13.0", + "@vueuse/shared": "9.13.0", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/metadata": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-9.13.0.tgz", + "integrity": "sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "license": "MIT", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/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/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "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", + "optional": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "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/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "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/echarts": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz", + "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.6.1" + } + }, + "node_modules/element-plus": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.10.1.tgz", + "integrity": "sha512-R+YM8b+s+3aQ3EeY33q0inn3ehRnunP42aDYoJxUtSZPgMPSXzYgmGEhIDP7Xg4NvY8raaSuO0/1fDLEfZ+nlA==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.1", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.14.182", + "@types/lodash-es": "^4.17.6", + "@vueuse/core": "^9.1.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.13", + "escape-html": "^1.0.3", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "lodash-unified": "^1.0.2", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.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.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.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.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "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/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", + "optional": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "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/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/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.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/immutable": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.2.tgz", + "integrity": "sha512-qHKXW1q6liAk1Oys6umoaZbDRqjcjgSrbnrifHsfsttza7zcvRAsL7mMV6xWcyhwQy7Xj5v4hhbr6b+iDYwlmQ==", + "dev": true, + "license": "MIT" + }, + "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", + "optional": true, + "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", + "optional": true, + "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", + "optional": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "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", + "optional": true, + "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/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.4", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", + "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rollup": { + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/sass": { + "version": "1.89.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz", + "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "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/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", + "optional": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/vite": { + "version": "4.5.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.14.tgz", + "integrity": "sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.27", + "rollup": "^3.27.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.16", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.16.tgz", + "integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.16", + "@vue/compiler-sfc": "3.5.16", + "@vue/runtime-dom": "3.5.16", + "@vue/server-renderer": "3.5.16", + "@vue/shared": "3.5.16" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz", + "integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/zrender": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz", + "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..942d9a0 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "lottery-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "axios": "^1.5.0", + "dayjs": "^1.11.9", + "echarts": "^5.4.3", + "element-plus": "^2.10.1", + "pinia": "^2.1.6", + "vue": "^3.3.4", + "vue-router": "^4.2.4" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.3.4", + "sass": "^1.66.1", + "vite": "^4.4.9" + } +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..6163715 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,119 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/api/lottery.js b/frontend/src/api/lottery.js new file mode 100644 index 0000000..abcdf5c --- /dev/null +++ b/frontend/src/api/lottery.js @@ -0,0 +1,93 @@ +import axios from 'axios' + +const api = axios.create({ + baseURL: '/api/v1/lottery' +}) + +export const lotteryApi = { + // 双色球相关接口 + getSSQLotteries(params) { + return api.get('/ssq/', { params }) + }, + + getLatestSSQ() { + return api.get('/ssq/latest') + }, + + createSSQLottery(data) { + return api.post('/ssq/', data) + }, + + getSSQStatistics() { + return api.get('/ssq/statistics').then(response => { + const data = response.data + return { + red_balls: data.red_balls.map(item => ({ + number: item[0], + count: item[1] + })), + blue_balls: data.blue_balls.map(item => ({ + number: item[0], + count: item[1] + })) + } + }) + }, + + importSSQData(file) { + const formData = new FormData() + formData.append('file', file) + return api.post('/ssq/import', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + }, + + // 大乐透相关接口 + getDLTLotteries(params) { + return api.get('/dlt/', { params }) + }, + + getLatestDLT() { + return api.get('/dlt/latest') + }, + + createDLTLottery(data) { + return api.post('/dlt/', data) + }, + + getDLTStatistics() { + return api.get('/dlt/statistics').then(response => { + const data = response.data + return { + front_balls: data.front_balls.map(item => ({ + number: item[0], + count: item[1] + })), + back_balls: data.back_balls.map(item => ({ + number: item[0], + count: item[1] + })) + } + }) + }, + + importDLTData(file) { + const formData = new FormData() + formData.append('file', file) + return api.post('/dlt/import', formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }) + }, + + generateSSQNumbers(params) { + return api.get('/ssq/generate', { params }) + }, + + generateDLTNumbers(params) { + return api.get('/dlt/generate', { params }) + } +} \ No newline at end of file diff --git a/frontend/src/assets/main.css b/frontend/src/assets/main.css new file mode 100644 index 0000000..ea0d3e1 --- /dev/null +++ b/frontend/src/assets/main.css @@ -0,0 +1,175 @@ +/* 全局样式 */ +html, body { + margin: 0; + padding: 0; + height: 100%; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + background: #f6f8fa; + color: #222; + font-size: 16px; +} + +#app { + height: 100%; +} + +/* 滚动条样式 */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: #c1c1c1; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #a8a8a8; +} + +/* Element Plus 主题覆盖 */ +.el-header { + background-color: #fff; + border-bottom: 1px solid #dcdfe6; + padding: 0 20px; +} + +.el-main { + background-color: #f5f7fa; + padding: 20px; +} + +.el-card { + border-radius: 14px; + box-shadow: 0 4px 24px 0 rgba(64,158,255,0.08); + border: none; + margin-bottom: 20px; +} + +.el-card__header { + padding: 18px 24px; + font-size: 18px; + font-weight: 600; + background: #f8fbff; + border-bottom: 1px solid #f0f0f0; +} + +.el-button { + border-radius: 8px; + font-size: 16px; + padding: 10px 24px; + min-width: 120px; + font-weight: 500; +} + +.el-button--primary { + background: linear-gradient(90deg, #409eff 0%, #66b1ff 100%); + border: none; +} + +.el-button--primary:hover { + background-color: #66b1ff; + border-color: #66b1ff; +} + +.el-table { + border-radius: 12px; + overflow: hidden; + font-size: 15px; +} + +.el-table th { + background: #f8fbff; + font-weight: 600; +} + +.el-pagination { + margin-top: 24px; + justify-content: center; +} + +.el-tabs__item { + font-size: 17px; + font-weight: 500; +} + +.el-input, .el-input-number { + border-radius: 8px; + font-size: 16px; +} + +.el-dialog { + border-radius: 16px; +} + +/* 动画效果 */ +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.3s ease; +} + +.fade-enter-from, +.fade-leave-to { + opacity: 0; +} + +/* 响应式布局 */ +@media screen and (max-width: 900px) { + .el-row { + flex-direction: column !important; + } + .el-col { + width: 100% !important; + max-width: 100% !important; + margin-bottom: 16px; + } + .el-card { + margin-bottom: 16px; + } + .el-button { + min-width: 90px; + font-size: 15px; + padding: 10px 0; + } +} + +@media screen and (max-width: 600px) { + body, html { + font-size: 15px; + } + .el-header h1 { + font-size: 15px !important; + } + .el-card__header { + font-size: 16px; + padding: 12px 10px; + } + .el-button { + font-size: 14px; + padding: 8px 0; + min-width: 70px; + } +} + +@media screen and (max-width: 768px) { + .el-col { + width: 100% !important; + } + + .el-form-item__label { + float: none; + display: block; + text-align: left; + padding: 0 0 8px; + } + + .el-form-item__content { + margin-left: 0 !important; + } +} \ No newline at end of file diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..2a6e784 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,23 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import router from './router' + +// 创建应用实例 +const app = createApp(App) + +// 注册 Element Plus 图标 +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +// 使用插件 +app.use(ElementPlus) +app.use(createPinia()) +app.use(router) + +// 挂载应用 +app.mount('#app') \ No newline at end of file diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..a763a75 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,36 @@ +import { createRouter, createWebHistory } from 'vue-router' + +const routes = [ + { + path: '/', + name: 'Home', + component: () => import('../views/Home.vue') + }, + { + path: '/ssq', + name: 'SSQ', + component: () => import('../views/SSQ.vue') + }, + { + path: '/dlt', + name: 'DLT', + component: () => import('../views/DLT.vue') + }, + { + path: '/statistics', + name: 'Statistics', + component: () => import('../views/Statistics.vue') + }, + { + path: '/number-generator', + name: 'NumberGenerator', + component: () => import('../views/NumberGenerator.vue') + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +export default router \ No newline at end of file diff --git a/frontend/src/views/DLT.vue b/frontend/src/views/DLT.vue new file mode 100644 index 0000000..af27f9c --- /dev/null +++ b/frontend/src/views/DLT.vue @@ -0,0 +1,403 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/Home.vue b/frontend/src/views/Home.vue new file mode 100644 index 0000000..df869bf --- /dev/null +++ b/frontend/src/views/Home.vue @@ -0,0 +1,261 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/NumberGenerator.vue b/frontend/src/views/NumberGenerator.vue new file mode 100644 index 0000000..8bcb57d --- /dev/null +++ b/frontend/src/views/NumberGenerator.vue @@ -0,0 +1,177 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/SSQ.vue b/frontend/src/views/SSQ.vue new file mode 100644 index 0000000..ab4683f --- /dev/null +++ b/frontend/src/views/SSQ.vue @@ -0,0 +1,401 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/Statistics.vue b/frontend/src/views/Statistics.vue new file mode 100644 index 0000000..192277d --- /dev/null +++ b/frontend/src/views/Statistics.vue @@ -0,0 +1,291 @@ + + + + + \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..649da80 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import path from 'path' + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': path.resolve(__dirname, 'src'), + }, + }, + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true, + }, + }, + }, +}) \ No newline at end of file