|
|
@@ -0,0 +1,523 @@
|
|
|
+import tkinter as tk
|
|
|
+from tkinter import filedialog, messagebox
|
|
|
+import pandas as pd
|
|
|
+from openpyxl import load_workbook
|
|
|
+import os
|
|
|
+import sys
|
|
|
+import re
|
|
|
+from collections import Counter
|
|
|
+
|
|
|
+class ExcelMapperApp:
|
|
|
+ def __init__(self, root):
|
|
|
+ self.root = root
|
|
|
+ self.root.title("Excel 自动填表工具")
|
|
|
+ self.root.geometry("750x550")
|
|
|
+ self.root.resizable(True, True)
|
|
|
+
|
|
|
+ if getattr(sys, 'frozen', False):
|
|
|
+ self.base_dir = os.path.dirname(sys.executable)
|
|
|
+ else:
|
|
|
+ self.base_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
+
|
|
|
+ self.source_file = tk.StringVar()
|
|
|
+ self.header_row = tk.StringVar(value="3")
|
|
|
+ self.source_header_row = tk.StringVar(value="2")
|
|
|
+ self.df = None
|
|
|
+ self.status_text = tk.StringVar(value="请选择来源Excel文件")
|
|
|
+
|
|
|
+ self.setup_ui()
|
|
|
+
|
|
|
+ def setup_ui(self):
|
|
|
+ title_label = tk.Label(self.root, text="Excel 自动填表工具", font=("Arial", 16, "bold"))
|
|
|
+ title_label.pack(pady=10)
|
|
|
+
|
|
|
+ file_frame = tk.LabelFrame(self.root, text="文件选择", font=("Arial", 10, "bold"))
|
|
|
+ file_frame.pack(pady=10, padx=20, fill="x")
|
|
|
+
|
|
|
+ tk.Label(file_frame, text="来源数据Excel:").grid(row=0, column=0, sticky="w", pady=5)
|
|
|
+ tk.Entry(file_frame, textvariable=self.source_file, width=50).grid(row=0, column=1, padx=5, pady=5)
|
|
|
+ tk.Button(file_frame, text="浏览", command=self.select_source_file).grid(row=0, column=2, pady=5)
|
|
|
+
|
|
|
+ tk.Label(file_frame, text="来源表头行号(横向格式用):").grid(row=1, column=0, sticky="w", pady=5)
|
|
|
+ tk.Entry(file_frame, textvariable=self.source_header_row, width=10).grid(row=1, column=1, sticky="w", padx=5, pady=5)
|
|
|
+ tk.Label(file_frame, text="(第2行是字段名)", fg="gray").grid(row=1, column=1, sticky="e", padx=5, pady=5)
|
|
|
+
|
|
|
+ tk.Label(file_frame, text="模板表头行号:").grid(row=2, column=0, sticky="w", pady=5)
|
|
|
+ tk.Entry(file_frame, textvariable=self.header_row, width=10).grid(row=2, column=1, sticky="w", padx=5, pady=5)
|
|
|
+ tk.Label(file_frame, text="(第3行是字段名)", fg="gray").grid(row=2, column=1, sticky="e", padx=5, pady=5)
|
|
|
+
|
|
|
+ template_path = os.path.join(self.base_dir, "模板.xlsx")
|
|
|
+ output_path = os.path.join(self.base_dir, "输出.xlsx")
|
|
|
+ tk.Label(file_frame, text=f"目标模板: {template_path}", fg="blue").grid(row=3, column=0, columnspan=3, sticky="w", pady=3)
|
|
|
+ tk.Label(file_frame, text=f"输出文件: {output_path}", fg="green").grid(row=4, column=0, columnspan=3, sticky="w", pady=3)
|
|
|
+
|
|
|
+ btn_frame = tk.Frame(self.root)
|
|
|
+ btn_frame.pack(pady=10)
|
|
|
+ tk.Button(btn_frame, text="🔍 查看来源结构", command=self.inspect_source,
|
|
|
+ bg="#9C27B0", fg="white", font=("Arial", 10), width=14).pack(side="left", padx=5)
|
|
|
+ tk.Button(btn_frame, text="🚀 开始生成", command=self.generate_output,
|
|
|
+ bg="#4CAF50", fg="white", font=("Arial", 10), width=14).pack(side="left", padx=5)
|
|
|
+ tk.Button(btn_frame, text="清空日志", command=self.clear_log,
|
|
|
+ bg="#f44336", fg="white", font=("Arial", 10), width=14).pack(side="left", padx=5)
|
|
|
+
|
|
|
+ log_frame = tk.LabelFrame(self.root, text="运行日志", font=("Arial", 10, "bold"))
|
|
|
+ log_frame.pack(pady=10, padx=20, fill="both", expand=True)
|
|
|
+
|
|
|
+ self.log_text = tk.Text(log_frame, height=14, font=("Courier", 9))
|
|
|
+ self.log_text.pack(side="left", fill="both", expand=True)
|
|
|
+
|
|
|
+ scrollbar = tk.Scrollbar(log_frame, command=self.log_text.yview)
|
|
|
+ scrollbar.pack(side="right", fill="y")
|
|
|
+ self.log_text.config(yscrollcommand=scrollbar.set)
|
|
|
+
|
|
|
+ status_bar = tk.Label(self.root, textvariable=self.status_text, relief="sunken", anchor="w",
|
|
|
+ font=("Arial", 9), bg="#f0f0f0")
|
|
|
+ status_bar.pack(side="bottom", fill="x")
|
|
|
+
|
|
|
+ def log(self, msg):
|
|
|
+ self.log_text.insert(tk.END, msg + "\n")
|
|
|
+ self.log_text.see(tk.END)
|
|
|
+ self.root.update()
|
|
|
+ print(msg)
|
|
|
+
|
|
|
+ def clear_log(self):
|
|
|
+ self.log_text.delete(1.0, tk.END)
|
|
|
+
|
|
|
+ def select_source_file(self):
|
|
|
+ file_path = filedialog.askopenfilename(
|
|
|
+ title="选择来源Excel文件",
|
|
|
+ filetypes=[("Excel files", "*.xlsx *.xls"), ("All files", "*.*")]
|
|
|
+ )
|
|
|
+ if file_path:
|
|
|
+ self.source_file.set(file_path)
|
|
|
+ self.status_text.set(f"已选择: {os.path.basename(file_path)}")
|
|
|
+ self.log(f"📁 已选择来源文件: {file_path}")
|
|
|
+
|
|
|
+ def inspect_source(self):
|
|
|
+ """检测并显示来源格式"""
|
|
|
+ source_path = self.source_file.get()
|
|
|
+ if not source_path:
|
|
|
+ messagebox.showwarning("警告", "请先选择来源Excel文件!")
|
|
|
+ return
|
|
|
+
|
|
|
+ self.log("\n" + "="*60)
|
|
|
+ self.log("🔍 分析来源Excel结构...")
|
|
|
+
|
|
|
+ try:
|
|
|
+ wb = load_workbook(source_path)
|
|
|
+ ws = wb.active
|
|
|
+ self.log(f"📊 共 {ws.max_row} 行, {ws.max_column} 列")
|
|
|
+
|
|
|
+ # 判断是横向还是纵向
|
|
|
+ first_row_vals = []
|
|
|
+ for col in range(1, min(20, ws.max_column + 1)):
|
|
|
+ val = ws.cell(row=1, column=col).value
|
|
|
+ if val:
|
|
|
+ first_row_vals.append(str(val))
|
|
|
+
|
|
|
+ is_horizontal = len(first_row_vals) >= 3 and not any(':' in v for v in first_row_vals[:3])
|
|
|
+
|
|
|
+ if is_horizontal:
|
|
|
+ self.log(f"\n📌 检测到:横向格式(多列表格)")
|
|
|
+ self.log(f" 第1行有 {len(first_row_vals)} 个有值单元格")
|
|
|
+ self.log(f" 前几个: {first_row_vals[:5]}")
|
|
|
+
|
|
|
+ # 显示第2行列名
|
|
|
+ row2_vals = []
|
|
|
+ for col in range(1, min(30, ws.max_column + 1)):
|
|
|
+ val = ws.cell(row=2, column=col).value
|
|
|
+ if val:
|
|
|
+ row2_vals.append(str(val))
|
|
|
+ if row2_vals:
|
|
|
+ self.log(f"\n📌 第2行(列名): {row2_vals[:10]}...")
|
|
|
+ else:
|
|
|
+ self.log(f"\n📌 检测到:纵向格式(从上往下排列)")
|
|
|
+
|
|
|
+ # 显示前20行
|
|
|
+ self.log(f"\n📌 前20行内容:")
|
|
|
+ for row in range(1, min(21, ws.max_row + 1)):
|
|
|
+ val = ws.cell(row=row, column=1).value
|
|
|
+ if val:
|
|
|
+ self.log(f" 第{row}行: {str(val)[:80]}")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ self.log(f"❌ 分析失败: {str(e)}")
|
|
|
+
|
|
|
+ def parse_vertical_format(self, ws):
|
|
|
+ """解析纵向格式(Key: Value 格式),相同Item Code合并"""
|
|
|
+ products_dict = {} # 用字典存储,key是Item Code
|
|
|
+ current_item_code = None
|
|
|
+ current_product = {}
|
|
|
+
|
|
|
+ field_map = {
|
|
|
+ "Item Code": "Item Code",
|
|
|
+ "产品名称": "产品名称",
|
|
|
+ "颜色": "颜色",
|
|
|
+ "材质": "材质",
|
|
|
+ "组装长度 (英寸)": "组装长度(英寸)",
|
|
|
+ "组装宽度 (英寸)": "组装宽度(英寸)",
|
|
|
+ "组装高度 (英寸)": "组装高度(英寸)",
|
|
|
+ "产品重量 (磅)": "产品重量(磅)",
|
|
|
+ "长度 (英寸)": "包装尺寸-长度(英寸)",
|
|
|
+ "宽度 (英寸)": "包装尺寸-宽度(英寸)",
|
|
|
+ "高度 (英寸)": "包装尺寸-高度(英寸)",
|
|
|
+ "重量 (磅)": "包装尺寸-重量(磅)",
|
|
|
+ }
|
|
|
+
|
|
|
+ price_pattern = re.compile(r'^\$?([\d.]+)$')
|
|
|
+
|
|
|
+ for row in range(1, ws.max_row + 1):
|
|
|
+ val = ws.cell(row=row, column=1).value
|
|
|
+ if val is None:
|
|
|
+ continue
|
|
|
+
|
|
|
+ val_str = str(val).strip()
|
|
|
+ if not val_str:
|
|
|
+ continue
|
|
|
+
|
|
|
+ # 检测是否是 "Key: Value" 格式
|
|
|
+ if ':' in val_str:
|
|
|
+ parts = val_str.split(':', 1)
|
|
|
+ key = parts[0].strip()
|
|
|
+ value = parts[1].strip() if len(parts) > 1 else ''
|
|
|
+
|
|
|
+ if key == "Item Code":
|
|
|
+ # 保存当前产品到字典
|
|
|
+ if current_item_code and current_product:
|
|
|
+ if current_item_code in products_dict:
|
|
|
+ # 合并(不覆盖已有字段)
|
|
|
+ for k, v in current_product.items():
|
|
|
+ if k not in products_dict[current_item_code] or not products_dict[current_item_code][k]:
|
|
|
+ products_dict[current_item_code][k] = v
|
|
|
+ else:
|
|
|
+ products_dict[current_item_code] = current_product.copy()
|
|
|
+
|
|
|
+ # 开始新产品
|
|
|
+ current_item_code = value
|
|
|
+ current_product = {"Item Code": value}
|
|
|
+ else:
|
|
|
+ mapped_key = field_map.get(key, key)
|
|
|
+ # 只有当前产品中没有该字段时才设置(保留第一次出现的值)
|
|
|
+ if mapped_key not in current_product or not current_product[mapped_key]:
|
|
|
+ current_product[mapped_key] = value
|
|
|
+ else:
|
|
|
+ # 没有冒号,可能是价格
|
|
|
+ match = price_pattern.match(val_str.replace('$', '').strip())
|
|
|
+ if match and current_item_code:
|
|
|
+ price_val = match.group(1)
|
|
|
+ # 判断是 Base Cost 还是 MSRP
|
|
|
+ if current_item_code in products_dict:
|
|
|
+ existing = products_dict[current_item_code]
|
|
|
+ if "优惠单价" not in existing or not existing["优惠单价"]:
|
|
|
+ existing["优惠单价"] = price_val
|
|
|
+ elif "Manufacturer Suggested Retail Price" not in existing or not existing["Manufacturer Suggested Retail Price"]:
|
|
|
+ existing["Manufacturer Suggested Retail Price"] = str(float(price_val) + 70)
|
|
|
+ else:
|
|
|
+ if "优惠单价" not in current_product or not current_product["优惠单价"]:
|
|
|
+ current_product["优惠单价"] = price_val
|
|
|
+ elif "Manufacturer Suggested Retail Price" not in current_product or not current_product["Manufacturer Suggested Retail Price"]:
|
|
|
+ current_product["Manufacturer Suggested Retail Price"] = str(float(price_val) + 70)
|
|
|
+
|
|
|
+ # 保存最后一个产品
|
|
|
+ if current_item_code and current_product:
|
|
|
+ if current_item_code in products_dict:
|
|
|
+ for k, v in current_product.items():
|
|
|
+ if k not in products_dict[current_item_code] or not products_dict[current_item_code][k]:
|
|
|
+ products_dict[current_item_code][k] = v
|
|
|
+ else:
|
|
|
+ products_dict[current_item_code] = current_product
|
|
|
+
|
|
|
+ # 转换为列表
|
|
|
+ return list(products_dict.values())
|
|
|
+
|
|
|
+ def generate_output(self):
|
|
|
+ self.log("\n" + "="*60)
|
|
|
+ self.log("🚀 开始处理...")
|
|
|
+
|
|
|
+ source_path = self.source_file.get()
|
|
|
+ template_path = os.path.join(self.base_dir, "模板.xlsx")
|
|
|
+
|
|
|
+ if not source_path:
|
|
|
+ messagebox.showwarning("警告", "请选择来源Excel文件!")
|
|
|
+ return
|
|
|
+
|
|
|
+ if not os.path.exists(template_path):
|
|
|
+ self.log(f"❌ 找不到模板文件: {template_path}")
|
|
|
+ messagebox.showerror("错误", f"找不到模板文件:\n{template_path}")
|
|
|
+ return
|
|
|
+
|
|
|
+ try:
|
|
|
+ header_row = int(self.header_row.get())
|
|
|
+ source_header = int(self.source_header_row.get())
|
|
|
+
|
|
|
+ # ========== 1. 检测并读取来源数据 ==========
|
|
|
+ wb_source = load_workbook(source_path)
|
|
|
+ ws_source = wb_source.active
|
|
|
+
|
|
|
+ # 检测格式
|
|
|
+ first_row_vals = []
|
|
|
+ for col in range(1, min(20, ws_source.max_column + 1)):
|
|
|
+ val = ws_source.cell(row=1, column=col).value
|
|
|
+ if val:
|
|
|
+ first_row_vals.append(str(val))
|
|
|
+
|
|
|
+ is_horizontal = len(first_row_vals) >= 3 and not any(':' in v for v in first_row_vals[:3])
|
|
|
+
|
|
|
+ if is_horizontal:
|
|
|
+ self.log(f"📌 检测到横向格式,使用 pandas 读取...")
|
|
|
+ self.df = pd.read_excel(source_path, header=source_header - 1)
|
|
|
+ self.log(f"✅ 成功读取 {len(self.df)} 行数据")
|
|
|
+ else:
|
|
|
+ self.log(f"📌 检测到纵向格式,使用解析器读取...")
|
|
|
+ products = self.parse_vertical_format(ws_source)
|
|
|
+ self.log(f"✅ 成功解析 {len(products)} 个产品")
|
|
|
+ self.df = pd.DataFrame(products)
|
|
|
+ self.log(f"📋 解析到的字段: {list(self.df.columns)}")
|
|
|
+
|
|
|
+ # ========== 2. 加载模板(直接修改原文件) ==========
|
|
|
+ self.log(f"\n📖 加载模板: {template_path}")
|
|
|
+ wb = load_workbook(template_path)
|
|
|
+ ws = wb.active
|
|
|
+
|
|
|
+ # ========== 3. 找到已有数据的最后一行 ==========
|
|
|
+ # 从第4行开始找,找到第一个完全空的行
|
|
|
+ last_row = 3 # 从第4行开始检查
|
|
|
+ for row in range(4, ws.max_row + 2):
|
|
|
+ is_empty = True
|
|
|
+ for col in range(1, min(20, ws.max_column + 1)): # 检查前20列
|
|
|
+ if ws.cell(row=row, column=col).value is not None:
|
|
|
+ is_empty = False
|
|
|
+ break
|
|
|
+ if is_empty:
|
|
|
+ last_row = row
|
|
|
+ break
|
|
|
+ else:
|
|
|
+ last_row = ws.max_row + 1
|
|
|
+
|
|
|
+ self.log(f"📌 模板中已有数据到第 {last_row - 1} 行,从第 {last_row} 行开始追加")
|
|
|
+
|
|
|
+ # ========== 4. 读取模板表头 ==========
|
|
|
+ col_indices = {}
|
|
|
+ for col in range(1, ws.max_column + 1):
|
|
|
+ cell_value = ws.cell(row=header_row, column=col).value
|
|
|
+ if cell_value:
|
|
|
+ col_name = str(cell_value).strip()
|
|
|
+ if col_name and not col_name.startswith("Unnamed"):
|
|
|
+ col_indices[col_name] = col
|
|
|
+
|
|
|
+ # ========== 5. 目标列映射 ==========
|
|
|
+ target_cols = [
|
|
|
+ "Supplier Part Number", "Manufacturer Part Number", "Base Cost",
|
|
|
+ "Manufacturer Suggested Retail Price", "Product Weight",
|
|
|
+ "Carton Weight 1", "Carton Height 1", "Carton Width 1", "Carton Depth 1",
|
|
|
+ "Base Color", "Base Material", "Color", "Material",
|
|
|
+ "Overall Depth - Front to Back", "Overall Height - Top to Bottom",
|
|
|
+ "Overall Product Weight", "Overall Width - Side to Side",
|
|
|
+ "Top Color", "Top Material"
|
|
|
+ ]
|
|
|
+
|
|
|
+ found_cols = {}
|
|
|
+ for target in target_cols:
|
|
|
+ if target in col_indices:
|
|
|
+ found_cols[target] = col_indices[target]
|
|
|
+
|
|
|
+ if not found_cols:
|
|
|
+ self.log(f"\n❌ 没有找到任何目标列!")
|
|
|
+ return
|
|
|
+
|
|
|
+ # ========== 6. 匹配来源列 ==========
|
|
|
+ source_mapping = {
|
|
|
+ "Item Code": "Item Code",
|
|
|
+ "产品名称": "产品名称",
|
|
|
+ "优惠单价": "优惠单价",
|
|
|
+ "产品重量": "产品重量(磅)",
|
|
|
+ "包装尺寸-重量": "包装尺寸-重量(磅)",
|
|
|
+ "包装尺寸-高度": "包装尺寸-高度(英寸)",
|
|
|
+ "包装尺寸-宽度": "包装尺寸-宽度(英寸)",
|
|
|
+ "包装尺寸-长度": "包装尺寸-长度(英寸)",
|
|
|
+ "颜色": "颜色",
|
|
|
+ "材质": "材质",
|
|
|
+ "组装长度": "组装长度(英寸)",
|
|
|
+ "组装高度": "组装高度(英寸)",
|
|
|
+ "组装宽度": "组装宽度(英寸)",
|
|
|
+ }
|
|
|
+
|
|
|
+ source_map = {}
|
|
|
+ for key, expected in source_mapping.items():
|
|
|
+ found = False
|
|
|
+ for col in self.df.columns:
|
|
|
+ if col == expected:
|
|
|
+ source_map[key] = col
|
|
|
+ found = True
|
|
|
+ break
|
|
|
+ if not found:
|
|
|
+ # 部分匹配
|
|
|
+ for col in self.df.columns:
|
|
|
+ if expected in col or col in expected:
|
|
|
+ source_map[key] = col
|
|
|
+ found = True
|
|
|
+ break
|
|
|
+ if not found:
|
|
|
+ source_map[key] = None
|
|
|
+
|
|
|
+ # ========== 7. 填充数据 ==========
|
|
|
+ def get_key(row):
|
|
|
+ item = row.get("Item Code", "")
|
|
|
+ name = row.get("产品名称", "")
|
|
|
+ return f"{item}_{name}"
|
|
|
+
|
|
|
+ keys = [get_key(row) for _, row in self.df.iterrows()]
|
|
|
+ key_count = Counter(keys)
|
|
|
+ key_index = Counter()
|
|
|
+
|
|
|
+ row_num = last_row
|
|
|
+ filled_count = 0
|
|
|
+
|
|
|
+ self.log(f"\n✍️ 开始追加数据...")
|
|
|
+
|
|
|
+ for idx, (_, src_row) in enumerate(self.df.iterrows()):
|
|
|
+ item_code = src_row.get("Item Code", "")
|
|
|
+ product_name = src_row.get("产品名称", "")
|
|
|
+
|
|
|
+ if pd.isna(item_code):
|
|
|
+ item_code = ""
|
|
|
+ if pd.isna(product_name):
|
|
|
+ product_name = ""
|
|
|
+
|
|
|
+ key = get_key(src_row)
|
|
|
+
|
|
|
+ if key_count[key] > 1:
|
|
|
+ key_index[key] += 1
|
|
|
+ suffix = f".{key_index[key] - 1}"
|
|
|
+ mfr_suffix = f".{key_index[key]}"
|
|
|
+ else:
|
|
|
+ suffix = ""
|
|
|
+ mfr_suffix = ""
|
|
|
+
|
|
|
+ self.log(f"\n--- 追加第 {idx+1}/{len(self.df)} 个产品 ---")
|
|
|
+ self.log(f" Item Code: '{item_code}'")
|
|
|
+ self.log(f" 产品名称: '{str(product_name)[:50]}...'")
|
|
|
+
|
|
|
+ fill_count = 0
|
|
|
+
|
|
|
+ # Supplier Part Number
|
|
|
+ if "Supplier Part Number" in found_cols:
|
|
|
+ val = f"{item_code}{product_name}{suffix}"
|
|
|
+ ws.cell(row=row_num, column=found_cols["Supplier Part Number"], value=val)
|
|
|
+ fill_count += 1
|
|
|
+
|
|
|
+ # Manufacturer Part Number
|
|
|
+ if "Manufacturer Part Number" in found_cols:
|
|
|
+ val = f"{item_code}{product_name}{mfr_suffix}"
|
|
|
+ ws.cell(row=row_num, column=found_cols["Manufacturer Part Number"], value=val)
|
|
|
+ fill_count += 1
|
|
|
+
|
|
|
+ # Base Cost
|
|
|
+ if "Base Cost" in found_cols:
|
|
|
+ col = source_map.get("优惠单价")
|
|
|
+ if col and col in src_row:
|
|
|
+ val = src_row[col]
|
|
|
+ if pd.notna(val):
|
|
|
+ ws.cell(row=row_num, column=found_cols["Base Cost"], value=val)
|
|
|
+ fill_count += 1
|
|
|
+
|
|
|
+ # MSRP
|
|
|
+ if "Manufacturer Suggested Retail Price" in found_cols:
|
|
|
+ col = source_map.get("优惠单价")
|
|
|
+ if col and col in src_row:
|
|
|
+ val = src_row[col]
|
|
|
+ try:
|
|
|
+ num = float(val) if pd.notna(val) else 0
|
|
|
+ result = num + 70
|
|
|
+ ws.cell(row=row_num, column=found_cols["Manufacturer Suggested Retail Price"], value=result)
|
|
|
+ fill_count += 1
|
|
|
+ except:
|
|
|
+ pass
|
|
|
+ else:
|
|
|
+ msrp_col = source_map.get("Manufacturer Suggested Retail Price")
|
|
|
+ if msrp_col and msrp_col in src_row:
|
|
|
+ val = src_row[msrp_col]
|
|
|
+ if pd.notna(val):
|
|
|
+ ws.cell(row=row_num, column=found_cols["Manufacturer Suggested Retail Price"], value=val)
|
|
|
+ fill_count += 1
|
|
|
+
|
|
|
+ # Product Weight
|
|
|
+ if "Product Weight" in found_cols:
|
|
|
+ col = source_map.get("产品重量")
|
|
|
+ if col and col in src_row:
|
|
|
+ val = src_row[col]
|
|
|
+ if pd.notna(val):
|
|
|
+ ws.cell(row=row_num, column=found_cols["Product Weight"], value=val)
|
|
|
+ fill_count += 1
|
|
|
+
|
|
|
+ # Carton
|
|
|
+ carton_map = [
|
|
|
+ ("Carton Weight 1", "包装尺寸-重量"),
|
|
|
+ ("Carton Height 1", "包装尺寸-高度"),
|
|
|
+ ("Carton Width 1", "包装尺寸-宽度"),
|
|
|
+ ("Carton Depth 1", "包装尺寸-长度"),
|
|
|
+ ]
|
|
|
+ for target, src_name in carton_map:
|
|
|
+ if target in found_cols:
|
|
|
+ col = source_map.get(src_name)
|
|
|
+ if col and col in src_row:
|
|
|
+ val = src_row[col]
|
|
|
+ if pd.notna(val):
|
|
|
+ ws.cell(row=row_num, column=found_cols[target], value=val)
|
|
|
+ fill_count += 1
|
|
|
+
|
|
|
+ # Color/Material
|
|
|
+ cm_map = [
|
|
|
+ ("Base Color", "颜色"),
|
|
|
+ ("Base Material", "材质"),
|
|
|
+ ("Color", "颜色"),
|
|
|
+ ("Material", "材质"),
|
|
|
+ ("Top Color", "颜色"),
|
|
|
+ ("Top Material", "材质"),
|
|
|
+ ]
|
|
|
+ for target, src_name in cm_map:
|
|
|
+ if target in found_cols:
|
|
|
+ col = source_map.get(src_name)
|
|
|
+ if col and col in src_row:
|
|
|
+ val = src_row[col]
|
|
|
+ if pd.notna(val):
|
|
|
+ ws.cell(row=row_num, column=found_cols[target], value=val)
|
|
|
+ fill_count += 1
|
|
|
+
|
|
|
+ # Overall
|
|
|
+ overall_map = [
|
|
|
+ ("Overall Depth - Front to Back", "组装长度"),
|
|
|
+ ("Overall Height - Top to Bottom", "组装高度"),
|
|
|
+ ("Overall Product Weight", "产品重量"),
|
|
|
+ ("Overall Width - Side to Side", "组装宽度"),
|
|
|
+ ]
|
|
|
+ for target, src_name in overall_map:
|
|
|
+ if target in found_cols:
|
|
|
+ col = source_map.get(src_name)
|
|
|
+ if col and col in src_row:
|
|
|
+ val = src_row[col]
|
|
|
+ if pd.notna(val):
|
|
|
+ ws.cell(row=row_num, column=found_cols[target], value=val)
|
|
|
+ fill_count += 1
|
|
|
+
|
|
|
+ self.log(f" 📊 本行填充 {fill_count} 个字段")
|
|
|
+ row_num += 1
|
|
|
+ filled_count += 1
|
|
|
+
|
|
|
+ # ========== 8. 直接保存到模板文件 ==========
|
|
|
+ wb.save(template_path)
|
|
|
+ self.log(f"\n✅ 成功追加到模板文件: {template_path}")
|
|
|
+ self.log(f"📊 共追加 {filled_count} 行数据")
|
|
|
+ self.status_text.set(f"✅ 已追加 {filled_count} 行到模板")
|
|
|
+ messagebox.showinfo("成功", f"已追加 {filled_count} 行数据到模板文件:\n{template_path}")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ self.log(f"\n❌ 错误: {str(e)}")
|
|
|
+ import traceback
|
|
|
+ self.log(traceback.format_exc())
|
|
|
+ messagebox.showerror("错误", f"生成失败:\n{str(e)}")
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ root = tk.Tk()
|
|
|
+ app = ExcelMapperApp(root)
|
|
|
+ root.mainloop()
|