| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210 |
- 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)
|