体育实测成绩转换成百分数的代码。添加了优秀率等计算

import tkinter as tk
from tkinter import filedialog, messagebox
import openpyxl
import re
import os  # 新增os模块导入
from openpyxl import Workbook
from tkinter import ttk  # 新增ttk导入
current_file_path = ""
time_based_projects = {'50米', '耐力跑'}
# 在文件开头添加新的导入
from tkinter import scrolledtext
# 在全局范围定义按钮对象
export_btn = None  # 新增全局变量声明
open_btn = None
export_dist_btn = None

# 新增:创建主界面表格
# def create_views_table(root):
#     frame = tk.Frame(root)
#     frame.pack(pady=10)
#
#     # 创建Treeview组件
#     tree = ttk.Treeview(frame, height=10)
#     tree["columns"] = ("序号", "班级", "实在人数", "检测人数", "优秀人数", "优秀率",
#                        "良好人数", "良好率", "合格人数", "合格率", "不合格人数", "不合格率", "总分合计")
#
#     # 设置列属性
#     tree.column("#0", width=0, stretch=tk.NO)
#     columns = [
#         ("序号", 50), ("班级", 80), ("实在人数", 80), ("检测人数", 80),
#         ("优秀人数", 80), ("优秀率", 80), ("良好人数", 80), ("良好率", 80),
#         ("合格人数", 80), ("合格率", 80), ("不合格人数", 80), ("不合格率", 80), ("总分合计", 100)
#     ]
#     for col, width in columns:
#         tree.column(col, width=width, anchor='center')
#         tree.heading(col, text=col)
#
#     # 添加滚动条
#     vsb = tk.Scrollbar(frame, orient="vertical", command=tree.yview)
#     tree.configure(yscrollcommand=vsb.set)
#
#     tree.grid(row=0, column=0, sticky='nsew')
#     vsb.grid(row=0, column=1, sticky='ns')
#     return tree
# 修改后的create_views_table函数
def create_views_table(parent):
    frame = ttk.Frame(parent)
    frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

    # 创建带样式的Treeview
    style = ttk.Style()
    style.configure("Treeview.Heading", font=('微软雅黑', 10, 'bold'))
    style.configure("Treeview", font=('微软雅黑', 9), rowheight=25)

    columns = ("序号", "班级", "实在人数", "检测人数", "优秀人数", "优秀率",
               "良好人数", "良好率", "合格人数", "合格率", "不合格人数", "不合格率", "总分合计")

    tree = ttk.Treeview(frame, columns=columns, show="headings", selectmode="browse")

    # 配置列
    col_widths = [50, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 80, 100]
    for col, width in zip(columns, col_widths):
        tree.heading(col, text=col)
        tree.column(col, width=width, anchor='center')

    # 添加滚动条
    vsb = ttk.Scrollbar(frame, orient="vertical", command=tree.yview)
    hsb = ttk.Scrollbar(frame, orient="horizontal", command=tree.xview)
    tree.configure(yscrollcommand=vsb.set, xscrollcommand=hsb.set)

    # 布局
    tree.grid(row=0, column=0, sticky="nsew")
    vsb.grid(row=0, column=1, sticky="ns")
    hsb.grid(row=1, column=0, sticky="ew")

    # 配置网格权重
    frame.grid_rowconfigure(0, weight=1)
    frame.grid_columnconfigure(0, weight=1)

    return tree

# 新增:计算成绩分布
def calculate_distribution():
    #添加弹出消息框,提醒用户确定手工处理过上面处理后的文件
    messagebox.showinfo("提示", "请确认已手工处理,并检查了过上一步生成的文件!请查看操作说明中第二步★中的提示。")

    global export_dist_btn
    global distribution_data
    file_path = filedialog.askopenfilename(filetypes=[("Excel文件", "*.xlsx")])
    if not file_path:
        return

    try:
        wb = openpyxl.load_workbook(file_path)
        sheet = wb.worksheets[0]
    except Exception as e:
        messagebox.showerror("错误", f"文件打开失败:{str(e)}")
        return

    # 确定列索引
    header = [cell.value for cell in sheet[1]]
    try:
        class_col = header.index("班级") + 1
        total_score_col = header.index("合计") + 1  # 按列名定位合计列
        score_cols = [i + 1 for i, h in enumerate(header) if "得分" in str(h)]
    except ValueError:
        messagebox.showerror("错误", "表格必须包含'班级'和'合计'列!")
        return

    # 统计班级数据
    class_data = {}
    # 先统计实在人数(班级列出现次数)
    for row in sheet.iter_rows(min_row=2):
        class_name = row[class_col - 1].value
        if class_name:
            if class_name not in class_data:
                class_data[class_name] = {
                    'total': 0,
                    'valid': 0,
                    'scores': [],
                    'grades': {'优秀': 0, '良好': 0, '合格': 0, '不合格': 0}
                }
            class_data[class_name]['total'] += 1

    # 再统计有效数据
    for row in sheet.iter_rows(min_row=2):
        class_name = row[class_col - 1].value
        if not class_name:
            continue

        # 检测有效数据
        valid = all(row[col - 1].value is not None for col in score_cols)
        if valid:
            class_data[class_name]['valid'] += 1
            try:
                total_score = float(row[total_score_col - 1].value)
            except:
                continue

            # 统计等级(修正区间判断)
            if total_score >= 90:
                class_data[class_name]['grades']['优秀'] += 1
            elif 80 <= total_score < 90:  # 良好:80<=x<90
                class_data[class_name]['grades']['良好'] += 1
            elif 60 <= total_score < 80:  # 合格:60<=x<80
                class_data[class_name]['grades']['合格'] += 1
            else:  # 不合格:x<60
                class_data[class_name]['grades']['不合格'] += 1

    # 生成最终数据
    distribution_data = []
    for idx, (class_name, data) in enumerate(sorted(class_data.items()), 1):
        valid_count = data['valid']
        grades = data['grades']

        distribution_data.append([
            idx,
            class_name,
            data['total'],  # 实在人数(班级出现次数)
            valid_count,
            grades['优秀'],
            f"{grades['优秀'] / valid_count:.2%}" if valid_count else "0.00%",
            grades['良好'],
            f"{grades['良好'] / valid_count:.2%}" if valid_count else "0.00%",
            grades['合格'],
            f"{grades['合格'] / valid_count:.2%}" if valid_count else "0.00%",
            grades['不合格'],
            f"{grades['不合格'] / valid_count:.2%}" if valid_count else "0.00%",
            sum(data['scores'])
        ])

    # 更新Treeview
    views.delete(*views.get_children())
    for item in distribution_data:
        views.insert("", "end", values=item)

    export_dist_btn.config(state=tk.NORMAL)


# 新增:导出成绩分布
def export_distribution():
    if not distribution_data:
        messagebox.showwarning("警告", "没有可导出的数据!")
        return

    file_path = filedialog.asksaveasfilename(
        defaultextension=".xlsx",
        filetypes=[("Excel文件", "*.xlsx")]
    )
    if not file_path:
        return

    wb = Workbook()
    ws = wb.active
    ws.title = "成绩分布"

    # 写入标题行
    headers = ["序号", "班级", "实在人数", "检测人数", "优秀人数", "优秀率",
               "良好人数", "良好率", "合格人数", "合格率", "不合格人数", "不合格率", "总分合计"]
    ws.append(headers)

    # 写入数据
    for row in distribution_data:
        ws.append(row)

    # 保存文件
    try:
        wb.save(file_path)
        messagebox.showinfo("成功", f"文件已保存到:{file_path}")
    except Exception as e:
        messagebox.showerror("错误", f"保存失败:{str(e)}")
def calculate_score(student_value, project_name, standard_scores):
    """根据评分标准计算得分(修正倒序项目处理)"""
    try:
        student_value = float(student_value)
        print(f"开始计算项目 {project_name},原始值:{student_value}")
    except:
        return None

    is_time_based = project_name in time_based_projects
    # 修改排序方式:时间类项目按数值升序排列(数值越小越好)
    sorted_scores = sorted(standard_scores, key=lambda x: x[1], reverse=not is_time_based)

    if not sorted_scores:
        return None

    # 修正边界值判断逻辑
    if is_time_based:
        # 时间类:数值越小越好
        if student_value <= sorted_scores[0][1]:  # 优于最高标准
            return sorted_scores[0][0]
        if student_value >= sorted_scores[-1][1]:  # 差于最低标准
            return sorted_scores[-1][0]
    else:
        # 普通项目:数值越大越好
        if student_value >= sorted_scores[0][1]:  # 优于最高标准
            return sorted_scores[0][0]
        if student_value <= sorted_scores[-1][1]:  # 差于最低标准
            return sorted_scores[-1][0]

    # 修正区间查找逻辑
    lower_score, upper_score = None, None
    for i in range(len(sorted_scores) - 1):
        current = sorted_scores[i]
        next_val = sorted_scores[i + 1]

        if is_time_based:
            # 时间类:检查是否在 current <= value <= next_val 区间
            if current[1] <= student_value <= next_val[1]:
                lower_score = current  # 实际对应更高分数
                upper_score = next_val  # 实际对应更低分数
                break
        else:
            # 普通项目:检查是否在 next_val <= value <= current 区间
            if next_val[1] <= student_value <= current[1]:
                lower_score = current
                upper_score = next_val
                break

    if not lower_score or not upper_score:
        return None

    # 修正插值计算逻辑
    if is_time_based:
        # 时间类:数值越大分数越低
        score_diff = lower_score[0] - upper_score[0]
        val_diff = upper_score[1] - lower_score[1]
        ratio = (student_value - lower_score[1]) / val_diff
        final_score = lower_score[0] - ratio * score_diff
    else:
        # 普通项目:数值越大分数越高
        score_diff = lower_score[0] - upper_score[0]
        val_diff = lower_score[1] - upper_score[1]
        ratio = (student_value - upper_score[1]) / val_diff
        final_score = upper_score[0] + ratio * score_diff

    print(f"计算明细:{lower_score} -> {upper_score} | 比率:{ratio:.2f} | 原始分:{final_score:.1f}")
    return int(final_score)


def get_standard_scores(wb, sheet_name, grade):
    """获取评分标准数据"""
    print(f"\n正在获取评分标准:{sheet_name} 年级:{grade}")
    if sheet_name not in wb.sheetnames:
        print(f"警告:找不到评分表 {sheet_name}")
        return None

    sheet = wb[sheet_name]
    grade_col = None

    # 查找年级列
    header = next(sheet.iter_rows(min_row=1, max_row=1, values_only=True))
    print("评分表标题行:", header)
    for idx, col in enumerate(header):
        if col == grade:
            grade_col = idx
            break

    if grade_col is None:
        print(f"在 {sheet_name} 中找不到年级列:{grade}")
        return None
    print(f"找到 {grade} 在第 {grade_col + 1} 列")

    # 读取数据
    scores = []
    for row_idx, row in enumerate(sheet.iter_rows(min_row=2, values_only=True), start=2):
        try:
            score = float(row[0])
            value = float(row[grade_col])
            scores.append((score, value))
            print(f"行 {row_idx}: 分数 {score} → 值 {value}")
        except Exception as e:
            print(f"行 {row_idx} 数据格式错误:{row}")
            continue

    print(f"共读取到 {len(scores)} 条有效评分标准")
    return scores


def export_scores():
    global current_file_path
    if not current_file_path:
        messagebox.showwarning("警告", "请先打开成绩文件!")
        return

    print("\n" + "=" * 50)
    print("开始导出成绩计算...")

    try:
        print(f"正在加载文件:{current_file_path}")
        wb = openpyxl.load_workbook(current_file_path)
    except Exception as e:
        messagebox.showerror("错误", f"文件打开失败:{str(e)}")
        return

    # 验证总表
    total_sheets = [s for s in wb.sheetnames if re.match(r'^[一二三四五六]年级_总表$', s)]
    if not total_sheets:
        messagebox.showerror("错误", "未找到有效的总表!")
        return
    print(f"找到总表:{total_sheets[0]}")

    total_sheet = wb[total_sheets[0]]

    # 解析年级
    grade_match = re.match(r'^(\D)年级_总表$', total_sheets[0])
    if not grade_match:
        messagebox.showerror("错误", "总表名称格式不正确!")
        return
    grade_cn = grade_match.group(1)
    grade = f"{grade_cn}年级"
    print(f"当前年级:{grade}")

    # 项目配置
    projects = [
        {'name': '肺活量', 'val_col': 6, 'score_col': 7},
        {'name': '50米', 'val_col': 8, 'score_col': 9},
        {'name': '坐位体前屈', 'val_col': 10, 'score_col': 11},
        {'name': '跳绳', 'val_col': 12, 'score_col': 13},
        {'name': '仰卧起坐', 'val_col': 14, 'score_col': 15},
        {'name': '耐力跑', 'val_col': 16, 'score_col': 17},
    ]
    print("项目配置:")
    for p in projects:
        print(f"{p['name']}: 值列 {p['val_col'] + 1} → 分列 {p['score_col'] + 1}")

    # 遍历数据行
    print("\n开始处理学生数据:")
    for row_idx, row in enumerate(total_sheet.iter_rows(min_row=2), start=2):
        try:
            name = row[2].value
            gender = row[3].value
            print(f"\n处理第 {row_idx} 行:{name} ({gender})")

            if gender not in ['男', '女']:
                print(f"性别异常:{gender},跳过此行")
                continue

            for project in projects:
                val_cell = row[project['val_col']]
                score_cell = row[project['score_col']]

                if not val_cell.value:
                    print(f"{project['name']} 无数据,跳过")
                    continue

                print(f"\n处理项目:{project['name']}")
                print(f"原始值:{val_cell.value}(单元格 {val_cell.coordinate})")

                sheet_name = f"{gender}生_{project['name']}"
                standard = get_standard_scores(wb, sheet_name, grade)

                if not standard:
                    print(f"缺少评分标准,跳过")
                    continue

                final_score = calculate_score(val_cell.value, project['name'], standard)
                if final_score is not None:
                    print(f"写入分数:{final_score} → {score_cell.coordinate}")
                    score_cell.value = final_score
                else:
                    print("计算失败,保持原值")

        except Exception as e:
            print(f"处理第 {row_idx} 行时发生错误:{str(e)}")
            continue

    # 保存到新文件
    try:
        new_file_path = os.path.splitext(current_file_path)[0] + "_处理后.xlsx"
        print(f"\n正在保存到新文件:{new_file_path}")
        wb.save(new_file_path)
        messagebox.showinfo("完成", f"成绩计算完成!\n已保存到:{new_file_path}")
    except Exception as e:
        messagebox.showerror("错误", f"文件保存失败:{str(e)}")




def open_excel_file():
    global export_btn  # 明确声明使用全局变量
    global current_file_path
    file_path = filedialog.askopenfilename(
        filetypes=[("Excel文件", "*.xlsx")],
        title="选择成绩文件"
    )
    if not file_path:
        return

    current_file_path = file_path
    try:
        wb = openpyxl.load_workbook(file_path)
        messagebox.showinfo("成功", "文件已加载,可以开始计算!")
        export_btn.config(state=tk.NORMAL)
    except Exception as e:
        messagebox.showerror("错误", f"文件打开失败:{str(e)}")



#以下为检查代码
def check_excel_file():
    global open_btn  # 明确声明使用全局变量
    file_path = filedialog.askopenfilename(
        filetypes=[("Excel文件", "*.xlsx")],
        title="选择成绩文件"
    )
    if not file_path:
        return

    try:
        wb = openpyxl.load_workbook(file_path)
    except Exception as e:
        messagebox.showerror("错误", f"文件打开失败:{str(e)}")
        return

    # 检查总表
    grade_chinese = ['一', '二', '三', '四', '五', '六']
    total_sheets = [s for s in wb.sheetnames
                    if re.match(f'^[{"".join(grade_chinese)}]年级_总表$', s)]
    print(f"总表:{total_sheets}")

    if  total_sheets:
        if validate_number_format(wb[total_sheets[0]]):
            open_btn.config(state=tk.NORMAL)


def validate_number_format(sheet):
    error_rows = []
    for row_idx, row in enumerate(sheet.iter_rows(min_row=2), start=2):  # 从第2行开始
        for cell in row[4:]:  # 前3列为基本信息,从第4列开始验证
            if cell.value is None:
                continue
            if not isinstance(cell.value, (int, float)):
                error_rows.append(row_idx)
                break

    if error_rows:
        message = f"总表中以下行包含非数字数据:\n{', '.join(map(str, error_rows))}"
        messagebox.showwarning("格式错误", message)
        return False
    else:
        messagebox.showinfo("成功", "数据格式正确!")
        return True


# 在创建GUI部分添加说明面板
def create_gui():
    global open_btn, export_btn ,export_dist_btn # 新增:声明使用全局变量
    root = tk.Tk()
    root.title("体育成绩管理系统 v2.0")
    root.geometry("1200x800")

    # 创建说明面板
    instruction_frame = ttk.LabelFrame(root, text="操作说明", padding=10)
    instruction_frame.pack(fill=tk.X, padx=10, pady=5)

    # 创建带滚动条的说明文本
    instr_text = scrolledtext.ScrolledText(
        instruction_frame,
        wrap=tk.WORD,
        height=8,
        font=('微软雅黑', 10),
        foreground="#444"
    )
    instr_text.pack(fill=tk.BOTH, expand=True)

    # 插入格式化说明内容
    instructions = """【操作流程指南】

1. 数据预处理阶段:
   > 多次点击“检查成绩文件”按钮,直到没有错误弹窗
   ※ 手工自动检查:性别列是否为空。数据格式是否正确
   ※ 必须处理完所有错误才能进行下一步

2. 成绩计算阶段:
   ① 点击“打开成绩文件”选择处理后的文件
   ② 点击“导出计算结果”生成带合计列的新文件
   ★ 重要操作:在生成的新文件中:
     - 检查所有"得分"列必须为数字
     - 合计列如果使用公式,请复制→粘贴为数值覆盖原列
     - 保存文件后才能进行下一步

3. 统计分析阶段:
   ① 点击“计算成绩分布”选择上一步保存的文件
   ② 在下方表格中核对预览结果
   ③ 确认无误后点击“导出成绩分布”生成最终报告

【注意事项】
● 红色文字标注的步骤为关键操作点
● 每次保存文件后请确认关闭Excel程序
● 合计列必须为纯数值,不能包含公式"""

    instr_text.insert(tk.INSERT, instructions)
    instr_text.configure(state=tk.DISABLED)  # 设为只读

    # 高亮关键文本
    instr_text.tag_configure("warning", foreground="red")
    start = instructions.index("必须处理完所有错误")
    end = start + len("必须处理完所有错误")
    instr_text.tag_add("warning", f"1.{start}", f"1.{end}")

    start = instructions.index("粘贴为数值覆盖原列")
    end = start + len("粘贴为数值覆盖原列")
    instr_text.tag_add("warning", f"8.{start}", f"8.{end}")

    # 原有按钮布局(添加样式修饰)
    btn_frame = ttk.Frame(root)
    btn_frame.pack(pady=10, fill=tk.X)

    style = ttk.Style()
    style.configure("TButton", font=('微软雅黑', 10), padding=6)
    style.map("TButton",
              foreground=[('active', 'blue'), ('!active', 'black')],
              background=[('active', '#e1e1e1'), ('!active', '#f0f0f0')]
              )

    check_btn = ttk.Button(
        btn_frame,
        text="检查成绩文件",
        command=check_excel_file,
        style="TButton"
    )
    check_btn.pack(side=tk.LEFT, padx=5)

    open_btn = ttk.Button(
        btn_frame,
        text="打开成绩文件",
        command=open_excel_file,
        style="TButton",
        state=tk.DISABLED
    )
    open_btn.pack(side=tk.LEFT, padx=5)

    export_btn = ttk.Button(
        btn_frame,
        text="计算导出结果",
        command=export_scores,
        style="TButton",
        state=tk.DISABLED
    )
    export_btn.pack(side=tk.LEFT, padx=5)

    # 新增分析按钮(与其他按钮区分样式)
    analysis_btn_frame = ttk.Frame(root)
    analysis_btn_frame.pack(pady=5, fill=tk.X)

    style.configure("Analysis.TButton", foreground="white", background="#4CAF50")
    calc_btn = ttk.Button(
        analysis_btn_frame,
        text="计算成绩分布",
        command=calculate_distribution,
        style="Analysis.TButton"
    )
    calc_btn.pack(side=tk.LEFT, padx=5)

    export_dist_btn = ttk.Button(
        analysis_btn_frame,
        text="导出成绩分布",
        command=export_distribution,
        style="Analysis.TButton",
        state = tk.DISABLED
    )
    export_dist_btn.pack(side=tk.LEFT, padx=5)

    # 创建数据表格(优化显示)
    global views
    views = create_views_table(root)

    return root



# 修改主程序启动方式
if __name__ == "__main__":
    root = create_gui()
    root.mainloop()

【操作流程指南】

1. 数据预处理阶段:
   > 多次点击“检查成绩文件”按钮,直到没有错误弹窗
   ※ 手工自动检查:性别列是否为空。数据格式是否正确
   ※ 必须处理完所有错误才能进行下一步

2. 成绩计算阶段:
   ① 点击“打开成绩文件”选择处理后的文件
   ② 点击“导出计算结果”生成带合计列的新文件
   ★ 重要操作:在生成的新文件中:
     - 检查所有"得分"列必须为数字
     - 合计列如果使用公式,请复制→粘贴为数值覆盖原列
     - 保存文件后才能进行下一步

3. 统计分析阶段:
   ① 点击“计算成绩分布”选择上一步保存的文件
   ② 在下方表格中核对预览结果
   ③ 确认无误后点击“导出成绩分布”生成最终报告

【注意事项】
● 红色文字标注的步骤为关键操作点
● 每次保存文件后请确认关闭Excel程序
● 合计列必须为纯数值,不能包含公式
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

PyAIGCMaster

1毛钱也是爱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值