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

572 lines
24 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-calendar-check me-2"></i>考勤详情
</h1>
<div>
<a href="{{ url_for('admin.attendance_management') }}" class="btn btn-secondary">
<i class="fas fa-arrow-left me-2"></i>返回列表
</a>
</div>
</div>
<!-- 基本信息卡片 -->
<div class="row mb-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-user me-2"></i>学生信息
</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-6">
<p><strong>学号:</strong> {{ weekly_record.student_number }}</p>
<p><strong>姓名:</strong> {{ weekly_record.name }}</p>
{% if student %}
<p><strong>年级:</strong> {{ student.grade }}</p>
<p><strong>学院:</strong> {{ student.college or '未设置' }}</p>
{% endif %}
</div>
<div class="col-6">
{% if student %}
<p><strong>专业:</strong> {{ student.major or '未设置' }}</p>
<p><strong>导师:</strong> {{ student.supervisor or '未设置' }}</p>
<p><strong>学位类型:</strong> {{ student.degree_type or '未设置' }}</p>
<p><strong>状态:</strong>
<span class="badge bg-{{ 'success' if student.status == '在读' else 'secondary' }}">
{{ student.status }}
</span>
</p>
{% endif %}
</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-calendar-week me-2"></i>考勤周期
</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-6">
<p><strong>开始日期:</strong> {{ weekly_record.week_start_date.strftime('%Y年%m月%d日') }}</p>
<p><strong>结束日期:</strong> {{ weekly_record.week_end_date.strftime('%Y年%m月%d日') }}</p>
</div>
<div class="col-6">
<p><strong>创建时间:</strong> {{ weekly_record.created_at.strftime('%Y-%m-%d %H:%M') }}</p>
<p><strong>更新时间:</strong> {{ weekly_record.updated_at.strftime('%Y-%m-%d %H:%M') }}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 统计数据卡片 -->
<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">
{{ "%.1f"|format(weekly_record.actual_work_hours) }}小时
</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-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(weekly_record.class_work_hours) }}小时
</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-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">
{{ weekly_record.absent_days }}天
</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(weekly_record.overtime_hours) }}小时
</div>
</div>
<div class="col-auto">
<i class="fas fa-moon fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</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-list me-2"></i>每日考勤明细
<small class="text-muted">(点击日期查看详细时段信息)</small>
</h6>
</div>
<div class="card-body">
{% if daily_details %}
<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 detail in daily_details %}
<tr>
<td>{{ detail.attendance_date.strftime('%m-%d') }}</td>
<td>
{% set weekday = detail.attendance_date.weekday() %}
{% if weekday == 0 %}周一
{% elif weekday == 1 %}周二
{% elif weekday == 2 %}周三
{% elif weekday == 3 %}周四
{% elif weekday == 4 %}周五
{% elif weekday == 5 %}周六
{% else %}周日
{% endif %}
</td>
<td>
{% if detail.status == '正常' %}
<span class="badge bg-success">{{ detail.status }}</span>
{% elif '迟到' in detail.status %}
<span class="badge bg-warning">{{ detail.status }}</span>
{% elif detail.status == '缺勤' %}
<span class="badge bg-danger">{{ detail.status }}</span>
{% elif detail.status == '请假' %}
<span class="badge bg-orange">{{ detail.status }}</span>
{% elif detail.status == '休息' %}
<span class="badge bg-info">{{ detail.status }}</span>
{% elif detail.status == '加班' %}
<span class="badge bg-primary">{{ detail.status }}</span>
{% else %}
<span class="badge bg-secondary">{{ detail.status }}</span>
{% endif %}
</td>
<td>
{% if detail.check_in_time %}
{{ detail.check_in_time.strftime('%H:%M') }}
{% else %}
<span class="text-muted">未打卡</span>
{% endif %}
</td>
<td>
{% if detail.check_out_time %}
{{ detail.check_out_time.strftime('%H:%M') }}
{% else %}
<span class="text-muted">未打卡</span>
{% endif %}
</td>
<td>
{% if detail.duration_hours %}
<span class="badge bg-primary">{{ detail.duration_hours }}h</span>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
{% if detail.summary_remarks %}
<small class="text-muted">{{ detail.summary_remarks }}</small>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>
{% if detail.status not in ['休息', '缺勤'] and detail.detailed_info %}
<button type="button" class="btn btn-sm btn-outline-primary"
onclick="showDetailModal('{{ detail.detail_id }}', '{{ detail.attendance_date.strftime('%Y-%m-%d') }}', '{{ detail.remarks|escape }}')"
title="查看详细时段">
<i class="fas fa-eye"></i>
</button>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center py-3">
<i class="fas fa-calendar-times fa-3x text-muted mb-3"></i>
<h5 class="text-muted">暂无每日考勤明细</h5>
<p class="text-muted">该考勤周期内没有详细的打卡记录</p>
</div>
{% endif %}
</div>
</div>
<!-- 统计分析和历史对比的其他部分... -->
<div class="row mb-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-3">
<div class="border-end">
<h6 class="text-success">{{ present_days }}</h6>
<small class="text-muted">正常天数</small>
</div>
</div>
<div class="col-3">
<div class="border-end">
<h6 class="text-warning">{{ late_days }}</h6>
<small class="text-muted">迟到天数</small>
</div>
</div>
<div class="col-3">
<div class="border-end">
<h6 class="text-danger">{{ absent_days }}</h6>
<small class="text-muted">缺勤天数</small>
</div>
</div>
<div class="col-3">
<h6 class="text-info">{{ "%.1f"|format(avg_daily_hours) }}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-history me-2"></i>历史对比
</h6>
</div>
<div class="card-body">
{% if historical_records %}
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>周期</th>
<th>出勤时长</th>
<th>旷工天数</th>
</tr>
</thead>
<tbody>
{% for record in historical_records %}
<tr>
<td>
<small>{{ record.week_start_date.strftime('%m-%d') }}</small>
</td>
<td>
<span class="badge bg-primary">{{ "%.1f"|format(record.actual_work_hours) }}h</span>
</td>
<td>
{% if record.absent_days > 0 %}
<span class="badge bg-warning">{{ record.absent_days }}</span>
{% else %}
<span class="badge bg-success">0</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted text-center mb-0">暂无历史记录</p>
{% endif %}
</div>
</div>
</div>
</div>
</div>
<!-- 详细时段信息模态框 -->
<div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="detailModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered"> <!-- 添加 modal-dialog-centered -->
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="detailModalLabel">
<i class="fas fa-clock me-2"></i>详细打卡时段 - <span id="modalDate"></span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="detailContent">
<!-- 内容将通过JavaScript动态填充 -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</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;
}
.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;
}
.period-card {
border: 1px solid #e3e6f0;
border-radius: 0.35rem;
padding: 1rem;
margin-bottom: 1rem;
}
.period-header {
font-weight: 600;
color: #5a5c69;
margin-bottom: 0.5rem;
}
.badge.bg-orange {
background-color: #fd7e14 !important;
}
.time-info {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
{% endblock %}
{% block extra_js %}
<script>
function showDetailModal(detailId, date, remarksJson) {
document.getElementById('modalDate').textContent = date;
console.log('调用showDetailModal', detailId, date, remarksJson); // 调试信息
let detailsData = null;
try {
if (remarksJson && remarksJson.startsWith('{')) {
const parsed = JSON.parse(remarksJson);
detailsData = parsed.details;
console.log('解析的详细数据:', detailsData); // 调试信息
}
} catch (e) {
console.error('解析详细信息失败:', e);
}
if (!detailsData) {
document.getElementById('detailContent').innerHTML =
'<p class="text-muted">暂无详细时段信息</p><p class="text-muted">原始数据: ' + remarksJson + '</p>';
} else {
let html = '';
// 显示各个时段的详情
const periods = [
{ key: 'morning', name: '早上时段', time: '09:45-11:30' },
{ key: 'afternoon', name: '下午时段', time: '13:30-18:30' },
{ key: 'evening', name: '晚上时段', time: '19:00-23:30' }
];
periods.forEach(period => {
if (detailsData[period.key]) {
const data = detailsData[period.key];
html += `
<div class="period-card">
<div class="period-header">
<i class="fas fa-clock me-2"></i>${period.name} (${period.time})
</div>
<div class="time-info">
<div>
<strong>签到:</strong>
<span class="badge ${getStatusClass(data.status, 'in')}">
${data.in || '未打卡'}
</span>
${data.late_minutes ? `<small class="text-warning">(迟到${data.late_minutes}分钟)</small>` : ''}
</div>
<div>
<strong>签退:</strong>
<span class="badge ${getStatusClass(data.status, 'out')}">
${data.out || '未打卡'}
</span>
${data.early_minutes ? `<small class="text-warning">(早退${data.early_minutes}分钟)</small>` : ''}
</div>
<div>
<strong>工时:</strong>
${calculatePeriodHours(data.in, data.out)}
</div>
</div>
</div>
`;
}
});
// 如果是周末加班
if (detailsData.overtime) {
html += `
<div class="period-card">
<div class="period-header">
<i class="fas fa-moon me-2"></i>周末加班
</div>
<div class="time-info">
<div>
<strong>开始:</strong>
<span class="badge bg-info">${detailsData.overtime.in || '未记录'}</span>
</div>
<div>
<strong>结束:</strong>
<span class="badge bg-info">${detailsData.overtime.out || '未记录'}</span>
</div>
<div>
<strong>加班时长:</strong>
${calculatePeriodHours(detailsData.overtime.in, detailsData.overtime.out)}
</div>
</div>
</div>
`;
}
if (html === '') {
html = '<p class="text-muted">该日期没有详细的时段打卡信息</p>';
}
document.getElementById('detailContent').innerHTML = html;
}
const modal = new bootstrap.Modal(document.getElementById('detailModal'));
modal.show();
}
function getStatusClass(status, type) {
if (status === 'normal') return 'bg-success';
if (status === 'late' && type === 'in') return 'bg-warning';
if (status === 'early_leave' && type === 'out') return 'bg-warning';
if (status === 'missing') return 'bg-secondary';
return 'bg-secondary';
}
function calculatePeriodHours(startTime, endTime) {
if (!startTime || !endTime) return '<span class="text-muted">-</span>';
try {
const start = new Date(`2000-01-01 ${startTime}:00`);
const end = new Date(`2000-01-01 ${endTime}:00`);
const diff = (end - start) / (1000 * 60 * 60);
if (diff > 0) {
return `<span class="badge bg-primary">${diff.toFixed(1)}h</span>`;
}
} catch (e) {
console.error('计算时长失败:', e);
}
return '<span class="text-muted">-</span>';
}
// 测试函数
function testModal() {
console.log('测试模态框');
document.getElementById('modalDate').textContent = '测试日期';
document.getElementById('detailContent').innerHTML = '<p>测试内容</p>';
const modal = new bootstrap.Modal(document.getElementById('detailModal'));
modal.show();
}
</script>
{% endblock %}