import tkinter as tk
from tkinter import filedialog, messagebox, ttk
import pandas as pd
import math
# 全局变量存储文件路径
file_path = None
results_df = pd.DataFrame(columns=['年级', '班级', '语文', '数学', '英语', '语文教师', '数学教师', '英语教师'])
def open_file():
""" 打开文件并存储路径 """
global file_path
file_path = filedialog.askopenfilename(filetypes=[("Excel files", "*.xlsx")])
if file_path:
status_label.config(text=f"已选择文件: {file_path}")
else:
status_label.config(text="未选择任何文件")
def calculate_averages():
""" 计算平均分并写入新表 """
global results_df
if not file_path:
messagebox.showerror("错误", "请先选择成绩表")
return
try:
# 使用 xlrd 读取 Excel 文件
xls = pd.ExcelFile(file_path)
sheets = xls.sheet_names
# 创建新的 DataFrame 来存储结果
results_df = pd.DataFrame(columns=['年级', '班级', "人数",'语文', '语文教师', '数学', '数学教师', '英语', '英语教师'])
# 获取教师任课表
teacher_sheet = None
for sheet in sheets:
if '教师任课表' in sheet:
teacher_sheet = pd.read_excel(xls, sheet_name=sheet)
break
if teacher_sheet is None:
raise ValueError("缺少教师任课表,请检查文件")
# 处理每个工作表
for sheet in sheets:
if '教师任课表' in sheet:
continue
df = pd.read_excel(xls, sheet_name=sheet)
# 确保所需列存在
required_columns = ['年级', '班级', '语文', '数学', '英语']
if not all(col in df.columns for col in required_columns):
raise ValueError(f"缺少必要列,请检查{sheet}表")
# 按年级班级分组
grouped = df.groupby(['年级', '班级'])
# 替换空值为 0
df[required_columns[2:]] = df[required_columns[2:]].fillna(0)
for (grade, class_), group in grouped:
num_students = len(group)
if checkbox_var.get():
num=math.floor(num_students * 0.08)
print("扣除:",num)
else:
num=0
print("扣除:",num)
exclude_num = max(0, int(num)) # 至少排除一人
# 计算去除最低分后的平均分
avg_scores = group[required_columns[2:]].apply(
lambda x: x.nlargest(num_students - exclude_num).mean(), axis=0)
# 提取教师信息
chinese_teacher = get_teacher_name(teacher_sheet, grade, class_, '语文')
math_teacher = get_teacher_name(teacher_sheet, grade, class_, '数学')
english_teacher = get_teacher_name(teacher_sheet, grade, class_, '英语')
# 添加到结果 DataFrame
result_row = pd.DataFrame({
'年级': [grade],
'班级': [class_],
'人数':[num_students],
'语文': [avg_scores['语文']],
'数学': [avg_scores['数学']],
'英语': [avg_scores['英语']],
'语文教师': [chinese_teacher],
'数学教师': [math_teacher],
'英语教师': [english_teacher]
})
# 在拼接之前,检查并删除全是 NA 的列
result_row = result_row.dropna(axis=1, how='all')
results_df = pd.concat([results_df, result_row], ignore_index=True)
# 更新结果显示表格
update_results_table(results_df)
# 写入新表
# with pd.ExcelWriter(file_path, mode='a', engine='openpyxl') as writer:
# results_df.to_excel(writer, sheet_name='计算表', index=False)
#
# messagebox.showinfo("成功", "计算完成并已保存至新表")
except Exception as e:
messagebox.showerror("错误", f"发生错误: {str(e)}")
def get_teacher_name(teacher_sheet, grade, class_, subject):
""" 从教师任课表中获取指定年级班级和科目的教师姓名 """
row = teacher_sheet[(teacher_sheet['年级'] == grade) & (teacher_sheet['班级'] == class_)]
if not row.empty:
return row[subject].iloc[0]
return ""
def update_results_table(df):
""" 更新结果显示表格 """
# 清除旧数据
results_treeview.delete(*results_treeview.get_children())
# 获取 DataFrame 的列标题
columns = list(df.columns)
# 重新配置 Treeview 的列标题和显示的列
results_treeview.config(columns=columns)
# 为每一列设置标题和宽度
for col in columns:
results_treeview.heading(col, text=col)
# 根据列名设置合理宽度
if '名次' in col :
results_treeview.column(col, width=40, stretch=False) # 名次与分差列宽度较小
elif '教师' in col:
results_treeview.column(col, width=60, stretch=False) # 教师列
elif '平均分' in col or '分差' in col:
results_treeview.column(col, width=100, stretch=False) # 其他列
else:
results_treeview.column(col, width=40, stretch=False) # 其他列宽度较小
# 插入新的数据
for index, row in df.iterrows():
results_treeview.insert('', 'end', values=row.tolist())
def save_results():
""" 另存结果显示表格中的数据到新文件 """
global results_df
if results_df.empty:
messagebox.showerror("错误", "没有计算结果可以保存")
return
new_file_path = filedialog.asksaveasfilename(defaultextension=".xlsx", filetypes=[("Excel files", "*.xlsx")])
if not new_file_path:
return
try:
results_df.to_excel(new_file_path, index=False)
messagebox.showinfo("成功", "结果已保存至新文件")
except Exception as e:
messagebox.showerror("错误", f"保存失败: {str(e)}")
def calculate_rank_and_diff():
""" 计算名次与分差 """
global results_df
if results_df.empty:
messagebox.showerror("错误", "没有可供计算的数据")
return
def calculate_for_subject(df, subject_col):
""" 按学科列计算名次和分差 """
for grade in df['年级'].unique():
grade_df = df[df['年级'] == grade].copy()
sorted_df = grade_df.sort_values(by=subject_col, ascending=False)
sorted_df['名次'] = range(1, len(sorted_df) + 1)
max_score = sorted_df[subject_col].iloc[0]
sorted_df['分差'] = sorted_df[subject_col] - max_score
df.loc[df['年级'] == grade, f'{subject_col}名次'] = sorted_df['名次']
df.loc[df['年级'] == grade, f'{subject_col}分差'] = sorted_df['分差']
# 计算各科目的名次和分差
calculate_for_subject(results_df, '语文')
calculate_for_subject(results_df, '数学')
calculate_for_subject(results_df, '英语')
# 调整列顺序,将名次和分差列插入到相应的“平均分”列后面
columns_order = [
'年级', '班级', '人数',
'语文教师',
'语文', '语文名次', '语文分差',
'数学教师',
'数学', '数学名次', '数学分差',
'英语教师',
'英语', '英语名次', '英语分差'
]
results_df = results_df[columns_order]
# 更新结果显示表格
update_results_table(results_df)
# 创建主窗口
root = tk.Tk()
root.title("中心小学成绩表处理工具")
root.geometry("1000x800") # 固定窗口大小
# 按钮布局
button_frame = tk.Frame(root)
button_frame.pack(pady=10)
# 添加一个多选框,默认为不选中
checkbox_var = tk.BooleanVar() # 使用 BooleanVar 跟踪状态
checkbox_var.set(False) # 设置初始状态为未选中
checkbox = tk.Checkbutton(root, text="扣除人数", variable=checkbox_var)
checkbox.pack(pady=5)
open_button = tk.Button(button_frame, text="打开成绩表", command=open_file)
open_button.pack(side=tk.LEFT, padx=5)
calculate_button = tk.Button(button_frame, text="开始计算", command=calculate_averages)
calculate_button.pack(side=tk.LEFT, padx=5)
# 添加新的按钮用于名次与分差计算
rank_diff_button = tk.Button(button_frame, text="名次与分差", command=calculate_rank_and_diff)
rank_diff_button.pack(side=tk.LEFT, padx=5)
save_button = tk.Button(button_frame, text="结果另存", command=save_results)
save_button.pack(side=tk.LEFT, padx=5)
status_label = tk.Label(root, text="")
status_label.pack(pady=10)
# 结果显示表格和滚动条
tree_frame = tk.Frame(root)
tree_frame.pack(fill=tk.BOTH, expand=True)
# 创建滚动条
# 创建结果显示表格
results_treeview = ttk.Treeview(tree_frame, columns=['年级', '班级', '人数', '语文教师','语文', '语文名次', '语文分差', '数学教师','数学', '数学名次', '数学分差', '英语教师' '英语', '英语名次', '英语分差' ], show='headings')
for col in results_treeview['columns']:
results_treeview.heading(col, text=col)
results_treeview.column(col, width=100)
# 创建滚动条
scroll_y = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL, command=results_treeview.yview)
scroll_x = ttk.Scrollbar(tree_frame, orient=tk.HORIZONTAL, command=results_treeview.xview)
# 配置滚动条
results_treeview.configure(yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set)
#
# # 设置 Treeview
# results_treeview = ttk.Treeview(tree_frame, columns=['年级', '班级', '人数', '语文平均分', '语文教师', '数学平均分', '数学教师', '英语平均分', '英语教师'],
# show='headings',
# yscrollcommand=scroll_y.set,
# xscrollcommand=scroll_x.set)
#
# # 设置每列宽度
# for col in results_treeview['columns']:
# results_treeview.heading(col, text=col)
# results_treeview.column(col, width=150, stretch=False)
# scroll_y = ttk.Scrollbar(tree_frame, orient=tk.VERTICAL, command=results_treeview.yview)
# scroll_x = ttk.Scrollbar(tree_frame, orient=tk.HORIZONTAL, command=results_treeview.xview)
#
#
# 将滚动条与 Treeview 绑定
scroll_y.pack(side=tk.RIGHT, fill=tk.Y)
scroll_x.pack(side=tk.BOTTOM, fill=tk.X)
results_treeview.pack(fill=tk.BOTH, expand=True)
# 设置 Treeview 样式
style = ttk.Style()
style.configure('Treeview', rowheight=30, fieldbackground='grey', background='white', borderwidth=4, relief='solid')
# 运行主循环
root.mainloop()