CHM_attendance/app/templates/admin/add_student.html
superlishunqin 77b57a9876 0914
2025-09-14 01:28:47 +08:00

528 lines
25 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% 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-user-plus me-2"></i>添加学生
</h1>
<a href="{{ url_for('admin.student_list') }}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>返回学生列表
</a>
</div>
<!-- 选项卡导航 -->
<ul class="nav nav-tabs mb-4" id="addStudentTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="single-tab" data-bs-toggle="tab" data-bs-target="#single"
type="button" role="tab" aria-controls="single" aria-selected="true">
<i class="fas fa-user me-2"></i>单个添加
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="batch-tab" data-bs-toggle="tab" data-bs-target="#batch"
type="button" role="tab" aria-controls="batch" aria-selected="false">
<i class="fas fa-users me-2"></i>批量导入
</button>
</li>
</ul>
<!-- 选项卡内容 -->
<div class="tab-content" id="addStudentTabsContent">
<!-- 单个添加选项卡 -->
<div class="tab-pane fade show active" id="single" role="tabpanel" aria-labelledby="single-tab">
<div class="row justify-content-center">
<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-info-circle me-2"></i>学生基本信息
</h6>
</div>
<div class="card-body">
<form id="addStudentForm" method="POST">
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">学号 <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="student_number" id="student_number" required
placeholder="请输入学号">
<div class="form-text">学号将作为登录账号使用</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">姓名 <span class="text-danger">*</span></label>
<input type="text" class="form-control" name="name" required
placeholder="请输入姓名">
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">性别 <span class="text-danger">*</span></label>
<select class="form-select" name="gender" required>
<option value="">请选择性别</option>
<option value="男"></option>
<option value="女"></option>
</select>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">年级 <span class="text-danger">*</span></label>
<input type="number" class="form-control" name="grade" required
min="20" max="30" placeholder="如23">
<div class="form-text">20级、21级、22级</div>
</div>
</div>
<div class="col-md-4">
<div class="mb-3">
<label class="form-label">手机号</label>
<input type="tel" class="form-control" name="phone" maxlength="11"
pattern="1[3-9]\d{9}" placeholder="请输入11位手机号">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">学院</label>
<input type="text" class="form-control" name="college"
placeholder="请输入学院名称">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">专业</label>
<input type="text" class="form-control" name="major"
placeholder="请输入专业名称">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">导师</label>
<input type="text" class="form-control" name="supervisor"
placeholder="请输入导师姓名">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">学位类型</label>
<select class="form-select" name="degree_type">
<option value="">请选择学位类型</option>
<option value="专硕">专硕</option>
<option value="学硕">学硕</option>
<option value="学博">学博</option>
<option value="专博">专博</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">入学日期</label>
<input type="date" class="form-control" name="enrollment_date">
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">状态</label>
<select class="form-select" name="status">
<option value="在读" selected>在读</option>
<option value="毕业">毕业</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">登录密码</label>
<input type="password" class="form-control" name="password" id="password" readonly
placeholder="将自动设置为学号">
<div class="form-text">
<i class="fas fa-info-circle text-info me-1"></i>
密码将自动设置为学号,学生可在登录后自行修改
</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3 d-flex align-items-end">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="custom_password">
<label class="form-check-label" for="custom_password">
自定义密码
</label>
</div>
</div>
</div>
</div>
<hr class="my-4">
<div class="d-flex justify-content-end gap-2">
<a href="{{ url_for('admin.student_list') }}" class="btn btn-secondary">
<i class="fas fa-times me-1"></i>取消
</a>
<button type="submit" class="btn btn-success">
<i class="fas fa-save me-1"></i>保存学生信息
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- 批量导入选项卡 -->
<div class="tab-pane fade" id="batch" role="tabpanel" aria-labelledby="batch-tab">
<div class="row justify-content-center">
<div class="col-lg-10">
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-upload me-2"></i>批量导入学生信息
</h6>
</div>
<div class="card-body">
<!-- 使用说明 -->
<div class="alert alert-info">
<h6><i class="fas fa-info-circle me-2"></i>使用说明</h6>
<ul class="mb-2">
<li>请上传Excel文件.xlsx或.xls格式</li>
<li>Excel文件应包含以下列姓名、性别、年级、学号、手机号、导师、学院、专业、学位类型</li>
<li>第一行应为表头,数据从第二行开始</li>
<li>学号不能重复,如有重复将跳过该记录</li>
<li>年级格式20、21、22等不要写成2020、2021</li>
</ul>
<div class="alert alert-warning mb-0">
<i class="fas fa-key me-2"></i>
<strong>密码设置:</strong>系统将自动将每个学生的密码设置为其学号,学生可在登录后自行修改
</div>
</div>
<!-- 模板下载 -->
<div class="mb-4">
<h6>📥 下载Excel模板</h6>
<a href="{{ url_for('admin.download_student_template') }}" class="btn btn-outline-primary">
<i class="fas fa-download me-1"></i>下载Excel模板
</a>
<small class="text-muted ms-2">建议先下载模板,按照格式填写学生信息</small>
</div>
<!-- 文件上传表单 -->
<form id="batchImportForm" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">选择Excel文件 <span class="text-danger">*</span></label>
<input type="file" class="form-control" name="excel_file" accept=".xlsx,.xls" required>
<div class="form-text">支持 .xlsx 和 .xls 格式</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="overwrite_existing" name="overwrite_existing">
<label class="form-check-label" for="overwrite_existing">
覆盖已存在的学生信息(默认跳过重复学号)
</label>
</div>
</div>
<hr class="my-4">
<div class="d-flex justify-content-end gap-2">
<button type="button" class="btn btn-secondary" onclick="clearBatchForm()">
<i class="fas fa-times me-1"></i>清空
</button>
<button type="submit" class="btn btn-primary">
<i class="fas fa-upload me-1"></i>开始导入
</button>
</div>
</form>
<!-- 导入结果显示区域 -->
<div id="importResult" class="mt-4" style="display: none;">
<h6>导入结果</h6>
<div id="importProgress" class="progress mb-3" style="display: none;">
<div class="progress-bar progress-bar-striped progress-bar-animated"
role="progressbar" style="width: 0%"></div>
</div>
<div id="importSummary"></div>
<div id="importDetails"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// 学号输入框变化时自动同步到密码字段
document.getElementById('student_number').addEventListener('input', function() {
const passwordField = document.getElementById('password');
const customPasswordCheckbox = document.getElementById('custom_password');
if (!customPasswordCheckbox.checked) {
passwordField.value = this.value;
}
});
// 自定义密码复选框处理
document.getElementById('custom_password').addEventListener('change', function() {
const passwordField = document.getElementById('password');
const studentNumberField = document.getElementById('student_number');
if (this.checked) {
passwordField.removeAttribute('readonly');
passwordField.placeholder = '请输入自定义密码';
passwordField.focus();
} else {
passwordField.setAttribute('readonly', true);
passwordField.placeholder = '将自动设置为学号';
passwordField.value = studentNumberField.value;
}
});
// 单个添加表单处理
document.getElementById('addStudentForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const data = Object.fromEntries(formData);
// 如果没有自定义密码,使用学号作为密码
const customPasswordCheckbox = document.getElementById('custom_password');
if (!customPasswordCheckbox.checked) {
data.password = data.student_number;
}
// 表单验证
if (!data.student_number || !data.name || !data.gender || !data.grade) {
alert('请填写所有必填项');
return;
}
// 验证手机号格式
if (data.phone && !/^1[3-9]\d{9}$/.test(data.phone)) {
alert('手机号格式不正确');
return;
}
// 验证密码不能为空
if (!data.password || data.password.trim() === '') {
alert('密码不能为空');
return;
}
// 禁用提交按钮
const submitBtn = this.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>保存中...';
fetch('/admin/students/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.success) {
// 显示成功消息
showAlert('success', result.message + `(密码:${data.password}`);
// 3秒后跳转到学生列表
setTimeout(() => {
window.location.href = '/admin/students';
}, 3000);
} else {
// 显示错误消息
showAlert('danger', result.message);
// 恢复提交按钮
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;
}
})
.catch(error => {
console.error('Error:', error);
showAlert('danger', '网络错误,请稍后重试');
// 恢复提交按钮
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;
});
});
// 批量导入表单处理
document.getElementById('batchImportForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
const fileInput = this.querySelector('input[name="excel_file"]');
if (!fileInput.files[0]) {
alert('请选择要上传的Excel文件');
return;
}
// 验证文件格式
const fileName = fileInput.files[0].name;
if (!fileName.match(/\.(xlsx|xls)$/i)) {
alert('请上传Excel文件.xlsx或.xls格式');
return;
}
// 显示进度条
const importResult = document.getElementById('importResult');
const importProgress = document.getElementById('importProgress');
const progressBar = importProgress.querySelector('.progress-bar');
importResult.style.display = 'block';
importProgress.style.display = 'block';
progressBar.style.width = '10%';
// 禁用提交按钮
const submitBtn = this.querySelector('button[type="submit"]');
const originalText = submitBtn.innerHTML;
submitBtn.disabled = true;
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>导入中...';
// 发送文件
fetch('/admin/students/batch_import', {
method: 'POST',
body: formData
})
.then(response => {
progressBar.style.width = '50%';
return response.json();
})
.then(result => {
progressBar.style.width = '100%';
// 隐藏进度条
setTimeout(() => {
importProgress.style.display = 'none';
}, 500);
// 显示结果
displayImportResult(result);
// 恢复提交按钮
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;
})
.catch(error => {
console.error('Error:', error);
progressBar.style.width = '100%';
progressBar.classList.add('bg-danger');
setTimeout(() => {
importProgress.style.display = 'none';
}, 500);
showAlert('danger', '导入失败,请稍后重试');
// 恢复提交按钮
submitBtn.disabled = false;
submitBtn.innerHTML = originalText;
});
});
// 显示导入结果
function displayImportResult(result) {
const summaryDiv = document.getElementById('importSummary');
const detailsDiv = document.getElementById('importDetails');
if (result.success) {
summaryDiv.innerHTML = `
<div class="alert alert-success">
<h6><i class="fas fa-check-circle me-2"></i>导入完成</h6>
<p class="mb-1">成功导入 <strong>${result.success_count}</strong> 个学生,
跳过 <strong>${result.skip_count || 0}</strong> 个,
失败 <strong>${result.error_count || 0}</strong> 个</p>
<small class="text-muted">
<i class="fas fa-key me-1"></i>所有学生的初始密码均为其学号
</small>
</div>
`;
// 显示详细信息
if (result.details && result.details.length > 0) {
let detailsHtml = '<div class="mt-3"><h6>详细信息</h6><ul class="list-group">';
result.details.forEach(detail => {
const iconClass = detail.status === 'success' ? 'text-success fas fa-check' :
detail.status === 'skip' ? 'text-warning fas fa-exclamation-triangle' :
'text-danger fas fa-times';
detailsHtml += `
<li class="list-group-item d-flex justify-content-between align-items-center">
<span>${detail.name} (${detail.student_number})</span>
<span class="${iconClass.includes('success') ? 'text-success' : iconClass.includes('warning') ? 'text-warning' : 'text-danger'}">
<i class="${iconClass}"></i> ${detail.message}
</span>
</li>
`;
});
detailsHtml += '</ul></div>';
detailsDiv.innerHTML = detailsHtml;
}
} else {
summaryDiv.innerHTML = `
<div class="alert alert-danger">
<h6><i class="fas fa-exclamation-triangle me-2"></i>导入失败</h6>
<p class="mb-0">${result.message}</p>
</div>
`;
}
}
// 清空批量导入表单
function clearBatchForm() {
document.getElementById('batchImportForm').reset();
document.getElementById('importResult').style.display = 'none';
}
// 显示提示消息
function showAlert(type, message) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type} alert-dismissible fade show`;
alertDiv.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check-circle' : 'exclamation-triangle'} me-2"></i>${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
`;
// 插入到当前活动的选项卡中
const activeTab = document.querySelector('.tab-pane.active .card-body');
activeTab.insertBefore(alertDiv, activeTab.firstChild);
// 滚动到顶部
window.scrollTo(0, 0);
}
// 学号输入框失焦时检查是否已存在
document.querySelector('input[name="student_number"]').addEventListener('blur', function() {
const studentNumber = this.value.trim();
if (studentNumber) {
// 这里可以添加AJAX检查学号是否已存在的逻辑
// 为了简化,暂时不实现
}
});
</script>
{% endblock %}