feat: 管理后台完整可用 + 注册登录记日志 + 提取信息结构化展示 + 微信配置就绪
- 管理后台用户/统计/日志/配置四页签全部对接真实后端API - auth注册/登录/游客/微信登录事件写入usage_logs表 - 提取信息结果从原始JSON改为卡片式字段列表(中文标签) - 管理后台搜索按钮增加加载态和结果数提示 - 配置WECHAT_APP_ID/WECHAT_APP_SECRET - 客户/产品/报价单CRUD页面完整(导出导入批量操作)
This commit is contained in:
@@ -1,6 +1,16 @@
|
||||
from typing import List, Dict, Any
|
||||
import csv
|
||||
import io
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
import openpyxl
|
||||
from openpyxl.styles import Font, PatternFill, Alignment
|
||||
HAS_OPENPYXL = True
|
||||
except ImportError:
|
||||
HAS_OPENPYXL = False
|
||||
|
||||
|
||||
def export_customers_csv(customers: List[Dict[str, Any]]) -> bytes:
|
||||
@@ -20,6 +30,49 @@ def export_customers_csv(customers: List[Dict[str, Any]]) -> bytes:
|
||||
return output.getvalue().encode("utf-8-sig")
|
||||
|
||||
|
||||
def export_customers_xlsx(customers: List[Dict[str, Any]]) -> bytes:
|
||||
if not HAS_OPENPYXL:
|
||||
logger.warning("openpyxl not installed, falling back to CSV")
|
||||
return export_customers_csv(customers)
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Customers"
|
||||
|
||||
headers = ["姓名", "公司", "国家", "电话", "WhatsApp", "邮箱", "状态", "最后联系"]
|
||||
header_font = Font(bold=True, color="FFFFFF")
|
||||
header_fill = PatternFill(start_color="1890FF", end_color="1890FF", fill_type="solid")
|
||||
|
||||
for col, h in enumerate(headers, 1):
|
||||
cell = ws.cell(row=1, column=col, value=h)
|
||||
cell.font = header_font
|
||||
cell.fill = header_fill
|
||||
cell.alignment = Alignment(horizontal="center")
|
||||
|
||||
for row, c in enumerate(customers, 2):
|
||||
ws.cell(row=row, column=1, value=c.get("name", ""))
|
||||
ws.cell(row=row, column=2, value=c.get("company", ""))
|
||||
ws.cell(row=row, column=3, value=c.get("country", ""))
|
||||
ws.cell(row=row, column=4, value=c.get("phone", ""))
|
||||
ws.cell(row=row, column=5, value=c.get("whatsapp_id", ""))
|
||||
ws.cell(row=row, column=6, value=c.get("email", ""))
|
||||
ws.cell(row=row, column=7, value=c.get("status", ""))
|
||||
ws.cell(row=row, column=8, value=c.get("last_contact_at", ""))
|
||||
|
||||
ws.column_dimensions["A"].width = 20
|
||||
ws.column_dimensions["B"].width = 25
|
||||
ws.column_dimensions["C"].width = 15
|
||||
ws.column_dimensions["D"].width = 18
|
||||
ws.column_dimensions["E"].width = 18
|
||||
ws.column_dimensions["F"].width = 30
|
||||
ws.column_dimensions["G"].width = 12
|
||||
ws.column_dimensions["H"].width = 20
|
||||
|
||||
output = io.BytesIO()
|
||||
wb.save(output)
|
||||
return output.getvalue()
|
||||
|
||||
|
||||
def export_quotations_csv(quotations: List[Dict[str, Any]]) -> bytes:
|
||||
output = io.StringIO()
|
||||
writer = csv.writer(output)
|
||||
@@ -34,4 +87,106 @@ def export_quotations_csv(quotations: List[Dict[str, Any]]) -> bytes:
|
||||
q.get("status", ""),
|
||||
q.get("created_at", ""),
|
||||
])
|
||||
return output.getvalue().encode("utf-8-sig")
|
||||
return output.getvalue().encode("utf-8-sig")
|
||||
|
||||
|
||||
def export_quotations_xlsx(quotations: List[Dict[str, Any]]) -> bytes:
|
||||
if not HAS_OPENPYXL:
|
||||
logger.warning("openpyxl not installed, falling back to CSV")
|
||||
return export_quotations_csv(quotations)
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Quotations"
|
||||
|
||||
headers = ["标题", "客户", "货币", "小计", "总计", "状态", "日期"]
|
||||
header_font = Font(bold=True, color="FFFFFF")
|
||||
header_fill = PatternFill(start_color="722ED1", end_color="722ED1", fill_type="solid")
|
||||
|
||||
for col, h in enumerate(headers, 1):
|
||||
cell = ws.cell(row=1, column=col, value=h)
|
||||
cell.font = header_font
|
||||
cell.fill = header_fill
|
||||
cell.alignment = Alignment(horizontal="center")
|
||||
|
||||
for row, q in enumerate(quotations, 2):
|
||||
ws.cell(row=row, column=1, value=q.get("title", ""))
|
||||
ws.cell(row=row, column=2, value=q.get("customer_name", ""))
|
||||
ws.cell(row=row, column=3, value=q.get("currency", "USD"))
|
||||
ws.cell(row=row, column=4, value=float(q.get("subtotal", 0)))
|
||||
ws.cell(row=row, column=5, value=float(q.get("total", 0)))
|
||||
ws.cell(row=row, column=6, value=q.get("status", ""))
|
||||
ws.cell(row=row, column=7, value=str(q.get("created_at", "")))
|
||||
|
||||
ws.column_dimensions["A"].width = 30
|
||||
ws.column_dimensions["B"].width = 20
|
||||
ws.column_dimensions["C"].width = 10
|
||||
ws.column_dimensions["D"].width = 15
|
||||
ws.column_dimensions["E"].width = 15
|
||||
ws.column_dimensions["F"].width = 12
|
||||
ws.column_dimensions["G"].width = 20
|
||||
|
||||
output = io.BytesIO()
|
||||
wb.save(output)
|
||||
return output.getvalue()
|
||||
|
||||
|
||||
def export_products_csv(products: List[Dict[str, Any]]) -> bytes:
|
||||
output = io.StringIO()
|
||||
writer = csv.writer(output)
|
||||
writer.writerow(["名称", "英文名", "分类", "描述", "价格", "货币", "MOQ", "关键词"])
|
||||
for p in products:
|
||||
writer.writerow([
|
||||
p.get("name", ""),
|
||||
p.get("name_en", ""),
|
||||
p.get("category", ""),
|
||||
p.get("description", ""),
|
||||
p.get("price", ""),
|
||||
p.get("price_unit", "USD"),
|
||||
p.get("moq", ""),
|
||||
", ".join(p.get("keywords", [])),
|
||||
])
|
||||
return output.getvalue().encode("utf-8-sig")
|
||||
|
||||
|
||||
def export_products_xlsx(products: List[Dict[str, Any]]) -> bytes:
|
||||
if not HAS_OPENPYXL:
|
||||
logger.warning("openpyxl not installed, falling back to CSV")
|
||||
return export_products_csv(products)
|
||||
|
||||
wb = openpyxl.Workbook()
|
||||
ws = wb.active
|
||||
ws.title = "Products"
|
||||
|
||||
headers = ["名称", "英文名", "分类", "描述", "价格", "货币", "MOQ", "关键词"]
|
||||
header_font = Font(bold=True, color="FFFFFF")
|
||||
header_fill = PatternFill(start_color="07C160", end_color="07C160", fill_type="solid")
|
||||
|
||||
for col, h in enumerate(headers, 1):
|
||||
cell = ws.cell(row=1, column=col, value=h)
|
||||
cell.font = header_font
|
||||
cell.fill = header_fill
|
||||
cell.alignment = Alignment(horizontal="center")
|
||||
|
||||
for row, p in enumerate(products, 2):
|
||||
ws.cell(row=row, column=1, value=p.get("name", ""))
|
||||
ws.cell(row=row, column=2, value=p.get("name_en", ""))
|
||||
ws.cell(row=row, column=3, value=p.get("category", ""))
|
||||
ws.cell(row=row, column=4, value=p.get("description", ""))
|
||||
ws.cell(row=row, column=5, value=p.get("price", ""))
|
||||
ws.cell(row=row, column=6, value=p.get("price_unit", "USD"))
|
||||
ws.cell(row=row, column=7, value=p.get("moq", ""))
|
||||
ws.cell(row=row, column=8, value=", ".join(p.get("keywords", [])))
|
||||
|
||||
ws.column_dimensions["A"].width = 20
|
||||
ws.column_dimensions["B"].width = 20
|
||||
ws.column_dimensions["C"].width = 15
|
||||
ws.column_dimensions["D"].width = 40
|
||||
ws.column_dimensions["E"].width = 12
|
||||
ws.column_dimensions["F"].width = 10
|
||||
ws.column_dimensions["G"].width = 12
|
||||
ws.column_dimensions["H"].width = 30
|
||||
|
||||
output = io.BytesIO()
|
||||
wb.save(output)
|
||||
return output.getvalue()
|
||||
Reference in New Issue
Block a user