class SCLMultiProcessor:
def __init__(self, root):
self.root = root
self.root.title("SCL文件处理系统 - 增强版")
self.root.geometry("1100x750") # 增加窗口尺寸以适应新控件
# 初始化变量
self.color_detector = EnhancedColorDetector()
self.stats_processor = SCLRuleProcessor(self.color_detector)
self.empty_cell_detector = EnhancedEmptyCellDetector(self.color_detector)
self.progress_var = tk.DoubleVar()
# 创建主框架
self.main_frame = ttk.Frame(root, padding="10")
self.main_frame.pack(fill=tk.BOTH, expand=True)
# 创建UI
self.create_ui()
# 记录UI初始化完成
logger.info("用户界面初始化完成")
def create_ui(self):
"""创建用户界面"""
# 文件选择区域
file_frame = ttk.LabelFrame(self.main_frame, text="文件选择", padding="10")
file_frame.pack(fill=tk.X, pady=5)
# 输入文件选择
input_frame = ttk.Frame(file_frame)
input_frame.pack(fill=tk.X, pady=5)
ttk.Label(input_frame, text="主输入文件:").pack(side=tk.LEFT, padx=5)
self.input_path_var = tk.StringVar()
input_entry = ttk.Entry(input_frame, textvariable=self.input_path_var, width=70)
input_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
ttk.Button(input_frame, text="浏览...", command=self.browse_input_file).pack(side=tk.LEFT, padx=5)
# 操作模式选择区域
mode_frame = ttk.LabelFrame(self.main_frame, text="操作模式", padding="10")
mode_frame.pack(fill=tk.X, pady=5)
# 添加操作模式单选按钮
self.operation_mode = tk.StringVar(value="stats") # 默认选择统计模式
ttk.Radiobutton(mode_frame, text="统计功能", variable=self.operation_mode, value="stats").pack(side=tk.LEFT, padx=10)
ttk.Radiobutton(mode_frame, text="SCL格式检查", variable=self.operation_mode, value="empty_check").pack(side=tk.LEFT, padx=10)
# 配置区域
config_frame = ttk.LabelFrame(self.main_frame, text="处理配置", padding="10")
config_frame.pack(fill=tk.X, pady=5)
# 添加SCL文件夹路径输入
scl_folder_frame = ttk.Frame(config_frame)
scl_folder_frame.pack(fill=tk.X, pady=5)
ttk.Label(scl_folder_frame, text="SCL文件夹路径:").grid(row=0, column=0, padx=5, sticky=tk.W)
self.scl_folder_var = tk.StringVar()
scl_folder_entry = ttk.Entry(scl_folder_frame, textvariable=self.scl_folder_var, width=60)
scl_folder_entry.grid(row=0, column=1, padx=5, sticky=tk.W)
ttk.Button(scl_folder_frame, text="浏览...", command=self.browse_scl_folder).grid(row=0, column=2, padx=5, sticky=tk.W)
# 搜索选项
search_frame = ttk.Frame(config_frame)
search_frame.pack(fill=tk.X, pady=5)
ttk.Label(search_frame, text="文件前缀:").grid(row=0, column=0, padx=5, sticky=tk.W)
self.prefix_var = tk.StringVar(value="SCL_")
ttk.Entry(search_frame, textvariable=self.prefix_var, width=10).grid(row=0, column=1, padx=5, sticky=tk.W)
# 添加CheckSheet路径输入
checksheet_frame = ttk.Frame(config_frame)
checksheet_frame.pack(fill=tk.X, pady=5)
ttk.Label(checksheet_frame, text="CheckSheet路径:").grid(row=0, column=0, padx=5, sticky=tk.W)
self.checksheet_path_var = tk.StringVar()
checksheet_entry = ttk.Entry(checksheet_frame, textvariable=self.checksheet_path_var, width=60)
checksheet_entry.grid(row=0, column=1, padx=5, sticky=tk.W)
ttk.Button(checksheet_frame, text="浏览...", command=self.browse_checksheet_path).grid(row=0, column=2, padx=5, sticky=tk.W)
# 添加性能提示
ttk.Label(config_frame, text="(表头固定在第3行,数据从第4行开始)").pack(anchor=tk.W, padx=5, pady=2)
# 颜色容差设置
tolerance_frame = ttk.Frame(config_frame)
tolerance_frame.pack(fill=tk.X, pady=5)
ttk.Label(tolerance_frame, text="颜色容差:").grid(row=0, column=0, padx=5, sticky=tk.W)
self.tolerance_var = tk.IntVar(value=30)
tolerance_spinbox = ttk.Spinbox(
tolerance_frame, from_=0, to=100, width=5, textvariable=self.tolerance_var
)
tolerance_spinbox.grid(row=0, column=1, padx=5, sticky=tk.W)
ttk.Label(tolerance_frame, text="(0-100,值越大颜色匹配越宽松)").grid(row=0, column=2, padx=5, sticky=tk.W)
# 日志选项
log_frame = ttk.Frame(config_frame)
log_frame.pack(fill=tk.X, pady=5)
ttk.Label(log_frame, text="日志级别:").grid(row=0, column=0, padx=5, sticky=tk.W)
self.log_level_var = tk.StringVar(value="INFO")
log_level_combo = ttk.Combobox(
log_frame, textvariable=self.log_level_var, width=10, state="readonly"
)
log_level_combo['values'] = ('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL')
log_level_combo.grid(row=0, column=1, padx=5, sticky=tk.W)
log_level_combo.bind("<<ComboboxSelected>>", self.change_log_level)
# 处理按钮
btn_frame = ttk.Frame(self.main_frame)
btn_frame.pack(fill=tk.X, pady=10)
ttk.Button(btn_frame, text="开始处理", command=self.process_file).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="查看日志", command=self.view_log).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="导出配置", command=self.export_config).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="加载配置", command=self.load_config).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="退出", command=self.root.destroy).pack(side=tk.RIGHT, padx=5)
# 进度条
progress_frame = ttk.Frame(self.main_frame)
progress_frame.pack(fill=tk.X, pady=5)
ttk.Label(progress_frame, text="处理进度:").pack(side=tk.LEFT, padx=5)
self.progress_bar = ttk.Progressbar(
progress_frame, variable=self.progress_var, maximum=100, length=700
)
self.progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
self.progress_label = ttk.Label(progress_frame, text="0%")
self.progress_label.pack(side=tk.LEFT, padx=5)
# 结果展示区域
result_frame = ttk.LabelFrame(self.main_frame, text="处理结果", padding="10")
result_frame.pack(fill=tk.BOTH, expand=True, pady=5)
# 结果文本框
self.result_text = scrolledtext.ScrolledText(
result_frame, wrap=tk.WORD, height=20
)
self.result_text.pack(fill=tk.BOTH, expand=True)
self.result_text.config(state=tk.DISABLED)
# 状态栏
self.status_var = tk.StringVar(value="就绪")
status_bar = ttk.Label(self.main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
status_bar.pack(fill=tk.X, pady=5)
logger.info("UI创建完成")
def browse_checksheet_path(self):
"""浏览CheckSheet文件夹"""
folder_path = filedialog.askdirectory(title="选择CheckSheet文件夹")
if folder_path:
self.checksheet_path_var.set(folder_path)
logger.info(f"已选择CheckSheet文件夹: {folder_path}")
def change_log_level(self, event=None):
"""更改日志级别"""
level = self.log_level_var.get()
logger.setLevel(getattr(logging, level))
logger.info(f"日志级别已更改为: {level}")
def browse_input_file(self):
"""浏览输入文件"""
file_path = filedialog.askopenfilename(
filetypes=[("Excel 文件", "*.xlsx *.xls"), ("所有文件", "*.*")]
)
if file_path:
self.input_path_var.set(file_path)
self.input_file = file_path
logger.info(f"已选择输入文件: {file_path}")
def browse_scl_folder(self):
"""浏览SCL文件夹"""
folder_path = filedialog.askdirectory(title="选择SCL文件夹")
if folder_path:
self.scl_folder_var.set(folder_path)
logger.info(f"已选择SCL文件夹: {folder_path}")
def highlight_cell(self, sheet, row, col, color="FFFF0000"):
"""为单元格设置背景色"""
try:
fill = PatternFill(start_color=color, end_color=color, fill_type="solid")
sheet.cell(row=row, column=col).fill = fill
return True
except Exception as e:
logger.error(f"设置单元格颜色失败: {str(e)}")
return False
def process_file(self):
"""处理文件 - 每次处理保存数据,下次运行重新开始"""
if not self.input_path_var.get():
messagebox.showwarning("警告", "请先选择输入文件")
logger.warning("未选择输入文件")
return
try:
# 重置结果
self.result_text.config(state=tk.NORMAL)
self.result_text.delete(1.0, tk.END)
self.result_text.insert(tk.END, "开始处理...\n")
self.result_text.see(tk.END)
self.result_text.config(state=tk.DISABLED)
self.status_var.set("开始处理文件...")
self.root.update()
# 更新颜色检测器的容差设置
self.color_detector.tolerance = self.tolerance_var.get()
logger.info(f"颜色容差设置为: {self.color_detector.tolerance}")
# 获取输入文件目录
input_file = self.input_path_var.get()
input_dir = os.path.dirname(input_file)
logger.info(f"开始处理文件: {input_file}")
logger.info(f"文件目录: {input_dir}")
# 获取SCL文件夹路径
scl_folder = self.scl_folder_var.get()
if not scl_folder:
# 如果没有指定SCL文件夹,则使用输入文件所在目录
scl_folder = input_dir
logger.info(f"未指定SCL文件夹,使用输入文件目录: {scl_folder}")
# 构建文件路径时使用SCL文件夹
file_path = os.path.join(scl_folder, file_name)
# 使用openpyxl加载工作簿(保留格式)
wb = openpyxl.load_workbook(input_file)
sheet = wb.active
logger.info(f"工作簿加载成功, 工作表: {sheet.title}")
# 获取配置参数
prefix = self.prefix_var.get()
operation_mode = self.operation_mode.get()
logger.info(f"配置参数: 文件前缀={prefix}, 操作模式={operation_mode}")
# 扫描E列(第5列)
total_rows = sheet.max_row
processed_count = 0
found_files = 0
problem_files = 0
logger.info(f"开始扫描E列, 总行数: {total_rows}")
start_time = time.time()
for row_idx in range(1, total_rows + 1):
# 更新进度
progress = (row_idx / total_rows) * 100
self.progress_var.set(progress)
self.progress_label.config(text=f"{progress:.1f}%")
self.root.update()
cell = sheet.cell(row=row_idx, column=5)
cell_value = str(cell.value) if cell.value else ""
# 检查是否包含前缀的文件名
if prefix in cell_value:
# 提取文件名(可能有多个以空格分隔)
file_names = re.findall(fr'{prefix}[^\s]+', cell_value)
logger.info(f"行 {row_idx}: 找到文件: {', '.join(file_names)}")
result_lines = []
file_has_problems = False # 标记当前行是否有问题文件
for file_name in file_names:
file_path = os.path.join(scl_folder, file_name)
# 检查文件是否存在
if not os.path.exists(file_path):
result_lines.append(f"{file_name}: 文件不存在")
logger.warning(f"文件不存在: {file_path}")
# 标记文件不存在的单元格为紫色
self.highlight_cell(sheet, row_idx, 5, "FF800080")
file_has_problems = True
problem_files += 1
continue
# 根据操作模式执行不同处理
if operation_mode == "stats":
# 统计模式
results, color_report, missing_data = self.stats_processor.process_file(file_path)
# 如果有数据缺失
if missing_data:
file_has_problems = True
problem_files += 1
result_lines.append(f"{file_name}: 数据缺失!")
for item in missing_data:
result_lines.append(f" - {item['message']}")
logger.warning(item['message'])
else:
result_lines.append(f"{file_name}: 处理完成")
# 将结果写入主Excel文件的不同列
for rule_name, result_str in results.items():
target_col = self.stats_processor.RULE_MAPPING.get(rule_name)
if target_col:
target_cell = sheet.cell(row=row_idx, column=target_col)
target_cell.value = result_str
elif operation_mode == "empty_check":
# SCL格式检查模式
checksheet_path = self.checksheet_path_var.get()
missing_data, marked_file_path = self.empty_cell_detector.detect_empty_cells(
file_path, checksheet_path
)
if missing_data:
file_has_problems = True
problem_files += 1
result_lines.append(f"{file_name}: 发现空单元格!")
for item in missing_data:
result_lines.append(f" - {item['message']}")
logger.warning(item['message'])
# 添加标记文件路径信息
if marked_file_path:
result_lines.append(f"已生成标记文件: {marked_file_path}")
else:
result_lines.append(f"{file_name}: 无空单元格问题")
found_files += 1
# 如果该行有文件存在问题,将E列单元格标红
if file_has_problems:
self.highlight_cell(sheet, row_idx, 5)
logger.info(f"行 {row_idx} E列单元格标记为红色(存在问题)")
# 更新结果文本框
self.result_text.config(state=tk.NORMAL)
self.result_text.insert(
tk.END, f"行 {row_idx} 处理结果:\n" + "\n".join(result_lines) + "\n\n"
)
self.result_text.see(tk.END)
self.result_text.config(state=tk.DISABLED)
processed_count += 1
# 保存修改后的Excel文件 - 每次处理保存数据
output_path = input_file.replace(".xlsx", "_processed.xlsx")
wb.save(output_path)
logger.info(f"结果已保存到: {output_path}")
elapsed_time = time.time() - start_time
status_msg = f"处理完成! 处理了 {processed_count} 个文件项, 耗时 {elapsed_time:.2f} 秒"
if problem_files > 0:
status_msg += f", {problem_files} 个文件存在问题"
self.status_var.set(status_msg)
logger.info(status_msg)
# 更新结果文本框
self.result_text.config(state=tk.NORMAL)
self.result_text.insert(
tk.END, f"\n{status_msg}\n"
f"结果已保存到: {output_path}\n"
)
self.result_text.see(tk.END)
self.result_text.config(state=tk.DISABLED)
messagebox.showinfo("完成", status_msg)
except Exception as e:
error_msg = f"处理文件时出错: {str(e)}"
logger.exception(f"处理文件时出错: {str(e)}")
messagebox.showerror("错误", error_msg)
self.status_var.set(f"错误: {str(e)}")
# 更新结果文本框
self.result_text.config(state=tk.NORMAL)
self.result_text.insert(tk.END, f"\n错误: {error_msg}\n")
self.result_text.see(tk.END)
self.result_text.config(state=tk.DISABLED)
def view_log(self):
"""查看日志"""
log_window = tk.Toplevel(self.root)
log_window.title("处理日志")
log_window.geometry("800x600")
log_frame = ttk.Frame(log_window, padding="10")
log_frame.pack(fill=tk.BOTH, expand=True)
# 日志文本框
log_text = scrolledtext.ScrolledText(
log_frame, wrap=tk.WORD, height=30
)
log_text.pack(fill=tk.BOTH, expand=True)
# 读取日志文件
log_file = 'scl_processor.log'
try:
if not os.path.exists(log_file):
with open(log_file, 'w', encoding='utf-8') as f:
f.write("日志文件已创建,暂无记录\n")
with open(log_file, 'r', encoding='utf-8') as f:
log_content = f.read()
log_text.insert(tk.END, log_content)
except Exception as e:
log_text.insert(tk.END, f"无法读取日志文件: {str(e)}")
# 设置为只读
log_text.config(state=tk.DISABLED)
# 添加刷新按钮
refresh_btn = ttk.Button(log_frame, text="刷新日志", command=lambda: self.refresh_log(log_text))
refresh_btn.pack(pady=5)
logger.info("日志查看窗口已打开")
def refresh_log(self, log_text):
"""刷新日志内容"""
log_text.config(state=tk.NORMAL)
log_text.delete(1.0, tk.END)
try:
with open('scl_processor.log', 'r', encoding='utf-8') as f:
log_content = f.read()
log_text.insert(tk.END, log_content)
except Exception as e:
log_text.insert(tk.END, f"刷新日志失败: {str(e)}")
log_text.config(state=tk.DISABLED)
log_text.see(tk.END)
logger.info("日志已刷新")
def export_config(self):
"""导出配置到文件"""
config = {
"prefix": self.prefix_var.get(),
"log_level": self.log_level_var.get(),
"operation_mode": self.operation_mode.get(),
"tolerance": self.tolerance_var.get(),
"checksheet_path": self.checksheet_path_var.get(),
"scl_folder": self.scl_folder_var.get()
}
file_path = filedialog.asksaveasfilename(
defaultextension=".json",
filetypes=[("JSON 文件", "*.json"), ("所有文件", "*.*")]
)
if file_path:
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write(str(config))
messagebox.showinfo("成功", f"配置已导出到: {file_path}")
logger.info(f"配置已导出到: {file_path}")
except Exception as e:
messagebox.showerror("错误", f"导出配置失败: {str(e)}")
logger.error(f"导出配置失败: {str(e)}")
def load_config(self):
"""从文件加载配置"""
file_path = filedialog.askopenfilename(
filetypes=[("JSON 文件", "*.json"), ("所有文件", "*.*")]
)
self.scl_folder_var.set(config.get("scl_folder", ""))
if file_path:
try:
with open(file_path, 'r', encoding='utf-8') as f:
config = eval(f.read())
self.prefix_var.set(config.get("prefix", "SCL_"))
self.log_level_var.set(config.get("log_level", "INFO"))
self.operation_mode.set(config.get("operation_mode", "stats"))
self.tolerance_var.set(config.get("tolerance", 30))
self.checksheet_path_var.set(config.get("checksheet_path", ""))
self.change_log_level()
messagebox.showinfo("成功", "配置已加载")
logger.info(f"配置已从 {file_path} 加载")
except Exception as e:
messagebox.showerror("错误", f"加载配置失败: {str(e)}")
logger.error(f"加载配置失败: {str(e)}")
给出完整的修改过后的这个类
最新发布