384 lines
16 KiB
HTML
384 lines
16 KiB
HTML
{% extends 'layout/base.html' %}
|
||
|
||
{% block title %}管理员控制台 - CHM考勤管理系统{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container-fluid mt-4">
|
||
<!-- 页面标题 -->
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<h1 class="h3 mb-0">
|
||
<i class="fas fa-tachometer-alt me-2"></i>管理员控制台
|
||
</h1>
|
||
<div class="text-muted" id="current-time">
|
||
<i class="fas fa-clock me-1"></i>
|
||
加载中...
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 统计卡片 -->
|
||
<div class="row mb-4">
|
||
<div class="col-xl-3 col-md-6 mb-4">
|
||
<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_students or 0 }}
|
||
</div>
|
||
</div>
|
||
<div class="col-auto">
|
||
<i class="fas fa-users fa-2x text-gray-300"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-xl-3 col-md-6 mb-4">
|
||
<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">
|
||
{{ total_attendance_records or 0 }}
|
||
</div>
|
||
</div>
|
||
<div class="col-auto">
|
||
<i class="fas fa-calendar-check fa-2x text-gray-300"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="col-xl-3 col-md-6 mb-4">
|
||
<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">
|
||
{{ pending_leaves or 0 }}
|
||
</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-xl-3 col-md-6 mb-4">
|
||
<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">
|
||
{{ recent_records or 0 }}
|
||
</div>
|
||
</div>
|
||
<div class="col-auto">
|
||
<i class="fas fa-calendar-week fa-2x text-gray-300"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 内容区域 -->
|
||
<div class="row">
|
||
<!-- 学院统计 -->
|
||
<div class="col-xl-6 col-lg-6">
|
||
<div class="card shadow mb-4">
|
||
<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-university me-2"></i>学院分布
|
||
</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
{% if college_stats %}
|
||
<div class="table-responsive">
|
||
<table class="table table-sm">
|
||
<thead>
|
||
<tr>
|
||
<th>学院</th>
|
||
<th class="text-end">学生数</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for college, count in college_stats %}
|
||
<tr>
|
||
<td>{{ college or '未知学院' }}</td>
|
||
<td class="text-end">
|
||
<span class="badge bg-primary">{{ count }}</span>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<div class="text-center text-muted py-4">
|
||
<i class="fas fa-chart-bar fa-3x mb-3"></i>
|
||
<p>暂无数据</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 导师统计 -->
|
||
<div class="col-xl-6 col-lg-6">
|
||
<div class="card shadow mb-4">
|
||
<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-user-tie me-2"></i>导师排行(TOP 10)
|
||
</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
{% if supervisor_stats %}
|
||
<div class="table-responsive">
|
||
<table class="table table-sm">
|
||
<thead>
|
||
<tr>
|
||
<th>导师</th>
|
||
<th class="text-end">学生数</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for supervisor, count in supervisor_stats %}
|
||
<tr>
|
||
<td>{{ supervisor or '未知导师' }}</td>
|
||
<td class="text-end">
|
||
<span class="badge bg-success">{{ count }}</span>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<div class="text-center text-muted py-4">
|
||
<i class="fas fa-chart-bar fa-3x mb-3"></i>
|
||
<p>暂无数据</p>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 最近请假申请 -->
|
||
{% if recent_leaves %}
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<div class="card shadow mb-4">
|
||
<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-file-alt me-2"></i>最近请假申请
|
||
</h6>
|
||
<a href="{{ url_for('admin.pending_leaves') }}" class="btn btn-primary btn-sm">
|
||
<i class="fas fa-eye me-1"></i>查看全部
|
||
</a>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="table-responsive">
|
||
<table class="table table-hover">
|
||
<thead>
|
||
<tr>
|
||
<th>学号</th>
|
||
<th>请假日期</th>
|
||
<th>请假原因</th>
|
||
<th>申请时间</th>
|
||
<th class="text-center">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for leave in recent_leaves %}
|
||
<tr>
|
||
<td>{{ leave.student_number }}</td>
|
||
<td>
|
||
{{ leave.leave_start_date.strftime('%Y-%m-%d') }}
|
||
{% if leave.leave_start_date != leave.leave_end_date %}
|
||
至 {{ leave.leave_end_date.strftime('%Y-%m-%d') }}
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<span class="text-truncate" style="max-width: 200px; display: inline-block;"
|
||
title="{{ leave.leave_reason }}">
|
||
{{ leave.leave_reason[:50] }}{% if leave.leave_reason|length > 50 %}...{% endif %}
|
||
</span>
|
||
</td>
|
||
<td>{{ leave.created_at.strftime('%m-%d %H:%M') }}</td>
|
||
<td class="text-center">
|
||
<div class="btn-group btn-group-sm">
|
||
<button class="btn btn-success btn-sm"
|
||
onclick="approveLeave({{ leave.leave_id }})">
|
||
<i class="fas fa-check"></i>
|
||
</button>
|
||
<button class="btn btn-danger btn-sm"
|
||
onclick="rejectLeave({{ leave.leave_id }})">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- 快捷操作 -->
|
||
<div class="row">
|
||
<div class="col-12">
|
||
<div class="card shadow mb-4">
|
||
<div class="card-header py-3">
|
||
<h6 class="m-0 font-weight-bold text-primary">
|
||
<i class="fas fa-bolt me-2"></i>快捷操作
|
||
</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<div class="col-md-3 mb-3">
|
||
<a href="{{ url_for('admin.student_list') }}" class="btn btn-outline-primary btn-block h-100">
|
||
<i class="fas fa-users fa-2x mb-2"></i><br>
|
||
<span>学生管理</span>
|
||
</a>
|
||
</div>
|
||
<div class="col-md-3 mb-3">
|
||
<a href="{{ url_for('admin.attendance_management') }}" class="btn btn-outline-success btn-block h-100">
|
||
<i class="fas fa-calendar-check fa-2x mb-2"></i><br>
|
||
<span>考勤管理</span>
|
||
</a>
|
||
</div>
|
||
<div class="col-md-3 mb-3">
|
||
<a href="{{ url_for('admin.upload_attendance') }}" class="btn btn-outline-info btn-block h-100">
|
||
<i class="fas fa-upload fa-2x mb-2"></i><br>
|
||
<span>上传数据</span>
|
||
</a>
|
||
</div>
|
||
<div class="col-md-3 mb-3">
|
||
<a href="{{ url_for('admin.statistics') }}" class="btn btn-outline-warning btn-block h-100">
|
||
<i class="fas fa-chart-bar fa-2x mb-2"></i><br>
|
||
<span>统计报表</span>
|
||
</a>
|
||
</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-warning {
|
||
border-left: 0.25rem solid #f6c23e !important;
|
||
}
|
||
.border-left-info {
|
||
border-left: 0.25rem solid #36b9cc !important;
|
||
}
|
||
|
||
.text-xs {
|
||
font-size: .7rem;
|
||
}
|
||
|
||
.btn-block {
|
||
display: block;
|
||
width: 100%;
|
||
text-align: center;
|
||
padding: 1rem;
|
||
}
|
||
|
||
.text-gray-800 {
|
||
color: #5a5c69 !important;
|
||
}
|
||
|
||
.text-gray-300 {
|
||
color: #dddfeb !important;
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
// 显示当前时间
|
||
function updateCurrentTime() {
|
||
const now = new Date();
|
||
const timeString = `${now.getFullYear()}年${(now.getMonth()+1).toString().padStart(2,'0')}月${now.getDate().toString().padStart(2,'0')}日 ${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`;
|
||
document.getElementById('current-time').innerHTML = `<i class="fas fa-clock me-1"></i>${timeString}`;
|
||
}
|
||
|
||
function approveLeave(leaveId) {
|
||
if (confirm('确认批准这个请假申请吗?')) {
|
||
fetch(`/admin/leave/${leaveId}/approve`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
}
|
||
}).then(response => {
|
||
if (response.ok) {
|
||
location.reload();
|
||
} else {
|
||
alert('操作失败,请稍后重试');
|
||
}
|
||
}).catch(error => {
|
||
console.error('Error:', error);
|
||
alert('操作失败,请稍后重试');
|
||
});
|
||
}
|
||
}
|
||
|
||
function rejectLeave(leaveId) {
|
||
if (confirm('确认拒绝这个请假申请吗?')) {
|
||
fetch(`/admin/leave/${leaveId}/reject`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
}
|
||
}).then(response => {
|
||
if (response.ok) {
|
||
location.reload();
|
||
} else {
|
||
alert('操作失败,请稍后重试');
|
||
}
|
||
}).catch(error => {
|
||
console.error('Error:', error);
|
||
alert('操作失败,请稍后重试');
|
||
});
|
||
}
|
||
}
|
||
|
||
// 页面加载时更新时间,然后每分钟更新一次
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
updateCurrentTime();
|
||
setInterval(updateCurrentTime, 60000); // 每分钟更新一次
|
||
});
|
||
</script>
|
||
{% endblock %}
|