import tkinter as tk from tkinter import messagebox, ttk import random import sqlite3 import datetime from points_system import PointsSystem class AttendanceSystem: def __init__(self): # 创建主窗口 self.root = tk.Tk() self.root.title("课堂点名系统 - 完整版") self.root.geometry("800x600") # 初始化积分系统 self.points_system = PointsSystem() # 创建标签页 self.notebook = ttk.Notebook(self.root) self.notebook.pack(fill='both', expand=True, padx=10, pady=10) # 点名页面 self.create_attendance_tab() # 积分排名页面 self.create_ranking_tab() # 关于页面 self.create_about_tab() # 初始加载数据 self.load_students() def create_attendance_tab(self): """创建点名标签页""" self.attendance_frame = ttk.Frame(self.notebook) self.notebook.add(self.attendance_frame, text="课堂点名") # 标题 title_label = tk.Label(self.attendance_frame, text="课堂随机点名系统", font=("Arial", 18, "bold")) title_label.pack(pady=10) # 控制按钮框架 button_frame = tk.Frame(self.attendance_frame) button_frame.pack(pady=10) # 导入按钮 import_button = tk.Button(button_frame, text="导入学生名单", font=("Arial", 12), command=self.import_students, bg="lightgreen", width=15) import_button.grid(row=0, column=0, padx=5) # 随机点名按钮 self.random_button = tk.Button(button_frame, text="随机点名", font=("Arial", 12), command=self.random_pick, bg="lightblue", width=15) self.random_button.grid(row=0, column=1, padx=5) # 权重点名按钮 self.weighted_button = tk.Button(button_frame, text="权重点名", font=("Arial", 12), command=self.weighted_pick, bg="orange", width=15) self.weighted_button.grid(row=0, column=2, padx=5) # 结果显示 self.result_label = tk.Label(self.attendance_frame, text="请先导入学生名单", font=("Arial", 20, "bold"), fg="red", height=3) self.result_label.pack(pady=10) # 积分操作按钮 points_frame = tk.Frame(self.attendance_frame) points_frame.pack(pady=5) tk.Button(points_frame, text="重复问题+0.5", command=lambda: self.add_special_points("repeat_question"), width=12).grid(row=0, column=0, padx=2) tk.Button(points_frame, text="回答正确+2", command=lambda: self.add_special_points("answer_correct"), width=12).grid(row=0, column=1, padx=2) tk.Button(points_frame, text="回答错误-1", command=lambda: self.add_special_points("answer_wrong"), width=12).grid(row=0, column=2, padx=2) # 重置积分按钮 reset_frame = tk.Frame(self.attendance_frame) reset_frame.pack(pady=5) tk.Button(reset_frame, text="重置所有积分", command=self.reset_points, font=("Arial", 10), bg="lightcoral", width=15).pack() # 学生名单显示 list_frame = tk.Frame(self.attendance_frame) list_frame.pack(fill='both', expand=True, padx=10, pady=10) tk.Label(list_frame, text="学生名单", font=("Arial", 12, "bold")).pack() # 创建表格样式的名单显示 columns = ("学号", "姓名", "专业", "积分") self.tree = ttk.Treeview(list_frame, columns=columns, show="headings", height=8) for col in columns: self.tree.heading(col, text=col) self.tree.column(col, width=100) self.tree.pack(fill='both', expand=True) def create_ranking_tab(self): """创建积分排名标签页""" self.ranking_frame = ttk.Frame(self.notebook) self.notebook.add(self.ranking_frame, text="积分排名") # 标题 tk.Label(self.ranking_frame, text="学生积分排行榜", font=("Arial", 16, "bold")).pack(pady=10) # 按钮框架 button_frame = tk.Frame(self.ranking_frame) button_frame.pack(pady=5) # 刷新按钮 tk.Button(button_frame, text="刷新排名", command=self.show_ranking, font=("Arial", 12), bg="lightyellow", width=10).pack(side='left', padx=5) # 导出按钮 tk.Button(button_frame, text="导出CSV", command=self.export_data, font=("Arial", 12), bg="lightgreen", width=10).pack(side='left', padx=5) # 显示图表按钮 tk.Button(button_frame, text="显示图表", command=self.show_chart, font=("Arial", 12), bg="lightcoral", width=10).pack(side='left', padx=5) # 排名显示 self.ranking_text = tk.Text(self.ranking_frame, height=15, width=60, font=("Arial", 11)) self.ranking_text.pack(padx=10, pady=10, fill='both', expand=True) # 初始显示排名 self.show_ranking() def create_about_tab(self): """创建关于页面""" self.about_frame = ttk.Frame(self.notebook) self.notebook.add(self.about_frame, text="关于") # 标题 title_label = tk.Label(self.about_frame, text="课堂点名系统", font=("Arial", 20, "bold")) title_label.pack(pady=20) # 版本信息 info_text = """ 开发信息: • 版本:v1.0 • 开发者:单人开发 • 开发时间:2024年11月 • 技术栈:Python + Tkinter + SQLite 系统功能: ✓ 学生名单导入 ✓ 随机点名 ✓ 权重点名(积分低优先) ✓ 积分管理系统 ✓ 数据可视化 ✓ CSV导出 积分规则: • 正常点名:+1分 • 重复问题:+0.5分 • 回答正确:+2分 • 回答错误:-1分 • 权重点名:积分越低越容易被点 """ info_label = tk.Label(self.about_frame, text=info_text, font=("Arial", 11), justify="left") info_label.pack(pady=10) def import_students(self): """导入学生名单""" test_students = [ ("031902100", "张三", "计算机科学与技术"), ("031902101", "李四", "软件工程"), ("031902102", "王五", "计算机科学与技术"), ("031902103", "赵六", "软件工程"), ("031902104", "钱七", "计算机科学与技术"), ("031902105", "孙八", "软件工程"), ("031902106", "周九", "计算机科学与技术"), ("031902107", "吴十", "软件工程") ] # 使用积分系统的方法来操作数据库 conn = sqlite3.connect('students.db') cursor = conn.cursor() # 清空现有数据 cursor.execute("DELETE FROM students") # 插入测试数据 for student_id, name, major in test_students: cursor.execute( "INSERT INTO students (student_id, name, major) VALUES (?, ?, ?)", (student_id, name, major) ) conn.commit() conn.close() # 更新显示 self.load_students() messagebox.showinfo("导入成功", f"成功导入 {len(test_students)} 名学生!") self.show_ranking() def load_students(self): """加载学生名单到表格""" # 清空表格 for item in self.tree.get_children(): self.tree.delete(item) # 直接从数据库读取 conn = sqlite3.connect('students.db') cursor = conn.cursor() cursor.execute("SELECT student_id, name, major, points FROM students") students_data = cursor.fetchall() conn.close() for student_id, name, major, points in students_data: self.tree.insert("", "end", values=(student_id, name, major, points)) # 更新结果显示 if students_data: self.result_label.config(text="准备就绪,可以开始点名") else: self.result_label.config(text="请先导入学生名单") def random_pick(self): """完全随机点名""" if not self.points_system.has_students(): messagebox.showwarning("提示", "请先导入学生名单!") return # 直接从数据库读取 conn = sqlite3.connect('students.db') cursor = conn.cursor() cursor.execute("SELECT student_id, name FROM students") students = cursor.fetchall() conn.close() if not students: messagebox.showwarning("提示", "请先导入学生名单!") return student_id, name = random.choice(students) self.show_pick_result(student_id, name, "random") def weighted_pick(self): """权重随机点名(积分低的更容易被选)""" if not self.points_system.has_students(): messagebox.showwarning("提示", "请先导入学生名单!") return result = self.points_system.get_weighted_random_student() if not result: messagebox.showwarning("提示", "请先导入学生名单!") return student_id, name, points = result self.show_pick_result(student_id, name, "weighted") def show_pick_result(self, student_id, name, pick_type): """显示点名结果""" # 更新积分 points_added = self.points_system.add_points(student_id, "attendance") # 显示结果 result_text = f"{name} ({student_id})" if pick_type == "weighted": result_text += "\n[权重模式:积分低优先]" self.result_label.config(text=result_text) # 刷新显示 self.load_students() self.show_ranking() messagebox.showinfo("点名结果", f"被点到的同学是:{name}\n" f"学号:{student_id}\n" f"积分+{points_added}") def add_special_points(self, points_type): """添加特殊积分""" # 获取当前选中的学生 selection = self.tree.selection() if not selection: messagebox.showwarning("提示", "请先点名选择一个学生!") return # 获取选中学生的学号 item = self.tree.item(selection[0]) student_id = item['values'][0] # 添加积分 points_added = self.points_system.add_points(student_id, points_type) # 刷新显示 self.load_students() self.show_ranking() point_names = { "repeat_question": "重复问题", "answer_correct": "回答正确", "answer_wrong": "回答错误" } messagebox.showinfo("积分更新", f"{point_names[points_type]}\n" f"积分{points_added:+}") def reset_points(self): """重置积分确认""" if not self.points_system.has_students(): messagebox.showwarning("提示", "请先导入学生名单!") return if messagebox.askyesno("确认", "确定要重置所有学生的积分吗?"): self.points_system.reset_all_points() self.load_students() self.show_ranking() messagebox.showinfo("成功", "所有积分已重置为0") def show_ranking(self): """显示积分排名""" ranking = self.points_system.get_ranking(10) self.ranking_text.delete(1.0, tk.END) if not ranking: self.ranking_text.insert(1.0, "暂无数据,请先导入学生名单") return # 显示排名 header = "排名 姓名 学号 积分\n" header += "=" * 35 + "\n" self.ranking_text.insert(1.0, header) for i, (name, student_id, points) in enumerate(ranking, 1): rank_text = f"{i:2d} {name:8} {student_id} {points:3d}分\n" self.ranking_text.insert(tk.END, rank_text) def export_data(self): """导出数据到CSV""" try: filename = self.points_system.export_to_csv() messagebox.showinfo("导出成功", f"数据已导出到:\n{filename}") except Exception as e: messagebox.showerror("导出失败", f"导出时出错:{str(e)}") def show_chart(self): """显示积分图表""" try: # 尝试使用matplotlib import matplotlib.pyplot as plt students_data = self.points_system.get_all_students_data() if not students_data: messagebox.showwarning("提示", "没有数据可显示") return names = [student[0] for student in students_data] points = [student[1] for student in students_data] # 创建图表 plt.figure(figsize=(12, 6)) # 柱状图 plt.subplot(1, 2, 1) bars = plt.bar(names, points, color=['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7']) plt.title('学生积分柱状图', fontsize=14, fontweight='bold') plt.xlabel('学生姓名') plt.ylabel('积分') plt.xticks(rotation=45) # 在柱子上显示数值 for bar, point in zip(bars, points): plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.1, str(point), ha='center', va='bottom') # 饼图(前5名) plt.subplot(1, 2, 2) top_names = names[:5] top_points = points[:5] # 如果数据不足5个,使用所有数据 if len(top_names) < 5: top_names = names top_points = points colors = ['#FF9999', '#66B2FF', '#99FF99', '#FFD700', '#FFB6C1'] plt.pie(top_points, labels=top_names, autopct='%1.1f%%', colors=colors[:len(top_names)]) plt.title('积分分布饼图(前5名)', fontsize=14, fontweight='bold') plt.tight_layout() plt.show() except ImportError: # 如果matplotlib不可用,显示文本图表 self.show_text_chart() def show_text_chart(self): """显示文本格式的图表(matplotlib不可用时)""" students_data = self.points_system.get_all_students_data() if not students_data: messagebox.showwarning("提示", "没有数据可显示") return chart_window = tk.Toplevel(self.root) chart_window.title("积分图表") chart_window.geometry("600x400") tk.Label(chart_window, text="积分排行榜 - 文本图表", font=("Arial", 16, "bold")).pack(pady=10) # 创建文本显示区域 text_widget = tk.Text(chart_window, font=("Consolas", 10), width=80, height=20) text_widget.pack(padx=10, pady=10, fill='both', expand=True) # 生成文本图表 chart_text = "积分柱状图(文本版):\n" chart_text += "=" * 50 + "\n\n" max_points = max(student[1] for student in students_data) if students_data else 1 for name, points in students_data: # 计算柱状图长度(最大20个字符) bar_length = int((points / max_points) * 20) if max_points > 0 else 0 bar = "█" * bar_length chart_text += f"{name:6} [{points:2d}分] {bar}\n" chart_text += f"\n导出时间:{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" chart_text += f"总学生数:{len(students_data)}人" text_widget.insert(1.0, chart_text) text_widget.config(state='disabled') # 设为只读 def run(self): """启动程序""" self.root.mainloop() if __name__ == "__main__": print("=== 课堂点名系统启动 ===") app = AttendanceSystem() app.run()