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

Rails Api+Backbone/Marionette架构下客户端PDF渲染与下载问题求助

解决方案分析

针对你遇到的AJAX请求PDF后无法直接处理的问题,结合你的技术栈(Rails API + Backbone/Marionette),下面是三个可行方向的具体方案:


1. 服务端优化配置,配合前端正确接收二进制响应

你的Rails服务端send_data的基础配置是没问题的,但AJAX请求默认不会触发浏览器的文件下载/预览行为,需要前端配合正确解析二进制数据。可以做以下调整:

服务端微调:
保留核心逻辑的同时,允许前端通过参数指定文件的展示方式(下载/内嵌预览),确保响应头完整:

def invoice
  @order = current_user.orders.with_uuid(params[:id])
  if @order
    @order.order_attachment.with_document_file do |file|
      File.open(file.path, 'r') do |f|
        pdf_content = f.read
        send_data pdf_content, 
          filename: 'report.pdf', 
          type: 'application/pdf', 
          disposition: params[:view_type] || 'attachment', # 支持前端指定是下载还是预览
          content_length: pdf_content.size
      end
    end
  else
    render nothing: true, status: :not_found
  end
end

前端调整请求:
用原生XMLHttpRequest或配置后的jQuery请求,指定响应类型为blob(二进制大对象),这是处理文件的关键:

// 原生XHR方案(推荐,处理二进制更可靠)
const xhr = new XMLHttpRequest();
xhr.open('GET', `${config.apiRootUrl}user/things/${@model.attributes.id}/invoice`, true);
xhr.responseType = 'blob'; // 必须指定响应类型为Blob

xhr.onload = function() {
  if (this.status === 200) {
    const blob = this.response;
    // 选项1:触发浏览器下载
    const url = window.URL.createObjectURL(blob);
    const downloadLink = document.createElement('a');
    downloadLink.href = url;
    downloadLink.download = 'report.pdf';
    document.body.appendChild(downloadLink);
    downloadLink.click();
    window.URL.revokeObjectURL(url); // 释放内存
    document.body.removeChild(downloadLink);

    // 选项2:在页面内嵌预览(比如用iframe)
    // const previewIframe = document.createElement('iframe');
    // previewIframe.src = url;
    // previewIframe.style.width = '100%';
    // previewIframe.style.height = '600px';
    // document.getElementById('pdf-preview-container').appendChild(previewIframe);
  }
};

xhr.send();

如果坚持用jQuery,需要配置xhrFields来指定响应类型:

$.ajax({
  url: `${config.apiRootUrl}user/things/${@model.attributes.id}/invoice`,
  method: 'GET',
  xhrFields: {
    responseType: 'blob'
  },
  success: function(blob) {
    // 同上处理Blob,触发下载或预览
    const url = window.URL.createObjectURL(blob);
    const downloadLink = document.createElement('a');
    downloadLink.href = url;
    downloadLink.download = 'report.pdf';
    document.body.appendChild(downloadLink);
    downloadLink.click();
    window.URL.revokeObjectURL(url);
    document.body.removeChild(downloadLink);
  },
  error: function(err) {
    console.log('获取PDF失败:', err);
  }
});

2. 客户端从原始数据重建PDF(原生JS即可,无需额外库)

如果你已经拿到了示例中那样的原始PDF文本数据,可以把它转成Blob再处理,不过这种方式要确保数据没有被编码损坏:

// 假设response.responseText是你拿到的原始PDF字符串
const rawPdfText = response.responseText;
// 将字符串转成Uint8Array二进制数组
const pdfBytes = new Uint8Array(rawPdfText.length);
for (let i = 0; i < rawPdfText.length; i++) {
  pdfBytes[i] = rawPdfText.charCodeAt(i);
}
// 生成PDF Blob
const pdfBlob = new Blob([pdfBytes], { type: 'application/pdf' });

// 后续处理同上:创建下载链接或内嵌预览
const url = window.URL.createObjectURL(pdfBlob);
// ...触发下载或预览的代码

⚠️ 注意:这种方式不如直接接收blob响应可靠,如果服务端返回的文本存在编码转换问题,可能会破坏PDF结构,所以优先推荐第一种方案。


3. 其他方案:用表单提交代替AJAX

如果不想处理AJAX的二进制数据逻辑,最简单的方式是动态创建表单提交请求,让浏览器直接处理服务端响应:

const downloadForm = document.createElement('form');
downloadForm.method = 'GET';
downloadForm.action = `${config.apiRootUrl}user/things/${@model.attributes.id}/invoice`;
// 如果需要传递额外参数,添加隐藏输入框
// const hiddenParam = document.createElement('input');
// hiddenParam.type = 'hidden';
// hiddenParam.name = 'extra_param';
// hiddenParam.value = 'value';
// downloadForm.appendChild(hiddenParam);

document.body.appendChild(downloadForm);
downloadForm.submit();
document.body.removeChild(downloadForm);

这种方式完全绕过AJAX,浏览器会自动识别服务端返回的PDF响应,直接弹出下载或在新标签页预览,代码简单且兼容性好,非常适合你的场景。


内容的提问来源于stack exchange,提问作者gastngouron

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:55:26