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:
@@ -0,0 +1,229 @@
|
||||
from typing import Optional, Dict, Any, List
|
||||
from datetime import datetime
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
from weasyprint import HTML
|
||||
HAS_WEASYPRINT = True
|
||||
except ImportError:
|
||||
HAS_WEASYPRINT = False
|
||||
logger.warning("weasyprint not installed, PDF generation disabled")
|
||||
|
||||
|
||||
QUOTATION_TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
@page {{
|
||||
size: A4;
|
||||
margin: 2cm;
|
||||
}}
|
||||
body {{
|
||||
font-family: 'Helvetica Neue', Arial, sans-serif;
|
||||
font-size: 12pt;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}}
|
||||
.header {{
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border-bottom: 2px solid #1890ff;
|
||||
padding-bottom: 20px;
|
||||
}}
|
||||
.header h1 {{
|
||||
font-size: 24pt;
|
||||
color: #1890ff;
|
||||
margin: 0;
|
||||
}}
|
||||
.header .number {{
|
||||
font-size: 14pt;
|
||||
color: #666;
|
||||
}}
|
||||
.info-grid {{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 30px;
|
||||
}}
|
||||
.info-block {{
|
||||
width: 48%;
|
||||
}}
|
||||
.info-block h3 {{
|
||||
font-size: 11pt;
|
||||
color: #1890ff;
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
padding-bottom: 4px;
|
||||
}}
|
||||
.info-block p {{
|
||||
margin: 4px 0;
|
||||
font-size: 10pt;
|
||||
}}
|
||||
table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 30px;
|
||||
}}
|
||||
th {{
|
||||
background: #1890ff;
|
||||
color: white;
|
||||
padding: 10px 8px;
|
||||
text-align: left;
|
||||
font-size: 10pt;
|
||||
}}
|
||||
td {{
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
font-size: 10pt;
|
||||
}}
|
||||
.amount-row td {{
|
||||
text-align: right;
|
||||
padding: 4px 8px;
|
||||
border: none;
|
||||
}}
|
||||
.total-row td {{
|
||||
font-weight: bold;
|
||||
font-size: 12pt;
|
||||
border-top: 2px solid #333;
|
||||
}}
|
||||
.terms {{
|
||||
margin-top: 30px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
}}
|
||||
.terms h3 {{
|
||||
font-size: 11pt;
|
||||
color: #1890ff;
|
||||
}}
|
||||
.terms p {{
|
||||
font-size: 9pt;
|
||||
color: #666;
|
||||
margin: 4px 0;
|
||||
}}
|
||||
.footer {{
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
font-size: 9pt;
|
||||
color: #999;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>QUOTATION</h1>
|
||||
<p class="number">#{quotation_number}</p>
|
||||
</div>
|
||||
|
||||
<div class="info-grid">
|
||||
<div class="info-block">
|
||||
<h3>Bill To</h3>
|
||||
<p>{customer_name}</p>
|
||||
<p>{customer_company}</p>
|
||||
<p>{customer_country}</p>
|
||||
</div>
|
||||
<div class="info-block">
|
||||
<h3>Quote Details</h3>
|
||||
<p>Date: {date}</p>
|
||||
<p>Valid Until: {valid_until}</p>
|
||||
<p>Currency: {currency}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Item</th>
|
||||
<th>Description</th>
|
||||
<th>Qty</th>
|
||||
<th>Unit</th>
|
||||
<th>Unit Price</th>
|
||||
<th>Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items_rows}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table>
|
||||
<tr class="amount-row"><td colspan="4"></td><td>Subtotal:</td><td>{subtotal}</td></tr>
|
||||
<tr class="amount-row"><td colspan="4"></td><td>Discount:</td><td>-{discount}</td></tr>
|
||||
<tr class="amount-row"><td colspan="4"></td><td>Shipping:</td><td>{shipping}</td></tr>
|
||||
<tr class="total-row"><td colspan="4"></td><td>TOTAL:</td><td>{total}</td></tr>
|
||||
</table>
|
||||
|
||||
<div class="terms">
|
||||
<h3>Terms & Conditions</h3>
|
||||
<p>Payment Terms: {payment_terms}</p>
|
||||
<p>Delivery Terms: {delivery_terms}</p>
|
||||
<p>Lead Time: {lead_time}</p>
|
||||
{notes_html}
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<p>Generated by TradeMate - {generated_at}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
class PDFGenerator:
|
||||
@staticmethod
|
||||
def generate_quotation(data: Dict[str, Any]) -> Optional[bytes]:
|
||||
if not HAS_WEASYPRINT:
|
||||
return None
|
||||
|
||||
items = data.get("items", [])
|
||||
items_rows = ""
|
||||
for i, item in enumerate(items, 1):
|
||||
items_rows += (
|
||||
f"<tr>"
|
||||
f"<td>{item.get('product_name', '')}</td>"
|
||||
f"<td>{item.get('description', '') or ''}</td>"
|
||||
f"<td>{item.get('quantity', 0)}</td>"
|
||||
f"<td>{item.get('unit', 'pcs')}</td>"
|
||||
f"<td>{item.get('unit_price', 0):.2f}</td>"
|
||||
f"<td>{item.get('total_price', 0):.2f}</td>"
|
||||
f"</tr>"
|
||||
)
|
||||
|
||||
cur = data.get("currency", "USD")
|
||||
subtotal = f"{cur} {data.get('subtotal', 0):.2f}"
|
||||
discount = f"{cur} {data.get('discount', 0):.2f}" if data.get("discount") else f"{cur} 0.00"
|
||||
shipping = f"{cur} {data.get('shipping', 0):.2f}" if data.get("shipping") else f"{cur} 0.00"
|
||||
total = f"{cur} {data.get('total', 0):.2f}"
|
||||
|
||||
notes_html = ""
|
||||
if data.get("notes"):
|
||||
notes_html = f"<p>Notes: {data['notes']}</p>"
|
||||
|
||||
html = QUOTATION_TEMPLATE.format(
|
||||
quotation_number=data.get("quotation_number", "N/A"),
|
||||
customer_name=data.get("customer_name", ""),
|
||||
customer_company=data.get("customer_company", "") or "",
|
||||
customer_country=data.get("customer_country", "") or "",
|
||||
date=data.get("date", datetime.utcnow().strftime("%Y-%m-%d")),
|
||||
valid_until=data.get("valid_until", "N/A"),
|
||||
currency=cur,
|
||||
items_rows=items_rows,
|
||||
subtotal=subtotal,
|
||||
discount=discount,
|
||||
shipping=shipping,
|
||||
total=total,
|
||||
payment_terms=data.get("payment_terms", "N/A"),
|
||||
delivery_terms=data.get("delivery_terms", "N/A"),
|
||||
lead_time=data.get("lead_time", "N/A"),
|
||||
notes_html=notes_html,
|
||||
generated_at=datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC"),
|
||||
)
|
||||
|
||||
pdf = HTML(string=html).write_pdf()
|
||||
return pdf
|
||||
|
||||
|
||||
pdf_generator = PDFGenerator()
|
||||
Reference in New Issue
Block a user