Compare commits

..

2 Commits

Author SHA1 Message Date
Misaka_Company
192443c3b9 feat: implement inline editing with proper status tracking
- Add EditableTreeview class for inline cell editing
- Handle invisible cells by scrolling to view before editing
- For new rows, allow partial input without validation errors
- Only validate completeness when saving, not during editing
- Add proper exception handling in edit callbacks
- Track original values for modified records
- Use temp keys for new rows that get updated when data is complete
- Fix status update to show after new row data is entered
- Clear edit state before destroying Entry to prevent duplicate callbacks

This enables users to:
- Double-click or press F2 to edit cells directly
- Press Insert to add new rows
- Press Delete to remove selected rows
- See visual indicators (green/yellow) for new/modified rows
- Get accurate status updates for pending changes

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-09 15:35:20 +08:00
Misaka_Company
2fb44a2a9b feat: add material type management dialog with checkbox filter
- Add MaterialTypeManagementDialog for managing MaterialsTypeToBeDeleted table
- Implement checkbox-based filtering by manager name with dynamic manager list
- Support CRUD operations: create, read, update, delete with local caching
- Add "类型管理" button in material validation tab to open the dialog
- Include select all/deselect all functionality for manager filters
- Track pending changes and show unsaved changes warning

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-09 13:17:25 +08:00
2 changed files with 974 additions and 0 deletions

View 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()

View File

@@ -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="物料类型管理")