如何使用wicked_pdf无需刷新页面批量生成PDF并直接下载
解决wicked_pdf无刷新批量生成PDF的问题
嘿,我来帮你搞定这个wicked_pdf无刷新批量生成PDF的需求!你当前遇到的核心痛点是更换模板后必须刷新页面才能下载,本质是常规的send_data/send_file会触发浏览器的默认下载流程,导致页面刷新。要实现无刷新下载,我们需要通过AJAX请求配合前端手动触发下载动作,下面给你两种可行的方案:
方案一:返回Base64编码的PDF,前端创建下载链接
这种方案不需要生成临时文件,直接把PDF内容编码后返回给前端,由前端处理下载逻辑,适合单文件生成场景。
控制器代码
class PdfGeneratorController < ApplicationController def generate_pdf # 获取用户选择的模板参数 selected_template = params[:template] # 使用wicked_pdf渲染指定模板的PDF内容 pdf_content = WickedPdf.new.pdf_from_string( render_to_string( template: "pdf_templates/#{selected_template}", layout: "pdf_layout" # 你的PDF布局文件 ) ) # 将PDF内容转成Base64编码 base64_pdf = Base64.encode64(pdf_content) # 返回JSON响应,包含编码后的PDF和文件名 render json: { pdf_base64: base64_pdf, filename: "document_#{selected_template}_#{Time.now.strftime('%Y%m%d')}.pdf" } end end
前端代码(jQuery示例)
// 监听模板选择器的变化事件 $('#template-select').on('change', function() { const template = $(this).val(); // 禁用选择器防止重复请求 $(this).prop('disabled', true); $.ajax({ url: '/pdf_generator/generate_pdf', // 对应你的控制器路由 method: 'POST', data: { template: template }, success: function(response) { // 将Base64编码转成Blob对象 const byteChars = atob(response.pdf_base64); const byteNumbers = new Array(byteChars.length); for (let i = 0; i < byteChars.length; i++) { byteNumbers[i] = byteChars.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); const pdfBlob = new Blob([byteArray], { type: 'application/pdf' }); // 创建临时下载链接并触发点击 const downloadUrl = URL.createObjectURL(pdfBlob); const link = document.createElement('a'); link.href = downloadUrl; link.download = response.filename; document.body.appendChild(link); link.click(); // 清理临时资源 setTimeout(() => { document.body.removeChild(link); URL.revokeObjectURL(downloadUrl); }, 100); }, complete: function() { // 请求完成后重新启用选择器 $('#template-select').prop('disabled', false); } }); });
方案二:生成临时PDF文件,返回下载URL
如果你的PDF文件较大,或者需要批量生成后打包,这种方案更合适——控制器生成PDF后保存到临时目录,返回下载链接给前端,前端触发下载后再清理临时文件。
控制器代码
class PdfGeneratorController < ApplicationController def generate_pdf selected_template = params[:template] # 生成唯一文件名避免冲突 filename = "document_#{selected_template}_#{SecureRandom.uuid}.pdf" temp_pdf_path = Rails.root.join('tmp', 'pdfs', filename) # 确保临时目录存在 FileUtils.mkdir_p(Rails.root.join('tmp', 'pdfs')) # 生成PDF并保存到临时路径 WickedPdf.new.pdf_from_string( render_to_string(template: "pdf_templates/#{selected_template}", layout: "pdf_layout"), save_to_file: temp_pdf_path ) # 返回下载URL(需要配置对应路由) render json: { download_url: "/pdf_generator/download_pdf?filename=#{filename}", filename: filename } end # 新增下载临时PDF的动作 def download_pdf filename = params[:filename] temp_pdf_path = Rails.root.join('tmp', 'pdfs', filename) if File.exist?(temp_pdf_path) # 发送文件作为附件下载 send_file temp_pdf_path, filename: filename, type: 'application/pdf', disposition: 'attachment' # 下载完成后删除临时文件(可选,也可以用定时任务批量清理) File.delete(temp_pdf_path) if File.exist?(temp_pdf_path) else head :not_found end end end
前端代码
$('#template-select').on('change', function() { const template = $(this).val(); $(this).prop('disabled', true); $.ajax({ url: '/pdf_generator/generate_pdf', method: 'POST', data: { template: template }, success: function(response) { // 创建隐藏的下载链接并触发点击 const link = document.createElement('a'); link.href = response.download_url; link.download = response.filename; link.style.display = 'none'; document.body.appendChild(link); link.click(); document.body.removeChild(link); }, complete: function() { $('#template-select').prop('disabled', false); } }); });
批量生成扩展:打包成ZIP下载
如果需要一次性生成多个模板的PDF,你可以在控制器中把多个PDF打包成ZIP文件,再返回给前端下载:
控制器代码(需要rubyzip gem)
# 先在Gemfile中添加 gem 'rubyzip',然后bundle install class PdfGeneratorController < ApplicationController def generate_batch_pdf selected_templates = params[:templates] # 前端传入的模板数组,比如["invoice", "report"] zip_filename = "batch_documents_#{Time.now.strftime('%Y%m%d')}.zip" temp_zip_path = Rails.root.join('tmp', 'zips', zip_filename) FileUtils.mkdir_p(Rails.root.join('tmp', 'zips')) # 打包多个PDF到ZIP Zip::File.open(temp_zip_path, Zip::File::CREATE) do |zipfile| selected_templates.each do |template| pdf_content = WickedPdf.new.pdf_from_string( render_to_string(template: "pdf_templates/#{template}", layout: "pdf_layout") ) # 向ZIP中添加PDF文件 zipfile.get_output_stream("document_#{template}.pdf") do |stream| stream.write(pdf_content) end end end # 返回ZIP的Base64编码(前端处理方式和PDF类似) base64_zip = Base64.encode64(File.read(temp_zip_path)) render json: { zip_base64: base64_zip, filename: zip_filename } # 清理临时ZIP文件 File.delete(temp_zip_path) if File.exist?(temp_zip_path) end end
注意事项
- 确保你已经正确安装了wicked_pdf的依赖(
wkhtmltopdf),不同系统的安装方式略有不同。 - 配置好Rails路由,比如:
resources :pdf_generator do post :generate_pdf post :generate_batch_pdf get :download_pdf end - 对于方案二的临时文件,建议用定时任务(比如whenever gem)定期清理
tmp/pdfs和tmp/zips目录,避免磁盘空间被占用。 - 前端可以添加加载状态提示,提升用户体验。
内容的提问来源于stack exchange,提问作者Antoine Wako
相关产品推荐
相关产品推荐

