from flask import Flask, render_template, request, jsonify, send_file, redirect, url_for, flash from services import RollCallService, ScoreService import io from models import init_db, get_db, Student # 新增导入 import pandas as pd # 新增导入 # 初始化数据库(首次运行创建表) init_db() app = Flask(__name__) app.secret_key = 'your_secret_key' # 用于flash提示 roll_call_service = RollCallService() score_service = ScoreService() # 首页 @app.route('/') def index(): return render_template('index.html') # 点名页面(支持选择模式) @app.route('/roll_call') def roll_call_page(): return render_template('roll_call.html') # 积分页面(含可视化) @app.route('/scores') def scores_page(): class_name = request.args.get('class_name', '软工K班') chart_base64 = score_service.generate_score_chart(class_name) return render_template('scores.html', chart_base64=chart_base64, class_name=class_name) # 新增:Excel导入页面 @app.route('/import') def import_page(): return render_template('import.html') # API:Excel导入学生 - 修复版本 @app.route('/api/students/import', methods=['POST']) def import_students(): # 移除嵌套的函数定义 try: # 检查文件是否存在 if 'excel_file' not in request.files: return jsonify({'success': False, 'message': '未选择文件'}) file = request.files['excel_file'] # 检查文件名 if file.filename == '': return jsonify({'success': False, 'message': '未选择文件'}) # 检查文件格式 if not (file.filename.endswith('.xlsx') or file.filename.endswith('.xls')): return jsonify({'success': False, 'message': '只支持 .xlsx 或 .xls 格式'}) # 读取Excel文件 df = pd.read_excel(file) required_columns = ['学号', '姓名'] missing_columns = [col for col in required_columns if col not in df.columns] if missing_columns: return jsonify({ 'success': False, 'message': f'缺少必要列: {", ".join(missing_columns)}' }) # 检查数据是否为空 if df.empty: return jsonify({'success': False, 'message': 'Excel文件为空'}) # 数据验证 for index, row in df.iterrows(): if pd.isna(row['学号']) or pd.isna(row['姓名']): return jsonify({ 'success': False, 'message': f'第{index + 2}行数据不完整' }) # 获取数据库会话 db = next(get_db()) # 保存到数据库 students_imported = 0 for index, row in df.iterrows(): # 检查学生是否已存在 existing_student = db.query(Student).filter_by(student_id=str(row['学号'])).first() if not existing_student: # 简化:只需要学号和姓名,其他字段使用默认值 student = Student( student_id=str(row['学号']), name=row['姓名'], major='软件工程', # 默认专业 class_name='软工K班', # 默认班级 total_score=0, call_count=0 ) db.add(student) students_imported += 1 else: # 如果学生已存在,更新姓名 existing_student.name = row['姓名'] db.commit() db.close() return jsonify({ 'success': True, 'message': f'成功导入 {students_imported} 名学生数据' }) except Exception as e: # 错误处理 db.rollback() db.close() return jsonify({ 'success': False, 'message': f'导入失败: {str(e)}' }) # API:点名(支持random/order模式) @app.route('/api/roll_call', methods=['POST']) def roll_call(): data = request.json class_name = data.get('class_name', '软工K班') call_mode = data.get('call_mode', 'random') # 新增模式参数 student, msg = roll_call_service.roll_call(class_name, call_mode) if student: return jsonify({'success': True, 'data': student.to_dict()}) return jsonify({'success': False, 'message': msg}) # 其他原有API保持不变,仅更新积分更新接口 @app.route('/api/scores/update', methods=['POST']) def update_score(): data = request.json student_id = data.get('student_id') answer_type = data.get('answer_type') performance = data.get('performance') status = data.get('status', 'present') # 新增到场状态 success, score_delta = roll_call_service.update_student_score(student_id, answer_type, performance, status) return jsonify({'success': success, 'score_delta': score_delta}) # 新增:导出积分详单API(复用原有路由,内部逻辑已更新) @app.route('/api/scores/export_detailed') def export_detailed_scores(): class_name = request.args.get('class_name', '软工K班') excel_data = score_service.export_scores_excel(class_name) return send_file( excel_data, mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', as_attachment=True, download_name=f'{class_name}_积分详单.xlsx' ) @app.route('/api/scores/ranking') def get_class_ranking(): class_name = request.args.get('class_name', '软工K班') students = score_service.get_class_ranking(class_name) return jsonify({ 'success': True, 'data': [s.to_dict() for s in students] }) if __name__ == '__main__': app.run(debug=True, port=5000)