<template>
  <div id="global-uploader" :class="{'global-uploader-single': !global}">
    <!-- 上传 -->
    <uploader
      ref="uploader"
      :options="initOptions"
      :fileStatusText="fileStatusText"
      :autoStart="false"
      @file-added="onFileAdded"
      @file-success="onFileSuccess"
      @file-progress="onFileProgress"
      @file-error="onFileError"
      class="uploader-app"
    >
      <uploader-unsupport></uploader-unsupport>

      <uploader-btn id="global-uploader-btn" ref="uploadBtn">{{btnName}}</uploader-btn>

      <uploader-list v-show="panelShow">
        <div class="file-panel" slot-scope="props" :class="{ collapse: collapse }">
          <div class="file-title">
            <div class="title">文件列表</div>
            <div class="operate">
              <el-button
                @click="collapse = !collapse"
                type="text"
                :title="collapse ? '展开' : '折叠'"
              >
                <i class="iconfont" :class="collapse ? 'el-icon-full-screen' : 'el-icon-minus'"></i>
              </el-button>
              <el-button @click="close" type="text" title="关闭">
                <i class="el-icon-close"></i>
              </el-button>
            </div>
          </div>

          <ul class="file-list">
            <li
              class="file-item"
              v-for="file in props.fileList"
              :key="file.id">
              <uploader-file
                :class="['file_' + file.id, customStatus]"
                ref="files"
                :file="file"
                :list="true"
              ></uploader-file>
            </li>
            <div class="no-file" v-if="!props.fileList.length">
              <i class="iconfont icon-empty-file"></i> 暂无待上传文件
            </div>
          </ul>
        </div>
      </uploader-list>
    </uploader>
  </div>
</template>

<script>
/**
 *  全局上传插件，两种调用方式
 *   1. 作为全局页面的组件，使用event bus
 *   调用方法：Bus.$emit('openUploader', {params: {}, options: {}})
 *               params: 发送给服务器的额外参数；
 *               options：上传选项，目前支持 target、testChunks、mergeFn、accept
 *
 *   监听函数：Bus.$on('fileAdded', fn); 文件选择后的回调
 *           Bus.$on('fileSuccess', fn); 文件上传成功的回调，监听后记得释放
 *
 *   2. 作为普通组件在单个页面中调用，使用props
 */
import { ACCEPT_CONFIG } from './js/config'
import Bus from './js/bus'
import SparkMD5 from 'spark-md5'
// import { mergeSimpleUpload } from '@/api'
import { fileMerge } from './js/fileuploader';
import store from "../store";
export default {
  props: {
    global: {
      type: Boolean,
      default: true
    },
    // 发送给服务器的额外参数
    params: {
      type: Object
    },
    btnName:{
      type:String,
      default: "选择文件"
    },
    options: {
      type: Object
    },
    fileList:{
      type:Array
    },
  },
  data() {
    return {
      initOptions: {
        target: window.g.url +'/common/sliceUpload',
        chunkSize: '2048000',
        fileParameterName: 'file',
        headers: {
          pingtai: "pingtai " + `${store.state.token}`,
        },
        // maxChunkRetries: 3,
        // 是否开启服务器分片校验
        testChunks: true,
        // 服务器分片校验函数，秒传及断点续传基础
        checkChunkUploadedByResponse: function (chunk, message) {
          let skip = false
          try {
            let objMessage = JSON.parse(message)
            if (objMessage.skipUpload) {
              skip = true
            } else {
              skip = (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0
            }
          } catch (e) {}
          return skip
        },
        query: (file, chunk) => {
          return {
            ...file.params
          }
        }
      },
      fileStatusText: {
        success: '上传成功',
        error: '上传失败',
        uploading: '上传中',
        paused: '已暂停',
        waiting: '等待上传'
      },
      panelShow: false, //选择文件后，展示上传panel
      collapse: false,
      customParams: {},
      customStatus: ''
    }
  },
  watch: {
    params: {
      handler(data) {
        if (data) {
          this.customParams = data
        }
      },
      immediate: true
    },
    options: {
      handler(data) {
        if (data) {
          setTimeout(() => {
            this.customizeOptions(data)
          }, 0)
        }
      },
      immediate: true
    }
  },
  mounted() {
    Bus.$on('openUploader', ({params={}, options={}}) => {
      this.customParams = params
      this.customizeOptions(options)
      if (this.$refs.uploadBtn) {
        this.$refs.uploadBtn.$el.click()
      }
    })
    if(this.fileList  && this.fileList.length > 0){
      this.props.fileList = this.fileList;
    }

  },
  computed: {
    // Uploader实例
    uploader() {
      return this.$refs.uploader.uploader
    }
  },
  methods: {
    mergeMethod(data){
      return fileMerge(dataParm);
    },

    // 自定义options
    customizeOptions(opts) {
      // 自定义上传url
      if (opts.target) {
        this.uploader.opts.target = opts.target
      }
      // 是否可以秒传、断点续传
      if (opts.testChunks !== undefined) {
        this.uploader.opts.testChunks = opts.testChunks
      }
      // merge 的方法，类型为Function，返回Promise
      this.mergeFn = opts.mergeFn || this.mergeMethod;
      // 自定义文件上传类型
      let input = document.querySelector('#global-uploader-btn input')
      let accept = opts.accept || ACCEPT_CONFIG.getAll()
      input.setAttribute('accept', accept.join())
    },
    onFileAdded(file) {
      this.panelShow = true
      this.emit('fileAdded')
      // 将额外的参数赋值到每个文件上，以不同文件使用不同params的需求
      file.params = this.customParams
      // 计算MD5
      this.computeMD5(file).then((result) => this.startUpload(result))
    },
    /**
     * 计算md5值，以实现断点续传及秒传
     * @param file
     * @returns Promise
     */
    computeMD5(file) {
      let fileReader = new FileReader()
      let time = new Date().getTime()
      let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
      let currentChunk = 0
      const chunkSize = 10 * 1024 * 1000
      let chunks = Math.ceil(file.size / chunkSize)
      let spark = new SparkMD5.ArrayBuffer()
      // 文件状态设为"计算MD5"
      this.statusSet(file.id, 'md5')
      file.pause()
      loadNext()
      return new Promise((resolve, reject) => {
        fileReader.onload = (e) => {
          spark.append(e.target.result)
          if (currentChunk < chunks) {
            currentChunk++
            loadNext()
            // 实时展示MD5的计算进度
            this.$nextTick(() => {
              const md5ProgressText ='校验MD5 '+ ((currentChunk/chunks)*100).toFixed(0)+'%'
              document.querySelector(`.custom-status-${file.id}`).innerText = md5ProgressText
            })
          } else {
            let md5 = spark.end()
            // md5计算完毕
            resolve({md5, file})
            console.log(
              `MD5计算完毕：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${file.size} 用时：${
                new Date().getTime() - time
              } ms`
            )
          }
        }
        fileReader.onerror = function () {
          this.error(`文件${file.name}读取出错，请检查该文件`)
          file.cancel()
          reject()
        }
      })
      function loadNext() {
        let start = currentChunk * chunkSize
        let end = start + chunkSize >= file.size ? file.size : start + chunkSize
        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
      }
    },
    // md5计算完毕，开始上传
    startUpload({md5, file}) {
      file.uniqueIdentifier = md5
      file.resume()
      this.statusRemove(file.id)
    },
    onFileSuccess(rootFile, file, response, chunk) {
      // let res = JSON.parse(response)
      // 服务端自定义的错误（即http状态码为200，但是是错误的情况），这种错误是Uploader无法拦截的
      // if (!res.result) {
      //   this.error(res.message)
      //   // 文件状态设为“失败”
      //   this.statusSet(file.id, 'failed')
      //   return
      // }
      // 如果服务端返回了需要合并的参数
      let objMessage = JSON.parse(response)
      // if (this.count > 0) {
      // 文件状态设为“合并中”
      this.statusSet(file.id, 'merging')
      let dataParm ={identifier:objMessage.data.id,id:file.id,filename:file.name};

      fileMerge(dataParm).then(res=>{
        this.$emit('fileSuccess',res.data)
        this.statusRemove(file.id)
      }) .catch((e) => {})


      // if (res.needMerge) {
      //   // 文件状态设为“合并中”
      //   this.statusSet(file.id, 'merging')
      //   this.mergeFn({
      //     tempName: res.tempName,
      //     fileName: file.name,
      //     ...file.params
      //   })
      //     .then((res) => {
      //       // 文件合并成功
      //       this.emit('fileSuccess')
      //       this.statusRemove(file.id)
      //     })
      //     .catch((e) => {})
      //   // 不需要合并
      // } else {
      //   this.emit('fileSuccess')
      //   console.log('上传成功')
      // }
    },
    onFileProgress(rootFile, file, chunk) {
      console.log(
        `上传中 ${file.name}，chunk：${chunk.startByte / 1024 / 1024} ~ ${
          chunk.endByte / 1024 / 1024
        }`
      )
    },
    onFileError(rootFile, file, response, chunk) {
      this.error(response)
    },
    close() {
      this.uploader.cancel()
      this.panelShow = false
      this.$refs.uploader.uploader.cancel()


      this.$refs.uploader.files = [];
      this.$refs.uploader.fileList = [];
    },
    /**
     * 新增的自定义的状态: 'md5'、'merging'、'transcoding'、'failed'
     * @param id
     * @param status
     */
    statusSet(id, status) {
      let statusMap = {
        md5: {
          text: '校验MD5',
          bgc: '#fff'
        },
        merging: {
          text: '合并中',
          bgc: '#e2eeff'
        },
        transcoding: {
          text: '转码中',
          bgc: '#e2eeff'
        },
        failed: {
          text: '上传失败',
          bgc: '#e2eeff'
        }
      }
      this.customStatus = status
      this.$nextTick(() => {
        const statusTag = document.createElement('p')
        statusTag.className = `custom-status-${id} custom-status`
        statusTag.innerText = statusMap[status].text
        statusTag.style.backgroundColor = statusMap[status].bgc
        const statusWrap = document.querySelector(`.file_${id} .uploader-file-status`)
        statusWrap.appendChild(statusTag)
      })
    },
    statusRemove(id) {
      this.customStatus = ''
      this.$nextTick(() => {
        const statusTag = document.querySelector(`.custom-status-${id}`)
        statusTag.remove()
      })
    },
    emit(e) {
      Bus.$emit(e)
      this.$emit(e)
    },
    error(msg) {
      this.$notify({
        title: '错误',
        message: msg,
        type: 'error',
        duration: 2000
      })
    }
  }
}
</script>

<style lang="less">
#global-uploader {
  &:not(.global-uploader-single) {
    position: fixed;
    z-index: 20;
    right: 15px;
    bottom: 15px;
    box-sizing: border-box;
  }
  .uploader-app {
    width: 400px;
    margin-left: -70px;
  }
  .file-panel {
    background-color: #fff;
    border: 1px solid #e2e2e2;
    border-radius: 7px 7px 0 0;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
    .file-title {
      display: flex;
      height: 40px;
      line-height: 40px;
      padding: 0 15px;
      border-bottom: 1px solid #ddd;
      display: none;
      .operate {
        flex: 1;
        text-align: right;
        i {
          font-size: 18px;
        }
      }
    }
    .file-list {
      position: relative;
      //height: 240px;
      overflow-x: hidden;
      overflow-y: auto;
      background-color: #fff;
      transition: all 0.3s;
      .file-item {
        background-color: #fff;
      }
    }
    &.collapse {
      .file-title {
        background-color: #e7ecf2;
      }
      .file-list {
        height: 0;
      }
    }
  }
  .no-file {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 16px;
  }
  .uploader-file {
    &.md5 {
      .uploader-file-resume {
        display: none;
      }
    }
  }
  .uploader-file-icon {
    &:before {
      content: '' !important;
    }
    &[icon='image'] {
      background: url(images/image-icon.png);
    }
    &[icon=audio] {
      background: url(images/audio-icon.png);
      background-size: contain;
    }
    &[icon='video'] {
      background: url(images/video-icon.png);
    }
    &[icon='document'] {
      background: url(images/text-icon.png);
    }
    &[icon=unknown] {
      background: url(images/zip.png) no-repeat center;
      background-size: contain;
    }
  }
  .uploader-file-actions > span {
    margin-right: 6px;
  }
  .custom-status {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
  }
}
/* 隐藏上传按钮 */
#global-uploader-btn {
  position: absolute;
  clip: rect(0, 0, 0, 0);
}
.global-uploader-single {
  #global-uploader-btn {
    position: relative;
    top: -40px;
    left: 178px;
    color: white;
    background: rgb(53, 72, 209);
  }
}
</style>
