From 75b1d1645308985fa374b9209fba91710084b873 Mon Sep 17 00:00:00 2001 From: yangy Date: Wed, 4 Mar 2026 18:17:34 +0800 Subject: [PATCH] Initial commit: Audio Dashboard Management System Made-with: Cursor --- .gitignore | 62 ++ backend/.env.example | 10 + backend/.gitignore | 38 + backend/README.md | 98 ++ backend/database.py | 39 + backend/main.py | 63 ++ backend/models/__init__.py | 4 + backend/models/brand.py | 21 + backend/models/model.py | 37 + backend/requirements.txt | 7 + backend/response.py | 36 + backend/restart.bat | 48 + backend/routes/__init__.py | 4 + backend/routes/brands.py | 106 +++ backend/routes/models.py | 214 +++++ backend/schemas.py | 54 ++ backend/start.bat | 5 + backend/stop.bat | 19 + backend/test.py | 12 + backend/test_api.py | 17 + backend/test_api_simple.py | 52 ++ backend/test_response.py | 65 ++ frontend/.gitignore | 12 + frontend/README.md | 148 +++ frontend/index.html | 13 + frontend/install.bat | 38 + frontend/package-lock.json | 1338 ++++++++++++++++++++++++++++ frontend/package.json | 21 + frontend/src/App.vue | 21 + frontend/src/api/brand.js | 65 ++ frontend/src/api/model.js | 76 ++ frontend/src/layout/index.vue | 105 +++ frontend/src/main.js | 13 + frontend/src/router/index.js | 31 + frontend/src/utils/request.js | 41 + frontend/src/views/brand/index.vue | 313 +++++++ frontend/src/views/model/index.vue | 486 ++++++++++ frontend/start.bat | 18 + frontend/vite.config.js | 21 + 39 files changed, 3771 insertions(+) create mode 100644 .gitignore create mode 100644 backend/.env.example create mode 100644 backend/.gitignore create mode 100644 backend/README.md create mode 100644 backend/database.py create mode 100644 backend/main.py create mode 100644 backend/models/__init__.py create mode 100644 backend/models/brand.py create mode 100644 backend/models/model.py create mode 100644 backend/requirements.txt create mode 100644 backend/response.py create mode 100644 backend/restart.bat create mode 100644 backend/routes/__init__.py create mode 100644 backend/routes/brands.py create mode 100644 backend/routes/models.py create mode 100644 backend/schemas.py create mode 100644 backend/start.bat create mode 100644 backend/stop.bat create mode 100644 backend/test.py create mode 100644 backend/test_api.py create mode 100644 backend/test_api_simple.py create mode 100644 backend/test_response.py create mode 100644 frontend/.gitignore create mode 100644 frontend/README.md create mode 100644 frontend/index.html create mode 100644 frontend/install.bat create mode 100644 frontend/package-lock.json create mode 100644 frontend/package.json create mode 100644 frontend/src/App.vue create mode 100644 frontend/src/api/brand.js create mode 100644 frontend/src/api/model.js create mode 100644 frontend/src/layout/index.vue create mode 100644 frontend/src/main.js create mode 100644 frontend/src/router/index.js create mode 100644 frontend/src/utils/request.js create mode 100644 frontend/src/views/brand/index.vue create mode 100644 frontend/src/views/model/index.vue create mode 100644 frontend/start.bat create mode 100644 frontend/vite.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c38d4f --- /dev/null +++ b/.gitignore @@ -0,0 +1,62 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +*.manifest +*.spec + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ +.DS_Store + +# Environment +.env +.env.local +.env.*.local + +# Logs +*.log +logs/ + +# Node +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* +dist/ +.cache/ + +# Testing +coverage/ +.nyc_output/ + +# Misc +*.bak +*.tmp +.TemporaryItems/ diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..b603cfc --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,10 @@ +# Database Configuration +DATABASE_HOST=localhost +DATABASE_PORT=3306 +DATABASE_NAME=audio +DATABASE_USER=root +DATABASE_PASSWORD=root1 + +# Application Settings +APP_NAME=Audio Dashboard API +DEBUG=True diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..4a8f42a --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,38 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Environment +.env +.env.local + +# Logs +*.log diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..c3c48c4 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,98 @@ +# Audio Dashboard Backend + +耳机品牌与型号管理平台后端 API + +## 技术栈 + +- **FastAPI** - 现代高性能 Web 框架 +- **SQLAlchemy** - ORM 框架 +- **PyMySQL** - MySQL 数据库驱动 +- **Pydantic** - 数据验证 + +## 项目结构 + +``` +backend/ +├── main.py # 应用入口 +├── database.py # 数据库配置 +├── models/ # 数据模型 +│ ├── __init__.py +│ ├── brand.py # 品牌模型 +│ └── model.py # 型号模型 +├── routes/ # API 路由 +│ ├── __init__.py +│ ├── brands.py # 品牌 API +│ └── models.py # 型号 API +├── schemas.py # Pydantic 模式 +├── requirements.txt # 依赖 +├── .env # 环境变量 +└── .env.example # 环境变量示例 +``` + +## 安装 + +1. 创建虚拟环境(可选但推荐): +```bash +python -m venv venv +# Windows +venv\Scripts\activate +# Linux/Mac +source venv/bin/activate +``` + +2. 安装依赖: +```bash +pip install -r requirements.txt +``` + +3. 配置环境变量: +复制 `.env.example` 为 `.env` 并根据需要修改配置 + +## 运行 + +```bash +# 开发模式 +python main.py + +# 或使用 uvicorn +uvicorn main:app --reload --host 0.0.0.0 --port 8000 +``` + +## API 文档 + +启动后访问: +- Swagger UI: http://localhost:8000/docs +- ReDoc: http://localhost:8000/redoc + +## API 端点 + +### 品牌管理 +- `GET /api/brands/` - 获取品牌列表 +- `GET /api/brands/{id}` - 获取单个品牌 +- `POST /api/brands/` - 创建品牌 +- `PUT /api/brands/{id}` - 更新品牌 +- `DELETE /api/brands/{id}` - 删除品牌 + +### 型号管理 +- `GET /api/models/` - 获取型号列表 +- `GET /api/models/{id}` - 获取单个型号 +- `POST /api/models/` - 创建型号 +- `PUT /api/models/{id}` - 更新型号 +- `DELETE /api/models/{id}` - 删除型号 + +## 数据库配置 + +默认配置: +- Host: localhost +- Port: 3306 +- Database: audio +- User: root +- Password: root1 + +可在 `.env` 文件中修改配置。 + +## 注意事项 + +1. 首次运行前请确保数据库已创建且表结构存在 +2. 生产环境请修改 CORS 配置,限制允许的域名 +3. 生产环境请使用更安全的密码管理方式 diff --git a/backend/database.py b/backend/database.py new file mode 100644 index 0000000..da4bf67 --- /dev/null +++ b/backend/database.py @@ -0,0 +1,39 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +import os +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +# Database configuration +DATABASE_HOST = os.getenv("DATABASE_HOST", "localhost") +DATABASE_PORT = os.getenv("DATABASE_PORT", "3306") +DATABASE_NAME = os.getenv("DATABASE_NAME", "audio") +DATABASE_USER = os.getenv("DATABASE_USER", "root") +DATABASE_PASSWORD = os.getenv("DATABASE_PASSWORD", "root123") + +# Create database URL (same format as working code) +DATABASE_URL = f"mysql+pymysql://{DATABASE_USER}:{DATABASE_PASSWORD}@{DATABASE_HOST}:{DATABASE_PORT}/{DATABASE_NAME}?charset=utf8mb4" + +# Create engine (same config as working code) +engine = create_engine( + DATABASE_URL, + echo=os.getenv("DEBUG", "False") == "True" +) + +# Create session factory +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +# Base class for models +Base = declarative_base() + + +def get_db(): + """Database session dependency""" + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..e8507bf --- /dev/null +++ b/backend/main.py @@ -0,0 +1,63 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +import os +from dotenv import load_dotenv + +from database import engine, Base +from routes import brands_router, models_router + +# Load environment variables +load_dotenv() + +# Create database tables (only if tables don't exist) +try: + Base.metadata.create_all(bind=engine) +except Exception as e: + print(f"Warning: Could not create tables: {e}") + print("Continuing without table creation...") + +# Create FastAPI app +app = FastAPI( + title=os.getenv("APP_NAME", "Audio Dashboard API"), + description="耳机品牌与型号管理平台后端 API", + version="1.0.0" +) + +# Configure CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # 生产环境应该限制具体域名 + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include routers +app.include_router(brands_router) +app.include_router(models_router) + + +@app.get("/") +def root(): + """根路径""" + return { + "message": "欢迎使用 Audio Dashboard API", + "docs": "/docs", + "redoc": "/redoc" + } + + +@app.get("/health") +def health_check(): + """健康检查""" + return {"status": "healthy"} + + +if __name__ == "__main__": + import uvicorn + uvicorn.run( + "main:app", + host="0.0.0.0", + port=8002, + reload=False + ) diff --git a/backend/models/__init__.py b/backend/models/__init__.py new file mode 100644 index 0000000..8ed3601 --- /dev/null +++ b/backend/models/__init__.py @@ -0,0 +1,4 @@ +from models.brand import Brand +from models.model import Model + +__all__ = ["Brand", "Model"] diff --git a/backend/models/brand.py b/backend/models/brand.py new file mode 100644 index 0000000..2e1b1a2 --- /dev/null +++ b/backend/models/brand.py @@ -0,0 +1,21 @@ +from sqlalchemy import Column, Integer, String +from database import Base + + +class Brand(Base): + """耳机品牌模型""" + __tablename__ = "brand" + __table_args__ = {"comment": "耳机品牌","extend_existing": True} + + id = Column(Integer, primary_key=True, autoincrement=True, comment="品牌 ID") + name = Column(String(100), unique=True, nullable=False, comment="品牌名称") + + def __repr__(self): + return f"" + + def to_dict(self): + """Convert to dictionary""" + return { + "id": self.id, + "name": self.name + } diff --git a/backend/models/model.py b/backend/models/model.py new file mode 100644 index 0000000..d75e926 --- /dev/null +++ b/backend/models/model.py @@ -0,0 +1,37 @@ +from sqlalchemy import Column, Integer, String, DateTime, func +from database import Base + + +class Model(Base): + """耳机型号模型""" + __tablename__ = "model" + __table_args__ = {"comment": "耳机型号"} + + id = Column(Integer, primary_key=True, autoincrement=True, comment="型号 ID") + brand_name = Column(String(100), nullable=False, comment="品牌名称") + name = Column(String(100), nullable=False, comment="型号名称") + form = Column(String(100), nullable=True, comment="形式") + rig = Column(String(100), nullable=True, comment="阻抗") + source = Column(String(100), nullable=True, comment="来源") + eq_key = Column(String(255), nullable=True, comment="EQ 键") + create_at = Column(DateTime, nullable=False, default=func.now(), comment="创建时间") + + __table_args__ = ( + {"comment": "耳机型号"}, + ) + + def __repr__(self): + return f"" + + def to_dict(self): + """Convert to dictionary""" + return { + "id": self.id, + "brand_name": self.brand_name, + "name": self.name, + "form": self.form, + "rig": self.rig, + "source": self.source, + "eq_key": self.eq_key, + "create_at": self.create_at.isoformat() if self.create_at else None + } diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..706af28 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,7 @@ +fastapi==0.104.1 +sqlalchemy==2.0.23 +pymysql==1.1.0 +cryptography==41.0.7 +pydantic==2.5.2 +uvicorn==0.24.0 +python-dotenv==1.0.0 diff --git a/backend/response.py b/backend/response.py new file mode 100644 index 0000000..56f8f50 --- /dev/null +++ b/backend/response.py @@ -0,0 +1,36 @@ +from typing import Optional, Any, List, Union +from pydantic import BaseModel, ConfigDict + + +class PageData(BaseModel): + """分页数据""" + model_config = ConfigDict(from_attributes=True) + + items: List[Any] + total: int + skip: int + limit: int + + +class ApiResponse(BaseModel): + """统一 API 响应格式""" + model_config = ConfigDict(from_attributes=True) + + code: int = 0 + msg: str = "success" + data: Optional[Any] = None + + @classmethod + def success(cls, data: Any = None, msg: str = "success"): + """成功响应""" + return cls(code=1, msg=msg, data=data) + + @classmethod + def error(cls, msg: str = "error", code: int = 0): + """错误响应""" + return cls(code=code, msg=msg, data=None) + + @classmethod + def no_data(cls, msg: str = "no data"): + """无数据响应""" + return cls(code=2, msg=msg, data=None) diff --git a/backend/restart.bat b/backend/restart.bat new file mode 100644 index 0000000..4075089 --- /dev/null +++ b/backend/restart.bat @@ -0,0 +1,48 @@ +@echo off +echo ================================================ +echo Audio Dashboard API - 重启服务 +echo ================================================ +echo. + +REM 切换到脚本所在目录 +cd /d "%~dp0" + +echo [1/3] 停止服务... +echo. + +REM 查找占用 8002 端口的进程 +set PID= +for /f "tokens=5" %%a in ('netstat -ano ^| findstr :8002') do ( + set PID=%%a + goto :found +) + +:found +if defined PID ( + echo 找到进程 PID: %PID% + taskkill /F /PID %PID% >nul 2>&1 + if errorlevel 1 ( + echo 无法终止进程 %PID%,可能已经停止 + ) else ( + echo 服务已停止 + ) + timeout /t 2 /nobreak >nul +) else ( + echo 端口 8002 没有被占用 +) + +echo. +echo [2/3] 等待端口释放... +timeout /t 2 /nobreak >nul + +echo. +echo [3/3] 启动服务... +echo. +echo 服务运行在 http://localhost:8002 +echo API 文档:http://localhost:8002/docs +echo. +echo 按 Ctrl+C 停止服务 +echo ================================================ +echo. + +python main.py diff --git a/backend/routes/__init__.py b/backend/routes/__init__.py new file mode 100644 index 0000000..4689497 --- /dev/null +++ b/backend/routes/__init__.py @@ -0,0 +1,4 @@ +from routes.brands import router as brands_router +from routes.models import router as models_router + +__all__ = ["brands_router", "models_router"] diff --git a/backend/routes/brands.py b/backend/routes/brands.py new file mode 100644 index 0000000..5e462a4 --- /dev/null +++ b/backend/routes/brands.py @@ -0,0 +1,106 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.orm import Session +from typing import List, Optional +from database import get_db +from models.brand import Brand +from schemas import BrandCreate, BrandUpdate, BrandResponse +from response import ApiResponse, PageData + +router = APIRouter(prefix="/api/brands", tags=["brands"]) + + +@router.get("/", response_model=ApiResponse) +def get_brands( + skip: int = Query(0, ge=0, description="跳过记录数"), + limit: int = Query(100, ge=1, le=1000, description="返回记录数"), + name: Optional[str] = Query(None, description="品牌名称(支持模糊查询)"), + db: Session = Depends(get_db) +): + """获取所有品牌列表(支持按名称模糊查询)""" + try: + query = db.query(Brand) + if name: + query = query.filter(Brand.name.like(f"%{name}%")) + + total = query.count() + brands = query.offset(skip).limit(limit).all() + + if not brands: + return ApiResponse(code=2, msg="empty", data=None) + + # 转换为字典列表 + brands_data = [brand.to_dict() for brand in brands] + + return ApiResponse(code=1, msg="success", data={"items": brands_data, "total": total, "skip": skip, "limit": limit}) + except Exception as e: + return ApiResponse(code=0, msg="error", data=None) + + +@router.get("/{brand_id}", response_model=ApiResponse) +def get_brand(brand_id: int, db: Session = Depends(get_db)): + """获取单个品牌""" + try: + brand = db.query(Brand).filter(Brand.id == brand_id).first() + if not brand: + return ApiResponse(code=2, msg="empty", data=None) + return ApiResponse(code=1, msg="success", data=brand.to_dict()) + except Exception as e: + return ApiResponse(code=0, msg="error", data=None) + + +@router.post("/", response_model=ApiResponse) +def create_brand(brand: BrandCreate, db: Session = Depends(get_db)): + """创建新品牌""" + try: + # 检查是否已存在 + existing = db.query(Brand).filter(Brand.name == brand.name).first() + if existing: + return ApiResponse.error(msg="品牌名称已存在", code=0) + + db_brand = Brand(name=brand.name) + db.add(db_brand) + db.commit() + db.refresh(db_brand) + return ApiResponse.success(data=db_brand.to_dict(), msg="品牌创建成功") + except Exception as e: + db.rollback() + return ApiResponse.error(msg=f"创建失败:{str(e)}", code=0) + + +@router.put("/{brand_id}", response_model=ApiResponse) +def update_brand(brand_id: int, brand: BrandUpdate, db: Session = Depends(get_db)): + """更新品牌""" + try: + db_brand = db.query(Brand).filter(Brand.id == brand_id).first() + if not db_brand: + return ApiResponse.no_data(msg="品牌不存在") + + # 如果更新名称,检查是否冲突 + if brand.name and brand.name != db_brand.name: + existing = db.query(Brand).filter(Brand.name == brand.name).first() + if existing: + return ApiResponse.error(msg="品牌名称已存在", code=0) + db_brand.name = brand.name + + db.commit() + db.refresh(db_brand) + return ApiResponse.success(data=db_brand.to_dict(), msg="品牌更新成功") + except Exception as e: + db.rollback() + return ApiResponse.error(msg=f"更新失败:{str(e)}", code=0) + + +@router.delete("/{brand_id}", response_model=ApiResponse) +def delete_brand(brand_id: int, db: Session = Depends(get_db)): + """删除品牌""" + try: + db_brand = db.query(Brand).filter(Brand.id == brand_id).first() + if not db_brand: + return ApiResponse.no_data(msg="品牌不存在") + + db.delete(db_brand) + db.commit() + return ApiResponse.success(msg="删除成功") + except Exception as e: + db.rollback() + return ApiResponse.error(msg=f"删除失败:{str(e)}", code=0) diff --git a/backend/routes/models.py b/backend/routes/models.py new file mode 100644 index 0000000..e4d5de3 --- /dev/null +++ b/backend/routes/models.py @@ -0,0 +1,214 @@ +from fastapi import APIRouter, Depends, HTTPException, Query, Body +from sqlalchemy.orm import Session +from typing import List, Optional +from database import get_db +from models.model import Model +from schemas import ModelCreate, ModelUpdate, ModelResponse +from response import ApiResponse, PageData +import requests +import os + +router = APIRouter(prefix="/api/models", tags=["models"]) + +# Meilisearch 配置(从环境变量读取) +MEILISEARCH_URL = os.getenv("MEILISEARCH_URL", "http://localhost:7700") +MEILISEARCH_API_KEY = os.getenv("MEILISEARCH_API_KEY", "") +MEILISEARCH_INDEX = os.getenv("MEILISEARCH_INDEX", "models") + + +@router.get("/", response_model=ApiResponse) +def get_models( + skip: int = Query(0, ge=0, description="跳过记录数"), + limit: int = Query(100, ge=1, le=1000, description="返回记录数"), + brand_name: Optional[str] = Query(None, description="按品牌名称模糊查询"), + name: Optional[str] = Query(None, description="按型号名称模糊查询"), + db: Session = Depends(get_db) +): + """获取所有型号列表(支持按品牌名称和型号名称模糊查询)""" + try: + query = db.query(Model) + if brand_name: + query = query.filter(Model.brand_name.like(f"%{brand_name}%")) + if name: + query = query.filter(Model.name.like(f"%{name}%")) + + total = query.count() + models = query.offset(skip).limit(limit).all() + + if not models: + return ApiResponse(code=2, msg="empty", data=None) + + # 转换为字典列表 + models_data = [model.to_dict() for model in models] + + return ApiResponse(code=1, msg="success", data={"items": models_data, "total": total, "skip": skip, "limit": limit}) + except Exception as e: + return ApiResponse(code=0, msg="error", data=None) + + +@router.get("/{model_id}", response_model=ApiResponse) +def get_model(model_id: int, db: Session = Depends(get_db)): + """获取单个型号""" + try: + model = db.query(Model).filter(Model.id == model_id).first() + if not model: + return ApiResponse(code=2, msg="empty", data=None) + return ApiResponse(code=1, msg="success", data=model.to_dict()) + except Exception as e: + return ApiResponse(code=0, msg="error", data=None) + + +@router.post("/", response_model=ApiResponse) +def create_model(model: ModelCreate, db: Session = Depends(get_db)): + """创建新型号""" + try: + # 检查是否已存在 + existing = db.query(Model).filter( + Model.brand_name == model.brand_name, + Model.name == model.name + ).first() + if existing: + return ApiResponse(code=0, msg="该品牌下型号名称已存在", data=None) + + db_model = Model( + brand_name=model.brand_name, + name=model.name, + form=model.form, + rig=model.rig, + source=model.source, + eq_key=model.eq_key + ) + db.add(db_model) + db.commit() + db.refresh(db_model) + return ApiResponse(code=1, msg="success", data=db_model.to_dict()) + except Exception as e: + db.rollback() + return ApiResponse(code=0, msg="error", data=None) + + +@router.put("/{model_id}", response_model=ApiResponse) +def update_model(model_id: int, model: ModelUpdate, db: Session = Depends(get_db)): + """更新型号""" + try: + db_model = db.query(Model).filter(Model.id == model_id).first() + if not db_model: + return ApiResponse(code=2, msg="empty", data=None) + + # 如果更新品牌或型号名称,检查是否冲突 + if model.brand_name or model.name: + new_brand_name = model.brand_name or db_model.brand_name + new_name = model.name or db_model.name + + if new_brand_name != db_model.brand_name or new_name != db_model.name: + existing = db.query(Model).filter( + Model.brand_name == new_brand_name, + Model.name == new_name + ).first() + if existing: + return ApiResponse(code=0, msg="该品牌下型号名称已存在", data=None) + + # 更新字段 + if model.brand_name: + db_model.brand_name = model.brand_name + if model.name: + db_model.name = model.name + if model.form is not None: + db_model.form = model.form + if model.rig is not None: + db_model.rig = model.rig + if model.source is not None: + db_model.source = model.source + if model.eq_key is not None: + db_model.eq_key = model.eq_key + + db.commit() + db.refresh(db_model) + return ApiResponse(code=1, msg="success", data=db_model.to_dict()) + except Exception as e: + db.rollback() + return ApiResponse(code=0, msg="error", data=None) + + +@router.delete("/{model_id}", response_model=ApiResponse) +def delete_model(model_id: int, db: Session = Depends(get_db)): + """删除型号""" + try: + db_model = db.query(Model).filter(Model.id == model_id).first() + if not db_model: + return ApiResponse(code=2, msg="empty", data=None) + + db.delete(db_model) + db.commit() + return ApiResponse(code=1, msg="success", data=None) + except Exception as e: + db.rollback() + return ApiResponse(code=0, msg="error", data=None) + + +@router.post("/push-to-search", response_model=ApiResponse) +def push_to_search( + model_ids: List[int] = Body(..., embed=True, description="型号 ID 列表"), + db: Session = Depends(get_db) +): + """推送型号数据到 Meilisearch""" + try: + if not model_ids: + return ApiResponse(code=0, msg="请选择要推送的型号", data=None) + + # 查询选中的型号 + models = db.query(Model).filter(Model.id.in_(model_ids)).all() + + if not models: + return ApiResponse(code=0, msg="未找到选中的型号数据", data=None) + + # 准备推送数据 + push_data = [] + for model in models: + model_dict = { + 'id': model.id, + 'brand_name': model.brand_name, + 'name': model.name, + 'rig': model.rig, + 'form': model.form, + 'source': model.source + } + # 只包含有值的字段 + push_data.append({k: v for k, v in model_dict.items() if v is not None}) + + # 推送到 Meilisearch + headers = { + "Authorization": f"Bearer {MEILISEARCH_API_KEY}", + "Content-Type": "application/json" + } + + # 如果索引不存在,先创建索引 + try: + # 更新或添加文档 + response = requests.post( + f"{MEILISEARCH_URL}/indexes/{MEILISEARCH_INDEX}/documents", + json=push_data, + headers=headers, + timeout=30 + ) + + if response.status_code not in [200, 202]: + return ApiResponse(code=0, msg=f"推送到 Meilisearch 失败:{response.text}", data=None) + + task_info = response.json() + + return ApiResponse( + code=1, + msg="success", + data={ + "pushed_count": len(push_data), + "task_uid": task_info.get("taskUid"), + "models": push_data + } + ) + + except requests.exceptions.RequestException as e: + return ApiResponse(code=0, msg=f"连接 Meilisearch 失败:{str(e)}", data=None) + + except Exception as e: + return ApiResponse(code=0, msg="error", data=None) diff --git a/backend/schemas.py b/backend/schemas.py new file mode 100644 index 0000000..5b3c0ad --- /dev/null +++ b/backend/schemas.py @@ -0,0 +1,54 @@ +from pydantic import BaseModel, Field +from typing import Optional +from datetime import datetime + + +# Brand Schemas +class BrandBase(BaseModel): + name: str = Field(..., min_length=1, max_length=100, description="品牌名称") + + +class BrandCreate(BrandBase): + pass + + +class BrandUpdate(BaseModel): + name: Optional[str] = Field(None, min_length=1, max_length=100, description="品牌名称") + + +class BrandResponse(BrandBase): + id: int + + class Config: + from_attributes = True + + +# Model Schemas +class ModelBase(BaseModel): + brand_name: str = Field(..., min_length=1, max_length=100, description="品牌名称") + name: str = Field(..., min_length=1, max_length=100, description="型号名称") + form: Optional[str] = Field(None, max_length=100, description="形式") + rig: Optional[str] = Field(None, max_length=100, description="阻抗") + source: Optional[str] = Field(None, max_length=100, description="来源") + eq_key: Optional[str] = Field(None, max_length=255, description="EQ 键") + + +class ModelCreate(ModelBase): + pass + + +class ModelUpdate(BaseModel): + brand_name: Optional[str] = Field(None, min_length=1, max_length=100, description="品牌名称") + name: Optional[str] = Field(None, min_length=1, max_length=100, description="型号名称") + form: Optional[str] = Field(None, max_length=100, description="形式") + rig: Optional[str] = Field(None, max_length=100, description="阻抗") + source: Optional[str] = Field(None, max_length=100, description="来源") + eq_key: Optional[str] = Field(None, max_length=255, description="EQ 键") + + +class ModelResponse(ModelBase): + id: int + create_at: datetime + + class Config: + from_attributes = True diff --git a/backend/start.bat b/backend/start.bat new file mode 100644 index 0000000..e2ff1c1 --- /dev/null +++ b/backend/start.bat @@ -0,0 +1,5 @@ +@echo off +echo Starting Audio Dashboard API... +cd /d "%~dp0" +python main.py +pause diff --git a/backend/stop.bat b/backend/stop.bat new file mode 100644 index 0000000..ae73f7c --- /dev/null +++ b/backend/stop.bat @@ -0,0 +1,19 @@ +@echo off +echo Stopping Audio Dashboard API... + +REM 查找占用 8002 端口的进程 +for /f "tokens=5" %%a in ('netstat -ano ^| findstr :8002') do ( + set PID=%%a + goto :found +) + +:found +if defined PID ( + echo Found process PID: %PID% + taskkill /F /PID %PID% + echo Service stopped. +) else ( + echo No service running on port 8002. +) + +pause diff --git a/backend/test.py b/backend/test.py new file mode 100644 index 0000000..f8dd9a8 --- /dev/null +++ b/backend/test.py @@ -0,0 +1,12 @@ +from database import get_db +from models import Brand + +if __name__ == "__main__": + db_gen = get_db() + db = next(db_gen) + try: + brands = db.query(Brand).all() + for brand in brands: + print(brand.name) + finally: + db_gen.close() # 这会触发 finally 块中的 db.close() \ No newline at end of file diff --git a/backend/test_api.py b/backend/test_api.py new file mode 100644 index 0000000..2353c5c --- /dev/null +++ b/backend/test_api.py @@ -0,0 +1,17 @@ +import requests +import json + +try: + response = requests.get("http://localhost:8001/api/brands/", params={"skip": 0, "limit": 10}) + print(f"Status Code: {response.status_code}") + print(f"Content-Type: {response.headers.get('content-type')}") + print(f"Response: {response.text[:500]}") + if response.status_code == 200: + data = response.json() + print(f"\nSuccess! Got {len(data)} brands") + for brand in data[:5]: + print(f" - {brand}") +except Exception as e: + print(f"Error: {e}") + import traceback + traceback.print_exc() diff --git a/backend/test_api_simple.py b/backend/test_api_simple.py new file mode 100644 index 0000000..988103c --- /dev/null +++ b/backend/test_api_simple.py @@ -0,0 +1,52 @@ +import requests +import json + +BASE_URL = "http://localhost:8002" + +def test_api(endpoint, method="GET", data=None, params=None): + """测试 API 接口""" + url = f"{BASE_URL}{endpoint}" + try: + if method == "GET": + response = requests.get(url, params=params) + elif method == "POST": + response = requests.post(url, json=data, params=params) + elif method == "PUT": + response = requests.put(url, json=data) + elif method == "DELETE": + response = requests.delete(url) + + result = response.json() + print(f"\n{method} {endpoint}") + print(f"Status: {response.status_code}") + print(f"Response: {json.dumps(result, ensure_ascii=False, indent=2)}") + return result + except Exception as e: + print(f"\n{method} {endpoint} - Error: {e}") + import traceback + traceback.print_exc() + return None + +# 测试品牌接口 +print("=" * 60) +print("测试品牌接口") +print("=" * 60) + +# 1. 获取品牌列表(成功,有数据) +test_api("/api/brands/", params={"skip": 0, "limit": 5}) + +# 2. 模糊查询品牌(成功,有数据) +test_api("/api/brands/", params={"skip": 0, "limit": 10, "name": "Audio"}) + +# 3. 模糊查询品牌(无数据) +test_api("/api/brands/", params={"skip": 0, "limit": 10, "name": "不存在的品牌 XYZ"}) + +# 4. 获取单个品牌(成功) +test_api("/api/brands/1") + +# 5. 获取单个品牌(不存在) +test_api("/api/brands/999999") + +print("\n" + "=" * 60) +print("测试完成!") +print("=" * 60) diff --git a/backend/test_response.py b/backend/test_response.py new file mode 100644 index 0000000..e1ad996 --- /dev/null +++ b/backend/test_response.py @@ -0,0 +1,65 @@ +import requests +import json + +BASE_URL = "http://localhost:8002" + +def test_api(endpoint, method="GET", data=None, params=None): + """测试 API 接口""" + url = f"{BASE_URL}{endpoint}" + try: + if method == "GET": + response = requests.get(url, params=params) + elif method == "POST": + response = requests.post(url, json=data, params=params) + elif method == "PUT": + response = requests.put(url, json=data) + elif method == "DELETE": + response = requests.delete(url) + + result = response.json() + print(f"\n{method} {endpoint}") + print(f"Status: {response.status_code}") + print(f"Response: {json.dumps(result, ensure_ascii=False, indent=2)}") + return result + except Exception as e: + print(f"\n{method} {endpoint} - Error: {e}") + return None + +# 测试品牌接口 +print("=" * 60) +print("测试品牌接口") +print("=" * 60) + +# 1. 获取品牌列表(成功,有数据) +test_api("/api/brands/", params={"skip": 0, "limit": 5}) + +# 2. 模糊查询品牌(成功,有数据) +test_api("/api/brands/", params={"skip": 0, "limit": 10, "name": "Audio"}) + +# 3. 模糊查询品牌(无数据) +test_api("/api/brands/", params={"skip": 0, "limit": 10, "name": "不存在的品牌XYZ"}) + +# 4. 获取单个品牌(成功) +test_api("/api/brands/1") + +# 5. 获取单个品牌(不存在) +test_api("/api/brands/999999") + +# 6. 创建品牌(成功) +test_api("/api/brands/", method="POST", data={"name": "Test Brand 测试"}) + +# 7. 创建品牌(重复) +test_api("/api/brands/", method="POST", data={"name": "1MORE"}) + +# 8. 更新品牌(成功) +test_api("/api/brands/1", method="PUT", data={"name": "1MORE Updated"}) + +# 9. 删除品牌(成功) +test_api("/api/brands/623", method="DELETE") + +# 10. 删除品牌(不存在) +test_api("/api/brands/999999", method="DELETE") + +print("\n" + "=" * 60) +print("测试完成!") +print("=" * 60) diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..c1f27a5 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,12 @@ +node_modules/ +dist/ +*.log +.DS_Store +Thumbs.db +.vscode/ +.idea/ +*.swp +*.swo +.env +.env.local +.env.*.local diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..7c30664 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,148 @@ +# 耳机管理平台 - 前端项目 + +## 技术栈 + +- **Vue 3** - 渐进式 JavaScript 框架 +- **Vue Router 4** - Vue.js 官方路由 +- **Element Plus** - 基于 Vue 3 的组件库 +- **Axios** - HTTP 客户端 +- **Vite** - 下一代前端构建工具 + +## 项目结构 + +``` +frontend/ +├── src/ +│ ├── api/ # API 接口 +│ │ └── brand.js # 品牌管理接口 +│ ├── layout/ # 布局组件 +│ │ └── index.vue # 主布局 +│ ├── router/ # 路由配置 +│ │ └── index.js # 路由定义 +│ ├── utils/ # 工具函数 +│ │ └── request.js # Axios 封装 +│ ├── views/ # 页面组件 +│ │ └── brand/ # 品牌管理页面 +│ │ └── index.vue +│ ├── App.vue # 根组件 +│ └── main.js # 入口文件 +├── index.html # HTML 模板 +├── package.json # 依赖配置 +├── vite.config.js # Vite 配置 +├── install.bat # 安装依赖脚本 +└── start.bat # 启动脚本 +``` + +## 快速开始 + +### 1. 安装依赖 + +**方式一:使用脚本(推荐)** +```bash +双击 install.bat +``` + +**方式二:手动安装** +```bash +cd frontend +pnpm install +``` + +### 2. 启动开发服务器 + +**方式一:使用脚本(推荐)** +```bash +双击 start.bat +``` + +**方式二:手动启动** +```bash +cd frontend +pnpm dev +``` + +启动后访问:http://localhost:3000 + +### 3. 构建生产版本 + +```bash +pnpm build +``` + +## 功能特性 + +### 品牌管理 + +- ✅ 品牌列表展示 +- ✅ 品牌名称模糊搜索 +- ✅ 新增品牌 +- ✅ 编辑品牌 +- ✅ 删除品牌 +- ✅ 分页功能 + +### 菜单结构 + +``` +耳机管理(一级菜单) + └── 品牌管理(二级菜单) +``` + +## API 配置 + +开发服务器已配置代理,将 `/api` 请求转发到后端服务: + +- 前端请求:`/api/brands/` +- 后端地址:`http://localhost:8002/api/brands/` + +配置位于 `vite.config.js` + +## 注意事项 + +1. **确保后端服务已启动** + - 后端运行在 http://localhost:8002 + - 使用 `backend/restart.bat` 重启后端服务 + +2. **端口占用** + - 前端使用 3000 端口 + - 后端使用 8002 端口 + - 如果端口被占用,请修改相应配置文件 + +3. **跨域问题** + - 开发环境使用 Vite 代理解决跨域 + - 生产环境需要配置 CORS 或使用 Nginx 反向代理 + +## 常见问题 + +### Q: 依赖安装失败 +A: 检查网络连接,或使用淘宝镜像: +```bash +pnpm config set registry https://registry.npmmirror.com +pnpm install +``` + +如果还没有安装 pnpm,请先安装: +```bash +npm install -g pnpm +``` + +### Q: 页面空白 +A: 打开浏览器控制台查看错误信息,确保后端服务正常运行 + +### Q: API 请求失败 +A: 检查后端服务是否启动,端口是否正确 + +## 开发规范 + +- 使用 Vue 3 Composition API (script setup) +- 使用 Element Plus 组件库 +- 统一使用中文注释和提示 +- API 响应格式:`{ code: 1, msg: "success", data: {...} }` + +## 后续扩展 + +可以添加的功能: +- [ ] 型号管理页面 +- [ ] 用户登录/权限管理 +- [ ] 数据导出功能 +- [ ] 批量操作 +- [ ] 操作日志 diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..ba4653f --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + 耳机管理平台 + + +
+ + + diff --git a/frontend/install.bat b/frontend/install.bat new file mode 100644 index 0000000..f96ffde --- /dev/null +++ b/frontend/install.bat @@ -0,0 +1,38 @@ +@echo off +chcp 65001 >nul +echo ================================================ +echo 前端项目 - 安装依赖 +echo ================================================ +echo. + +cd /d "%~dp0" + +echo 正在安装依赖... +echo. + +REM 使用官方镜像安装 +pnpm config set registry https://registry.npmjs.org +pnpm install --registry=https://registry.npmjs.org + +if errorlevel 1 ( + echo. + echo 依赖安装失败!请检查网络连接。 + echo 尝试使用淘宝镜像... + echo. + pnpm config set registry https://registry.npmmirror.com + pnpm install +) + +if errorlevel 1 ( + echo. + echo 依赖安装失败!请检查网络连接。 + pause +) else ( + echo. + echo 依赖安装完成! + echo. + echo 运行以下命令启动项目: + echo pnpm dev + echo. + pause +) diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..b4341c7 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1338 @@ +{ + "name": "audio-dashboard-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "audio-dashboard-frontend", + "version": "1.0.0", + "dependencies": { + "axios": "^1.5.0", + "element-plus": "^2.3.14", + "vue": "^3.3.4", + "vue-router": "^4.2.4" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.3.4", + "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.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "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.2", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/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.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "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.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "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.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz", + "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.29", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz", + "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz", + "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.29", + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz", + "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "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.29", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.29.tgz", + "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.29.tgz", + "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.29", + "@vue/shared": "3.5.29" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz", + "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.29", + "@vue/runtime-core": "3.5.29", + "@vue/shared": "3.5.29", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.29.tgz", + "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.29", + "@vue/shared": "3.5.29" + }, + "peerDependencies": { + "vue": "3.5.29" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.29", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.29.tgz", + "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/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/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/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/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.13.6", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "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/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.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "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/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/element-plus": { + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.3.tgz", + "integrity": "sha512-RwLVtFpeHjZ4UCtHxVi1/sGR2cr2xOL7hqWa7qJc/+gdO6QavVG8Nw1C647obCb3tIg2ztMhNbIIjZUv+6z1og==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^3.4.1", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "^10.11.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.19", + "lodash": "^4.17.23", + "lodash-es": "^4.17.23", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.3.0" + } + }, + "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.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/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/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "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/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/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/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/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/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/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "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/rollup": { + "version": "3.30.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.30.0.tgz", + "integrity": "sha512-kQvGasUgN+AlWGliFn2POSajRQEsULVYFGTvOZmK06d7vCD+YhZztt70kGk3qaeAXeWYL5eO7zx+rAubBc55eA==", + "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/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/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.29", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz", + "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.29", + "@vue/compiler-sfc": "3.5.29", + "@vue/runtime-dom": "3.5.29", + "@vue/server-renderer": "3.5.29", + "@vue/shared": "3.5.29" + }, + "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..9ec0a6c --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,21 @@ +{ + "name": "audio-dashboard-frontend", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.3.4", + "vue-router": "^4.2.4", + "axios": "^1.5.0", + "element-plus": "^2.3.14" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^4.3.4", + "vite": "^4.4.9" + }, + "packageManager": "pnpm@8.0.0" +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..109c6f6 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/frontend/src/api/brand.js b/frontend/src/api/brand.js new file mode 100644 index 0000000..2cf1763 --- /dev/null +++ b/frontend/src/api/brand.js @@ -0,0 +1,65 @@ +import request from '@/utils/request' + +/** + * 获取品牌列表 + * @param {Object} params - 查询参数 + * @param {number} params.skip - 跳过记录数 + * @param {number} params.limit - 返回记录数 + * @param {string} params.name - 品牌名称(模糊查询) + */ +export function getBrands(params) { + return request({ + url: '/brands/', + method: 'get', + params + }) +} + +/** + * 获取单个品牌 + * @param {number} id - 品牌 ID + */ +export function getBrand(id) { + return request({ + url: `/brands/${id}`, + method: 'get' + }) +} + +/** + * 创建品牌 + * @param {Object} data - 品牌数据 + * @param {string} data.name - 品牌名称 + */ +export function createBrand(data) { + return request({ + url: '/brands/', + method: 'post', + data + }) +} + +/** + * 更新品牌 + * @param {Object} data - 品牌数据 + * @param {number} data.id - 品牌 ID + * @param {string} data.name - 品牌名称 + */ +export function updateBrand(data) { + return request({ + url: `/brands/${data.id}`, + method: 'put', + data + }) +} + +/** + * 删除品牌 + * @param {number} id - 品牌 ID + */ +export function deleteBrand(id) { + return request({ + url: `/brands/${id}`, + method: 'delete' + }) +} diff --git a/frontend/src/api/model.js b/frontend/src/api/model.js new file mode 100644 index 0000000..64e0dae --- /dev/null +++ b/frontend/src/api/model.js @@ -0,0 +1,76 @@ +import request from '@/utils/request' + +/** + * 获取型号列表 + * @param {Object} params - 查询参数 + * @param {number} params.skip - 跳过记录数 + * @param {number} params.limit - 返回记录数 + * @param {string} params.brand_name - 品牌名称(模糊查询) + * @param {string} params.name - 型号名称(模糊查询) + */ +export function getModels(params) { + return request({ + url: '/models/', + method: 'get', + params + }) +} + +/** + * 获取单个型号 + * @param {number} id - 型号 ID + */ +export function getModel(id) { + return request({ + url: `/models/${id}`, + method: 'get' + }) +} + +/** + * 创建型号 + * @param {Object} data - 型号数据 + */ +export function createModel(data) { + return request({ + url: '/models/', + method: 'post', + data + }) +} + +/** + * 更新型号 + * @param {Object} data - 型号数据 + * @param {number} data.id - 型号 ID + */ +export function updateModel(data) { + return request({ + url: `/models/${data.id}`, + method: 'put', + data + }) +} + +/** + * 删除型号 + * @param {number} id - 型号 ID + */ +export function deleteModel(id) { + return request({ + url: `/models/${id}`, + method: 'delete' + }) +} + +/** + * 推送到 Meilisearch + * @param {Array} modelIds - 型号 ID 列表 + */ +export function pushToMeilisearch(modelIds) { + return request({ + url: '/models/push-to-search', + method: 'post', + data: { model_ids: modelIds } + }) +} diff --git a/frontend/src/layout/index.vue b/frontend/src/layout/index.vue new file mode 100644 index 0000000..79e3666 --- /dev/null +++ b/frontend/src/layout/index.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..69cc997 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,13 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import zhCn from 'element-plus/dist/locale/zh-cn.mjs' +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +app.use(ElementPlus, { locale: zhCn }) +app.use(router) + +app.mount('#app') diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..d1e7ac3 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,31 @@ +import { createRouter, createWebHistory } from 'vue-router' +import Layout from '@/layout/index.vue' + +const routes = [ + { + path: '/', + component: Layout, + redirect: '/brand', + children: [ + { + path: '/brand', + name: 'Brand', + component: () => import('@/views/brand/index.vue'), + meta: { title: '品牌管理', icon: 'collection' } + }, + { + path: '/model', + name: 'Model', + component: () => import('@/views/model/index.vue'), + meta: { title: '型号管理', icon: 'list' } + } + ] + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +export default router diff --git a/frontend/src/utils/request.js b/frontend/src/utils/request.js new file mode 100644 index 0000000..961a2df --- /dev/null +++ b/frontend/src/utils/request.js @@ -0,0 +1,41 @@ +import axios from 'axios' +import { ElMessage } from 'element-plus' + +// 创建 axios 实例 +const request = axios.create({ + baseURL: '/api', + timeout: 10000 +}) + +// 请求拦截器 +request.interceptors.request.use( + config => { + return config + }, + error => { + console.error('请求错误:', error) + return Promise.reject(error) + } +) + +// 响应拦截器 +request.interceptors.response.use( + response => { + const res = response.data + + // 如果响应码为 0,表示错误 + if (res.code === 0) { + ElMessage.error(res.msg || '请求失败') + return Promise.reject(new Error(res.msg || '请求失败')) + } + + return res + }, + error => { + console.error('响应错误:', error) + ElMessage.error(error.message || '网络错误') + return Promise.reject(error) + } +) + +export default request diff --git a/frontend/src/views/brand/index.vue b/frontend/src/views/brand/index.vue new file mode 100644 index 0000000..73685bc --- /dev/null +++ b/frontend/src/views/brand/index.vue @@ -0,0 +1,313 @@ + + + + + diff --git a/frontend/src/views/model/index.vue b/frontend/src/views/model/index.vue new file mode 100644 index 0000000..b9c3114 --- /dev/null +++ b/frontend/src/views/model/index.vue @@ -0,0 +1,486 @@ + + + + + diff --git a/frontend/start.bat b/frontend/start.bat new file mode 100644 index 0000000..3b911f2 --- /dev/null +++ b/frontend/start.bat @@ -0,0 +1,18 @@ +@echo off +chcp 65001 >nul +echo ================================================ +echo 前端项目 - 启动开发服务器 +echo ================================================ +echo. + +cd /d "%~dp0" + +echo 启动开发服务器... +echo. +echo 访问地址:http://localhost:3000 +echo API 代理:http://localhost:8002 +echo. + +pnpm dev + +pause diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..8f757b2 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'path' + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': resolve(__dirname, 'src') + } + }, + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:8002', + changeOrigin: true + } + } + } +})