Compare commits
2 Commits
13d6c20383
...
192443c3b9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
192443c3b9 | ||
|
|
2fb44a2a9b |
961
gui/material_type_management_dialog.py
Normal file
961
gui/material_type_management_dialog.py
Normal file
@@ -0,0 +1,961 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
物料类型管理对话框
|
||||||
|
|
||||||
|
提供对 MaterialsTypeToBeDeleted 表的管理功能:
|
||||||
|
- 查看所有记录
|
||||||
|
- 按负责人筛选(复选框)
|
||||||
|
- 直接在表格中编辑(双击或按F2)
|
||||||
|
- 新增/删除记录(直接在表格中操作)
|
||||||
|
- 保存更改到数据库
|
||||||
|
"""
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, messagebox
|
||||||
|
from typing import List, Dict, Any, Optional
|
||||||
|
|
||||||
|
|
||||||
|
class EditableTreeview(ttk.Treeview):
|
||||||
|
"""可编辑的 Treeview 组件
|
||||||
|
|
||||||
|
支持双击单元格进行编辑,使用 Entry 组件覆盖在单元格上。
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent, on_edit_complete=None, on_edit_cancelled=None, **kwargs):
|
||||||
|
"""初始化可编辑的 Treeview
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent: 父容器
|
||||||
|
on_edit_complete: 编辑完成回调 (item_id, column, old_value, new_value)
|
||||||
|
on_edit_cancelled: 编辑取消回调 (item_id, column, old_value)
|
||||||
|
**kwargs: 传递给 Treeview 的参数
|
||||||
|
"""
|
||||||
|
super().__init__(parent, **kwargs)
|
||||||
|
self.on_edit_complete = on_edit_complete
|
||||||
|
self.on_edit_cancelled = on_edit_cancelled
|
||||||
|
|
||||||
|
# 编辑状态
|
||||||
|
self.editing_item = None
|
||||||
|
self.editing_column = None
|
||||||
|
self.edit_entry = None
|
||||||
|
|
||||||
|
# 绑定事件
|
||||||
|
self.bind("<Double-1>", self._on_double_click)
|
||||||
|
self.bind("<Return>", self._on_enter_key)
|
||||||
|
self.bind("<F2>", self._on_f2_key)
|
||||||
|
self.bind("<Escape>", self._on_escape_key)
|
||||||
|
self.bind("<Delete>", self._on_delete_key)
|
||||||
|
|
||||||
|
def _on_double_click(self, event):
|
||||||
|
"""处理双击事件,开始编辑"""
|
||||||
|
region = self.identify_region(event.x, event.y)
|
||||||
|
if region == "cell":
|
||||||
|
column = self.identify_column(event.x)
|
||||||
|
item = self.identify_row(event.y)
|
||||||
|
self.start_edit(item, column)
|
||||||
|
|
||||||
|
def _on_f2_key(self, event):
|
||||||
|
"""处理 F2 键,开始编辑选中单元格"""
|
||||||
|
selection = self.selection()
|
||||||
|
if selection:
|
||||||
|
item = selection[0]
|
||||||
|
# 编辑第二列(物料名称列)
|
||||||
|
self.start_edit(item, "#1")
|
||||||
|
|
||||||
|
def _on_enter_key(self, event):
|
||||||
|
"""处理 Enter 键,完成编辑并移动到下一行"""
|
||||||
|
if self.editing_item:
|
||||||
|
self._finish_edit()
|
||||||
|
# 尝试移动到下一行
|
||||||
|
selection = self.selection()
|
||||||
|
if selection:
|
||||||
|
item = selection[0]
|
||||||
|
next_item = self.next(item)
|
||||||
|
if next_item:
|
||||||
|
self.selection_set(next_item)
|
||||||
|
self.focus(next_item)
|
||||||
|
|
||||||
|
def _on_escape_key(self, event):
|
||||||
|
"""处理 Escape 键,取消编辑"""
|
||||||
|
if self.editing_item:
|
||||||
|
self._cancel_edit()
|
||||||
|
|
||||||
|
def _on_delete_key(self, event):
|
||||||
|
"""处理 Delete 键,触发删除(由父组件处理)"""
|
||||||
|
pass # 父组件会处理删除逻辑
|
||||||
|
|
||||||
|
def start_edit(self, item: str, column: str):
|
||||||
|
"""开始编辑单元格
|
||||||
|
|
||||||
|
Args:
|
||||||
|
item: Treeview item ID
|
||||||
|
column: 列标识(如 "#1", "#2")
|
||||||
|
"""
|
||||||
|
if not item or not column:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查 item 是否存在
|
||||||
|
if not self.exists(item):
|
||||||
|
return
|
||||||
|
|
||||||
|
# 如果正在编辑,先完成
|
||||||
|
if self.editing_item:
|
||||||
|
self._finish_edit()
|
||||||
|
|
||||||
|
# 获取单元格位置和当前值
|
||||||
|
bbox_result = self.bbox(item, column)
|
||||||
|
if not bbox_result:
|
||||||
|
# 单元格不可见(在滚动区域外),先滚动到可见区域
|
||||||
|
self.see(item)
|
||||||
|
bbox_result = self.bbox(item, column)
|
||||||
|
if not bbox_result:
|
||||||
|
# 仍然无法获取位置,放弃编辑
|
||||||
|
return
|
||||||
|
|
||||||
|
x, y, width, height = bbox_result
|
||||||
|
values = self.item(item, "values")
|
||||||
|
|
||||||
|
# 根据列索引获取值
|
||||||
|
column_index = int(column[1:]) - 1
|
||||||
|
if column_index < 0 or column_index >= len(values):
|
||||||
|
return
|
||||||
|
|
||||||
|
current_value = values[column_index]
|
||||||
|
|
||||||
|
# 创建 Entry 组件
|
||||||
|
self.edit_entry = ttk.Entry(self)
|
||||||
|
self.edit_entry.insert(0, current_value)
|
||||||
|
self.edit_entry.select_range(0, tk.END)
|
||||||
|
self.edit_entry.focus_set()
|
||||||
|
|
||||||
|
# 定位 Entry 组件
|
||||||
|
self.edit_entry.place(x=x, y=y, width=width, height=height)
|
||||||
|
|
||||||
|
# 记录编辑状态
|
||||||
|
self.editing_item = item
|
||||||
|
self.editing_column = column_index
|
||||||
|
|
||||||
|
# 绑定事件
|
||||||
|
self.edit_entry.bind("<FocusOut>", self._on_entry_focus_out)
|
||||||
|
self.edit_entry.bind("<Return>", lambda e: self._finish_edit())
|
||||||
|
self.edit_entry.bind("<Escape>", lambda e: self._cancel_edit())
|
||||||
|
|
||||||
|
def _finish_edit(self):
|
||||||
|
"""完成编辑"""
|
||||||
|
if not self.edit_entry or not self.editing_item:
|
||||||
|
return
|
||||||
|
|
||||||
|
new_value = self.edit_entry.get()
|
||||||
|
values = self.item(self.editing_item, "values")
|
||||||
|
old_value = values[self.editing_column] if self.editing_column < len(values) else ""
|
||||||
|
|
||||||
|
# 销毁 Entry(先销毁,防止重复触发)
|
||||||
|
entry = self.edit_entry
|
||||||
|
editing_item = self.editing_item
|
||||||
|
editing_column = self.editing_column
|
||||||
|
|
||||||
|
self.edit_entry = None
|
||||||
|
self.editing_item = None
|
||||||
|
self.editing_column = None
|
||||||
|
|
||||||
|
entry.destroy()
|
||||||
|
|
||||||
|
# 调用回调
|
||||||
|
try:
|
||||||
|
if self.on_edit_complete:
|
||||||
|
self.on_edit_complete(editing_item, editing_column, old_value, new_value)
|
||||||
|
except Exception as e:
|
||||||
|
# 回调出错,清除状态
|
||||||
|
print(f"Error in on_edit_complete: {e}")
|
||||||
|
|
||||||
|
def _cancel_edit(self):
|
||||||
|
"""取消编辑"""
|
||||||
|
if not self.edit_entry or not self.editing_item:
|
||||||
|
return
|
||||||
|
|
||||||
|
values = self.item(self.editing_item, "values")
|
||||||
|
old_value = values[self.editing_column] if self.editing_column < len(values) else ""
|
||||||
|
|
||||||
|
# 销毁 Entry(先保存状态引用)
|
||||||
|
entry = self.edit_entry
|
||||||
|
editing_item = self.editing_item
|
||||||
|
editing_column = self.editing_column
|
||||||
|
|
||||||
|
self.edit_entry = None
|
||||||
|
self.editing_item = None
|
||||||
|
self.editing_column = None
|
||||||
|
|
||||||
|
entry.destroy()
|
||||||
|
|
||||||
|
# 调用回调
|
||||||
|
try:
|
||||||
|
if self.on_edit_cancelled:
|
||||||
|
self.on_edit_cancelled(editing_item, editing_column, old_value)
|
||||||
|
except Exception as e:
|
||||||
|
# 回调出错,状态已清除
|
||||||
|
print(f"Error in on_edit_cancelled: {e}")
|
||||||
|
|
||||||
|
def _on_entry_focus_out(self, event):
|
||||||
|
"""Entry 失去焦点时完成编辑"""
|
||||||
|
# 延迟执行,避免在点击其他控件时触发
|
||||||
|
self.after(100, self._finish_edit_if_focus_lost)
|
||||||
|
|
||||||
|
def _finish_edit_if_focus_lost(self):
|
||||||
|
"""如果焦点不在 Entry 上,完成编辑"""
|
||||||
|
if self.edit_entry and self.focus_get() != self.edit_entry:
|
||||||
|
self._finish_edit()
|
||||||
|
|
||||||
|
|
||||||
|
class MaterialTypeManagementDialog(tk.Toplevel):
|
||||||
|
"""物料类型管理对话框
|
||||||
|
|
||||||
|
管理 MaterialsTypeToBeDeleted 表的 CRUD 操作。
|
||||||
|
支持直接在表格中编辑。
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 行状态标记
|
||||||
|
ROW_STATUS_UNCHANGED = "unchanged"
|
||||||
|
ROW_STATUS_NEW = "new"
|
||||||
|
ROW_STATUS_MODIFIED = "modified"
|
||||||
|
ROW_STATUS_DELETED = "deleted"
|
||||||
|
|
||||||
|
def __init__(self, parent, title: str = "类型管理"):
|
||||||
|
"""初始化对话框
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent: 父窗口
|
||||||
|
title: 窗口标题
|
||||||
|
"""
|
||||||
|
super().__init__(parent)
|
||||||
|
self.title(title)
|
||||||
|
self.geometry("900x650")
|
||||||
|
|
||||||
|
# 使窗口模态
|
||||||
|
self.transient(parent)
|
||||||
|
self.grab_set()
|
||||||
|
|
||||||
|
# 数据缓存
|
||||||
|
self.original_data: List[Dict[str, Any]] = [] # 原始数据(从数据库加载)
|
||||||
|
self.current_data: List[Dict[str, Any]] = [] # 当前显示的数据
|
||||||
|
self.row_status: Dict[str, str] = {} # key -> 行状态
|
||||||
|
self.original_values: Dict[str, Tuple[str, str]] = {} # key -> (original_material, original_manager) 用于修改
|
||||||
|
|
||||||
|
# 筛选相关
|
||||||
|
self.managers: List[str] = []
|
||||||
|
self.manager_checkboxes: Dict[str, tk.BooleanVar] = {}
|
||||||
|
self.select_all_var: tk.BooleanVar = tk.BooleanVar(value=True)
|
||||||
|
|
||||||
|
# 创建界面
|
||||||
|
self._create_widgets()
|
||||||
|
|
||||||
|
# 加载数据
|
||||||
|
self._load_data()
|
||||||
|
|
||||||
|
# 等待窗口关闭
|
||||||
|
self.wait_window()
|
||||||
|
|
||||||
|
def _create_widgets(self):
|
||||||
|
"""创建界面组件"""
|
||||||
|
# 主容器
|
||||||
|
main_container = ttk.Frame(self, padding=10)
|
||||||
|
main_container.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# 顶部:筛选区域
|
||||||
|
filter_frame = ttk.LabelFrame(main_container, text="筛选(按负责人)", padding=10)
|
||||||
|
filter_frame.pack(fill=tk.X, pady=(0, 10))
|
||||||
|
|
||||||
|
self._create_filter_area(filter_frame)
|
||||||
|
|
||||||
|
# 中部:数据表格
|
||||||
|
table_frame = ttk.LabelFrame(main_container, text="数据列表(双击编辑,Delete删除,Insert新增)", padding=10)
|
||||||
|
table_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
|
||||||
|
|
||||||
|
self._create_table(table_frame)
|
||||||
|
|
||||||
|
# 底部:操作按钮
|
||||||
|
button_frame = ttk.Frame(main_container)
|
||||||
|
button_frame.pack(fill=tk.X)
|
||||||
|
|
||||||
|
self._create_buttons(button_frame)
|
||||||
|
|
||||||
|
def _create_filter_area(self, parent):
|
||||||
|
"""创建筛选区域"""
|
||||||
|
# 创建复选框容器
|
||||||
|
canvas_container = ttk.Frame(parent)
|
||||||
|
canvas_container.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# Canvas和滚动条
|
||||||
|
self.filter_canvas = tk.Canvas(canvas_container, height=80)
|
||||||
|
scrollbar = ttk.Scrollbar(canvas_container, orient="vertical", command=self.filter_canvas.yview)
|
||||||
|
self.filter_frame = ttk.Frame(self.filter_canvas)
|
||||||
|
|
||||||
|
self.filter_frame.bind(
|
||||||
|
"<Configure>",
|
||||||
|
lambda e: self.filter_canvas.configure(scrollregion=self.filter_frame.bbox("all"))
|
||||||
|
)
|
||||||
|
|
||||||
|
self.filter_canvas.create_window((0, 0), window=self.filter_frame, anchor="nw")
|
||||||
|
self.filter_canvas.configure(yscrollcommand=scrollbar.set)
|
||||||
|
|
||||||
|
# 布局
|
||||||
|
self.filter_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||||||
|
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||||
|
|
||||||
|
# 鼠标滚轮支持
|
||||||
|
def _on_mousewheel(event):
|
||||||
|
self.filter_canvas.yview_scroll(int(-1*(event.delta/120)), "units")
|
||||||
|
self.filter_canvas.bind("<MouseWheel>", _on_mousewheel)
|
||||||
|
|
||||||
|
# 快捷按钮
|
||||||
|
button_frame = ttk.Frame(parent)
|
||||||
|
button_frame.pack(fill=tk.X, pady=(5, 0))
|
||||||
|
|
||||||
|
ttk.Button(button_frame, text="全选", command=self._select_all_managers).pack(side=tk.LEFT, padx=5)
|
||||||
|
ttk.Button(button_frame, text="取消全选", command=self._deselect_all_managers).pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
def _create_table(self, parent):
|
||||||
|
"""创建数据表格"""
|
||||||
|
columns = ("material_name", "manager_name")
|
||||||
|
self.tree = EditableTreeview(
|
||||||
|
parent,
|
||||||
|
columns=columns,
|
||||||
|
show="headings",
|
||||||
|
selectmode="extended",
|
||||||
|
on_edit_complete=self._on_edit_complete,
|
||||||
|
on_edit_cancelled=self._on_edit_cancelled
|
||||||
|
)
|
||||||
|
|
||||||
|
# 设置列标题和宽度
|
||||||
|
self.tree.heading("material_name", text="物料名称")
|
||||||
|
self.tree.heading("manager_name", text="负责人")
|
||||||
|
|
||||||
|
self.tree.column("material_name", width=400)
|
||||||
|
self.tree.column("manager_name", width=200)
|
||||||
|
|
||||||
|
# 添加滚动条
|
||||||
|
scrollbar_y = ttk.Scrollbar(parent, orient=tk.VERTICAL, command=self.tree.yview)
|
||||||
|
scrollbar_x = ttk.Scrollbar(parent, orient=tk.HORIZONTAL, command=self.tree.xview)
|
||||||
|
|
||||||
|
self.tree.configure(
|
||||||
|
yscrollcommand=scrollbar_y.set,
|
||||||
|
xscrollcommand=scrollbar_x.set
|
||||||
|
)
|
||||||
|
|
||||||
|
# 布局
|
||||||
|
self.tree.grid(row=0, column=0, sticky="nsew")
|
||||||
|
scrollbar_y.grid(row=0, column=1, sticky="ns")
|
||||||
|
scrollbar_x.grid(row=1, column=0, sticky="ew")
|
||||||
|
|
||||||
|
parent.rowconfigure(0, weight=1)
|
||||||
|
parent.columnconfigure(0, weight=1)
|
||||||
|
|
||||||
|
# 绑定键盘快捷键
|
||||||
|
self.tree.bind("<Insert>", lambda e: self._add_new_row())
|
||||||
|
self.tree.bind("<Delete>", lambda e: self._delete_selected_rows())
|
||||||
|
|
||||||
|
# 右键菜单
|
||||||
|
self.context_menu = tk.Menu(self.tree, tearoff=0)
|
||||||
|
self.context_menu.add_command(label="新增记录 (Insert)", command=self._add_new_row)
|
||||||
|
self.context_menu.add_command(label="编辑记录 (F2)", command=self._edit_selected_cell)
|
||||||
|
self.context_menu.add_separator()
|
||||||
|
self.context_menu.add_command(label="删除记录 (Delete)", command=self._delete_selected_rows)
|
||||||
|
|
||||||
|
self.tree.bind("<Button-3>", self._show_context_menu)
|
||||||
|
|
||||||
|
def _create_buttons(self, parent):
|
||||||
|
"""创建底部按钮"""
|
||||||
|
ttk.Button(parent, text="新增 (Insert)", command=self._add_new_row).pack(side=tk.LEFT, padx=5)
|
||||||
|
ttk.Button(parent, text="删除 (Delete)", command=self._delete_selected_rows).pack(side=tk.LEFT, padx=5)
|
||||||
|
ttk.Button(parent, text="刷新", command=self._load_data).pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
# 待保存提示
|
||||||
|
self.status_label = ttk.Label(parent, text="")
|
||||||
|
self.status_label.pack(side=tk.LEFT, padx=20)
|
||||||
|
|
||||||
|
# 右侧按钮
|
||||||
|
right_frame = ttk.Frame(parent)
|
||||||
|
right_frame.pack(side=tk.RIGHT)
|
||||||
|
|
||||||
|
ttk.Button(right_frame, text="保存", command=self._save_changes).pack(side=tk.LEFT, padx=5)
|
||||||
|
ttk.Button(right_frame, text="关闭", command=self._close_dialog).pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
def _show_context_menu(self, event):
|
||||||
|
"""显示右键菜单"""
|
||||||
|
item = self.tree.identify_row(event.y)
|
||||||
|
if item:
|
||||||
|
self.tree.selection_set(item)
|
||||||
|
self.context_menu.post(event.x_root, event.y_root)
|
||||||
|
|
||||||
|
def _create_filter_checkboxes(self):
|
||||||
|
"""创建筛选复选框"""
|
||||||
|
for widget in self.filter_frame.winfo_children():
|
||||||
|
widget.destroy()
|
||||||
|
self.manager_checkboxes.clear()
|
||||||
|
|
||||||
|
from db.materials_to_be_deleted_dao import MaterialsTypeToBeDeletedDAO
|
||||||
|
dao = MaterialsTypeToBeDeletedDAO()
|
||||||
|
self.managers = dao.get_managers()
|
||||||
|
|
||||||
|
if not self.managers:
|
||||||
|
ttk.Label(self.filter_frame, text="正在加载数据...").pack(anchor="w")
|
||||||
|
return
|
||||||
|
|
||||||
|
items_per_column = 6
|
||||||
|
|
||||||
|
# 全选复选框
|
||||||
|
ttk.Checkbutton(
|
||||||
|
self.filter_frame,
|
||||||
|
text="全选",
|
||||||
|
variable=self.select_all_var,
|
||||||
|
command=self._on_select_all_toggle
|
||||||
|
).grid(row=0, column=0, sticky="w", padx=5, pady=2)
|
||||||
|
|
||||||
|
# 负责人复选框
|
||||||
|
for idx, manager in enumerate(self.managers):
|
||||||
|
var = tk.BooleanVar(value=True)
|
||||||
|
self.manager_checkboxes[manager] = var
|
||||||
|
|
||||||
|
row = (idx // items_per_column) + 1
|
||||||
|
col = idx % items_per_column
|
||||||
|
|
||||||
|
ttk.Checkbutton(
|
||||||
|
self.filter_frame,
|
||||||
|
text=manager,
|
||||||
|
variable=var,
|
||||||
|
command=self._on_manager_checkbox_change
|
||||||
|
).grid(row=row, column=col, sticky="w", padx=5, pady=2)
|
||||||
|
|
||||||
|
def _on_select_all_toggle(self):
|
||||||
|
"""全选复选框状态改变"""
|
||||||
|
select_all = self.select_all_var.get()
|
||||||
|
for var in self.manager_checkboxes.values():
|
||||||
|
var.set(select_all)
|
||||||
|
self._apply_filter()
|
||||||
|
|
||||||
|
def _on_manager_checkbox_change(self):
|
||||||
|
"""单个负责人复选框状态改变"""
|
||||||
|
all_selected = all(var.get() for var in self.manager_checkboxes.values())
|
||||||
|
self.select_all_var.set(all_selected)
|
||||||
|
self._apply_filter()
|
||||||
|
|
||||||
|
def _select_all_managers(self):
|
||||||
|
"""全选所有负责人"""
|
||||||
|
self.select_all_var.set(True)
|
||||||
|
for var in self.manager_checkboxes.values():
|
||||||
|
var.set(True)
|
||||||
|
self._apply_filter()
|
||||||
|
|
||||||
|
def _deselect_all_managers(self):
|
||||||
|
"""取消全选"""
|
||||||
|
self.select_all_var.set(False)
|
||||||
|
for var in self.manager_checkboxes.values():
|
||||||
|
var.set(False)
|
||||||
|
self._apply_filter()
|
||||||
|
|
||||||
|
def _get_selected_managers(self) -> List[str]:
|
||||||
|
"""获取选中的负责人列表"""
|
||||||
|
return [
|
||||||
|
manager for manager, var in self.manager_checkboxes.items()
|
||||||
|
if var.get()
|
||||||
|
]
|
||||||
|
|
||||||
|
def _load_data(self):
|
||||||
|
"""从数据库加载数据"""
|
||||||
|
try:
|
||||||
|
from db.materials_to_be_deleted_dao import MaterialsTypeToBeDeletedDAO
|
||||||
|
|
||||||
|
dao = MaterialsTypeToBeDeletedDAO()
|
||||||
|
self.original_data = dao.get_all_materials()
|
||||||
|
self.current_data = list(self.original_data)
|
||||||
|
self.row_status = {}
|
||||||
|
self.original_values = {}
|
||||||
|
|
||||||
|
# 重新创建筛选复选框
|
||||||
|
self._create_filter_checkboxes()
|
||||||
|
|
||||||
|
# 刷新显示
|
||||||
|
self._apply_filter()
|
||||||
|
self._update_status()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("错误", f"加载数据失败:{str(e)}")
|
||||||
|
|
||||||
|
def _apply_filter(self):
|
||||||
|
"""应用筛选条件"""
|
||||||
|
selected_managers = self._get_selected_managers()
|
||||||
|
|
||||||
|
if not selected_managers:
|
||||||
|
self.current_data = []
|
||||||
|
else:
|
||||||
|
# 获取未删除的数据
|
||||||
|
base_data = [
|
||||||
|
r for r in self.original_data
|
||||||
|
if self.row_status.get(self._get_record_key(r), self.ROW_STATUS_UNCHANGED) != self.ROW_STATUS_DELETED
|
||||||
|
]
|
||||||
|
|
||||||
|
# 添加新增的记录
|
||||||
|
new_records = [
|
||||||
|
r for r, status in self.row_status.items()
|
||||||
|
if status == self.ROW_STATUS_NEW
|
||||||
|
]
|
||||||
|
|
||||||
|
# 从行状态中恢复新增记录的数据
|
||||||
|
for item_id in list(self.row_status.keys()):
|
||||||
|
if self.row_status[item_id] == self.ROW_STATUS_NEW:
|
||||||
|
# 尝试从 tree 中获取数据
|
||||||
|
try:
|
||||||
|
if self.tree.exists(item_id):
|
||||||
|
values = self.tree.item(item_id, "values")
|
||||||
|
new_records.append({
|
||||||
|
'MaterialName': values[0],
|
||||||
|
'ManagerName': values[1]
|
||||||
|
})
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 合并数据并筛选
|
||||||
|
all_data = base_data + new_records
|
||||||
|
self.current_data = [
|
||||||
|
r for r in all_data
|
||||||
|
if r.get('ManagerName') in selected_managers
|
||||||
|
]
|
||||||
|
|
||||||
|
self._refresh_tree()
|
||||||
|
|
||||||
|
def _get_record_key(self, record: Dict[str, Any]) -> str:
|
||||||
|
"""获取记录的唯一键"""
|
||||||
|
return f"{record.get('MaterialName', '')}|{record.get('ManagerName', '')}"
|
||||||
|
|
||||||
|
def _refresh_tree(self):
|
||||||
|
"""刷新表格显示"""
|
||||||
|
# 保存当前正在编辑的行数据
|
||||||
|
editing_data = {}
|
||||||
|
for key, status in self.row_status.items():
|
||||||
|
if status == self.ROW_STATUS_NEW and key.startswith("temp:"):
|
||||||
|
item_id = key.split(":", 1)[1]
|
||||||
|
if self.tree.exists(item_id):
|
||||||
|
values = self.tree.item(item_id, "values")
|
||||||
|
if values[0] or values[1]: # 保存有数据的新增行
|
||||||
|
editing_data[key] = values
|
||||||
|
|
||||||
|
# 保存当前选中的行和滚动位置
|
||||||
|
selected_items = self.tree.selection()
|
||||||
|
selected_data = []
|
||||||
|
for item in selected_items:
|
||||||
|
if self.tree.exists(item):
|
||||||
|
values = self.tree.item(item, "values")
|
||||||
|
selected_data.append((values[0], values[1]))
|
||||||
|
|
||||||
|
# 清空表格
|
||||||
|
for item in self.tree.get_children():
|
||||||
|
self.tree.delete(item)
|
||||||
|
|
||||||
|
# 填充数据
|
||||||
|
for record in self.current_data:
|
||||||
|
item_id = self.tree.insert(
|
||||||
|
"",
|
||||||
|
tk.END,
|
||||||
|
values=(
|
||||||
|
record.get('MaterialName', ''),
|
||||||
|
record.get('ManagerName', '')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 恢复行状态
|
||||||
|
key = self._get_record_key(record)
|
||||||
|
if key in self.row_status:
|
||||||
|
status = self.row_status[key]
|
||||||
|
if status == self.ROW_STATUS_NEW:
|
||||||
|
self.tree.item(item_id, tags=('new',))
|
||||||
|
elif status == self.ROW_STATUS_MODIFIED:
|
||||||
|
self.tree.item(item_id, tags=('modified',))
|
||||||
|
|
||||||
|
# 恢复正在编辑的新增行
|
||||||
|
for temp_key, values in editing_data.items():
|
||||||
|
item_id = temp_key.split(":", 1)[1]
|
||||||
|
new_item_id = self.tree.insert("", tk.END, values=values)
|
||||||
|
# 更新 key 映射
|
||||||
|
if values[0] and values[1]:
|
||||||
|
new_key = self._get_record_key({'MaterialName': values[0], 'ManagerName': values[1]})
|
||||||
|
self.row_status[new_key] = self.ROW_STATUS_NEW
|
||||||
|
self.tree.item(new_item_id, tags=('new',))
|
||||||
|
else:
|
||||||
|
# 空行,保持临时 key
|
||||||
|
self.row_status[temp_key] = self.ROW_STATUS_NEW
|
||||||
|
self.row_status[new_key] = self.ROW_STATUS_NEW
|
||||||
|
|
||||||
|
# 配置标签样式
|
||||||
|
self.tree.tag_configure('new', background='#e6f7e6') # 浅绿色
|
||||||
|
self.tree.tag_configure('modified', background='#fff4e6') # 浅黄色
|
||||||
|
|
||||||
|
# 恢复选中状态
|
||||||
|
for material, manager in selected_data:
|
||||||
|
for item in self.tree.get_children():
|
||||||
|
values = self.tree.item(item, "values")
|
||||||
|
if values[0] == material and values[1] == manager:
|
||||||
|
self.tree.selection_add(item)
|
||||||
|
break
|
||||||
|
|
||||||
|
def _add_new_row(self):
|
||||||
|
"""添加新行"""
|
||||||
|
# 插入空行
|
||||||
|
item_id = self.tree.insert("", tk.END, values=("", ""))
|
||||||
|
|
||||||
|
# 使用临时 key 标记为新行(编辑后会更新为正确的 key)
|
||||||
|
temp_key = f"temp:{item_id}"
|
||||||
|
self.row_status[temp_key] = self.ROW_STATUS_NEW
|
||||||
|
|
||||||
|
# 设置标签
|
||||||
|
self.tree.item(item_id, tags=('new',))
|
||||||
|
|
||||||
|
# 选中并开始编辑第一个单元格
|
||||||
|
self.tree.selection_set(item_id)
|
||||||
|
self.tree.focus(item_id)
|
||||||
|
self.tree.start_edit(item_id, "#1")
|
||||||
|
|
||||||
|
self._update_status()
|
||||||
|
|
||||||
|
def _edit_selected_cell(self):
|
||||||
|
"""编辑选中的单元格"""
|
||||||
|
selection = self.tree.selection()
|
||||||
|
if selection:
|
||||||
|
item = selection[0]
|
||||||
|
self.tree.start_edit(item, "#1")
|
||||||
|
|
||||||
|
def _delete_selected_rows(self):
|
||||||
|
"""删除选中的行"""
|
||||||
|
selection = self.tree.selection()
|
||||||
|
if not selection:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not messagebox.askyesno("确认", f"确定要删除选中的 {len(selection)} 条记录吗?"):
|
||||||
|
return
|
||||||
|
|
||||||
|
for item in selection:
|
||||||
|
values = self.tree.item(item, "values")
|
||||||
|
material_name = values[0]
|
||||||
|
manager_name = values[1]
|
||||||
|
|
||||||
|
key = self._get_record_key({'MaterialName': material_name, 'ManagerName': manager_name})
|
||||||
|
temp_key = f"temp:{item}"
|
||||||
|
|
||||||
|
# 如果是新增的行,直接移除
|
||||||
|
if temp_key in self.row_status and self.row_status[temp_key] == self.ROW_STATUS_NEW:
|
||||||
|
del self.row_status[temp_key]
|
||||||
|
else:
|
||||||
|
# 标记为删除
|
||||||
|
self.row_status[key] = self.ROW_STATUS_DELETED
|
||||||
|
|
||||||
|
self.tree.delete(item)
|
||||||
|
|
||||||
|
self._update_status()
|
||||||
|
|
||||||
|
def _on_edit_complete(self, item_id: str, column: int, old_value: str, new_value: str):
|
||||||
|
"""编辑完成回调"""
|
||||||
|
new_value = new_value.strip()
|
||||||
|
|
||||||
|
# 获取编辑前的完整值
|
||||||
|
old_values = list(self.tree.item(item_id, "values"))
|
||||||
|
|
||||||
|
# 计算新的完整值
|
||||||
|
new_values = list(old_values)
|
||||||
|
new_values[column] = new_value
|
||||||
|
|
||||||
|
new_material = new_values[0]
|
||||||
|
new_manager = new_values[1]
|
||||||
|
|
||||||
|
# 检查是否是新增的行
|
||||||
|
temp_key = f"temp:{item_id}"
|
||||||
|
is_new_row = temp_key in self.row_status and self.row_status[temp_key] == self.ROW_STATUS_NEW
|
||||||
|
|
||||||
|
# 对于新增行,如果只输入了部分字段,允许继续
|
||||||
|
if is_new_row:
|
||||||
|
# 更新表格显示
|
||||||
|
self.tree.item(item_id, values=new_values)
|
||||||
|
# 检查是否已输入完整数据
|
||||||
|
if new_material and new_manager:
|
||||||
|
# 输入完整,更新 key
|
||||||
|
new_key = self._get_record_key({'MaterialName': new_material, 'ManagerName': new_manager})
|
||||||
|
# 检查重复
|
||||||
|
for other_item in self.tree.get_children():
|
||||||
|
if other_item == item_id:
|
||||||
|
continue
|
||||||
|
other_values = self.tree.item(other_item, "values")
|
||||||
|
if other_values[0] == new_material and other_values[1] == new_manager:
|
||||||
|
messagebox.showwarning("警告", "该记录已存在")
|
||||||
|
self.tree.item(item_id, values=("", ""))
|
||||||
|
self._update_status()
|
||||||
|
return
|
||||||
|
# 从临时 key 更新为实际 key
|
||||||
|
del self.row_status[temp_key]
|
||||||
|
self.row_status[new_key] = self.ROW_STATUS_NEW
|
||||||
|
# 更新状态提示(无论是否输入完整都更新)
|
||||||
|
self._update_status()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 对于现有行的编辑,验证输入
|
||||||
|
if not new_value:
|
||||||
|
messagebox.showwarning("警告", "物料名称和负责人不能为空")
|
||||||
|
# 恢复原值
|
||||||
|
self.tree.item(item_id, values=old_values)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 检查数据完整性
|
||||||
|
if not new_material or not new_manager:
|
||||||
|
self.tree.item(item_id, values=new_values)
|
||||||
|
return
|
||||||
|
|
||||||
|
new_key = self._get_record_key({'MaterialName': new_material, 'ManagerName': new_manager})
|
||||||
|
|
||||||
|
# 检查是否重复(除了自己)
|
||||||
|
for other_item in self.tree.get_children():
|
||||||
|
if other_item == item_id:
|
||||||
|
continue
|
||||||
|
other_values = self.tree.item(other_item, "values")
|
||||||
|
if other_values[0] == new_material and other_values[1] == new_manager:
|
||||||
|
messagebox.showwarning("警告", "该记录已存在")
|
||||||
|
# 恢复原值
|
||||||
|
self.tree.item(item_id, values=old_values)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 更新表格显示
|
||||||
|
self.tree.item(item_id, values=new_values)
|
||||||
|
|
||||||
|
# 更新行状态
|
||||||
|
# 检查是否是新增的行(查找临时 key)
|
||||||
|
temp_key = f"temp:{item_id}"
|
||||||
|
is_new_row = temp_key in self.row_status and self.row_status[temp_key] == self.ROW_STATUS_NEW
|
||||||
|
|
||||||
|
if is_new_row:
|
||||||
|
# 新增行:从临时 key 更新为实际 key
|
||||||
|
del self.row_status[temp_key]
|
||||||
|
self.row_status[new_key] = self.ROW_STATUS_NEW
|
||||||
|
else:
|
||||||
|
# 现有行的编辑,标记为修改
|
||||||
|
# 获取原始值
|
||||||
|
if new_key in self.original_values:
|
||||||
|
# 已经修改过,使用已保存的原始值
|
||||||
|
old_material, old_manager = self.original_values[new_key]
|
||||||
|
else:
|
||||||
|
# 第一次修改,从 old_values 获取原始值
|
||||||
|
old_material = old_values[0] if column != 0 else old_value
|
||||||
|
old_manager = old_values[1] if column != 1 else old_value
|
||||||
|
self.original_values[new_key] = (old_material, old_manager)
|
||||||
|
|
||||||
|
self.row_status[new_key] = self.ROW_STATUS_MODIFIED
|
||||||
|
|
||||||
|
# 设置标签
|
||||||
|
self.tree.item(item_id, tags=('modified',))
|
||||||
|
|
||||||
|
self._update_status()
|
||||||
|
|
||||||
|
def _on_edit_cancelled(self, item_id: str, column: int, old_value: str):
|
||||||
|
"""编辑取消回调"""
|
||||||
|
# 恢复原值
|
||||||
|
values = list(self.tree.item(item_id, "values"))
|
||||||
|
values[column] = old_value
|
||||||
|
self.tree.item(item_id, values=values)
|
||||||
|
|
||||||
|
def _save_changes(self):
|
||||||
|
"""保存更改到数据库"""
|
||||||
|
# 收集所有更改
|
||||||
|
to_insert = []
|
||||||
|
to_delete = []
|
||||||
|
to_update = []
|
||||||
|
|
||||||
|
# 遍历所有行状态
|
||||||
|
for key, status in self.row_status.items():
|
||||||
|
if status == self.ROW_STATUS_DELETED:
|
||||||
|
# 解析 key
|
||||||
|
parts = key.split('|')
|
||||||
|
if len(parts) == 2:
|
||||||
|
to_delete.append({'MaterialName': parts[0], 'ManagerName': parts[1]})
|
||||||
|
|
||||||
|
elif status == self.ROW_STATUS_NEW:
|
||||||
|
# 从表格中获取数据
|
||||||
|
# 处理临时 key 或正常 key
|
||||||
|
item_to_find = None
|
||||||
|
if key.startswith("temp:"):
|
||||||
|
# 临时 key,从 key 中提取 item_id
|
||||||
|
item_id = key.split(":", 1)[1]
|
||||||
|
if self.tree.exists(item_id):
|
||||||
|
item_to_find = item_id
|
||||||
|
else:
|
||||||
|
# 正常 key,在表格中查找匹配的行
|
||||||
|
for item in self.tree.get_children():
|
||||||
|
values = self.tree.item(item, "values")
|
||||||
|
item_key = self._get_record_key({'MaterialName': values[0], 'ManagerName': values[1]})
|
||||||
|
if item_key == key:
|
||||||
|
item_to_find = item
|
||||||
|
break
|
||||||
|
|
||||||
|
if item_to_find:
|
||||||
|
values = self.tree.item(item_to_find, "values")
|
||||||
|
if values[0] and values[1]: # 只保存非空行
|
||||||
|
to_insert.append({'MaterialName': values[0], 'ManagerName': values[1]})
|
||||||
|
|
||||||
|
elif status == self.ROW_STATUS_MODIFIED:
|
||||||
|
# 从表格中获取新数据,从 original_values 获取旧数据
|
||||||
|
if key in self.original_values:
|
||||||
|
old_material, old_manager = self.original_values[key]
|
||||||
|
|
||||||
|
# 从表格中找到对应的新数据
|
||||||
|
for item in self.tree.get_children():
|
||||||
|
values = self.tree.item(item, "values")
|
||||||
|
item_key = self._get_record_key({'MaterialName': values[0], 'ManagerName': values[1]})
|
||||||
|
if item_key == key:
|
||||||
|
to_update.append({
|
||||||
|
'old': {'MaterialName': old_material, 'ManagerName': old_manager},
|
||||||
|
'new': {'MaterialName': values[0], 'ManagerName': values[1]}
|
||||||
|
})
|
||||||
|
break
|
||||||
|
|
||||||
|
total_changes = len(to_insert) + len(to_delete) + len(to_update)
|
||||||
|
|
||||||
|
if total_changes == 0:
|
||||||
|
messagebox.showinfo("提示", "没有需要保存的更改")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 确认保存
|
||||||
|
msg_parts = []
|
||||||
|
if to_insert:
|
||||||
|
msg_parts.append(f"新增 {len(to_insert)} 条")
|
||||||
|
if to_delete:
|
||||||
|
msg_parts.append(f"删除 {len(to_delete)} 条")
|
||||||
|
if to_update:
|
||||||
|
msg_parts.append(f"更新 {len(to_update)} 条")
|
||||||
|
|
||||||
|
if not messagebox.askyesno(
|
||||||
|
"确认保存",
|
||||||
|
"确定要将以下更改保存到数据库吗?\n\n" + "\n".join(msg_parts)
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
from db.materials_to_be_deleted_dao import MaterialsTypeToBeDeletedDAO
|
||||||
|
|
||||||
|
dao = MaterialsTypeToBeDeletedDAO()
|
||||||
|
|
||||||
|
stats = {
|
||||||
|
'insert_success': 0,
|
||||||
|
'insert_failed': 0,
|
||||||
|
'delete_success': 0,
|
||||||
|
'delete_failed': 0,
|
||||||
|
'update_success': 0,
|
||||||
|
'update_failed': 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# 执行插入
|
||||||
|
for record in to_insert:
|
||||||
|
if dao.insert_material(record['MaterialName'], record['ManagerName']):
|
||||||
|
stats['insert_success'] += 1
|
||||||
|
else:
|
||||||
|
stats['insert_failed'] += 1
|
||||||
|
|
||||||
|
# 执行删除
|
||||||
|
for record in to_delete:
|
||||||
|
if dao.delete_material(record['MaterialName'], record['ManagerName']):
|
||||||
|
stats['delete_success'] += 1
|
||||||
|
else:
|
||||||
|
stats['delete_failed'] += 1
|
||||||
|
|
||||||
|
# 执行更新
|
||||||
|
for update in to_update:
|
||||||
|
old = update['old']
|
||||||
|
new = update['new']
|
||||||
|
if dao.delete_material(old['MaterialName'], old['ManagerName']):
|
||||||
|
if dao.insert_material(new['MaterialName'], new['ManagerName']):
|
||||||
|
stats['update_success'] += 1
|
||||||
|
else:
|
||||||
|
dao.insert_material(old['MaterialName'], old['ManagerName'])
|
||||||
|
stats['update_failed'] += 1
|
||||||
|
else:
|
||||||
|
stats['update_failed'] += 1
|
||||||
|
|
||||||
|
# 显示结果
|
||||||
|
result_parts = []
|
||||||
|
if stats['insert_success'] > 0:
|
||||||
|
result_parts.append(f"新增成功:{stats['insert_success']} 条")
|
||||||
|
if stats['insert_failed'] > 0:
|
||||||
|
result_parts.append(f"新增失败:{stats['insert_failed']} 条")
|
||||||
|
if stats['delete_success'] > 0:
|
||||||
|
result_parts.append(f"删除成功:{stats['delete_success']} 条")
|
||||||
|
if stats['delete_failed'] > 0:
|
||||||
|
result_parts.append(f"删除失败:{stats['delete_failed']} 条")
|
||||||
|
if stats['update_success'] > 0:
|
||||||
|
result_parts.append(f"更新成功:{stats['update_success']} 条")
|
||||||
|
if stats['update_failed'] > 0:
|
||||||
|
result_parts.append(f"更新失败:{stats['update_failed']} 条")
|
||||||
|
|
||||||
|
result_msg = "保存完成!\n\n" + "\n".join(result_parts)
|
||||||
|
|
||||||
|
if (stats['insert_failed'] + stats['delete_failed'] + stats['update_failed']) > 0:
|
||||||
|
messagebox.showwarning("完成(部分失败)", result_msg)
|
||||||
|
else:
|
||||||
|
messagebox.showinfo("成功", result_msg)
|
||||||
|
|
||||||
|
# 重新加载数据
|
||||||
|
self._load_data()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("错误", f"保存失败:{str(e)}")
|
||||||
|
|
||||||
|
def _update_status(self):
|
||||||
|
"""更新状态标签"""
|
||||||
|
total_changes = 0
|
||||||
|
|
||||||
|
for key, status in self.row_status.items():
|
||||||
|
if status == self.ROW_STATUS_DELETED:
|
||||||
|
total_changes += 1
|
||||||
|
elif status == self.ROW_STATUS_NEW:
|
||||||
|
# 对于新增行,检查是否有实际数据
|
||||||
|
if key.startswith("temp:"):
|
||||||
|
# 临时 key,检查表格中的数据
|
||||||
|
item_id = key.split(":", 1)[1]
|
||||||
|
if self.tree.exists(item_id):
|
||||||
|
values = self.tree.item(item_id, "values")
|
||||||
|
if values[0] and values[1]:
|
||||||
|
total_changes += 1
|
||||||
|
else:
|
||||||
|
total_changes += 1
|
||||||
|
elif status == self.ROW_STATUS_MODIFIED:
|
||||||
|
total_changes += 1
|
||||||
|
|
||||||
|
if total_changes > 0:
|
||||||
|
self.status_label.config(
|
||||||
|
text=f"有 {total_changes} 项待保存的更改",
|
||||||
|
foreground="red"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.status_label.config(text="")
|
||||||
|
|
||||||
|
def _close_dialog(self):
|
||||||
|
"""关闭对话框"""
|
||||||
|
total_changes = 0
|
||||||
|
|
||||||
|
for key, status in self.row_status.items():
|
||||||
|
if status == self.ROW_STATUS_DELETED:
|
||||||
|
total_changes += 1
|
||||||
|
elif status == self.ROW_STATUS_NEW:
|
||||||
|
# 对于新增行,检查是否有实际数据
|
||||||
|
if key.startswith("temp:"):
|
||||||
|
# 临时 key,检查表格中的数据
|
||||||
|
item_id = key.split(":", 1)[1]
|
||||||
|
if self.tree.exists(item_id):
|
||||||
|
values = self.tree.item(item_id, "values")
|
||||||
|
if values[0] and values[1]:
|
||||||
|
total_changes += 1
|
||||||
|
else:
|
||||||
|
total_changes += 1
|
||||||
|
elif status == self.ROW_STATUS_MODIFIED:
|
||||||
|
total_changes += 1
|
||||||
|
|
||||||
|
if total_changes > 0:
|
||||||
|
if not messagebox.askyesno(
|
||||||
|
"警告",
|
||||||
|
f"有 {total_changes} 项未保存的更改,确定要关闭吗?"
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.destroy()
|
||||||
@@ -18,6 +18,7 @@ from io import StringIO
|
|||||||
from contextlib import redirect_stdout
|
from contextlib import redirect_stdout
|
||||||
from gui.widgets import FileSelector, LogText
|
from gui.widgets import FileSelector, LogText
|
||||||
from gui.config_manager import ConfigManager
|
from gui.config_manager import ConfigManager
|
||||||
|
from gui.material_type_management_dialog import MaterialTypeManagementDialog
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
@@ -287,6 +288,14 @@ class MaterialValidationTab(ttk.Frame):
|
|||||||
)
|
)
|
||||||
self.export_button.pack(side=tk.LEFT, padx=5)
|
self.export_button.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
|
# 类型管理按钮
|
||||||
|
self.type_management_button = ttk.Button(
|
||||||
|
button_frame,
|
||||||
|
text="类型管理",
|
||||||
|
command=self.open_type_management
|
||||||
|
)
|
||||||
|
self.type_management_button.pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
def _create_result_table(self, parent):
|
def _create_result_table(self, parent):
|
||||||
"""创建结果表格"""
|
"""创建结果表格"""
|
||||||
# 新列定义:选择、材料名称、材料代码、规格、型号、负责人
|
# 新列定义:选择、材料名称、材料代码、规格、型号、负责人
|
||||||
@@ -802,3 +811,7 @@ class MaterialValidationTab(ttk.Frame):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("错误", f"导出失败:{str(e)}")
|
messagebox.showerror("错误", f"导出失败:{str(e)}")
|
||||||
self.log_text.error(f"导出失败:{str(e)}")
|
self.log_text.error(f"导出失败:{str(e)}")
|
||||||
|
|
||||||
|
def open_type_management(self):
|
||||||
|
"""打开类型管理窗口"""
|
||||||
|
dialog = MaterialTypeManagementDialog(self, title="物料类型管理")
|
||||||
|
|||||||
Reference in New Issue
Block a user