新增条码映射编辑功能图形化界面

This commit is contained in:
2025-05-10 11:39:11 +08:00
parent 7b7d491663
commit 5c0b709528
46 changed files with 2510 additions and 499 deletions
Binary file not shown.
+468
View File
@@ -0,0 +1,468 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
对话框工具模块
-------------
提供各种弹窗和对话框显示功能
"""
import os
import tkinter as tk
from tkinter import messagebox, ttk
from datetime import datetime
def create_custom_dialog(title="提示", message="", result_file=None, time_info=None,
count_info=None, amount_info=None, additional_info=None):
"""
创建自定义结果对话框
Args:
title: 对话框标题
message: 主要消息
result_file: 结果文件路径(如果有)
time_info: 时间信息(如:订单时间)
count_info: 数量信息(如:处理条目数)
amount_info: 金额信息(如:总金额)
additional_info: 其他附加信息(字典格式)
Returns:
dialog: 对话框对象
"""
# 创建对话框
dialog = tk.Toplevel()
dialog.title(title)
dialog.geometry("450x320")
dialog.resizable(False, False)
# 使弹窗居中显示
center_window(dialog)
# 添加标题
tk.Label(dialog, text=message, font=("Arial", 16, "bold")).pack(pady=10)
# 创建内容框架
result_frame = tk.Frame(dialog)
result_frame.pack(pady=10, fill=tk.BOTH, expand=True)
# 添加时间、数量、金额等信息
if time_info:
tk.Label(result_frame, text=f"时间信息: {time_info}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
if count_info:
tk.Label(result_frame, text=f"处理数量: {count_info}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
if amount_info:
tk.Label(result_frame, text=f"金额信息: {amount_info}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
# 添加其他附加信息
if additional_info and isinstance(additional_info, dict):
for key, value in additional_info.items():
tk.Label(result_frame, text=f"{key}: {value}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
# 如果有结果文件,显示文件信息
if result_file and os.path.exists(result_file):
tk.Label(result_frame, text=f"输出文件: {os.path.basename(result_file)}", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
# 成功提示
tk.Label(result_frame, text="处理已成功完成!", font=("Arial", 12, "bold"), fg="#28a745").pack(pady=10)
# 文件信息框
file_frame = tk.Frame(result_frame, relief=tk.GROOVE, borderwidth=1)
file_frame.pack(fill=tk.X, padx=15, pady=5)
tk.Label(file_frame, text="文件信息", font=("Arial", 10, "bold")).pack(anchor=tk.W, padx=10, pady=5)
# 获取文件大小和时间
try:
file_size = os.path.getsize(result_file)
file_time = datetime.fromtimestamp(os.path.getmtime(result_file))
size_text = f"{file_size / 1024:.1f} KB" if file_size < 1024*1024 else f"{file_size / (1024*1024):.1f} MB"
tk.Label(file_frame, text=f"文件大小: {size_text}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
tk.Label(file_frame, text=f"创建时间: {file_time.strftime('%Y-%m-%d %H:%M:%S')}", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
except:
tk.Label(file_frame, text="无法获取文件信息", font=("Arial", 10)).pack(anchor=tk.W, padx=10, pady=2)
# 添加按钮
button_frame = tk.Frame(dialog)
button_frame.pack(pady=10)
tk.Button(button_frame, text="打开文件", command=lambda: os.startfile(result_file)).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="打开所在文件夹", command=lambda: os.startfile(os.path.dirname(result_file))).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="关闭", command=dialog.destroy).pack(side=tk.LEFT, padx=5)
else:
# 如果没有结果文件或文件不存在
if result_file:
tk.Label(result_frame, text="未找到输出文件", font=("Arial", 12)).pack(anchor=tk.W, padx=20, pady=5)
tk.Label(result_frame, text="请检查输出目录", font=("Arial", 12, "bold"), fg="#dc3545").pack(pady=10)
# 添加按钮
button_frame = tk.Frame(dialog)
button_frame.pack(pady=10)
tk.Button(button_frame, text="打开输出目录", command=lambda: os.startfile(os.path.abspath("data/output"))).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="关闭", command=dialog.destroy).pack(side=tk.LEFT, padx=5)
# 确保窗口显示在最前
dialog.lift()
dialog.attributes('-topmost', True)
dialog.after_idle(lambda: dialog.attributes('-topmost', False))
return dialog
def show_custom_dialog(*args, **kwargs):
"""
显示自定义对话框
参数与create_custom_dialog相同
Returns:
dialog: 对话框对象
"""
return create_custom_dialog(*args, **kwargs)
def center_window(window):
"""使窗口居中显示"""
window.update_idletasks()
width = window.winfo_width()
height = window.winfo_height()
x = (window.winfo_screenwidth() // 2) - (width // 2)
y = (window.winfo_screenheight() // 2) - (height // 2)
window.geometry('{}x{}+{}+{}'.format(width, height, x, y))
def create_barcode_mapping_dialog(parent=None, on_save=None, current_mappings=None):
"""
创建条码映射编辑弹窗
Args:
parent: 父窗口
on_save: 保存回调函数,接收修改后的映射数据
current_mappings: 当前的映射数据
Returns:
dialog: 对话框对象
"""
dialog = tk.Toplevel(parent)
dialog.title("条码映射编辑")
dialog.geometry("600x500")
dialog.resizable(True, True)
# 使弹窗居中显示
center_window(dialog)
# 创建主框架
main_frame = tk.Frame(dialog)
main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 创建选项卡控件
tab_control = ttk.Notebook(main_frame)
# 创建两个选项卡页面
tab1 = tk.Frame(tab_control)
tab2 = tk.Frame(tab_control)
tab_control.add(tab1, text="条码映射")
tab_control.add(tab2, text="特殊处理")
tab_control.pack(expand=True, fill=tk.BOTH)
# ========= 条码映射选项卡 =========
# 顶部输入区域
input_frame = tk.Frame(tab1)
input_frame.pack(fill=tk.X, padx=5, pady=5)
tk.Label(input_frame, text="源条码:").grid(row=0, column=0, padx=5, pady=5)
source_entry = tk.Entry(input_frame, width=20)
source_entry.grid(row=0, column=1, padx=5, pady=5)
tk.Label(input_frame, text="目标条码:").grid(row=0, column=2, padx=5, pady=5)
target_entry = tk.Entry(input_frame, width=20)
target_entry.grid(row=0, column=3, padx=5, pady=5)
# 存储映射列表的变量
mapping_list = []
# 映射列表显示区域
list_frame = tk.Frame(tab1)
list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
columns = ("源条码", "目标条码")
mapping_tree = ttk.Treeview(list_frame, columns=columns, show="headings", selectmode="browse")
for col in columns:
mapping_tree.heading(col, text=col)
mapping_tree.column(col, width=100)
mapping_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 添加滚动条
scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=mapping_tree.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
mapping_tree.configure(yscrollcommand=scrollbar.set)
# ========= 特殊处理选项卡 =========
# 顶部输入区域
special_input_frame = tk.Frame(tab2)
special_input_frame.pack(fill=tk.X, padx=5, pady=5)
tk.Label(special_input_frame, text="条码:").grid(row=0, column=0, padx=5, pady=5)
special_barcode_entry = tk.Entry(special_input_frame, width=20)
special_barcode_entry.grid(row=0, column=1, padx=5, pady=5)
tk.Label(special_input_frame, text="乘数:").grid(row=1, column=0, padx=5, pady=5)
multiplier_entry = tk.Entry(special_input_frame, width=10)
multiplier_entry.grid(row=1, column=1, padx=5, pady=5)
tk.Label(special_input_frame, text="目标单位:").grid(row=1, column=2, padx=5, pady=5)
unit_entry = tk.Entry(special_input_frame, width=10)
unit_entry.grid(row=1, column=3, padx=5, pady=5)
tk.Label(special_input_frame, text="固定单价:").grid(row=2, column=0, padx=5, pady=5)
price_entry = tk.Entry(special_input_frame, width=10)
price_entry.grid(row=2, column=1, padx=5, pady=5)
tk.Label(special_input_frame, text="规格:").grid(row=2, column=2, padx=5, pady=5)
spec_entry = tk.Entry(special_input_frame, width=10)
spec_entry.grid(row=2, column=3, padx=5, pady=5)
tk.Label(special_input_frame, text="描述:").grid(row=3, column=0, padx=5, pady=5)
desc_entry = tk.Entry(special_input_frame, width=40)
desc_entry.grid(row=3, column=1, columnspan=3, padx=5, pady=5)
# 特殊处理列表显示区域
special_list_frame = tk.Frame(tab2)
special_list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
special_columns = ("条码", "乘数", "目标单位", "固定单价", "规格", "描述")
special_tree = ttk.Treeview(special_list_frame, columns=special_columns, show="headings", selectmode="browse")
for col in special_columns:
special_tree.heading(col, text=col)
special_tree.column(col, width=80)
special_tree.column("描述", width=200)
special_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# 添加滚动条
special_scrollbar = ttk.Scrollbar(special_list_frame, orient=tk.VERTICAL, command=special_tree.yview)
special_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
special_tree.configure(yscrollcommand=special_scrollbar.set)
# 存储特殊处理列表的变量
special_list = []
# 按钮区域
def add_mapping():
source = source_entry.get().strip()
target = target_entry.get().strip()
if not source or not target:
messagebox.showwarning("输入错误", "源条码和目标条码不能为空")
return
# 检查是否已存在
for item in mapping_list:
if item[0] == source:
messagebox.showwarning("重复条码", f"条码 {source} 已存在映射")
return
# 添加到列表
mapping_list.append((source, target))
mapping_tree.insert("", tk.END, values=(source, target))
# 清空输入框
source_entry.delete(0, tk.END)
target_entry.delete(0, tk.END)
def remove_mapping():
selected = mapping_tree.selection()
if not selected:
messagebox.showwarning("未选择", "请先选择要删除的条目")
return
# 获取选中项的索引
item = mapping_tree.item(selected[0])
source = item['values'][0]
# 从列表中移除
for i, (s, _) in enumerate(mapping_list):
if s == source:
mapping_list.pop(i)
break
# 从树中移除
mapping_tree.delete(selected[0])
def add_special():
barcode = special_barcode_entry.get().strip()
multiplier = multiplier_entry.get().strip()
unit = unit_entry.get().strip()
price = price_entry.get().strip()
spec = spec_entry.get().strip()
desc = desc_entry.get().strip()
if not barcode:
messagebox.showwarning("输入错误", "条码不能为空")
return
# 检查是否已存在
for item in special_list:
if item[0] == barcode:
messagebox.showwarning("重复条码", f"条码 {barcode} 已存在特殊处理")
return
# 添加到列表
special_list.append((barcode, multiplier, unit, price, spec, desc))
special_tree.insert("", tk.END, values=(barcode, multiplier, unit, price, spec, desc))
# 清空输入框
special_barcode_entry.delete(0, tk.END)
multiplier_entry.delete(0, tk.END)
unit_entry.delete(0, tk.END)
price_entry.delete(0, tk.END)
spec_entry.delete(0, tk.END)
desc_entry.delete(0, tk.END)
def remove_special():
selected = special_tree.selection()
if not selected:
messagebox.showwarning("未选择", "请先选择要删除的条目")
return
# 获取选中项的索引
item = special_tree.item(selected[0])
barcode = item['values'][0]
# 从列表中移除
for i, (b, _, _, _, _, _) in enumerate(special_list):
if b == barcode:
special_list.pop(i)
break
# 从树中移除
special_tree.delete(selected[0])
# 条码映射按钮
btn_frame = tk.Frame(tab1)
btn_frame.pack(fill=tk.X, padx=5, pady=5)
add_btn = tk.Button(btn_frame, text="添加映射", command=add_mapping)
add_btn.pack(side=tk.LEFT, padx=5)
remove_btn = tk.Button(btn_frame, text="删除映射", command=remove_mapping)
remove_btn.pack(side=tk.LEFT, padx=5)
# 特殊处理按钮
special_btn_frame = tk.Frame(tab2)
special_btn_frame.pack(fill=tk.X, padx=5, pady=5)
add_special_btn = tk.Button(special_btn_frame, text="添加特殊处理", command=add_special)
add_special_btn.pack(side=tk.LEFT, padx=5)
remove_special_btn = tk.Button(special_btn_frame, text="删除特殊处理", command=remove_special)
remove_special_btn.pack(side=tk.LEFT, padx=5)
# 底部按钮区域
bottom_frame = tk.Frame(dialog)
bottom_frame.pack(fill=tk.X, padx=10, pady=10)
def save_mappings():
# 构建保存数据
mappings = {}
# 添加条码映射
for source, target in mapping_list:
mappings[source] = {
'map_to': target,
'description': f'条码映射:{source} -> {target}'
}
# 添加特殊处理
for barcode, multiplier, unit, price, spec, desc in special_list:
mappings[barcode] = {}
if multiplier:
try:
# 安全地转换multiplier为数字
if isinstance(multiplier, str):
if '.' in multiplier:
mappings[barcode]['multiplier'] = float(multiplier)
else:
mappings[barcode]['multiplier'] = int(multiplier)
else:
# 已经是数字类型
mappings[barcode]['multiplier'] = multiplier
except ValueError:
# 如果转换失败,保持原始字符串
mappings[barcode]['multiplier'] = multiplier
if unit:
mappings[barcode]['target_unit'] = unit
if price:
try:
# 安全地转换price为浮点数
mappings[barcode]['fixed_price'] = float(price)
except ValueError:
# 如果转换失败,保持原始字符串
mappings[barcode]['fixed_price'] = price
if spec:
mappings[barcode]['specification'] = spec
if desc:
mappings[barcode]['description'] = desc
# 调用保存回调
if on_save:
on_save(mappings)
messagebox.showinfo("保存成功", f"已保存{len(mapping_list)}个条码映射和{len(special_list)}个特殊处理规则")
dialog.destroy()
def cancel():
dialog.destroy()
save_btn = tk.Button(bottom_frame, text="保存", command=save_mappings)
save_btn.pack(side=tk.RIGHT, padx=5)
cancel_btn = tk.Button(bottom_frame, text="取消", command=cancel)
cancel_btn.pack(side=tk.RIGHT, padx=5)
# 导入当前映射数据
if current_mappings:
for barcode, data in current_mappings.items():
if 'map_to' in data:
# 这是条码映射
mapping_list.append((barcode, data['map_to']))
mapping_tree.insert("", tk.END, values=(barcode, data['map_to']))
else:
# 这是特殊处理
multiplier = data.get('multiplier', '')
unit = data.get('target_unit', '')
price = data.get('fixed_price', '')
spec = data.get('specification', '')
desc = data.get('description', '')
special_list.append((barcode, multiplier, unit, price, spec, desc))
special_tree.insert("", tk.END, values=(barcode, multiplier, unit, price, spec, desc))
# 确保窗口显示在最前
dialog.transient(parent)
dialog.grab_set()
return dialog
def show_barcode_mapping_dialog(*args, **kwargs):
"""
显示条码映射编辑弹窗
参数与create_barcode_mapping_dialog相同
Returns:
dialog: 对话框对象
"""
# 确保已导入ttk
import tkinter.ttk as ttk
return create_barcode_mapping_dialog(*args, **kwargs)
+49
View File
@@ -97,6 +97,36 @@ def get_logger(name: str) -> logging.Logger:
return setup_logger(name)
return logger
def set_log_level(level: str) -> None:
"""
设置所有日志记录器的级别
Args:
level: 日志级别(DEBUG, INFO, WARNING, ERROR, CRITICAL)
"""
level_map = {
'debug': logging.DEBUG,
'info': logging.INFO,
'warning': logging.WARNING,
'error': logging.ERROR,
'critical': logging.CRITICAL
}
# 获取对应的日志级别
log_level = level_map.get(level.lower(), logging.INFO)
# 获取所有记录器
loggers = [logging.getLogger(name) for name in logging.root.manager.loggerDict]
# 设置每个记录器的级别
for logger in loggers:
logger.setLevel(log_level)
# 设置根记录器的级别
logging.getLogger().setLevel(log_level)
print(f"所有日志记录器级别已设置为: {logging.getLevelName(log_level)}")
def close_logger(name: str) -> None:
"""
关闭日志记录器的所有处理器
@@ -113,6 +143,25 @@ def close_logger(name: str) -> None:
_handlers.pop(f"{name}_file", None)
_handlers.pop(f"{name}_console", None)
def close_all_loggers() -> None:
"""
关闭所有日志记录器的处理器
"""
# 获取所有记录器
loggers = [logging.getLogger(name) for name in logging.root.manager.loggerDict]
# 关闭每个记录器的处理器
for logger in loggers:
if hasattr(logger, 'handlers'):
for handler in logger.handlers[:]:
handler.close()
logger.removeHandler(handler)
# 清空处理器缓存
_handlers.clear()
print("所有日志记录器已关闭")
def cleanup_active_marker(name: str) -> None:
"""
清理日志活跃标记