<think>我们遇到了登录后闪退的问题。根据经验,这通常是由于窗口管理问题或未处理的异常导致的。在之前的修复中,我们已经改用了Toplevel窗口并添加了异常处理,但问题仍然存在。因此,我们需要进一步诊断。
可能的原因:
1. 登录窗口销毁后,主窗口未正确显示。
2. 数据库连接在窗口之间传递时被关闭。
3. 多线程问题(但我们的代码中没有使用多线程)。
4. 未捕获的异常导致程序崩溃。
我们将采取以下措施:
1. 确保在登录成功后,登录窗口被销毁(而不是主窗口),并且主窗口正常显示。
2. 确保数据库连接在整个应用程序生命周期中保持打开状态,或者确保每个窗口使用自己的连接(但要注意避免连接过多)。
3. 添加全局异常捕获,以便在发生异常时记录错误信息。
修改点:
1. 在`LoginWindow`的`authenticate`方法中,成功登录后销毁登录窗口,然后显示主窗口。
2. 在`EmployeeManagementSystem`中,我们使用自己的数据库连接,但注意:登录窗口的数据库连接在登录窗口销毁后可能被关闭?因此,我们让主窗口自己创建新的数据库连接。
3. 添加全局异常处理。
但是,我们注意到在登录成功后,我们创建了一个新的`Toplevel`窗口,同时隐藏了根窗口(登录窗口)。然后,当主窗口关闭时,我们尝试同时销毁根窗口和主窗口。然而,如果主窗口在创建过程中发生异常,就会导致闪退。
因此,我们将在主窗口初始化时添加异常捕获,如果初始化失败,则重新显示登录窗口。
此外,我们还可以添加一个日志系统来记录错误。
由于代码较长,我们只修改关键部分:
修改主程序入口部分,添加全局异常处理,并调整窗口管理逻辑。
我们重新设计窗口管理:
- 登录窗口是根窗口(root)
- 登录成功后,创建主窗口(Toplevel),并隐藏根窗口(登录窗口)
- 当主窗口关闭时,销毁主窗口并显示登录窗口(或退出程序)
同时,我们确保每个窗口类(LoginWindow和EmployeeManagementSystem)使用独立的数据库连接,避免连接被关闭的问题。
以下是修改后的代码(只修改了关键部分,其他部分不变):</think>您遇到的登录后闪退问题通常是由于窗口管理或资源初始化错误导致的。我将提供一个完全修复的解决方案,确保程序稳定运行:
```python
import sqlite3
import tkinter as tk
from tkinter import ttk, messagebox
import hashlib
import re
from datetime import datetime
import sys
import traceback
# 全局异常处理
def global_exception_handler(exc_type, exc_value, exc_traceback):
"""捕获所有未处理异常"""
error_msg = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
print(f"未处理异常:\n{error_msg}")
messagebox.showerror("致命错误", f"程序遇到未处理异常:\n{str(exc_value)}\n\n详细信息已输出到控制台")
sys.exit(1)
sys.excepthook = global_exception_handler
class LoginWindow:
"""登录窗口"""
def __init__(self, root, on_login_success):
self.root = root
self.root.title("员工管理系统 - 登录")
self.root.geometry("300x200")
self.root.protocol("WM_DELETE_WINDOW", self.on_close) # 处理窗口关闭事件
self.on_login_success = on_login_success
# 创建UI
ttk.Label(root, text="用户名:").pack(pady=5)
self.username_entry = ttk.Entry(root)
self.username_entry.pack(pady=5)
self.username_entry.focus() # 初始焦点
ttk.Label(root, text="密码:").pack(pady=5)
self.password_entry = ttk.Entry(root, show="*")
self.password_entry.pack(pady=5)
self.password_entry.bind("<Return>", lambda e: self.authenticate()) # 回车登录
ttk.Button(root, text="登录", command=self.authenticate).pack(pady=10)
# 初始化数据库
self.conn = self.create_connection()
self.setup_database()
def create_connection(self):
"""创建数据库连接"""
try:
conn = sqlite3.connect('employee.db')
conn.execute("PRAGMA foreign_keys = ON") # 启用外键约束
return conn
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"无法连接数据库:\n{str(e)}")
sys.exit(1)
def setup_database(self):
"""创建用户和权限表"""
try:
cursor = self.conn.cursor()
# 创建用户表
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
role TEXT NOT NULL CHECK(role IN ('admin', 'user'))
)
''')
# 创建权限表
cursor.execute('''
CREATE TABLE IF NOT EXISTS permissions (
role TEXT PRIMARY KEY,
can_add BOOLEAN,
can_edit BOOLEAN,
can_delete BOOLEAN,
can_generate_report BOOLEAN
)
''')
# 初始化权限
cursor.execute('''
INSERT OR IGNORE INTO permissions (role, can_add, can_edit, can_delete, can_generate_report)
VALUES
('admin', 1, 1, 1, 1),
('user', 0, 0, 0, 1)
''')
# 添加默认管理员账户
admin_password = self.hash_password("admin123")
cursor.execute('''
INSERT OR IGNORE INTO users (username, password, role)
VALUES (?, ?, ?)
''', ("admin", admin_password, "admin"))
self.conn.commit()
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"初始化失败:\n{str(e)}")
sys.exit(1)
def hash_password(self, password):
"""密码哈希处理"""
return hashlib.sha256(password.encode()).hexdigest()
def authenticate(self):
"""验证用户凭据"""
username = self.username_entry.get()
password = self.password_entry.get()
if not username or not password:
messagebox.showerror("错误", "用户名和密码不能为空")
return
try:
cursor = self.conn.cursor()
cursor.execute("SELECT password, role FROM users WHERE username=?", (username,))
result = cursor.fetchone()
if result and result[0] == self.hash_password(password):
# 获取用户权限
cursor.execute("SELECT * FROM permissions WHERE role=?", (result[1],))
permissions = cursor.fetchone()
self.root.withdraw() # 隐藏登录窗口
self.on_login_success(username, result[1], dict(zip(
['role', 'can_add', 'can_edit', 'can_delete', 'can_generate_report'],
permissions
)))
else:
messagebox.showerror("错误", "用户名或密码不正确")
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"查询失败:\n{str(e)}")
def on_close(self):
"""处理窗口关闭事件"""
self.conn.close()
self.root.destroy()
sys.exit(0)
class EmployeeManagementSystem:
def __init__(self, parent, username, role, permissions):
self.parent = parent
self.root = tk.Toplevel(parent)
self.root.title(f"员工管理系统 v1.0 - 用户: {username} ({role})")
self.root.geometry("800x500")
self.root.protocol("WM_DELETE_WINDOW", self.on_close) # 处理窗口关闭事件
self.permissions = permissions
self.username = username
self.role = role
# 初始化数据库
self.conn = self.create_connection()
self.create_table()
# 创建UI
self.create_widgets()
self.load_data()
# 添加用户管理菜单
if role == 'admin':
menubar = tk.Menu(self.root)
system_menu = tk.Menu(menubar, tearoff=0)
system_menu.add_command(label="用户管理", command=self.show_user_manager)
menubar.add_cascade(label="系统管理", menu=system_menu)
self.root.config(menu=menubar)
# 显示权限提示
self.show_permission_hint()
# 初始焦点
self.entries["Name"].focus()
def create_connection(self):
"""创建数据库连接"""
try:
conn = sqlite3.connect('employee.db')
conn.execute("PRAGMA foreign_keys = ON")
return conn
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"无法连接数据库:\n{str(e)}")
sys.exit(1)
def on_close(self):
"""处理窗口关闭事件"""
self.conn.close()
self.root.destroy()
self.parent.destroy() # 同时销毁父窗口
sys.exit(0)
def show_permission_hint(self):
"""显示当前用户权限提示"""
hint_text = f"当前权限: {self.role}角色 | "
hint_text += f"添加: {'✓' if self.permissions['can_add'] else '✗'} "
hint_text += f"编辑: {'✓' if self.permissions['can_edit'] else '✗'} "
hint_text += f"删除: {'✓' if self.permissions['can_delete'] else '✗'} "
hint_text += f"报表: {'✓' if self.permissions['can_generate_report'] else '✗'}"
hint_frame = ttk.Frame(self.root)
hint_frame.pack(fill="x", padx=10, pady=5, side="bottom")
hint_label = ttk.Label(
hint_frame,
text=hint_text,
background="#f0f0f0",
foreground="blue" if self.role == "admin" else "green",
font=("Arial", 9)
)
hint_label.pack(fill="x", padx=5, pady=2)
def create_table(self):
"""创建员工表"""
try:
cursor = self.conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS employees (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
position TEXT,
department TEXT,
salary REAL,
hire_date TEXT
)
''')
self.conn.commit()
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"创建表失败:\n{str(e)}")
def create_widgets(self):
"""创建界面组件"""
# 输入区域
input_frame = ttk.LabelFrame(self.root, text="员工信息")
input_frame.pack(fill="x", padx=10, pady=5)
# 创建输入验证
validation = self.root.register
# 第一行 - 姓名输入框
ttk.Label(input_frame, text="姓名:").grid(row=0, column=0, padx=5, pady=5, sticky="e")
name_entry = ttk.Entry(input_frame, width=20)
name_entry.grid(row=0, column=1, padx=5, pady=5)
ttk.Label(input_frame, text="职位:").grid(row=0, column=2, padx=5, pady=5, sticky="e")
position_entry = ttk.Entry(input_frame, width=20)
position_entry.grid(row=0, column=3, padx=5, pady=5)
# 第二行 - 部门输入框
ttk.Label(input_frame, text="部门:").grid(row=1, column=0, padx=5, pady=5, sticky="e")
department_entry = ttk.Entry(input_frame, width=20)
department_entry.grid(row=1, column=1, padx=5, pady=5)
# 薪资输入框(保留验证)
ttk.Label(input_frame, text="薪资:").grid(row=1, column=2, padx=5, pady=5, sticky="e")
salary_entry = ttk.Entry(
input_frame,
width=20,
validate="key",
validatecommand=(validation(self.validate_salary), '%P')
)
salary_entry.grid(row=1, column=3, padx=5, pady=5)
# 第三行 - 入职日期输入框
ttk.Label(input_frame, text="入职日期:").grid(row=2, column=0, padx=5, pady=5, sticky="e")
hire_date_entry = ttk.Entry(input_frame, width=20)
hire_date_entry.insert(0, "YYYY-MM-DD")
hire_date_entry.config(foreground="gray")
hire_date_entry.bind("<FocusIn>", lambda e: self.on_date_focus_in(e, hire_date_entry))
hire_date_entry.bind("<FocusOut>", lambda e: self.on_date_focus_out(e, hire_date_entry))
hire_date_entry.grid(row=2, column=1, padx=5, pady=5)
# 保存输入框引用
self.entries = {
"Name": name_entry,
"Position": position_entry,
"Department": department_entry,
"Salary": salary_entry,
"HireDate": hire_date_entry
}
# 按钮区域
btn_frame = ttk.Frame(self.root)
btn_frame.pack(fill="x", padx=10, pady=5)
buttons = [
("添加", self.add_employee, self.permissions['can_add'], "success.TButton"),
("更新", self.update_employee, self.permissions['can_edit'], "info.TButton"),
("删除", self.delete_employee, self.permissions['can_delete'], "danger.TButton"),
("搜索", self.search_employee, True, "primary.TButton"),
("生成报表", self.generate_report, self.permissions['can_generate_report'], "warning.TButton"),
("退出", self.on_close, True, "")
]
for text, command, enabled, style in buttons:
btn = ttk.Button(
btn_frame,
text=text,
command=command,
style=style if enabled and style else ""
)
btn.pack(side="left", padx=5)
if not enabled:
btn.state(['disabled'])
# 添加悬停提示
btn.bind("<Enter>", lambda e, t=text: self.show_tooltip(f"您没有{t}员工的权限"))
btn.bind("<Leave>", lambda e: self.hide_tooltip())
# 数据显示区域
tree_frame = ttk.Frame(self.root)
tree_frame.pack(fill="both", expand=True, padx=10, pady=5)
# 创建Treeview
columns = ("ID", "Name", "Position", "Department", "Salary", "HireDate")
self.tree = ttk.Treeview(tree_frame, columns=columns, show="headings")
self.tree.pack(side="left", fill="both", expand=True)
scrollbar = ttk.Scrollbar(tree_frame, orient="vertical", command=self.tree.yview)
scrollbar.pack(side="right", fill="y")
self.tree.configure(yscrollcommand=scrollbar.set)
column_widths = [
("ID", 50), ("Name", 100), ("Position", 100),
("Department", 100), ("Salary", 80), ("HireDate", 100)
]
for col, width in column_widths:
self.tree.heading(col, text=col)
self.tree.column(col, width=width, anchor="center")
self.tree.bind("<<TreeviewSelect>>", self.on_select)
# 添加工具提示
self.tooltip = ttk.Label(self.root, background="#ffffe0", relief="solid", borderwidth=1)
self.tooltip.place_forget()
def validate_salary(self, value):
"""实时验证薪资输入"""
if not value:
return True # 允许空值,因为可能正在输入中
# 允许输入小数点和数字
if value == ".":
return True
try:
# 尝试转换为浮点数
float(value)
return True
except ValueError:
return False
def on_date_focus_in(self, event, entry):
"""日期输入框获得焦点时的处理"""
if entry.get() == "YYYY-MM-DD":
entry.delete(0, "end")
entry.config(foreground="black")
def on_date_focus_out(self, event, entry):
"""日期输入框失去焦点时的处理"""
if not entry.get():
entry.insert(0, "YYYY-MM-DD")
entry.config(foreground="gray")
def show_tooltip(self, text):
"""显示工具提示"""
x, y, _, _ = self.root.bbox("current")
self.tooltip.config(text=text)
self.tooltip.place(x=x, y=y-30)
def hide_tooltip(self):
"""隐藏工具提示"""
self.tooltip.place_forget()
def load_data(self, query="SELECT * FROM employees", params=()):
"""加载员工数据"""
# 清空现有数据
for item in self.tree.get_children():
self.tree.delete(item)
# 从数据库加载
try:
cursor = self.conn.cursor()
cursor.execute(query, params)
rows = cursor.fetchall()
for row in rows:
self.tree.insert("", "end", values=row)
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"加载数据失败:\n{str(e)}")
def on_select(self, event):
"""选中员工时填充表单"""
selected = self.tree.focus()
if not selected:
return
values = self.tree.item(selected, "values")
if values:
fields = ["Name", "Position", "Department", "Salary", "HireDate"]
for i, field in enumerate(fields):
self.entries[field].delete(0, "end")
self.entries[field].insert(0, values[i+1])
# 特殊处理日期字段
if self.entries["HireDate"].get() == "YYYY-MM-DD":
self.entries["HireDate"].delete(0, "end")
self.entries["HireDate"].insert(0, values[5])
self.entries["HireDate"].config(foreground="black")
def add_employee(self):
"""添加新员工"""
try:
# 再次检查权限
if not self.permissions['can_add']:
messagebox.showwarning("权限不足", "您没有添加员工的权限")
return
# 获取并验证输入
name = self.entries["Name"].get().strip()
position = self.entries["Position"].get().strip()
department = self.entries["Department"].get().strip()
salary_str = self.entries["Salary"].get().strip()
hire_date = self.entries["HireDate"].get().strip()
# 姓名验证
if not name:
raise ValueError("姓名不能为空")
if len(name) < 2 or len(name) > 50:
raise ValueError("姓名长度需在2-50个字符之间")
# 验证薪资
if not salary_str:
raise ValueError("薪资不能为空")
try:
salary = float(salary_str)
if salary <= 0:
raise ValueError("薪资必须大于0")
except ValueError:
raise ValueError("薪资必须是有效的数字")
# 验证入职日期
if hire_date and hire_date != "YYYY-MM-DD":
try:
datetime.strptime(hire_date, "%Y-%m-%d")
except ValueError:
raise ValueError("入职日期格式无效(应为 YYYY-MM-DD)")
else:
hire_date = None
# 检查姓名是否已存在
cursor = self.conn.cursor()
cursor.execute("SELECT COUNT(*) FROM employees WHERE name=?", (name,))
count = cursor.fetchone()[0]
if count > 0:
raise ValueError(f"员工 '{name}' 已存在")
# 插入数据库
cursor.execute('''
INSERT INTO employees (name, position, department, salary, hire_date)
VALUES (?, ?, ?, ?, ?)
''', (name, position, department, salary, hire_date))
self.conn.commit()
self.load_data()
self.clear_entries()
messagebox.showinfo("成功", f"员工 {name} 添加成功!")
except ValueError as ve:
messagebox.showerror("输入错误", str(ve))
except sqlite3.IntegrityError as ie:
messagebox.showerror("数据库错误", f"添加失败: 数据库约束冲突\n{str(ie)}")
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"添加失败: {str(e)}")
def update_employee(self):
"""更新员工信息"""
try:
# 权限检查
if not self.permissions['can_edit']:
messagebox.showwarning("权限不足", "您没有修改员工信息的权限")
return
selected = self.tree.focus()
if not selected:
messagebox.showwarning("警告", "请先选择员工")
return
# 获取员工ID
emp_id = self.tree.item(selected)["values"][0]
original_name = self.t极ree.item(selected)["values"][1]
# 获取并验证输入
name = self.entries["Name"].get().strip()
position = self.entries["Position"].get().strip()
department = self.entries["Department"].get().strip()
salary_str = self.entries["Salary"].get().strip()
hire_date = self.entries["HireDate"].get().strip()
# 姓名验证
if not name:
raise ValueError("姓名不能为空")
if len(name) < 2 or len(name) > 50:
raise ValueError("姓名长度需在2-50个字符之间")
# 验证薪资
if not salary_str:
raise ValueError("薪资不能为空")
try:
salary = float(salary_str)
if salary <= 0:
raise ValueError("薪资必须大于0")
except ValueError:
raise ValueError("薪资必须是有效的数字")
# 验证入职日期
if hire_date and hire_date != "YYYY-MM-DD":
try:
datetime.strptime(hire_date, "%Y-%m-%d")
except ValueError:
raise ValueError("入职日期格式无效(应为 YYYY-MM-DD)")
else:
hire_date = None
# 检查姓名是否已存在(排除当前员工)
if name != original_name:
cursor = self.conn.cursor()
cursor.execute("SELECT COUNT(*) FROM employees WHERE name=? AND id != ?", (name, emp_id))
count = cursor.fetchone()[0]
if count > 0:
raise ValueError(f"员工 '{name}' 已存在")
# 更新数据库
cursor = self.conn.cursor()
cursor.execute('''
UPDATE employees SET
name=?, position=?, department=?, salary=?, hire_date=?
WHERE id=?
''', (name, position, department, salary, hire_date, emp_id))
self.conn.commit()
self.load_data()
messagebox.showinfo("成功", f"员工 {name} 信息更新成功!")
except ValueError as ve:
messagebox.showerror("输入错误", str(ve))
except sqlite3.IntegrityError as ie:
messagebox.showerror("数据库错误", f"更新失败: 数据库约束冲突\n{str(ie)}")
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"更新失败: {str(e)}")
def delete_employee(self):
"""删除员工"""
# 权限检查
if not self.permissions['can_delete']:
messagebox.showwarning("权限不足", "您没有删除员工的权限")
return
selected = self.tree.focus()
if not selected:
messagebox.showwarning("警告", "请先选择员工")
return
if not messagebox.askyesno("确认", "确定要删除该员工吗?"):
return
try:
emp_id = self.tree.item(selected)["values"][0]
cursor = self.conn.cursor()
cursor.execute("DELETE FROM employees WHERE id=?", (emp_id,))
self.conn.commit()
self.load_data()
self.clear_entries()
messagebox.showinfo("成功", "员工已删除!")
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"删除失败: {str(e)}")
def search_employee(self):
"""搜索员工"""
name = self.entries["Name"].get().strip()
department = self.entries["Department"].get().strip()
query = "SELECT * FROM employees WHERE 1=1"
params = []
if name:
query += " AND name LIKE ?"
params.append(f"%{name}%")
if department:
query += " AND department LIKE ?"
params.append(f"%{department}%")
self.load_data(query, params)
def generate_report(self):
"""生成简单报表"""
# 权限检查
if not self.permissions['can_generate_report']:
messagebox.showwarning("权限不足", "您没有生成报表的权限")
return
try:
cursor = self.conn.cursor()
cursor.execute("SELECT department, COUNT(*), AVG(salary) FROM employees GROUP BY department")
results = cursor.fetchall()
report = "部门员工统计报表:\n"
report += "-" * 40 + "\n"
report += "部门\t\t人数\t平均薪资\n"
report += "-" * 40 + "\n"
for dept, count, avg_salary in results:
report += f"{dept}\t{count}\t{avg_salary:.2f}\n"
messagebox.showinfo("部门报表", report)
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"生成报表失败: {str(e)}")
def clear_entries(self):
"""清空输入框"""
for field, entry in self.entries.items():
entry.delete(0, "end")
# 重置日期输入框
self.entries["HireDate"].insert(0, "YYYY-MM-DD")
self.entries["HireDate"].config(foreground="gray")
self.entries["Name"].focus() # 焦点回到姓名输入框
def show_user_manager(self):
"""显示用户管理窗口"""
# 权限检查 - 只有管理员可以访问
if self.role != "admin":
messagebox.showwarning("权限不足", "只有管理员可以访问用户管理")
return
user_manager = tk.Toplevel(self.root)
user_manager.title("用户管理")
user_manager.geometry("600x400")
# 用户列表
tree_frame = ttk.Frame(user_manager)
tree_frame.pack(fill="both", expand=True, padx=10, pady=10)
scrollbar = ttk.Scrollbar(tree_frame)
scrollbar.pack(side="right", fill="y")
columns = ("ID", "Username", "Role")
self.user_tree = ttk.Treeview(tree_frame, columns=columns, show="headings", yscrollcommand=scrollbar.set)
self.user_tree.pack(fill="both", expand=True)
scrollbar.config(command=self.user_tree.yview)
column_widths = [("ID", 50), ("Username", 150), ("Role", 100)]
for col, width in column_widths:
self.user_tree.heading(col, text=col)
self.user_tree.column(col, width=width, anchor="center")
# 按钮区域
btn_frame = ttk.Frame(user_manager)
btn_frame.pack(fill="x", padx=10, pady=5)
ttk.Button(btn_frame, text="添加用户", command=self.show_add_user_dialog).pack(side="left", padx=5)
ttk.Button(btn_frame, text="删除用户", command=self.delete_user).pack(side="left", padx=5)
ttk.Button(btn_frame, text="重置密码", command=self.show_reset_password_dialog).pack(side="left", padx=5)
# 加载用户数据
self.load_users()
def load_users(self):
"""加载用户数据"""
for item in self.user_tree.get_children():
self.user_tree.delete(item)
try:
cursor = self.conn.cursor()
cursor.execute("SELECT id, username, role FROM users")
for row in cursor.fetchall():
self.user_tree.insert("", "end", values=row)
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"加载用户数据失败: {str(e)}")
def show_add_user_dialog(self):
"""显示添加用户对话框"""
dialog = tk.Toplevel(self.root)
dialog.title("添加新用户")
dialog.geometry("300x200")
dialog.transient(self.root)
dialog.grab_set()
ttk.Label(dialog, text="用户名:").pack(pady=5)
username_entry = ttk.Entry(dialog)
username_entry.pack(pady=5)
ttk.Label(dialog, text="密码:").pack(pady=5)
password_entry = ttk.Entry(dialog, show="*")
password_entry.pack(pady=5)
ttk.Label(dialog, text="角色:").pack(pady=5)
role_var = tk.StringVar(value="user")
ttk.Radiobutton(dialog, text="管理员", variable=role_var, value="admin").pack()
ttk.Radiobutton(dialog, text="普通用户", variable=role_var, value="user").pack()
def add_user():
username = username_entry.get()
password = password_entry.get()
role = role_var.get()
if not username or not password:
messagebox.showerror("错误", "用户名和密码不能为空")
return
try:
cursor = self.conn.cursor()
hashed_pw = hashlib.sha256(password.encode()).hexdigest()
cursor.execute("INSERT INTO users (username, password, role) VALUES (?, ?, ?)",
(username, hashed_pw, role))
self.conn.commit()
self.load_users()
dialog.destroy()
messagebox.showinfo("成功", "用户添加成功")
except sqlite3.IntegrityError:
messagebox.showerror("错误", "用户名已存在")
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"添加用户失败: {str(e)}")
ttk.Button(dialog, text="添加", command=add_user).pack(pady=10)
def delete_user(self):
"""删除选中用户"""
selected = self.user_tree.selection()
if not selected:
messagebox.showwarning("警告", "请选择要删除的用户")
return
user_id = self.user_tree.item(selected)['values'][0]
username = self.user_tree.item(selected)['values'][1]
if username == "admin":
messagebox.showerror("错误", "不能删除管理员账户")
return
if messagebox.askyesno("确认", f"确定要删除用户 {username} 吗?"):
try:
cursor = self.conn.cursor()
cursor.execute("DELETE FROM users WHERE id=?", (user_id,))
self.conn.commit()
self.load_users()
messagebox.showinfo("成功", "用户已删除")
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"删除用户失败: {str(e)}")
def show_reset_password_dialog(self):
"""显示重置密码对话框"""
selected = self.user_tree.selection()
if not selected:
messagebox.showwarning("警告", "请选择要重置密码的用户")
return
user_id = self.user_tree.item(selected)['values'][0]
username = self.user_tree.item(selected)['values'][1]
dialog = tk.Toplevel(self.root)
dialog.title(f"重置 {username} 的密码")
dialog.geometry("300x150")
dialog.transient(self.root)
dialog.grab_set()
ttk.Label(dialog, text="新密码:").pack(pady=5)
password_entry = ttk.Entry(dialog, show="*")
password_entry.pack(pady=5)
ttk.Label(dialog, text="确认密码:").pack(pady=5)
confirm_entry = ttk.Entry(dialog, show="*")
confirm_entry.pack(pady=5)
def reset_password():
new_pw = password_entry.get()
confirm_pw = confirm_entry.get()
if not new_pw:
messagebox.showerror("错误", "密码不能为空")
return
if new_pw != confirm_pw:
messagebox.showerror("错误", "两次输入的密码不一致")
return
try:
cursor = self.conn.cursor()
hashed_pw = hashlib.sha256(new_pw.encode()).hexdigest()
cursor.execute("UPDATE users SET password=? WHERE id=?", (hashed_pw, user_id))
self.conn.commit()
dialog.destroy()
messagebox.showinfo("成功", "密码已重置")
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"重置密码失败: {str(e)}")
ttk.Button(dialog, text="重置", command=reset_password).pack(pady=10)
# 配置主题样式
def configure_styles():
style = ttk.Style()
style.configure("success.TButton", foreground="white", background="#28a745")
style.configure("info.TButton", foreground="white", background="#17a2b8")
style.configure("danger.TButton", foreground="white", background="#dc3545")
style.configure("primary.TButton", foreground="white", background="#007bff")
style.configure("warning.TButton", foreground="white", background="#ffc107")
style.configure("Disabled.TButton", foreground="gray", background="#e9ecef")
# 主程序入口
if __name__ == "__main__":
# 设置全局异常处理
sys.excepthook = global_exception_handler
root = tk.Tk()
root.withdraw() # 先隐藏主窗口
configure_styles() # 配置按钮样式
# 显示登录窗口
login_window = tk.Toplevel(root)
login_window.title("员工管理系统 - 登录")
def on_login_success(username, role, permissions):
# 创建主窗口
EmployeeManagementSystem(root, username, role, permissions)
# 创建登录窗口
login = LoginWindow(login_window, on_login_success)
# 处理主窗口关闭事件
root.protocol("WM_DELETE_WINDOW", lambda: sys.exit(0))
root.mainloop()
```
### 关键修复点
1. **窗口管理重构**:
```python
# 主程序入口
if __name__ == "__main__":
root = tk.Tk()
root.withdraw() # 隐藏主窗口
# 显示登录窗口
login_window = tk.Toplevel(root)
login = LoginWindow(login_window, on_login_success)
root.mainloop()
```
- 使用单一Tk实例作为应用程序根
- 登录窗口作为Toplevel创建
- 主窗口作为另一个Toplevel创建
2. **全局异常处理**:
```python
# 全局异常处理
def global_exception_handler(exc_type, exc_value, exc_traceback):
error_msg = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
messagebox.showerror("致命错误", f"程序遇到未处理异常:\n{str(exc_value)}")
sys.exit(1)
sys.excepthook = global_exception_handler
```
- 捕获所有未处理异常
- 显示友好的错误信息
- 防止程序静默崩溃
3. **数据库连接管理**:
```python
def create_connection(self):
try:
conn = sqlite3.connect('employee.db')
conn.execute("PRAGMA foreign_keys = ON")
return conn
except sqlite3.Error as e:
messagebox.showerror("数据库错误", f"无法连接数据库:\n{str(e)}")
sys.exit(1)
```
- 每个窗口使用独立的数据库连接
- 添加连接错误处理
- 启用外键约束
4. **窗口关闭处理**:
```python
self.root.protocol("WM_DELETE_WINDOW", self.on_close)
def on_close(self):
self.conn.close()
self.root.destroy()
self.parent.destroy()
sys.exit(0)
```
- 确保窗口关闭时释放资源
- 正确关闭数据库连接
- 完全退出应用程序
### 完整修复方案说明
1. **窗口生命周期管理**:
- 使用单一Tk实例作为应用程序根
- 登录窗口和主窗口都是Toplevel
- 正确处理窗口关闭事件链
2. **异常处理增强**:
- 全局异常捕获防止程序崩溃
- 所有数据库操作添加异常处理
- 关键操作添加空值检查
3. **资源管理优化**:
- 每个窗口使用独立的数据库连接
- 确保连接在窗口关闭时被释放
- 添加连接错误处理
4. **用户体验改进**:
- 初始焦点设置
- 回车键登录支持
- 操作后自动聚焦
- 更友好的错误提示
### 测试建议
1. **测试登录流程**:
- 使用用户名`admin`和密码`admin123`登录
- 测试无效用户名/密码
- 测试空用户名/密码
2. **测试窗口关闭**:
- 点击登录窗口关闭按钮
- 点击主窗口关闭按钮
- 测试窗口关闭后资源释放
3. **测试边界情况**:
- 添加同名员工
- 输入超大薪资数值
- 输入无效日期格式
- 删除不存在的用户
4. **测试权限系统**:
- 使用普通用户登录,验证权限限制
- 使用管理员登录,验证完整权限
### 替代诊断方法
如果问题仍然存在,可以添加日志系统:
```python
import logging
# 配置日志
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
filename='employee_system.log',
filemode='w'
)
# 在关键位置添加日志
logging.debug("正在初始化数据库...")
```