需求:页面点击导出,先按照页面条件去数据库查询,然后将查询到的数据导出。
问题:由于查询特别耗时,所以点击之后页面会看上去没有反应
方案1:就在点击之后在页面增加了一个进度条,等待后端查询结束之后,导出时,进度条会显示导出进度,导出结束之后进度条会消失。效果如下:
方案2:点击导出时前端增加一个遮罩层,遮罩层中间显示正在下载,导出完成后遮罩层消失,好处是可以既给用户提示,还可以阻止用户再次点击导出按钮。效果如下:
注意点:后端需要在响应头中设置ContentLength,前端需要用这个更新进度
response.setContentLength(excelBytes.length); // 设置Content-Length
方案1:进度条
html代码:
<button type="button" id="export_btn" class="layui-btn btn_blue">导 出</button>
<!-- 进度条容器 --><div id="progressContainer" style="display:none; margin-top:20px;"> <div id="progressBar"></div></div><!-- 显示下载进度百分比 --><div id="progressText" style="display:none; margin-top:5px;">下载进度: 0%</div>
css:
#progressContainer { width: 100%; background-color: #f3f3f3; border: 1px solid #ccc; border-radius: 5px;}#progressBar { width: 0%; height: 20px; background-color: #4caf50; border-radius: 5px;}
js代码:
//进度条$('#export_btn').on('click', function () { // 获取表单数据并构建FormData对象 var formData = $('#searchForm').serializeArray(); var form = new FormData(); $.each(formData, function () { form.append(this.name, this.value); }); // 添加额外的参数 form.append('publishFrom', '${RequestParameters.type}'); // 创建XHR对象 var xhr = new XMLHttpRequest(); xhr.open('POST', '路径/exportData', true); xhr.responseType = 'blob'; // 设置响应类型为blob // 显示进度条 $('#progressContainer').show(); $('#progressText').show(); $('#progressBar').css('width', '0%'); $('#progressText').text('下载进度: 0%'); // 设置请求头以模拟表单提交 // xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); // 监听下载进度 xhr.onprogress = function (event) { if (event.lengthComputable) { var percentComplete = Math.round((event.loaded / event.total) * 100); console.log('Loaded:', event.loaded, 'Total:', event.total); $('#progressBar').css('width', percentComplete + '%'); $('#progressText').text('下载进度: ' + percentComplete + '%'); } else { console.log('无法计算进度'); } }; // 下载完成后处理 xhr.onload = function () { if (xhr.status === 200) { // 隐藏进度条 $('#progressContainer').hide(); $('#progressText').hide(); // 创建下载链接并触发下载 var blob = xhr.response; var downloadUrl = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = downloadUrl; // 从响应头中获取文件名 var disposition = xhr.getResponseHeader('Content-Disposition'); var fileName = '下载文件.xlsx'; if (disposition && disposition.indexOf('filename*=utf-8\'\'') !== -1) { var filenameRegex = /filename\*=utf-8''(.+)/; var matches = filenameRegex.exec(disposition); if (matches != null && matches[1]) { fileName = decodeURIComponent(matches[1]); } } a.download = fileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(downloadUrl); } else { alert('下载失败,请重试。'); $('#progressContainer').hide(); $('#progressText').hide(); } }; // 发送请求 xhr.send(form);});
后端代码,使用easyExcel导出
//数据查询List<Sell> sellList = this.search();// 将Excel写入ByteArrayOutputStreamtry (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { // 使用EasyExcel将数据写入ByteArrayOutputStream EasyExcel.write(baos, Sell.class) .sheet("列表") .doWrite(sellList); // 获取Excel字节数组 byte[] excelBytes = baos.toByteArray(); // 设置响应头 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String fileName = URLEncoder.encode("列表导出_Sell", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx"); response.setHeader("Cache-Control", "max-age=0"); response.setContentLength(excelBytes.length); // 设置Content-Length // 将Excel字节数组写入响应 try (OutputStream out = response.getOutputStream()) { out.write(excelBytes); out.flush(); }} catch (IOException e) { e.printStackTrace();}
方案2:遮罩层
html代码:
<div id="loadingMask" class="loading-mask" style="display: none;"> <div class="loading-content"> <div class="spinner"></div> <p>正在导出中...</p> </div></div>
css:
.loading-mask { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 9999; display: flex; justify-content: center; align-items: center;}.loading-content { text-align: center; color: #fff;}.spinner { border: 8px solid rgba(255, 255, 255, 0.3); border-top: 8px solid #fff; border-radius: 50%; width: 60px; height: 60px; animation: spin 1s linear infinite; margin: 0 auto 20px;}@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); }}button:disabled { opacity: 0.6; cursor: not-allowed;}
js代码(阻止了有遮罩层时用户仍然可以通过键盘或其他方式触发多次点击):
$(document).ready(function () { // 确保DOM加载完成后执行 $('#export_btn').on('click', function (e) { e.preventDefault(); // 阻止默认表单提交行为 var $exportBtn = $(this); // 禁用导出按钮,防止重复点击 $exportBtn.prop('disabled', true); // 显示遮罩层 $('#loadingMask').show(); // 获取表单数据并构建FormData对象 var formData = $('#searchForm').serializeArray(); var form = new FormData(); $.each(formData, function () { form.append(this.name, this.value); }); // 添加额外的参数 form.append('publishFrom', '${RequestParameters.type}'); // 创建XHR对象 var xhr = new XMLHttpRequest(); xhr.open('POST', '路径/exportData', true); xhr.responseType = 'blob'; // 设置响应类型为blob // 监听下载完成后处理 xhr.onload = function () { $('#loadingMask').hide(); // 隐藏遮罩层 $exportBtn.prop('disabled', false); // 启用导出按钮 if (xhr.status === 200) { // 创建下载链接并触发下载 var blob = xhr.response; var downloadUrl = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = downloadUrl; // 从响应头中获取文件名 var disposition = xhr.getResponseHeader('Content-Disposition'); var fileName = '下载文件.xlsx'; if (disposition && disposition.indexOf("filename*=utf-8''") !== -1) { // 修改:修正单引号字符 var filenameRegex = /filename\*=utf-8''(.+)/; var matches = filenameRegex.exec(disposition); if (matches != null && matches[1]) { fileName = decodeURIComponent(matches[1]); } } a.download = fileName; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(downloadUrl); } else { alert('下载失败,请重试。'); } }; // 监听网络错误 xhr.onerror = function () { $('#loadingMask').hide(); // 隐藏遮罩层 $exportBtn.prop('disabled', false); // 启用导出按钮 alert('网络错误,请检查您的连接。'); }; xhr.send(form); });});
后端代码和方案1一致