昨天在写博客的个人动态页面,里面涉及到了图片上传。

  之前我都是用的别人的插件和elementUI的upload组件。但是现在没法用了。

  页面的效果差别有点大,如果改elementUI的样式,会很累。

  这时候很愁人啊。(懒啊!)

  这是页面开发的效果,很像qq空间的感觉。

  ?

  我参考了(简称xhr)和Fetch

  文档地址:

  XMLHttpRequest:https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest

  fetch:https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

  上面的xhr其实就是我们用的最多的http的核心函数,早年的jquery.ajax和如今的axios都是用的这个。

  fetch,兼容性差点,但是是最新的标准函数。已经没有那么多乱七八糟的东西了。把xhr干成了ajax的样子。

  总结:

  xhr的情况下需要完整的自己写一遍ajax函数

  fetch呢是新标准,缺点是ie全系不支持,并且edge也有部分不支持。

  哎呀:就是懒啊。不想自己写。(而且:自己写封装要考虑好多东西)

  这个吗,没什么多说的。肯定不会选jq。

  而且我早先的时候自己封装过axios,形成了自己项目下的专属使用的ajax函数。所以这次的封装是在自己封装的ajax函数上进行复用。

  这里放出下我现如今的整体的axios内部的情况。

  主角是下半部分的axios.upload

  "use strict";

  import Vue from "vue";

  import axios from "axios";

  import VueAxios from "vue-axios";

  import { Notification, Loading } from "element-ui";

  import apiList from "https://blog.csdn.net/qq_32858649/article/details/api-list"; //接口列表数据

  const store = require("store");

  import control from "@/common/control_center/index";

  // axios全局导入设置

  Vue.use(VueAxios, axios);

  Vue.prototype.$api = apiList; //将接口列表数据绑定到vue全局

  //自定义消息提示函数信息

  let customMsg = {

  //成功信息提示

  sucIfno(info) {

  Notification({

  title: "答对了!",

  type: "success",

  message: info

  });

  },

  //错误信息提示

  errIfno(info) {

  Notification({

  title: "答错了呢",

  type: "error",

  message: info

  });

  }

  };

  // 授权函数封装

  const authorize = herders => {

  const user_info = store.get("user_info");

  if (user_info && user_info.sign) {

  herders.Authorization = user_info.sign;

  return herders;

  } else {

  return herders;

  }

  };

  // axios函数封装

  const ajax = ({

  url = "",

  loading = false, //加载拦截

  baseURL = apiList.baseURL,

  data = {},

  headers = { "Content-Type": "application/json;charset=UTF-8" }, //头部信息处理

  method = "get",

  success = false, //成功信息提示

  error = true, //错误信息提示

  timeout = 1000

  }) => {

  // 数据过滤,过滤字段中空数据等

  const filter = record => {

  for (let key in record) {

  !record[key] && delete record[key];

  }

  return record;

  };

  //接口全局加载提示

  let loadingInstance = "";

  if (loading !== false) {

  loadingInstance = Loading.service({

  lock: true,

  text: loading !== true ? loading : "努力加载中……",

  spinner: "el-icon-loading",

  background: "rgba(0, 0, 0, 0.5)"

  });

  }

  return new Promise((suc, err) => {

  // 预处理数据部分

  method = method.toLocaleLowerCase(); //转化为小写

  headers = authorize(headers);

  axios({

  url: url,

  baseURL: baseURL,

  headers: headers,

  method: method,

  [method === "post" ? "data" : "params"]: filter(data),

  timeout: timeout

  })

  .then(response => {

  loadingInstance && loadingInstance.close();

  // 刷新口令以及接口判断是否退出登录

  if (!control.refresh_sign_or_out(response)) {

  customMsg.errIfno("数据异常,退出登录");

  err(response);

  }

  const res = response.data;

  //自定义成功失败处理,code值代表后端接口数据处理成功或者失败

  // 后端返回格式

  if (res && res.code === 0) {

  success !== false &&

  customMsg.sucIfno(success === true ? "信息处理成功" : success);

  suc(res);

  } else {

  error !== false &&

  customMsg.errIfno(res.msg ? res.msg : "信息处理失败");

  err(res);

  }

  })

  .catch(e => {

  console.log(e);

  loadingInstance && loadingInstance.close();

  error !== false ? customMsg.errIfno("接口异常") : false;

  //catch代表网络异常部分和后端返回结果无关

  err(e);

  });

  });

  };

  //暴露的ajax函数,进一步封装节流和防抖

  let shakeTime = "";

  axios.ajax = options => {

  //参数预处理

  let shake = options.shake || false; //不等于false直接传true或者防抖时间

  //防抖函数处理

  if (shake === false) {

  //不进行防抖处理

  return new Promise((suc, err) => {

  ajax(options)

  .then(e => {

  suc(e);

  })

  .catch(e => {

  err(e);

  });

  });

  } else {

  //进行防抖处理

  return new Promise((suc, err) => {

  shakeTime && clearTimeout(shakeTime);

  let callNow = !shakeTime;

  if (callNow) {

  ajax(options)

  .then(e => {

  suc(e);

  })

  .catch(e => {

  err(e);

  });

  }

  shakeTime = setTimeout(

  () => {

  shakeTime = null;

  }, //见注解

  shake === true ? 700 : shake

  );

  });

  }

  };

  axios.upload = options => {

  // 对uri地址进行数据拼接

  const new_url = obj => {

  if (obj) {

  let fields = "";

  for (let key in obj) {

  fields = fields + `${key}=${obj[key]}`;

  }

  return "?" + fields;

  } else {

  return "";

  }

  };

  options.fdata = options.fdata || ""; //文件上传的url拼接地址

  options.success = options.success || "文件上传成功";

  options.url = options.url + new_url(options.fdata);

  options.headers = options.headers || {};

  let header = { "Content-Type": "multipart/form-data" };

  for (let i in header) {

  options.headers[i] = header[i];

  }

  options.method = "post";

  options.multiple = options.multiple || false; //是否多文件,默认false

  //文件类型验证,注意传入数组,默认["image/jpeg", "image/png"]

  options.type = options.type || ["image/jpeg", "image/png"];

  options.size = options.size || 0; //文件大小限制,默认0

  options.max = options.max || 5; //最多上传几个文件

  //文件验证处理

  let input = document.createElement("input");

  input.type = "file";

  options.multiple ? (input.multiple = "multiple") : "";

  input.click();

  return new Promise((suc, err) => {

  let type = options.type;

  input.addEventListener("input", watchUpload, false);

  function watchUpload(event) {

  //移除监听

  let remove = () => {

  input.removeEventListener("input", watchUpload, false);

  input = null;

  };

  const file = event.path[0].files;

  const len = file.length;

  // 文件数量限制

  if (len > options.max) {

  err(file);

  remove();

  customMsg.errIfno("文件个数超过" + options.max);

  return false;

  }

  let formData = new FormData();

  for (let i = 0; i < len; i++) {

  // 文件大小限制

  if (options.size !== 0 && file[i].size / 1024 / 1024 > options.size) {

  err(file[i]);

  remove();

  customMsg.errIfno(file[i].name + "文件超过指定大小");

  return false;

  }

  // 文件类型限制

  if (type.length > 0 && !type.includes(file[i].type)) {

  err(file);

  remove();

  customMsg.errIfno(file[i].name + "文件类型为" + file[i].type);

  return false;

  }

  formData.append("dhtUpload", file[i], file[i].name);

  }

  options.data = formData;

  // 最终进行文件上传

  options.baseURL = "";

  ajax(options)

  .then(e => {

  suc(e);

  })

  .catch(e => {

  err(e);

  });

  }

  });

  };

  export default axios;

  注意,其实本身是直接用的axios效果没什么差别。不要被我自己封装的ajax迷糊了。

  这里的初衷就是我以函数的形式,而不在页面上存在input元素。我看网上很多教程都是预先定义好input节点的

  这里我放出自己一个基于input的颜色选择器。文件的方式类似这个

  //颜色选择器

  colorSelect() {

  let input = document.createElement("input");

  input.type = "color";

  input.click();

  input.addEventListener("input", watchColorPicker, false);

  function watchColorPicker(event) {

  //console.log(event.target.value);

  //移除监听

  input.removeEventListener("input", watchColorPicker, false);

  input = "";

  }

  }

  

  我们要的是path里面的值。看看path里面

  

  注意这里就一个input元素,我们取input中的files数组就是我们的文件了。

  这里看看单个文件的情况

  

  这里我们能看到每个文件的大小,类型的情况。这些在后面会使用到。比如我们限制文件上传个数,文件的大小和文件类型

  这里我直接放MDN文档了。

  https://developer.mozilla.org/zh-CN/docs/Web/API/FormData/Using_FormData_Objects

  其实我们只需要知道,是下面这样用的就行了。

  let formData = new FormData();

  formData.append("dhtUpload", file[i], file[i].name);

  append参数:文件名称,文件,文件名称

  这里其实我没太理解

  放一下elementUI源码中的一个情况。

  formData.append(option.filename, option.file, option.file.name);

  具体没太懂。

  const ajax = ({

  url = "",

  loading = false, //加载拦截

  baseURL = apiList.baseURL,

  data = {},

  headers = { "Content-Type": "application/json;charset=UTF-8" }, //头部信息处理

  method = "get",

  success = false, //成功信息提示

  error = true, //错误信息提示

  timeout = 1000

  })

  上面就是我的ajax函数的参数。

  // 对uri地址进行数据拼接

  const new_url = obj => {

  if (obj) {

  let fields = "";

  for (let key in obj) {

  fields = fields + `${key}=${obj[key]}`;

  }

  return "?" + fields;

  } else {

  return "";

  }

  };

  options.baseURL = ""; //个人处理,需要兼容之前的elementui等插件的上传

  options.fdata = options.fdata || ""; //文件上传的url拼接地址

  options.success = options.success || "文件上传成功";

  options.url = options.url + new_url(options.fdata);

  options.loading = options.loading || true;

  options.headers = options.headers || {};

  options.headers["Content-Type"] = "multipart/form-data";

  options.method = "post";

  options.multiple = options.multiple || false; //是否多文件,默认false

  //文件类型验证,注意传入数组,默认["image/jpeg", "image/png"]

  options.type = options.type || ["image/jpeg", "image/png"];

  options.size = options.size || 0; //文件大小限制,默认0

  options.max = options.max || 5; //最多上传几个文件

  看着参数好多是吧。其实没多少。

  解析一下:

  fdata:因为文件上传地址肯定是多样的。但是我们又需要传递一些文件之外的信息。这时候其实可以在地址栏拼接参数。后端也是能获取到的。

  new_url 和fdata是为了这个存在的。

  success:单纯的的文件上传提示信息。这个是因为之前ajax封装将提示信息封装进去了,但是默认成功不提示。

  headers:这个没什么多说的,但是为了保证headers可能有别的存在,那么就这样进行设置了。"Content-Type":"multipart/form-data"这个是必须的

  loading:文件加载的时候全屏出现加载提示

  method:必须是post

  multiple:这个这里缺失了一点,就是为了控制文件是多文件还是单文件上传

  type:文件类型的控制。这里传入数组。默认控制图片文件。传入什么默认限制这些类型

  size:限制每个文件的大小,0不限制

  max:限制文件个数,单文件没意义

  //文件验证处理

  let input = document.createElement("input");

  input.type = "file";

  options.multiple ? (input.multiple = "multiple") : "";

  input.click();

  代码就这么简单,这里options.multiple就是刚才参数设置的意义所在了

  这里我放整个控制部分,然后拆开解析

  return new Promise((suc, err) => {

  let type = options.type;

  input.addEventListener("input", watchUpload, false);

  function watchUpload(event) {

  //console.log(event);

  //移除监听

  let remove = () => {

  input.removeEventListener("input", watchUpload, false);

  input = null;

  };

  const file = event.path[0].files;

  const len = file.length;

  // 文件数量限制

  if (len > options.max) {

  remove();

  customMsg.errIfno("文件个数超过" + options.max);

  err(file);

  return false;

  }

  let formData = new FormData();

  for (let i = 0; i < len; i++) {

  // 文件大小限制

  if (options.size !== 0 && file[i].size / 1024 / 1024 > options.size) {

  remove();

  customMsg.errIfno(file[i].name + "文件超过指定大小");

  err(file[i]);

  return false;

  }

  // 文件类型限制

  if (type.length > 0 && !type.includes(file[i].type)) {

  remove();

  customMsg.errIfno(file[i].name + "文件类型为" + file[i].type);

  err(file);

  return false;

  }

  formData.append("dhtUpload", file[i], file[i].name);

  }

  options.data = formData;

  // 最终进行文件上传

  ajax(options)

  .then(e => {

  suc(e);

  })

  .catch(e => {

  err(e);

  });

  // 没有问题下,清空监听。

  remove();

  }

  });

  //console.log(event);

  //移除监听

  let remove = () => {

  input.removeEventListener("input", watchUpload, false);

  input = null;

  };

  const file = event.path[0].files;

  const len = file.length;

  // 文件数量限制

  if (len > options.max) {

  remove();

  customMsg.errIfno("文件个数超过" + options.max);

  err(file);

  return false;

  }

  let formData = new FormData();

  这里看注释也知道我干了什么。

  第一:我获取文件数组。并且预先将移除监听处理好,因为很多地方会用到。

  第二:我判断文件数量限制。超过的话,promise(代码中的err(file))返回错误;并且注销监听

  第三:先声明formdata对象

  for (let i = 0; i < len; i++) {

  // 文件大小限制

  if (options.size !== 0 && file[i].size / 1024 / 1024 > options.size) {

  remove();

  customMsg.errIfno(file[i].name + "文件超过指定大小");

  err(file[i]);

  return false;

  }

  // 文件类型限制

  if (type.length > 0 && !type.includes(file[i].type)) {

  remove();

  customMsg.errIfno(file[i].name + "文件类型为" + file[i].type);

  err(file);

  return false;

  }

  formData.append("dhtUpload", file[i], file[i].name);

  }

  options.data = formData;

  这里应该很明显了。我遍历每个file文件,并且进行文件大小和文件类型的判断限制。

  最后将通过的formdata数据赋值给ajax的data对象中

  // 最终进行文件上传

  ajax(options)

  .then(e => {

  suc(e);

  })

  .catch(e => {

  err(e);

  });

  // 没有问题下,清空监听。

  remove();

  this.axios

  .upload({

  url: this.$api.static().upload_pictures

  })

  .then(e => {

  console.log("成功", e);

  })

  .catch(e => {

  console.log("错误", e);

  });