test.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. import os
  2. from typing import Dict, List
  3. from openpyxl import Workbook
  4. from openpyxl.styles import Font, Alignment, PatternFill
  5. from openpyxl.drawing.image import Image
  6. import logging
  7. logger = logging.getLogger(__name__)
  8. class ExcelExporter:
  9. """Excel导出器,用于将商品组合数据导出为Excel格式"""
  10. def __init__(self):
  11. self.wb = None
  12. self.ws = None
  13. self.styles = {
  14. 'GREEN_FONT': Font(color="FF0000"), # 注意:这里颜色定义可能有误,应该是00FF00为绿色
  15. 'BLUE_FONT': Font(color="0000FF"),
  16. 'RED_FONT': Font(color="00FF00"), # 注意:这里应该是FF0000为红色
  17. 'BOLD_FONT': Font(bold=True),
  18. 'CENTER_ALIGN': Alignment(horizontal='center', vertical='center'),
  19. 'RED_FILL': PatternFill(start_color="FF9999", end_color="FF9999", fill_type="solid"),
  20. 'GREEN_FILL': PatternFill(start_color="99FF99", end_color="99FF99", fill_type="solid")
  21. }
  22. def export_to_excel(self, data: Dict, filename: str = None) -> str:
  23. """
  24. 将商品数据导出到Excel文件
  25. Args:
  26. data: 包含商品信息的字典
  27. filename: 输出的Excel文件名
  28. Returns:
  29. 导出的文件路径
  30. """
  31. try:
  32. self._initialize_workbook()
  33. self._extract_data(data)
  34. self._setup_worksheet()
  35. self._write_data()
  36. return self._save_workbook(filename)
  37. except Exception as e:
  38. logger.error(f"导出Excel失败: {e}")
  39. raise
  40. def _initialize_workbook(self):
  41. """初始化工作簿和工作表"""
  42. self.wb = Workbook()
  43. self.ws = self.wb.active
  44. self.ws.title = "商品数据"
  45. def _extract_data(self, data: Dict):
  46. """从数据中提取所需信息"""
  47. self.primary_good = data["primary_goods_info"]["goods_info"]
  48. self.combine_two = data["combine_two_info"]
  49. self.combine_three = data["combine_three_info"]
  50. self.outfit_orders = data["outfit_orders"]
  51. self.primary_code = self.primary_good['sku'] + self.primary_good['color_code']
  52. self.header_count = [len(order) for order in self.outfit_orders]
  53. def _setup_worksheet(self):
  54. """设置工作表基本属性"""
  55. self._setup_column_dimensions()
  56. self._write_headers()
  57. self.ws.freeze_panes = 'A2'
  58. def _setup_column_dimensions(self):
  59. """设置列宽"""
  60. # 图片列
  61. for col in ['B', 'C', 'D']:
  62. self.ws.column_dimensions[col].width = 13.6
  63. # 数据列
  64. for col in ['A', *[self._get_column_letter(i) for i in range(4, 58)]]:
  65. self.ws.column_dimensions[col].width = 14 if col != 'A' else 40
  66. def _write_headers(self):
  67. """写入表头"""
  68. self._write_main_headers()
  69. self._write_sub_headers()
  70. def _write_main_headers(self):
  71. """写入主表头"""
  72. headers = [
  73. "组合", "主推款图片", "连带款1图片", "连带款2图片", "连带排名",
  74. "连带频次", "主商品频次", "连带率", "连带件数", "连带金额", ""
  75. ]
  76. for col_idx, header in enumerate(headers, start=1):
  77. cell = self.ws.cell(row=1, column=col_idx, value=header)
  78. cell.font = self.styles['BOLD_FONT']
  79. cell.alignment = self.styles['CENTER_ALIGN']
  80. def _write_sub_headers(self):
  81. """写入子表头(大单组合)"""
  82. # ... 原有的子表头逻辑,略作调整使用self.styles
  83. def _write_data(self):
  84. """写入所有数据"""
  85. self._write_combine_two_data()
  86. self._write_combine_three_data()
  87. self._write_outfit_orders_data()
  88. self._format_worksheet()
  89. def _write_combine_two_data(self):
  90. """写入两种商品组合数据"""
  91. for row_idx, item in enumerate(self.combine_two, start=2):
  92. self._write_combine_two_row(row_idx, item)
  93. def _write_combine_two_row(self, row_idx: int, item: Dict):
  94. """写入单行两种商品组合数据"""
  95. goods_info = item['goods_info']
  96. sub_code = goods_info['sku'] + goods_info['color_code']
  97. # 写入数据
  98. data_mapping = [
  99. (1, f"{self.primary_code},{sub_code}"), (4, "22组合"),
  100. (5, row_idx-1), (6, item['freq']), (7, item['main_freq']),
  101. (8, item['combine_rate']), (9, item['count']), (10, item['sales'])
  102. ]
  103. for col, value in data_mapping:
  104. cell = self.ws.cell(row=row_idx, column=col, value=value)
  105. cell.font = self.styles['GREEN_FONT']
  106. # 插入图片
  107. self._insert_image(self.primary_good['image_path'], f'B{row_idx}')
  108. self._insert_image(goods_info['image_path'], f'C{row_idx}')
  109. def _write_combine_three_data(self):
  110. """写入三种商品组合数据"""
  111. start_row = 2 + len(self.combine_two)
  112. for row_idx, item in enumerate(self.combine_three, start=start_row):
  113. self._write_combine_three_row(row_idx, item, start_row)
  114. def _write_combine_three_row(self, row_idx: int, item: Dict, start_row: int):
  115. """写入单行三种商品组合数据"""
  116. goods_info = item['goods_info']
  117. sub1_code = goods_info[0]['sku'] + goods_info[0]['color_code']
  118. sub2_code = goods_info[1]['sku'] + goods_info[1]['color_code']
  119. data_mapping = [
  120. (1, f"{self.primary_code},{sub1_code},{sub2_code}"),
  121. (5, row_idx - (1 + len(self.combine_two))), (6, item['freq']),
  122. (7, item['main_freq']), (8, item['combine_rate']),
  123. (9, item['count']), (10, item['sales'])
  124. ]
  125. for col, value in data_mapping:
  126. cell = self.ws.cell(row=row_idx, column=col, value=value)
  127. cell.font = self.styles['BLUE_FONT']
  128. # 插入图片
  129. self._insert_image(self.primary_good['image_path'], f'B{row_idx}')
  130. self._insert_image(goods_info[0]['image_path'], f'C{row_idx}')
  131. self._insert_image(goods_info[1]['image_path'], f'D{row_idx}')
  132. def _write_outfit_orders_data(self):
  133. """写入大单组合数据"""
  134. # ... 原有逻辑,略作调整
  135. def _insert_image(self, image_path: str, cell_location: str):
  136. """在指定单元格插入图片"""
  137. try:
  138. if os.path.exists(image_path):
  139. img = Image(image_path)
  140. img.width = img.height = 100
  141. self.ws.add_image(img, cell_location)
  142. except Exception as e:
  143. logger.warning(f"插入图片失败 {image_path}: {e}")
  144. def _format_worksheet(self):
  145. """格式化工作表"""
  146. # 设置百分比格式
  147. for cell in self.ws['H']:
  148. cell.number_format = '0.00%'
  149. # 设置行高
  150. data_rows_count = len(self.combine_two) + len(self.combine_three)
  151. for row in range(2, data_rows_count + 2):
  152. self.ws.row_dimensions[row].height = 80
  153. # 设置居中对齐
  154. for row in self.ws.iter_rows():
  155. for cell in row:
  156. cell.alignment = self.styles['CENTER_ALIGN']
  157. def _save_workbook(self, filename: str) -> str:
  158. """保存工作簿到文件"""
  159. if not filename:
  160. filename = f"{self.primary_code}.xlsx"
  161. os.makedirs('./output', exist_ok=True)
  162. file_path = os.path.join('./output', filename)
  163. self.wb.save(file_path)
  164. logger.info(f"数据已成功写入 {file_path} 文件")
  165. return file_path
  166. @staticmethod
  167. def _get_column_letter(idx: int) -> str:
  168. """获取列字母(简化版本)"""
  169. from openpyxl.utils import get_column_letter
  170. return get_column_letter(idx)
  171. # 使用示例
  172. def export_to_excel(data: Dict, filename: str = None) -> str:
  173. """保持原有函数接口的包装函数"""
  174. exporter = ExcelExporter()
  175. return exporter.export_to_excel(data, filename)