excel_export.py 1.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
  1. """将行数据序列化为 Excel 字节流。"""
  2. from __future__ import annotations
  3. import json
  4. from io import BytesIO
  5. from typing import Iterable
  6. from urllib.parse import quote
  7. from openpyxl import Workbook
  8. from openpyxl.utils import get_column_letter
  9. def _cell_value(raw: object) -> object:
  10. if raw is None:
  11. return ""
  12. if isinstance(raw, (list, dict)):
  13. return json.dumps(raw, ensure_ascii=False)
  14. return raw
  15. def rows_to_excel_bytes(
  16. rows: Iterable[dict[str, object]],
  17. columns: list[tuple[str, str]],
  18. *,
  19. sheet_name: str = "Sheet1",
  20. ) -> bytes:
  21. workbook = Workbook()
  22. worksheet = workbook.active
  23. worksheet.title = sheet_name[:31]
  24. headers = [header for header, _ in columns]
  25. worksheet.append(headers)
  26. for row in rows:
  27. worksheet.append([_cell_value(row.get(field)) for _, field in columns])
  28. for index, (header, _) in enumerate(columns, start=1):
  29. column_letter = get_column_letter(index)
  30. max_len = len(header)
  31. for cell in worksheet[column_letter]:
  32. if cell.value is not None:
  33. max_len = max(max_len, len(str(cell.value)))
  34. worksheet.column_dimensions[column_letter].width = min(max_len + 2, 60)
  35. buffer = BytesIO()
  36. workbook.save(buffer)
  37. return buffer.getvalue()
  38. def build_content_disposition(filename: str) -> str:
  39. ascii_fallback = "export.xlsx"
  40. encoded = quote(filename, safe="")
  41. return f"attachment; filename=\"{ascii_fallback}\"; filename*=UTF-8''{encoded}"