Flask网站无报错但无法导出Excel文件的原因排查
问题
我开发了一个简单的Python Flask网站,核心功能是用户填写表单后,可在HTML表格查看历史输入,并能选择部分表格行导出为Excel文件。目前网站其他功能正常(数据可正确存入数据库、表格展示正常等),但Excel导出功能无法实现——服务器返回200状态码(日志显示:127.0.0.1 - - [24/May/2023 19:21:00] "POST /export HTTP/1.1" 200 -),但客户端并未触发Excel文件下载。
前端HTML代码
<button id="exportButton" class="btn btn-primary">Export to Excel</button> <script> $(document).ready(function () { $('#projectTable').DataTable(); $('#exportButton').click(function () { var selectedRows = []; $('.row-checkbox:checked').each(function () { var rowData = $(this).closest('tr').find('td').map(function () { return $(this).text(); }).get(); selectedRows.push(rowData); }); // Send selectedRows to the server for export $.ajax({ type: 'POST', url: '/export', data: JSON.stringify(selectedRows), contentType: 'application/json', success: function (response) { // Handle the server's response, if needed // For example, you can show a success message or trigger a file download console.log('Export successful!'); }, error: function (xhr, status, error) { // Handle errors, if any console.error('Export error:', error); } }); }); }); </script> </body> </html>
后端Flask代码
@views.route('/export', methods=['POST']) @login_required def export(): selected_rows = request.get_json() # Get the selected rows' data from the request # Create a DataFrame from the selected rows' data df = pd.DataFrame(selected_rows, columns=['header1', 'header2', 'header3', 'header4', 'header5', 'header6', 'header7', 'header8']) # Generate an Excel file from the DataFrame excel_file = io.BytesIO() df.to_excel(excel_file, index=False) excel_file.seek(0) # Move the file pointer to the beginning of the file # Prepare the response headers headers = { 'Content-Disposition': 'attachment; filename=selected_rows.xlsx', 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } # Return the Excel file contents as the response return Response(excel_file, headers=headers)
原因及解决方案
核心原因
AJAX请求默认不会自动处理Content-Disposition: attachment这类触发浏览器下载的响应头,它只会把响应内容拿到回调函数里,不会触发下载行为。这就是服务器返回200但没有下载的根本原因。
解决方案
有两种可行的修改方式:
方式一:修改前端,手动触发下载
在AJAX的success回调里,把服务器返回的二进制数据转换成Blob对象,然后创建下载链接触发浏览器下载:
修改后的前端脚本:
<button id="exportButton" class="btn btn-primary">Export to Excel</button> <script> $(document).ready(function () { $('#projectTable').DataTable(); $('#exportButton').click(function () { var selectedRows = []; $('.row-checkbox:checked').each(function () { var rowData = $(this).closest('tr').find('td').map(function () { return $(this).text(); }).get(); selectedRows.push(rowData); }); $.ajax({ type: 'POST', url: '/export', data: JSON.stringify(selectedRows), contentType: 'application/json', responseType: 'blob', // 关键:指定响应类型为二进制Blob success: function (blob) { // 创建下载链接 const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'selected_rows.xlsx'; // 指定文件名 document.body.appendChild(a); a.click(); // 清理资源 window.URL.revokeObjectURL(url); document.body.removeChild(a); console.log('Export successful!'); }, error: function (xhr, status, error) { console.error('Export error:', error); } }); }); }); </script>
方式二:改用表单提交替代AJAX
如果不想处理Blob,可以把选中的行数据通过隐藏表单提交,浏览器会自动处理下载响应:
前端修改:
<form id="exportForm" method="POST" action="/export"> <input type="hidden" id="selectedRowsInput" name="selected_rows"> <button type="submit" id="exportButton" class="btn btn-primary">Export to Excel</button> </form> <script> $(document).ready(function () { $('#projectTable').DataTable(); $('#exportForm').submit(function (e) { var selectedRows = []; $('.row-checkbox:checked').each(function () { var rowData = $(this).closest('tr').find('td').map(function () { return $(this).text(); }).get(); selectedRows.push(rowData); }); // 把数据转成JSON字符串存入隐藏输入框 $('#selectedRowsInput').val(JSON.stringify(selectedRows)); }); }); </script>
后端对应修改:
因为现在数据是通过表单提交的,不再是JSON,所以要调整获取数据的方式:
import json @views.route('/export', methods=['POST']) @login_required def export(): # 从表单获取JSON字符串并解析 selected_rows = json.loads(request.form.get('selected_rows')) df = pd.DataFrame(selected_rows, columns=['header1', 'header2', 'header3', 'header4', 'header5', 'header6', 'header7', 'header8']) excel_file = io.BytesIO() df.to_excel(excel_file, index=False) excel_file.seek(0) headers = { 'Content-Disposition': 'attachment; filename=selected_rows.xlsx', 'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' } return Response(excel_file, headers=headers)
内容的提问来源于stack exchange,提问作者user21955070
相关产品推荐
相关产品推荐

