feat: 修复 H5 底部导航覆盖 + 更新项目进度文档

## H5 底部导航修复 (Bug #10)
- 精简 App.vue,移除重复 tabbar,仅保留全局样式
- uni-page 设置 height: calc(100% - 50px) + overflow-y: auto
- 内容区域精确停在底部导航上方,独立滚动不再叠加
- 恢复 custom-tab-bar 组件

## 项目进度文档
- PROGRESS.md 更新至 10 个 Bug 修复
- 新增 H5 底部导航修复记录
- 新增历史变更条目
This commit is contained in:
TradeMate Dev
2026-05-12 20:24:42 +08:00
parent 69e164dcae
commit 7b62c2f8b4
125 changed files with 19725 additions and 728 deletions
+94 -2
View File
@@ -1,8 +1,11 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File, Response
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated, Optional
from typing import Annotated, Optional, List
from app.database import get_db
from app.services.customer import CustomerService
from app.services.customer_health import CustomerHealthService
from app.services.import_service import import_service
from app.services import export
from app.core.security import decode_token
from app.api.v1.deps import get_current_user_id
@@ -87,6 +90,95 @@ async def delete_customer(
return {"message": "Customer deleted"}
@router.post("/import")
async def import_customers(
file: UploadFile = File(...),
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
):
from app.workers.tasks import process_customer_import
content = await file.read()
filename = file.filename or ""
if filename.endswith(".xlsx"):
records, parse_errors = import_service.parse_xlsx(content)
elif filename.endswith(".csv"):
records, parse_errors = import_service.parse_csv(content)
else:
raise HTTPException(status_code=400, detail="Unsupported file format. Use .xlsx or .csv")
if parse_errors and not records:
raise HTTPException(status_code=400, detail=f"Parse failed: {'; '.join(parse_errors)}")
valid, validation_errors = import_service.validate_records(records)
all_errors = parse_errors + validation_errors
imported_count = 0
for record in valid:
try:
svc = CustomerService(db)
await svc.create_customer(user_id, record)
imported_count += 1
except Exception as e:
all_errors.append(f"Import failed for {record.get('name', 'unknown')}: {str(e)}")
return {
"imported": imported_count,
"total": len(records),
"errors": all_errors,
"filename": filename,
}
@router.get("/export/csv")
async def export_customers(
status: Optional[str] = None,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
):
service = CustomerService(db)
result = await service.list_customers(user_id, status, 1, 9999)
items = result.get("items", [])
csv_bytes = export.export_customers_csv(items)
return Response(
content=csv_bytes,
media_type="text/csv",
headers={"Content-Disposition": "attachment; filename=customers.csv"},
)
@router.get("/health-overview")
async def get_health_overview(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
):
service = CustomerHealthService(db)
return await service.get_health_overview(user_id)
@router.get("/health-scores")
async def get_all_health_scores(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
):
service = CustomerHealthService(db)
return {"items": await service.get_all_health_scores(user_id)}
@router.get("/{customer_id}/health")
async def get_customer_health(
customer_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
):
service = CustomerHealthService(db)
health = await service.get_customer_health(user_id, customer_id)
if not health:
raise HTTPException(status_code=404, detail="Customer not found")
return health
@router.get("/{customer_id}/conversation")
async def get_conversation(
customer_id: str,