将Excel文件从后端发送到前端,然后在前端下载

时间:2019-11-18 22:22:17

标签: node.js rest express http-post exceljs

我已经使用Exceljs npm模块在后端(Express JS)创建了一个Excel文件。我将其存储在临时目录中。现在,我想将文件从后端发送到前端,并在用户单击按钮时将其下载到前端。我在两件事上感到震惊  1.如何通过HTTP POST请求将文件从后端发送到前端  2.然后如何在前端

下载文件

编辑内容:

我需要前端为一个按钮,它将文件附加到该按钮,然后下载。这就是我的代码的样子,我没有从后端到前端正确获取文件

前端文件:

 function(parm1,parm2,parm3){
 let url =${path}?parmA=${parm1}&parmB=${parm2}&parmC=${parm3};
 let serviceDetails = {};
 serviceDetails["method"] = "GET";
 serviceDetails["mode"] = "cors";
 serviceDetails["headers"] = {
 "Content-Type": "application/json"
 };

fetch(url, serviceDetails)
  .then(res => {
    if (res.status != 200) {
      return false;
    }
    var file = new Blob([res], { type : 'application/octet-stream' });
    a = document.createElement('a'), file;
    a.href = window.URL.createObjectURL(file);
    a.target = "_blank"; 
    a.download = "excel.xlsx";
    document.body.appendChild(a);
    a.click(); 
    document.body.removeChild(a); 

  }).catch(error => {
     return false;
  });

}`

router.js

var abc = ... // this is a object for the controller.js file
router.get('/path', function(req, res) {
abc.exportintoExcel(req, res);
});

controller.js

let xyz = ... //this is a object for the service.js file
 exports.exportintoExcel = function(req, res) {
 xyz.exportintoExcel(reqParam,res);
 }

service.js

exportintoExcel(req,response){
       //I have a excel file in my server root directory
        const filepath = path.join(__dirname,'../../nav.txt');
        response.sendFile(filepath);
    })
}

2 个答案:

答案 0 :(得分:0)

这是对先前答案的完整重写,如果有人需要该答案,对不起,但是此版本更好。我正在使用一个由express-generator创建的项目,并在以下三个文件中工作:

  • routes / index.js
  • views / index.ejs
  • public / javascripts / main.js

index.ejs

从锚标签开始,该锚标签具有download属性,所需的文件名和空的href属性。稍后,我们将用表示URL的ObjectURL填充main.js文件中的href

<body>
  <a id="downloadExcelLink" download="excelFile.xlsx" href="#">Download Excel File</a>
  <script type="text/javascript" src="/javascripts/main.js"></script>
</body>

public / javascripts / main.js

选择锚元素,然后向路由fetch()发出/downloadExcel请求。将响应转换为Blob,然后从该ObjectURL创建一个Blob。然后,您可以将锚标记的href属性设置为此ObjectURL

const downloadExcelLink = document.getElementById('downloadExcelLink');

(async () => {
  const downloadExcelResponse = await fetch('/downloadExcel');
  const downloadExcelBlob = await downloadExcelResponse.blob();
  const downloadExcelObjectURL = URL.createObjectURL(downloadExcelBlob);
  downloadExcelLink.href = downloadExcelObjectURL;
})();

routes / index.js

在索引路由器中,您只需要调用res.sendFile()函数并将其传递到服务器上Excel文件的路径即可。

router.get('/downloadExcel', (req, res, next) => {
  const excelFilePath = path.join(__dirname, '../tmp/excel.xlsx');
  res.sendFile(excelFilePath, (err) => {
    if (err) console.log(err);
  });
});

就是这样!您可以找到该项目的git repo here。如果无法按原样在您的项目中使用此代码,请对其进行克隆并尝试一下。

工作原理

页面加载后,将向服务器发出4个请求,如控制台输出所示:

GET / 200 2.293 ms - 302
GET /stylesheets/style.css 200 1.123 ms - 111
GET /javascripts/main.js 200 1.024 ms - 345
GET /downloadExcel 200 2.395 ms - 4679

前三个请求是对index.ejs(/),CSS样式表和我们的main.js文件的请求。第四个请求通过我们的调用发送到main.js文件中的fetch('/downloadExcel')

const downloadExcelResponse = await fetch('/downloadExcel');

我在该路由的route / index.js中有一个路由处理程序设置,该路由使用res.sendFile()从我们的文件系统发送文件作为响应:

router.get('/downloadExcel', (req, res, next) => {
  const excelFilePath = path.join(__dirname, '../tmp/excel.xlsx');
  res.sendFile(excelFilePath, (err) => {
    if (err) console.log(err);
  });
});

excelFilePath必须是您系统上文件的路径。在我的系统上,这是路由器文件和Excel文件的布局:

/
/routes/index.js
/tmp/excel.xlsx

从Express服务器发送的响应存储为downloadExcelResponse,作为对main.js文件中对fetch()的调用的返回值:

const downloadExcelResponse = await fetch('/downloadExcel');

downloadExcelResponseResponse对象,出于我们的目的,我们想使用Blob方法将其变成Response.blob()对象:

const downloadExcelBlob = await downloadExcelResponse.blob();

现在我们有了Blob,我们可以调用URL.convertObjectURL()将此Blob转换成我们可以用作href的下载链接:

const downloadExcelObjectURL = URL.createObjectURL(downloadExcelBlob);

这时,我们有了一个表示浏览器中Excel文件的URL,并且可以通过将href指向该URL,方法是将其添加到我们先前选择的href属性的DOM元素中:

页面加载后,我们在此行中选择了锚元素:

<a id="downloadExcelLink" download="excelFile.xlsx" href="#">Download Excel File</a>

因此,我们在发出href请求的函数中,将URL添加到fetch

downloadExcelLink.href = downloadExcelObjectURL;

您可以在浏览器中检出元素,并在页面加载时看到href属性已被更改:

enter image description here

注意,在我的计算机上,锚标记现在为:

<a id="downloadExcelLink" download="excelFile.xlsx" href="blob:http://localhost:3000/aa48374e-ebef-461a-96f5-d94dd6d2c383">Download Excel File</a>

由于链接上存在download属性,因此,单击链接时,浏览器将下载href指向的内容,在我们的示例中是{{1}的URL },代表Excel文档。

我从以下来源获得了我的信息:

以下是下载过程在我的计算机上的外观的gif图像:

enter image description here

答案 1 :(得分:0)

好的,现在我看到了您的代码,我可以尝试一些帮助。我对您的示例进行了一些重构,以使我更容易理解,但可以随时根据您的需求进行调整。

index.html

我不知道您正在使用的页面是什么样子,但是在您的示例中,您似乎在fetch()调用期间使用JavaScript创建了锚元素。我只是在实际页面中使用HTML创建一个页面,有没有原因您不能这样做?

<body>
  <a id="downloadLink" download="excel.xlsx" href="#">Download Excel File</a>
  <script type="text/javascript" src="/javascripts/test.js"></script>
</body

有了这个,这是我的前端JS文件版本:

test.js

const downloadLink = document.getElementById('downloadLink');

sendFetch('a', 'b', 'c');

function sendFetch(param1, param2, param3) {

  const path = 'http://localhost:3000/excelTest';
  const url = `${path}?parmA=${param1}&parmB=${param2}&parmC=${param3}`;
  const serviceDetails = {};
  serviceDetails.method = "GET";
  serviceDetails.mode = "cors";
  serviceDetails.headers = {
    "Content-Type": "application/json"
  };

  fetch(url, serviceDetails).then((res) => {
    if (res.status != 200) {
      return false;
    }
    res.blob().then((excelBlob) => {
      const excelBlobURL = URL.createObjectURL(excelBlob);
      downloadLink.href = excelBlobURL;
    });
  }).catch((error) => {
     return false;
  });
}

我不得不填写一些细节,因为我无法分辨代码中发生了什么。这是我更改的内容:

  1. 选择了DOM元素而不是创建它:

您的版本:

a = document.createElement('a'), file;

我的版本:

index.html

<a id="downloadLink" download="excel.xlsx" href="#">Download Excel File</a>

test.js

const downloadLink = document.getElementById('downloadLink');

这免除了创建元素的麻烦。除非您出于某种原因需要这样做,否则我不会。我也不确定file在您的原始作品中的作用。

  1. 命名函数并更改参数->参数列表的参数

您的版本:

function(parm1,parm2,parm3){

我的版本:

function sendFetch(param1, param2, param3) {

我不确定您实际上如何调用函数,所以我将其命名。另外,parm还不清楚。参数也不是很好,应该描述它的含义,但是我不知道您的代码。

  1. 创建一个path变量并将url赋值放在反引号中

您的版本:

let url =${path}?parmA=${parm1}&parmB=${parm2}&parmC=${parm3};

我的版本:

const path = 'http://localhost:3000/excelTest';
const url = `${path}?parmA=${param1}&parmB=${param2}&parmC=${param3}`;

在您的版本中,该url分配应该引发错误。看起来您想使用字符串插值,但是您需要为此添加反引号。另外,我必须定义一个path变量,因为在您的代码中没有看到一个变量。

  1. 清理了一些格式

我在serviceDetails中使用了“点”符号,但这只是个人喜好。我还更改了fetch()调用的间距,但无需在此处重新打印。不应该有任何影响。

  1. 根据获取响应创建一个blob

您的版本:

var file = new Blob([res], { type : 'application/octet-stream' });

我的版本:

res.blob().then((excelBlob) => {

我不确定您为什么要调用Blob构造函数以及该[res]应该是什么。从Response返回的fetch()对象具有一个blob()方法,该方法返回一个承诺,该承诺可以解析为Blob并具有数据所处的MIME类型。在Excel文档中,这是application/vnd.openxmlformats-officedocument.spreadsheetml.sheet

  1. Blob创建一个ObjectURL并将此URL添加到定位标记的href

您的版本:

a = document.createElement('a'), file;
a.href = window.URL.createObjectURL(file);
a.target = "_blank"; 
a.download = "excel.xlsx";
document.body.appendChild(a);
a.click(); 
document.body.removeChild(a);

我的版本:

const excelBlobURL = URL.createObjectURL(excelBlob);
downloadLink.href = excelBlobURL;

您必须进行大量的DOM操作,但我不确定为什么需要这样做。如果必须动态创建此元素,那么我不确定为什么要“单击”该元素,然后删除它(如果用户应该能够单击它)。也许可以为我弄清楚为什么要这样做,或者是否确实需要这样做。无论哪种方式,在我的版本中,我都创建ObjectURL然后分配它,但是您也可以很容易地不将其存储在变量中。

  1. 调用发送获取请求的函数。

因为我的功能签名是:

function sendFetch(param1, param2, param3)

我需要在某个地方调用它以触发请求,所以我这样做是这样的:

sendFetch('a', 'b', 'c'); 

页面加载后的正确时间,如您从服务器日志中看到的那样:

GET / 304 0.448 ms - -
GET /javascripts/test.js 304 1.281 ms - -
GET /excelTest?parmA=a&parmB=b&parmC=c 304 0.783 ms - -

前两个请求是针对index.html页面和test.js文件的,然后使用我传入的参数触发提取请求。我不确定您如何在应用程序中执行此操作,因为不包含在您的代码中。

我刚才介绍的所有内容都是前端。我假设您的服务器端代码实际上是通过调用service.js中的response.sendFile()发送一个excel文件。如果您确定文件正在发送,那么根据应用程序调整后,我给您的代码应该可以使用。

因此,总而言之,这段代码的作用是:

  1. 加载未设置href属性的锚标记的HTML页面。
  2. 向服务器发送fetch()请求。
  3. 将获取响应转换为Blob,然后从该ObjectURL创建一个Blob
  4. ObjectURL分配给锚标记的href属性。

当用户单击“下载Excel文件”链接时,应下载Excel工作表。如果您不希望他们在fetch请求之后才看到链接,则可以肯定地在JS中创建锚标记,如果您想了解如何做,请告诉我。

像以前一样,这是一张gif,显示它在我的计算机上的外观(这与您的版本和我的修改有关):

enter image description here