superlishunqin 3e6c8d353c SmartDSP
2025-06-12 00:38:27 +08:00

564 lines
21 KiB
HTML

{% extends 'layout/base.html' %}
{% block title %}个人统计 - SmartDSP考勤管理系统{% 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-chart-line 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>
<!-- 学生基本信息 -->
<div class="row mb-4">
<div class="col">
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-user me-2"></i>基本信息
</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3">
<strong>学号:</strong> {{ student.student_number }}
</div>
<div class="col-md-3">
<strong>姓名:</strong> {{ student.name }}
</div>
<div class="col-md-3">
<strong>年级:</strong> {{ student.grade }}级
</div>
<div class="col-md-3">
<strong>入学日期:</strong> {{ student.enrollment_date.strftime('%Y-%m-%d') if student.enrollment_date else '未设置' }}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 筛选器 -->
<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.statistics') }}" 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.statistics') }}" class="btn btn-outline-secondary">
<i class="fas fa-refresh me-1"></i>重置
</a>
</div>
</div>
</form>
</div>
</div>
<!-- 入学以来总体统计 -->
{% if all_time_stats %}
<div class="row mb-4">
<div class="col">
<div class="card shadow border-left-primary">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-graduation-cap me-2"></i>入学以来总体表现
</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-2">
<div class="text-center">
<h4 class="text-primary">{{ all_time_stats.attendance_weeks }}</h4>
<small class="text-muted">总考勤周数</small>
</div>
</div>
<div class="col-md-2">
<div class="text-center">
<h4 class="text-success">{{ "%.1f"|format(all_time_stats.total_work_hours) }}</h4>
<small class="text-muted">总工作时长(h)</small>
</div>
</div>
<div class="col-md-2">
<div class="text-center">
<h4 class="text-info">{{ "%.1f"|format(all_time_stats.total_class_hours) }}</h4>
<small class="text-muted">班内工作(h)</small>
</div>
</div>
<div class="col-md-2">
<div class="text-center">
<h4 class="text-warning">{{ all_time_stats.total_late_count }}</h4>
<small class="text-muted">迟到次数</small>
</div>
</div>
<div class="col-md-2">
<div class="text-center">
<h4 class="text-danger">{{ all_time_stats.total_absent_days }}</h4>
<small class="text-muted">旷工天数</small>
</div>
</div>
<div class="col-md-2">
<div class="text-center">
<h4 class="text-secondary">{{ "%.1f"|format(all_time_stats.attendance_rate) }}%</h4>
<small class="text-muted">出勤率</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
<!-- 当前筛选条件下的统计 -->
<div class="row mb-4">
<div class="col-md-3">
<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.attendance_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-3">
<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_work_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-3">
<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-3">
<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.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>
<!-- 图表展示区域 -->
<div class="row mb-4">
<!-- 月度统计图表 -->
<div class="col-lg-8">
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-chart-bar me-2"></i>月度考勤统计
</h6>
</div>
<div class="card-body">
<canvas id="monthlyChart" width="400" height="200"></canvas>
</div>
</div>
</div>
<!-- 最近趋势图表 -->
<div class="col-lg-4">
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-chart-line me-2"></i>最近12周趋势
</h6>
</div>
<div class="card-body">
<canvas id="trendChart" width="400" height="200"></canvas>
</div>
</div>
</div>
</div>
<!-- 详细记录表格 -->
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-table me-2"></i>详细考勤记录
</h6>
</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>
</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.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>
{% 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>
{% set performance_score = (record.actual_work_hours / 40 * 100) if record.actual_work_hours else 0 %}
{% if performance_score >= 80 %}
<span class="badge bg-success">优秀</span>
{% elif performance_score >= 60 %}
<span class="badge bg-warning">良好</span>
{% else %}
<span class="badge bg-danger">待改善</span>
{% endif %}
</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>
{% else %}
<div class="text-center py-5">
<i class="fas fa-chart-line fa-4x text-muted mb-3"></i>
<h5 class="text-muted">暂无统计数据</h5>
<p class="text-muted">当前筛选条件下没有找到考勤记录</p>
</div>
{% endif %}
</div>
</div>
<!-- 快捷操作 -->
<div class="row mt-4">
<div class="col">
<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-md-3 mb-2">
<a href="{{ url_for('student.attendance') }}" class="btn btn-outline-primary w-100">
<i class="fas fa-calendar-check me-2"></i>考勤记录
</a>
</div>
<div class="col-md-3 mb-2">
<a href="{{ url_for('auth.profile') }}" class="btn btn-outline-secondary w-100">
<i class="fas fa-user me-2"></i>个人资料
</a>
</div>
<div class="col-md-3 mb-2">
<button type="button" class="btn btn-outline-success w-100" onclick="exportStatistics()">
<i class="fas fa-download me-2"></i>导出统计
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</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;
}
.text-xs {
font-size: 0.7rem;
}
.card {
border: 0;
box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15) !important;
}
.table th {
background-color: #f8f9fc;
border-top: none;
font-weight: 600;
font-size: 0.85rem;
color: #5a5c69;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* 图表容器样式 */
canvas {
max-height: 300px;
}
</style>
{% endblock %}
{% block extra_js %}
<script>
// 月度统计图表
const monthlyData = {
labels: [
{% for stat in monthly_stats %}
'{{ stat.month }}',
{% endfor %}
],
datasets: [{
label: '总工作时长',
data: [
{% for stat in monthly_stats %}
{{ stat.total_hours or 0 }},
{% endfor %}
],
backgroundColor: 'rgba(78, 115, 223, 0.2)',
borderColor: 'rgba(78, 115, 223, 1)',
borderWidth: 2,
fill: true
}, {
label: '班内工作时长',
data: [
{% for stat in monthly_stats %}
{{ stat.class_hours or 0 }},
{% endfor %}
],
backgroundColor: 'rgba(28, 200, 138, 0.2)',
borderColor: 'rgba(28, 200, 138, 1)',
borderWidth: 2,
fill: true
}]
};
const monthlyCtx = document.getElementById('monthlyChart').getContext('2d');
const monthlyChart = new Chart(monthlyCtx, {
type: 'line',
data: monthlyData,
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '工作时长 (小时)'
}
},
x: {
title: {
display: true,
text: '月份'
}
}
},
plugins: {
legend: {
display: true,
position: 'top'
},
title: {
display: true,
text: '月度工作时长趋势'
}
}
}
});
// 最近12周趋势图表
const trendData = {
labels: [
{% for week in recent_weeks %}
'{{ week.week_start_date.strftime("%m/%d") }}',
{% endfor %}
],
datasets: [{
label: '周工作时长',
data: [
{% for week in recent_weeks %}
{{ week.actual_work_hours }},
{% endfor %}
],
backgroundColor: 'rgba(54, 185, 204, 0.2)',
borderColor: 'rgba(54, 185, 204, 1)',
borderWidth: 2,
tension: 0.1
}]
};
const trendCtx = document.getElementById('trendChart').getContext('2d');
const trendChart = new Chart(trendCtx, {
type: 'line',
data: trendData,
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '时长(h)'
}
}
},
plugins: {
legend: {
display: false
},
title: {
display: true,
text: '近期趋势'
}
}
}
});
// 导出统计功能
function exportStatistics() {
const params = new URLSearchParams(window.location.search);
params.set('export', 'excel');
const exportUrl = '{{ url_for("student.statistics") }}?' + params.toString();
const link = document.createElement('a');
link.href = exportUrl;
link.download = '个人考勤统计.xlsx';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// 页面加载完成后的初始化
document.addEventListener('DOMContentLoaded', function() {
console.log('个人统计页面已加载');
// 显示统计摘要
{% if total_stats %}
console.log('统计摘要:', {
考勤周数: {{ total_stats.attendance_weeks }},
总工作时长: {{ total_stats.total_work_hours }},
迟到次数: {{ total_stats.total_late_count }},
周均时长: {{ total_stats.avg_weekly_hours }}
});
{% endif %}
});
</script>
{% endblock %}