716 lines
32 KiB
HTML
716 lines
32 KiB
HTML
{% extends 'layout/base.html' %}
|
||
|
||
{% block title %}考勤管理 - SmartDSP考勤管理系统{% 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-calendar-check me-2"></i>考勤管理
|
||
</h1>
|
||
<div>
|
||
<a href="{{ url_for('admin.upload_attendance') }}" class="btn btn-primary me-2">
|
||
<i class="fas fa-upload me-2"></i>上传数据
|
||
</a>
|
||
<a href="{{ url_for('admin.dashboard') }}" class="btn btn-secondary">
|
||
<i class="fas fa-home me-2"></i>返回首页
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 搜索和筛选 -->
|
||
<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-search me-2"></i>搜索筛选
|
||
</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
<form method="GET" class="row g-3" id="searchForm">
|
||
<div class="col-md-2">
|
||
<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-2">
|
||
<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-3">
|
||
<label for="student_search" class="form-label">学生姓名/学号</label>
|
||
<input type="text" class="form-control" id="student_search" name="student_search"
|
||
placeholder="输入姓名或学号" value="{{ student_search or '' }}">
|
||
</div>
|
||
<div class="col-md-2">
|
||
<label for="sort_by" class="form-label">排序方式</label>
|
||
<select class="form-select" id="sort_by" name="sort_by">
|
||
<option value="created_at_desc" {{ 'selected' if sort_by == 'created_at_desc' else '' }}>最新记录</option>
|
||
<option value="created_at_asc" {{ 'selected' if sort_by == 'created_at_asc' else '' }}>最早记录</option>
|
||
<option value="actual_work_hours_desc" {{ 'selected' if sort_by == 'actual_work_hours_desc' else '' }}>出勤时长↓</option>
|
||
<option value="actual_work_hours_asc" {{ 'selected' if sort_by == 'actual_work_hours_asc' else '' }}>出勤时长↑</option>
|
||
<option value="class_work_hours_desc" {{ 'selected' if sort_by == 'class_work_hours_desc' else '' }}>班内工作↓</option>
|
||
<option value="class_work_hours_asc" {{ 'selected' if sort_by == 'class_work_hours_asc' else '' }}>班内工作↑</option>
|
||
<option value="absent_count_desc" {{ 'selected' if sort_by == 'absent_count_desc' or sort_by == 'absent_days_desc' else '' }}>缺勤次数↓</option>
|
||
<option value="absent_count_asc" {{ 'selected' if sort_by == 'absent_count_asc' or sort_by == 'absent_days_asc' else '' }}>缺勤次数↑</option>
|
||
<option value="late_count_desc" {{ 'selected' if sort_by == 'late_count_desc' else '' }}>迟到次数↓</option>
|
||
<option value="late_count_asc" {{ 'selected' if sort_by == 'late_count_asc' else '' }}>迟到次数↑</option>
|
||
<option value="overtime_hours_desc" {{ 'selected' if sort_by == 'overtime_hours_desc' else '' }}>加班时长↓</option>
|
||
<option value="overtime_hours_asc" {{ 'selected' if sort_by == 'overtime_hours_asc' else '' }}>加班时长↑</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3 d-flex align-items-end">
|
||
<button type="submit" class="btn btn-primary me-2">
|
||
<i class="fas fa-search me-1"></i>搜索
|
||
</button>
|
||
<a href="{{ url_for('admin.attendance_management') }}" class="btn btn-outline-secondary me-2">
|
||
<i class="fas fa-refresh"></i>
|
||
</a>
|
||
<button type="button" class="btn btn-outline-info" onclick="toggleAdvancedSearch()" title="高级搜索">
|
||
<i class="fas fa-cog"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 🔥 新增:高级搜索区域 -->
|
||
<div id="advancedSearchArea" class="col-12 mt-3" style="display: none;">
|
||
<div class="border-top pt-3">
|
||
<h6 class="text-muted mb-3">
|
||
<i class="fas fa-filter me-2"></i>高级搜索
|
||
</h6>
|
||
<div class="row g-3">
|
||
<div class="col-md-6">
|
||
<label class="form-label">导师(多选)</label>
|
||
<div class="supervisor-checkboxes" style="max-height: 200px; overflow-y: auto; border: 1px solid #ced4da; border-radius: 0.375rem; padding: 10px;">
|
||
{% for supervisor in all_supervisors %}
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" name="supervisor"
|
||
value="{{ supervisor }}" id="supervisor_{{ loop.index }}"
|
||
{{ 'checked' if supervisor in selected_supervisors else '' }}>
|
||
<label class="form-check-label" for="supervisor_{{ loop.index }}">
|
||
{{ supervisor }}
|
||
</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
<div class="mt-2">
|
||
<button type="button" class="btn btn-sm btn-outline-primary me-2" onclick="selectAllSupervisors()">全选</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearAllSupervisors()">清空</button>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label class="form-label">年级(多选)</label>
|
||
<div class="grade-checkboxes" style="max-height: 200px; overflow-y: auto; border: 1px solid #ced4da; border-radius: 0.375rem; padding: 10px;">
|
||
{% for grade in all_grades %}
|
||
<div class="form-check">
|
||
<input class="form-check-input" type="checkbox" name="grade"
|
||
value="{{ grade }}" id="grade_{{ grade }}"
|
||
{{ 'checked' if grade|string in selected_grades else '' }}>
|
||
<label class="form-check-label" for="grade_{{ grade }}">
|
||
{% if grade >= 20 %}
|
||
{{ grade }}级
|
||
{% else %}
|
||
{{ grade }}年级
|
||
{% endif %}
|
||
</label>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
<div class="mt-2">
|
||
<button type="button" class="btn btn-sm btn-outline-primary me-2" onclick="selectAllGrades()">全选</button>
|
||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearAllGrades()">清空</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 隐藏字段保持分页状态 -->
|
||
<input type="hidden" name="page" value="{{ pagination.page if pagination else 1 }}">
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 考勤记录表格 -->
|
||
<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-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 class="sortable" data-sort="actual_work_hours">
|
||
出勤时长
|
||
<i class="fas fa-sort ms-1 text-muted sort-icon"></i>
|
||
</th>
|
||
<th class="sortable" data-sort="class_work_hours">
|
||
班内工作
|
||
<i class="fas fa-sort ms-1 text-muted sort-icon"></i>
|
||
</th>
|
||
<th class="sortable" data-sort="absent_count">
|
||
缺勤次数
|
||
<i class="fas fa-sort ms-1 text-muted sort-icon"></i>
|
||
</th>
|
||
<th class="sortable" data-sort="late_count">
|
||
迟到次数
|
||
<i class="fas fa-sort ms-1 text-muted sort-icon"></i>
|
||
</th>
|
||
<th class="sortable" data-sort="overtime_hours">
|
||
加班时长
|
||
<i class="fas fa-sort ms-1 text-muted sort-icon"></i>
|
||
</th>
|
||
<th class="sortable" data-sort="created_at">
|
||
记录时间
|
||
<i class="fas fa-sort ms-1 text-muted sort-icon"></i>
|
||
</th>
|
||
<th class="text-center">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for record in attendance_records %}
|
||
<tr>
|
||
<td>
|
||
<a href="{{ url_for('admin.student_detail', student_number=record.student_number) }}"
|
||
class="text-decoration-none">
|
||
{{ record.student_number }}
|
||
</a>
|
||
</td>
|
||
<td>{{ record.name }}</td>
|
||
<td>
|
||
<small>
|
||
{{ record.week_start_date.strftime('%Y-%m-%d') }}<br>
|
||
至 {{ record.week_end_date.strftime('%Y-%m-%d') }}
|
||
</small>
|
||
</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>
|
||
{# 🔥 修改:使用 absent_count 而不是 absent_days,单位改为"次" #}
|
||
{% set absent_count = record.absent_count if record.absent_count is defined else 0 %}
|
||
{% if absent_count > 0 %}
|
||
<span class="badge bg-warning">{{ absent_count }}次</span>
|
||
{% else %}
|
||
<span class="badge bg-success">0次</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% set late_count = record.late_count if record.late_count is defined else 0 %}
|
||
{% if late_count > 0 %}
|
||
<span class="badge bg-warning">{{ late_count }}次</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 class="text-center">
|
||
<div class="btn-group btn-group-sm">
|
||
<button type="button" class="btn btn-outline-primary"
|
||
onclick="viewDetails({{ record.record_id }})"
|
||
title="查看详情">
|
||
<i class="fas fa-eye"></i>
|
||
</button>
|
||
<button type="button" class="btn btn-outline-danger"
|
||
onclick="deleteRecord({{ record.record_id }}, '{{ record.name }}')"
|
||
title="删除">
|
||
<i class="fas fa-trash"></i>
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- 分页 -->
|
||
{% if pagination.pages > 1 %}
|
||
<nav aria-label="考勤记录分页">
|
||
<ul class="pagination justify-content-center">
|
||
{% if pagination.has_prev %}
|
||
<li class="page-item">
|
||
<a class="page-link" href="{{ url_for('admin.attendance_management',
|
||
page=pagination.prev_num,
|
||
start_date=start_date,
|
||
end_date=end_date,
|
||
student_search=student_search,
|
||
sort_by=sort_by) }}">
|
||
<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('admin.attendance_management',
|
||
page=page_num,
|
||
start_date=start_date,
|
||
end_date=end_date,
|
||
student_search=student_search,
|
||
sort_by=sort_by) }}">
|
||
{{ 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('admin.attendance_management',
|
||
page=pagination.next_num,
|
||
start_date=start_date,
|
||
end_date=end_date,
|
||
student_search=student_search,
|
||
sort_by=sort_by) }}">
|
||
<i class="fas fa-chevron-right"></i>
|
||
</a>
|
||
</li>
|
||
{% endif %}
|
||
</ul>
|
||
</nav>
|
||
{% endif %}
|
||
|
||
{% else %}
|
||
<!-- 空状态 -->
|
||
<div class="text-center py-5">
|
||
<i class="fas fa-inbox fa-4x text-muted mb-3"></i>
|
||
<h5 class="text-muted">暂无考勤记录</h5>
|
||
<p class="text-muted mb-4">还没有上传任何考勤数据</p>
|
||
<a href="{{ url_for('admin.upload_attendance') }}" class="btn btn-primary">
|
||
<i class="fas fa-upload me-2"></i>立即上传考勤数据
|
||
</a>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 统计信息卡片 -->
|
||
{% if attendance_records %}
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<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-chart-bar me-2"></i>当前筛选统计
|
||
</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row text-center">
|
||
<div class="col-2">
|
||
<div class="border-end">
|
||
<h6 class="text-primary">{{ pagination.total }}</h6>
|
||
<small class="text-muted">总记录</small>
|
||
</div>
|
||
</div>
|
||
<div class="col-2">
|
||
<div class="border-end">
|
||
<h6 class="text-success">
|
||
{{ attendance_records|sum(attribute='actual_work_hours')|round(1) }}h
|
||
</h6>
|
||
<small class="text-muted">总出勤</small>
|
||
</div>
|
||
</div>
|
||
<div class="col-1">
|
||
<div class="border-end">
|
||
<h6 class="text-danger">
|
||
{# 🔥 修改:使用 absent_count 统计总缺勤次数 #}
|
||
{% set total_absent_count = attendance_records|sum(attribute='absent_count') if attendance_records[0].absent_count is defined else 0 %}
|
||
{{ total_absent_count }}
|
||
</h6>
|
||
<small class="text-muted">缺勤</small>
|
||
</div>
|
||
</div>
|
||
<div class="col-1">
|
||
<div class="border-end">
|
||
<h6 class="text-warning">
|
||
{% set total_leave = statistics.total_leave_days if statistics and statistics.total_leave_days else 0 %}
|
||
{{ total_leave }}
|
||
</h6>
|
||
<small class="text-muted">请假</small>
|
||
</div>
|
||
</div>
|
||
<div class="col-2">
|
||
<div class="border-end">
|
||
<h6 class="text-warning">
|
||
{% set total_late = attendance_records|sum(attribute='late_count') if attendance_records[0].late_count is defined else 0 %}
|
||
{{ total_late }}
|
||
</h6>
|
||
<small class="text-muted">迟到次数</small>
|
||
</div>
|
||
</div>
|
||
<div class="col-2">
|
||
<div class="border-end">
|
||
<h6 class="text-info">
|
||
{{ attendance_records|sum(attribute='overtime_hours')|round(1) }}h
|
||
</h6>
|
||
<small class="text-muted">总加班</small>
|
||
</div>
|
||
</div>
|
||
<div class="col-2">
|
||
<h6 class="text-secondary">
|
||
{{ "%.1f"|format((attendance_records|sum(attribute='actual_work_hours')) / (attendance_records|length) if attendance_records|length > 0 else 0) }}h
|
||
</h6>
|
||
<small class="text-muted">平均出勤</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<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-tools me-2"></i>快捷操作
|
||
</h6>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row">
|
||
<div class="col-6 mb-2">
|
||
<a href="{{ url_for('admin.upload_attendance') }}"
|
||
class="btn btn-outline-primary btn-block">
|
||
<i class="fas fa-upload me-2"></i>上传新数据
|
||
</a>
|
||
</div>
|
||
<div class="col-6 mb-2">
|
||
<a href="{{ url_for('admin.student_list') }}"
|
||
class="btn btn-outline-success btn-block">
|
||
<i class="fas fa-users me-2"></i>学生管理
|
||
</a>
|
||
</div>
|
||
<div class="col-6 mb-2">
|
||
<a href="{{ url_for('admin.statistics') }}"
|
||
class="btn btn-outline-info btn-block">
|
||
<i class="fas fa-chart-line me-2"></i>统计报表
|
||
</a>
|
||
</div>
|
||
<div class="col-6 mb-2">
|
||
<button class="btn btn-outline-secondary btn-block" onclick="exportData()">
|
||
<i class="fas fa-download me-2"></i>导出数据
|
||
</button>
|
||
</div>
|
||
<div class="col-6 mb-2">
|
||
<button class="btn btn-outline-warning btn-block" onclick="clearAllFilters()">
|
||
<i class="fas fa-eraser me-2"></i>清空筛选
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- 删除确认模态框 -->
|
||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h5 class="modal-title" id="deleteModalLabel">
|
||
<i class="fas fa-exclamation-triangle text-warning me-2"></i>确认删除
|
||
</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p>您确定要删除 <strong id="studentName"></strong> 的这条考勤记录吗?</p>
|
||
<p class="text-danger"><small>此操作不可撤销!</small></p>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
|
||
<button type="button" class="btn btn-danger" id="confirmDelete">确认删除</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endblock %}
|
||
|
||
{% block extra_css %}
|
||
<style>
|
||
.btn-block {
|
||
display: block;
|
||
width: 100%;
|
||
}
|
||
|
||
.border-end {
|
||
border-right: 1px solid #dee2e6;
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
.btn-group-sm > .btn {
|
||
padding: 0.25rem 0.5rem;
|
||
font-size: 0.75rem;
|
||
}
|
||
|
||
/* 排序功能样式 */
|
||
.sortable {
|
||
cursor: pointer;
|
||
user-select: none;
|
||
position: relative;
|
||
transition: background-color 0.2s ease;
|
||
}
|
||
|
||
.sortable:hover {
|
||
background-color: #e9ecef !important;
|
||
}
|
||
|
||
.sort-icon {
|
||
font-size: 0.7rem;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.sortable:hover .sort-icon {
|
||
color: #007bff !important;
|
||
}
|
||
|
||
.sortable.sort-active {
|
||
background-color: #e7f1ff !important;
|
||
}
|
||
|
||
.sortable.sort-active .sort-icon {
|
||
color: #007bff !important;
|
||
}
|
||
|
||
.sortable.sort-asc .sort-icon:before {
|
||
content: "\f0de"; /* fa-sort-up */
|
||
}
|
||
|
||
.sortable.sort-desc .sort-icon:before {
|
||
content: "\f0dd"; /* fa-sort-down */
|
||
}
|
||
|
||
/* 响应式调整 */
|
||
@media (max-width: 768px) {
|
||
.col-2 {
|
||
flex: 0 0 33.333333%;
|
||
max-width: 33.333333%;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.table-responsive {
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.badge {
|
||
font-size: 0.65rem;
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script>
|
||
let recordToDelete = null;
|
||
|
||
function viewDetails(recordId) {
|
||
window.location.href = `/admin/attendance/${recordId}/details`;
|
||
}
|
||
|
||
function editRecord(recordId) {
|
||
window.location.href = `/admin/attendance/${recordId}/edit`;
|
||
}
|
||
|
||
function deleteRecord(recordId, studentName) {
|
||
recordToDelete = recordId;
|
||
document.getElementById('studentName').textContent = studentName;
|
||
const modal = new bootstrap.Modal(document.getElementById('deleteModal'));
|
||
modal.show();
|
||
}
|
||
|
||
document.getElementById('confirmDelete').addEventListener('click', function() {
|
||
if (recordToDelete) {
|
||
const form = document.createElement('form');
|
||
form.method = 'POST';
|
||
form.action = `/admin/attendance/${recordToDelete}/delete`;
|
||
|
||
const csrfToken = document.querySelector('meta[name="csrf-token"]');
|
||
if (csrfToken) {
|
||
const input = document.createElement('input');
|
||
input.type = 'hidden';
|
||
input.name = 'csrf_token';
|
||
input.value = csrfToken.getAttribute('content');
|
||
form.appendChild(input);
|
||
}
|
||
|
||
document.body.appendChild(form);
|
||
form.submit();
|
||
}
|
||
});
|
||
|
||
function exportData() {
|
||
// 使用 admin.js 中的 exportCurrentFilter 函数
|
||
exportCurrentFilter();
|
||
}
|
||
|
||
// 自动设置结束日期为开始日期的一周后
|
||
document.getElementById('start_date').addEventListener('change', function() {
|
||
const startDate = new Date(this.value);
|
||
const endDate = new Date(startDate);
|
||
endDate.setDate(startDate.getDate() + 6);
|
||
document.getElementById('end_date').value = endDate.toISOString().split('T')[0];
|
||
});
|
||
|
||
// 排序选择器变化时自动提交表单
|
||
document.getElementById('sort_by').addEventListener('change', function() {
|
||
// 重置到第一页
|
||
const pageInput = document.querySelector('input[name="page"]');
|
||
if (pageInput) {
|
||
pageInput.value = 1;
|
||
}
|
||
document.getElementById('searchForm').submit();
|
||
});
|
||
|
||
// 获取当前URL参数的函数
|
||
function getUrlParams() {
|
||
const params = new URLSearchParams(window.location.search);
|
||
return {
|
||
start_date: params.get('start_date') || '',
|
||
end_date: params.get('end_date') || '',
|
||
student_search: params.get('student_search') || '',
|
||
sort_by: params.get('sort_by') || 'created_at_desc'
|
||
};
|
||
}
|
||
|
||
// 构建新的排序URL
|
||
function buildSortUrl(sortField, currentParams) {
|
||
const currentSort = currentParams.sort_by;
|
||
let newSort;
|
||
|
||
if (currentSort === `${sortField}_asc`) {
|
||
newSort = `${sortField}_desc`;
|
||
} else {
|
||
newSort = `${sortField}_asc`;
|
||
}
|
||
|
||
const url = new URL(window.location.origin + window.location.pathname);
|
||
url.searchParams.set('sort_by', newSort);
|
||
if (currentParams.start_date) url.searchParams.set('start_date', currentParams.start_date);
|
||
if (currentParams.end_date) url.searchParams.set('end_date', currentParams.end_date);
|
||
if (currentParams.student_search) url.searchParams.set('student_search', currentParams.student_search);
|
||
// 重置到第一页
|
||
url.searchParams.set('page', '1');
|
||
|
||
return url.toString();
|
||
}
|
||
|
||
// 表头排序功能
|
||
function setupTableSorting() {
|
||
const sortableHeaders = document.querySelectorAll('.sortable');
|
||
const currentParams = getUrlParams();
|
||
|
||
console.log('当前URL参数:', currentParams); // 调试信息
|
||
|
||
sortableHeaders.forEach(header => {
|
||
header.addEventListener('click', function(e) {
|
||
e.preventDefault();
|
||
const sortField = this.getAttribute('data-sort');
|
||
console.log('点击排序字段:', sortField); // 调试信息
|
||
|
||
const newUrl = buildSortUrl(sortField, currentParams);
|
||
console.log('新URL:', newUrl); // 调试信息
|
||
|
||
window.location.href = newUrl;
|
||
});
|
||
});
|
||
|
||
// 更新当前排序状态的显示
|
||
const currentSortBy = currentParams.sort_by;
|
||
console.log('当前排序:', currentSortBy); // 调试用
|
||
|
||
if (currentSortBy && currentSortBy.includes('_')) {
|
||
// 移除所有现有的排序状态
|
||
sortableHeaders.forEach(header => {
|
||
header.classList.remove('sort-active', 'sort-asc', 'sort-desc');
|
||
});
|
||
|
||
// 解析排序字段和方向
|
||
const lastUnderscoreIndex = currentSortBy.lastIndexOf('_');
|
||
if (lastUnderscoreIndex > 0) {
|
||
const field = currentSortBy.substring(0, lastUnderscoreIndex);
|
||
const direction = currentSortBy.substring(lastUnderscoreIndex + 1);
|
||
|
||
console.log('解析排序:', field, direction); // 调试用
|
||
|
||
const header = document.querySelector(`[data-sort="${field}"]`);
|
||
if (header) {
|
||
header.classList.add('sort-active');
|
||
if (direction === 'desc') {
|
||
header.classList.add('sort-desc');
|
||
} else {
|
||
header.classList.add('sort-asc');
|
||
}
|
||
console.log('设置排序状态成功:', header.textContent.trim()); // 调试用
|
||
} else {
|
||
console.log('未找到排序表头:', field); // 调试用
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 高级搜索切换功能已在 admin.js 中实现
|
||
|
||
// DOM加载完成后初始化
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
console.log('页面加载完成,初始化排序功能'); // 调试信息
|
||
setupTableSorting();
|
||
});
|
||
</script>
|
||
|
||
<script src="{{ url_for('static', filename='js/admin.js') }}"></script>
|
||
{% endblock %} |