自动匹配上架图片.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import tkinter as tk
  2. from tkinter import ttk, filedialog, messagebox, scrolledtext
  3. import pandas as pd
  4. import os
  5. import threading
  6. import requests
  7. from openpyxl import load_workbook
  8. from openpyxl.utils import get_column_letter
  9. import re
  10. class ImageMatcherApp:
  11. def __init__(self, root):
  12. self.root = root
  13. self.root.title("图片匹配与上传工具")
  14. self.root.geometry("850x650")
  15. # 存储文件路径
  16. self.template_path = tk.StringVar()
  17. self.image_dir = tk.StringVar()
  18. # 固定上传配置
  19. self.upload_url = "https://cos.port.run/uploadFile"
  20. self.upload_group = "soundSynthesis"
  21. self.upload_user = "1"
  22. # 创建UI
  23. self.create_widgets()
  24. def create_widgets(self):
  25. # 主框架
  26. main_frame = ttk.Frame(self.root, padding="10")
  27. main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  28. # 配置网格权重
  29. self.root.columnconfigure(0, weight=1)
  30. self.root.rowconfigure(0, weight=1)
  31. main_frame.columnconfigure(1, weight=1)
  32. main_frame.rowconfigure(4, weight=1)
  33. # 标题
  34. title_label = ttk.Label(main_frame, text="图片匹配与上传工具", font=("Arial", 16, "bold"))
  35. title_label.grid(row=0, column=0, columnspan=3, pady=10)
  36. # 模板文件选择
  37. ttk.Label(main_frame, text="模板文件:").grid(row=1, column=0, sticky=tk.W, pady=5)
  38. ttk.Entry(main_frame, textvariable=self.template_path, width=50).grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5, padx=5)
  39. ttk.Button(main_frame, text="选择模板", command=self.select_template).grid(row=1, column=2, pady=5, padx=5)
  40. # 图片目录选择
  41. ttk.Label(main_frame, text="图片目录:").grid(row=2, column=0, sticky=tk.W, pady=5)
  42. ttk.Entry(main_frame, textvariable=self.image_dir, width=50).grid(row=2, column=1, sticky=(tk.W, tk.E), pady=5, padx=5)
  43. ttk.Button(main_frame, text="选择目录", command=self.select_image_dir).grid(row=2, column=2, pady=5, padx=5)
  44. # 按钮框架
  45. button_frame = ttk.Frame(main_frame)
  46. button_frame.grid(row=3, column=0, columnspan=3, pady=10)
  47. ttk.Button(button_frame, text="开始处理", command=self.start_matching, width=15).pack(side=tk.LEFT, padx=5)
  48. ttk.Button(button_frame, text="清空日志", command=self.clear_log, width=15).pack(side=tk.LEFT, padx=5)
  49. # 日志框
  50. log_frame = ttk.LabelFrame(main_frame, text="日志", padding="5")
  51. log_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=10)
  52. log_frame.columnconfigure(0, weight=1)
  53. log_frame.rowconfigure(0, weight=1)
  54. self.log_text = scrolledtext.ScrolledText(log_frame, height=15, width=80, wrap=tk.WORD)
  55. self.log_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  56. # 状态栏
  57. self.status_var = tk.StringVar()
  58. self.status_var.set("就绪")
  59. status_bar = ttk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
  60. status_bar.grid(row=5, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
  61. # 存储生成的文件路径
  62. self.generated_file_path = None
  63. def select_template(self):
  64. filename = filedialog.askopenfilename(
  65. title="选择模板文件",
  66. filetypes=[("Excel文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
  67. )
  68. if filename:
  69. self.template_path.set(filename)
  70. self.log(f"已选择模板文件: {filename}")
  71. def select_image_dir(self):
  72. directory = filedialog.askdirectory(title="选择图片目录")
  73. if directory:
  74. self.image_dir.set(directory)
  75. self.log(f"已选择图片目录: {directory}")
  76. def log(self, message):
  77. """在日志框中添加消息"""
  78. self.log_text.insert(tk.END, f"{message}\n")
  79. self.log_text.see(tk.END)
  80. self.root.update_idletasks()
  81. def clear_log(self):
  82. """清空日志"""
  83. self.log_text.delete(1.0, tk.END)
  84. def start_matching(self):
  85. """开始匹配处理"""
  86. if not self.template_path.get():
  87. messagebox.showerror("错误", "请先选择模板文件!")
  88. return
  89. if not self.image_dir.get():
  90. messagebox.showerror("错误", "请先选择图片目录!")
  91. return
  92. thread = threading.Thread(target=self.process_matching, daemon=True)
  93. thread.start()
  94. def process_matching(self):
  95. """执行匹配处理"""
  96. try:
  97. self.status_var.set("处理中...")
  98. self.log("=" * 50)
  99. self.log("开始处理...")
  100. template_path = self.template_path.get()
  101. image_dir = self.image_dir.get()
  102. self.log(f"读取模板文件: {template_path}")
  103. # 使用openpyxl加载工作簿,保留所有样式
  104. wb = load_workbook(template_path)
  105. ws = wb.active
  106. # 获取表头(第二行)
  107. headers = []
  108. for col in range(1, ws.max_column + 1):
  109. cell_value = ws.cell(row=2, column=col).value
  110. headers.append(str(cell_value) if cell_value else '')
  111. self.log(f"找到 {len(headers)} 列")
  112. # 查找各列的列号
  113. col_a_index = 1 # A列是第1列
  114. product_main_col = None
  115. image_cols = {}
  116. for idx, header in enumerate(headers, start=1):
  117. if header == '产品主图':
  118. product_main_col = idx
  119. elif '上架图片' in header:
  120. # 提取数字
  121. match = re.search(r'(\d+)', header)
  122. if match:
  123. num = int(match.group(1))
  124. image_cols[num] = idx
  125. if product_main_col is None:
  126. self.log("警告: 未找到'产品主图'列")
  127. else:
  128. self.log(f"找到产品主图列: 第{product_main_col}列")
  129. self.log(f"找到上架图片列: {len(image_cols)} 列")
  130. # 处理每一行(从第三行开始)
  131. processed_count = 0
  132. for row_idx in range(3, ws.max_row + 1):
  133. # 获取A列的值
  134. a_cell = ws.cell(row=row_idx, column=col_a_index)
  135. a_value = str(a_cell.value).strip() if a_cell.value else ''
  136. if not a_value or a_value == 'nan' or a_value == 'None':
  137. continue
  138. self.log(f"\n处理第 {row_idx} 行, A列值: {a_value}")
  139. # 处理产品主图 (图片编号1)
  140. if product_main_col is not None:
  141. img_url = self.find_and_upload_image(a_value, 1, image_dir)
  142. if img_url:
  143. ws.cell(row=row_idx, column=product_main_col, value=img_url)
  144. self.log(f" 产品主图上传成功: {img_url[:50]}...")
  145. else:
  146. self.log(f" 产品主图: 未找到或上传失败")
  147. # 处理上架图片1-6 (图片编号2-7)
  148. for i in range(1, 7):
  149. if i in image_cols:
  150. img_url = self.find_and_upload_image(a_value, i+1, image_dir)
  151. if img_url:
  152. ws.cell(row=row_idx, column=image_cols[i], value=img_url)
  153. self.log(f" 上架图片{i}上传成功: {img_url[:50]}...")
  154. else:
  155. self.log(f" 上架图片{i}: 未找到或上传失败")
  156. processed_count += 1
  157. # 保存结果(保留所有样式)
  158. output_path = self.generate_output_path(template_path)
  159. wb.save(output_path)
  160. self.generated_file_path = output_path
  161. self.log(f"\n处理完成!共处理 {processed_count} 行数据")
  162. self.log(f"结果已保存到: {output_path}")
  163. self.status_var.set(f"处理完成!共处理 {processed_count} 行")
  164. messagebox.showinfo("完成", f"处理完成!\n共处理 {processed_count} 行数据\n结果保存在:\n{output_path}")
  165. except Exception as e:
  166. error_msg = f"处理出错: {str(e)}"
  167. self.log(f"错误: {error_msg}")
  168. self.status_var.set("处理出错")
  169. messagebox.showerror("错误", error_msg)
  170. import traceback
  171. self.log(traceback.format_exc())
  172. def generate_output_path(self, template_path):
  173. """生成输出文件路径"""
  174. dir_path = os.path.dirname(template_path)
  175. base_name = os.path.basename(template_path)
  176. name_without_ext = os.path.splitext(base_name)[0]
  177. new_filename = f"{name_without_ext}_结果.xlsx"
  178. return os.path.join(dir_path, new_filename)
  179. def find_and_upload_image(self, a_value, num, image_dir):
  180. """查找图片并上传,返回URL"""
  181. # 查找本地图片
  182. extensions = ['.jpg', '.jpeg', '.png']
  183. local_file = None
  184. for ext in extensions:
  185. filename = f"{a_value}_{num}{ext}"
  186. filepath = os.path.join(image_dir, filename)
  187. if os.path.exists(filepath):
  188. local_file = filepath
  189. break
  190. if not local_file:
  191. return None
  192. # 上传图片到服务器
  193. try:
  194. full_url = f"{self.upload_url}?group={self.upload_group}&user={self.upload_user}"
  195. # 根据文件扩展名确定content-type
  196. content_type = 'image/jpeg'
  197. if local_file.lower().endswith('.png'):
  198. content_type = 'image/png'
  199. with open(local_file, 'rb') as f:
  200. files = {'file': (os.path.basename(local_file), f, content_type)}
  201. response = requests.post(full_url, files=files, timeout=30)
  202. if response.status_code == 200:
  203. result = response.json()
  204. if result.get('err') == 0:
  205. return result.get('url')
  206. else:
  207. self.log(f" 上传失败: {result.get('msg', '未知错误')}")
  208. return None
  209. else:
  210. self.log(f" 上传请求失败,状态码: {response.status_code}")
  211. return None
  212. except Exception as e:
  213. self.log(f" 上传出错: {str(e)}")
  214. return None
  215. def main():
  216. root = tk.Tk()
  217. app = ImageMatcherApp(root)
  218. root.mainloop()
  219. if __name__ == "__main__":
  220. main()