| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- 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}")
|