| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- import tkinter as tk
- from tkinter import filedialog, messagebox, ttk
- import pandas as pd
- from openpyxl import load_workbook
- import os
- import sys
- import shutil
- from collections import Counter
- from datetime import datetime
- class ExcelMapperApp:
- def __init__(self, root):
- self.root = root
- self.root.title("Excel 自动填表工具")
- self.root.geometry("800x600")
- 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="4")
- self.source_header_row = tk.StringVar(value="2")
- self.df = None
- self.valid_values = {} # 存储 Valid Values 工作表的数据
- 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="(第4行是字段名)", fg="gray").grid(row=2, column=1, sticky="e", padx=5, pady=5)
-
- template_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="(数据将直接追加到模板文件末尾)", 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.check_template_header,
- bg="#FF9800", 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=16, 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} 列")
-
- self.log("\n📌 前3行 × 前20列:")
- for row in range(1, min(4, ws.max_row + 1)):
- values = []
- for col in range(1, min(21, ws.max_column + 1)):
- val = ws.cell(row=row, column=col).value
- if val:
- values.append(f"列{col}:{str(val)[:30]}")
- if values:
- self.log(f" 第{row}行: {' | '.join(values)}")
-
- self.log(f"\n📌 第2行(列名):")
- row2_vals = []
- for col in range(1, min(ws.max_column + 1, 30)):
- val = ws.cell(row=2, column=col).value
- if val:
- row2_vals.append(f"列{col}:{str(val)[:25]}")
- if row2_vals:
- self.log(f" {' | '.join(row2_vals)}")
-
- except Exception as e:
- self.log(f"❌ 分析失败: {str(e)}")
-
- def check_template_header(self):
- template_path = os.path.join(self.base_dir, "模板.xlsx")
-
- if not os.path.exists(template_path):
- self.log(f"❌ 找不到模板文件: {template_path}")
- return
-
- self.log("\n" + "="*60)
- self.log("🔍 检查模板表头...")
-
- try:
- wb = load_workbook(template_path)
- ws = wb.active
- self.log(f"📊 共 {ws.max_row} 行, {ws.max_column} 列")
-
- # 显示前5行
- for row in range(1, min(6, ws.max_row + 1)):
- values = []
- for col in range(1, min(31, ws.max_column + 1)):
- val = ws.cell(row=row, column=col).value
- if val:
- values.append(f"列{col}:{str(val)[:30]}")
- if values:
- self.log(f"\n📌 第{row}行: {' | '.join(values[:10])}")
- if len(values) > 10:
- self.log(f" ... 还有 {len(values)-10} 个有值单元格")
- else:
- self.log(f"\n📌 第{row}行: (全空)")
-
- # 检查 Valid Values 工作表
- if "Valid Values" in wb.sheetnames:
- self.log(f"\n✅ 找到 'Valid Values' 工作表")
- ws_valid = wb["Valid Values"]
- self.log(f" 共 {ws_valid.max_row} 行, {ws_valid.max_column} 列")
-
- # 读取前几行
- for row in range(1, min(6, ws_valid.max_row + 1)):
- values = []
- for col in range(1, min(10, ws_valid.max_column + 1)):
- val = ws_valid.cell(row=row, column=col).value
- if val:
- values.append(str(val)[:20])
- if values:
- self.log(f" Valid Values 第{row}行: {' | '.join(values)}")
- else:
- self.log(f"\n⚠️ 未找到 'Valid Values' 工作表")
-
- except Exception as e:
- self.log(f"❌ 检查失败: {str(e)}")
-
- def load_valid_values(self, wb):
- """从 Valid Values 工作表读取有效值"""
- valid_data = {}
-
- if "Valid Values" not in wb.sheetnames:
- self.log("⚠️ 未找到 'Valid Values' 工作表,将使用硬编码默认值")
- return valid_data
-
- ws_valid = wb["Valid Values"]
-
- # 读取第1行作为列名
- headers = []
- for col in range(1, ws_valid.max_column + 1):
- val = ws_valid.cell(row=1, column=col).value
- if val:
- headers.append(str(val).strip())
- else:
- headers.append(f"Column_{col}")
-
- # 读取数据行
- for row in range(2, ws_valid.max_row + 1):
- for col in range(1, len(headers) + 1):
- val = ws_valid.cell(row=row, column=col).value
- if val:
- header = headers[col - 1]
- if header not in valid_data:
- valid_data[header] = []
- if str(val).strip() not in valid_data[header]:
- valid_data[header].append(str(val).strip())
-
- return valid_data
-
- 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. 备份模板 ==========
- backup_path = template_path.replace(".xlsx", f"_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx")
- shutil.copy2(template_path, backup_path)
- self.log(f"📦 已备份模板到: {backup_path}")
-
- # ========== 2. 读取来源数据 ==========
- self.log(f"📖 读取来源数据: {source_path}")
- self.log(f"📌 使用第 {source_header} 行作为列名")
-
- self.df = pd.read_excel(source_path, header=source_header - 1)
- self.log(f"✅ 成功读取 {len(self.df)} 行数据")
-
- # ========== 3. 加载模板并读取 Valid Values ==========
- self.log(f"\n📖 加载模板: {template_path}")
- wb = load_workbook(template_path)
- ws = wb.active
-
- # 加载 Valid Values
- self.valid_values = self.load_valid_values(wb)
- self.log(f"📋 从 Valid Values 读取到 {len(self.valid_values)} 个字段的有效值")
-
- # ========== 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
-
- self.log(f"📊 模板中共有 {len(col_indices)} 个有名称的列")
-
- # ========== 5. 找到已有数据的最后一行 ==========
- last_row = 5
- for row in range(5, ws.max_row + 2):
- is_empty = True
- for col in range(1, min(20, ws.max_column + 1)):
- 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} 行开始追加")
-
- # ========== 6. 定义默认值映射 ==========
- # 格式: 目标列名 -> (值来源, 处理方式)
- # 值来源: 'fixed' 固定值, 'valid' 从Valid Values取第一个, 'source' 从来源取, 'computed' 计算
- default_mapping = {
- # 固定值
- "Variant Type": ("fixed", "Not Variant"),
- "Minimum Order Quantity": ("fixed", 1),
- "Force Quantity Multiplier": ("fixed", 1),
- "Display Set Quantity": ("fixed", 1),
- "Ship Type": ("valid", "Ship Type"), # 从Valid Values取第一个
- "Lead Time": ("fixed", 48),
- "Replacement Lead Time": ("fixed", 120),
- "Flat Pack": ("fixed", "Yes"),
- "Assembly Required": ("fixed", "Yes"),
- "Canada Product Restriction": ("fixed", "Yes"),
- "CARB Phase II Compliant (formaldehyde emissions)": ("fixed", "Yes"),
- "Commercial Warranty": ("fixed", "Yes"),
- "CANFER Compliant": ("fixed", "Yes"),
- "Commercial Warranty Length": ("fixed", "1 Years"),
- "Composite Wood Product (CWP)": ("fixed", "Yes"),
- "Country Of Manufacturer": ("fixed", "China"),
- "General Certificate of Conformity (GCC)": ("fixed", "Yes"),
- "Hazard Class(es)": ("fixed", "Does Not Apply"),
- "Hazardous Material / Dangerous Goods": ("fixed", "No"),
- "Hazardous Material Weight": ("fixed", "Does Not Apply"),
- "ISTA Certified": ("popup", ""), # 弹出输入框
- "Level of Assembly": ("fixed", "Full Assembly Needed"),
- "Packing Group": ("fixed", "Does Not Apply"),
- "Reason for Restriction": ("fixed", "Does Not Apply"),
- "Supplier Intended and Approved Use": ("fixed", "Residential Use"),
- "TSCA Title VI Compliant (formaldehyde emissions)": ("fixed", "Yes"),
- "UN or ID number": ("fixed", "Does Not Apply"),
- "Uniform Packaging and Labeling Regulations (UPLR) Compliant": ("fixed", "Yes"),
- "Warning Required": ("fixed", "No"),
- "Warranty Length": ("fixed", "1 Years"),
- "Battery or Batteries Included": ("fixed", "No"),
- # 动态值(从来源取)
- "Product Type": ("source", "产品类目"),
- "Brand": ("valid", "Brand"), # 从Valid Values取第一个
- }
-
- # ========== 7. 匹配来源列 ==========
- source_mapping = {
- "Item Code": "Item Code",
- "产品名称": "产品名称",
- "优惠单价": "优惠单价",
- "产品重量": "产品重量(磅)",
- "包装尺寸-重量": "包装尺寸-重量(磅)",
- "包装尺寸-高度": "包装尺寸-高度(英寸)",
- "包装尺寸-宽度": "包装尺寸-宽度(英寸)",
- "包装尺寸-长度": "包装尺寸-长度(英寸)",
- "颜色": "颜色",
- "材质": "材质",
- "组装长度": "组装长度(英寸)",
- "组装高度": "组装高度(英寸)",
- "组装宽度": "组装宽度(英寸)",
- "产品类目": "产品类目",
- }
-
- self.log(f"\n🔍 匹配来源列:")
- 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 found:
- self.log(f" ✅ '{key}' -> '{source_map[key]}'")
- else:
- self.log(f" ❌ '{key}' -> 未找到")
- source_map[key] = None
-
- # ========== 8. 填充数据 ==========
- 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 col_indices:
- val = f"{item_code}{product_name}{suffix}"
- ws.cell(row=row_num, column=col_indices["Supplier Part Number"], value=val)
- fill_count += 1
-
- # ===== Manufacturer Part Number =====
- if "Manufacturer Part Number" in col_indices:
- val = f"{item_code}{product_name}{mfr_suffix}"
- ws.cell(row=row_num, column=col_indices["Manufacturer Part Number"], value=val)
- fill_count += 1
-
- # ===== Base Cost =====
- if "Base Cost" in col_indices and source_map.get("优惠单价"):
- col = source_map["优惠单价"]
- val = src_row.get(col, "")
- if pd.notna(val):
- ws.cell(row=row_num, column=col_indices["Base Cost"], value=val)
- fill_count += 1
-
- # ===== MSRP =====
- if "Manufacturer Suggested Retail Price" in col_indices and source_map.get("优惠单价"):
- col = source_map["优惠单价"]
- val = src_row.get(col, 0)
- try:
- num = float(val) if pd.notna(val) else 0
- ws.cell(row=row_num, column=col_indices["Manufacturer Suggested Retail Price"], value=num + 70)
- fill_count += 1
- except:
- pass
-
- # ===== Product Weight =====
- if "Product Weight" in col_indices and source_map.get("产品重量"):
- col = source_map["产品重量"]
- val = src_row.get(col, "")
- if pd.notna(val):
- ws.cell(row=row_num, column=col_indices["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 col_indices and source_map.get(src_name):
- col = source_map[src_name]
- val = src_row.get(col, "")
- if pd.notna(val):
- ws.cell(row=row_num, column=col_indices[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 col_indices and source_map.get(src_name):
- col = source_map[src_name]
- val = src_row.get(col, "")
- if pd.notna(val):
- ws.cell(row=row_num, column=col_indices[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 col_indices and source_map.get(src_name):
- col = source_map[src_name]
- val = src_row.get(col, "")
- if pd.notna(val):
- ws.cell(row=row_num, column=col_indices[target], value=val)
- fill_count += 1
-
- # ===== 默认值字段 =====
- variant_type = "Not Variant" # 默认值
-
- for target_col, (source_type, source_val) in default_mapping.items():
- if target_col not in col_indices:
- continue
-
- col_idx = col_indices[target_col]
-
- if source_type == "fixed":
- # 固定值
- ws.cell(row=row_num, column=col_idx, value=source_val)
- fill_count += 1
- self.log(f" ✅ {target_col} = '{source_val}' (固定值)")
-
- elif source_type == "valid":
- # 从 Valid Values 取第一个值
- if source_val in self.valid_values and self.valid_values[source_val]:
- val = self.valid_values[source_val][0]
- ws.cell(row=row_num, column=col_idx, value=val)
- fill_count += 1
- self.log(f" ✅ {target_col} = '{val}' (从Valid Values)")
- else:
- self.log(f" ⚠️ {target_col}: Valid Values中无数据,使用默认值'Not Variant'")
- ws.cell(row=row_num, column=col_idx, value="Not Variant")
- fill_count += 1
-
- elif source_type == "source":
- # 从来源取
- if source_map.get(source_val):
- col = source_map[source_val]
- val = src_row.get(col, "")
- if pd.notna(val):
- ws.cell(row=row_num, column=col_idx, value=val)
- fill_count += 1
- self.log(f" ✅ {target_col} = '{val}' (从来源)")
- else:
- self.log(f" ⚠️ {target_col}: 来源为空")
- else:
- self.log(f" ⚠️ {target_col}: 找不到来源列'{source_val}'")
-
- elif source_type == "popup":
- # 弹出输入框(ISTA Certified)
- self.log(f" ⏳ 请为 {target_col} 输入值...")
- # 这里用简单方式:如果10秒没输入就跳过
- result = self.show_input_dialog(f"请输入 {target_col}", "包装认证 (ISTA Certified):")
- if result:
- ws.cell(row=row_num, column=col_idx, value=result)
- fill_count += 1
- self.log(f" ✅ {target_col} = '{result}' (用户输入)")
- else:
- self.log(f" ⏭️ {target_col}: 用户跳过")
-
- # ===== 特殊逻辑:Variant Grouping 1/2 =====
- # Variant Grouping 1: 如果 Variant Type 是 Not Variant 则不填,否则填 "Color"
- if "Variant Grouping 1" in col_indices:
- if variant_type != "Not Variant":
- ws.cell(row=row_num, column=col_indices["Variant Grouping 1"], value="Color")
- fill_count += 1
-
- if "Variant Attribute Name On Site 1" in col_indices:
- if variant_type != "Not Variant":
- ws.cell(row=row_num, column=col_indices["Variant Attribute Name On Site 1"], value="Finish")
- fill_count += 1
-
- if "Variant Grouping 2" in col_indices:
- if variant_type != "Not Variant":
- ws.cell(row=row_num, column=col_indices["Variant Grouping 2"], value="Size")
- fill_count += 1
-
- if "Variant Attribute Name On Site 2" in col_indices:
- if variant_type != "Not Variant":
- ws.cell(row=row_num, column=col_indices["Variant Attribute Name On Site 2"], value="Dimensions")
- fill_count += 1
-
- self.log(f" 📊 本行填充 {fill_count} 个字段")
- row_num += 1
- filled_count += 1
-
- # ========== 9. 保存 ==========
- wb.save(template_path)
- self.log(f"\n✅ 成功追加到模板: {template_path}")
- self.log(f"📊 共追加 {filled_count} 行数据")
- self.log(f"📦 备份文件: {backup_path}")
- self.status_text.set(f"✅ 已追加 {filled_count} 行到模板")
- messagebox.showinfo("成功",
- f"已追加 {filled_count} 行数据到模板文件:\n{template_path}\n\n"
- f"备份文件保存在: {backup_path}"
- )
-
- except Exception as e:
- self.log(f"\n❌ 错误: {str(e)}")
- import traceback
- self.log(traceback.format_exc())
- messagebox.showerror("错误", f"生成失败:\n{str(e)}")
-
- def show_input_dialog(self, title, prompt):
- """显示输入对话框,10秒超时自动跳过"""
- result = tk.StringVar()
-
- dialog = tk.Toplevel(self.root)
- dialog.title(title)
- dialog.geometry("400x120")
- dialog.transient(self.root)
- dialog.grab_set()
-
- tk.Label(dialog, text=prompt, font=("Arial", 11)).pack(pady=10)
- entry = tk.Entry(dialog, width=40)
- entry.pack(pady=5)
- entry.focus()
-
- # 超时计数器
- timeout_seconds = 10
- time_label = tk.Label(dialog, text=f"剩余 {timeout_seconds} 秒...", fg="gray")
- time_label.pack(pady=5)
-
- def on_ok():
- result.set(entry.get())
- dialog.destroy()
-
- def on_cancel():
- dialog.destroy()
-
- def countdown(count):
- if count <= 0:
- dialog.destroy()
- return
- time_label.config(text=f"剩余 {count} 秒...")
- dialog.after(1000, countdown, count - 1)
-
- tk.Button(dialog, text="确定", command=on_ok, width=10).pack(side="left", padx=20, pady=10)
- tk.Button(dialog, text="跳过", command=on_cancel, width=10).pack(side="right", padx=20, pady=10)
-
- dialog.after(1000, countdown, timeout_seconds - 1)
- self.root.wait_window(dialog)
-
- return result.get()
- if __name__ == "__main__":
- root = tk.Tk()
- app = ExcelMapperApp(root)
- root.mainloop()
|