588

V1

2022/03/22阅读:62主题:自定义主题1

文件拖拽上传

一直以来,我都觉得将文件拖拽进浏览器,就实现了文件上传是一件非常酷的事情。

我以为酷=难,所以我也没有去了解如何去实现。但是,我在smashingmagzine上看到了这篇文章,才发现不过如此。

虽然实现起来并不难,但是整个过程中涉及的API非常多。

对于我这种毫无记忆力的人,是不可能完全记下来的,因此我就用这篇文章记录下来。

最终效果
最终效果

实现这个上传和进度的展示,就得实现前端和后端。这里我先写后端是如何实现的,然后再写前端。

(因为此处后端的实现更简单一点,不感兴趣的小伙伴可以直接略过后端,毕竟我们是前端)

后端实现

在这里我使用express实现服务器功能,使用multer接收multipart/form-data文件。

首先初始化服务器,一些express的常规操作:

const express = require("express");
const port = 3000;
const baseUrl = `http://localhost:${port}/`;

const app = express();

// 让uploads文件夹成为公开的文件夹
// 之后就能展示上传的图片了
app.use("/uploads", express.static("uploads"));

// 允许跨域
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin""*");
  next();
});

// 就只有一个upload路由
app.post("/upload",  (req, res) => {
  
});

app.listen(3000, () => {
  console.log(`running at ${baseUrl}`);
});

接着,使用multer处理客户端上传的multipart/form-data表单数据。

以下是对它的一些配置,具体可以见它的官网:

const multer = require("multer");

//这些都是配置项,在它npm介绍有写
// 存储到本地硬盘 文件夹为./uploads,并设置文件名
const storage = multer.diskStorage({
  // 存储位置为./uploads
  destination(req, file, cb) {
    cb(null"./uploads");
  },
  //设置文件名
  filename(req, file, cb) {
    const uniqueSuffix = Date.now() + "-" + Math.round(Math.random()) + "-";
    cb(null, uniqueSuffix + file.originalname);
  },
});

// 用这些配置初始化multer,得到upload中间件
const upload = multer({
  storage: storage,
  limits: {
    // 图片最大尺寸为15mb
    fileSize1024 * 1024 * 15,
  },
  // 只接收图片类型的文件
  fileFilter(req, file, cb) {
    if (file.mimetype.includes("image")) {
      return cb(nulltrue);
    }
    cb(nullfalse);
  },
});

配置完了后,我们使用multer作为中间件。在express中,针对某个路由,可以使用多个中间件。

因此,对于/upload路由,我们先用multer进行处理,然后再实现自己想要的操作。

//初始化multer得到的upload中间件
// upload.array()代表允许客户端一次上传多个文件
app.post("/upload", upload.array("file"20), (req, res) => {
//
});

我们可以指定上传单个文件(.single),或者上传多个文件(.array),以及混合起来不同的filedname。

上图中的filedname是指前端的FormData中的fieldname,如:

const fd = new FormData();
fd.append("file", data);
// file就是一个fieldname

调用multer中间件,我们再得到的req就多了一个file(s)属性,file(s)属性下又有如下表格中的属性。(是file还是files属性取决于我们使用的是single,还是array。)

因为我用的array模式,所以是files
因为我用的array模式,所以是files

在file(s)中有一个属性叫path,这个就是文件存储的路径。因为之前我把uploads文件夹设置为了static公开的状态,所以我只要将这个path和express应用运行的地址拼接,就能得到图片的完整地址。

app.post("/upload", upload.array("file"20), (req, res) => {
  console.log(req);
  let urls = [];
  req.files.forEach(({ path }) => {
    path = path.replaceAll("\\""/");
    //将路径拼接
    urls.push(baseUrl + path);
  });
  //把数据返回给前端
  res.json({
    status200,
    urls,
  });
});

最后,后端的全部代码如下:

const express = require("express");
const multer = require("multer");
const port = 3000;
const baseUrl = `http://localhost:${port}/`;

// 存储到本地硬盘 文件夹为./uploads,并设置文件名
const storage = multer.diskStorage({
  destination(req, file, cb) {
    cb(null"./uploads");
  },
  filename(req, file, cb) {
    const uniqueSuffix = Date.now() + "-" + Math.round(Math.random()) + "-";
    cb(null, uniqueSuffix + file.originalname);
  },
});
const upload = multer({
  storage: storage,
  limits: {
    // 图片最大尺寸为15mb
    fileSize1024 * 1024 * 15,
  },
  // 只接收图片类型的文件
  fileFilter(req, file, cb) {
    if (file.mimetype.includes("image")) {
      return cb(nulltrue);
    }
    cb(nullfalse);
  },
});

const app = express();

// 让uploads文件夹成为公开的文件夹
app.use("/uploads", express.static("uploads"));

// 允许跨域
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin""*");
  next();
});

app.post("/upload", upload.array("file"20), (req, res) => {
  console.log(req);
  let urls = [];
  req.files.forEach(({ path }) => {
    path = imgPath.replaceAll("\\""/");
    urls.push(baseUrl + path);
  });
  res.json({
    status200,
    urls,
  });
});

app.listen(3000, () => {
  console.log(`running at ${baseUrl}`);
});

前端

接下来,进入到略微复杂的前端,将覆盖如下内容:

  • drag api
  • 文件上传 fetch
  • 图片展示 FileReader api
  • html中的progress标签
  • 使用xhr替换fetch更好地实现进度展示

真的并不难,但是组合在一起是真的多,超级容易混乱。

分类:

前端

标签:

JavaScript

作者介绍

588
V1