CHM_attendance/app/templates/admin/statistics.html
superlishunqin e7fa4bc030 first commit
2025-06-11 19:56:34 +08:00

431 lines
15 KiB
HTML

{% extends 'layout/base.html' %}
{% block title %}统计报表 - CHM考勤管理系统{% endblock %}
{% block extra_css %}
<style>
.chart-container canvas {
max-height: 300px !important;
}
.chart-container {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
min-height: 350px; /* 确保容器有足够高度 */
}
.statistics-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
}
.grade-section {
background: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.grade-title {
color: #495057;
border-bottom: 2px solid #007bff;
padding-bottom: 10px;
margin-bottom: 20px;
}
.student-card {
background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 15px;
margin-bottom: 10px;
transition: all 0.3s ease;
}
.student-card:hover {
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.stats-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
}
.stat-item {
text-align: center;
flex: 1;
}
.stat-value {
font-size: 1.2em;
font-weight: bold;
color: #007bff;
}
.stat-label {
font-size: 0.9em;
color: #6c757d;
}
.search-section {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.chart-container {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="fas fa-chart-bar me-2"></i>统计报表</h2>
<div>
<a href="{{ url_for('admin.export_statistics') }}" class="btn btn-success">
<i class="fas fa-download me-1"></i>导出数据
</a>
</div>
</div>
</div>
</div>
<!-- 总体统计卡片 -->
<div class="row mb-4">
<div class="col-md-3">
<div class="statistics-card">
<div class="text-center">
<h3>{{ overall_stats.total_students }}</h3>
<p class="mb-0">总学生数</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="statistics-card">
<div class="text-center">
<h3>{{ "%.1f"|format(overall_stats.total_work_hours) }}</h3>
<p class="mb-0">总出勤时长(小时)</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="statistics-card">
<div class="text-center">
<h3>{{ overall_stats.total_absent_days }}</h3>
<p class="mb-0">总缺勤天数</p>
</div>
</div>
</div>
<div class="col-md-3">
<div class="statistics-card">
<div class="text-center">
<h3>{{ overall_stats.total_late_count }}</h3>
<p class="mb-0">总迟到次数</p>
</div>
</div>
</div>
</div>
<!-- 搜索和筛选 -->
<div class="search-section">
<form method="GET" class="row g-3">
<div class="col-md-3">
<label class="form-label">搜索学生</label>
<input type="text" class="form-control" name="search"
value="{{ search }}" placeholder="姓名或学号">
</div>
<div class="col-md-2">
<label class="form-label">年级</label>
<select class="form-select" name="grade">
<option value="">全部年级</option>
{% for grade in grades %}
<option value="{{ grade }}" {{ 'selected' if selected_grade == grade|string }}>
{% if grade == 1 %}研一{% elif grade == 2 %}研二{% elif grade == 3 %}研三{% else %}{{ grade }}年级{% endif %}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label class="form-label">学院</label>
<select class="form-select" name="college">
<option value="">全部学院</option>
{% for college in colleges %}
<option value="{{ college }}" {{ 'selected' if selected_college == college }}>{{ college }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-2">
<label class="form-label">导师</label>
<select class="form-select" name="supervisor">
<option value="">全部导师</option>
{% for supervisor in supervisors %}
<option value="{{ supervisor }}" {{ 'selected' if selected_supervisor == supervisor }}>{{ supervisor }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label class="form-label">时间范围</label>
<div class="row">
<div class="col-6">
<input type="date" class="form-control" name="start_date" value="{{ start_date }}">
</div>
<div class="col-6">
<input type="date" class="form-control" name="end_date" value="{{ end_date }}">
</div>
</div>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">
<i class="fas fa-search me-1"></i>筛选
</button>
<a href="{{ url_for('admin.statistics') }}" class="btn btn-outline-secondary ms-2">
<i class="fas fa-undo me-1"></i>重置
</a>
</div>
</form>
</div>
<!-- 按年级分组的学生统计 -->
<div class="row">
<div class="col-12">
{% for grade_label, students in grade_groups.items() %}
<div class="grade-section">
<h4 class="grade-title">
<i class="fas fa-graduation-cap me-2"></i>{{ grade_label }} ({{ students|length }}人)
</h4>
<div class="row">
{% for student in students %}
<div class="col-md-6 col-lg-4 mb-3">
<div class="student-card">
<div class="d-flex justify-content-between align-items-start">
<div>
<h6 class="mb-1">
<a href="{{ url_for('admin.student_detail', student_number=student.student_number) }}"
class="text-decoration-none">
{{ student.name }}
</a>
</h6>
<small class="text-muted">{{ student.student_number }}</small>
</div>
<div class="text-end">
{% if student.total_work_hours >= 200 %}
<span class="badge bg-success">优秀</span>
{% elif student.total_work_hours >= 100 %}
<span class="badge bg-primary">良好</span>
{% elif student.total_work_hours >= 50 %}
<span class="badge bg-warning">一般</span>
{% else %}
<span class="badge bg-danger">待改进</span>
{% endif %}
</div>
</div>
<div class="stats-row mt-3">
<div class="stat-item">
<div class="stat-value">{{ "%.1f"|format(student.total_work_hours) }}</div>
<div class="stat-label">总工时</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ student.total_absent_days }}</div>
<div class="stat-label">缺勤天数</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ student.total_late_count }}</div>
<div class="stat-label">迟到次数</div>
</div>
<div class="stat-item">
<div class="stat-value">{{ student.avg_weekly_hours }}</div>
<div class="stat-label">周均工时</div>
</div>
</div>
<div class="mt-2">
<small class="text-muted">
<i class="fas fa-building me-1"></i>{{ student.college or '未设置' }} |
<i class="fas fa-user-tie me-1"></i>{{ student.supervisor or '未设置' }}
</small>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
{% if not grade_groups %}
<div class="text-center py-5">
<i class="fas fa-search fa-3x text-muted mb-3"></i>
<h5 class="text-muted">没有找到符合条件的学生</h5>
</div>
{% endif %}
</div>
</div>
<!-- 图表区域 -->
<div class="row mt-4">
<div class="col-md-6">
<div class="chart-container">
<h5><i class="fas fa-chart-line me-2"></i>月度考勤趋势</h5>
<canvas id="monthlyChart" width="400" height="200"></canvas>
</div>
</div>
<div class="col-md-6">
<div class="chart-container">
<h5><i class="fas fa-chart-pie me-2"></i>学院分布</h5>
<canvas id="collegeChart" width="400" height="200"></canvas>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// 检查数据
console.log('月度统计数据:', [{% for stat in monthly_stats %}{ month: '{{ stat.month }}', hours: {{ stat.total_hours or 0 }} }{% if not loop.last %},{% endif %}{% endfor %}]);
console.log('学院统计数据:', [{% for stat in college_stats %}{ college: '{{ stat.college or "未设置" }}', count: {{ stat.student_count }} }{% if not loop.last %},{% endif %}{% endfor %}]);
// 月度趋势图
const monthlyCtx = document.getElementById('monthlyChart').getContext('2d');
// 准备月度数据
const monthlyData = [{% for stat in monthly_stats %}{{ stat.total_hours or 0 }}{% if not loop.last %},{% endif %}{% endfor %}];
const monthlyLabels = [{% for stat in monthly_stats %}'{{ stat.month }}'{% if not loop.last %},{% endif %}{% endfor %}];
console.log('图表数据:', monthlyData);
console.log('图表标签:', monthlyLabels);
const monthlyChart = new Chart(monthlyCtx, {
type: 'line',
data: {
labels: monthlyLabels,
datasets: [{
label: '总工时',
data: monthlyData,
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.1,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: '工时(小时)'
}
},
x: {
title: {
display: true,
text: '月份'
}
}
},
plugins: {
title: {
display: true,
text: '月度工时统计'
},
legend: {
display: true,
position: 'top'
}
}
}
});
// 学院分布图
const collegeCtx = document.getElementById('collegeChart').getContext('2d');
// 准备学院数据
const collegeData = [{% for stat in college_stats %}{{ stat.student_count }}{% if not loop.last %},{% endif %}{% endfor %}];
const collegeLabels = [{% for stat in college_stats %}'{{ stat.college or "未设置" }}'{% if not loop.last %},{% endif %}{% endfor %}];
console.log('学院数据:', collegeData);
console.log('学院标签:', collegeLabels);
// 只有当有数据时才创建图表
if (collegeData.length > 0 && collegeData.some(d => d > 0)) {
const collegeChart = new Chart(collegeCtx, {
type: 'doughnut',
data: {
labels: collegeLabels,
datasets: [{
data: collegeData,
backgroundColor: [
'#FF6384',
'#36A2EB',
'#FFCE56',
'#4BC0C0',
'#9966FF',
'#FF9F40',
'#C7C7C7',
'#FF6384'
],
borderWidth: 2,
borderColor: '#fff'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
title: {
display: true,
text: '学院学生分布'
},
legend: {
position: 'bottom',
labels: {
padding: 20,
usePointStyle: true
}
}
}
}
});
} else {
// 如果没有数据,显示提示信息
collegeCtx.font = '16px Arial';
collegeCtx.textAlign = 'center';
collegeCtx.fillText('暂无数据', collegeCtx.canvas.width / 2, collegeCtx.canvas.height / 2);
}
// 如果月度数据为空,显示提示
if (monthlyData.length === 0 || monthlyData.every(d => d === 0)) {
const monthlyCanvas = document.getElementById('monthlyChart');
const ctx = monthlyCanvas.getContext('2d');
ctx.font = '16px Arial';
ctx.textAlign = 'center';
ctx.fillText('暂无月度数据', monthlyCanvas.width / 2, monthlyCanvas.height / 2);
}
</script>
{% endblock %}