从后端发送Excel文件到前端并在前端下载

3

我使用 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);
    })
}

在我回答之前,我想确保我理解您的需求。您希望用户能够单击一个按钮,然后将Excel文件(您已经创建好了)下载到他们的计算机上,是这样吗? - djs
2个回答

1

这是之前答案的完全重写,如果有人需要之前的内容,非常抱歉,但这个版本更优。我正在使用一个由express-generator创建的项目,并在三个文件中工作:

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

index.ejs

从一个带有download属性的锚标签开始,使用您想要的任何文件名和空的href属性。我们将在main.js文件中使用代表Excel文件的ObjectURL填充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

选择锚元素,然后向路由 /downloadExcel 发送一个 fetch() 请求。将响应转换为 Blob,然后从此 Blob 创建一个 ObjectURL。然后可以将锚标签的 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存储库。如果您无法在项目中使用此代码,请克隆它并尝试自己运行。

工作原理

当页面加载时,会向我们的服务器发送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');

我在routes/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服务器发送的响应被存储在main.js文件中fetch()调用的返回值downloadExcelResponse中:
const downloadExcelResponse = await fetch('/downloadExcel');

downloadExcelResponse是一个Response对象,为了我们的目的,我们需要使用Response.blob()方法将其转换为Blob对象:

const downloadExcelBlob = await downloadExcelResponse.blob();

现在我们有了Blob,我们可以调用URL.convertObjectURL()将这个Blob转换成我们可以用作下载链接的href
const downloadExcelObjectURL = URL.createObjectURL(downloadExcelBlob);

目前为止,我们已经在浏览器中有一个代表Excel文件的URL,我们可以通过将其添加到我们之前选择的DOM元素的href属性中,将href指向该URL:

页面加载时,我们使用以下代码选择了锚元素:

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

因此,我们在进行 fetch 请求的函数中将 URL 添加到 href 中:

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指向的任何内容,而在我们的情况下,它是代表Excel文档的Blob的URL。

我从以下来源获取了信息:

这是一个gif,展示了下载过程在我的机器上的样子:

enter image description here


嘿,res.download('file_name')没有自动下载文件。我首先在后端服务器目录中使用exceljs npm模块创建Excel文件workbook.xlsx.writeFile("./file_name.xlsx");然后当我执行以下操作时,response.download('./file_name.xlsx'); 它不会自动下载。 - Navaneeth
@Navaneeth 好的,你试过我发布的解决方案了吗?如果文件存在于你的硬盘上,并且你正在使用Express应用程序,你应该能够根据自己的需要调整我的解决方案。克隆我的repo并添加你的exceljs npm模块,将我的excel.xlsx文件替换为你自己的文件,看看是否有效。如果这不起作用,请发布更多的代码,我可以尝试帮助你。 - djs
@Navaneeth,我找到了一个更好的解决方案,请查看我的更新答案。 - djs
@daniel.Schoreder 我编辑了问题中的内容。 - Navaneeth
@Navaneeth 好的,我尝试使用你的代码作为另一个答案的基础。看看那个是否更接近你想要的。如果那个不清楚,我很乐意帮助你,给你更多指导。 - djs

1

好的,现在我看到了你的代码,我可以尝试一点帮助。我稍微重构了你的示例,以便更容易理解,但请随意根据自己的需要进行调整。

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是干什么用的。
2. 给函数命名并将“parm”更改为“param”以表示参数列表。
function(parm1,parm2,parm3){

我的版本:
function sendFetch(param1, param2, param3) {

我不确定你如何实际调用函数,所以我给它命名了。此外,parm并不清晰。Param也不太好,应该描述它是什么,但我从你的代码中无法知道。

  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"变量,因为我没有在你的代码中看到它。
清理了一些格式。
我对serviceDetails使用了"点"符号表示法,但这只是个人偏好。我还改变了fetch()调用的间距,但这里不需要重印。不会影响任何东西。
创建一个blob从fetch响应中。
var file = new Blob([res], { type : 'application/octet-stream' });

我的版本:

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

我不确定为什么要调用 Blob 构造函数以及 [res] 应该是什么。从 fetch() 返回的 Response 对象具有一个 blob() 方法,该方法返回一个 Promise,该 Promise 解析为一个 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,然后将其分配,但你也可以很容易地不将其存储在变量中。
7. 调用发送fetch请求的函数。

因为我的函数签名是:

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文件的,然后使用我传递的参数触发了fetch请求。我不确定你在应用程序中如何实现这一点,因为它没有包含在你的代码中。
我刚刚讲解的所有内容都是前端相关的。我假设你的服务器端代码实际上是通过调用service.js中的response.sendFile()发送excel文件的。如果你确定文件已经被发送,那么我给你的代码应该可以工作,只需要根据你的应用进行调整。
因此,总之,这段代码的作用是:
  1. 加载一个带有未设置href属性的锚标签的HTML页面。
  2. 向服务器发送一个fetch()请求。
  3. 将fetch响应转换为Blob,然后从这个Blob创建一个ObjectURL
  4. 将该ObjectURL分配给锚标签的href属性。
当用户点击“下载Excel文件”链接时,应下载Excel表格。如果您不希望在fetch请求之前让他们看到该链接,您绝对可以使用JS创建锚标记,如果您想知道如何做,请告诉我。
与之前一样,这里是一个gif,展示了它在我的机器上的效果(这是使用您的版本和我的修改后的版本):

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接