Vue+element-ui上传文件到七牛云组件

Vue+element-ui上传文件和分片上传到七牛云

  • 一、实现效果
  • 二、上传到七牛云的过程
  • 三、实现组件代码
    • 1.上传组件代码
    • 2.引入组件
  • 3.提示

公司可能会将图片文件等存储到七牛云、阿里云等服务器方便管理。我这里将文件上传到七牛云,通过七牛云的api来实现上传和分片上传。使用element-ui的el-upload组件改造来完成。

一、实现效果

点击添加附件弹出dialog操作框
在这里插入图片描述
选取文件后添加文件到待上传列表,点击上传后同时上传文件,超过一定大小(自己设置)的文件会采用分片上传。
Vue+element-ui上传文件到七牛云组件_第1张图片
上传后的添加附件变为查看附件,表示已经有文件上传。
在这里插入图片描述

二、上传到七牛云的过程

  1. 首先安装七牛云的JS的sdk

npm install qiniu-js

七牛云JS开发文档

  1. 我们需要token和key,token是用作七牛云上传的凭证,key用来作文件上传地址的标识。
  2. token和key通过后台获取,我这里还返回了存储区域(不同的区域上传地址不一样),存储域名,文件限制格式。
  3. token和key会在调用七牛云JS接口的时候使用。

三、实现组件代码

1.上传组件代码

/components/uploadFile.vue

<template>
  <div>
    <!-- 上传文件按钮 -->
    <div class="in_block" @click="getSpaceMethod()">
      <span
        v-if="attachment.length"
        class="verticall ml_10 col2c pointer font13"
      >
        <i class="el-icon-upload mr_10"></i>
        <span>查看附件 ({{ attachment.length }})</span></span
      >
      <span
        v-else
        class="verticall ml_10 col2c pointer font13"
        @click="getSpaceMethod()"
      >
        <i class="el-icon-upload mr_10"></i>
        <span>{{ $t("addAttachment") }}</span>
      </span>
    </div>
    <el-dialog
      title="上传附件"
      :visible.sync="uploadDia"
      append-to-body
      :close-on-click-modal="false"
      width="760px"
    >
      <div>
        <el-upload
          class="upload-demo"
          ref="upload"
          :action="qiniuDomain(spaceData.region)"
          :on-change="fileChange"
          :on-progress="fileProgress"
          :http-request="customerUpload"
          :before-upload="beforeUpload"
          :on-success="handleSuccess"
          :on-error="handleError"
          :auto-upload="false"
          multiple
          :show-file-list="false"
          :file-list="uploadFileList"
        >
          <template #trigger>
            <el-button size="small">选取文件</el-button>
          </template>
          <el-button
            class="right"
            :disabled="!uploadFileList.length"
            size="small"
            type="primary"
            @click="submitUpload"
            >上传</el-button
          >
        </el-upload>
        <!-- 文件列表信息 -->
        <div v-if="uploadFileList.length" class="progress-box">
          <div class="list-item">
            <div class="item-name">名称</div>
            <div class="item-size">大小</div>
            <div class="item-progress">文件进度</div>
            <div class="item-status">状态</div>
            <div class="item-operate">操作</div>
          </div>
          <div v-for="(item, index) in uploadFileList" :key="item.uid">
            <div class="list-item">
              <div class="item-name">
                <span>{{ index + 1 }}.{{ item.name }}</span>
              </div>
              <div class="item-size">
                {{ $util.getFileSize(item.size) }}
              </div>
              <div class="item-progress">
                <el-progress :percentage="Number(item.percentage.toFixed(0))" />
              </div>
              <div class="item-status">
                <i :class="fileStatuIcon(item.status)"></i>
                {{ item.status | fileStatus }}
              </div>
              <div class="item-operate">
                <el-popconfirm
                  hideIcon
                  title="确定删除当前文件吗?"
                  @confirm="delFile(item.name, index)"
                >
                  <template #reference>
                    <span class="coldel pointer">删除</span>
                  </template>
                </el-popconfirm>
              </div>
            </div>
          </div>
        </div>
        <div v-else class="progress-box vertical">请选择文件上传</div>
      </div>
      <div slot="footer">
        <el-button type="primary" @click="uploadDia = false">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import * as qiniu from "qiniu-js";
import axios from "axios";
import Vue from "vue";
// 单个文件的状态 对应描述
const fileStatus = {
  ready: {
    code: "ready",
    name: "待上传",
  },
  wait: {
    code: "wait",
    name: "待上传",
  },
  uploading: {
    code: "uploading",
    name: "上传中",
  },
  success: {
    code: "success",
    name: "成功",
  },

  fail: {
    code: "fail",
    name: "失败",
  },
  error: {
    code: "error",
    name: "失败",
  },
  secondPass: {
    code: "secondPass",
    name: "已秒传",
  },
  pause: {
    code: "pause",
    name: "暂停",
  },
  resume: {
    code: "resume",
    name: "恢复",
  },
};
export default {
  props: {
    //做数据回显时传入的文件列表数据
    //每一项格式{    file_ext: "", file_name: "",file_path: "",file_size:"" } 我这里是这样的
    curFileList: {
      type: Array,
      default() {
        return [];
      },
    },
    //是做添加附件还是需要附件的回显(默认是添加)
    isAddItem: {
      type: Boolean,
      default() {
        return false;
      },
    },
  },
  data() {
    return {
      uploadDia: false,
      spaceData: {
        mime: [], //后台限制的文件格式
        space: "", //存储空间标识(七牛云or阿里云)
        domain: "", //存储域名
        token: "",
        unique_key: "",
        region: "10", //存储区域
      },
      uploadFileList: [], //上传文件列表  {name,size,percentage,status,uid}  uid唯一标识
      attachment: [], //已上传文件列表 与父组件的数据对应
    };
  },

  filters: {
    fileStatus(status) {
      return fileStatus[status].name;
    },
  },
  computed: {
    //文件上传状态的图标
    fileStatuIcon() {
      return function (status) {
        let className = "";
        switch (status) {
          case "uploading":
            className = "el-icon-loading";
            break;
          case "success":
          case "secondPass":
            className = "el-icon-circle-check";
            break;
          case "error":
            className = "el-icon-circle-close";
            break;
        }
        return className;
      };
    },

    qiniuDomain() {
      return function (region) {
        switch (region) {
          case "10":
            return "https://upload.qiniup.com";
          case "20":
            return "https://upload-z1.qiniup.com";
          case "30":
            return "https://upload-z2.qiniup.com";
          case "40":
            return "https://upload-na0.qiniup.com";
          case "50":
            return "https://upload-as0.qiniup.com";
          default:
            return "https://upload-z1.qiniup.com";
        }
      };
    },
  },
  mounted() {
    //数据回显时 将父组件传来的文件列表同步显示到页面
    this.uploadFileList = this.curFileList.map((item) => {
      return {
        name: item.file_name, //文件名称(必须)
        size: item.file_size, //文件大小(必须)
        percentage: 100, //设置上传进度为100
        status: "success", //设置上传状态为成功
      };
    });
    this.attachment = this.curFileList;
  },
  methods: {
    //手动上传文件
    submitUpload() {
      this.$refs.upload.submit();
    },

    //附件改变(增加删除时)
    fileChange(file, fileList) {
      const index = this.uploadFileList.findIndex(
        (item) => item.name == file.name
      );

      if (index == -1) {
        this.uploadFileList = fileList;
      }
    },

    /**
     * @description: 上传中的处理 让文件列表数据于显示的文件列表数据相同
     * @param {*} event
     * @param {*} file
     * @param {文件列表} fileList
     * @return {*}
     */
    fileProgress(event, file, fileList) {
      this.uploadFileList = fileList;
    },

    /**
     * @description: 上传之前的文件处理
     * @param {待上传文件信息} file
     * @return {*}
     */
    beforeUpload(file) {
      const isLt2M = file.size < this.spaceData.max;
      const type = this.spaceData.mime.includes(file.type);
      if (!isLt2M) {
        this.$message.warning(
          `${file.name}文件过大,不能超出${this.spaceData.max / 1024 / 1024}M!`
        );
        const index = this.uploadFileList.findIndex(
          (item) => item.name == file.name
        );
        this.uploadFileList.splice(index, 1);
        return isLt2M;
      }
      if (!type) {
        this.$message.warning(`${file.name}文件类型不允许!`);
        const index = this.uploadFileList.findIndex(
          (item) => item.name == file.name
        );
        this.uploadFileList.splice(index, 1);
        return type;
      }
      return isLt2M && type;
    },

    /**
     * @description: 上传成功后的向父组件同步文件数据
     * @param {上传成功后接口返回的数据} res
     * @param {当前上传的文件信息} file
     * @return {*}
     */
    handleSuccess(res, file) {
      const file_name = file.name;
      const file_ext = file.name.substring(
        file.name.lastIndexOf(".") + 1,
        file.name.length
      );
      const file_size = file.raw.size;
      const file_path = this.spaceData.domain + `/${res.key}`; //七牛云上绑定的域名 拼接了这个key 就是你上传文件的地址了
      //上传成功后重组数据向父组件传对应格式的数据 与props里的curFileList数据格式对应
      const data = {
        file_ext,
        file_name,
        file_path,
        file_size,
      };
      this.attachment.push(data);
      this.$emit("syncFile", this.attachment);
    },

    //上传错误处理
    handleError(err) {
      this.$message.error(err);
    },

    /**
     * @description: 删除文件  先判断此文件是否已经上传成功
     * @param {*文件名称} name
     * @param {*数据在uploadFileList的当前下标} index
     * @return {*}
     */
    delFile(name, index) {
      const attachmentIndex = this.attachment.findIndex((item) => {
        return item.file_name == name;
      });
      //如果已经上传了并且是添加的时候 就调用接口删除  否则直接删除数组数据
      const url = "/xxx/xxx"; //删除文件的接口(根据自己情况)
      if (attachmentIndex !== -1) {
        if (this.isAddItem) {
          const UploadFile = this.attachment[attachmentIndex];
          const data = {
            file: UploadFile.file_path,
          };
          axios({
            url,
            method: "post",
            data,
          }).then((res) => {
            if (res.data.code === 1007) {
              this.uploadFileList.splice(index, 1);
              this.attachment.splice(attachmentIndex, 1);
              this.$emit("syncFile", this.attachment);
              this.$message.success(this.$t("delSucc"));
            } else {
              this.$message.error(res.data.message);
            }
          });
          //更新的时候删除文件  直接删除文件
        } else {
          this.uploadFileList.splice(index, 1);
          this.attachment.splice(attachmentIndex, 1);
          this.$emit("syncFile", this.attachment);
        }
      } else {
        //如果还未上传成功
        //如果在上传中 调用el-upload的abort停止上传
        if (this.uploadFileList[index].status == "uploading") {
          this.$refs.upload.abort(this.uploadFileList[index]);
          this.uploadFileList.splice(index, 1);
        } else {
          this.uploadFileList.splice(index, 1);
        }
      }
    },

    upload(token, key, obj, next, error, complete) {
      const { file } = obj;
      // 因人而异,自行解决
      let region = "";
      switch (this.spaceData.region) {
        case "10":
          region = qiniu.region.z0;
          break;
        case "20":
          region = qiniu.region.z1;
          break;
        case "30":
          region = qiniu.region.z2;
          break;
        case "40":
          region = qiniu.region.na0;
          break;
        case "50":
          region = qiniu.region.as0;
          break;
      }
      const putExtra = {
          // fname: file.name, // fname: string,文件原文件名
          params: {}, // params: object,用来放置自定义变量
          mimeType: null, // mimeType: null || array,用来限制上传文件类型,为 null 时表示不对文件类型限制;限制类型放到数组里: ["image/png", "image/jpeg", "image/gif"]
        },
        config = {
          useCdnDomain: true,
          region, //z0华东区域 z1华北区域  z2代表华南区域  na0北美区域   as0新加坡区域
        };

      let options = {
        // quality: 0.92,
        // noCompressIfLarger: true,
        // maxWidth: 1000,
        // maxHeight: 618,
      };

      let observable = qiniu.upload(file, key, token, putExtra, config);
      let subscription = observable.subscribe(next, error, complete);
      return subscription;
    },

    //上传七牛云
    customerUpload(request) {
      const { file } = request;
      const uid = file.uid;
      const index = this.uploadFileList.findIndex((item) => item.uid == uid);
      let token = this.spaceData.token;
      let key = this.spaceData.unique_key + file.name;
      this.upload(
        token,
        key,
        request,
        (next) => {
          let total = next.total;
          //设置进度条
          Vue.set(this.uploadFileList[index], "percentage", total.percent);
          Vue.set(this.uploadFileList[index], "status", "uploading");
        },
        (error) => {
          request.onError(error);
        },
        (complete) => {
          request.onSuccess(complete);
        }
      );
    },

    //获取token和key等
    getSpaceMethod() {
      this.uploadDia = true;
      const url = "/xxx/xxx"; //获取七牛token的接口地址
      axios({
        method: "POST",
        url,
      })
        .then((res) => {
          if (res.data.code === 1001) {
            const data = res.data.data;
            this.spaceData = data;
          } else {
            this.$message.error(res.data.message);
          }
        })
        .catch(() => {});
    },
  },
};
</script>

<style lang="scss" scoped>
.font13 {
  font-size: 13px;
}
.col2c {
  color: #2c6ee5;
}
.coldel {
  color: #e91c47;
}
.pointer {
  cursor: pointer;
}
.in_block {
  display: inline-block;
}
.vertical {
  display: flex;
  align-items: center;
  justify-content: center;
}
.verticall {
  display: flex;
  align-items: center;
  justify-content: flex-start;
}
.ml_10 {
  margin-left: 10px;
}
.mr_10 {
  margin-right: 10px;
}
.progress-box {
  min-height: 260px;
  width: 100%;

  .list-item {
    padding: 8px 10px;
    display: flex;
    justify-content: center;
    align-items: center;
    line-height: 25px;
    position: relative;

    .item-name {
      flex: 2;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      margin-right: 6px;
    }
    .item-size {
      flex: 1;
    }
    .item-progress {
      flex: 2;
      padding-right: 30px;
    }
    .item-status {
      flex: 1;
      .el-icon-circle-check {
        color: #67c23a;
      }
      .el-icon-circle-close {
        color: #f00;
      }
    }
    .item-operate {
      flex: 1;
    }
  }
}
</style>

2.引入组件

<template>
	<div>
	   <uploadFile
          @syncFile="syncFile"
          isAddItem
          :curFileList="attachment"
       ></uploadFile>
	</div>
</template>
<script>
//引入组件
import uploadFile from './components/uploadFile'
export default {
	 components: { uploadFile},
	 data(){
	 	return{
	 		attachment:[], //已上传列表
	 	}
	 },
	 methods:{
	 //上传和删除后同步文件数据
		syncFile(data) {
      		this.attachment = data;
    	},
	}
}
</script>

3.提示

这个组件要使用之前看一下我在组件里写的注释,要改的地方根据自己的情况改就行了。溜了,溜了。。。。。。

你可能感兴趣的:(vue相关,vue.js,elementui,upload)