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:
@@ -1,13 +1,39 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Response
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import Annotated, Optional
|
||||
from pydantic import BaseModel
|
||||
from app.database import get_db
|
||||
from app.services.quotation import QuotationService
|
||||
from app.services.pdf_generator import pdf_generator
|
||||
from app.services import export
|
||||
from app.api.v1.deps import get_current_user_id
|
||||
from app.models.quotation import Quotation
|
||||
from app.models.customer import Customer
|
||||
from sqlalchemy import select, and_
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class InquiryRequest(BaseModel):
|
||||
inquiry_text: str
|
||||
customer_id: Optional[str] = None
|
||||
|
||||
|
||||
@router.post("/generate-from-inquiry")
|
||||
async def generate_from_inquiry(
|
||||
data: InquiryRequest,
|
||||
user_id: str = Depends(get_current_user_id),
|
||||
db: Annotated[AsyncSession, Depends(get_db)] = None,
|
||||
):
|
||||
service = QuotationService(db)
|
||||
result = await service.generate_from_inquiry(
|
||||
user_id=user_id,
|
||||
inquiry_text=data.inquiry_text,
|
||||
customer_id=data.customer_id,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@router.post("")
|
||||
async def create_quotation(
|
||||
data: dict,
|
||||
@@ -58,3 +84,78 @@ async def update_quotation_status(
|
||||
if not quotation:
|
||||
raise HTTPException(status_code=404, detail="Quotation not found")
|
||||
return quotation
|
||||
|
||||
|
||||
@router.get("/export/csv")
|
||||
async def export_quotations(
|
||||
user_id: str = Depends(get_current_user_id),
|
||||
db: Annotated[AsyncSession, Depends(get_db)] = None,
|
||||
):
|
||||
service = QuotationService(db)
|
||||
result = await service.list_quotations(user_id, 1, 9999)
|
||||
items = result.get("items", [])
|
||||
csv_bytes = export.export_quotations_csv(items)
|
||||
return Response(
|
||||
content=csv_bytes,
|
||||
media_type="text/csv",
|
||||
headers={"Content-Disposition": "attachment; filename=quotations.csv"},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{quotation_id}/pdf")
|
||||
async def export_quotation_pdf(
|
||||
quotation_id: str,
|
||||
user_id: str = Depends(get_current_user_id),
|
||||
db: Annotated[AsyncSession, Depends(get_db)] = None,
|
||||
):
|
||||
service = QuotationService(db)
|
||||
quotation = await service.get_quotation(user_id, quotation_id)
|
||||
if not quotation:
|
||||
raise HTTPException(status_code=404, detail="Quotation not found")
|
||||
|
||||
result = await db.execute(
|
||||
select(Customer).where(Customer.id == quotation["customer_id"])
|
||||
)
|
||||
customer = result.scalar_one_or_none()
|
||||
|
||||
pdf_data = pdf_generator.generate_quotation({
|
||||
"quotation_number": f"{quotation_id[:8].upper()}",
|
||||
"customer_name": customer.name if customer else "",
|
||||
"customer_company": customer.company if customer else "",
|
||||
"customer_country": customer.country if customer else "",
|
||||
"date": quotation["created_at"][:10] if quotation.get("created_at") else "",
|
||||
"valid_until": quotation.get("valid_until", ""),
|
||||
"currency": quotation.get("currency", "USD"),
|
||||
"items": quotation.get("items", []),
|
||||
"subtotal": quotation.get("subtotal", 0),
|
||||
"discount": quotation.get("discount", 0),
|
||||
"shipping": quotation.get("shipping", 0),
|
||||
"total": quotation.get("total", 0),
|
||||
"payment_terms": quotation.get("payment_terms", ""),
|
||||
"delivery_terms": quotation.get("delivery_terms", ""),
|
||||
"lead_time": quotation.get("lead_time", ""),
|
||||
"notes": quotation.get("notes", ""),
|
||||
})
|
||||
|
||||
if not pdf_data:
|
||||
raise HTTPException(status_code=501, detail="PDF generation not available (weasyprint not installed)")
|
||||
|
||||
service = QuotationService(db)
|
||||
result = await db.execute(
|
||||
select(Quotation).where(
|
||||
and_(Quotation.id == quotation_id, Quotation.user_id == user_id)
|
||||
)
|
||||
)
|
||||
q = result.scalar_one_or_none()
|
||||
if q:
|
||||
pdf_url = f"/quotations/{quotation_id}/pdf"
|
||||
q.pdf_url = pdf_url
|
||||
await db.flush()
|
||||
|
||||
return Response(
|
||||
content=pdf_data,
|
||||
media_type="application/pdf",
|
||||
headers={
|
||||
"Content-Disposition": f'attachment; filename="quotation-{quotation_id[:8]}.pdf"',
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user