feat: 管理后台完整可用 + 注册登录记日志 + 提取信息结构化展示 + 微信配置就绪

- 管理后台用户/统计/日志/配置四页签全部对接真实后端API
- auth注册/登录/游客/微信登录事件写入usage_logs表
- 提取信息结果从原始JSON改为卡片式字段列表(中文标签)
- 管理后台搜索按钮增加加载态和结果数提示
- 配置WECHAT_APP_ID/WECHAT_APP_SECRET
- 客户/产品/报价单CRUD页面完整(导出导入批量操作)
This commit is contained in:
TradeMate Dev
2026-05-18 23:50:48 +08:00
parent 32d2b57df7
commit 4755cc75ba
14 changed files with 1495 additions and 119 deletions
+108 -2
View File
@@ -1,10 +1,21 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import APIRouter, Depends, HTTPException, Query, Response, UploadFile, File
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Optional
from typing import Optional, List
from app.database import get_db
from app.services.product import ProductService
from app.services import export
from app.api.v1.deps import get_current_user_id
from pydantic import BaseModel
import io
import logging
logger = logging.getLogger(__name__)
try:
import openpyxl
HAS_OPENPYXL = True
except ImportError:
HAS_OPENPYXL = False
router = APIRouter()
@@ -50,6 +61,101 @@ async def list_products(
return await service.list_products(user_id, category, page, size)
@router.get("/export/csv")
async def export_products(
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
result = await service.list_products(user_id, None, 1, 9999)
items = result.get("items", [])
csv_bytes = export.export_products_csv(items)
return Response(
content=csv_bytes,
media_type="text/csv",
headers={"Content-Disposition": "attachment; filename=products.csv"},
)
@router.get("/export/xlsx")
async def export_products_xlsx(
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
result = await service.list_products(user_id, None, 1, 9999)
items = result.get("items", [])
xlsx_bytes = export.export_products_xlsx(items)
return Response(
content=xlsx_bytes,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={"Content-Disposition": "attachment; filename=products.xlsx"},
)
@router.post("/import")
async def import_products(
file: UploadFile = File(...),
user_id: str = Depends(get_current_user_id),
db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
content = await file.read()
filename = file.filename.lower()
if filename.endswith(".xlsx"):
if not HAS_OPENPYXL:
raise HTTPException(status_code=501, detail="openpyxl not installed, XLSX import unavailable")
wb = openpyxl.load_workbook(io.BytesIO(content))
ws = wb.active
rows = list(ws.iter_rows(min_row=2, values_only=True))
records = []
for row in rows:
if not row[0]:
continue
records.append({
"name": str(row[0] or ""),
"name_en": str(row[1] or ""),
"category": str(row[2] or ""),
"description": str(row[3] or ""),
"price": str(row[4] or ""),
"price_unit": str(row[5] or "USD") if len(row) > 5 else "USD",
"moq": str(row[6] or "") if len(row) > 6 else "",
"keywords": [k.strip() for k in str(row[7] or "").split(",") if k.strip()] if len(row) > 7 else [],
})
elif filename.endswith(".csv"):
import csv
reader = csv.DictReader(io.StringIO(content.decode("utf-8-sig")))
records = []
for row in reader:
name = row.get("名称", row.get("name", "")).strip()
if not name:
continue
records.append({
"name": name,
"name_en": row.get("英文名", row.get("name_en", "")),
"category": row.get("分类", row.get("category", "")),
"description": row.get("描述", row.get("description", "")),
"price": row.get("价格", row.get("price", "")),
"price_unit": row.get("货币", row.get("price_unit", "USD")),
"moq": row.get("MOQ", row.get("moq", "")),
"keywords": [k.strip() for k in row.get("关键词", row.get("keywords", "")).split(",") if k.strip()],
})
else:
raise HTTPException(status_code=400, detail="Unsupported file format. Use .xlsx or .csv")
imported = 0
errors = []
for rec in records:
try:
await service.create_product(user_id, rec)
imported += 1
except Exception as e:
errors.append(f"{rec.get('name', '')}: {str(e)}")
return {"imported": imported, "errors": errors}
@router.get("/{product_id}")
async def get_product(
product_id: str,