<template>
  <div class="d2p-file-uploader" :class="{'is-disabled':disabled}" >
  <el-upload :class="uploadClass"
             :file-list="fileList"
             :disabled="disabled"
             :http-request="httpRequest"
             :on-exceed="onExceed"
             :on-remove="handleUploadFileRemove"
             :on-success="handleUploadFileSuccess"
             :on-error="handleUploadFileError"
             :on-progress="handleUploadProgress"
             @blur="handleBlur"
             ref="fileUploader"
             v-bind="_elProps"
  >
    <el-button :disabled="disabled" :size="btnSize" type="primary" v-if="_elProps.listType === 'text' || this._elProps.listType === 'picture'">{{btnName}}</el-button>
    <div class="avatar-item-wrapper"  v-else-if="this._elProps.listType === 'picture-card'">
      <i class="el-icon-plus avatar-uploader-icon" />
    </div>
    <template v-else-if="_elProps.listType ===  'avatar'">
      <div class="avatar-item-wrapper">
        <div class="status-uploading" v-if="avatarLoading!=null">
          <el-progress type="circle" :percentage="avatarLoading" :width="70"/>
        </div>
        <div v-if="avatarUrl!=null" class="avatar">
          <el-image :src="avatarUrl">
            <div slot="placeholder" class="image-slot">
              <img src="./loading-spin.svg">
            </div>
          </el-image>
          <div class="preview" @click.stop="" >
            <i class="el-icon-zoom-in" @click="previewAvatar"></i>
            <i class="el-icon-delete" v-if="!disabled" @click="removeAvatar"></i>
          </div>
        </div>
        <i class="el-icon-plus avatar-uploader-icon" v-else/>
      </div>
    </template>
  </el-upload>
    <el-dialog :visible.sync="dialogVisible"  v-bind="preview" append-to-body >
      <div style="text-align: center">
        <img  style="max-width: 100%;" :src="dialogImageUrl" alt="">
      </div>
    </el-dialog>
  </div>
</template>

<script>
import SparkMD5 from 'spark-md5'
import D2pUploader from 'd2p-extends/src/uploader'
import lodash from 'lodash'
import { d2CrudPlus } from 'd2-crud-plus'
import log from 'd2p-extends/src/utils/util.log'
import util from '@/libs/util'
// 文件上传组件,依赖D2pUploader
export default {
  name: 'd2p-file-uploader',
  mixins: [d2CrudPlus.inputBase],
  props: {
    // 选择文件按钮的大小
    btnSize: { default: 'small' },
    // 选择文件按钮的名称
    btnName: { default: '选择文件' },
    // 可选哪些类型的文件
    accept: {},
    // 上传后端类型，[cos,qiniu,alioss,form]
    type: {
      type: String,
      default: undefined // 上传类型：form cos qiniu  alioss
    },
    // 值：url<br/>
    // 或 [url1,url2]<br/>
    // 或 {url:'url',md5:'',size:number}<br/>
    // 或 [{url:'url',md5:'',size:number}]<br/>
    // <br/>
    // limit=1 时 input事件返回 {url:'url',md5:'',size:number}<br/>
    // limit>1 时 input事件返回 数组<br/>
    value: {
      type: [String, Array, Object]
    },
    // 样式后缀 追加到url的后面，进行图片处理，需要到对象存储平台配置样式
    suffix: {
      type: String,
      required: false
    },
    // 返回类型: url=仅返回链接, object=包含md5和size , key=仅返回文件key
    returnType: {
      type: String,
      default: 'url'
    },
    // 自定义参数
    custom: {
      type: Object
    },
    // 内部封装[el-upload](https://element.eleme.cn/#/zh-CN/component/upload)组件的属性参数<br/>
    // 注意，form方式上传的action、name、headers等参数不在此设置
    elProps: {
      type: Object
    },
    // 预览对话框的配置
    preview: {
      type: Object
    },
    // 文件大小限制 <br/>
    // 如果传入{limit,tip(fileSize,limit){vm.$message('可以自定义提示')}}
    sizeLimit: {
      type: Number, Object
    },
    // 构建下载url方法
    buildUrl: {
      type: Function,
      default: function (value, item) { return (typeof value === 'object') ? item.url : value }
    },
    // 上传组件参数，会临时覆盖全局上传配置参数[d2p-uploader](/guide/extends/uploader.html)
    uploader: {
      type: Object,
      default () { return {} }
    },
    // 与el-upload一致
    beforeUpload: {
      type: Function
    }
  },
  data () {
    return {
      fileList: [],
      context: {},
      dialogImageUrl: '',
      dialogVisible: false,
      avatarLoading: undefined,
      baseURL: util.baseURL()
    }
  },
  created () {
    this.emitValue = this.value
    this.initValue(this.value)
  },
  watch: {
    value (value) {
      if (this.dispatch) {
        this.dispatch('ElFormItem', 'el.form.blur')
      }
      this.$emit('change', value)
      if (this.emitValue === value) {
        return
      }
      this.emitValue = value
      this.initValue(value)
    }
  },
  computed: {
    _elProps () {
      const defaultElProps = this.getDefaultElProps()
      Object.assign(defaultElProps, this.elProps)
      return defaultElProps
    },
    avatarUrl () {
      if (this.fileList.length > 0) {
        const file = this.fileList[0]
        log.debug('file,', file, file.status)
        if (file.response != null && file.response.url != null) {
          return file.response.url
        } else if (file.url != null) {
          return file.url
        }
      }
      return null
    },
    uploadClass () {
      if (this._elProps.listType === 'avatar') {
        return 'avatar-uploader'
      } else if (this._elProps.listType === 'picture-card') {
        if (this.fileList && this._elProps.limit !== 0 && this.fileList.length >= this._elProps.limit) {
          return 'image-uploader hide-plus'
        }
        return 'image-uploader'
      }
      return 'file-uploader'
    }
  },
  methods: {
    handleBlur () {
      console.log('blur')
    },
    getDefaultElProps () {
      return {
        limit: 0,
        listType: 'text',
        showFileList: true,
        action: '',
        onPreview: (file) => {
          if (this._elProps.listType === 'picture-card' || this._elProps.listType === 'picture' || this._elProps.listType === 'avatar') {
            this.dialogImageUrl = file.url
            this.dialogVisible = true
          } else {
            window.open(file.url)
          }
        },
        beforeUpload: async (file) => {
          if (this.beforeUpload) {
            const ret = await this.beforeUpload(file, { vm: this })
            if (ret === false) {
              return
            }
          }
          if (this.sizeLimit == null) {
            return true
          }
          let limit = this.sizeLimit
          let showMessage = null
          if (typeof limit === 'number') {
            limit = this.sizeLimit
            showMessage = (fileSize, limit) => {
              if (this.$message) {
                const limitTip = this.computeFileSize(limit)
                const fileSizeTip = this.computeFileSize(file.size)
                this.$message({ message: '文件大小不能超过' + limitTip + '，当前文件大小:' + fileSizeTip, type: 'warning' })
              }
            }
          } else {
            limit = this.sizeLimit.limit
            showMessage = this.sizeLimit.tip
          }
          if (file.size > limit) {
            log.debug('文件大小超过限制：', file.size)
            showMessage(file.size, limit)
            return false
          }
        }
      }
    },
    setValue (value) {
      this.initValue(value)
      // this.$emit('change', this.fileList)
    },
    getUploader () {
      let type = this.type
      if (this.uploader != null && this.uploader.type != null) {
        type = this.uploader.type
      }
      return D2pUploader.getUploader(type)
    },
    initValue (value) {
      let fileList = []
      if (value == null) {

      } else if (typeof (value) === 'string') {
        if (value !== '') {
          const fileName = value.substring(value.lastIndexOf('/') + 1)
          fileList = [{ value: value, name: fileName }]
        }
      } else if (value instanceof Array) {
        if (value.length > 0 && typeof (value[0]) === 'string') {
          const tmp = []
          value.forEach(item => {
            const fileName = item.substring(item.lastIndexOf('/') + 1)
            tmp.push({ value: item, name: fileName })
          })
          fileList = tmp
        } else {
          fileList = value
        }
      } else if (value instanceof Object) {
        fileList = [value]
      }
      for (const item of fileList) {
        if (item.value == null) {
          item.value = item.url
        }
        item.url = this.buildUrl(item.value, item)
      }
      this.resetFileList(fileList)
    },
    computeFileSize (fileSize) {
      let sizeTip = fileSize
      if (fileSize > (1024 * 1024 * 1024)) {
        sizeTip = (fileSize / (1024 * 1024 * 1024)).toFixed(2) + 'G'
      } else if (fileSize > (1024 * 1024)) {
        sizeTip = (fileSize / (1024 * 1024)).toFixed(2) + 'M'
      } else {
        sizeTip = Math.round(fileSize / (1024)) + 'K'
      }
      return sizeTip
    },
    resetFileList (fileList) {
      this.$set(this, 'fileList', fileList)
    },
    handleUploadProgress (event, file, fileList) {
      if (this._elProps.listType === 'avatar') {
        log.debug('progress', event, file)
        this.avatarLoading = Math.floor(event.percent)
        if (event.percent === 100) {
          this.avatarLoading = undefined
        }
      }
      this.$emit('progress', event, { file, fileList, vm: this })
    },
    handleUploadFileSuccess (res, file, fileList) {
      res.size = res.size != null ? res.size : file.size
      res.name = res.name != null ? res.name : file.name
      res.value = this.getReturnValue(res)
      const value = this.returnType === 'object' ? res.url : res.value
      const url = this.buildUrl(value, res)
      file.url = res.url = url
      this.resetFileList(fileList)
      this.$emit('success', res, file)
      const list = []
      for (const item of fileList) {
        // if (item.status === 'uploading') {
        //   log.debug('当前文件上传完成，等待剩下的文件全部上传成功后再更新value')
        //   return
        // }
        if (item.url.indexOf('http') !== 0 && item.url.indexOf('https') !== 0 && item.url.indexOf(this.baseURL) !== 0) {
          item.url = this.baseURL + item.url
        }
        if (item.response != null && item.response.url != null) {
          list.push({ ...item.response })
        } else {
          list.push(item)
        }
      }
      log.debug('handleUploadFileSuccess list', list, res)
      this.emit(res, list)
    },
    isHasUploadingItem () {
      const fileList = this.$refs.fileUploader.uploadFiles
      if (fileList && fileList.length > 0) {
        for (const item of fileList) {
          if (item.status === 'uploading') {
            return true
          }
        }
      }
      return false
    },
    handleUploadFileRemove (file, fileList) {
      this.fileList = fileList
      console.log('remove', fileList)
      this.emitList(fileList)
      this.$emit('remove', file, fileList)
    },
    handleUploadFileError (err, file, fileList) {
      console.error('文件上传失败', err, file, fileList)
      this.$message({ type: 'error', message: '文件上传失败' })
      this.$emit('error', err, file, fileList)
    },
    previewAvatar ($event) {
      $event.stopPropagation()
      this._elProps.onPreview(this.fileList[0])
    },
    removeAvatar ($event) {
      $event.stopPropagation()
      this.resetFileList([])
      this.emit() // 返回undefined，相当于清空已有的值
    },
    emit (res, list) {
      if (this._elProps.limit === 1) {
        const value = res ? res.value : undefined
        this.emitValue = value
        this.$emit('input', value)
      } else {
        this.emitList(list)
      }
    },
    emitList (list) {
      if (list) {
        const tmp = []
        list.forEach(item => {
          tmp.push(this.getReturnValue(item))
        })
        list = tmp
      }
      this.emitValue = list
      this.$emit('input', list)
    },
    getReturnValue (item) {
      const value = item[this.returnType]
      if (value != null) {
        return value
      }
      return item
    },
    httpRequest (option) {
      Promise.all([
        this.doUpload(option),
        this.computeMd5(option.file)
      ]).then((ret) => {
        // 得到组合结果， size，md5
        const result = ret[0]
        result.md5 = ret[1]
        option.onSuccess(result)
      })
    },
    doUpload (option) {
      let config = this.uploader
      if (config == null) {
        config = {}
      }
      if (!lodash.isEmpty(this._elProps.action)) {
        config.action = this._elProps.action
      }
      if (!lodash.isEmpty(this._elProps.name)) {
        config.name = this._elProps.name
      }
      if (!lodash.isEmpty(this._elProps.data)) {
        config.data = this._elProps.data
      }
      if (!lodash.isEmpty(this._elProps.headers)) {
        config.headers = this._elProps.headers
      }
      if (!lodash.isEmpty(this.custom)) {
        config.custom = this.custom
      }
      const uploadOption = {
        file: option.file,
        fileName: option.file.name,
        onProgress: option.onProgress,
        onError: option.onError,
        config: config
      }
      this.$emit('start', uploadOption)
      return this.getUploader().then(uploader => {
        return uploader.upload(uploadOption)
      }).then(ret => {
        if (this.suffix != null) {
          ret.url += this.suffix
        }
        return ret
      })
    },
    onExceed (files, fileList) {
      log.debug('文件数量超出限制')
      if (this._elProps.limit === 1) {
        this.clearFiles()
        this.$refs.fileUploader.handleStart(files[0])
        this.$refs.fileUploader.submit()
        return
      }
      this.$message({
        showClose: true,
        message: '已达最大限制数量，请删除一个文件后再上传',
        type: 'warning'
      })
    },
    clearFiles () {
      if (this.$refs.fileUploader != null) {
        this.$refs.fileUploader.clearFiles()
      }
    },
    getFileList () {
      return this.fileList
    },
    computeMd5 (file) {
      return new Promise((resolve, reject) => {
        const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
        const chunkSize = 2097152 // Read in chunks of 2MB
        const chunks = Math.ceil(file.size / chunkSize)
        let currentChunk = 0
        const spark = new SparkMD5.ArrayBuffer()
        const fileReader = new FileReader()

        fileReader.onload = (e) => {
          spark.append(e.target.result) // Append array buffer
          currentChunk++

          if (currentChunk < chunks) {
            loadNext()
          } else {
            const md5 = spark.end()
            log.debug('computed hash', md5) // Compute hash

            resolve(md5)
          }
        }

        fileReader.onerror = function (error) {
          // eslint-disable-next-line prefer-promise-reject-errors
          reject('md5 computer error', error)
        }

        function loadNext () {
          const start = currentChunk * chunkSize
          const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize

          fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
        }

        loadNext()
      })
    }
  }
}
</script>
<style lang="scss">
  .d2p-file-uploader{
    &.is-disabled{
      .avatar-item-wrapper{
        background-color: #F5F7FA;
        border-color: #E4E7ED;
        color: #C0C4CC;
        cursor: not-allowed
      }
      li{
        cursor: not-allowed
      }
      .el-upload-list__item-actions{
        cursor: not-allowed
      }
    }
    .hide-plus{
      .el-upload--picture-card{
        display: none;
      }
    }
    .avatar-uploader{
      display: flex;
      .el-upload{
        width: 100px;
        height: 100px;
        /*display: flex;*/
        /*justify-content: center;*/
        /*align-items: center;*/
        background-color: #fbfdff;
        border: 1px dashed #c0ccda;
        border-radius: 6px;
      }
      .el-upload img{
        max-width: 100px;
        max-height: 100px;
      }
      .el-icon-plus.avatar-uploader-icon {
        vertical-align: top;
        font-size: 28px;
        color: #8c939d;
        line-height: 100px;
      }
      .status-uploading{
        border-radius: 6px;
        position: absolute;
        width: 100%;
        height: 100%;
        left: 0;
        top: 0;
        cursor: default;
        text-align: center;
        color: #fff;
        opacity: 1;
        font-size: 20px;
        background-color: rgba(0, 0, 0, 0.5);
        -webkit-transition: opacity .3s;
        transition: opacity .3s;
        display: flex;
        justify-content: center;
        align-items: center;
        .el-progress{
          width: 70px;
          height: 70px;
          .el-progress__text {
            color:#fff;
          }
        }

      }
    }
    .el-upload--picture-card .el-icon-plus.avatar-uploader-icon {
      vertical-align: top;
      font-size: 28px;
      color: #8c939d;
      line-height: 100px;
    }
    .image-uploader .el-upload-list--picture-card .el-upload-list__item-thumbnail {
      max-width: 100%;
      max-height: 100%;
      width:auto;
      height: auto;
    }
     .el-upload-list--picture .el-upload-list__item-thumbnail {
      max-height: 100%;
      height: auto;
    }
    .image-uploader .el-upload-list--picture-card .el-upload-list__item {
      /*display: flex;*/
      /*justify-content: center;*/
      /*align-items: center;*/
      text-align: center;
      line-height: 125px;
    }
    .image-uploader{
      /*display: flex;flex-wrap: wrap;*/
      .el-upload-list--picture-card .el-upload-list__item-actions{
        line-height: 100px;
      }
      .el-upload-list--picture-card {
        /*display: flex;*/
        /*flex-wrap: wrap;*/
      }
      .el-upload-list__item-status-label{
        line-height: 1;
      }
    }

    .el-upload--picture-card {
      background-color: #fbfdff;
      border: 1px dashed #c0ccda;
      border-radius: 6px;
      box-sizing: border-box;
      width: 100px;
      height: 100px;
      cursor: pointer;
      /*display: flex;*/
      /*justify-content: center;*/
      /*align-items: center;*/
    }
    .el-upload-list--picture-card {
      .el-upload-list__item{
        width: 100px;
        height: 100px;
      }
      .el-progress-circle{
        width: 70px !important;
        height: 70px !important;
      }
      .el-progress{
        width: 70px !important;
        height: 70px !important;
      }
    }
    .avatar-item-wrapper{
      display: flex;
      justify-content: center;
      align-items: center;
      width: 100%;
      height: 100%;
      position: relative;
      .avatar{
        display: contents;
      }
    }

    .preview{
      border-radius: 6px;
      position: absolute;
      width: 100%;
      height: 100%;
      left: 0;
      top: 0;
      cursor: default;
      text-align: center;
      color: #fff;
      opacity: 0;
      font-size: 20px;
      background-color: rgba(0,0,0,.9);
      -webkit-transition: opacity .3s;
      transition: opacity .3s;
      &:hover{
        opacity: 0.9;
      }
      display: flex;
      justify-content: center;
      align-items: center;
      i{
        margin: 0 7px;
        cursor: pointer;
      }
    }
  }

</style>
