import
tkinter as tk
from
tkinter
import
ttk, messagebox, scrolledtext
from
datetime
import
datetime
import
hashlib
import
os
import
traceback
import
sqlite3
from
sqlite3
import
Error
DB_CONFIG
=
{
'database'
:
'sysobjects.db'
,
}
DEPARTMENTS
=
[
"技术部"
,
"市场部"
,
"财务部"
,
"人力资源部"
,
"产品部"
,
"销售部"
,
"客服部"
]
PERMISSIONS_LIST
=
[
"申请"
,
"审核"
,
"撤销"
,
"作废"
,
"管理"
,
"编辑"
]
class
PasswordUtils:
@staticmethod
def
hash_password(password:
str
, salt:
str
=
None
)
-
>
tuple
:
if
not
salt:
salt
=
os.urandom(
16
).
hex
()
else
:
if
len
(salt) !
=
32
:
raise
ValueError(
"盐值必须为32位十六进制字符串"
)
salt_bytes
=
bytes.fromhex(salt)
dk
=
hashlib.pbkdf2_hmac(
'sha256'
, password.encode(), salt_bytes,
100000
)
return
dk.
hex
(), salt
class
DatabaseManager:
def
__init__(
self
):
self
.conn_str
=
DB_CONFIG[
'database'
]
self
.conn
=
None
self
.connect()
def
connect(
self
):
try
:
self
.conn
=
sqlite3.connect(
self
.conn_str)
return
True
except
Error as e:
messagebox.showerror(
"连接失败"
, f
"数据库连接错误: {str(e)}"
)
return
False
def
init_db(
self
):
try
:
cursor
=
self
.conn.cursor()
cursor.execute(
)
cursor.execute(
)
self
.conn.commit()
return
True
except
Error as e:
self
.conn.rollback()
messagebox.showerror(
"初始化失败"
, f
"数据库初始化错误: {str(e)}"
)
print
(e)
return
False
def
execute_query(
self
, query:
str
, params
=
None
):
try
:
cursor
=
self
.conn.cursor()
print
(f
"[DEBUG] 执行查询: {query}"
)
print
(f
"[DEBUG] 参数: {params}"
)
if
params:
cursor.execute(query, params)
else
:
cursor.execute(query)
if
cursor.description:
result
=
cursor.fetchall()
print
(f
"[DEBUG] 查询结果: {result}"
)
else
:
self
.conn.commit()
result
=
cursor.rowcount
return
result
except
Error as e:
print
(f
"[ERROR] 数据库错误: {str(e)}"
)
self
.conn.rollback()
messagebox.showerror(
"查询错误"
, f
"执行SQL失败: {str(e)}"
)
return
None
def
authenticate(
self
, username:
str
, password:
str
)
-
>
tuple
:
result
=
self
.execute_query(
"SELECT password_hash, salt, name, department, permissions FROM users WHERE username = ?"
,
(username,)
)
if
result:
stored_hash
=
result[
0
][
0
]
salt
=
result[
0
][
1
]
print
(f
"[认证] 盐值: {salt} (长度: {len(salt)})"
)
print
(f
"[认证] 存储哈希: {stored_hash} (长度: {len(stored_hash)})"
)
try
:
if
len
(salt) !
=
32
:
raise
ValueError(f
"无效的盐值长度: {len(salt)},应为32位"
)
input_hash, _
=
PasswordUtils.hash_password(password, salt)
print
(f
"[认证] 计算哈希: {input_hash} (长度: {len(input_hash)})"
)
return
(input_hash
=
=
stored_hash), result[
0
][
2
], result[
0
][
3
], result[
0
][
4
].split(
","
)
except
ValueError as e:
print
(f
"[错误] 盐值验证失败: {str(e)}"
)
return
False
,
None
,
None
, []
return
False
,
None
,
None
, []
class
RegisterWindow(tk.Toplevel):
def
__init__(
self
, parent, db):
super
().__init__(parent)
self
.db
=
db
self
.title(
"用户注册"
)
self
.geometry(
"400x300"
)
main_frame
=
ttk.Frame(
self
)
main_frame.place(relx
=
0.5
, rely
=
0.5
, anchor
=
"center"
)
form_frame
=
ttk.Frame(main_frame)
form_frame.grid(row
=
0
, column
=
0
, sticky
=
"")
label_width
=
8
entry_width
=
20
combo_width
=
18
pad
=
5
ttk.Label(form_frame, text
=
"用户名:"
, width
=
label_width, anchor
=
"e"
).grid(
row
=
0
, column
=
0
, padx
=
pad, pady
=
pad, sticky
=
"e"
)
self
.entry_username
=
ttk.Entry(form_frame, width
=
entry_width)
self
.entry_username.grid(row
=
0
, column
=
1
, padx
=
pad, pady
=
pad, sticky
=
"w"
)
ttk.Label(form_frame, text
=
"密码:"
, width
=
label_width, anchor
=
"e"
).grid(
row
=
1
, column
=
0
, padx
=
pad, pady
=
pad, sticky
=
"e"
)
self
.entry_password
=
ttk.Entry(form_frame, show
=
"*"
, width
=
entry_width)
self
.entry_password.grid(row
=
1
, column
=
1
, padx
=
pad, pady
=
pad, sticky
=
"w"
)
ttk.Label(form_frame, text
=
"确认密码:"
, width
=
label_width, anchor
=
"e"
).grid(
row
=
2
, column
=
0
, padx
=
pad, pady
=
pad, sticky
=
"e"
)
self
.entry_confirm
=
ttk.Entry(form_frame, show
=
"*"
, width
=
entry_width)
self
.entry_confirm.grid(row
=
2
, column
=
1
, padx
=
pad, pady
=
pad, sticky
=
"w"
)
ttk.Label(form_frame, text
=
"姓名:"
, width
=
label_width, anchor
=
"e"
).grid(
row
=
3
, column
=
0
, padx
=
pad, pady
=
pad, sticky
=
"e"
)
self
.entry_name
=
ttk.Entry(form_frame, width
=
entry_width)
self
.entry_name.grid(row
=
3
, column
=
1
, padx
=
pad, pady
=
pad, sticky
=
"w"
)
ttk.Label(form_frame, text
=
"部门:"
, width
=
label_width, anchor
=
"e"
).grid(
row
=
4
, column
=
0
, padx
=
pad, pady
=
pad, sticky
=
"e"
)
self
.combo_department
=
ttk.Combobox(form_frame, width
=
combo_width,
values
=
DEPARTMENTS, state
=
"readonly"
)
self
.combo_department.grid(row
=
4
, column
=
1
, padx
=
pad, pady
=
pad, sticky
=
"w"
)
ttk.Label(form_frame, text
=
"权限:"
, width
=
label_width, anchor
=
"e"
).grid(
row
=
5
, column
=
0
, padx
=
pad, pady
=
pad, sticky
=
"e"
)
perm_frame
=
ttk.Frame(form_frame)
perm_frame.grid(row
=
5
, column
=
1
, padx
=
(
3
, pad), pady
=
pad, sticky
=
"w"
)
self
.permission_vars
=
{perm: tk.BooleanVar()
for
perm
in
PERMISSIONS_LIST}
for
perm
in
PERMISSIONS_LIST:
cb
=
ttk.Checkbutton(perm_frame, text
=
perm, variable
=
self
.permission_vars[perm])
cb.pack(side
=
tk.LEFT, padx
=
2
)
btn_frame
=
ttk.Frame(main_frame)
btn_frame.grid(row
=
1
, column
=
0
, pady
=
15
)
ttk.Button(btn_frame, text
=
"提交注册"
, command
=
self
.submit).pack()
self
.update_idletasks()
parent_x
=
parent.winfo_x()
parent_y
=
parent.winfo_y()
parent_width
=
parent.winfo_width()
parent_height
=
parent.winfo_height()
window_width
=
self
.winfo_reqwidth()
window_height
=
self
.winfo_reqheight()
x
=
parent_x
+
(parent_width
-
window_width)
/
/
2
y
=
parent_y
+
(parent_height
-
window_height)
/
/
2
self
.geometry(f
"+{x}+{y}"
)
self
.transient(parent)
self
.grab_set()
self
.focus_force()
def
submit(
self
):
name
=
self
.entry_name.get().strip()
username
=
self
.entry_username.get().strip()
password
=
self
.entry_password.get().strip()
confirm
=
self
.entry_confirm.get().strip()
department
=
self
.combo_department.get()
permissions
=
[perm
for
perm, var
in
self
.permission_vars.items()
if
var.get()]
if
not
all
([name, username, password, confirm, department]):
messagebox.showwarning(
"输入错误"
,
"所有字段必须填写"
)
return
if
password !
=
confirm:
messagebox.showwarning(
"输入错误"
,
"两次密码不一致"
)
return
if
not
permissions:
messagebox.showwarning(
"输入错误"
,
"至少选择一个权限"
)
return
if
self
.db.execute_query(
"SELECT username FROM users WHERE username = ?"
, (username,)):
messagebox.showwarning(
"错误"
,
"用户名已存在"
)
return
hashed_pw, salt
=
PasswordUtils.hash_password(password)
result
=
self
.db.execute_query(
"INSERT INTO users (username, password_hash, name, department, salt, permissions) VALUES (?, ?, ?, ?, ?, ?)"
,
(username, hashed_pw, name, department, salt,
","
.join(permissions))
)
if
result
is
not
None
:
messagebox.showinfo(
"成功"
,
"用户注册成功"
)
self
.destroy()
class
UserManagerWindow(tk.Toplevel):
def
__init__(
self
, parent, db):
super
().__init__(parent)
self
.db
=
db
self
.title(
"用户管理"
)
self
.geometry(
"1000x400"
)
self
.tree
=
ttk.Treeview(
self
, columns
=
(
"用户名"
,
"姓名"
,
"部门"
,
"权限"
), show
=
"headings"
)
self
.tree.heading(
"用户名"
, text
=
"用户名"
)
self
.tree.heading(
"姓名"
, text
=
"姓名"
)
self
.tree.heading(
"部门"
, text
=
"部门"
)
self
.tree.heading(
"权限"
, text
=
"权限"
)
self
.tree.column(
"用户名"
, width
=
120
)
self
.tree.column(
"姓名"
, width
=
100
)
self
.tree.column(
"部门"
, width
=
120
)
self
.tree.column(
"权限"
, width
=
600
)
self
.tree.pack(fill
=
tk.BOTH, expand
=
True
, padx
=
10
, pady
=
10
)
btn_frame
=
ttk.Frame(
self
)
btn_frame.pack(pady
=
5
)
ttk.Button(btn_frame, text
=
"刷新"
, command
=
self
.load_users).pack(side
=
tk.LEFT, padx
=
5
)
ttk.Button(btn_frame, text
=
"修改权限"
, command
=
self
.open_edit_window).pack(side
=
tk.LEFT, padx
=
5
)
self
.load_users()
self
.center_window(parent)
def
center_window(
self
, parent):
self
.update_idletasks()
parent_x
=
parent.winfo_x()
parent_y
=
parent.winfo_y()
parent_width
=
parent.winfo_width()
parent_height
=
parent.winfo_height()
window_width
=
self
.winfo_reqwidth()
window_height
=
self
.winfo_reqheight()
x
=
parent_x
+
(parent_width
-
window_width)
/
/
2
y
=
parent_y
+
(parent_height
-
window_height)
/
/
2
self
.geometry(f
"+{x}+{y}"
)
def
load_users(
self
):
for
item
in
self
.tree.get_children():
self
.tree.delete(item)
users
=
self
.db.execute_query(
"SELECT username, name, department, permissions FROM users"
)
if
users:
for
user
in
users:
print
(f
"用户名: {user[0]}, 姓名: {user[1]}"
)
self
.tree.insert("", tk.END, values
=
(
user[
0
],
user[
1
],
user[
2
],
user[
3
]
))
def
open_edit_window(
self
):
selected
=
self
.tree.selection()
if
not
selected:
messagebox.showwarning(
"提示"
,
"请先选择一个用户"
)
return
username
=
self
.tree.item(selected[
0
])[
"values"
][
0
]
UserEditWindow(
self
,
self
.db, username)
print
(username, UserEditWindow,
'#######################'
)
class
UserEditWindow(tk.Toplevel):
def
__init__(
self
, parent, db, username):
super
().__init__(parent)
self
.db
=
db
self
.username
=
username
self
.title(f
"编辑用户权限 - {username}"
)
self
.geometry(
"400x300"
)
ttk.Label(
self
, text
=
"用户名:"
).pack(pady
=
5
)
ttk.Label(
self
, text
=
username).pack()
user_data
=
self
.db.execute_query(
"SELECT name, department, permissions FROM users WHERE username = ?"
,
(username,)
)[
0
]
ttk.Label(
self
, text
=
"姓名:"
).pack(pady
=
5
)
ttk.Label(
self
, text
=
user_data[
0
]).pack()
ttk.Label(
self
, text
=
"部门:"
).pack(pady
=
5
)
ttk.Label(
self
, text
=
user_data[
1
]).pack()
ttk.Label(
self
, text
=
"权限:"
).pack(pady
=
5
)
self
.permission_vars
=
{perm: tk.BooleanVar()
for
perm
in
PERMISSIONS_LIST}
current_perms
=
user_data[
2
].split(
","
)
perm_frame
=
ttk.Frame(
self
)
perm_frame.pack()
for
perm
in
PERMISSIONS_LIST:
cb
=
ttk.Checkbutton(perm_frame, text
=
perm, variable
=
self
.permission_vars[perm])
cb.pack(side
=
tk.LEFT, padx
=
5
)
if
perm
in
current_perms:
self
.permission_vars[perm].
set
(
True
)
ttk.Button(
self
, text
=
"保存修改"
, command
=
self
.save_changes).pack(pady
=
15
)
self
.center_window(parent)
def
center_window(
self
, parent):
self
.update_idletasks()
parent_x
=
parent.winfo_x()
parent_y
=
parent.winfo_y()
parent_width
=
parent.winfo_width()
parent_height
=
parent.winfo_height()
window_width
=
self
.winfo_reqwidth()
window_height
=
self
.winfo_reqheight()
x
=
parent_x
+
(parent_width
-
window_width)
/
/
2
y
=
parent_y
+
(parent_height
-
window_height)
/
/
2
self
.geometry(f
"+{x}+{y}"
)
def
save_changes(
self
):
new_perms
=
[perm
for
perm, var
in
self
.permission_vars.items()
if
var.get()]
print
((
","
.join(new_perms),
self
.username),
'~~~~~~~~~~~~~~~'
)
if
not
new_perms:
messagebox.showwarning(
"错误"
,
"必须至少选择一个权限"
)
return
result
=
self
.db.execute_query(
"UPDATE users SET permissions = ? WHERE username = ?"
,
(
","
.join(new_perms),
self
.username)
)
print
(f
"[DEBUG] 更新权限结果: {result}"
)
if
result
=
=
1
:
messagebox.showinfo(
"成功"
,
"权限已更新"
)
self
.master.load_users()
self
.destroy()
elif
result
=
=
0
:
messagebox.showerror(
"错误"
,
"用户不存在或更新失败"
)
else
:
messagebox.showerror(
"错误"
, f
"更新失败,数据库错误: {result}"
)
class
LoginWindow(tk.Toplevel):
def
__init__(
self
, parent, db):
super
().__init__(parent)
self
.db
=
db
self
.parent
=
parent
self
.title(
"用户登录"
)
self
.geometry(
"300x200"
)
ttk.Label(
self
, text
=
"用户名:"
).grid(row
=
1
, column
=
0
, padx
=
30
, pady
=
20
, sticky
=
(tk.E))
self
.entry_username
=
ttk.Entry(
self
)
self
.entry_username.grid(row
=
1
, column
=
1
)
ttk.Label(
self
, text
=
"密码:"
).grid(row
=
2
, column
=
0
, padx
=
30
, pady
=
10
, sticky
=
(tk.E))
self
.entry_password
=
ttk.Entry(
self
, show
=
"*"
)
self
.entry_password.grid(row
=
2
, column
=
1
)
ttk.Button(
self
, text
=
"登录"
, command
=
self
.authenticate).grid(row
=
3
, column
=
1
, pady
=
20
, sticky
=
(tk.W))
self
.protocol(
"WM_DELETE_WINDOW"
,
self
.on_close)
self
.update_idletasks()
screen_width
=
self
.winfo_screenwidth()
screen_height
=
self
.winfo_screenheight()
window_width
=
self
.winfo_reqwidth()
window_height
=
self
.winfo_reqheight()
x
=
(screen_width
-
window_width)
/
/
2
y
=
(screen_height
-
window_height)
/
/
2
self
.geometry(f
"+{x}+{y}"
)
def
authenticate(
self
):
username
=
self
.entry_username.get().strip()
password
=
self
.entry_password.get().strip()
valid, name, department, permissions
=
self
.db.authenticate(username, password)
if
valid:
self
.destroy()
self
.parent.deiconify()
MainApplication(
self
.parent,
self
.db, name, department, permissions)
else
:
messagebox.showerror(
"登录失败"
,
"用户名或密码错误"
)
def
on_close(
self
):
self
.parent.destroy()
class
ModifyWindow(tk.Toplevel):
def
__init__(
self
, parent, db, app_id, current_reason, user_name, user_perms, callback):
super
().__init__(parent)
self
.db
=
db
self
.app_id
=
app_id
self
.user_name
=
user_name
self
.callback
=
callback
self
.user_perms
=
user_perms
self
.title(
"修改申请事由"
)
self
.geometry(
"600x400"
)
ttk.Label(
self
, text
=
"申请事由:"
).pack(pady
=
5
)
self
.text_reason
=
scrolledtext.ScrolledText(
self
, wrap
=
tk.WORD, height
=
10
)
self
.text_reason.insert(tk.INSERT, current_reason)
self
.text_reason.pack(fill
=
tk.BOTH, expand
=
True
, padx
=
10
, pady
=
5
)
btn_frame
=
ttk.Frame(
self
)
btn_frame.pack(pady
=
10
)
ttk.Button(btn_frame, text
=
"提交修改"
, command
=
self
.submit).pack(side
=
tk.LEFT, padx
=
5
)
ttk.Button(btn_frame, text
=
"取消"
, command
=
self
.destroy).pack(side
=
tk.LEFT, padx
=
5
)
self
.center_window(parent)
def
center_window(
self
, parent):
self
.update_idletasks()
parent_x
=
parent.winfo_x()
parent_y
=
parent.winfo_y()
parent_width
=
parent.winfo_width()
parent_height
=
parent.winfo_height()
window_width
=
self
.winfo_reqwidth()
window_height
=
self
.winfo_reqheight()
x
=
parent_x
+
(parent_width
-
window_width)
/
/
2
y
=
parent_y
+
(parent_height
-
window_height)
/
/
2
self
.geometry(f
"+{x}+{y}"
)
def
submit(
self
):
new_reason
=
self
.text_reason.get(
"1.0"
, tk.END).strip()
if
not
new_reason:
messagebox.showwarning(
"错误"
,
"申请事由不能为空"
)
return
if
"管理"
in
self
.user_perms:
query
=
"UPDATE doc_applications SET reason=? WHERE id=? AND status='待审核'"
params
=
(new_reason,
self
.app_id)
else
:
query
=
params
=
(new_reason,
self
.app_id,
self
.user_name)
result
=
self
.db.execute_query(query, params)
if
result
=
=
1
:
messagebox.showinfo(
"成功"
,
"修改申请成功"
)
self
.callback()
self
.destroy()
else
:
messagebox.showerror(
"错误"
,
"修改失败,请检查申请状态"
)
class
ChangePasswordWindow(tk.Toplevel):
def
__init__(
self
, parent, db, username):
super
().__init__(parent)
self
.db
=
db
self
.username
=
username
self
.title(
"修改密码"
)
self
.geometry(
"350x220"
)
form_frame
=
ttk.Frame(
self
)
form_frame.pack(padx
=
10
, pady
=
10
, fill
=
tk.BOTH, expand
=
True
)
ttk.Label(form_frame, text
=
"当前密码:"
).grid(row
=
0
, column
=
0
, padx
=
10
, pady
=
10
, sticky
=
"e"
)
self
.entry_current
=
ttk.Entry(form_frame, show
=
"*"
)
self
.entry_current.grid(row
=
0
, column
=
1
, padx
=
10
, pady
=
10
)
ttk.Label(form_frame, text
=
"新密码:"
).grid(row
=
1
, column
=
0
, padx
=
10
, pady
=
10
, sticky
=
"e"
)
self
.entry_new
=
ttk.Entry(form_frame, show
=
"*"
)
self
.entry_new.grid(row
=
1
, column
=
1
, padx
=
10
, pady
=
10
)
ttk.Label(form_frame, text
=
"确认密码:"
).grid(row
=
2
, column
=
0
, padx
=
10
, pady
=
10
, sticky
=
"e"
)
self
.entry_confirm
=
ttk.Entry(form_frame, show
=
"*"
)
self
.entry_confirm.grid(row
=
2
, column
=
1
, padx
=
10
, pady
=
10
)
btn_frame
=
ttk.Frame(
self
)
btn_frame.pack(pady
=
5
)
ttk.Button(btn_frame, text
=
"提交"
, command
=
self
.submit).pack(side
=
tk.LEFT, padx
=
5
)
ttk.Button(btn_frame, text
=
"取消"
, command
=
self
.destroy).pack(side
=
tk.LEFT, padx
=
5
)
def
submit(
self
):
current_pw
=
self
.entry_current.get().strip()
new_pw
=
self
.entry_new.get().strip()
confirm_pw
=
self
.entry_confirm.get().strip()
if
not
all
([current_pw, new_pw, confirm_pw]):
messagebox.showwarning(
"错误"
,
"所有字段必须填写"
)
return
if
new_pw !
=
confirm_pw:
messagebox.showwarning(
"错误"
,
"新密码不一致"
)
self
.clear_entries()
return
try
:
result
=
self
.db.execute_query(
"SELECT password_hash, salt FROM users WHERE username = ?"
,
(
self
.username,)
)
if
result
is
None
:
messagebox.showerror(
"错误"
,
"数据库查询失败,请检查连接"
)
self
.clear_entries()
return
stored_hash
=
result[
0
][
0
]
old_salt
=
result[
0
][
1
]
print
(f
"[DEBUG] 旧盐值: {old_salt} (长度: {len(old_salt)})"
)
print
(f
"[DEBUG] 存储的哈希: {stored_hash}"
)
current_hash, _
=
PasswordUtils.hash_password(current_pw, old_salt)
if
current_hash !
=
stored_hash:
messagebox.showerror(
"错误"
,
"当前密码不正确"
)
self
.clear_entries()
return
print
(f
"[DEBUG] 计算出的当前哈希: {current_hash}"
)
new_salt
=
os.urandom(
16
).
hex
()
new_hash, _
=
PasswordUtils.hash_password(new_pw, new_salt)
result
=
self
.db.execute_query(
"UPDATE users SET password_hash = ?, salt = ? WHERE username = ?"
,
(new_hash, new_salt,
self
.username)
)
print
(f
"[DEBUG] 更新结果: {result} 行受影响"
)
if
result
=
=
1
:
messagebox.showinfo(
"成功"
,
"密码修改成功"
)
self
.destroy()
else
:
messagebox.showerror(
"错误"
,
"密码修改失败,请重试"
)
self
.clear_entries()
except
Exception as e:
messagebox.showerror(
"错误"
, f
"发生错误: {str(e)}"
)
self
.clear_entries()
traceback.print_exc()
def
clear_entries(
self
):
self
.entry_current.delete(
0
, tk.END)
self
.entry_new.delete(
0
, tk.END)
self
.entry_confirm.delete(
0
, tk.END)
class
MainApplication:
def
__init__(
self
, root, db, name, department, permissions):
self
.root
=
root
self
.db
=
db
self
.user_name
=
name
self
.department
=
department
self
.permissions
=
permissions
self
.root.title(f
"单据管理系统 - {self.user_name} ({department})"
)
self
.root.geometry(
"1200x680"
)
self
.center_window()
self
.top_frame
=
ttk.Frame(
self
.root)
self
.top_frame.pack(fill
=
tk.X, padx
=
10
, pady
=
5
)
ttk.Label(
self
.top_frame, text
=
f
"当前用户: {self.user_name} | 部门: {department}"
).pack(side
=
tk.LEFT)
if
"管理"
in
self
.permissions:
ttk.Button(
self
.top_frame, text
=
"注册用户"
, command
=
self
.open_register).pack(side
=
tk.LEFT, padx
=
5
)
ttk.Button(
self
.top_frame, text
=
"用户管理"
, command
=
self
.open_user_manager).pack(side
=
tk.LEFT)
ttk.Button(
self
.top_frame, text
=
"修改密码"
, command
=
self
.open_change_password).pack(side
=
tk.LEFT, padx
=
5
)
self
.create_form()
self
.create_table()
self
.create_actions()
self
.load_data()
def
open_change_password(
self
):
ChangePasswordWindow(
self
.root,
self
.db,
self
.user_name)
def
center_window(
self
):
self
.root.update_idletasks()
screen_width
=
self
.root.winfo_screenwidth()
screen_height
=
self
.root.winfo_screenheight()
window_width
=
1200
window_height
=
680
x
=
(screen_width
-
window_width)
/
/
2
y
=
(screen_height
-
window_height)
/
/
2
self
.root.geometry(f
"{window_width}x{window_height}+{x}+{y}"
)
def
create_actions(
self
):
btn_frame
=
ttk.Frame(
self
.root)
btn_frame.pack(pady
=
10
)
self
.action_buttons
=
{}
actions
=
{
"批准"
: (
"审核"
,
self
.approve_application),
"撤销"
: (
"撤销"
,
self
.revoke_approval),
"作废"
: (
"作废"
,
self
.void_application),
"修改"
: (
"编辑"
,
self
.modify_application),
"刷新"
: (
None
,
self
.load_data),
"详情"
: (
None
,
self
.show_detail)
}
for
text, (perm, cmd)
in
actions.items():
if
perm
is
None
or
perm
in
self
.permissions:
btn
=
ttk.Button(btn_frame, text
=
text, command
=
cmd)
btn.pack(side
=
tk.LEFT, padx
=
5
)
self
.action_buttons[text]
=
btn
def
update_buttons(
self
, event
=
None
):
selection
=
self
.tree.selection()
current_status
=
""
current_applicant
=
""
if
selection:
values
=
self
.tree.item(selection[
0
])[
"values"
]
current_status
=
values[
-
1
].split()[
-
1
]
current_applicant
=
values[
2
]
if
"修改"
in
self
.action_buttons:
btn
=
self
.action_buttons[
"修改"
]
has_edit
=
"编辑"
in
self
.permissions
is_self
=
current_applicant
=
=
self
.user_name
valid_status
=
current_status
=
=
"待审核"
enable
=
has_edit
and
valid_status
and
(is_self
or
"管理"
in
self
.permissions)
btn.config(state
=
tk.NORMAL
if
enable
else
tk.DISABLED)
def
modify_application(
self
):
selected_id
=
self
.get_selected_id()
if
not
selected_id:
return
record
=
self
.db.execute_query(
"SELECT * FROM doc_applications WHERE id = ?"
,
(selected_id,)
)
if
not
record:
return
record
=
record[
0
]
if
"编辑"
not
in
self
.permissions
and
"管理"
not
in
self
.permissions:
messagebox.showwarning(
"权限不足"
,
"您没有权限修改申请"
)
return
if
record.status !
=
"待审核"
:
messagebox.showwarning(
"操作无效"
,
"只能修改待审核的申请"
)
return
if
record.applicant !
=
self
.user_name
and
"管理"
not
in
self
.permissions:
messagebox.showwarning(
"操作受限"
,
"只能修改自己提交的申请"
)
return
ModifyWindow(
self
.root,
self
.db,
selected_id,
record.reason,
self
.user_name,
self
.permissions,
self
.load_data
)
def
update_buttons(
self
, event
=
None
):
selection
=
self
.tree.selection()
current_status
=
""
current_applicant
=
""
if
selection:
values
=
self
.tree.item(selection[
0
])[
"values"
]
current_status
=
values[
-
1
].split()[
-
1
]
current_applicant
=
values[
2
]
if
"修改"
in
self
.action_buttons:
btn
=
self
.action_buttons[
"修改"
]
has_edit
=
"编辑"
in
self
.permissions
is_self
=
current_applicant
=
=
self
.user_name
valid_status
=
current_status
=
=
"待审核"
enable
=
valid_status
and
( (has_edit
and
is_self)
or
(
"管理"
in
self
.permissions) )
btn.config(state
=
tk.NORMAL
if
enable
else
tk.DISABLED)
def
create_form(
self
):
form_frame
=
ttk.LabelFrame(
self
.root, text
=
"新建申请"
)
form_frame.pack(fill
=
tk.X, padx
=
10
, pady
=
5
)
fields
=
[
(
"单据类型"
,
"combobox"
, [
"差旅费"
,
"办公用品"
,
"采购"
,
"其他"
]),
(
"申请人"
,
"entry"
),
(
"申请部门"
,
"combobox"
, DEPARTMENTS),
(
"申请金额"
,
"entry"
),
(
"申请事由"
,
"text"
)
]
self
.widgets
=
{}
for
row, (label, widget_type,
*
args)
in
enumerate
(fields):
ttk.Label(form_frame, text
=
f
"{label}:"
).grid(row
=
row, column
=
0
, padx
=
10
, pady
=
10
, sticky
=
tk.E)
if
widget_type
=
=
"combobox"
:
widget
=
ttk.Combobox(form_frame, values
=
args[
0
], state
=
"readonly"
)
if
label
=
=
"申请部门"
:
widget.
set
(
self
.department)
widget.config(state
=
"disabled"
)
elif
widget_type
=
=
"entry"
:
widget
=
ttk.Entry(form_frame)
if
label
=
=
"申请金额"
:
widget.insert(
0
,
"0"
)
if
label
=
=
"申请人"
:
widget.insert(
0
,
self
.user_name)
widget.config(state
=
"disabled"
)
elif
widget_type
=
=
"text"
:
widget
=
tk.Text(form_frame, height
=
5
, width
=
40
)
widget.grid(row
=
row, column
=
1
, padx
=
10
, pady
=
10
, sticky
=
tk.W)
self
.widgets[label]
=
widget
submit_btn
=
ttk.Button(form_frame, text
=
"提交申请"
, command
=
self
.submit_application,
state
=
tk.NORMAL
if
"申请"
in
self
.permissions
else
tk.DISABLED)
submit_btn.grid(row
=
5
, column
=
1
, pady
=
10
, sticky
=
tk.W)
def
create_table(
self
):
table_frame
=
ttk.LabelFrame(
self
.root, text
=
"申请记录"
)
table_frame.pack(fill
=
tk.BOTH, expand
=
True
, padx
=
10
, pady
=
5
)
columns
=
(
"申请编号"
,
"单据类型"
,
"申请人"
,
"申请部门"
,
"申请日期"
,
"申请金额"
,
"申请事由"
,
"审批状态"
)
self
.tree
=
ttk.Treeview(table_frame, columns
=
columns, show
=
"headings"
)
col_widths
=
[
80
,
100
,
90
,
110
,
150
,
90
,
250
,
120
]
for
col, width
in
zip
(columns, col_widths):
self
.tree.column(col, width
=
width, anchor
=
tk.CENTER)
self
.tree.heading(col, text
=
col)
vsb
=
ttk.Scrollbar(table_frame, orient
=
"vertical"
, command
=
self
.tree.yview)
self
.tree.configure(yscrollcommand
=
vsb.
set
)
self
.tree.pack(side
=
tk.LEFT, fill
=
tk.BOTH, expand
=
True
)
vsb.pack(side
=
tk.RIGHT, fill
=
tk.Y)
self
.tree.bind(
"<<TreeviewSelect>>"
,
self
.update_buttons)
self
.tree.bind(
"<Double-1>"
,
lambda
e:
self
.show_detail())
def
load_data(
self
):
for
item
in
self
.tree.get_children():
self
.tree.delete(item)
records
=
self
.db.execute_query(
"SELECT * FROM doc_applications ORDER BY apply_date DESC"
)
if
records:
status_map
=
{
'待审核'
: (
'🔔'
,
'待审核'
),
'已审核'
: (
'🔕'
,
'已批准'
),
'Rejected'
: (
'❌'
,
'已驳回'
),
'Voided'
: (
'✘'
,
'已作废'
)
}
for
row
in
records:
icon, text
=
status_map.get(row[
7
], (
'☆'
, row[
7
]))
amount_display
=
"***"
if
row[
7
]
=
=
'Voided'
else
f
"¥{row[5]:.2f}"
reason_display
=
"*****"
if
row[
7
]
=
=
'Voided'
else
(
row[
6
][:
35
]
+
"..."
if
len
(row[
6
]) >
35
else
row[
6
]
)
print
(
"row4"
, row[
4
],
type
(row[
4
]))
apply_date
=
datetime.strptime(row[
4
],
"%Y-%m-%d %H:%M:%S"
)
self
.tree.insert("", tk.END, values
=
(
row[
0
],
row[
1
],
row[
2
],
row[
3
],
row[
4
],
amount_display,
reason_display,
f
"{icon} {text}"
))
def
submit_application(
self
):
data
=
{
"单据类型"
:
self
.widgets[
"单据类型"
].get(),
"申请人"
:
self
.widgets[
"申请人"
].get().strip(),
"申请部门"
:
self
.widgets[
"申请部门"
].get(),
"申请金额"
:
self
.widgets[
"申请金额"
].get().strip(),
"申请事由"
:
self
.widgets[
"申请事由"
].get(
"1.0"
, tk.END).strip()
}
salt
=
os.urandom(
16
).
hex
()
try
:
amount
=
float
(data[
"申请金额"
])
if
amount <
=
0
:
raise
ValueError
except
ValueError:
messagebox.showwarning(
"输入错误"
,
"金额必须为大于0的有效数字"
)
return
for
field, value
in
data.items():
if
not
value:
messagebox.showwarning(
"输入错误"
, f
"请填写{field}"
)
return
try
:
amount
=
float
(data[
"申请金额"
])
if
amount <
0
:
raise
ValueError
except
ValueError:
messagebox.showwarning(
"输入错误"
,
"金额必须为有效正数"
)
return
result
=
self
.db.execute_query(
"INSERT INTO doc_applications (doc_type, applicant, department, apply_date, amount, reason, salt) "
"VALUES (?, ?, ?, ?, ?, ?, ?)"
,
(
data[
"单据类型"
],
data[
"申请人"
],
data[
"申请部门"
],
datetime.now().replace(microsecond
=
0
),
amount,
data[
"申请事由"
],
salt
)
)
if
result:
messagebox.showinfo(
"提交成功"
,
"申请已成功提交!"
)
self
.clear_form()
self
.load_data()
def
clear_form(
self
):
for
label, widget
in
self
.widgets.items():
if
isinstance
(widget, ttk.Combobox):
if
label
=
=
"申请部门"
:
widget.
set
(
self
.department)
else
:
widget.
set
('')
elif
isinstance
(widget, tk.Text):
widget.delete(
"1.0"
, tk.END)
else
:
widget.delete(
0
, tk.END)
def
approve_application(
self
):
selected_id
=
self
.get_selected_id()
if
selected_id:
result
=
self
.db.execute_query(
"UPDATE doc_applications SET status='已审核' WHERE id=? AND status='待审核'"
,
(selected_id,)
)
if
result
=
=
1
:
self
.load_data()
messagebox.showinfo(
"操作成功"
,
"申请已批准"
)
def
revoke_approval(
self
):
selected_id
=
self
.get_selected_id()
if
selected_id:
result
=
self
.db.execute_query(
"UPDATE doc_applications SET status='待审核' WHERE id=? AND status='已审核'"
,
(selected_id,)
)
if
result
=
=
1
:
self
.load_data()
messagebox.showinfo(
"操作成功"
,
"批准已撤销"
)
def
void_application(
self
):
selected_id
=
self
.get_selected_id()
if
selected_id:
if
messagebox.askyesno(
"确认作废"
,
"确定要作废此申请吗?"
):
result
=
self
.db.execute_query(
"UPDATE doc_applications SET status='Voided' WHERE id=? AND status IN ('待审核', '已审核')"
,
(selected_id,)
)
if
result
=
=
1
:
self
.load_data()
messagebox.showinfo(
"操作成功"
,
"申请已作废"
)
def
show_detail(
self
):
selected_id
=
self
.get_selected_id()
if
selected_id:
record
=
self
.db.execute_query(
"SELECT * FROM doc_applications WHERE id = ?"
,
(selected_id,)
)
if
record:
detail_win
=
tk.Toplevel(
self
.root)
detail_win.title(f
"申请详情 - ID: {selected_id}"
)
text_area
=
scrolledtext.ScrolledText(detail_win, wrap
=
tk.WORD, width
=
80
, height
=
20
)
text_area.pack(padx
=
10
, pady
=
10
)
apply_date
=
datetime.strptime(record[
0
][
4
],
"%Y-%m-%d %H:%M:%S"
)
details
=
(
f
"申请编号: {record[0][0]}\n"
f
"单据类型: {record[0][1]}\n"
f
"申请人: {record[0][2]}\n"
f
"申请部门: {record[0][3]}\n"
f
"申请时间: {apply_date.strftime('%Y-%m-%d %H:%M:%S')}\n"
f
"申请金额: ¥{record[0][5]:.2f}\n"
f
"当前状态: {record[0][7]}\n\n"
"申请事由:\n"
+
record[
0
][
6
]
)
text_area.insert(tk.INSERT, details)
text_area.config(state
=
tk.DISABLED)
def
get_selected_id(
self
):
selection
=
self
.tree.selection()
if
not
selection:
messagebox.showwarning(
"提示"
,
"请先选择一条记录"
)
return
None
return
self
.tree.item(selection[
0
])[
"values"
][
0
]
def
open_register(
self
):
RegisterWindow(
self
.root,
self
.db)
def
open_user_manager(
self
):
UserManagerWindow(
self
.root,
self
.db)
if
__name__
=
=
"__main__"
:
try
:
def
init_admin(db):
existing
=
db.execute_query(
"SELECT * FROM users WHERE username = ?"
, (
"admin"
,))
password
=
"admin123"
hashed_pw, salt
=
PasswordUtils.hash_password(password)
print
(f
"[初始化] 管理员盐值: {salt} (长度: {len(salt)})"
)
print
(f
"[初始化] 管理员哈希: {hashed_pw} (长度: {len(hashed_pw)})"
)
if
not
existing:
print
(f
"[初始化] 创建管理员账户"
)
db.execute_query(
"INSERT INTO users (username, password_hash, name, department, salt, permissions) "
"VALUES (?, ?, ?, ?, ?, ?)"
,
(
"admin"
, hashed_pw,
"系统管理员"
,
"技术部"
, salt,
"申请,审核,撤销,作废,管理"
)
)
else
:
print
(f
"[初始化] 管理员账户已存在,跳过创建"
)
root
=
tk.Tk()
root.withdraw()
db
=
DatabaseManager()
if
db.connect()
and
db.init_db():
init_admin(db)
login_window
=
LoginWindow(root, db)
login_window.mainloop()
else
:
root.destroy()
except
Exception as e:
traceback.print_exc()
input
(
"程序崩溃,按回车查看错误详情"
)