breakpoint.vue 8.4 KB
Newer Older
1 2
<template>
  <div class="hello">
J
jinlan.du 已提交
3 4 5 6 7
     <el-divider content-position="left">大文件上传</el-divider>
    <form id="fromCont" method="post" >
      <div class="fileUpload">
        选择文件<input @change="choseFile" id="file" multiple="multiple" type="file"  />
      </div>
8
    </form>
J
jinlan.du 已提交
9 10 11 12
     <el-button @click="getFile" :disabled="limitFileSize" type="primary" size="medium" class="uploadBtn">上传文件</el-button>
    <div class="el-upload__tip">请上传不超过5MB的文件</div>
    <div class="list">
      <transition-group name="list" tag="p">
13
        <div class="list-item" v-for="item in uploadList" :key="item.name" >
J
jinlan.du 已提交
14 15 16 17 18 19 20 21 22
          <i class="el-icon-document"></i>
          <span>{{ item.name }}</span>
          <span v-if="file" class="percentage" >{{percentage}}%</span>
          <el-progress  :show-text='false' :text-inside="false" :stroke-width="2" :percentage="percentage"></el-progress>
        </div> 
      </transition-group>
   </div>
    
    <!-- <span
23
      v-if="this.file"
J
jinlan.du 已提交
24
    >{{Math.floor(((this.formDataList.length-this.waitNum)/this.formDataList.length)*100)}}%</span> -->
1
1319612909 已提交
25
    <div class="tips">此版本为先行体验功能测试版,样式美化和性能优化正在进行中,上传切片文件和合成的完整文件分别再QMPlusserver目录的breakpointDir文件夹和fileDir文件夹</div>
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
  </div>
</template>
<script>
import SparkMD5 from 'spark-md5'
import axios from 'axios'
import {
  findFile,
  breakpointContinueFinish,
  removeChunk
} from '@/api/breakpoint'
export default {
  name: 'HelloWorld',
  data() {
    return {
      file: null,
      fileMd5: '',
      formDataList: [],
      waitUpLoad: [],
J
jinlan.du 已提交
44 45 46 47 48 49
      waitNum: 0,
      limitFileSize: false,
      percentage:0,
      percentageFlage: true,
      customColor: '#409eff',
      uploadList:[]
50
    }
J
jinlan.du 已提交
51 52 53
  },
  created(){
   
54 55 56 57 58 59
  },
  methods: {
    // 选中文件的函数
    async choseFile(e) {
      const fileR = new FileReader() // 创建一个reader用来读取文件流
      const file = e.target.files[0] // 获取当前文件
J
jinlan.du 已提交
60
      const maxSize = 5*1024*1024
61
      this.file = file // file 丢全局方便后面用 可以改进为func传参形式
J
jinlan.du 已提交
62
    if(file.size<maxSize){
63 64 65
      fileR.readAsArrayBuffer(file) // 把文件读成ArrayBuffer  主要为了保持跟后端的流一致
      fileR.onload = async e => {
        // 读成arrayBuffer的回调 e 为方法自带参数 相当于 dom的e 流存在e.target.result 中
66
        const blob = e.target.result
67
        let spark = new SparkMD5.ArrayBuffer() // 创建md5制造工具 (md5用于检测文件一致性 这里不懂就打电话问我)
68
        spark.append(blob) // 文件流丢进工具
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
        this.fileMd5 = spark.end() // 工具结束 产生一个a 总文件的md5
        const FileSliceCap = 1 * 1024 * 1024 // 分片字节数
        let start = 0 // 定义分片开始切的地方
        let end = 0 // 每片结束切的地方a
        let i = 0 // 第几片
        this.formDataList = [] // 分片存储的一个池子 丢全局
        while (end < file.size) {
          // 当结尾数字大于文件总size的时候 结束切片
          start = i * FileSliceCap // 计算每片开始位置
          end = (i + 1) * FileSliceCap // 计算每片结束位置
          var fileSlice = this.file.slice(start, end) // 开始切  file.slice 为 h5方法 对文件切片 参数为 起止字节数
          const formData = new window.FormData() // 创建FormData用于存储传给后端的信息
          formData.append('fileMd5', this.fileMd5) // 存储总文件的Md5 让后端知道自己是谁的切片
          formData.append('file', fileSlice) //当前的切片
          formData.append('chunkNumber', i) // 当前是第几片
          formData.append('fileName', this.file.name) //当前文件的文件名 用于后端文件切片的命名  formData.appen 为 formData对象添加参数的方法
          this.formDataList.push({ key: i, formData }) // 把当前切片信息 自己是第几片 存入我们方才准备好的池子
          i++
        }
        const params = {
          fileName: this.file.name,
          fileMd5: this.fileMd5,
          chunkTotal: this.formDataList.length
        }
        const res = await findFile(params)
        // 全部切完以后 发一个请求给后端 拉当前文件后台存储的切片信息 用于检测有多少上传成功的切片
        const finishList = res.data.file.ExaFileChunk // 上传成功的切片
        const IsFinish = res.data.file.IsFinish // 是否是同文件不同命 (文件md5相同 文件名不同 则默认是同一个文件但是不同文件名 此时后台数据库只需要拷贝一下数据库文件即可 不需要上传文件 即秒传功能)
        if (!IsFinish) {
          // 当是断点续传时候
          this.waitUpLoad = this.formDataList.filter(all => {
            return !(
              finishList &&
              finishList.some(fi => fi.FileChunkNumber === all.key)
            ) // 找出需要上传的切片
          })
        } else {
          this.waitUpLoad = [] // 秒传则没有需要上传的切片
        }
        this.waitNum = this.waitUpLoad.length // 记录长度用于百分比展示
      }
J
jinlan.du 已提交
110 111 112 113
      } else {
         this.limitFileSize = true
         this.$message('请上传小于5M文件')
      }
114 115 116 117 118 119 120
    },
    getFile() {
      // 确定按钮
      if (this.file == null) {
        this.$message('请先上传文件')
        return
      }
J
jinlan.du 已提交
121 122 123 124
      this.percentage = Math.floor(((this.formDataList.length-this.waitNum)/this.formDataList.length)*100)
      if(this.percentage == 100){
        this.percentageFlage = false
      }
125
      this.sliceFile() // 上传切片
J
jinlan.du 已提交
126 127 128
      if(this.percentage == 100){
        this.uploadList.push(this.file)
      }
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
    },
    sliceFile() {
      this.waitUpLoad &&
        this.waitUpLoad.map(item => {
          //需要上传的切片
          item.formData.append('chunkTotal', this.formDataList.length) // 切片总数携带给后台 总有用的
          const fileR = new FileReader() // 功能同上
          const file = item.formData.get('file')
          fileR.readAsArrayBuffer(file)
          fileR.onload = e => {
            let spark = new SparkMD5.ArrayBuffer()
            spark.append(e.target.result)
            item.formData.append('chunkMd5', spark.end()) // 获取当前切片md5 后端用于验证切片完整性
            this.upLoadFileSlice(item)
          }
        })
    },
    async upLoadFileSlice(item) {
      // 切片上传
      await axios.post(process.env.VUE_APP_BASE_API+"/fileUploadAndDownload/breakpointContinue",item.formData)
      this.waitNum-- // 百分数增加
      if (this.waitNum == 0) {
        // 切片传完以后 合成文件
        const params = {
          fileName: this.file.name,
          fileMd5: this.fileMd5
        }
        const res = await breakpointContinueFinish(params)
J
jinlan.du 已提交
157
        if (res.success) {
158 159 160 161 162 163 164 165 166 167 168 169 170 171
          // 合成文件过后 删除缓存切片
          const params = {
            fileName: this.file.name,
            fileMd5: this.fileMd5,
            filePath: res.data.filePath
          }
          await removeChunk(params)
        }
      }
    }
  }
}
</script>

J
jinlan.du 已提交
172
<style lang='scss' scoped>
173 174 175 176 177 178 179 180 181 182 183 184 185 186
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
J
jinlan.du 已提交
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
#fromCont{
  display: inline-block;
}
.fileUpload{
    padding: 4px 10px;
    height: 20px;
    line-height: 20px;
    position: relative;
    cursor: pointer;
    color: #000;
    border: 1px solid #c1c1c1;
    border-radius: 4px;
    overflow: hidden;
    display: inline-block;
  input{
    position: absolute;
    font-size: 100px;
    right: 0;
    top: 0;
    opacity: 0;
    cursor: pointer;
  }
 
}
 .fileName{
    display: inline-block;
    vertical-align: top;
    margin: 6px 15px 0 15px;
  }
  .uploadBtn{
    position: relative;
    top: -10px;
    margin-left: 15px;
  }
  .tips{
    margin-top: 30px;
    font-size: 14px;
    font-weight: 400;
    color: #606266;
  }
  .el-divider{
    margin: 0 0 30px 0;
  }
 
 .list{
   margin-top:15px;
 }
 .list-item {
  display: block;
  margin-right: 10px;
  color: #606266;
  line-height: 25px;
  margin-bottom: 5px;
  width: 40%;
   .percentage{
          float: right;
        }
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter, .list-leave-to
/* .list-leave-active for below version 2.1.8 */ {
  opacity: 0;
  transform: translateY(-30px);
}
</style>