You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

465 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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()