superlishunqin e7fa4bc030 first commit
2025-06-11 19:56:34 +08:00

540 lines
22 KiB
HTML

{% extends 'layout/base.html' %}
{% block title %}我的考勤 - CHM考勤管理系统{% endblock %}
{% block content %}
<div class="container-fluid mt-4">
<!-- 页面标题 -->
<div class="row mb-4">
<div class="col">
<h2 class="fw-bold text-primary">
<i class="fas fa-calendar-check me-2"></i>我的考勤记录
</h2>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="{{ url_for('student.dashboard') }}">首页</a></li>
<li class="breadcrumb-item active">考勤记录</li>
</ol>
</nav>
</div>
</div>
<!-- 统计卡片 -->
{% if total_stats %}
<div class="row mb-4">
<div class="col-md-2">
<div class="card border-left-primary shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
总考勤周数
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">
{{ total_stats.total_weeks }}周
</div>
</div>
<div class="col-auto">
<i class="fas fa-calendar-week fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-2">
<div class="card border-left-success shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">
总出勤时长
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">
{{ "%.1f"|format(total_stats.total_actual_hours) }}h
</div>
</div>
<div class="col-auto">
<i class="fas fa-clock fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-2">
<div class="card border-left-info shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">
班内工作
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">
{{ "%.1f"|format(total_stats.total_class_hours) }}h
</div>
</div>
<div class="col-auto">
<i class="fas fa-briefcase fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-2">
<div class="card border-left-warning shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-warning text-uppercase mb-1">
迟到次数
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">
{{ total_stats.total_late_count }}次
</div>
</div>
<div class="col-auto">
<i class="fas fa-exclamation-triangle fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-2">
<div class="card border-left-danger shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-danger text-uppercase mb-1">
旷工天数
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">
{{ total_stats.total_absent_days }}天
</div>
</div>
<div class="col-auto">
<i class="fas fa-times-circle fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-2">
<div class="card border-left-secondary shadow h-100 py-2">
<div class="card-body">
<div class="row no-gutters align-items-center">
<div class="col mr-2">
<div class="text-xs font-weight-bold text-secondary text-uppercase mb-1">
周均时长
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">
{{ "%.1f"|format(total_stats.avg_weekly_hours) }}h
</div>
</div>
<div class="col-auto">
<i class="fas fa-chart-line fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<!-- 筛选器 -->
<div class="card mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-filter me-2"></i>筛选条件
</h6>
</div>
<div class="card-body">
<form method="GET" action="{{ url_for('student.attendance') }}" class="row g-3">
<div class="col-md-4">
<label for="start_date" class="form-label">开始日期</label>
<input type="date" class="form-control" id="start_date" name="start_date"
value="{{ start_date or '' }}">
</div>
<div class="col-md-4">
<label for="end_date" class="form-label">结束日期</label>
<input type="date" class="form-control" id="end_date" name="end_date"
value="{{ end_date or '' }}">
</div>
<div class="col-md-4 d-flex align-items-end">
<div class="d-grid gap-2 d-md-flex w-100">
<button type="submit" class="btn btn-primary">
<i class="fas fa-search me-1"></i>筛选
</button>
<a href="{{ url_for('student.attendance') }}" class="btn btn-outline-secondary">
<i class="fas fa-refresh me-1"></i>重置
</a>
{% if attendance_records %}
<button type="button" class="btn btn-outline-info" onclick="exportData()">
<i class="fas fa-download me-1"></i>导出
</button>
{% endif %}
</div>
</div>
</form>
</div>
</div>
<!-- 考勤记录表格 -->
<div class="card shadow">
<div class="card-header py-3 d-flex justify-content-between align-items-center">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-table me-2"></i>考勤记录列表
</h6>
{% if attendance_records %}
<span class="badge bg-info">
共 {{ pagination.total }} 条记录
</span>
{% endif %}
</div>
<div class="card-body">
{% if attendance_records %}
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th>周次</th>
<th>实际工作时长</th>
<th>班内工作时长</th>
<th>迟到次数</th>
<th>旷工天数</th>
<th>加班时长</th>
<th>记录时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for record in attendance_records %}
<tr>
<td>
<div>
<strong>{{ record.week_start_date.strftime('%Y-%m-%d') }}</strong>
<small class="d-block text-muted">
至 {{ record.week_end_date.strftime('%Y-%m-%d') }}
</small>
</div>
</td>
<td>
<span class="badge bg-primary">{{ "%.1f"|format(record.actual_work_hours) }}h</span>
</td>
<td>
<span class="badge bg-success">{{ "%.1f"|format(record.class_work_hours) }}h</span>
</td>
<td>
{% if record.late_count > 0 %}
<span class="badge bg-warning">{{ record.late_count }}次</span>
{% else %}
<span class="badge bg-success">0次</span>
{% endif %}
</td>
<td>
{% if record.absent_days > 0 %}
<span class="badge bg-danger">{{ record.absent_days }}天</span>
{% else %}
<span class="badge bg-success">0天</span>
{% endif %}
</td>
<td>
{% if record.overtime_hours > 0 %}
<span class="badge bg-info">{{ "%.1f"|format(record.overtime_hours) }}h</span>
{% else %}
<span class="badge bg-secondary">0h</span>
{% endif %}
</td>
<td>
<small class="text-muted">
{{ record.created_at.strftime('%m-%d %H:%M') }}
</small>
</td>
<td>
<a href="{{ url_for('student.attendance_details', record_id=record.record_id) }}"
class="btn btn-sm btn-outline-primary">
<i class="fas fa-eye me-1"></i>查看详情
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- 分页 -->
{% if pagination.pages > 1 %}
<nav aria-label="考勤记录分页" class="mt-4">
<ul class="pagination justify-content-center">
{% if pagination.has_prev %}
<li class="page-item">
<a class="page-link" href="{{ url_for('student.attendance', page=pagination.prev_num, start_date=start_date, end_date=end_date) }}">
<i class="fas fa-chevron-left"></i>
</a>
</li>
{% endif %}
{% for page_num in pagination.iter_pages() %}
{% if page_num %}
{% if page_num != pagination.page %}
<li class="page-item">
<a class="page-link" href="{{ url_for('student.attendance', page=page_num, start_date=start_date, end_date=end_date) }}">
{{ page_num }}
</a>
</li>
{% else %}
<li class="page-item active">
<span class="page-link">{{ page_num }}</span>
</li>
{% endif %}
{% else %}
<li class="page-item disabled">
<span class="page-link"></span>
</li>
{% endif %}
{% endfor %}
{% if pagination.has_next %}
<li class="page-item">
<a class="page-link" href="{{ url_for('student.attendance', page=pagination.next_num, start_date=start_date, end_date=end_date) }}">
<i class="fas fa-chevron-right"></i>
</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<div class="text-center py-5">
<i class="fas fa-calendar-times fa-4x text-muted mb-3"></i>
<h5 class="text-muted">暂无考勤记录</h5>
<p class="text-muted">当前筛选条件下没有找到考勤记录</p>
<a href="{{ url_for('student.dashboard') }}" class="btn btn-primary">
<i class="fas fa-home me-2"></i>返回首页
</a>
</div>
{% endif %}
</div>
</div>
<!-- 快捷操作 -->
{% if attendance_records %}
<div class="row mt-4">
<div class="col-md-6">
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-chart-pie me-2"></i>本期统计分析
</h6>
</div>
<div class="card-body">
<div class="row text-center">
<div class="col-4">
<div class="border-end">
<h6 class="text-primary">{{ "%.1f"|format(total_stats.avg_weekly_hours) }}h</h6>
<small class="text-muted">周均出勤</small>
</div>
</div>
<div class="col-4">
<div class="border-end">
<h6 class="text-success">{{ "%.1f"|format((total_stats.total_class_hours / total_stats.total_actual_hours * 100) if total_stats.total_actual_hours > 0 else 0) }}%</h6>
<small class="text-muted">班内工作率</small>
</div>
</div>
<div class="col-4">
<h6 class="text-info">{{ "%.1f"|format((total_stats.total_overtime_hours / total_stats.total_weeks) if total_stats.total_weeks > 0 else 0) }}h</h6>
<small class="text-muted">周均加班</small>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-tools me-2"></i>快捷操作
</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-6 mb-2">
<a href="{{ url_for('student.statistics') }}" class="btn btn-outline-primary btn-block">
<i class="fas fa-chart-bar me-2"></i>统计报表
</a>
</div>
<div class="col-6 mb-2">
<a href="{{ url_for('auth.profile') }}" class="btn btn-outline-secondary btn-block">
<i class="fas fa-user me-2"></i>个人资料
</a>
</div>
<div class="col-6 mb-2">
<a href="{{ url_for('auth.change_password') }}" class="btn btn-outline-warning btn-block">
<i class="fas fa-key me-2"></i>修改密码
</a>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_css %}
<style>
.border-left-primary {
border-left: 0.25rem solid #4e73df !important;
}
.border-left-success {
border-left: 0.25rem solid #1cc88a !important;
}
.border-left-info {
border-left: 0.25rem solid #36b9cc !important;
}
.border-left-warning {
border-left: 0.25rem solid #f6c23e !important;
}
.border-left-danger {
border-left: 0.25rem solid #e74a3b !important;
}
.border-left-secondary {
border-left: 0.25rem solid #858796 !important;
}
.border-end {
border-right: 1px solid #dee2e6;
}
.text-xs {
font-size: 0.7rem;
}
.card {
border: 0;
box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15) !important;
}
.btn-block {
display: block;
width: 100%;
}
.table th {
background-color: #f8f9fc;
border-top: none;
font-weight: 600;
font-size: 0.85rem;
color: #5a5c69;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.badge {
font-size: 0.75rem;
}
/* 响应式调整 */
@media (max-width: 768px) {
.col-md-2 {
flex: 0 0 50%;
max-width: 50%;
margin-bottom: 1rem;
}
.table-responsive {
font-size: 0.8rem;
}
.badge {
font-size: 0.65rem;
}
}
</style>
{% endblock %}
{% block extra_js %}
<script>
function exportData() {
const params = new URLSearchParams(window.location.search);
params.set('export', 'excel');
// 构建导出URL
const exportUrl = '{{ url_for("student.attendance") }}?' + params.toString();
// 创建临时链接并触发下载
const link = document.createElement('a');
link.href = exportUrl;
link.download = '我的考勤记录.xlsx';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// 自动设置结束日期为开始日期的一个月后
document.getElementById('start_date').addEventListener('change', function() {
const startDate = new Date(this.value);
if (startDate && !document.getElementById('end_date').value) {
const endDate = new Date(startDate);
endDate.setMonth(startDate.getMonth() + 1);
document.getElementById('end_date').value = endDate.toISOString().split('T')[0];
}
});
// 页面加载完成后的初始化
document.addEventListener('DOMContentLoaded', function() {
console.log('学生考勤记录页面已加载');
// 显示统计信息的提示
{% if total_stats and total_stats.total_weeks > 0 %}
console.log('统计信息:', {
总周数: {{ total_stats.total_weeks }},
总出勤时长: {{ total_stats.total_actual_hours }},
迟到次数: {{ total_stats.total_late_count }},
周均时长: {{ total_stats.avg_weekly_hours }}
});
{% endif %}
});
// 如果有迟到记录,显示温馨提示
{% if total_stats and total_stats.total_late_count > 0 %}
setTimeout(function() {
if ({{ total_stats.total_late_count }} > 5) {
const toast = `
<div class="toast show position-fixed bottom-0 end-0 m-3" role="alert" style="z-index: 1055;">
<div class="toast-header">
<i class="fas fa-exclamation-triangle text-warning me-2"></i>
<strong class="me-auto">考勤提醒</strong>
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
</div>
<div class="toast-body">
您的迟到次数较多({{ total_stats.total_late_count }}次),请注意准时上班。
</div>
</div>
`;
document.body.insertAdjacentHTML('beforeend', toast);
// 5秒后自动关闭
setTimeout(function() {
const toastElement = document.querySelector('.toast');
if (toastElement) {
toastElement.remove();
}
}, 5000);
}
}, 1000);
{% endif %}
</script>
{% endblock %}