import os from tqdm import tqdm from typing import Dict from openpyxl import Workbook from openpyxl.drawing.image import Image from openpyxl.styles import Alignment, Font, PatternFill from openpyxl.utils import get_column_letter from openpyxl.styles.numbers import FORMAT_PERCENTAGE_00 from utils.tools import read_json_file from utils.logger_config import setup_logger logger = setup_logger(__name__) def number_to_letter(n): if n < 1: return None result = [] while n > 0: n -= 1 # 调整索引从0开始(A=0, B=1,...) remainder = n % 26 result.append(chr(65 + remainder)) # 65是'A'的ASCII码 n = n // 26 return ''.join(reversed(result)) # 反转拼接结果 def export_to_excel(data: Dict, filename: str = None) -> None: """ 将商品数据导出到Excel文件,并在单元格中插入实际图片 参数: data: 包含商品信息的字典 filename: 输出的Excel文件名 """ # 创建工作簿和工作表 wb = Workbook() ws = wb.active ws.title = "商品数据" # 定义字体颜色 - 使用16进制RGB颜色码 GREEN_FONT = Font(color="FF0000") # 绿色 BLUE_FONT = Font(color="0000FF") # 蓝色 RED_FONT = Font(color="00FF00") # 设置列宽 _setup_column_dimensions(ws) # 提取各商品信息 primary_good = data["primary_goods_info"]["goods_info"] combine_two = data["combine_two_info"] combine_three = data["combine_three_info"] outfit_orders = data["outfit_orders"] header_count = [len(order) for order in outfit_orders] # 设置表头 _write_headers(ws, header_count) ws.freeze_panes = 'A2' # 主商品编码 primary_code = primary_good['sku'] + primary_good['color_code'] # 处理两种组合数据 _process_combine_two(ws, primary_good, primary_code, combine_two, GREEN_FONT) _process_combine_three(ws, primary_good, primary_code, combine_three, len(combine_two), BLUE_FONT) _process_outfit_orders(ws, outfit_orders, [GREEN_FONT, RED_FONT]) # 格式化工作表 _format_worksheet(ws, len(combine_two) + len(combine_three)) # 保存Excel文件 if not filename: filename = primary_code + '.xlsx' os.makedirs('./output', exist_ok=True) # 确保输出目录存在 file_path = os.path.join('./output', filename) wb.save(file_path) logger.info(f"数据已成功写入 {file_path} 文件") return file_path def _setup_column_dimensions(ws): """设置工作表列宽""" # 图片列 for col in ['B', 'C', 'D']: ws.column_dimensions[col].width = 13.6 # 数据列 for col in ['A', *[get_column_letter(i) for i in range(4, 58)]]: ws.column_dimensions[col].width = 14 if col != 'A' else 40 def _write_headers(ws, header_count): """写入表头""" # 大单组合表头 first_sub_header = ["大单组合一", "大单组合二", "大单组合三", "大单组合四", "大单组合五"] second_sub_header = ["图片", "款号", "价格"] * sum(header_count) + ["价格"] # 连带款表头 headers = [ "组合", "主推款图片", "连带款1图片", "连带款2图片", "连带排名", "连带频次", "主商品频次", "连带率", "连带件数", "连带金额", "" ] # 设置连带款表头 for col_idx, header in enumerate(headers, start=1): cell = ws.cell(row=1, column=col_idx) cell.value = header cell.font = Font(bold=True) cell.alignment = Alignment(horizontal='center') # 设置连带款父表头 start_col = 12 current_col = start_col # 当前写入位置 # 定义红绿交替的颜色填充 red_fill = PatternFill(start_color="FF9999", end_color="FF9999", fill_type="solid") green_fill = PatternFill(start_color="99FF99", end_color="99FF99", fill_type="solid") for idx, count in enumerate(header_count): # 计算合并范围 merge_start = current_col merge_end = current_col + count * 3 - 1 print(f"写入位置:{current_col}") print(f"合并范围:{merge_start}-{merge_end}") # 写入单元格内容 cell = ws.cell(row=1, column=current_col) cell.value = f"大单组合:{idx+1}" # 使用idx+1作为组合编号 cell.font = Font(bold=True) cell.alignment = Alignment(horizontal='center') # 根据索引奇偶性选择颜色 if idx % 2 == 0: # 偶数索引使用红色 cell.fill = red_fill else: # 奇数索引使用绿色 cell.fill = green_fill # 合并单元格 merge_range = f"{number_to_letter(merge_start)}1:{number_to_letter(merge_end)}1" ws.merge_cells(merge_range) # 更新下一个组合的起始列 current_col = merge_end + 1 print('--------------------------------') # 设置连带款子表头 for col_idx, header in enumerate(second_sub_header, start=12): cell = ws.cell(row=2, column=col_idx) cell.value = header cell.font = Font(bold=True) cell.alignment = Alignment(horizontal='center') def _process_combine_two(ws, primary_good, primary_code, combine_two, font_color): """处理两种商品的组合数据""" for row_idx, item in enumerate(combine_two, start=2): goods_info = item['goods_info'] sub_code = goods_info['sku'] + goods_info['color_code'] # 写入数据并设置字体颜色 for col, value in [ (1, f"{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']) ]: cell = ws.cell(row=row_idx, column=col, value=value) cell.font = font_color # 插入图片 _insert_image(ws, primary_good['image_path'], f'B{row_idx}') _insert_image(ws, goods_info['image_path'], f'C{row_idx}') def _process_combine_three(ws, primary_good, primary_code, combine_three, combine_two_count, font_color): """处理三种商品的组合数据""" for row_idx, item in enumerate(combine_three, start=2 + combine_two_count): 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'] # 写入数据并设置字体颜色 for col, value in [ (1, f"{primary_code},{sub1_code},{sub2_code}"), (5, row_idx - (1 + combine_two_count)), (6, item['freq']), (7, item['main_freq']), (8, item['combine_rate']), (9, item['count']), (10, item['sales']) ]: cell = ws.cell(row=row_idx, column=col, value=value) cell.font = font_color # 插入图片 _insert_image(ws, primary_good['image_path'], f'B{row_idx}') _insert_image(ws, goods_info[0]['image_path'], f'C{row_idx}') _insert_image(ws, goods_info[1]['image_path'], f'D{row_idx}') def _process_outfit_orders(ws, outfit_orders, font_colors): start_col = 12 start_row = 3 total_price = 0 color_change = False for _, combine_order in enumerate(outfit_orders, start=2): current_font = font_colors[int(color_change)] for col_idx, order in enumerate(combine_order, start=12): for col, value in [(col_idx-11, order["skc"]), (col_idx-11, float(order["price"]))]: cell = ws.cell(row=start_row, column=start_col + col, value=value) cell.font = current_font start_col += 1 total_price += float(order["price"]) if not order["image_path"]: ws.cell(row=start_row, column=start_col + col - 3, value="None") else: col_name = number_to_letter(start_col + col - 3) _insert_image(ws, order["image_path"], f'{col_name}{start_row}') start_col += len(combine_order) color_change = not color_change ws.cell(row=start_row, column=start_col+col-2, value=total_price) def _insert_image(ws, image_path, cell_location): """在指定单元格插入图片""" try: if os.path.exists(image_path): img = Image(image_path) img.width = img.height = 100 # 设置图片大小 ws.add_image(img, cell_location) except Exception as e: logger.info(f"未获取到商品图片") def _format_worksheet(ws, data_rows_count): """格式化工作表""" # 设置连带率列格式 for cell in ws['H']: cell.number_format = FORMAT_PERCENTAGE_00 # 设置行高 for row in range(2, data_rows_count + 2): ws.row_dimensions[row].height = 80 # 设置所有单元格居中对齐 alignment = Alignment(horizontal='center', vertical='center') for row in ws.iter_rows(): for cell in row: cell.alignment = alignment # 使用示例 if __name__ == "__main__": files_path = "./test/output/" files = os.listdir(files_path) for file in tqdm(files): if file.split('.')[-1] == "json": try: file_path = os.path.join(files_path, file) file_content = read_json_file(file_path) file_name = file.replace("json", "xlsx") export_to_excel(file_content, file_name) print(f"完成文件处理:{file_path}") except Exception as e: print(f"文件处理失败:{file_path}")