SpringBoot项目Excel模板下载功能详解
项目背景
"知分"系统是一款基于SpringBoot和Vue架构开发的B/S模式在线成绩管理平台,旨在解决传统成绩通知方式(如纸质成绩单、家长群通知)存在的效率低下、易出错、查询不便且不易保存等问题。
该系统作为连接家长与教师的数字化桥梁,具有以下核心价值:
- 对于家长:通过微信小程序即可随时随地、安全便捷地查询子女的最新考试成绩与排名,促进家校沟通
- 对于教师/学校:提供高效的Excel一键式成绩录入工具,自动完成分数统计与排名分析,通过可视化图表直观展示教学成果,极大减轻成绩管理工作负担
该系统实现了成绩管理的数字化、自动化与智能化,显著提升了家校互动的体验与效率。
本文将重点讲解成绩录入功能中Excel模板下载的实现细节。
功能展示
点击"录入成绩"按钮后会出现弹窗:
该功能支持上传xls和xlsx文件,并提供模板下载功能。模板会自动加载对应班级学生和考试名称,教师只需填写各科分数即可。
前端实现详解
1. 按钮触发逻辑
前端页面使用Element UI组件,点击"录入成绩"按钮触发相应事件:
<div class="card-actions"><el-button type="primary" size="small" @click="handleImportScore(classItem.id)"class="action-btn"><i class="el-icon-upload"></i>录入成绩</el-button><el-button type="default" size="small" @click="handleViewDetails(classItem.id)"class="action-btn"><i class="el-icon-view"></i>查看详情</el-button>
</div>
点击"录入成绩"按钮后,会触发handleImportScore
方法并将班级ID作为参数传递:
// 处理导入成绩
handleImportScore(classId) {this.currentClassId = classIdthis.scoreDialogVisible = truethis.examName = ''this.fileList = []this.selectedFile = null
}
此方法主要完成以下工作:
- 设置当前班级ID
- 打开成绩录入弹窗
- 重置相关表单数据
2. 模板下载功能
弹窗中的模板下载区域代码如下:
<div class="upload-tips"><el-button type="primary" size="small" @click="downloadExcelTemplate"class="download-template-btn"><i class="el-icon-download"></i>点击下载样本</el-button><p style="margin-top: 10px; color: #909399; font-size: 12px;">下载后填写分数,再上传即可</p>
</div>
点击下载按钮后,触发downloadExcelTemplate
方法:
// 下载Excel模板
downloadExcelTemplate() {// 条件校验if (!this.currentClassId) {this.$message.error('未选择班级')return}if (!this.selectedExamId) {this.$message.warning('请先选择考试')return}try {// 显示加载提示this.$message({message: '正在生成模板,请稍候',type: 'info',duration: 0})// 获取班级和考试信息用于构建文件名const exam = this.homeData.exams.find(e => e.id === this.selectedExamId)const className = this.homeData.classes.find(c => c.id === this.currentClassId)?.name || '未知班级'const examName = exam?.name || '未知考试'// 设置token到请求头,防止被权限拦截const token = this.$store.state.user.tokenif (!token) {this.$message.warning('登录状态失效,请重新登录')setTimeout(() => {this.$router.push('/login')}, 1000)return}// 使用XHR方式下载文件,确保携带tokenconst xhr = new XMLHttpRequest()const requestUrl = `/teacher/home/downloadTemplate/${this.currentClassId}/${this.selectedExamId}`xhr.open('GET', requestUrl, true)xhr.setRequestHeader('Authorization', 'Bearer ' + token)xhr.responseType = 'blob'// 传递className和examName到回调函数中const downloadContext = {className: className,examName: examName,component: this}xhr.onload = function() {// 处理返回结果if (xhr.status === 200) {// 关闭所有消息提示downloadContext.component.$message.closeAll()downloadContext.component.$message.success('模板下载成功')try {// 设置文件名let finalFileName = `${downloadContext.className}成绩导入表${downloadContext.examName}.xlsx`const contentDisposition = xhr.getResponseHeader('Content-Disposition')// 根据响应类型设置正确的MIME类型let mimeType = 'application/octet-stream'const contentType = xhr.getResponseHeader('Content-Type')if (contentType) {mimeType = contentType}// 创建下载链接const blob = new Blob([xhr.response], { type: mimeType })const url = URL.createObjectURL(blob)// 创建a标签并模拟点击下载const downloadLink = document.createElement('a')downloadLink.href = urldownloadLink.setAttribute('download', finalFileName)downloadLink.style.display = 'none'document.body.appendChild(downloadLink)downloadLink.click()// 延迟移除,确保点击事件完成setTimeout(() => {document.body.removeChild(downloadLink)URL.revokeObjectURL(url)}, 100)} catch (downloadError) {console.error('下载文件处理失败:', downloadError)downloadContext.component.$message.error('文件下载处理失败')}} else {console.error('请求失败,状态码:', xhr.status)downloadContext.component.$message.closeAll()downloadContext.component.$message.error('生成模板失败:请稍后重试')}}xhr.onerror = function(e) {console.error('网络错误:', e)this.$message.closeAll()this.$message.error('生成模板失败:网络错误')}xhr.send()} catch (error) {console.error('生成模板失败:', error)this.$message.closeAll()this.$message.error('生成模板失败:' + (error.message || '请稍后重试'))}
}
该方法的关键实现点包括:
- 进行前置条件校验(班级和考试选择)
- 获取token并设置到请求头中
- 使用XMLHttpRequest发起请求,设置responseType为blob
- 处理响应并创建下载链接
- 添加异常处理机制,确保用户体验
后端实现详解
1. Controller层实现
后端使用SpringBoot框架,Controller层代码如下:
package com.eduscore.controller;import com.eduscore.domain.Student;
import com.eduscore.service.ITeacherHomeService;
import com.eduscore.common.core.controller.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;@RestController
@RequestMapping("/teacher/home")
public class TeacherHomeController extends BaseController {@Autowiredprivate ITeacherHomeService teacherHomeService;/*** 下载Excel成绩导入模板*/@GetMapping("/downloadTemplate/{classId}/{examId}")public void downloadExcelTemplate(@PathVariable Integer classId, @PathVariable Integer examId, HttpServletResponse response) {try {// 设置响应头response.setContentType("text/csv;charset=utf-8");response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("成绩导入模板.csv", "UTF-8"));// 获取班级学生列表List<Student> students = teacherHomeService.getClassStudents(classId);// 获取班级考试信息Map<String, Object> examInfo = teacherHomeService.getClassExamInfo(classId, examId);// 获取考试科目List<String> subjectNames = (List<String>) examInfo.get("subjectNames");// 生成CSV内容StringBuilder csvContent = new StringBuilder();// 添加标题行csvContent.append("学生姓名,班级");for (String subject : subjectNames) {csvContent.append(",").append(subject);}csvContent.append(",").append("考试名称");csvContent.append("\n");// 添加学生数据行String className = (String) examInfo.get("className");String examName = (String) examInfo.get("examName");for (Student student : students) {csvContent.append(student.getName()).append(",").append(className);// 为每个科目添加空成绩列for (int i = 0; i < subjectNames.size(); i++) {csvContent.append(",");}csvContent.append(",").append(examName);csvContent.append("\n");}// 输出CSV文件try (OutputStream out = response.getOutputStream()) {// 添加BOM头,确保Excel能正确识别UTF-8编码out.write(0xEF);out.write(0xBB);out.write(0xBF);out.write(csvContent.toString().getBytes(StandardCharsets.UTF_8));out.flush();}} catch (IOException e) {e.printStackTrace();}}
}
2. Service层实现
Service层负责业务逻辑处理:
package com.eduscore.service.impl;import com.eduscore.domain.*;
import com.eduscore.mapper.*;
import com.eduscore.service.ITeacherHomeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.HashMap;@Service
public class TeacherHomeServiceImpl implements ITeacherHomeService {@Autowiredprivate TeacherMapper teacherMapper;@Autowiredprivate SchoolMapper schoolMapper;@Autowiredprivate StudentMapper studentMapper;@Autowiredprivate ClassMapper classMapper;@Autowiredprivate ExamMapper examMapper;@Autowiredprivate ScoreMapper scoreMapper;@Autowiredprivate SubjectMapper subjectMapper;@Overridepublic Map<String, Object> getClassExamInfo(Integer classId, Integer examId) {// 获取班级信息Class clazz = classMapper.selectClassById(classId);// 获取考试信息Exam exam = examMapper.selectExamById(examId);// 获取该班级该考试的所有科目List<Subject> subjects = subjectMapper.selectSubjectList(new Subject());Map<String, Object> result = new HashMap<>();result.put("className", clazz != null ? clazz.getName() : "");result.put("examName", exam != null ? exam.getName() : "");List<String> subjectNames = subjects.stream().map(Subject::getName).collect(Collectors.toList());result.put("subjectNames", subjectNames);return result;}
}
实现原理总结
- 前端触发:用户点击下载模板按钮,前端进行条件校验并携带认证信息发起请求
- 后端处理:Controller接收请求,调用Service层获取班级、考试和科目信息
- 模板生成:后端动态生成CSV格式的模板文件,包含学生列表和科目信息
- 文件返回:设置正确的响应头和信息,将文件流返回给前端
- 前端下载:前端接收文件流,创建下载链接并触发浏览器下载
生成的模板示例
通过以上实现,教师可以下载预先填充了班级学生信息和考试科目的模板,只需填写分数即可完成成绩录入,极大提高了工作效率。
下一篇文章将详细讲解成绩录入功能的实现细节,包括文件上传、数据解析和批量导入等关键技术点。