如何让 react-dropzone 中的预览支持非图片文件?

14

我需要帮助,想知道如何使用 react-dropzone NPM 包在除图片文件 (*.png, *.jpg/jpeg, *.gif 等) 之外的文件上显示上传文件的预览。

目前,在网页表单中使用 Dropzone 上传其它类型的文件时,如果上传的是图片文件(*.png, *.jpg等),我们设置的预览就会显示一个小缩略图(见下图)。

然而,如果我上传其他类型的文件,比如 MS-Outlook 的 *.docx、*.xlsx,或 Adobe Acrobat 的 *.pdf 文件,则只会显示一个空白框和一个损坏文件的图像以及我所放置的任何alt="..."文本,例如 "已上传文件预览" (见下图)。

我们所使用的代码几乎是直接从React Dropzon网站上的预览示例中复制过来的,因此我想知道是否有什么遗漏了呢?

我尝试过以下方法:

  1. 创建一个使用react-dropzone<Dropzone>页面,它设置为接受任何文件类型,并使用https://react-dropzone.js.org/#previews中提供的代码进行预览,在<Dropzone>部分的底部添加 "预览" <aside>
  2. 拖放任何类型的图像文件(*.png, *.jpg或*.jpeg、*.gif等)。
  3. 查看出现的预览(应该可以,是一个图像文件的缩略图)。
  4. 现在将任何其他类型的文件(.doc, .xls, *.pdf等)拖放到<Dropzone>上。
  5. 查看出现的该文件的预览(应该是空白并带有一个损坏文件的图像以及您所放置的任何描述文件的alt="..."文本)。

在文件顶部导入了 Dropzone:

import React, { Component, /* useCallback */ } from "react";
...
import Dropzone from "react-dropzone";
...

缩略图的样式/CSS代码 —

...
const thumbsContainer = {
  display: "flex",
  flexDirection: "row",
  flexWrap: "wrap",
  marginTop: 16
};

const thumb = {
  display: "inline-flex",
  borderRadius: 2,
  border: "1px solid #eaeaea",
  marginBottom: 8,
  marginRight: 8,
  width: 100,
  height: 100,
  padding: 4,
  boxSizing: "border-box"
};

const thumbInner = {
  display: "flex",
  minWidth: 0,
  overflow: "hidden"
};

const img = {
  display: "block",
  width: "auto",
  height: "100%"
};
...

onDrop()回调函数——

...
onDrop = (acceptedFiles, rejectedFiles) => {
  let files = acceptedFiles.map(async file => {
    let data = new FormData();
    data.append("file", file);

    let item = await axios
      .post("form/upload", data, {
        headers: {
          "X-Requested-With": "XMLHttpRequest",
          "Content-Type": "application/x-www-form-urlencoded"
        }
      })
      .then(response => {
        return Object.assign(file, {
          preview: URL.createObjectURL(file),
          filename: response.data.filename
        });
      })
      .catch(err => {
        let rejects = rejectedFiles.map(async file => {
          let data = new FormData();
          await data.append("file", file);

          console.log("There was an error while attempting to add your files:", err);
          console.log("The files that were rejected were:\n", rejects.join('\n'));
        })
      });
    return item;
  });
  Promise.all(files)
    .then(completed => {
      let fileNames = completed.map(function(item) {
        return item["filename"];
      });
      this.setState({ files: completed, fileNames: fileNames });
    })
    .catch(err => {
      console.log('DROPZONE ERROR:', err);
    });
  };
...

使用 <Dropzone> 的实际 JSX 代码在 React.JS 的 return() 中——

...
<Form.Field>
  <label>Upload Files or Screenshots</label>
  <Dropzone accept={acceptedFileTypes} onDrop={this.onDrop}>
    {({ getRootProps, getInputProps, isDragActive }) => {
      return (
        <div
          {...getRootProps()}
          className={classNames("dropzone", {
            "dropzone--isActive": isDragActive
          })}
        >
          <input {...getInputProps()} />
          {isDragActive ? (
            <div>
              <div className="centered">
                <Icon name="cloud upload" size="big" />
              </div>
              <div className="centered">Drop Files Here.</div>
              <div className="centered">
                <Button className="drop-button">
                  Or Click to Select
                </Button>
              </div>
            </div>
          ) : (
            <div>
              <div className="centered">
                <Icon name="cloud upload" size="big" />
              </div>
              <div className="centered">
                Drag and Drop Supporting Files here to Upload.
              </div>
              <div className="centered">
                <Button className="drop-button">
                  Or Click to Select
                </Button>
              </div>
            </div>
          )}
        </div>
      );
    }}
  </Dropzone>
  <aside style={thumbsContainer}>{thumbs}</aside>
</Form.Field>
...

期望的行为 —

我希望所有文件类型都能生成正确的预览。

我的设置 —

  • MacBook Pro 13-Inch 2018
  • 2.7 GHz Intel Core i7 处理器
  • 16 GB 2133 MHz LPDDR3 RAM 内存
  • MacOS 版本 10.14.4
  • React 和 React-DOM 版本 16.5.2
  • Node.JS 版本 10.15.2
  • yarn 版本 1.15.2
  • React Dropzone 版本 10.1.4
  • Google Chrome for Mac OS 浏览器版本 74.0.3729.131 (Official Build) (64-bit)
  • 上传到 AWS EC2 实例容器服务器。

...所以,除了图像以外的其他文件预览不起作用,这是我做错了什么吗?除了图像文件之外,这不是一个功能吗?请指导。

另外还有几个问题 —

  1. 如果在使用 react-dropzone 时无法为图像以外的文件生成预览,是否有一种方法可以使图像文件生成预览,而所有其他文件只列出上传的文件名等信息,就像您的React Dropzone 网站上的某些示例一样?如果有,如何在拖放文件到 <Dropzone> 时在两者之间切换?
  2. 在我们的代码中,我们几乎完全从React Dropzone 网站的示例复制了,我们发现当您将一组文件拖放到 <Dropzone> 上,然后尝试再次拖放时,会清除第一组文件并用新的文件替换它们。是否有一种方法可以让 <Dropzone> 在每次拖放时累加添加文件,而不仅仅是清除以前的文件并用新的文件替换?如果有,那么请说明具体步骤。
  3. 如果所有其他文件都使用空白框作为文件预览,是否有一种方法可以去掉“损坏文件”的图标?

我很感激任何建设性的回复。 谢谢您提前。


似乎这与浏览器有关。Safari在macOS和iOS上都能很好地生成PDF的预览。 - Dylan
1
我想,除非您有一个用于生成预览的服务或库,否则可能会很困难。我会努力显示与非图像文件类型相关的图标。 - David Kerr
2
你尝试过使用HTML原生支持的<object>标签吗?它可以生成多种不同mime类型的预览。或者,你可以为你支持的类型(例如所有图片和PDF)创建预览,对于其余类型,则添加一个带有一个角落低垂和文件扩展名作为文本的彩色矩形作为替代方案。 - Fatorice
1个回答

0

只是为了澄清,React-dropzone 不是在您的页面上显示预览的工具。您是通过使用图像标签来实现的。
现在,针对您想要展示所有文档文件类型的需求,您首先需要一个解析器用于所需的类型,因为浏览器不支持读取除 pdf 之外的文档文件(.doc/.xlsx)。
类似于 react-doc-viewer 这样的工具。

有了它,您可以读取上传的文件并使用软件包渲染器来显示这些文件。

const docs = [
    { uri: "https://url-to-my-pdf.pdf" },
    { uri: require("./example-files/pdf.pdf") }, // Local File
];
... 
<DocViewer documents={docs} />;

如果您不想安装库来完成此操作,只需更改显示所选文件的代码部分即可。
const thumbs = files.map(file => {
  // Add other images types as needed
  if(["image/png", "image/jpeg"].includes(file.type)){
    return (
      <div style={thumb} key={file.name}>
        <div style={thumbInner}>
          <img
            src={file.preview}
            style={img}
            // Revoke data uri after image is loaded
            onLoad={() => { URL.revokeObjectURL(file.preview) }}
          />
        </div>
      </div>
    );
  }
  // Handling other file types
  return (
    <div style={thumb} key={file.name}>
      <div style={thumbInner}>
        <p>{file.name}</p
      </div>
    </div>
  );
});

您可以为不同的文件 MIME 类型添加不同的条件。
文件 MIME 类型的完整列表可在 mdn 文档 中找到。

  1. 如果您想将数据追加到同一个列表中,您需要更改 onDrop() 函数,因为在 Promise 的 resolve 部分中您正在覆盖该列表。
let fileNames = [...this.state.filenames, 
   ...completed.map(function(item) {
     return item["filename"];
})];
this.setState({files: [...this.state.files, ...completed], fileNames: fileNames });
  • 第一部分应该解决这个问题

  • 1
    是的,你说得对,我没有意识到它使用了微软服务器来查看文件,因为我实际上从未使用过它。所以我猜解析本地文件是行不通的。 - Scramjet

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