feat: complete web application — FastAPI backend + Vue 3 SPA frontend
- Full FastAPI backend with JWT auth, file management, processing pipeline, memory CRUD, barcode mappings, config management, cloud sync - Vue 3 + Element Plus frontend with dashboard, task history, HTTP logs, memory editor, barcode editor, config editor, sync page - HTTP request logging middleware with SQLite persistence - Task history tracking with progress and retry support - File metadata recording for upload/download operations - WebAuth section in config.ini for bcrypt password storage - Bug fix: logs.py count query returns tuple not dict Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
"""Product memory CRUD endpoints."""
|
||||
|
||||
from typing import Optional, List, Dict
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends, Query
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..auth.dependencies import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/api/memory", tags=["memory"])
|
||||
|
||||
_project_root = Path(__file__).resolve().parent.parent.parent.parent
|
||||
_db_path = str(_project_root / "data" / "product_cache.db")
|
||||
_excel_source = str(_project_root / "templates" / "商品资料.xlsx")
|
||||
|
||||
|
||||
class MemoryItem(BaseModel):
|
||||
barcode: str
|
||||
name: str
|
||||
spec: Optional[str] = None
|
||||
unit: Optional[str] = None
|
||||
price: Optional[float] = None
|
||||
confidence: int = 0
|
||||
source: str = "ocr"
|
||||
last_used: Optional[str] = None
|
||||
use_count: int = 0
|
||||
|
||||
|
||||
class MemoryUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
spec: Optional[str] = None
|
||||
unit: Optional[str] = None
|
||||
price: Optional[float] = None
|
||||
confidence: Optional[int] = None
|
||||
|
||||
|
||||
class MemoryListResponse(BaseModel):
|
||||
items: List[MemoryItem]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
|
||||
|
||||
def _get_db():
|
||||
from app.core.db.product_db import ProductDatabase
|
||||
return ProductDatabase(_db_path, _excel_source)
|
||||
|
||||
|
||||
def _row_to_item(row: Dict) -> MemoryItem:
|
||||
return MemoryItem(
|
||||
barcode=row.get("barcode", ""),
|
||||
name=row.get("name", ""),
|
||||
spec=row.get("spec"),
|
||||
unit=row.get("unit"),
|
||||
price=row.get("price"),
|
||||
confidence=row.get("confidence", 0),
|
||||
source=row.get("source", "ocr"),
|
||||
last_used=row.get("last_used"),
|
||||
use_count=row.get("use_count", 0),
|
||||
)
|
||||
|
||||
|
||||
@router.get("", response_model=MemoryListResponse)
|
||||
async def list_memory(
|
||||
search: str = "",
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=200),
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
db = _get_db()
|
||||
results = db.get_all_memories()
|
||||
|
||||
if search:
|
||||
s = search.lower()
|
||||
results = [r for r in results if s in r.get("barcode", "").lower() or s in r.get("name", "").lower()]
|
||||
|
||||
total = len(results)
|
||||
start = (page - 1) * page_size
|
||||
page_items = results[start:start + page_size]
|
||||
|
||||
return MemoryListResponse(
|
||||
items=[_row_to_item(r) for r in page_items],
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{barcode}")
|
||||
async def get_memory(
|
||||
barcode: str,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
db = _get_db()
|
||||
product = db.get_memory(barcode)
|
||||
if not product:
|
||||
raise HTTPException(404, f"未找到条码 {barcode} 的记忆记录")
|
||||
return product
|
||||
|
||||
|
||||
@router.put("/{barcode}")
|
||||
async def update_memory(
|
||||
barcode: str,
|
||||
body: MemoryUpdate,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
db = _get_db()
|
||||
existing = db.get_memory(barcode)
|
||||
if not existing:
|
||||
raise HTTPException(404, f"未找到条码 {barcode}")
|
||||
|
||||
update_data = body.model_dump(exclude_none=True)
|
||||
if not update_data:
|
||||
raise HTTPException(400, "没有提供更新数据")
|
||||
|
||||
db.update_memory(barcode, update_data)
|
||||
return {"message": f"已更新 {barcode}", "updated_fields": list(update_data.keys())}
|
||||
|
||||
|
||||
@router.delete("/{barcode}")
|
||||
async def delete_memory(
|
||||
barcode: str,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
db = _get_db()
|
||||
existing = db.get_memory(barcode)
|
||||
if not existing:
|
||||
raise HTTPException(404, f"未找到条码 {barcode}")
|
||||
db.delete_memory(barcode)
|
||||
return {"message": f"已删除 {barcode}"}
|
||||
|
||||
|
||||
@router.post("/reimport")
|
||||
async def reimport_memory(
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
db = _get_db()
|
||||
try:
|
||||
count = db.reimport()
|
||||
return {"message": f"重新导入完成,共导入 {count} 条记录", "count": count}
|
||||
except Exception as e:
|
||||
raise HTTPException(500, f"导入失败: {e}")
|
||||
|
||||
|
||||
@router.get("/export/sync")
|
||||
async def export_memory(
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
db = _get_db()
|
||||
data = db.export_for_sync()
|
||||
return {"data": data, "count": len(data)}
|
||||
|
||||
|
||||
@router.post("/import/sync")
|
||||
async def import_memory(
|
||||
data: dict,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
):
|
||||
db = _get_db()
|
||||
try:
|
||||
count = db.import_from_sync(data.get("data", []))
|
||||
return {"message": f"导入完成,共 {count} 条", "count": count}
|
||||
except Exception as e:
|
||||
raise HTTPException(500, f"导入失败: {e}")
|
||||
Reference in New Issue
Block a user