save_data.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. import os
  2. from tqdm import tqdm
  3. from typing import Dict
  4. from openpyxl import Workbook
  5. from openpyxl.drawing.image import Image
  6. from openpyxl.styles import Alignment, Font, PatternFill
  7. from openpyxl.utils import get_column_letter
  8. from openpyxl.styles.numbers import FORMAT_PERCENTAGE_00
  9. from utils.tools import read_json_file
  10. from utils.logger_config import setup_logger
  11. logger = setup_logger(__name__)
  12. def number_to_letter(n):
  13. if n < 1:
  14. return None
  15. result = []
  16. while n > 0:
  17. n -= 1 # 调整索引从0开始(A=0, B=1,...)
  18. remainder = n % 26
  19. result.append(chr(65 + remainder)) # 65是'A'的ASCII码
  20. n = n // 26
  21. return ''.join(reversed(result)) # 反转拼接结果
  22. def export_to_excel(data: Dict, filename: str = None) -> None:
  23. """
  24. 将商品数据导出到Excel文件,并在单元格中插入实际图片
  25. 参数:
  26. data: 包含商品信息的字典
  27. filename: 输出的Excel文件名
  28. """
  29. # 创建工作簿和工作表
  30. wb = Workbook()
  31. ws = wb.active
  32. ws.title = "商品数据"
  33. # 定义字体颜色 - 使用16进制RGB颜色码
  34. GREEN_FONT = Font(color="FF0000") # 绿色
  35. BLUE_FONT = Font(color="0000FF") # 蓝色
  36. RED_FONT = Font(color="00FF00")
  37. # 设置列宽
  38. _setup_column_dimensions(ws)
  39. # 提取各商品信息
  40. primary_good = data["primary_goods_info"]["goods_info"]
  41. combine_two = data["combine_two_info"]
  42. combine_three = data["combine_three_info"]
  43. outfit_orders = data["outfit_orders"]
  44. header_count = [len(order) for order in outfit_orders]
  45. # 设置表头
  46. _write_headers(ws, header_count)
  47. ws.freeze_panes = 'A2'
  48. # 主商品编码
  49. primary_code = primary_good['sku'] + primary_good['color_code']
  50. # 处理两种组合数据
  51. _process_combine_two(ws, primary_good, primary_code, combine_two, GREEN_FONT)
  52. _process_combine_three(ws, primary_good, primary_code, combine_three, len(combine_two), BLUE_FONT)
  53. _process_outfit_orders(ws, outfit_orders, [GREEN_FONT, RED_FONT])
  54. # 格式化工作表
  55. _format_worksheet(ws, len(combine_two) + len(combine_three))
  56. # 保存Excel文件
  57. if not filename:
  58. filename = primary_code + '.xlsx'
  59. os.makedirs('./output', exist_ok=True) # 确保输出目录存在
  60. file_path = os.path.join('./output', filename)
  61. wb.save(file_path)
  62. logger.info(f"数据已成功写入 {file_path} 文件")
  63. return file_path
  64. def _setup_column_dimensions(ws):
  65. """设置工作表列宽"""
  66. # 图片列
  67. for col in ['B', 'C', 'D']:
  68. ws.column_dimensions[col].width = 13.6
  69. # 数据列
  70. for col in ['A', *[get_column_letter(i) for i in range(4, 58)]]:
  71. ws.column_dimensions[col].width = 14 if col != 'A' else 40
  72. def _write_headers(ws, header_count):
  73. """写入表头"""
  74. # 大单组合表头
  75. first_sub_header = ["大单组合一", "大单组合二", "大单组合三", "大单组合四", "大单组合五"]
  76. second_sub_header = ["图片", "款号", "价格"] * sum(header_count) + ["价格"]
  77. # 连带款表头
  78. headers = [
  79. "组合", "主推款图片", "连带款1图片", "连带款2图片", "连带排名",
  80. "连带频次", "主商品频次", "连带率", "连带件数", "连带金额", ""
  81. ]
  82. # 设置连带款表头
  83. for col_idx, header in enumerate(headers, start=1):
  84. cell = ws.cell(row=1, column=col_idx)
  85. cell.value = header
  86. cell.font = Font(bold=True)
  87. cell.alignment = Alignment(horizontal='center')
  88. # 设置连带款父表头
  89. start_col = 12
  90. current_col = start_col # 当前写入位置
  91. # 定义红绿交替的颜色填充
  92. red_fill = PatternFill(start_color="FF9999", end_color="FF9999", fill_type="solid")
  93. green_fill = PatternFill(start_color="99FF99", end_color="99FF99", fill_type="solid")
  94. for idx, count in enumerate(header_count):
  95. # 计算合并范围
  96. merge_start = current_col
  97. merge_end = current_col + count * 3 - 1
  98. print(f"写入位置:{current_col}")
  99. print(f"合并范围:{merge_start}-{merge_end}")
  100. # 写入单元格内容
  101. cell = ws.cell(row=1, column=current_col)
  102. cell.value = f"大单组合:{idx+1}" # 使用idx+1作为组合编号
  103. cell.font = Font(bold=True)
  104. cell.alignment = Alignment(horizontal='center')
  105. # 根据索引奇偶性选择颜色
  106. if idx % 2 == 0: # 偶数索引使用红色
  107. cell.fill = red_fill
  108. else: # 奇数索引使用绿色
  109. cell.fill = green_fill
  110. # 合并单元格
  111. merge_range = f"{number_to_letter(merge_start)}1:{number_to_letter(merge_end)}1"
  112. ws.merge_cells(merge_range)
  113. # 更新下一个组合的起始列
  114. current_col = merge_end + 1
  115. print('--------------------------------')
  116. # 设置连带款子表头
  117. for col_idx, header in enumerate(second_sub_header, start=12):
  118. cell = ws.cell(row=2, column=col_idx)
  119. cell.value = header
  120. cell.font = Font(bold=True)
  121. cell.alignment = Alignment(horizontal='center')
  122. def _process_combine_two(ws, primary_good, primary_code, combine_two, font_color):
  123. """处理两种商品的组合数据"""
  124. for row_idx, item in enumerate(combine_two, start=2):
  125. goods_info = item['goods_info']
  126. sub_code = goods_info['sku'] + goods_info['color_code']
  127. # 写入数据并设置字体颜色
  128. for col, value in [
  129. (1, f"{primary_code},{sub_code}"),
  130. (4, "22组合"),
  131. (5, row_idx-1),
  132. (6, item['freq']),
  133. (7, item['main_freq']),
  134. (8, item['combine_rate']),
  135. (9, item['count']),
  136. (10, item['sales'])
  137. ]:
  138. cell = ws.cell(row=row_idx, column=col, value=value)
  139. cell.font = font_color
  140. # 插入图片
  141. _insert_image(ws, primary_good['image_path'], f'B{row_idx}')
  142. _insert_image(ws, goods_info['image_path'], f'C{row_idx}')
  143. def _process_combine_three(ws, primary_good, primary_code, combine_three, combine_two_count, font_color):
  144. """处理三种商品的组合数据"""
  145. for row_idx, item in enumerate(combine_three, start=2 + combine_two_count):
  146. goods_info = item['goods_info']
  147. sub1_code = goods_info[0]['sku'] + goods_info[0]['color_code']
  148. sub2_code = goods_info[1]['sku'] + goods_info[1]['color_code']
  149. # 写入数据并设置字体颜色
  150. for col, value in [
  151. (1, f"{primary_code},{sub1_code},{sub2_code}"),
  152. (5, row_idx - (1 + combine_two_count)),
  153. (6, item['freq']),
  154. (7, item['main_freq']),
  155. (8, item['combine_rate']),
  156. (9, item['count']),
  157. (10, item['sales'])
  158. ]:
  159. cell = ws.cell(row=row_idx, column=col, value=value)
  160. cell.font = font_color
  161. # 插入图片
  162. _insert_image(ws, primary_good['image_path'], f'B{row_idx}')
  163. _insert_image(ws, goods_info[0]['image_path'], f'C{row_idx}')
  164. _insert_image(ws, goods_info[1]['image_path'], f'D{row_idx}')
  165. def _process_outfit_orders(ws, outfit_orders, font_colors):
  166. start_col = 12
  167. start_row = 3
  168. total_price = 0
  169. color_change = False
  170. for _, combine_order in enumerate(outfit_orders, start=2):
  171. current_font = font_colors[int(color_change)]
  172. for col_idx, order in enumerate(combine_order, start=12):
  173. for col, value in [(col_idx-11, order["skc"]), (col_idx-11, float(order["price"]))]:
  174. cell = ws.cell(row=start_row, column=start_col + col, value=value)
  175. cell.font = current_font
  176. start_col += 1
  177. total_price += float(order["price"])
  178. if not order["image_path"]:
  179. ws.cell(row=start_row, column=start_col + col - 3, value="None")
  180. else:
  181. col_name = number_to_letter(start_col + col - 3)
  182. _insert_image(ws, order["image_path"], f'{col_name}{start_row}')
  183. start_col += len(combine_order)
  184. color_change = not color_change
  185. ws.cell(row=start_row, column=start_col+col-2, value=total_price)
  186. def _insert_image(ws, image_path, cell_location):
  187. """在指定单元格插入图片"""
  188. try:
  189. if os.path.exists(image_path):
  190. img = Image(image_path)
  191. img.width = img.height = 100 # 设置图片大小
  192. ws.add_image(img, cell_location)
  193. except Exception as e:
  194. logger.info(f"未获取到商品图片")
  195. def _format_worksheet(ws, data_rows_count):
  196. """格式化工作表"""
  197. # 设置连带率列格式
  198. for cell in ws['H']:
  199. cell.number_format = FORMAT_PERCENTAGE_00
  200. # 设置行高
  201. for row in range(2, data_rows_count + 2):
  202. ws.row_dimensions[row].height = 80
  203. # 设置所有单元格居中对齐
  204. alignment = Alignment(horizontal='center', vertical='center')
  205. for row in ws.iter_rows():
  206. for cell in row:
  207. cell.alignment = alignment
  208. # 使用示例
  209. if __name__ == "__main__":
  210. files_path = "./test/output/"
  211. files = os.listdir(files_path)
  212. for file in tqdm(files):
  213. if file.split('.')[-1] == "json":
  214. try:
  215. file_path = os.path.join(files_path, file)
  216. file_content = read_json_file(file_path)
  217. file_name = file.replace("json", "xlsx")
  218. export_to_excel(file_content, file_name)
  219. print(f"完成文件处理:{file_path}")
  220. except Exception as e:
  221. print(f"文件处理失败:{file_path}")