背景

网站使用 Wordpress 搭建,现在要在一个 wpcf7 的插件表单中添加一个拖拽上传功能。目前只找到 dropzonejs 比较符合需求。但是 dropzonejs 文档写的过于简陋,如何将文件上传功能嵌入到 form 并且在 submit 时一并提交过去(不是异步上传文件),这在文档上中的 options 和 methods 根本找不到解决办法。经过折腾后找到以下解决方案。

快捷通道

如果你使用的也是 wpcf7 ,那么直接使用 Drag and Drop Multiple File Upload – Contact Form 7 这个插件吧,下面的内容都不需要看了

1. 加载 dropzone

1
2
import Dropzone from "dropzone";
import "dropzone/dist/dropzone.css";

2. 在 form 中添加拖拽上传区域

下面是在原油的 file 字段的下方添加一个 .dropzone-box 节点,用于显示 dropzone UI

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
const $fileLabel = $(".wpcf7-file").parents("label");
$fileLabel.hide();
const $title = $fileLabel.text().trim();

$fileLabel.parent().append(
  $(`
    <div class="dropzone-box">
      <p>${$title}</p>
      <div class="dropzone">
        <div class="upload-hint">
          <div class="icon"></div>
          <div>Drag & drop reference images here or <span class="highlight">Browse</span></div>
        </div>
        <div class="dropzone-preview"></div>
      </div>
    </div>
  `)
);

3. 初始化 dropzone

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//  因为不希望它影响到原有的 form
//  所以这里选择的 DOM 是 .dropzone 而不是一个 form
let myDropzone = new Dropzone(".dropzone", {
  //  因为我们没有选择 form ,必须提供 url,这里随便输入一个字符串就行了
  //  因为我们会重写 submitRequest 方法,所以这个地址并不会被访问
  url: "/",
  //  这里要设置未 true,这样才会在点击上传文件时能显示上传成功动画
  autoProcessQueue: true,
  uploadMultiple: true,
  parallelUploads: 100,
  maxFiles: 100,
  addRemoveLinks: true,
  //  这里是第 2步中生成的预览文件存放的 DOM
  previewsContainer: ".dropzone-preview",
  //  设置可点击上传的 DOM
  clickable: ".dropzone",
});

//  默认情况下,在上传文件时 dropzonejs 会将文件提交到 url 参数指定的地址上去
//  但是我们这里的需求是希望它跟 wpcf 一起发送
//  因此这里当文件发送时直接全部设置为成功
myDropzone.submitRequest = (_, __, files) => {
  for (let file of files) {
    file.status = "success";
    myDropzone.emit("success", file, "success");
    myDropzone.emit("complete", file);
  }
};

4. 监听文件添加和删除

dropzonejs 添加文件和删除时,我们需要将文件保存下来,以便在 wpcf 提交时同时提交过去

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let files: File[] = [];
myDropzone.on("addedfile", (file) => {
  files.push(file);
  console.log(`File added: ${file.name}`);
});

myDropzone.on("removedfile", (file) => {
  files = files.filter((_file) => _file !== file);
  console.log(`File removed: ${file.name}`);
});

5. 拦截 wpcf submit 并且提交 files

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//  手动处理 form 提交
$(".wpcf7-submit").on("click", (ev) => {
  ev.preventDefault();
  ev.stopPropagation();

  (async () => {
    //  这里调用的是 wpcf7 提供的全局方法
    //  通过阅读其源代码,发现可以通过 submit 的第二个参数提供 submitter 来额外添加字段
    window["wpcf7"].submit($(".wpcf7-form").get(0), {
      submitter: {
        name: "files",
        value: await files.map(async file=> await fileToBuffer(file))
      },
    });
  })();
});

async function fileToBuffer(file: File) {
  return new Promise<any>((res) => {
    var reader = new FileReader();

    reader.onload = function (event) {
      //  @ts-ignore
      res(reader.result);
    };

    reader.readAsArrayBuffer(file);
  });
}