0914-2-version

This commit is contained in:
superlishunqin 2025-09-14 02:00:19 +08:00
parent 77b57a9876
commit 441b478391
5 changed files with 209 additions and 179 deletions

View File

@ -6,6 +6,68 @@ from app.utils.database import safe_add_and_commit
from datetime import datetime, timedelta from datetime import datetime, timedelta
from sqlalchemy import and_, or_, desc from sqlalchemy import and_, or_, desc
# 这个文件包含需要在student.py中添加的缺勤次数计算函数
def calculate_absent_count_for_record(record):
"""为单个考勤记录计算真实的缺勤次数"""
import json
from app.models import DailyAttendanceDetail
# 获取该记录的每日明细
daily_details = DailyAttendanceDetail.query.filter_by(
weekly_record_id=record.record_id
).all()
absent_count = 0
for detail in daily_details:
# 处理完全缺勤的情况
if detail.status == '缺勤' and (not detail.remarks or not detail.remarks.startswith('{')):
# 完全缺勤的天数,早上+下午都缺勤
absent_count += 2 # 早上缺勤1次 + 下午缺勤1次
continue
if detail.remarks and detail.remarks.startswith('{'):
try:
remarks_data = json.loads(detail.remarks)
details_info = remarks_data.get('details', {})
# 统计缺勤次数
# 检查早上缺勤morning_in AND morning_out 都missing
morning_data = details_info.get('morning', {})
morning_in_time = morning_data.get('in')
morning_out_time = morning_data.get('out')
morning_in_status = morning_data.get('status', 'missing')
if ((not morning_in_time or morning_in_status == 'missing') and
(not morning_out_time)):
absent_count += 1
# 检查下午缺勤afternoon_in AND afternoon_out 都missing
afternoon_data = details_info.get('afternoon', {})
afternoon_in_time = afternoon_data.get('in')
afternoon_out_time = afternoon_data.get('out')
afternoon_in_status = afternoon_data.get('status', 'missing')
if ((not afternoon_in_time or afternoon_in_status == 'missing') and
(not afternoon_out_time)):
absent_count += 1
except (json.JSONDecodeError, KeyError, AttributeError):
continue
return absent_count
def add_absent_count_to_records(records):
"""为考勤记录列表添加缺勤次数计算属性"""
total_absent_count = 0
for record in records:
absent_count = calculate_absent_count_for_record(record)
record.absent_count = absent_count
total_absent_count += absent_count
return total_absent_count
student_bp = Blueprint('student', __name__) student_bp = Blueprint('student', __name__)
@ -35,9 +97,9 @@ def dashboard():
db.func.sum(WeeklyAttendance.actual_work_hours) db.func.sum(WeeklyAttendance.actual_work_hours)
).filter_by(student_number=current_user.student_number).scalar() or 0 ).filter_by(student_number=current_user.student_number).scalar() or 0
total_absent_days = db.session.query( # 获取所有考勤记录并计算真实缺勤次数
db.func.sum(WeeklyAttendance.absent_days) all_records = WeeklyAttendance.query.filter_by(student_number=current_user.student_number).all()
).filter_by(student_number=current_user.student_number).scalar() or 0 total_absent_count = add_absent_count_to_records(all_records)
# 获取未审批的请假记录 # 获取未审批的请假记录
pending_leaves = LeaveRecord.query.filter_by( pending_leaves = LeaveRecord.query.filter_by(
@ -50,7 +112,7 @@ def dashboard():
recent_attendance=recent_attendance, recent_attendance=recent_attendance,
total_records=total_records, total_records=total_records,
total_work_hours=float(total_work_hours), total_work_hours=float(total_work_hours),
total_absent_days=int(total_absent_days), total_absent_count=int(total_absent_count),
pending_leaves=pending_leaves) pending_leaves=pending_leaves)
@ -147,7 +209,9 @@ def attendance():
total_actual_hours = sum(record.actual_work_hours for record, _ in all_records) total_actual_hours = sum(record.actual_work_hours for record, _ in all_records)
total_class_hours = sum(record.class_work_hours for record, _ in all_records) total_class_hours = sum(record.class_work_hours for record, _ in all_records)
total_absent_days = sum(record.absent_days for record, _ in all_records) # 为记录添加缺勤次数计算
record_list = [record for record, _ in all_records]
total_absent_count = add_absent_count_to_records(record_list)
total_overtime_hours = sum(record.overtime_hours for record, _ in all_records) total_overtime_hours = sum(record.overtime_hours for record, _ in all_records)
total_late_count = sum(late_count for _, late_count in all_records) total_late_count = sum(late_count for _, late_count in all_records)
@ -164,7 +228,7 @@ def attendance():
'total_weeks': len(all_records), 'total_weeks': len(all_records),
'total_actual_hours': total_actual_hours, 'total_actual_hours': total_actual_hours,
'total_class_hours': total_class_hours, 'total_class_hours': total_class_hours,
'total_absent_days': total_absent_days, 'total_absent_count': total_absent_count,
'total_overtime_hours': total_overtime_hours, 'total_overtime_hours': total_overtime_hours,
'total_late_count': total_late_count, 'total_late_count': total_late_count,
'total_leave_days': total_leave_days, 'total_leave_days': total_leave_days,
@ -200,6 +264,61 @@ def attendance_details(record_id):
weekly_record_id=record_id weekly_record_id=record_id
).order_by(DailyAttendanceDetail.attendance_date).all() ).order_by(DailyAttendanceDetail.attendance_date).all()
# 🔥 新增:计算真实的缺勤次数和迟到次数
def calculate_real_counts(daily_details_list):
"""计算真实的缺勤次数和迟到次数"""
late_count = 0
absent_count = 0
for detail in daily_details_list:
# 🔥 处理完全缺勤的情况
if detail.status == '缺勤' and (not detail.remarks or not detail.remarks.startswith('{')):
# 完全缺勤的天数,早上+下午都缺勤
absent_count += 2 # 早上缺勤1次 + 下午缺勤1次
continue
if detail.remarks and detail.remarks.startswith('{'):
try:
remarks_data = json.loads(detail.remarks)
details_info = remarks_data.get('details', {})
# 统计迟到次数
for period in ['morning', 'afternoon', 'evening']:
period_data = details_info.get(period, {})
if period_data.get('status') == 'late':
late_count += 1
# 统计缺勤次数
# 检查早上缺勤morning_in AND morning_out 都missing
morning_data = details_info.get('morning', {})
morning_in_time = morning_data.get('in')
morning_out_time = morning_data.get('out')
morning_in_status = morning_data.get('status', 'missing')
if ((not morning_in_time or morning_in_status == 'missing') and
(not morning_out_time)):
absent_count += 1
# 检查下午缺勤afternoon_in AND afternoon_out 都missing
afternoon_data = details_info.get('afternoon', {})
afternoon_in_time = afternoon_data.get('in')
afternoon_out_time = afternoon_data.get('out')
afternoon_in_status = afternoon_data.get('status', 'missing')
if ((not afternoon_in_time or afternoon_in_status == 'missing') and
(not afternoon_out_time)):
absent_count += 1
except (json.JSONDecodeError, KeyError, AttributeError):
continue
return late_count, absent_count
# 🔥 计算真实的次数并添加到record对象中
real_late_count, real_absent_count = calculate_real_counts(daily_details)
record.late_count = real_late_count
record.absent_count = real_absent_count
# 处理每日详情,计算工作时长和解析详细信息 # 处理每日详情,计算工作时长和解析详细信息
processed_daily_details = [] processed_daily_details = []
for detail in daily_details: for detail in daily_details:
@ -264,8 +383,9 @@ def attendance_details(record_id):
desc(WeeklyAttendance.week_start_date) desc(WeeklyAttendance.week_start_date)
).limit(5).all() ).limit(5).all()
# 🔥 修改:传递 weekly_record 而不是 record以匹配模板
return render_template('student/attendance_details.html', return render_template('student/attendance_details.html',
record=record, weekly_record=record, # 🔥 关键修改
student=student, student=student,
daily_details=processed_daily_details, daily_details=processed_daily_details,
total_days=total_days, total_days=total_days,
@ -275,7 +395,6 @@ def attendance_details(record_id):
avg_daily_hours=avg_daily_hours, avg_daily_hours=avg_daily_hours,
recent_records=recent_records) recent_records=recent_records)
@student_bp.route('/statistics') @student_bp.route('/statistics')
@login_required @login_required
def statistics(): def statistics():
@ -313,12 +432,15 @@ def statistics():
# 获取考勤记录,按周排序 # 获取考勤记录,按周排序
attendance_records = attendance_query.order_by(desc(WeeklyAttendance.week_start_date)).all() attendance_records = attendance_query.order_by(desc(WeeklyAttendance.week_start_date)).all()
# 计算真实缺勤次数
add_absent_count_to_records(attendance_records)
# 计算统计数据 # 计算统计数据
total_stats = { total_stats = {
'total_work_hours': sum(record.actual_work_hours for record in attendance_records), 'total_work_hours': sum(record.actual_work_hours for record in attendance_records),
'total_class_hours': sum(record.class_work_hours for record in attendance_records), 'total_class_hours': sum(record.class_work_hours for record in attendance_records),
'total_overtime_hours': sum(record.overtime_hours for record in attendance_records), 'total_overtime_hours': sum(record.overtime_hours for record in attendance_records),
'total_absent_days': sum(record.absent_days for record in attendance_records), 'total_absent_count': sum(getattr(record, 'absent_count', 0) for record in attendance_records),
'attendance_weeks': len(attendance_records) 'attendance_weeks': len(attendance_records)
} }
@ -400,11 +522,15 @@ def statistics():
if all_records: if all_records:
# 计算入学以来的总统计 # 计算入学以来的总统计
enrollment_weeks = (datetime.now().date() - student.enrollment_date).days // 7 enrollment_weeks = (datetime.now().date() - student.enrollment_date).days // 7
# 🔥 修复:先计算缺勤次数,再定义字典
add_absent_count_to_records(all_records)
all_time_stats = { all_time_stats = {
'total_work_hours': sum(record.actual_work_hours for record in all_records), 'total_work_hours': sum(record.actual_work_hours for record in all_records),
'total_class_hours': sum(record.class_work_hours for record in all_records), 'total_class_hours': sum(record.class_work_hours for record in all_records),
'total_overtime_hours': sum(record.overtime_hours for record in all_records), 'total_overtime_hours': sum(record.overtime_hours for record in all_records),
'total_absent_days': sum(record.absent_days for record in all_records), 'total_absent_count': sum(getattr(record, 'absent_count', 0) for record in all_records),
'attendance_weeks': len(all_records), 'attendance_weeks': len(all_records),
'enrollment_weeks': enrollment_weeks, 'enrollment_weeks': enrollment_weeks,
'attendance_rate': round(len(all_records) / max(enrollment_weeks, 1) * 100, 'attendance_rate': round(len(all_records) / max(enrollment_weeks, 1) * 100,

View File

@ -108,10 +108,10 @@
<div class="row no-gutters align-items-center"> <div class="row no-gutters align-items-center">
<div class="col mr-2"> <div class="col mr-2">
<div class="text-xs font-weight-bold text-danger text-uppercase mb-1"> <div class="text-xs font-weight-bold text-danger text-uppercase mb-1">
旷工天 缺勤次
</div> </div>
<div class="h5 mb-0 font-weight-bold text-gray-800"> <div class="h5 mb-0 font-weight-bold text-gray-800">
{{ total_stats.total_absent_days }}天 {{ total_stats.total_absent_count }}次
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
@ -204,7 +204,7 @@
<th>实际工作时长</th> <th>实际工作时长</th>
<th>班内工作时长</th> <th>班内工作时长</th>
<th>迟到次数</th> <th>迟到次数</th>
<th>旷工天</th> <th>缺勤次</th>
<th>加班时长</th> <th>加班时长</th>
<th>记录时间</th> <th>记录时间</th>
<th>操作</th> <th>操作</th>
@ -236,7 +236,7 @@
</td> </td>
<td> <td>
{% if record.absent_days > 0 %} {% if record.absent_days > 0 %}
<span class="badge bg-danger">{{ record.absent_days }}天</span> <span class="badge bg-danger">{{ record.absent_count if record.absent_count is defined else 0 }}次</span>
{% else %} {% else %}
<span class="badge bg-success">0天</span> <span class="badge bg-success">0天</span>
{% endif %} {% endif %}

View File

@ -6,18 +6,9 @@
<div class="container-fluid mt-4"> <div class="container-fluid mt-4">
<!-- 页面标题 --> <!-- 页面标题 -->
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="d-flex justify-content-between align-items-center mb-4">
<div> <h1 class="h3 mb-0">
<h2 class="fw-bold text-primary mb-0"> <i class="fas fa-calendar-check me-2"></i>考勤详情
<i class="fas fa-calendar-check me-2"></i>我的考勤详情 </h1>
</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('student.dashboard') }}">首页</a></li>
<li class="breadcrumb-item"><a href="{{ url_for('student.attendance') }}">考勤记录</a></li>
<li class="breadcrumb-item active">考勤详情</li>
</ol>
</nav>
</div>
<div> <div>
<a href="{{ url_for('student.attendance') }}" class="btn btn-secondary"> <a href="{{ url_for('student.attendance') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-2"></i>返回列表 <i class="fas fa-arrow-left me-2"></i>返回列表
@ -37,8 +28,8 @@
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<p><strong>学号:</strong> {{ record.student_number }}</p> <p><strong>学号:</strong> {{ weekly_record.student_number }}</p>
<p><strong>姓名:</strong> {{ record.name }}</p> <p><strong>姓名:</strong> {{ weekly_record.name }}</p>
{% if student %} {% if student %}
<p><strong>年级:</strong> {{ student.grade }}</p> <p><strong>年级:</strong> {{ student.grade }}</p>
<p><strong>学院:</strong> {{ student.college or '未设置' }}</p> <p><strong>学院:</strong> {{ student.college or '未设置' }}</p>
@ -71,12 +62,12 @@
<div class="card-body"> <div class="card-body">
<div class="row"> <div class="row">
<div class="col-6"> <div class="col-6">
<p><strong>开始日期:</strong> {{ record.week_start_date.strftime('%Y年%m月%d日') }}</p> <p><strong>开始日期:</strong> {{ weekly_record.week_start_date.strftime('%Y年%m月%d日') }}</p>
<p><strong>结束日期:</strong> {{ record.week_end_date.strftime('%Y年%m月%d日') }}</p> <p><strong>结束日期:</strong> {{ weekly_record.week_end_date.strftime('%Y年%m月%d日') }}</p>
</div> </div>
<div class="col-6"> <div class="col-6">
<p><strong>创建时间:</strong> {{ record.created_at.strftime('%Y-%m-%d %H:%M') }}</p> <p><strong>创建时间:</strong> {{ weekly_record.created_at.strftime('%Y-%m-%d %H:%M') }}</p>
<p><strong>更新时间:</strong> {{ record.updated_at.strftime('%Y-%m-%d %H:%M') }}</p> <p><strong>更新时间:</strong> {{ weekly_record.updated_at.strftime('%Y-%m-%d %H:%M') }}</p>
</div> </div>
</div> </div>
</div> </div>
@ -95,7 +86,7 @@
实际出勤时长 实际出勤时长
</div> </div>
<div class="h5 mb-0 font-weight-bold text-gray-800"> <div class="h5 mb-0 font-weight-bold text-gray-800">
{{ "%.1f"|format(record.actual_work_hours) }}小时 {{ "%.1f"|format(weekly_record.actual_work_hours) }}小时
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
@ -115,7 +106,7 @@
班内工作时长 班内工作时长
</div> </div>
<div class="h5 mb-0 font-weight-bold text-gray-800"> <div class="h5 mb-0 font-weight-bold text-gray-800">
{{ "%.1f"|format(record.class_work_hours) }}小时 {{ "%.1f"|format(weekly_record.class_work_hours) }}小时
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
@ -131,11 +122,13 @@
<div class="card-body"> <div class="card-body">
<div class="row no-gutters align-items-center"> <div class="row no-gutters align-items-center">
<div class="col mr-2"> <div class="col mr-2">
{# 🔥 修改:标题改为“缺勤次数” #}
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1"> <div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
旷工天 缺勤次
</div> </div>
<div class="h5 mb-0 font-weight-bold text-gray-800"> <div class="h5 mb-0 font-weight-bold text-gray-800">
{{ record.absent_days }}天 {# 🔥 修改:使用 weekly_record.absent_count单位改为“次” #}
{{ weekly_record.absent_count if weekly_record.absent_count is defined else 0 }}次
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
@ -155,7 +148,7 @@
加班时长 加班时长
</div> </div>
<div class="h5 mb-0 font-weight-bold text-gray-800"> <div class="h5 mb-0 font-weight-bold text-gray-800">
{{ "%.1f"|format(record.overtime_hours) }}小时 {{ "%.1f"|format(weekly_record.overtime_hours) }}小时
</div> </div>
</div> </div>
<div class="col-auto"> <div class="col-auto">
@ -172,7 +165,7 @@
<div class="card-header py-3"> <div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary"> <h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-list me-2"></i>每日考勤明细 <i class="fas fa-list me-2"></i>每日考勤明细
<small class="text-muted">(点击详情按钮查看详细时段信息)</small> <small class="text-muted">(点击日期查看详细时段信息)</small>
</h6> </h6>
</div> </div>
<div class="card-body"> <div class="card-body">
@ -194,10 +187,7 @@
<tbody> <tbody>
{% for detail in daily_details %} {% for detail in daily_details %}
<tr class="{% if '迟到' in detail.status %}table-warning{% elif detail.status == '缺勤' %}table-danger{% endif %}"> <tr class="{% if '迟到' in detail.status %}table-warning{% elif detail.status == '缺勤' %}table-danger{% endif %}">
<td> <td>{{ detail.attendance_date.strftime('%m-%d') }}</td>
<strong>{{ detail.attendance_date.strftime('%m-%d') }}</strong>
<small class="d-block text-muted">{{ detail.attendance_date.strftime('%Y') }}</small>
</td>
<td> <td>
{% set weekday = detail.attendance_date.weekday() %} {% set weekday = detail.attendance_date.weekday() %}
{% if weekday == 0 %}周一 {% if weekday == 0 %}周一
@ -208,10 +198,6 @@
{% elif weekday == 5 %}周六 {% elif weekday == 5 %}周六
{% else %}周日 {% else %}周日
{% endif %} {% endif %}
{% if weekday >= 5 %}
<small class="badge bg-info">休息日</small>
{% endif %}
</td> </td>
<td> <td>
{% if detail.status == '正常' %} {% if detail.status == '正常' %}
@ -232,14 +218,14 @@
</td> </td>
<td> <td>
{% if detail.check_in_time %} {% if detail.check_in_time %}
<span class="badge bg-primary">{{ detail.check_in_time.strftime('%H:%M') }}</span> {{ detail.check_in_time.strftime('%H:%M') }}
{% else %} {% else %}
<span class="text-muted">未打卡</span> <span class="text-muted">未打卡</span>
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% if detail.check_out_time %} {% if detail.check_out_time %}
<span class="badge bg-success">{{ detail.check_out_time.strftime('%H:%M') }}</span> {{ detail.check_out_time.strftime('%H:%M') }}
{% else %} {% else %}
<span class="text-muted">未打卡</span> <span class="text-muted">未打卡</span>
{% endif %} {% endif %}
@ -273,8 +259,8 @@
</table> </table>
</div> </div>
{% else %} {% else %}
<div class="text-center py-5"> <div class="text-center py-3">
<i class="fas fa-calendar-times fa-4x text-muted mb-3"></i> <i class="fas fa-calendar-times fa-3x text-muted mb-3"></i>
<h5 class="text-muted">暂无每日考勤明细</h5> <h5 class="text-muted">暂无每日考勤明细</h5>
<p class="text-muted">该考勤周期内没有详细的打卡记录</p> <p class="text-muted">该考勤周期内没有详细的打卡记录</p>
</div> </div>
@ -282,7 +268,7 @@
</div> </div>
</div> </div>
<!-- 统计分析和历史对比 --> <!-- 统计分析和历史对比的其他部分... -->
<div class="row mb-4"> <div class="row mb-4">
<div class="col-md-6"> <div class="col-md-6">
<div class="card shadow"> <div class="card shadow">
@ -307,8 +293,9 @@
</div> </div>
<div class="col-3"> <div class="col-3">
<div class="border-end"> <div class="border-end">
<h6 class="text-danger">{{ absent_days }}</h6> {# 🔥 修改:标题改为“缺勤次数”,数据源改为 weekly_record.absent_count #}
<small class="text-muted">缺勤天数</small> <h6 class="text-danger">{{ weekly_record.absent_count if weekly_record.absent_count is defined else 0 }}</h6>
<small class="text-muted">缺勤次数</small>
</div> </div>
</div> </div>
<div class="col-3"> <div class="col-3">
@ -316,23 +303,6 @@
<small class="text-muted">日均时长</small> <small class="text-muted">日均时长</small>
</div> </div>
</div> </div>
<!-- 出勤率计算 -->
<hr>
<div class="row text-center">
<div class="col-4">
<h6 class="text-primary">{{ "%.1f"|format((present_days / max(total_days, 1) * 100)) }}%</h6>
<small class="text-muted">出勤率</small>
</div>
<div class="col-4">
<h6 class="text-success">{{ "%.1f"|format((record.class_work_hours / max(record.actual_work_hours, 1) * 100)) }}%</h6>
<small class="text-muted">班内工作率</small>
</div>
<div class="col-4">
<h6 class="text-info">{{ total_days }}</h6>
<small class="text-muted">考勤天数</small>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -345,19 +315,19 @@
</h6> </h6>
</div> </div>
<div class="card-body"> <div class="card-body">
{% if recent_records %} {% if historical_records %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-sm"> <table class="table table-sm">
<thead> <thead>
<tr> <tr>
<th>周期</th> <th>周期</th>
<th>出勤时长</th> <th>出勤时长</th>
<th>旷工天数</th> {# 🔥 修改:表头改为“缺勤次数” #}
<th>对比</th> <th>缺勤次数</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for record_item in recent_records %} {% for record_item in historical_records %}
<tr> <tr>
<td> <td>
<small>{{ record_item.week_start_date.strftime('%m-%d') }}</small> <small>{{ record_item.week_start_date.strftime('%m-%d') }}</small>
@ -366,20 +336,12 @@
<span class="badge bg-primary">{{ "%.1f"|format(record_item.actual_work_hours) }}h</span> <span class="badge bg-primary">{{ "%.1f"|format(record_item.actual_work_hours) }}h</span>
</td> </td>
<td> <td>
{% if record_item.absent_days > 0 %} {# 🔥 修改:使用 record_item.absent_count单位改为“次” #}
<span class="badge bg-warning">{{ record_item.absent_days }}</span> {% set absent_count = record_item.absent_count if record_item.absent_count is defined else 0 %}
{% if absent_count > 0 %}
<span class="badge bg-warning">{{ absent_count }}次</span>
{% else %} {% else %}
<span class="badge bg-success">0</span> <span class="badge bg-success">0次</span>
{% endif %}
</td>
<td>
{% set diff = record.actual_work_hours - record_item.actual_work_hours %}
{% if diff > 0 %}
<small class="text-success">+{{ "%.1f"|format(diff) }}h</small>
{% elif diff < 0 %}
<small class="text-danger">{{ "%.1f"|format(diff) }}h</small>
{% else %}
<small class="text-muted">-</small>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
@ -394,27 +356,11 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 操作建议 -->
{% if late_days > 0 or absent_days > 0 %}
<div class="alert alert-warning" role="alert">
<h6 class="alert-heading"><i class="fas fa-exclamation-triangle me-2"></i>考勤提醒</h6>
<p class="mb-0">
{% if late_days > 0 %}
本周有 <strong>{{ late_days }}</strong> 天迟到,
{% endif %}
{% if absent_days > 0 %}
<strong>{{ absent_days }}</strong> 天缺勤,
{% endif %}
请注意调整作息时间,保持良好的考勤记录。
</p>
</div>
{% endif %}
</div> </div>
<!-- 详细时段信息模态框 --> <!-- 详细时段信息模态框 -->
<div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="detailModalLabel" aria-hidden="true"> <div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="detailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered"> <div class="modal-dialog modal-lg modal-dialog-centered"> <!-- 添加 modal-dialog-centered -->
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title" id="detailModalLabel"> <h5 class="modal-title" id="detailModalLabel">
@ -472,54 +418,22 @@
border-radius: 0.35rem; border-radius: 0.35rem;
padding: 1rem; padding: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;
background-color: #f8f9fc;
} }
.period-header { .period-header {
font-weight: 600; font-weight: 600;
color: #5a5c69; color: #5a5c69;
margin-bottom: 0.75rem; margin-bottom: 0.5rem;
padding-bottom: 0.5rem; }
border-bottom: 1px solid #e3e6f0;
.badge.bg-orange {
background-color: #fd7e14 !important;
} }
.time-info { .time-info {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.badge.bg-orange {
background-color: #fd7e14 !important;
color: white;
}
.time-info > div {
flex: 1;
min-width: 120px;
}
/* 高亮迟到和缺勤行 */
.table-warning {
--bs-table-accent-bg: rgba(255, 193, 7, 0.1);
}
.table-danger {
--bs-table-accent-bg: rgba(220, 53, 69, 0.1);
}
@media (max-width: 768px) {
.time-info {
flex-direction: column;
align-items: flex-start;
}
.time-info > div {
width: 100%;
margin-bottom: 0.5rem;
}
} }
</style> </style>
{% endblock %} {% endblock %}
@ -529,7 +443,7 @@
function showDetailModal(detailId, date, remarksJson) { function showDetailModal(detailId, date, remarksJson) {
document.getElementById('modalDate').textContent = date; document.getElementById('modalDate').textContent = date;
console.log('调用showDetailModal', detailId, date, remarksJson); console.log('调用showDetailModal', detailId, date, remarksJson); // 调试信息
let detailsData = null; let detailsData = null;
@ -537,7 +451,7 @@ function showDetailModal(detailId, date, remarksJson) {
if (remarksJson && remarksJson.startsWith('{')) { if (remarksJson && remarksJson.startsWith('{')) {
const parsed = JSON.parse(remarksJson); const parsed = JSON.parse(remarksJson);
detailsData = parsed.details; detailsData = parsed.details;
console.log('解析的详细数据:', detailsData); console.log('解析的详细数据:', detailsData); // 调试信息
} }
} catch (e) { } catch (e) {
console.error('解析详细信息失败:', e); console.error('解析详细信息失败:', e);
@ -545,15 +459,15 @@ function showDetailModal(detailId, date, remarksJson) {
if (!detailsData) { if (!detailsData) {
document.getElementById('detailContent').innerHTML = document.getElementById('detailContent').innerHTML =
'<div class="alert alert-info"><i class="fas fa-info-circle me-2"></i>暂无详细时段信息</div>'; '<p class="text-muted">暂无详细时段信息</p><p class="text-muted">原始数据: ' + remarksJson + '</p>';
} else { } else {
let html = ''; let html = '';
// 显示各个时段的详情 // 显示各个时段的详情
const periods = [ const periods = [
{ key: 'morning', name: '早上时段', time: '09:45-11:30', icon: 'fa-sun' }, { key: 'morning', name: '早上时段', time: '09:45-11:30' },
{ key: 'afternoon', name: '下午时段', time: '13:30-18:30', icon: 'fa-cloud-sun' }, { key: 'afternoon', name: '下午时段', time: '13:30-18:30' },
{ key: 'evening', name: '晚上时段', time: '19:00-23:30', icon: 'fa-moon' } { key: 'evening', name: '晚上时段', time: '19:00-23:30' }
]; ];
periods.forEach(period => { periods.forEach(period => {
@ -562,8 +476,7 @@ function showDetailModal(detailId, date, remarksJson) {
html += ` html += `
<div class="period-card"> <div class="period-card">
<div class="period-header"> <div class="period-header">
<i class="fas ${period.icon} me-2"></i>${period.name} <i class="fas fa-clock me-2"></i>${period.name} (${period.time})
<small class="text-muted">(${period.time})</small>
</div> </div>
<div class="time-info"> <div class="time-info">
<div> <div>
@ -571,14 +484,14 @@ function showDetailModal(detailId, date, remarksJson) {
<span class="badge ${getStatusClass(data.status, 'in')}"> <span class="badge ${getStatusClass(data.status, 'in')}">
${data.in || '未打卡'} ${data.in || '未打卡'}
</span> </span>
${data.late_minutes ? `<small class="text-warning d-block">(迟到${data.late_minutes}分钟)</small>` : ''} ${data.late_minutes ? `<small class="text-warning">(迟到${data.late_minutes}分钟)</small>` : ''}
</div> </div>
<div> <div>
<strong>签退:</strong> <strong>签退:</strong>
<span class="badge ${getStatusClass(data.status, 'out')}"> <span class="badge ${getStatusClass(data.status, 'out')}">
${data.out || '未打卡'} ${data.out || '未打卡'}
</span> </span>
${data.early_minutes ? `<small class="text-warning d-block">(早退${data.early_minutes}分钟)</small>` : ''} ${data.early_minutes ? `<small class="text-warning">(早退${data.early_minutes}分钟)</small>` : ''}
</div> </div>
<div> <div>
<strong>工时:</strong> <strong>工时:</strong>
@ -595,7 +508,7 @@ function showDetailModal(detailId, date, remarksJson) {
html += ` html += `
<div class="period-card"> <div class="period-card">
<div class="period-header"> <div class="period-header">
<i class="fas fa-business-time me-2"></i>周末加班 <i class="fas fa-moon me-2"></i>周末加班
</div> </div>
<div class="time-info"> <div class="time-info">
<div> <div>
@ -616,7 +529,7 @@ function showDetailModal(detailId, date, remarksJson) {
} }
if (html === '') { if (html === '') {
html = '<div class="alert alert-warning"><i class="fas fa-exclamation-triangle me-2"></i>该日期没有详细的时段打卡信息</div>'; html = '<p class="text-muted">该日期没有详细的时段打卡信息</p>';
} }
document.getElementById('detailContent').innerHTML = html; document.getElementById('detailContent').innerHTML = html;
@ -640,12 +553,7 @@ function calculatePeriodHours(startTime, endTime) {
try { try {
const start = new Date(`2000-01-01 ${startTime}:00`); const start = new Date(`2000-01-01 ${startTime}:00`);
const end = new Date(`2000-01-01 ${endTime}:00`); const end = new Date(`2000-01-01 ${endTime}:00`);
let diff = (end - start) / (1000 * 60 * 60); const diff = (end - start) / (1000 * 60 * 60);
// 处理跨天情况
if (diff < 0) {
diff += 24;
}
if (diff > 0) { if (diff > 0) {
return `<span class="badge bg-primary">${diff.toFixed(1)}h</span>`; return `<span class="badge bg-primary">${diff.toFixed(1)}h</span>`;
@ -657,17 +565,13 @@ function calculatePeriodHours(startTime, endTime) {
return '<span class="text-muted">-</span>'; return '<span class="text-muted">-</span>';
} }
// 页面加载完成后初始化 // 测试函数
document.addEventListener('DOMContentLoaded', function() { function testModal() {
console.log('学生考勤详情页面已加载'); console.log('测试模态框');
document.getElementById('modalDate').textContent = '测试日期';
// 显示考勤统计 document.getElementById('detailContent').innerHTML = '<p>测试内容</p>';
console.log('考勤统计:', { const modal = new bootstrap.Modal(document.getElementById('detailModal'));
正常天数: {{ present_days }}, modal.show();
迟到天数: {{ late_days }}, }
缺勤天数: {{ absent_days }},
日均时长: {{ "%.1f"|format(avg_daily_hours) }}
});
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -58,8 +58,8 @@
<div class="card-body"> <div class="card-body">
<div class="d-flex justify-content-between"> <div class="d-flex justify-content-between">
<div> <div>
<h4 class="card-title">{{ total_absent_days }}</h4> <h4 class="card-title">{{ total_absent_count }}</h4>
<p class="card-text">旷工天</p> <p class="card-text">缺勤次</p>
</div> </div>
<div class="align-self-center"> <div class="align-self-center">
<i class="fas fa-exclamation-triangle fa-2x opacity-75"></i> <i class="fas fa-exclamation-triangle fa-2x opacity-75"></i>
@ -108,7 +108,7 @@
<th>周次</th> <th>周次</th>
<th>实际工作时长</th> <th>实际工作时长</th>
<th>班内工作时长</th> <th>班内工作时长</th>
<th>旷工天</th> <th>缺勤次</th>
<th>加班时长</th> <th>加班时长</th>
</tr> </tr>
</thead> </thead>
@ -128,7 +128,7 @@
</td> </td>
<td> <td>
{% if record.absent_days > 0 %} {% if record.absent_days > 0 %}
<span class="badge bg-danger">{{ record.absent_days }}天</span> <span class="badge bg-danger">{{ record.absent_count if record.absent_count is defined else 0 }}次</span>
{% else %} {% else %}
<span class="badge bg-success">0天</span> <span class="badge bg-success">0天</span>
{% endif %} {% endif %}

View File

@ -119,8 +119,8 @@
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
<div class="text-center"> <div class="text-center">
<h4 class="text-danger">{{ all_time_stats.total_absent_days }}</h4> <h4 class="text-danger">{{ all_time_stats.total_absent_count }}</h4>
<small class="text-muted">旷工天</small> <small class="text-muted">缺勤次</small>
</div> </div>
</div> </div>
<div class="col-md-2"> <div class="col-md-2">
@ -267,7 +267,7 @@
<th>实际工作时长</th> <th>实际工作时长</th>
<th>班内工作时长</th> <th>班内工作时长</th>
<th>加班时长</th> <th>加班时长</th>
<th>旷工天</th> <th>缺勤次</th>
<th>考勤状态</th> <th>考勤状态</th>
<th>操作</th> <th>操作</th>
</tr> </tr>
@ -298,7 +298,7 @@
</td> </td>
<td> <td>
{% if record.absent_days > 0 %} {% if record.absent_days > 0 %}
<span class="badge bg-danger">{{ record.absent_days }}天</span> <span class="badge bg-danger">{{ record.absent_count if record.absent_count is defined else 0 }}次</span>
{% else %} {% else %}
<span class="badge bg-success">0天</span> <span class="badge bg-success">0天</span>
{% endif %} {% endif %}