v1.6版本更新

This commit is contained in:
2026-05-06 13:32:12 +08:00
parent 5c0b9c8516
commit 47be179c09
8 changed files with 115 additions and 40 deletions

View File

@@ -228,9 +228,9 @@ classmanager/
| 班主任 | 全班 | 无限制 | 可撤销任何记录 | 全班所有记录 | 学生/管理员/科目管理、数据导出 | | 班主任 | 全班 | 无限制 | 可撤销任何记录 | 全班所有记录 | 学生/管理员/科目管理、数据导出 |
| 班长 | 全班 | ±5分 | 可撤销任何记录 | 全班所有记录 | - | | 班长 | 全班 | ±5分 | 可撤销任何记录 | 全班所有记录 | - |
| 学习委员 | 全班 | ±5分以内加减分 | 不可撤销 | 仅自己提交的 | 作业管理、科目管理 | | 学习委员 | 全班 | ±5分以内加减分 | 不可撤销 | 仅自己提交的 | 作业管理、科目管理 |
| 考勤委员 | 全班 | 仅扣分(按规则) | 不可撤销 | 仅自己提交的 | 考勤管理 | | 考勤委员 | 全班 | 仅扣分最多扣8分 | 不可撤销 | 仅自己提交的 | 考勤管理 |
| 劳动委员 | 全班 | ±1分(卫生值日) | 不可撤销 | 仅自己提交的 | - | | 劳动委员 | 全班 | ±1分以内 | 不可撤销 | 仅自己提交的 | - |
| 志愿委员 | 全班 | 仅加分 | 不可撤销 | 仅自己提交的 | - | | 志愿委员 | 全班 | 仅加分,最多+5分 | 不可撤销 | 仅自己提交的 | - |
| 学生 | 自己 | 无 | 无 | 自己的历史 | 修改密码 | | 学生 | 自己 | 无 | 无 | 自己的历史 | 修改密码 |
| 家长 | 子女总分 | 无 | 无 | 不可见详情 | - | | 家长 | 子女总分 | 无 | 无 | 不可见详情 | - |
@@ -267,6 +267,8 @@ classmanager/
| v1.3 | 2026.4.27 | 考勤时段系统(早上/中午/晚修三时段)、历史记录扣分类型筛选、管理员/科目信息编辑、全链路输入安全校验 | | v1.3 | 2026.4.27 | 考勤时段系统(早上/中午/晚修三时段)、历史记录扣分类型筛选、管理员/科目信息编辑、全链路输入安全校验 |
| v1.4 | 2026.4.28 | 全量代码审查修复双重密码哈希bug、学生端XSS漏洞、性能优化、Pydantic schema统一、权限检查补全、考勤委员撤销权限 | | v1.4 | 2026.4.28 | 全量代码审查修复双重密码哈希bug、学生端XSS漏洞、性能优化、Pydantic schema统一、权限检查补全、考勤委员撤销权限 |
| v1.5 | 2026.4.29 | 用户反馈修复登录封禁5分钟+手动解锁、加减分回显修复、学习委员5分限制修复、按钮样式补全 | | v1.5 | 2026.4.29 | 用户反馈修复登录封禁5分钟+手动解锁、加减分回显修复、学习委员5分限制修复、按钮样式补全 |
| v1.5.1 | 2026.4.29 | 权限修复:考勤委员提示遗漏、历史记录权限泄露、时间筛选失效、作业页分数限制与后端同步 |
| v1.6 | 2026.4.29 | 全量一致性审计:前后端配置统一(.env.example/config.py/config.php、清理废弃全局变量、角色权限表精确化 |
## 许可证 ## 许可证

View File

@@ -109,7 +109,7 @@ MONITOR_MAX_SUBTRACT=-5
STUDY_COMMISSIONER_MAX_POINTS=5 STUDY_COMMISSIONER_MAX_POINTS=5
# 考勤委员单次扣分上限(绝对值) # 考勤委员单次扣分上限(绝对值)
ATTENDANCE_REP_MAX_POINTS=5 ATTENDANCE_REP_MAX_POINTS=8
# 劳动委员单次加减分上限(绝对值) # 劳动委员单次加减分上限(绝对值)
LABOR_REP_MAX_POINTS=1 LABOR_REP_MAX_POINTS=1

View File

@@ -41,18 +41,50 @@ class ConductModel:
)) ))
@staticmethod @staticmethod
async def count_student_records(student_id: int, include_revoked: bool = False) -> int: async def count_student_records(
student_id: int,
include_revoked: bool = False,
start_date: str = None,
end_date: str = None,
recorder_id: int = None
) -> int:
"""统计学生操行分记录总数""" """统计学生操行分记录总数"""
revoked_condition = "" if include_revoked else " AND is_revoked = 0" conditions = ["student_id = %s"]
sql = f"SELECT COUNT(*) as total FROM conduct_records WHERE student_id = %s{revoked_condition}" params = [student_id]
result = await execute_one(sql, (student_id,)) if not include_revoked:
conditions.append("is_revoked = 0")
if start_date:
conditions.append("DATE(created_at) >= %s")
params.append(start_date)
if end_date:
conditions.append("DATE(created_at) <= %s")
params.append(end_date)
if recorder_id:
conditions.append("recorder_id = %s")
params.append(recorder_id)
where = " AND ".join(conditions)
sql = f"SELECT COUNT(*) as total FROM conduct_records WHERE {where}"
result = await execute_one(sql, tuple(params))
return result["total"] if result else 0 return result["total"] if result else 0
@staticmethod @staticmethod
async def count_records_by_recorder(recorder_id: int) -> int: async def count_records_by_recorder(
recorder_id: int,
start_date: str = None,
end_date: str = None
) -> int:
"""统计记录人提交的操行分记录总数""" """统计记录人提交的操行分记录总数"""
sql = "SELECT COUNT(*) as total FROM conduct_records WHERE recorder_id = %s" conditions = ["recorder_id = %s"]
result = await execute_one(sql, (recorder_id,)) params = [recorder_id]
if start_date:
conditions.append("DATE(created_at) >= %s")
params.append(start_date)
if end_date:
conditions.append("DATE(created_at) <= %s")
params.append(end_date)
where = " AND ".join(conditions)
sql = f"SELECT COUNT(*) as total FROM conduct_records WHERE {where}"
result = await execute_one(sql, tuple(params))
return result["total"] if result else 0 return result["total"] if result else 0
@staticmethod @staticmethod
@@ -60,36 +92,65 @@ class ConductModel:
student_id: int, student_id: int,
limit: int = 50, limit: int = 50,
offset: int = 0, offset: int = 0,
include_revoked: bool = False include_revoked: bool = False,
start_date: str = None,
end_date: str = None,
recorder_id: int = None
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""获取学生操行分记录""" """获取学生操行分记录"""
sql = """ conditions = ["cr.student_id = %s"]
params = [student_id]
if not include_revoked:
conditions.append("cr.is_revoked = 0")
if start_date:
conditions.append("DATE(cr.created_at) >= %s")
params.append(start_date)
if end_date:
conditions.append("DATE(cr.created_at) <= %s")
params.append(end_date)
if recorder_id:
conditions.append("cr.recorder_id = %s")
params.append(recorder_id)
where = " AND ".join(conditions)
sql = f"""
SELECT cr.*, u.real_name as recorder_name SELECT cr.*, u.real_name as recorder_name
FROM conduct_records cr FROM conduct_records cr
LEFT JOIN users u ON cr.recorder_id = u.user_id LEFT JOIN users u ON cr.recorder_id = u.user_id
WHERE cr.student_id = %s WHERE {where}
ORDER BY cr.created_at DESC
LIMIT %s OFFSET %s
""" """
if not include_revoked: params.extend([limit, offset])
sql += " AND cr.is_revoked = 0" return await execute_query(sql, tuple(params))
sql += " ORDER BY cr.created_at DESC LIMIT %s OFFSET %s"
return await execute_query(sql, (student_id, limit, offset))
@staticmethod @staticmethod
async def get_records_by_recorder( async def get_records_by_recorder(
recorder_id: int, recorder_id: int,
limit: int = 50, limit: int = 50,
offset: int = 0 offset: int = 0,
start_date: str = None,
end_date: str = None
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
"""获取操作人提交的记录""" """获取操作人提交的记录"""
sql = """ conditions = ["cr.recorder_id = %s", "cr.is_revoked = 0"]
params = [recorder_id]
if start_date:
conditions.append("DATE(cr.created_at) >= %s")
params.append(start_date)
if end_date:
conditions.append("DATE(cr.created_at) <= %s")
params.append(end_date)
where = " AND ".join(conditions)
sql = f"""
SELECT cr.*, s.name as student_name SELECT cr.*, s.name as student_name
FROM conduct_records cr FROM conduct_records cr
JOIN students s ON cr.student_id = s.student_id JOIN students s ON cr.student_id = s.student_id
WHERE cr.recorder_id = %s AND cr.is_revoked = 0 WHERE {where}
ORDER BY cr.created_at DESC ORDER BY cr.created_at DESC
LIMIT %s OFFSET %s LIMIT %s OFFSET %s
""" """
return await execute_query(sql, (recorder_id, limit, offset)) params.extend([limit, offset])
return await execute_query(sql, tuple(params))
@staticmethod @staticmethod
async def get_all_records( async def get_all_records(

View File

@@ -273,22 +273,35 @@ class ConductService:
total = total_result["total"] if total_result else 0 total = total_result["total"] if total_result else 0
elif student_id: elif student_id:
# 管理员查看指定学生 # 普通管理员查看指定学生(仅返回自己操作的记录)
records = await ConductModel.get_student_records( records = await ConductModel.get_student_records(
student_id=student_id, student_id=student_id,
limit=page_size, limit=page_size,
offset=offset offset=offset,
start_date=start_date,
end_date=end_date,
recorder_id=user_id
)
total = await ConductModel.count_student_records(
student_id=student_id,
start_date=start_date,
end_date=end_date,
recorder_id=user_id
) )
total = await ConductModel.count_student_records(student_id)
else: else:
# 查看自己提交的记录 # 查看自己提交的记录
records = await ConductModel.get_records_by_recorder( records = await ConductModel.get_records_by_recorder(
recorder_id=user_id, recorder_id=user_id,
limit=page_size, limit=page_size,
offset=offset offset=offset,
start_date=start_date,
end_date=end_date
)
total = await ConductModel.count_records_by_recorder(
recorder_id=user_id,
start_date=start_date,
end_date=end_date
) )
total = await ConductModel.count_records_by_recorder(user_id)
return { return {
"records": records, "records": records,

View File

@@ -41,11 +41,8 @@ ICP_NUMBER=京ICP备1234567890号-x
DEDUCTION_HOMEWORK_NOT_SUBMIT=2 DEDUCTION_HOMEWORK_NOT_SUBMIT=2
# 作业-迟交扣分 # 作业-迟交扣分
DEDUCTION_HOMEWORK_LATE=1 DEDUCTION_HOMEWORK_LATE=1
# 作业-每次加减分上限(绝对值) # 作业-每次加减分上限(绝对值,仅影响作业管理页面的前端输入限制
HOMEWORK_MAX_POINTS=3 HOMEWORK_MAX_POINTS=5
# 学习委员单次加减分上限(绝对值)
STUDY_COMMISSIONER_MAX_POINTS=5
# 考勤-缺勤扣分 # 考勤-缺勤扣分
DEDUCTION_ATTENDANCE_ABSENT=3 DEDUCTION_ATTENDANCE_ABSENT=3

View File

@@ -208,11 +208,14 @@ loadStudents();
<label>分数变动</label> <label>分数变动</label>
<input type="number" id="pointsChange" required placeholder="正数为加分,负数为扣分"> <input type="number" id="pointsChange" required placeholder="正数为加分,负数为扣分">
<small><?php <small><?php
if ($role === '班长') echo '班长单次±5分以内'; $hints = [
elseif ($role === '学习委员') echo '学习委员单次±5分以内'; '班长' => '班长单次±5分以内',
elseif ($role === '劳动委员') echo '劳动委员仅限±1分'; '学习委员' => '学习委员单次±5分以内',
elseif ($role === '志愿委员') echo '志愿委员仅限加分'; '考勤委员' => '考勤委员仅限扣分单次最多扣8分',
else echo '班主任无限制'; '劳动委员' => '劳动委员单次±1分以内',
'志愿委员' => '志愿委员仅限加分,最多+5分',
];
echo $hints[$role] ?? '班主任无限制';
?></small> ?></small>
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@@ -106,7 +106,7 @@ var selectedStudentIds = [];
const hwRole = '<?php echo $role; ?>'; const hwRole = '<?php echo $role; ?>';
// 初始化扣分配置 // 初始化扣分配置
const hwMaxPoints = hwRole === '班主任' ? 100 : (window.HOMEWORK_MAX_POINTS || 5); const hwMaxPoints = hwRole === '班主任' ? 100 : 5;
const hwNotSubmit = window.DEDUCTION_HOMEWORK_NOT_SUBMIT || 2; const hwNotSubmit = window.DEDUCTION_HOMEWORK_NOT_SUBMIT || 2;
const hwLate = window.DEDUCTION_HOMEWORK_LATE || 1; const hwLate = window.DEDUCTION_HOMEWORK_LATE || 1;

View File

@@ -47,7 +47,6 @@ $page_title = $page_title ?? '首页';
window.USER_STORAGE_KEY = '<?php echo USER_STORAGE_KEY; ?>'; window.USER_STORAGE_KEY = '<?php echo USER_STORAGE_KEY; ?>';
window.DEDUCTION_HOMEWORK_NOT_SUBMIT = <?php echo DEDUCTION_HOMEWORK_NOT_SUBMIT; ?>; window.DEDUCTION_HOMEWORK_NOT_SUBMIT = <?php echo DEDUCTION_HOMEWORK_NOT_SUBMIT; ?>;
window.DEDUCTION_HOMEWORK_LATE = <?php echo DEDUCTION_HOMEWORK_LATE; ?>; window.DEDUCTION_HOMEWORK_LATE = <?php echo DEDUCTION_HOMEWORK_LATE; ?>;
window.HOMEWORK_MAX_POINTS = <?php echo HOMEWORK_MAX_POINTS; ?>;
window.DEDUCTION_ATTENDANCE_ABSENT = <?php echo DEDUCTION_ATTENDANCE_ABSENT; ?>; window.DEDUCTION_ATTENDANCE_ABSENT = <?php echo DEDUCTION_ATTENDANCE_ABSENT; ?>;
window.DEDUCTION_ATTENDANCE_LATE = <?php echo DEDUCTION_ATTENDANCE_LATE; ?>; window.DEDUCTION_ATTENDANCE_LATE = <?php echo DEDUCTION_ATTENDANCE_LATE; ?>;
window.DEDUCTION_ATTENDANCE_LEAVE = <?php echo DEDUCTION_ATTENDANCE_LEAVE; ?>; window.DEDUCTION_ATTENDANCE_LEAVE = <?php echo DEDUCTION_ATTENDANCE_LEAVE; ?>;