小杰python高级——小项目——成绩管理系统

发布于:2025-09-02 ⋅ 阅读:(17) ⋅ 点赞:(0)

        下边代码部分写的有点冗余、鲁棒性一般,所发内容均是为了记录自己在此方向上的成长过程,实力欠缺,大家多多包涵!!!

题目

项目名称:学生成绩数据可视化项目

项目时间:一天

项目成绩:100分,占总成绩的60%

项目提交:源码+效果视频

项目功能:

根据给定的学生成绩进行数据统计与分析,客户端界面如下:

**********************************************

|学生成绩管理系统|

-------------------------

***************1统计数据********************

***************2查询成绩********************

***************3绘制图表********************

**********************************************

基本要求(80分):

  • 客户端根据输入的指令,发送给服务器,服务器进行相应的动作响应
  • 统计数据:将每门课的最高分、最低分、平均分、及格率进行统计,并存放在当前excel表的新sheet中,服务器将数据统计完成后给客户端回复"统计完成"
  • 查询成绩:可以根据人名进行查询课程成绩,也可以查看每门课的课程名称查看最高分、最低分、平均分、及格率,将查找到的内容在客户端运行窗口进行显示
  • 绘制图表:根据每门课的及格率绘制饼图;根据C语言成绩的前5名绘制成绩走势折线图;服务器绘制完成后给客户端回复“绘制完成”,并将图片数据回复给客户端,客户端进行绘制图像

扩展功能(20分):

  • 实现并发服务器(多个客户端可以连接一个服务器)

client:

import time
import numpy as np
import pandas as pd
import socket as st
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec


def user_face():
    # 封装封面
    print("************************************************")
    print("              |学生成绩管理系统|")
    print("        -------------------------")
    print("  ***************1统计数据********************")
    print("  ***************2查询成绩********************")
    print("  ***************3绘制图表********************")
    print("  ***************4关闭程序********************")
    print("************************************************")


if __name__ == '__main__':
    # 进行客服端与服务器的连接
    socket1 = st.socket(st.AF_INET, st.SOCK_STREAM)
    socket1.connect(('192.168.0.106', 7777))
    print('连接到服务器')
    off = '与服务器连接成功'
    socket1.send(off.encode())  # 传递给服务器确保连接成功
    time.sleep(1)

    user_face()
    # 开始实现项目功能
    while True:
        op = input("请输入指令1,2,3,4指定查询内容")
        if op == '4':
            socket1.sendall(op.encode())  # 发送数据
            socket1.close()  # 关闭socket连接
            break
        elif op not in ['1', '2', '3']:
            print("请输入正确指令")
            continue
        socket1.sendall(op.encode())  # 发送数据
        if op == '1':
            data = socket1.recv(1024).decode()  # 接收数据
            print(data)
        elif op == '2':
            name = input("请输入要查看成绩的姓名或课程名称:")
            socket1.sendall(name.encode())
            print(socket1.recv(1024).decode())
        elif op == '3':
            # fig = socket1.recv(1024).decode()
            arr = plt.imread('./figure1.png')
            plt.imshow(arr)
            plt.show()
            continue
    exit()

server:

import numpy as np
import pandas as pd
import socket as st
import time
import json
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
from openpyxl import load_workbook
from openpyxl.utils.dataframe import dataframe_to_rows

# 设置中文显示的字体
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决负号显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
# 读取
all_sheets = pd.read_excel('./学生成绩信息表.xlsx', sheet_name=['C基础', '数据结构', 'STM32', 'C++'],
                           usecols=['姓名', '考试成绩'])


def sheet():
    """
    构建一个只有成绩和姓名的dataframe
    :return: merged_data
    """
    # 为每个工作表的成绩列添加课程名称作为后缀,避免合并后列名冲突
    for course in all_sheets:
        # 重命名考试成绩列为"课程名_成绩"
        all_sheets[course] = all_sheets[course].rename(
            columns={'考试成绩': f'{course}_成绩'}
        )
    # 从第一个工作表开始,逐步合并所有表
    # 初始合并结果为第一个工作表数据
    df_merged = all_sheets['C基础']
    # 依次合并其他工作表
    for course in ['数据结构', 'STM32', 'C++']:
        df_merged = df_merged.merge(
            all_sheets[course],  # 要合并的表
            on='姓名',  # 合并键(姓名)
            how='outer'  # 外连接:保留所有表中的姓名
        )
    # 未设置姓名索引前数据
    df = df_merged
    # 设置姓名为索引
    df_merged.set_index('姓名', inplace=True)
    return df, df_merged


def tongji():
    df, df_merged = sheet()
    # 统计每门课的最高分、最低分、平均分和及格率
    stats = pd.DataFrame()
    stats['最高分'] = df_merged.max()
    stats['最低分'] = df_merged.min()
    stats['平均分'] = df_merged.mean()
    stats['及格率'] = (df_merged >= 70).sum() / df_merged.shape[0]
    # stats1 = stats.T
    # 将人与各门特殊值连接,生成一个表
    # ret = pd.concat([df_merged, stats1], axis=0)
    with pd.ExcelWriter('./学生成绩信息表.xlsx', mode='a', if_sheet_exists='replace', engine='openpyxl') as writer:
        stats.to_excel(writer, sheet_name="汇总")
    temp = pd.read_excel('./学生成绩信息表.xlsx', sheet_name=['汇总'], usecols=['及格率'])
    return stats


def search_grade(name):
    """
    查询成绩
    :param name:
    :return:
    """
    # 引用自定义的函数,引用其数据
    df1, df_merged1 = sheet()
    stats1 = tongji()
    # 转置一下,dataframe无法输出行
    ret = stats1.T
    ret1 = df_merged1.T
    # 查询各科成绩特点
    if name in ret.columns:
        zhi = ret[name]
        return zhi
    # 查询个人分数
    elif name in ret1.columns:
        zhi = ret1[name]
        return zhi


def draw():
    # 创建画布
    fig = plt.figure(figsize=(8, 5))
    # 创建2行5列的网格布局,但第二行将合并为一个子图
    gs = GridSpec(2, 4, figure=fig, height_ratios=[1, 1])  # 两行高度比例相同
    # 第二行的1个整体子图,横跨所有5列
    ax_bottom = fig.add_subplot(gs[1, :])  # 第二行,所有列
    ax_bottom.set_title('5')
    # 调整布局间距
    plt.tight_layout()
    ax1 = fig.add_subplot(gs[0, 0])
    ax2 = fig.add_subplot(gs[0, 1])
    ax3 = fig.add_subplot(gs[0, 2])
    ax4 = fig.add_subplot(gs[0, 3])
    # 设置支持中文字体
    plt.rcParams['font.sans-serif'] = ['SimHei']
    # 调用统计里及格率的参数
    temp1 = tongji()
    temp = temp1['及格率']
    # 绘制C语言、数据结构、stm32、C++ 及格率饼图
    ax1.pie([temp['C基础_成绩'], 1 - temp['C基础_成绩']], autopct='%0.2f%%', labels=['及格率', '不及格率'])
    ax1.set_title('C基础及格率')
    ax2.pie([temp['数据结构_成绩'], 1 - temp['数据结构_成绩']], autopct='%0.2f%%', labels=['及格率', '不及格率'])
    ax2.set_title('数据结构及格率')
    ax3.pie([temp['STM32_成绩'], 1 - temp['STM32_成绩']], autopct='%0.2f%%', labels=['及格率', '不及格率'])
    ax3.set_title('STM32及格率')
    ax4.pie([temp['C++_成绩'], 1 - temp['C++_成绩']], autopct='%0.2f%%', labels=['及格率', '不及格率'])
    ax4.set_title('C++及格率')

    # 前五名成绩获取
    df, df_merged = sheet()
    num = df_merged
    a = num[::][:5]
    print(a)
    # 第二行,所有列
    ax5 = fig.add_subplot(gs[1, :])
    # 根据C语言成绩的前5名绘制成绩走势折线图
    # 定义线条样式和颜色
    styles = ['-o', '-s', '-^', '-D']
    colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728']
    # 绘制每门课程的折线图
    for i, column in enumerate(a.columns):
        plt.plot(a.index, a[column], styles[i], color=colors[i], label=column)
    # 添加标题和标签
    plt.title('各门课程成绩走势')
    plt.xlabel('学生姓名')
    plt.ylabel('成绩')
    # 设置Y轴范围,使图表右侧显示成绩
    plt.ylim(50, 130)
    # 添加图例
    plt.legend(fontsize=8)
    # 调整布局
    # plt.tight_layout()
    ax = plt.gca()
    # 保存图片为文件
    plt.savefig('./figure1.png')
    return


if __name__ == '__main__':
    # 建立连接
    sfd = st.socket(st.AF_INET, st.SOCK_STREAM)
    sfd.bind(('192.168.0.106', 7777))
    # sfd.connect(('192.168.0.106', 7777))
    sfd.listen(10)
    socket1, addr = sfd.accept()
    off = socket1.recv(60)
    # 接收客户端,查看是否连接成功
    print(off.decode())
    # 等待一下,防止时间偏
    time.sleep(1)

    while True:
        # 接收命令
        zhi_xing = socket1.recv(60)
        ming_ling = zhi_xing.decode()
        # 对命令进行相应操作
        if ming_ling == '4':
            break
        if ming_ling == '1':
            df0, df_merged0 = sheet()
            stats0 = tongji()
            one_data = '统计完成'
            # 1. 将字典序列化为JSON字符串
            json_str2 = json.dumps(one_data, ensure_ascii=False)
            # 2. 转换为字节流(UTF-8编码)
            bytes_data2 = json_str2.encode('utf-8')
            socket1.sendall(bytes_data2)
        elif ming_ling == '2':
            # 接收完整代码
            # received_code = sfd.recv(1024).decode()
            # data0 = exec(received_code)  # 直接执行,无需 seed
            # data0 = sfd.recv(1024).decode()
            data0 = socket1.recv(60).decode()
            result0 = search_grade(data0)
            # 1. 将字典序列化为JSON字符串
            # json_str = json.dumps(result0, ensure_ascii=False)
            # 2. 转换为字节流(UTF-8编码)
            # bytes_data = json_str.encode('utf-8')
            # 发送数据
            # socket1.sendall(result0)
            # socket1.sendall(result0.encode())
            # return0为Series对象,应先转化为字典
            result1 = result0.to_dict()
            json_str = json.dumps(result1, ensure_ascii=False)
            bytes_data5 = json_str.encode('utf-8')
            socket1.sendall(bytes_data5)
            print("字典数据已发送")
        elif ming_ling == '3':
            draw()
            arr0 = plt.imread('./figure1.png')
            # 1. 将字典序列化为JSON字符串
            # json_str1 = json.dumps(arr0, ensure_ascii=False)
            # 2. 转换为字节流(UTF-8编码)
            # bytes_data1 = json_str1.encode('utf-8')
            # 发送数据
            # socket1.sendall(bytes_data1)
            continue
    exit()