import os from typing import Dict, List from openpyxl import Workbook from openpyxl.styles import Font, Alignment, PatternFill from openpyxl.drawing.image import Image import logging logger = logging.getLogger(__name__) class ExcelExporter: """Excel导出器,用于将商品组合数据导出为Excel格式""" def __init__(self): self.wb = None self.ws = None self.styles = { 'GREEN_FONT': Font(color="FF0000"), # 注意:这里颜色定义可能有误,应该是00FF00为绿色 'BLUE_FONT': Font(color="0000FF"), 'RED_FONT': Font(color="00FF00"), # 注意:这里应该是FF0000为红色 'BOLD_FONT': Font(bold=True), 'CENTER_ALIGN': Alignment(horizontal='center', vertical='center'), 'RED_FILL': PatternFill(start_color="FF9999", end_color="FF9999", fill_type="solid"), 'GREEN_FILL': PatternFill(start_color="99FF99", end_color="99FF99", fill_type="solid") } def export_to_excel(self, data: Dict, filename: str = None) -> str: """ 将商品数据导出到Excel文件 Args: data: 包含商品信息的字典 filename: 输出的Excel文件名 Returns: 导出的文件路径 """ try: self._initialize_workbook() self._extract_data(data) self._setup_worksheet() self._write_data() return self._save_workbook(filename) except Exception as e: logger.error(f"导出Excel失败: {e}") raise def _initialize_workbook(self): """初始化工作簿和工作表""" self.wb = Workbook() self.ws = self.wb.active self.ws.title = "商品数据" def _extract_data(self, data: Dict): """从数据中提取所需信息""" self.primary_good = data["primary_goods_info"]["goods_info"] self.combine_two = data["combine_two_info"] self.combine_three = data["combine_three_info"] self.outfit_orders = data["outfit_orders"] self.primary_code = self.primary_good['sku'] + self.primary_good['color_code'] self.header_count = [len(order) for order in self.outfit_orders] def _setup_worksheet(self): """设置工作表基本属性""" self._setup_column_dimensions() self._write_headers() self.ws.freeze_panes = 'A2' def _setup_column_dimensions(self): """设置列宽""" # 图片列 for col in ['B', 'C', 'D']: self.ws.column_dimensions[col].width = 13.6 # 数据列 for col in ['A', *[self._get_column_letter(i) for i in range(4, 58)]]: self.ws.column_dimensions[col].width = 14 if col != 'A' else 40 def _write_headers(self): """写入表头""" self._write_main_headers() self._write_sub_headers() def _write_main_headers(self): """写入主表头""" headers = [ "组合", "主推款图片", "连带款1图片", "连带款2图片", "连带排名", "连带频次", "主商品频次", "连带率", "连带件数", "连带金额", "" ] for col_idx, header in enumerate(headers, start=1): cell = self.ws.cell(row=1, column=col_idx, value=header) cell.font = self.styles['BOLD_FONT'] cell.alignment = self.styles['CENTER_ALIGN'] def _write_sub_headers(self): """写入子表头(大单组合)""" # ... 原有的子表头逻辑,略作调整使用self.styles def _write_data(self): """写入所有数据""" self._write_combine_two_data() self._write_combine_three_data() self._write_outfit_orders_data() self._format_worksheet() def _write_combine_two_data(self): """写入两种商品组合数据""" for row_idx, item in enumerate(self.combine_two, start=2): self._write_combine_two_row(row_idx, item) def _write_combine_two_row(self, row_idx: int, item: Dict): """写入单行两种商品组合数据""" goods_info = item['goods_info'] sub_code = goods_info['sku'] + goods_info['color_code'] # 写入数据 data_mapping = [ (1, f"{self.primary_code},{sub_code}"), (4, "22组合"), (5, row_idx-1), (6, item['freq']), (7, item['main_freq']), (8, item['combine_rate']), (9, item['count']), (10, item['sales']) ] for col, value in data_mapping: cell = self.ws.cell(row=row_idx, column=col, value=value) cell.font = self.styles['GREEN_FONT'] # 插入图片 self._insert_image(self.primary_good['image_path'], f'B{row_idx}') self._insert_image(goods_info['image_path'], f'C{row_idx}') def _write_combine_three_data(self): """写入三种商品组合数据""" start_row = 2 + len(self.combine_two) for row_idx, item in enumerate(self.combine_three, start=start_row): self._write_combine_three_row(row_idx, item, start_row) def _write_combine_three_row(self, row_idx: int, item: Dict, start_row: int): """写入单行三种商品组合数据""" goods_info = item['goods_info'] sub1_code = goods_info[0]['sku'] + goods_info[0]['color_code'] sub2_code = goods_info[1]['sku'] + goods_info[1]['color_code'] data_mapping = [ (1, f"{self.primary_code},{sub1_code},{sub2_code}"), (5, row_idx - (1 + len(self.combine_two))), (6, item['freq']), (7, item['main_freq']), (8, item['combine_rate']), (9, item['count']), (10, item['sales']) ] for col, value in data_mapping: cell = self.ws.cell(row=row_idx, column=col, value=value) cell.font = self.styles['BLUE_FONT'] # 插入图片 self._insert_image(self.primary_good['image_path'], f'B{row_idx}') self._insert_image(goods_info[0]['image_path'], f'C{row_idx}') self._insert_image(goods_info[1]['image_path'], f'D{row_idx}') def _write_outfit_orders_data(self): """写入大单组合数据""" # ... 原有逻辑,略作调整 def _insert_image(self, image_path: str, cell_location: str): """在指定单元格插入图片""" try: if os.path.exists(image_path): img = Image(image_path) img.width = img.height = 100 self.ws.add_image(img, cell_location) except Exception as e: logger.warning(f"插入图片失败 {image_path}: {e}") def _format_worksheet(self): """格式化工作表""" # 设置百分比格式 for cell in self.ws['H']: cell.number_format = '0.00%' # 设置行高 data_rows_count = len(self.combine_two) + len(self.combine_three) for row in range(2, data_rows_count + 2): self.ws.row_dimensions[row].height = 80 # 设置居中对齐 for row in self.ws.iter_rows(): for cell in row: cell.alignment = self.styles['CENTER_ALIGN'] def _save_workbook(self, filename: str) -> str: """保存工作簿到文件""" if not filename: filename = f"{self.primary_code}.xlsx" os.makedirs('./output', exist_ok=True) file_path = os.path.join('./output', filename) self.wb.save(file_path) logger.info(f"数据已成功写入 {file_path} 文件") return file_path @staticmethod def _get_column_letter(idx: int) -> str: """获取列字母(简化版本)""" from openpyxl.utils import get_column_letter return get_column_letter(idx) # 使用示例 def export_to_excel(data: Dict, filename: str = None) -> str: """保持原有函数接口的包装函数""" exporter = ExcelExporter() return exporter.export_to_excel(data, filename)