List控件的label文字过程闪动

<mx:List labelField="title" dataTipField="title" showDataTips="true" itemRenderer="mx.controls.Label">

labelField:显示的文字
dataTipField:当文字过长时,提示小框里显示的item字段
showDataTips:当文字过长时,显示提示小框
问题1:可以显示提示框,但是不断闪动
解决:itemRenderer="mx.controls.Label"


from:http://stevehealy.org/techblog/?p=23
import time import tkinter as tk from tkinter import messagebox, ttk, filedialog from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options import threading import subprocess import os import tempfile import atexit import json from datetime import datetime import sqlite3 from list import TestCaseTableWidget import pandas as pd import chardet import os class ListThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.daemon = True # 设置为守护线程,当主程序退出时,子线程也会退出 def run(self): # 在新线程中启动PyQt5应用 import sys from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv) window = TestCaseTableWidget() window.show() sys.exit(app.exec_()) class WebFormFiller: def __init__(self): self.driver = None self.chrome_process = None self.user_data_dir = None self.root = tk.Tk() self.root.title("网页表单自动填充工具") self.root.geometry("1200x800") self.root.resizable(True, True) # 数据库文件路径 self.db_file_path = "test_cases.db" # 初始化数据库 self.init_database() # 注册退出清理函数 # atexit.register(self.cleanup) # 创建主界面 self.create_widgets() def init_database(self): """初始化SQLite数据库""" try: conn = sqlite3.connect(self.db_file_path) cursor = conn.cursor() # 创建富文本数据表 cursor.execute(''' CREATE TABLE IF NOT EXISTS test_cases ( id INTEGER PRIMARY KEY AUTOINCREMENT, case_id TEXT UNIQUE, html_content TEXT, plain_text TEXT, created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') conn.commit() conn.close() print("数据库初始化成功") except Exception as e: print(f"数据库初始化失败: {e}") def create_widgets(self): # 创建主框架 main_frame = tk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 标题 title_label = tk.Label(main_frame, text="网页表单自动填充工具", font=("Arial", 18, "bold")) title_label.pack(pady=10) # 创建主水平布局容器 main_container = tk.Frame(main_frame) main_container.pack(fill=tk.BOTH, expand=True) # 左侧区域 - 表单数据和配置 left_frame = tk.Frame(main_container) left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5) # 右侧区域 - 日志和浏览器设置 right_frame = tk.Frame(main_container) right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5) # ========== 左侧区域内容 ========== # URL输入区域 url_frame = tk.LabelFrame(left_frame, text="网页设置", padx=10, pady=10) url_frame.pack(fill=tk.X, pady=5) tk.Label(url_frame, text="目标网页URL:", font=("Arial", 12, "bold")).pack(anchor="w") self.url_entry = tk.Entry(url_frame, width=60) self.url_entry.pack(fill=tk.X, pady=5) self.url_entry.insert(0, "https://jira-phone.mioffice.cn") # 添加文件选择框架(垂直布局) file_frame = tk.LabelFrame(left_frame, text="文件选择", padx=10, pady=10) file_frame.pack(fill=tk.X, pady=10) # 第一个文件选择框(旧文件) file1_frame = tk.Frame(file_frame) file1_frame.pack(fill=tk.X, pady=2) tk.Label(file1_frame, text="旧版本CSV文件:", font=("Arial", 10)).pack(side=tk.LEFT, padx=(0, 5)) self.old_file_entry = tk.Entry(file1_frame, width=40) self.old_file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) tk.Button(file1_frame, text="浏览", command=self.browse_old_file).pack(side=tk.LEFT) file2_frame = tk.Frame(file_frame) file2_frame.pack(fill=tk.X, pady=5) tk.Label(file2_frame, text="新版本CSV文件:", font=("Arial", 10)).pack(side=tk.LEFT, padx=(0, 5)) self.new_file_entry = tk.Entry(file2_frame, width=40) self.new_file_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) tk.Button(file2_frame, text="浏览", command=self.browse_new_file).pack(side=tk.LEFT) # 表单数据区域 - 水平布局 tk.Button(file2_frame, text="检索", command=self.start_compare_csv).pack(side=tk.LEFT) # 表单数据区域 - 水平布局 data_frame = tk.LabelFrame(left_frame, text="表单数据", padx=10, pady=10) data_frame.pack(fill=tk.BOTH, expand=True, pady=5) # # 序列号选择区域 serial_frame = tk.Frame(data_frame) serial_frame.pack(fill=tk.X, pady=5) # self.serial_combo = ttk.Combobox(serial_frame, width=30, state="normal") # self.serial_combo.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True) self.filtered_items = [] # 当前显示的过滤后选项 # 创建Combobox tk.Label(serial_frame, text="选择序列号:").pack(side=tk.LEFT, padx=5) self.serial_combo = ttk.Combobox(serial_frame,width=30,values=self.filtered_items,state="normal") self.serial_combo.pack(side=tk.LEFT, fill=tk.X, expand=True) # 绑定键盘事件 self.serial_combo.bind("<KeyRelease>", self.on_key_release) # 绑定选择事件 self.serial_combo.bind("<<ComboboxSelected>>", self.on_serial_changed) # 添加清除按钮 self.clear_btn = ttk.Button(serial_frame, text="×", width=2, command=self.clear_input) self.clear_btn.pack(side=tk.RIGHT, padx=(2, 0)) refresh_button = tk.Button(serial_frame, text="刷新", command=self.refresh_serial_list, bg="#2196F3", fg="white", font=("Arial", 8)) refresh_button.pack(side=tk.LEFT, padx=5) # 绑定下拉框选择事件 # self.serial_combo.bind('<<ComboboxSelected>>', self.on_serial_changed) # 文本插入区域 - 水平布局 text_inputs_frame = tk.Frame(data_frame) text_inputs_frame.pack(fill=tk.BOTH, expand=True, pady=5) # 文本插入1 text1_frame = tk.LabelFrame(text_inputs_frame, text="文本插入1", padx=5, pady=5) text1_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5) self.text_input1 = tk.Text(text1_frame, width=40, height=8, font=("Arial", 10)) text1_scrollbar = ttk.Scrollbar(text1_frame, orient="vertical", command=self.text_input1.yview) self.text_input1.configure(yscrollcommand=text1_scrollbar.set) self.text_input1.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) text1_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.text_input1.insert("1.0", "这是第一个文本插入区域的内容。") # 文本插入2 text2_frame = tk.LabelFrame(text_inputs_frame, text="文本插入2", padx=5, pady=5) text2_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5) self.text_input2 = tk.Text(text2_frame, width=40, height=8, font=("Arial", 10)) text2_scrollbar = ttk.Scrollbar(text2_frame, orient="vertical", command=self.text_input2.yview) self.text_input2.configure(yscrollcommand=text2_scrollbar.set) self.text_input2.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) text2_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) self.text_input2.insert("1.0", "这是第二个文本插入区域的内容。") # 配置区域 - 水平布局 config_frame = tk.Frame(left_frame) config_frame.pack(fill=tk.X, pady=5) # 字段映射配置 mapping_frame = tk.LabelFrame(config_frame, text="字段映射配置", padx=10, pady=10) mapping_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5) tk.Label(mapping_frame, text="字段映射:", font=("Arial", 9, "bold")).pack(anchor="w") # 创建字段映射表格 fields = [ ("字段1标识", "text_input1"), ("字段2标识", "text_input2") ] self.field_mappings = {} for i, (field_name, field_key) in enumerate(fields): row_frame = tk.Frame(mapping_frame) row_frame.pack(fill=tk.X, pady=2) tk.Label(row_frame, text=f"{field_name}:", width=12, anchor="w").pack(side=tk.LEFT) entry = tk.Entry(row_frame, width=20) entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True) # 设置默认的映射值 if field_key == "text_input1": entry.insert(0, "测试用例id") else: entry.insert(0, "描述") self.field_mappings[field_key] = entry # 富文本编辑器配置 rich_text_frame = tk.LabelFrame(config_frame, text="富文本编辑器设置", padx=10, pady=10) rich_text_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5) # TinyMCE配置 tinymce_frame = tk.Frame(rich_text_frame) tinymce_frame.pack(fill=tk.X, pady=5) self.auto_detect_tinymce = tk.BooleanVar(value=True) tk.Checkbutton(tinymce_frame, text="自动识别TinyMCE编辑器", variable=self.auto_detect_tinymce, font=("Arial", 9)).pack(anchor="w") self.tinymce_content_type = tk.StringVar(value="text") content_type_frame = tk.Frame(rich_text_frame) content_type_frame.pack(fill=tk.X, pady=5) tk.Label(content_type_frame, text="内容类型:", font=("Arial", 9)).pack(side=tk.LEFT) tk.Radiobutton(content_type_frame, text="纯文本", variable=self.tinymce_content_type, value="text", font=("Arial", 8)).pack(side=tk.LEFT, padx=5) tk.Radiobutton(content_type_frame, text="HTML格式", variable=self.tinymce_content_type, value="html", font=("Arial", 8)).pack(side=tk.LEFT, padx=5) # 自定义字符检测区域 char_detection_frame = tk.LabelFrame(config_frame, text="自定义字符检测", padx=10, pady=10) char_detection_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5) # 自定义字符输入 char_input_frame = tk.Frame(char_detection_frame) char_input_frame.pack(fill=tk.X, pady=5) tk.Label(char_input_frame, text="检测字符:").pack(anchor="w") self.char_entry = tk.Entry(char_input_frame, width=30) self.char_entry.pack(fill=tk.X, pady=2) self.char_entry.insert(0, "请填写检测字符") # 默认检测字符 # 检测间隔设置 interval_frame = tk.Frame(char_detection_frame) interval_frame.pack(fill=tk.X, pady=5) tk.Label(interval_frame, text="检测间隔(秒):").pack(side=tk.LEFT) self.interval_entry = tk.Entry(interval_frame, width=10) self.interval_entry.pack(side=tk.LEFT, padx=5) self.interval_entry.insert(0, "60") # 检测控制按钮 button_frame = tk.Frame(char_detection_frame) button_frame.pack(fill=tk.X, pady=5) self.start_detect_button = tk.Button(button_frame, text="开始检测", command=self.start_char_detection, bg="#2196F3", fg="white", font=("Arial", 9)) self.start_detect_button.pack(side=tk.LEFT, padx=2) self.stop_detect_button = tk.Button(button_frame, text="停止检测", command=self.stop_char_detection, bg="#f44336", fg="white", font=("Arial", 9)) self.stop_detect_button.pack(side=tk.LEFT, padx=2) # ========== 右侧区域内容 ========== # 检测日志区域 log_frame = tk.LabelFrame(right_frame, text="检测日志", padx=10, pady=10) log_frame.pack(fill=tk.BOTH, expand=True, pady=5) # 创建日志文本框和滚动条 self.log_text = tk.Text(log_frame, width=60, height=15, font=("Consolas", 9)) log_scrollbar = ttk.Scrollbar(log_frame, orient="vertical", command=self.log_text.yview) self.log_text.configure(yscrollcommand=log_scrollbar.set) self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) log_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 清空日志按钮 clear_log_button = tk.Button(log_frame, text="清空日志", command=self.clear_log, bg="#FF9800", fg="white", font=("Arial", 9)) clear_log_button.pack(side=tk.BOTTOM, pady=5) # 调试模式选项区域 debug_frame = tk.LabelFrame(right_frame, text="浏览器设置", padx=10, pady=10) debug_frame.pack(fill=tk.X, pady=5) # 调试端口设置 port_frame = tk.Frame(debug_frame) port_frame.pack(fill=tk.X, pady=5) tk.Label(port_frame, text="调试端口:").pack(side=tk.LEFT) self.port_entry = tk.Entry(port_frame, width=10) self.port_entry.pack(side=tk.LEFT, padx=5) self.port_entry.insert(0, "9222") # 自动填写选项 auto_options_frame = tk.Frame(debug_frame) auto_options_frame.pack(fill=tk.X, pady=5) self.auto_detect_var = tk.BooleanVar(value=True) tk.Checkbutton(auto_options_frame, text="自动探测表单字段", variable=self.auto_detect_var, font=("Arial", 9)).pack(side=tk.LEFT, padx=10) self.delay_fill_var = tk.BooleanVar(value=True) tk.Checkbutton(auto_options_frame, text="延迟填充(推荐)", variable=self.delay_fill_var, font=("Arial", 9)).pack(side=tk.LEFT, padx=10) # ========== 底部按钮区域 ========== # 按钮区域 button_frame = tk.Frame(main_frame) button_frame.pack(pady=20) # 第一行按钮 button_row1 = tk.Frame(button_frame) button_row1.pack(pady=5) self.start_button = tk.Button(button_row1, text="开始自动填充", command=self.start_filling, bg="#4CAF50", fg="white", font=("Arial", 12, "bold"), width=15, height=2) self.start_button.pack(side=tk.LEFT, padx=15) self.stop_button = tk.Button(button_row1, text="停止", command=self.stop_filling, bg="#f44336", fg="white", font=("Arial", 12, "bold"), width=15, height=2) self.stop_button.pack(side=tk.LEFT, padx=15) # 第二行按钮 button_row2 = tk.Frame(button_frame) button_row2.pack(pady=5) self.launch_browser_button = tk.Button(button_row2, text="启动浏览器", command=self.launch_browser, bg="#FF9800", fg="white", font=("Arial", 10, "bold"), width=10, height=1) self.launch_browser_button.pack(side=tk.LEFT, padx=5) self.test_button = tk.Button(button_row2, text="连接浏览器", command=self.connect_browser, bg="#2196F3", fg="white", font=("Arial", 10), width=10, height=1) self.test_button.pack(side=tk.LEFT, padx=5) self.test_button = tk.Button(button_row2, text="列表文件", command=self.DK, bg="#2196F3", fg="white", font=("Arial", 10), width=10, height=1) self.test_button.pack(side=tk.LEFT, padx=5) self.test_button = tk.Button(button_row2, text="探测表单", command=self.detect_form_fields, bg="#2196F3", fg="white", font=("Arial", 10), width=10, height=1) self.test_button.pack(side=tk.LEFT, padx=5) # 状态显示 self.status_var = tk.StringVar() self.status_var.set("准备就绪") status_label = tk.Label(main_frame, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W, bg="#e0e0e0", font=("Arial", 10)) status_label.pack(fill=tk.X, pady=5) # 进度条 self.progress = ttk.Progressbar(main_frame, mode='indeterminate') self.progress.pack(fill=tk.X, pady=5) # 字符检测控制变量 self.char_detection_running = False self.char_detection_timer = None # 初始化序列号列表 self.refresh_serial_list() self.all_case_ids = [] # 存储所有Case ID用于搜索 def on_key_release(self, event): """处理键盘释放事件,动态过滤选项""" # 获取当前输入框内容 current_text = self.serial_combo.get() # 过滤选项:只保留包含当前输入文本的选项 self.filtered_items = [ item for item in self.all_case_ids if current_text.lower() in item.lower() ] # 更新下拉列表 self.serial_combo["values"] = self.filtered_items # 如果过滤后还有选项,自动打开下拉列表 if self.filtered_items and event.keysym not in ["Return", "Escape"]: self.serial_combo.event_generate('<Down>') def on_select(self, event): """当选择下拉项后,更新输入框为完整内容""" selected = self.serial_combo.get() self.serial_combo.delete(0, tk.END) self.serial_combo.insert(0, selected) def clear_input(self): """清除输入框内容并重置下拉列表""" self.serial_combo.set("") self.serial_combo["values"] = self.all_case_ids self.filtered_items = self.all_case_ids def get(self): """获取当前选择的值""" return self.serial_combo.get() def set(self, value): """设置当前值""" self.serial_combo.set(value) serial_combo 控件输入要等文字显示在输入框中,才实现检索功能,下拉框显示搜索的数据
最新发布
12-03
import tkinter as tk from tkinter import messagebox, simpledialog, ttk import random import json import os from datetime import datetime, timedelta from pypinyin import lazy_pinyin, Style from threading import Thread import openpyxl from openpyxl.styles import Alignment, Font, PatternFill from openpyxl.utils import get_column_letter class VirtualKeyboard: """虚拟键盘类""" def __init__(self, parent, entry_widget=None): self.parent = parent self.entry = entry_widget self.window = None def show(self, entry_widget=None): if entry_widget: self.entry = entry_widget if self.window and self.window.winfo_exists(): self.window.lift() return self.window = tk.Toplevel(self.parent) self.window.title("虚拟键盘") self.window.geometry("600x250") self.window.resizable(False, False) self.window.attributes('-topmost', True) self.window.configure(bg='#f0f8ff') keys = [ ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '='], ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']'], ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', "'"], ['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/'] ] for i, row in enumerate(keys): frame = tk.Frame(self.window, bg='#f0f8ff') frame.pack(pady=2) for key in row: btn = tk.Button( frame, text=key, width=4, height=2, command=lambda k=key: self.insert_key(k), bg='#ffffff', fg='#333333', font=('Arial', 10) ) btn.pack(side=tk.LEFT, padx=1) func_frame = tk.Frame(self.window, bg='#f0f8ff') func_frame.pack(pady=2) tk.Button( func_frame, text="空格", width=8, height=2, command=lambda: self.insert_key(" "), bg='#4CAF50', fg='white' ).pack(side=tk.LEFT, padx=1) tk.Button( func_frame, text="删除", width=8, height=2, command=self.delete_key, bg='#f44336', fg='white' ).pack(side=tk.LEFT, padx=1) tk.Button( func_frame, text="关闭", width=8, height=2, command=self.window.destroy, bg='#607D8B', fg='white' ).pack(side=tk.LEFT, padx=1) def insert_key(self, key): if self.entry: self.entry.insert(tk.INSERT, key) def delete_key(self): if self.entry and self.entry.selection_present(): self.entry.delete(tk.SEL_FIRST, tk.SEL_LAST) else: pos = self.entry.index(tk.INSERT) if pos > 0: self.entry.delete(pos - 1) class ClassManager: """班级积分管理系统主类""" def __init__(self, root): self.root = root self.student_frames = {} self.root.title("13班积分管理系统") self.root.geometry("1200x800") self.root.resizable(False, False) self.root.configure(bg='#f0f8ff') # DPI 感知(仅 Windows) try: import ctypes ctypes.windll.shcore.SetProcessDpiAwareness(1) except: pass # 字体设置 self.title_font = ("微软雅黑", 20, "bold") self.button_font = ("微软雅黑", 10) self.text_font = ("微软雅黑", 9) self.small_font = ("微软雅黑", 8) # 颜色常量 self.BG_NORMAL = "white" self.BG_SELECTED = "#e3f2fd" self.COLOR_POSITIVE = "#4CAF50" self.COLOR_NEGATIVE = "#f44336" self.COLOR_NEUTRAL = "#333" self.selected_students = set() # 连续操作模式标志位 self.continuous_mode = False self.continuous_var = tk.BooleanVar(value=self.continuous_mode) # 加载用户设置 self.load_settings() # 登录验证 if not self.check_login_password(): self.root.destroy() return # 初始化数据 self.students = self.load_data() if not self.students: self.initialize_from_roster() self.admin_password = self.load_admin_password() # 构建拼音缓存 self.pinyin_cache = {} self.build_pinyin_cache() # 创建界面 self.create_widgets() self.update_display() # 绑定窗口大小变化事件(防抖) self.resize_after_id = None self.root.bind("<Configure>", self.on_window_resize) # 拖动变量 self.mini_window = None self.mini_drag_data = {"x": 0, "y": 0} # 上次保存时间(节流) self.last_save_time = 0 # 协议关闭 self.root.protocol("WM_DELETE_WINDOW", self.on_closing) def build_pinyin_cache(self): """构建姓名到拼音首字母和全拼的缓存""" for name in self.students: if not name: continue initials = ''.join([p[0].lower() for p in lazy_pinyin(name, style=Style.FIRST_LETTER)]) full_py = ''.join(lazy_pinyin(name, style=Style.NORMAL)).lower() self.pinyin_cache[name] = {'initials': initials, 'full_pinyin': full_py} def check_login_password(self): login_password = self.load_login_password() if not login_password: return self.setup_login_password() attempts = 3 while attempts > 0: password = self.ask_password("登录验证", "请输入登录密码:") if password == login_password: return True attempts -= 1 msg = f"密码错误,还剩{attempts}次机会" if attempts > 0 else "密码错误次数过多" messagebox.showerror("密码错误", msg, parent=self.root) if attempts == 0: return False return False def setup_login_password(self): password = self.ask_password("设置登录密码", "请设置登录密码:") if not password: return False confirm = self.ask_password("确认密码", "请再次输入:") if password != confirm: messagebox.showerror("错误", "两次输入不一致") return False self.save_login_password(password) messagebox.showinfo("成功", "登录密码设置成功") return True def load_login_password(self): try: if os.path.exists("login_password.json"): with open("login_password.json", "r", encoding="utf-8") as f: return json.load(f) except Exception as e: print("加载登录密码失败:", e) return None def save_login_password(self, pwd): with open("login_password.json", "w", encoding="utf-8") as f: json.dump(pwd, f, ensure_ascii=False) def on_closing(self): if messagebox.askokcancel("退出", "确定要退出吗?"): self.save_data_async() self.save_settings() # 保存用户设置 if self.mini_window and self.mini_window.winfo_exists(): self.mini_window.destroy() self.root.destroy() def load_admin_password(self): try: if os.path.exists("admin_password.json"): with open("admin_password.json", "r", encoding="utf-8") as f: return json.load(f) except Exception as e: print("加载管理密码失败:", e) return "12" def save_admin_password(self): with open("admin_password.json", "w", encoding="utf-8") as f: json.dump(self.admin_password, f, ensure_ascii=False) def change_login_password(self): old = self.ask_password("验证原密码", "请输入原密码:") current = self.load_login_password() if old != current: messagebox.showerror("错误", "原密码错误!") return new = self.ask_password("新密码", "请输入新密码:") if not new: return confirm = self.ask_password("确认", "请再输一次:") if new != confirm: messagebox.showerror("错误", "两次不一致!") return self.save_login_password(new) messagebox.showinfo("成功", "登录密码修改成功!") def change_admin_password(self): old = self.ask_password("验证原密码", "请输入管理密码:") if old != self.admin_password: messagebox.showerror("错误", "原密码错误!") return new = self.ask_password("新管理密码", "请输入两位数字:") if not new or len(new) != 2 or not new.isdigit(): messagebox.showerror("错误", "必须是两位数字!") return confirm = self.ask_password("确认", "请再输一次:") if new != confirm: messagebox.showerror("错误", "两次不一致!") return self.admin_password = new self.save_admin_password() messagebox.showinfo("成功", "管理密码修改成功!") def forget_login_password(self): admin = self.ask_password("验证管理密码", "请输入管理密码:") if admin != self.admin_password: messagebox.showerror("错误", "管理密码错误!") return new = self.ask_password("重置登录密码", "请输入新登录密码:") if not new: return confirm = self.ask_password("确认", "请再输一次:") if new != confirm: messagebox.showerror("错误", "两次不一致!") return self.save_login_password(new) messagebox.showinfo("成功", "登录密码已重置!") def ask_password(self, title, prompt): dialog = tk.Toplevel(self.root) dialog.title(title) dialog.geometry("400x200") dialog.transient(self.root) dialog.grab_set() dialog.configure(bg='#f0f8ff') tk.Label(dialog, text=prompt, font=self.text_font, bg='#f0f8ff').pack(pady=20) pwd_var = tk.StringVar() entry = tk.Entry(dialog, textvariable=pwd_var, show='*', font=self.text_font, width=20) entry.pack(pady=10) # 绑定虚拟键盘 entry.bind("<Button-1>", lambda e: VirtualKeyboard(dialog, entry).show()) result = [] def on_ok(): result.append(pwd_var.get()) dialog.destroy() def on_cancel(): result.append(None) dialog.destroy() btn_frame = tk.Frame(dialog, bg='#f0f8ff') btn_frame.pack(pady=20) tk.Button(btn_frame, text="确定", command=on_ok, bg='#4CAF50', fg='white', width=10).pack(side=tk.LEFT, padx=10) tk.Button(btn_frame, text="取消", command=on_cancel, bg='#f44336', fg='white', width=10).pack(side=tk.LEFT, padx=10) entry.focus_set() dialog.wait_window() return result[0] if result else None def initialize_from_roster(self): roster = [ "安琪儿", "白睦尧", "白沛妍", "鲍星焱", "边子轩", "常佳怡", "钞依冉", "崔浩然", "崔嘉诺", "高博程", "高方圆", "高海翔", "高瑞泽", "高硕", "高雅萍", "郭欣宇", "韩静舒", "贺佳萱", "贺予绘", "黄钰涵", "李佳倩", "李金格", "李俊辰", "梁家伊", "刘博宇", "刘锐宇", "刘思乔", "刘旭尧", "刘雅涵", "柳登程", "马涛涛", "孟家旭", "苗宇辰", "裴雨轩", "拓雨诺", "王嘉浩", "王婷雨", "王怡婷", "王梓", "谢孟芝", "徐茂瑞", "杨少轩", "杨雪琦", "尤宇琪", "尤子潇", "张皓茗", "张皓雅", "张家瑜", "张朴淳", "张瑞霞", "张雅琪", "赵雅彤", "赵勇杰", "郑梦茹" ] for name in roster: self.students[name] = {"score": 0, "history": []} self.save_data_throttled() def load_data(self): try: if os.path.exists("class_data.json"): with open("class_data.json", "r", encoding="utf-8") as f: data = json.load(f) # 兼容旧格式:{name: score} for k, v in data.items(): if isinstance(v, int): data[k] = {"score": v, "history": []} return data except Exception as e: print("加载数据失败:", e) return {} def save_data_throttled(self): now = datetime.now().timestamp() if hasattr(self, 'last_save_time') and now - self.last_save_time < 2: return self.save_data_async() self.last_save_time = now def save_data_async(self): def _save(): try: with open("class_data.json", "w", encoding="utf-8") as f: json.dump(self.students, f, ensure_ascii=False, indent=2) except Exception as e: print("异步保存失败:", e) Thread(target=_save, daemon=True).start() def create_widgets(self): top_frame = tk.Frame(self.root, bg='#3b5998', height=50) top_frame.pack(fill=tk.X) top_frame.pack_propagate(False) tk.Label(top_frame, text="13班积分管理系统", font=self.title_font, fg='white', bg='#3b5998').pack(side=tk.LEFT, padx=20, pady=8) tk.Button(top_frame, text="缩小", command=self.minimize_window, font=self.button_font, bg='#607D8B', fg='white').pack(side=tk.RIGHT, padx=10) self.manage_btn = tk.Button( top_frame, text="管理", command=self.show_management_menu, font=self.button_font, bg='#E91E63', fg='white' ) self.manage_btn.pack(side=tk.RIGHT, padx=10) button_frame = tk.Frame(self.root, bg='#f0f8ff') button_frame.pack(pady=5, fill=tk.X, padx=10) buttons = [ ("添加学生", self.add_student, '#4CAF50'), ("加分", self.show_add_points_menu, '#2196F3'), ("减分", self.show_subtract_points_menu, '#f44336'), ("随机抽选", self.random_select_multi, '#9C27B0'), ("积分历史", self.show_history, '#FF9800'), ("全选", self.select_all, '#607D8B'), ("取消全选", self.deselect_all, '#795548'), ("生成周报", self.generate_weekly_report, '#009688'), ] for i, (text, cmd, color) in enumerate(buttons): tk.Button( button_frame, text=text, command=cmd, width=10, height=1, font=self.button_font, bg=color, fg='white' ).grid(row=0, column=i, padx=4, pady=2) display_frame = tk.Frame(self.root, bg='#f0f8ff') display_frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=5) surname_frame = tk.Frame(display_frame, bg='#f0f8ff') surname_frame.pack(fill=tk.X, pady=(0, 3)) tk.Label(surname_frame, text="姓氏:", font=self.small_font, bg='#f0f8ff').pack(side=tk.LEFT) surnames = ["安", "白", "鲍", "边", "常", "钞", "崔", "高", "郭", "韩", "贺", "黄", "李", "梁", "刘", "柳", "马", "孟", "苗", "裴", "拓", "王", "谢", "徐", "尤", "张", "赵", "全部"] for s in surnames: tk.Button( surname_frame, text=s, font=("微软雅黑", 8), width=2, height=1, command=lambda x=s: self.filter_by_surname(x), bg="#e0e0e0" ).pack(side=tk.LEFT, padx=1) search_frame = tk.Frame(display_frame, bg='#f0f8ff') search_frame.pack(fill=tk.X, pady=(0, 5)) tk.Label(search_frame, text="搜索:", font=self.text_font, bg='#f0f8ff').pack(side=tk.LEFT) self.search_var = tk.StringVar() self.search_var.trace("w", self.on_search_change) search_entry = tk.Entry(search_frame, textvariable=self.search_var, font=self.text_font, width=30) search_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True) search_entry.bind("<Button-1>", lambda e: VirtualKeyboard(self.root, search_entry).show()) container = tk.Frame(display_frame, bg='#f0f8ff') container.pack(fill=tk.BOTH, expand=True) self.canvas = tk.Canvas(container, bg='#f0f8ff', highlightthickness=0) scrollbar = tk.Scrollbar(container, orient="vertical", command=self.canvas.yview) self.scrollable_frame = tk.Frame(self.canvas, bg='#f0f8ff') self.scrollable_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all"))) self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") self.canvas.configure(yscrollcommand=scrollbar.set) self.canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") status_bar = tk.Label( self.root, text="就绪", bd=1, relief=tk.SUNKEN, anchor=tk.W, height=2 ) status_bar.pack(side=tk.BOTTOM, fill=tk.X, padx=0, pady=0) def on_window_resize(self, event): if event.widget != self.root: return if self.resize_after_id: self.root.after_cancel(self.resize_after_id) self.resize_after_id = self.root.after(50, self.check_and_update_display) def check_and_update_display(self): try: current_width = self.canvas.winfo_width() if hasattr(self, '_last_canvas_width') and self._last_canvas_width == current_width: return self._last_canvas_width = current_width self.update_display() except tk.TclError: pass def filter_by_surname(self, surname): if surname == "全部": filtered = self.students else: filtered = {name: data for name, data in self.students.items() if name.startswith(surname)} self.display_filtered(filtered, f"【{surname}】相关学生") def on_search_change(self, *args): query = self.search_var.get().strip().lower() if not query: self.update_display() return filtered = {} for name, data in self.students.items(): info = self.pinyin_cache.get(name, {}) if (query in name.lower() or query in info.get('full_pinyin', '') or query == info.get('initials', '') or all(c in info.get('initials', '') for c in query)): filtered[name] = data self.display_filtered(filtered, f"匹配“{query}”的结果") def display_filtered(self, students_dict, desc="结果"): for widget in self.scrollable_frame.winfo_children(): widget.destroy() if not students_dict: lbl = tk.Label(self.scrollable_frame, text="无匹配学生", font=self.text_font, fg="#999") lbl.pack(pady=20) return sorted_items = sorted(students_dict.items(), key=lambda x: x[1]["score"], reverse=True) self._create_blocks(sorted_items) def _create_blocks(self, student_list): """ 创建学生展示块,支持高精度点击选择 """ if not student_list: return try: width = max(self.canvas.winfo_width(), 800) except tk.TclError: return blocks_per_row = 9 block_width = 105 block_height = 80 new_frames = {} for i, (name, data) in enumerate(student_list): row, col = divmod(i, blocks_per_row) is_selected = name in self.selected_students bg_color = self.BG_SELECTED if is_selected else self.BG_NORMAL highlight_color = "#1976D2" if is_selected else "gray" highlight_thickness = 2 if is_selected else 1 frame = None if name in self.student_frames: try: if self.student_frames[name].winfo_exists(): frame = self.student_frames[name] except tk.TclError: pass if frame is not None: # 复用现有 frame frame.grid(row=row, column=col, padx=3, pady=3, sticky="nsew") frame.config( bg=bg_color, highlightbackground=highlight_color, highlightthickness=highlight_thickness ) for child in frame.winfo_children(): child.config(bg=bg_color) else: # 创建新 frame frame = tk.Frame( self.scrollable_frame, width=block_width, height=block_height, relief=tk.RAISED, borderwidth=1, bg=bg_color, highlightbackground=highlight_color, highlightthickness=highlight_thickness ) frame.grid(row=row, column=col, padx=3, pady=3, sticky="nsew") frame.grid_propagate(False) # 添加序号 tk.Label(frame, text=f"{i + 1}", font=self.small_font, bg=bg_color, fg='#666').place(x=2, y=1) # 添加姓名 tk.Label( frame, text=name, font=self.text_font, bg=bg_color, wraplength=90, fg='#333' ).place(relx=0.5, y=22, anchor=tk.CENTER) # 添加分数 score = data["score"] score_color = (self.COLOR_POSITIVE if score > 0 else self.COLOR_NEGATIVE if score < 0 else self.COLOR_NEUTRAL) score_text = f"+{score}" if score > 0 else str(score) tk.Label( frame, text=score_text, font=("微软雅黑", 11, "bold"), bg=bg_color, fg=score_color ).place(relx=0.5, y=52, anchor=tk.CENTER) # ✅ 主要修改:仅对容器 frame 绑定点击 frame.bind("<Button-1>", self.make_click_handler(name)) # ❗阻止子控件拦截鼠标事件(实现“事件穿透”) for child in frame.winfo_children(): # 转发点击事件到父容器 child.bind("<Button-1>", lambda e, f=frame: f.event_generate("<Button-1>", x=e.x, y=e.y)) # 阻止按钮释放行为干扰 child.bind("<ButtonRelease-1>", lambda e: "break") # 去除手型光标,表明不可独立交互 child.configure(cursor="") new_frames[name] = frame def make_click_handler(self, name): """ 生成学生块的点击处理器 支持单击选中/取消,双击查看详情,防止子控件拦截事件 """ def handler(event): widget = event.widget if not widget.winfo_exists(): return # 防止重复调度 if hasattr(widget, '_click_after_id'): self.root.after_cancel(widget._click_after_id) def do_select(): # 清除调度标记 if hasattr(widget, '_click_after_id'): delattr(widget, '_click_after_id') # 切换选中状态 if name in self.selected_students: self.selected_students.remove(name) else: self.selected_students.add(name) # 更新 UI frame = self.student_frames.get(name) if frame and frame.winfo_exists(): bg_color = self.BG_SELECTED if name in self.selected_students else self.BG_NORMAL highlight = "#1976D2" if name in self.selected_students else "gray" thickness = 2 if name in self.selected_students else 1 frame.config(bg=bg_color, highlightbackground=highlight, highlightthickness=thickness) for child in frame.winfo_children(): child.config(bg=bg_color) self.save_data_throttled() self.update_display() # 确保界面同步 # 设置延时执行(防抖) widget._click_after_id = self.root.after(150, do_select) # 绑定双击查看详情(优先级更高) def on_double(e): if hasattr(widget, '_click_after_id'): self.root.after_cancel(widget._click_after_id) delattr(widget, '_click_after_id') self.show_student_detail(name) # 确保双击绑定只一次 if not hasattr(widget, '_double_bound'): widget.bind("<Double-Button-1>", on_double) widget._double_bound = True return handler def update_display(self): self.display_filtered(self.students, "全部学生") def add_student(self): name = simpledialog.askstring("添加学生", "请输入学生姓名:", parent=self.root) if not name: return if name in self.students: messagebox.showwarning("重复", f"学生 {name} 已存在!") return self.students[name] = {"score": 0, "history": []} initials = ''.join([p[0].lower() for p in lazy_pinyin(name, style=Style.FIRST_LETTER)]) full_py = ''.join(lazy_pinyin(name, style=Style.NORMAL)).lower() self.pinyin_cache[name] = {'initials': initials, 'full_pinyin': full_py} self.save_data_throttled() self.update_display() messagebox.showinfo("成功", f"已添加学生:{name}") def show_add_points_menu(self): self.adjust_points_with_buttons("加分", 1) def show_subtract_points_menu(self): self.adjust_points_with_buttons("减分", -1) def adjust_points_with_buttons(self, action, multiplier): if not self.selected_students: messagebox.showwarning("提示", "请先选择学生!", parent=self.root) return dialog = tk.Toplevel(self.root) dialog.title(action) dialog.geometry("440x220") dialog.resizable(False, False) dialog.transient(self.root) dialog.grab_set() dialog.configure(bg='#f0f8ff') tk.Label( dialog, text=f"请选择{action}分数:", font=self.text_font, bg='#f0f8ff' ).pack(pady=(15, 5)) button_frame = tk.Frame(dialog, bg='#f0f8ff') button_frame.pack(pady=10) values = [1, 2, 3, 4, 5, 10] colors = ['#4CAF50', '#2196F3', '#FF9800', '#9C27B0', '#607D8B', '#795548'] texts = [f"{v}分" for v in values] for i in range(len(values)): btn = tk.Button( button_frame, text=texts[i], font=self.button_font, width=8, height=2, bg=colors[i], fg='white', command=lambda v=values[i]: self.do_adjust_points(dialog, v * multiplier) ) btn.pack(side=tk.LEFT, padx=8) custom_frame = tk.Frame(dialog, bg='#f0f8ff') custom_frame.pack(pady=8) tk.Button( custom_frame, text="自定义", font=self.button_font, width=8, height=2, bg='#607D8B', fg='white', command=lambda: self.do_adjust_points_custom(dialog, multiplier) ).pack() tk.Button( dialog, text="取消", font=self.button_font, width=10, bg='#f44336', fg='white', command=dialog.destroy ).pack(pady=10) dialog.update_idletasks() x = self.root.winfo_x() + (self.root.winfo_width() // 2) - (dialog.winfo_width() // 2) y = self.root.winfo_y() + (self.root.winfo_height() // 2) - (dialog.winfo_height() // 2) dialog.geometry(f"+{x}+{y}") dialog.focus_force() def do_adjust_points(self, dialog, change): now = datetime.now().strftime("%Y-%m-%d %H:%M") for name in self.selected_students: self.students[name]["score"] += change self.students[name]["history"].append({ "time": now, "change": change, "type": "add" if change > 0 else "sub" }) if not self.continuous_mode: self.selected_students.clear() dialog.destroy() self.save_data_throttled() self.update_display() messagebox.showinfo("成功", f"{change:+} 分操作完成!", parent=self.root) def do_adjust_points_custom(self, dialog, multiplier): value = simpledialog.askinteger("自定义", "请输入分数:", parent=dialog, minvalue=1, maxvalue=100) if value is not None: self.do_adjust_points(dialog, value * multiplier) def random_select_multi(self): dialog = tk.Toplevel(self.root) dialog.title("随机抽选") dialog.geometry("320x180") dialog.resizable(False, False) dialog.transient(self.root) dialog.grab_set() dialog.configure(bg='#f0f8ff') tk.Label(dialog, text="请选择抽取人数:", font=self.text_font, bg='#f0f8ff').pack(pady=10) ctrl_frame = tk.Frame(dialog, bg='#f0f8ff') ctrl_frame.pack(pady=10) count_var = tk.IntVar(value=1) def decrease(): if count_var.get() > 1: count_var.set(count_var.get() - 1) def increase(): count_var.set(count_var.get() + 1) tk.Button(ctrl_frame, text="−", font=("Arial", 14), width=3, command=decrease, bg="#f44336", fg="white").pack(side=tk.LEFT) num_label = tk.Label( ctrl_frame, textvariable=count_var, font=("Arial", 16, "bold"), width=5, relief=tk.SUNKEN, bg="white" ) num_label.pack(side=tk.LEFT, padx=10) tk.Button(ctrl_frame, text="+", font=("Arial", 14), width=3, command=increase, bg="#4CAF50", fg="white").pack(side=tk.LEFT) def do_select(): n = count_var.get() total = len(self.students) if n > total: messagebox.showwarning("提示", f"学生总数只有 {total} 人,无法抽取 {n} 人!", parent=dialog) return candidates = list(self.students.keys()) selected = random.sample(candidates, n) self.selected_students.clear() for name in selected: self.selected_students.add(name) self.update_display() result = "\n".join([f"第{i + 1}位:{name}" for i, name in enumerate(selected)]) messagebox.showinfo(f"抽中 {n} 人", f"🎉 抽中名单:\n\n{result}", parent=dialog) dialog.destroy() btn_frame = tk.Frame(dialog, bg='#f0f8ff') btn_frame.pack(pady=20) tk.Button( btn_frame, text="确定抽取", font=self.button_font, width=10, bg='#4CAF50', fg='white', command=do_select ).pack(side=tk.LEFT, padx=10) tk.Button( btn_frame, text="取消", font=self.button_font, width=10, bg='#f44336', fg='white', command=dialog.destroy ).pack(side=tk.LEFT, padx=10) dialog.focus_force() def show_history(self): if not self.selected_students: messagebox.showwarning("提示", "请先选择学生!", parent=self.root) return if len(self.selected_students) > 1: messagebox.showwarning("提示", "只能查看单个学生的历史!", parent=self.root) return name = list(self.selected_students)[0] history = self.students[name]["history"] if not history: messagebox.showinfo("历史记录", f"{name} 暂无积分变动。", parent=self.root) return hist_text = "\n".join([f"[{r['time']}] {'+' if r['change'] > 0 else ''}{r['change']}" for r in history]) detail_win = tk.Toplevel(self.root) detail_win.title(f"{name} 的积分历史") detail_win.geometry("400x300") text = tk.Text(detail_win, font=self.text_font, wrap=tk.WORD) text.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) text.insert(tk.END, hist_text) text.config(state=tk.DISABLED) def select_all(self): self.selected_students = set(self.students.keys()) self.update_display() def deselect_all(self): self.selected_students.clear() self.update_display() def reset_points(self): if messagebox.askyesno("确认", "确定要重置所有学生的积分为0吗?", parent=self.root): now = datetime.now().strftime("%Y-%m-%d %H:%M") for name in self.students: old_score = self.students[name]["score"] if old_score != 0: self.students[name]["history"].append({ "time": now, "change": -old_score, "type": "reset" }) self.students[name]["score"] = 0 self.save_data_throttled() self.update_display() messagebox.showinfo("成功", "所有积分已重置为0", parent=self.root) def clear_all_history(self): if messagebox.askyesno("确认", "确定要清除所有历史记录吗?", parent=self.root): for name in self.students: self.students[name]["history"] = [] self.save_data_throttled() messagebox.showinfo("成功", "所有历史记录已清除", parent=self.root) def show_student_detail(self, name): data = self.students[name] history = "\n".join([ f"[{r['time']}] {'+' if r['change'] > 0 else ''}{r['change']}" for r in data["history"][-10:] ]) msg = f"姓名:{name}\n当前积分:{data['score']}\n\n最近10条记录:\n{history if history else '无'}" messagebox.showinfo("学生详情", msg, parent=self.root) def show_management_menu(self): menu = tk.Menu(self.root, tearoff=0, font=self.text_font) menu.add_command(label="修改登录密码", command=self.change_login_password) menu.add_command(label="修改管理密码", command=self.change_admin_password) menu.add_separator() menu.add_command(label="忘记登录密码", command=self.forget_login_password) menu.add_separator() menu.add_command(label="重置积分", command=self.reset_points) menu.add_command(label="清除全部历史", command=self.clear_all_history) menu.add_separator() menu.add_checkbutton( label="连续操作模式", variable=self.continuous_var, command=self.toggle_continuous_mode, font=self.text_font ) x = self.manage_btn.winfo_rootx() y = self.manage_btn.winfo_rooty() + self.manage_btn.winfo_height() menu.post(x, y) def toggle_continuous_mode(self): self.continuous_mode = self.continuous_var.get() def minimize_window(self): if self.mini_window and self.mini_window.winfo_exists(): self.mini_window.lift() return self.mini_window = tk.Toplevel(self.root) self.mini_window.title("13班") self.mini_window.geometry("100x40") self.mini_window.overrideredirect(True) self.mini_window.attributes('-topmost', True) screen_width = self.mini_window.winfo_screenwidth() self.mini_window.geometry(f"+{screen_width - 110}+10") self.mini_window.configure(bg='#3b5998') label = tk.Label( self.mini_window, text="班级管理", font=("微软雅黑", 12, "bold"), fg='white', bg='#3b5998', cursor="hand2" ) label.pack(fill=tk.BOTH, expand=True) label.bind("<ButtonPress-1>", self.start_mini_drag) label.bind("<B1-Motion>", self.on_mini_drag) label.bind("<Double-Button-1>", self.restore_window) label.bind("<Enter>", lambda e: label.config(bg='#4a69a0')) label.bind("<Leave>", lambda e: label.config(bg='#3b5998')) self.root.withdraw() def start_mini_drag(self, event): self.mini_drag_data["x"] = event.x self.mini_drag_data["y"] = event.y def on_mini_drag(self, event): x = self.mini_window.winfo_x() + (event.x - self.mini_drag_data["x"]) y = self.mini_window.winfo_y() + (event.y - self.mini_drag_data["y"]) self.mini_window.geometry(f"+{x}+{y}") def restore_window(self, event=None): if self.mini_window and self.mini_window.winfo_exists(): self.mini_window.destroy() self.mini_window = None self.root.deiconify() self.root.lift() def generate_weekly_report(self): week_ago = datetime.now() - timedelta(days=7) report_win = tk.Toplevel(self.root) report_win.title("积分周报") report_win.geometry("900x600") report_win.configure(bg='#f0f8ff') tk.Label(report_win, text="积分周报(本周变化总览)", font=self.title_font, bg='#f0f8ff').pack(pady=10) tree_frame = tk.Frame(report_win) tree_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) scroll = ttk.Scrollbar(tree_frame) scroll.pack(side=tk.RIGHT, fill=tk.Y) columns = ("name", "weekly_change", "changes") tree = ttk.Treeview(tree_frame, columns=columns, show="headings", yscrollcommand=scroll.set) tree.heading("name", text="姓名") tree.heading("weekly_change", text="净变化") tree.heading("changes", text="变动详情") tree.column("name", width=100, anchor=tk.CENTER) tree.column("weekly_change", width=80, anchor=tk.CENTER) tree.column("changes", width=400, anchor=tk.W) tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scroll.config(command=tree.yview) data = {} for name, student in self.students.items(): changes = [ r for r in student["history"] if datetime.strptime(r["time"], "%Y-%m-%d %H:%M") >= week_ago ] values = [f"{'+' if c['change'] > 0 else ''}{c['change']}" for c in changes] summary = " ".join(values) + " 分" if values else "无变动" net = sum(c["change"] for c in changes) data[name] = {"summary": summary, "net": net} for name in sorted(data, key=lambda x: data[x]["net"], reverse=True): info = data[name] tag = ("positive",) if info["net"] > 0 else ("negative",) if info["net"] < 0 else () tree.insert("", tk.END, values=(name, info["net"], info["summary"]), tags=tag) tree.tag_configure("positive", foreground=self.COLOR_POSITIVE) tree.tag_configure("negative", foreground=self.COLOR_NEGATIVE) btn_frame = tk.Frame(report_win, bg='#f0f8ff') btn_frame.pack(pady=10) tk.Button( btn_frame, text="导出 TXT", command=lambda: self.export_weekly_report_txt(data), bg='#4CAF50', fg='white', font=self.button_font, width=10 ).pack(side=tk.LEFT, padx=5) tk.Button( btn_frame, text="导出 Excel", command=lambda: self.export_weekly_report_excel(data), bg='#FF9800', fg='white', font=self.button_font, width=10 ).pack(side=tk.LEFT, padx=5) def export_weekly_report_txt(self, data): try: filename = f"周报_{datetime.now().strftime('%Y%m%d_%H%M')}.txt" with open(filename, "w", encoding="utf-8") as f: f.write("13班积分周报\n\n") for name, d in sorted(data.items(), key=lambda x: x[1]["net"], reverse=True): f.write(f"{name}: {d['summary']} (净变化: {d['net']})\n") messagebox.showinfo("成功", f"已导出至 {filename}") except Exception as e: messagebox.showerror("错误", str(e)) def export_weekly_report_excel(self, data): try: filename = f"周报_{datetime.now().strftime('%Y%m%d_%H%M')}.xlsx" wb = openpyxl.Workbook() ws = wb.active ws.title = "积分周报" cell = ws.cell(1, 1, "13班积分周报") cell.font = Font(size=16, bold=True, color="1a5fb4") ws.merge_cells('A1:C1') headers = ["姓名", "净变化", "详细变动"] for col_idx, header in enumerate(headers, 1): cell = ws.cell(2, col_idx, header) cell.font = Font(bold=True) cell.fill = PatternFill(start_color="d0ebff", end_color="d0ebff", fill_type="solid") ws.column_dimensions[get_column_letter(col_idx)].width = [12, 10, 50][col_idx - 1] for idx, (name, d) in enumerate(sorted(data.items(), key=lambda x: x[1]["net"], reverse=True), start=3): ws.cell(idx, 1, name) net_cell = ws.cell(idx, 2, d["net"]) net_cell.font = Font(color="006400" if d["net"] > 0 else "DC143C" if d["net"] < 0 else "000000") ws.cell(idx, 3, d["summary"]) for row in ws.iter_rows(min_row=2, max_row=len(data) + 2, min_col=1, max_col=3): for cell in row: cell.alignment = Alignment(vertical="center", wrap_text=True) wb.save(filename) messagebox.showinfo("成功", f"Excel 已导出至:\n{filename}") except Exception as e: messagebox.showerror("导出失败", f"无法创建文件:\n{str(e)}") def save_settings(self): settings = { "continuous_mode": self.continuous_mode } try: with open("settings.json", "w", encoding="utf-8") as f: json.dump(settings, f, ensure_ascii=False, indent=2) except Exception as e: print("保存设置失败:", e) def load_settings(self): try: if os.path.exists("settings.json"): with open("settings.json", "r", encoding="utf-8") as f: data = json.load(f) self.continuous_mode = data.get("continuous_mode", False) self.continuous_var.set(self.continuous_mode) except Exception as e: print("加载设置失败:", e) # 启动入口 if __name__ == "__main__": root = tk.Tk() app = ClassManager(root) root.mainloop() 减小选中延迟并直接给出修改后代码
09-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值