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