You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:48:17