import os
import subprocess
import tkinter as tk
from tkinter import messagebox, font, ttk
import socket
import platform
import pymssql
from datetime import datetime
import psutil
# --- 全局配置 ---
current_time = datetime.now()
server = "192.168.2.39"
user = "aaaaa"
password = "bbbbb"
database = "cccccc"
formatted_time = current_time.strftime("%Y-%m-%d %H:%M:%S")
WRDLV4_EXE_PATH = r"C:\Windows\System32\wrdlv4.exe"
# 数据库表名
TABLE_NAME = "computer_V"
# 默认值为 "是" 的选项
DEFAULT_YES = "是"
DEFAULT_NO = "否"
DEFAULT_SCAN_STATUS_OPTIONS = [DEFAULT_YES, DEFAULT_NO]
DEFAULT_INSTALLED_STATUS_OPTIONS = [DEFAULT_YES, DEFAULT_NO]
DEFAULT_USB_STATUS_OPTIONS = [DEFAULT_YES, DEFAULT_NO]
DEFAULT_FIREWALL_STATUS_OPTIONS = [DEFAULT_YES, DEFAULT_NO]
# --- 自动获取系统信息的函数 ---
def insert_one(row_data):
"""
将单条数据插入或更新到指定的数据库表。
如果 mac_address 存在,则更新该行;否则插入新行。
参数:
row_data (list/tuple): 包含所有字段数据的有序序列。
顺序必须与 SQL INSERT/UPDATE 语句中的列对应。
返回值:
0: 插入/更新成功
str (错误信息): 插入/更新失败
"""
# 确保所有数据都是字符串,以防止 pymssql 错误
# 假设 row_data 的顺序与数据库表的字段顺序一致
# 并且我们已经定义了常量来存储这些字段名
# 这里为了演示,直接使用解包
try:
(mac_address, department, hostname, system_type, system_version,
entry_time_str,emp_vname,
emp_name, emp_id, area, ip_address, ip_guard_status,
virus_scan_status, antivirus_installed_status, usb_disabled_status, firewall_enabled_status) = row_data
except ValueError as e:
return f"数据格式错误,所需字段数量不匹配: {e}"
# 确保所有字段都是字符串,特别是可能包含 None 的字段
str_row_data = [str(item) if item is not None else "" for item in row_data]
conn = None
try:
conn = pymssql.connect(server, user, password, database)
cursor = conn.cursor()
# --------------- 数据库操作 ---------------
# 1. 尝试更新
# 注意:SQLAlchemy or ORM 可以更方便地管理这些,但直接使用 pymssql
# 需要手动处理。这里使用占位符 %s。
update_sql = f"""
UPDATE {TABLE_NAME}
SET ICTDRI = %s,
hostname = %s,
system_type = %s,
system_version = %s,
entry_time = %s,
emp = %s, -- 厂商姓名
emp_id = %s, -- 厂商ID (原来是 emp, 假定 emp_id 是新加的,或 emp 含义改变)
area = %s, -- 厂商电话
ip_address = %s, -- 新增加的 IP 地址
ip_guard = %s, -- 新增加的 IP Guard 状态
virus_scan_status = %s, -- 新的字段名
antivirus_installed_status = %s, -- 新的字段名
usb_disabled_status = %s, -- 新的字段名
firewall_enabled_status = %s, -- 新的字段名
vendorname = %s
WHERE mac_address = %s
"""
# 参数顺序需要与 UPDATE SET 后面的列顺序一致,并且最后是 WHERE 子句的条件
update_params = (department, hostname, system_type, system_version,
entry_time_str,emp_id,
emp_name, area, ip_address, ip_guard_status,
virus_scan_status, antivirus_installed_status, usb_disabled_status, firewall_enabled_status,emp_vname,
mac_address) # 最后一位是 WHERE mac_address = %s
cursor.execute(update_sql, update_params)
if cursor.rowcount == 0:
# 2. 如果没有行被更新,说明 mac_address 不存在,则插入
insert_sql = f"""
INSERT INTO {TABLE_NAME} (
mac_address, ICTDRI, hostname, system_type, system_version,
entry_time, emp, emp_id, area, ip_address, ip_guard,
virus_scan_status, antivirus_installed_status, usb_disabled_status, firewall_enabled_status,vendorname
)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
"""
# 参数顺序必须与 INSERT INTO (...) 后面的列顺序一致
insert_params = (mac_address, department, hostname, system_type, system_version,
entry_time_str,emp_id,
emp_name, area, ip_address, ip_guard_status,
virus_scan_status, antivirus_installed_status, usb_disabled_status, firewall_enabled_status,emp_vname)
cursor.execute(insert_sql, insert_params)
conn.commit()
return 0 # 插入成功
else:
conn.commit()
return 0 # 更新成功
except pymssql.Error as e:
if conn:
conn.rollback()
return f"数据库错误: {e}"
except Exception as e:
if conn:
conn.rollback()
return f"其他错误: {e}"
finally:
if conn:
try:
cursor.close()
except Exception:
return "关闭游标错误"
try:
conn.close()
except Exception:
return "关闭连接错误"
def get_mac_address():
"""
获取所有网络接口的 MAC 地址。
返回一个字符串,其中包含所有 MAC 地址,每个地址占一行。
"""
mac_info = []
try:
interfaces = psutil.net_if_addrs()
for interface_name, addresses in interfaces.items():
for address in addresses:
if address.family == psutil.AF_LINK: # AF_LINK is the layer 2 address family (MAC address)
mac_info.append(f"{interface_name}: {address.address}")
break # Found a MAC address for this interface, move to the next interface
except Exception as e:
print(f"获取MAC地址时发生错误: {e}")
return "获取MAC地址失败"
return "\n".join(mac_info) if mac_info else "未找到MAC地址"
def get_ip_address():
"""
获取本机 IP 地址(连接外部网络的主 IP)。
"""
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 尝试连接一个外部地址,这样可以获取到路由出去的IP
# Google's public DNS server is commonly used for this.
s.connect(("8.8.8.8", 80))
ip = s.getsockname()[0]
s.close()
return ip
except Exception as e:
print(f"获取IP地址时发生错误: {e}")
return "未能获取IP地址"
def get_hostname():
"""
获取计算机名。
"""
try:
return socket.gethostname()
except Exception as e:
print(f"获取主机名时发生错误: {e}")
return "未能获取主机名"
def get_system_os_info():
"""
获取系统类型和版本信息。
"""
system_platform = platform.system().lower()
release = platform.release() # e.g., '10', '11', '22.0.0' for macOS
version_details = platform.version() # e.g., '10.0.19045.3996' for Windows, 'Darwin Kernel Version 23.1.0' for macOS
# --- 系统类型 ---
if 'windows' in system_platform:
system_type = 'Windows'
elif 'darwin' in system_platform:
system_type = 'macOS'
else:
system_type = f'Other ({system_platform.capitalize()})'
# --- 版本信息 ---
friendly_version_name = release # Default to the release identifier
if system_type == 'Windows':
try:
# Extract major.minor.build.revision. For example, on Windows 10/11: 10.0.19045.3996
parts = version_details.split('.')
if len(parts) >= 3:
os_build = int(parts[2]) # Extract the OS Build number
# Mapping OS Builds to Windows versions (approximations)
if release == '10': # Windows 10
if os_build >= 22621: friendly_version_name = "Windows 11 Equivalent (≥ 22H2)" # Later builds might be Win11 compatible on older hardware
elif os_build >= 19045: friendly_version_name = "Windows 10 22H2"
elif os_build >= 19044: friendly_version_name = "Windows 10 21H2"
elif os_build >= 19043: friendly_version_name = "Windows 10 21H1"
elif os_build >= 19042: friendly_version_name = "Windows 10 20H2"
elif os_build >= 19041: friendly_version_name = "Windows 10 2004"
else: friendly_version_name = f"Windows 10 (Build {os_build})"
elif release == '11': # Windows 11
if os_build >= 22631: friendly_version_name = "Windows 11 23H2"
elif os_build >= 22621: friendly_version_name = "Windows 11 22H2"
elif os_build >= 22000: friendly_version_name = "Windows 11 21H2"
else: friendly_version_name = f"Windows 11 (Build {os_build})"
else: # Older Windows versions
friendly_version_name = f"Windows {release} (Build {os_build})"
else:
friendly_version_name = f"Windows {release} (Unknown Build)"
except (ValueError, IndexError):
friendly_version_name = f"Windows {release} (Detailed: {version_details})" # Fallback if parsing fails
elif system_type == 'macOS':
# platform.release() for macOS returns Darwin kernel version like '23.1.0'
# We can try to map this to macOS version names.
try:
major_kernel_version = int(release.split('.')[0])
if major_kernel_version >= 23:
friendly_version_name = "macOS Sonoma"
elif major_kernel_version == 22:
friendly_version_name = "macOS Ventura"
elif major_kernel_version == 21:
friendly_version_name = "macOS Monterey"
elif major_kernel_version == 20:
friendly_version_name = "macOS Big Sur"
# Add more mappings if needed
else:
friendly_version_name = f"macOS (Kernel {major_kernel_version})"
except (ValueError, IndexError):
friendly_version_name = f"macOS (Kernel {release})"
# Return friendly name and the detailed version string
return system_type, friendly_version_name, version_details
# --- Tkinter 界面相关函数 ---
def submit_data():
"""
提交按钮点击时调用的函数。
获取所有输入框的内容并插入/更新数据库。
"""
# 获取自动填充的信息
mac_address = text_mac_address.get("1.0", tk.END).strip()
# MAC 地址可能有多个,这里我们只取第一行作为主MAC地址
# mac_address = mac_address_text.split('\n')[0].split(': ')[-1] if mac_address_text else ""
department = entry_DRI.get().strip()
hostname = entry_hostname.get().strip()
system_type = entry_system_type.get().strip()
system_version = entry_system_version.get().strip()
entry_time_str = entry_today.get().strip()
emp_name = entry_emp_name.get().strip() # 厂商姓名
emp_vname = entry_emp_vname.get().strip() # 厂商姓名
emp_id = entry_emp_id.get().strip() # 厂商ID (原 emp)
area = entry_area.get().strip() # 厂商电话
ip_address = entry_ip_address.get().strip()
ip_guard_status = combo_ip_guard.get() # 使用 Combobox 的当前值
virus_scan_status = combo_ntivirus_scan.get()
antivirus_installed_status = combo_antivirus_installed.get()
usb_disabled_status = combo_usb_disabled.get()
firewall_enabled_status = combo_firewall_enabled.get()
if not emp_name or not emp_vname or not emp_id or not area or not department or not mac_address or not hostname:
messagebox.showwarning("输入警告", "缺少必填项!需要全部填写完整")
return
# 准备要插入的数据列表
row_data = [
mac_address,
department,
hostname,
system_type,
system_version,
entry_time_str,
emp_name,
emp_vname,
emp_id,
area,
ip_address,
ip_guard_status,
virus_scan_status,
antivirus_installed_status,
usb_disabled_status,
firewall_enabled_status
]
# 插入或更新数据
result = insert_one(row_data)
if result == 0:
messagebox.showinfo("提交成功", "数据已成功提交!")
# 提交后重置部分字段
reset_manual_fields()
# 重新填充自动字段 (例如MAC, Hostname, OS信息)
populate_auto_fields()
else:
messagebox.showerror("提交失败", result) # 使用 errorbox 显示错误信息
def reset_manual_fields():
"""
清空手动输入的字段。
"""
entry_DRI.delete(0, tk.END)
entry_emp_name.delete(0, tk.END)
entry_emp_vname.delete(0, tk.END)
entry_emp_id.delete(0, tk.END)
entry_area.delete(0, tk.END)
# IP地址和IP Guard由GUI选项决定,不在此清空
# 状态选择框会通过populate_auto_fields和default值恢复
def firewallinfo():
"""
检查Windows防火墙状态(仅考虑专用和公用配置文件)。
如果专用和公用配置文件都启用,返回 1。
否则(任何一个关闭),返回 0。
"""
try:
# 使用netsh命令检查Windows防火墙状态
# 指定encoding='utf-8',通常在现代Windows环境下更通用。
# 如果遇到乱码,再尝试回退到'cp936'或'gbk'
result = subprocess.run(
["netsh", "advfirewall", "show", "allprofiles", "state"],
capture_output=True, text=True, encoding="cp936" # <<< 修改此处
# encoding="cp936" # 如果 utf-8 乱码,可以尝试这个
)
output = result.stdout
print("--- netsh output ---") # 打印原始输出以供调试
print(output)
print("--------------------")
# 将输出按行分割,方便逐行处理
lines = output.splitlines()
# 初始化状态标志
private_enabled = False
public_enabled = False
# 遍历每一行来查找专用和公用配置文件的状态
for i, line in enumerate(lines):
# 查找“专用配置文件”
if "专用配置文件" in line or 'Private Profile Settings:' in line:
# 检查下一行是否包含“状态”和“启用”或“ON”
if i + 2 < len(lines) and ("状态" in lines[i+2] or "State" in lines[i+2]):
status_line = lines[i+2] # 状态值通常在“状态”行之后(可能隔一行)
if "启用" in status_line or "ON" in status_line.upper():
private_enabled = True
else:
private_enabled = False # Explicitly set to False if not enabled
# 查找“公用配置文件”
elif "公用配置文件" in line or 'Public Profile Settings:' in line:
# 检查下一行是否包含“状态”和“启用”或“ON”
if i + 2 < len(lines) and ("状态" in lines[i+2] or "State" in lines[i+2]):
status_line = lines[i+2] # 状态值通常在“状态”行之后(可能隔一行)
if "启用" in status_line or "ON" in status_line.upper():
public_enabled = True
else:
public_enabled = False # Explicitly set to False if not enabled
# 判断最终结果:只有当专用和公用都启用时才返回 1
if private_enabled and public_enabled:
print("Private and Public profiles are ENABLED.")
return 1
else:
print(f"Private enabled: {private_enabled}, Public enabled: {public_enabled}. At least one is OFF. Returning 0.")
return 0
except FileNotFoundError:
print("Error: 'netsh' command not found. Please ensure it's in your PATH.")
return 0
except Exception as e:
print(f"An unexpected error occurred: {e}")
return 0
def ipguardinfo():
"""
检测 C:\Windows\System32\wrdlv4.exe 是否存在。
如果存在,返回 1。
如果不存在,返回 0。
"""
if os.path.exists(WRDLV4_EXE_PATH):
print(f"'{WRDLV4_EXE_PATH}' found.")
return 1
else:
print(f"'{WRDLV4_EXE_PATH}' not found.")
return 0
def populate_auto_fields():
"""
在窗口启动时自动填充字段。
"""
# 填充MAC地址 (所有发现的MAC地址,每行一个)
auto_mac_info = get_mac_address()
text_mac_address.delete("1.0", "end")
text_mac_address.insert("1.0", auto_mac_info)
# 填充IP地址
auto_ip = get_ip_address()
entry_ip_address.delete(0, tk.END)
entry_ip_address.insert(0, auto_ip)
# entry_ip_address.config(state='normal') # Make it editable if needed, or leave readonly
# 填充计算机名
auto_hostname = get_hostname()
entry_hostname.delete(0, tk.END)
entry_hostname.insert(0, auto_hostname)
# entry_hostname.config(state='readonly') # Can be readonly if auto-filled
# 填充系统类型和版本
auto_system_type, auto_sys_friendly, auto_sys_detailed = get_system_os_info()
entry_system_type.delete(0, tk.END)
entry_system_type.insert(0, auto_system_type)
# entry_system_type.config(state='readonly')
full_system_version_display = f"{auto_sys_friendly} ({auto_sys_detailed})"
entry_system_version.delete(0, tk.END)
entry_system_version.insert(0, full_system_version_display)
# entry_system_version.config(state='readonly')
# --- 填充预设的默认值 ---
# 登记时间
entry_today.delete(0, tk.END)
entry_today.insert(0, formatted_time) # Use the globally defined formatted_time
# 状态选择框 (Combobox) 的默认值
if ipguardinfo():
combo_ip_guard.set(DEFAULT_YES)
else:
combo_ip_guard.set(DEFAULT_NO)
combo_ntivirus_scan.set(DEFAULT_YES)
combo_antivirus_installed.set(DEFAULT_YES)
combo_usb_disabled.set(DEFAULT_YES)
if firewallinfo():
combo_firewall_enabled.set(DEFAULT_YES)
else:
combo_firewall_enabled.set(DEFAULT_NO)
# --- GUI 布局和样式 ---
def center_window(window):
"""
使 Tkinter 窗口在屏幕中心显示。
"""
window.update_idletasks() # 确保窗口尺寸是确定的
width = window.winfo_width()
height = window.winfo_height()
screen_width = window.winfo_screenwidth()
screen_height = window.winfo_screenheight()
x = (screen_width // 2) - (width // 2)
y = (screen_height // 2) - (height // 2)
window.geometry(f'{width}x{height}+{x}+{y}')
# --- GUI 主程序 ---
root = tk.Tk()
root.title("设备信息录入系统")
# --- 字体设置 ---
# 定义一个大写字体,用于标签和输入框
label_font_size = 12 # 调整字体大小
input_font_size = 12
button_font_size = 14
# 调整整个界面的字体大小(可选,如果需要的话)
style = ttk.Style()
style.configure("TLabel", font=("Arial", label_font_size))
style.configure("TEntry", font=("Arial", input_font_size))
style.configure("TButton", font=("Arial", button_font_size))
style.configure("TCombobox", font=("Arial", input_font_size))
# 主框架
main_frame = tk.Frame(root, padx=20, pady=20, bg="#f0f0f0") # 添加背景色
main_frame.pack(expand=True, fill="both")
# --- Grid 配置 ---
# configure column weights so entry widgets expand
main_frame.columnconfigure(1, weight=1) # Entry widgets will expand horizontally
# --- Row 0: 登记时间 ---
tk.Label(main_frame, text="登记时间:", font=("Arial", label_font_size)).grid(row=0, column=0, sticky="w", pady=5)
entry_today = tk.Entry(main_frame, width=40, font=("Arial", input_font_size), bg="#e0e0e0", relief="sunken") # 浅灰色背景,凹陷边框
entry_today.grid(row=0, column=1, sticky="ew", pady=5) # sticky="ew" 让entry随列扩展
# --- Row 1: IP地址 ---
tk.Label(main_frame, text="IP 地址:", font=("Arial", label_font_size)).grid(row=1, column=0, sticky="w", pady=5)
entry_ip_address = tk.Entry(main_frame, width=40, font=("Arial", input_font_size), bg="#e0e0e0", relief="sunken")
entry_ip_address.grid(row=1, column=1, sticky="ew", pady=5)
# --- Row 2: MAC 地址 ---
tk.Label(main_frame, text="MAC 地址:", font=("Arial", label_font_size)).grid(row=2, column=0, sticky="w", pady=5)
text_mac_address = tk.Text(main_frame, width=40, height=2, font=("Arial", input_font_size), wrap="word", borderwidth=2,bg="#e0e0e0", relief="sunken")
text_mac_address.grid(row=2, column=1, sticky="nsew", pady=5) #nsew for expand in all directions
# --- Row 3: 计算机名 ---
tk.Label(main_frame, text="计算机名:", font=("Arial", label_font_size)).grid(row=3, column=0, sticky="w", pady=5)
entry_hostname = tk.Entry(main_frame, width=40, font=("Arial", input_font_size), bg="#e0e0e0", relief="sunken")
entry_hostname.grid(row=3, column=1, sticky="ew", pady=5)
# --- Row 4: 系统类型 ---
tk.Label(main_frame, text="系统类型:", font=("Arial", label_font_size)).grid(row=4, column=0, sticky="w", pady=5)
entry_system_type = tk.Entry(main_frame, width=40, font=("Arial", input_font_size), bg="#e0e0e0", relief="sunken")
entry_system_type.grid(row=4, column=1, sticky="ew", pady=5)
# --- Row 5: 系统版本 ---
tk.Label(main_frame, text="系统版本:", font=("Arial", label_font_size)).grid(row=5, column=0, sticky="w", pady=5)
entry_system_version = tk.Entry(main_frame, width=40, font=("Arial", input_font_size), bg="#e0e0e0", relief="sunken")
entry_system_version.grid(row=5, column=1, sticky="ew", pady=5)
tk.Label(main_frame, text="厂商名称:", font=("Arial", label_font_size)).grid(row=6, column=0, sticky="w", pady=5)
entry_emp_vname = tk.Entry(main_frame, width=40, font=("Arial", input_font_size))
entry_emp_vname.grid(row=6, column=1, sticky="ew", pady=5)
# --- Row 6: 厂商姓名 ---
tk.Label(main_frame, text="厂商姓名:", font=("Arial", label_font_size)).grid(row=7, column=0, sticky="w", pady=5)
entry_emp_name = tk.Entry(main_frame, width=40, font=("Arial", input_font_size))
entry_emp_name.grid(row=7, column=1, sticky="ew", pady=5)
# --- Row 7: 厂商ID (原 emp) ---
tk.Label(main_frame, text="厂商简称:", font=("Arial", label_font_size)).grid(row=8, column=0, sticky="w", pady=5)
entry_emp_id = tk.Entry(main_frame, width=40, font=("Arial", input_font_size))
entry_emp_id.grid(row=8, column=1, sticky="ew", pady=5)
# --- Row 8: 厂商电话/联系方式 ---
tk.Label(main_frame, text="厂商电话:", font=("Arial", label_font_size)).grid(row=9, column=0, sticky="w", pady=5)
entry_area = tk.Entry(main_frame, width=40, font=("Arial", input_font_size))
entry_area.grid(row=9, column=1, sticky="ew", pady=5)
# --- Row 9: IT部门/第三方(如ICT DRI) ---
tk.Label(main_frame, text="ICT DRI:", font=("Arial", label_font_size)).grid(row=10, column=0, sticky="w", pady=5)
entry_DRI = tk.Entry(main_frame, width=40, font=("Arial", input_font_size))
entry_DRI.grid(row=10, column=1, sticky="ew", pady=5)
# --- Row 10: IP Guard ---
tk.Label(main_frame, text="IP Guard是否安装:", font=("Arial", label_font_size)).grid(row=11, column=0, sticky="w", pady=5)
combo_ip_guard = ttk.Combobox(main_frame, values=DEFAULT_SCAN_STATUS_OPTIONS, width=36, font=("Arial", input_font_size), state="readonly") # Match width of entry widgets
combo_ip_guard.grid(row=11, column=1, sticky="ew", pady=5)
combo_ip_guard.config(state=tk.DISABLED)
# --- Row 11: 病毒是否扫描 ---
tk.Label(main_frame, text="病毒扫描:", font=("Arial", label_font_size)).grid(row=12, column=0, sticky="w", pady=5)
combo_ntivirus_scan = ttk.Combobox(main_frame, values=DEFAULT_SCAN_STATUS_OPTIONS, width=36, font=("Arial", input_font_size), state="readonly")
combo_ntivirus_scan.grid(row=12, column=1, sticky="ew", pady=5)
combo_ntivirus_scan.config(state=tk.DISABLED)
# --- Row 12: 防毒软件是否安装 ---
tk.Label(main_frame, text="防毒软件:", font=("Arial", label_font_size)).grid(row=13, column=0, sticky="w", pady=5)
combo_antivirus_installed = ttk.Combobox(main_frame, values=DEFAULT_INSTALLED_STATUS_OPTIONS, width=36, font=("Arial", input_font_size), state="readonly")
combo_antivirus_installed.grid(row=13, column=1, sticky="ew", pady=5)
combo_antivirus_installed.config(state=tk.DISABLED)
# --- Row 13: USB 是否禁用 ---
tk.Label(main_frame, text="USB 禁用:", font=("Arial", label_font_size)).grid(row=14, column=0, sticky="w", pady=5)
combo_usb_disabled = ttk.Combobox(main_frame, values=DEFAULT_USB_STATUS_OPTIONS, width=36, font=("Arial", input_font_size), state="readonly")
combo_usb_disabled.grid(row=14, column=1, sticky="ew", pady=5)
combo_usb_disabled.config(state=tk.DISABLED)
# --- Row 14: 防火墙是否启用 ---
tk.Label(main_frame, text="防火墙是否启用:", font=("Arial", label_font_size)).grid(row=15, column=0, sticky="w", pady=5)
combo_firewall_enabled = ttk.Combobox(main_frame, values=DEFAULT_FIREWALL_STATUS_OPTIONS, width=36, font=("Arial", input_font_size), state="readonly")
combo_firewall_enabled.grid(row=15, column=1, sticky="ew", pady=5)
combo_firewall_enabled.config(state=tk.DISABLED)
# --- 提交按钮 ---
# 使用ttk.Button以获得更好的平台原生外观
submit_button = ttk.Button(main_frame, text="提 交", command=submit_data) # 减少了 width/height, 让它更灵活
submit_button.grid(row=16, column=0, columnspan=2, pady=20) # 增加了 pady
# --- 窗口尺寸和居中 ---
# 自动调整窗口大小以适应内容,然后居中
root.update_idletasks() # 必须在获取尺寸前调用
root.geometry("") # 允许 Tkinter 自动调整大小
center_window(root)
# --- 启动时填充自动字段 ---
populate_auto_fields()
# --- 运行主事件循环 ---
root.mainloop()