<template>
  <div class="content">
    <el-upload
      v-loading="isUploading"
      :disabled="componentError"
      class="upload-demo"
      drag
      :headers="headers"
      :file-list="viewFileList"
      :name="name"
      :show-file-list="showFileList"
      :list-type="showFileListType"
      :multiple="multiple"
      :action="uploadUrl"
      :limit="limit"
      :on-success="handleUploadSuccess"
      :before-upload="handleBeforeUpload"
      :on-error="handleUploadError"
      :on-exceed="handleExceed"
      :before-remove="handleBeforeRemove"
      :on-remove="handleRemove"
      :on-preview="handlePreviewOpen"
    >
      <i class="el-icon-upload"></i>
      <div
        class="el-upload__text"
        v-if="!componentError"
      >将文件拖到此处，或<em>点击上传</em></div>
      <div
        class="error-text"
        v-else
      > 组件配置错误，请查看控制台
      </div>
      <div
        class="el-upload__tip"
        slot="tip"
      >
        文件大小不超过{{ limitSize }}m，文件类型为{{ types.toString() }}
      </div>
    </el-upload>

    <!-- 预览 -->
    <el-dialog
      title="预览"
      :visible.sync="previewObj.show"
      width="60%"
      :before-close="handlePreviewClose"
    >
      <div class="preview-content">
        <template v-if="previewObj.file && previewObj.file.type.includes('image')">
          <el-image class="preview-item" :src="previewObj.file.url" />
        </template>
        <template v-if="previewObj.file && previewObj.file.type.includes('video')">
          <video class="preview-item" controls :src="previewObj.file.url" />
        </template>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import mime from 'mime'
let fullLoading = null
const baseURL = process.env.VUE_APP_DOMAIN_PREFIX
export default {
  name: "Upload",
  props: {
    headers:{
      type:Object,
      default:()=>({})
    },
    /** 上传时候表单的KEY */
    name: {
      type: String,
      default: () => "file"
    },
    /** 限制上传数量 */
    limit: {
      type: Number,
      default: () => 5
    },
    /** 限制的那张大小 单位M */
    limitSize: {
      type: Number,
      default: () => 5
    },
    /** 是否多选 */
    multiple: {
      type: Boolean,
      default: () => true
    },
    /** 是否展示文件列表 */
    showFileList: {
      type: Boolean,
      default: () => true
    },
    /** 文件展示方式 text/picture/picture-card */
    showFileListType:{
      type:String,
      default:()=>'text'
    },
    /** 允许上传的文件尾缀 string[] */
    types: {
      type: Array,
      default: () => (['jpg', 'png', 'gif'])
    },
    /** 默认的文件列表 string[] */
    defaultFileList: {
      type: Array,
      default: () => ([])
    },
    /** 上传成功后端返回的字段名称 */
    responseFileName: {
      type: String,
      default: () => 'url'
    },
    /** 是否需要全屏loading */
    needFullScreenLoading:{
      type:Boolean,
      default:()=>true
    }
  },
  data() {
    return {
      uploadUrl: `${ baseURL }/file/upload`,
      // 真实文件列表
      fileList: [],
      // 默认展示的list，解决多上传只回调success一次的问题
      viewFileList:[],
      // 组件是否部署错误
      componentError: false,
      // 是否正在上传
      isUploading:false,
      // 预览对象
      previewObj:{
        show:false,
        file:null
      }
    }
  },
  watch: {
    defaultFileList: {
      handler() {
        // 判断类型
        const flag = Object.prototype.toString.call(this.defaultFileList) === '[object Array]'
          && this.defaultFileList.length > 0 &&
          Object.prototype.toString.call(this.defaultFileList[0]) !== '[object String]'
        if (flag) {
          this.componentError = true
          throw new Error('defaultFileList格式错误，应为string[]格式')
        }else{
          this.componentError = false
        }
        this.viewFileList = this.defaultFileList.map(defaultFilePath => ({name: defaultFilePath, url: defaultFilePath}))
        this.viewFileList.forEach(item=>{
          this.fileList.push(item)
        })
      },
      deep: true,
      immediate: true
    },
    fileList:{
      handler(){
        this.handleNotifyFather()
      },
      deep:true,
      immediate:false
    }
  },
  methods: {
    /**
     * 检查type是否符合types的mime
     * @param type 文件后缀
     * @param types 可用文件后缀集合
     */
    handleCheckFileMime(type, types) {
      const typeMimes = types.map(item => mime.getType(item))
      return typeMimes.includes(type)
    },

    handleCheckFileSize(fileSize, limitSize) {
      const limitByteSize = limitSize * 1024 * 1024
      return limitByteSize > fileSize
    },

    /**
     * 上传之前的钩子
     * @param file
     * @return {undefined}
     */
    handleBeforeUpload(file) {
      // 检查mime
      const fileType = file.type || mime.getType(file.name.slice(file.name.lastIndexOf('.') + 1))
      const checkFileMime = this.handleCheckFileMime(fileType, this.types)
      const checkFileSize = this.handleCheckFileSize(file.size, this.limitSize);
      !checkFileSize ? file.isJumpRemove = true : undefined
      !checkFileSize ? this.$notify.warning(`文件大小不得超出${ this.limitSize }m`) : undefined
      !checkFileMime ? file.isJumpRemove = true : undefined
      !checkFileMime ? this.$notify.warning(`文件类型不在合法列表 ${ this.types }`) : undefined
      if(checkFileSize && checkFileMime){
        // 开启loading
        this.isUploading = true
        if(this.needFullScreenLoading){
          fullLoading = this.$loading({
            background:`rgba(255,255,255,0.5)`,
            text:'上传中',
            fullscreen:true
          })
        }
      }
      return checkFileSize && checkFileMime
    },

    /**
     * 上传成功钩子
     * @param response
     * @param file
     * @param fileList
     */
    handleUploadSuccess(response, file, fileList) {
      this.isUploading = false
      if(this.needFullScreenLoading){
        fullLoading?.close()
      }
      const successObj = {
        url: response.data[this.responseFileName],
        name: file.name
      }
      file.url =  response.data[this.responseFileName]
      this.fileList.push(successObj)
    },

    /**
     * 上传失败的钩子
     * @param err
     * @param file
     * @param fileList
     */
    handleUploadError(err, file, fileList) {
    },

    /**
     * 超出数量的钩子
     * @param files
     * @param fileList
     */
    handleExceed(files, fileList) {
      this.$notify.warning(`文件总数大于可上传数量 ${ this.limit }`)
    },

    /**
     * 文件即将移除的钩子
     * @param file
     * @param fileList
     */
    async handleBeforeRemove(file, fileList) {
      // 如果是超出文件大小调用，放行
      if (file?.raw?.isJumpRemove) {
        return true
      }
      return await this.$confirm('此操作将会删除已上传的文件, 是否继续?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      })
    },

    /**
     * 移除文件的钩子
     */
    handleRemove(file, fileList) {
      if (file?.raw?.isJumpRemove) {
        return
      }
      this.fileList.splice(this.fileList.findIndex(fileItem => file?.response?.data[this.responseFileName] === fileItem.url || file.url === fileItem.url), 1)
    },

    /**
     * 通知父组件
     */
    handleNotifyFather(){
      this.$emit('change',this.fileList)
    },

    /**
     * 预览
     * 图片视频直接预览，其他下载
     * @param file
     */
    handlePreviewOpen(file){
      if(!file.type){
        file.type = mime.getType(file?.url?.slice(file?.url?.lastIndexOf('.')+1)) || mime.getType(file?.name?.slice(file?.name.lastIndexOf('.')+1)) || undefined
      }
      if(file.type.includes('image') || file.type.includes('video')){
        this.previewObj.file = file
        this.previewObj.show = true
      }else{
        this.$confirm('需要下载才能预览此文件, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          let htmlAnchorElement = document.createElement('a');
          htmlAnchorElement.download = file?.url.slice(file?.url.lastIndexOf('/')+1)
          htmlAnchorElement.target='_bank'
          htmlAnchorElement.href = file?.url
          htmlAnchorElement.click()
          htmlAnchorElement = null
        }).catch(() => {
        });
      }
    },

    handlePreviewClose(){
      this.previewObj.file = null
      this.previewObj.show = false
    }
  }
}
</script>

<style
  lang="scss"
  scoped
>
::v-deep .el-upload {
  width: 100% !important;

  .el-upload-dragger {
    width: 100% !important;
  }
}

.error-text {
  font-size: 18px;
  font-weight: bolder;
  color: red;
  animation: error-animation 2.5s ease-in-out infinite;
}

@keyframes error-animation {
  0%, 100% {
    font-size: 18px;
    color: red;
  }
  25%, 75% {
    font-size: 16px;
    color: #b9b1b1;
  }
  50% {
    font-size: 18px;
    color: #500000;
  }
}

.preview-content{
  display: flex;
  align-items: center;
  justify-content: center;
  .preview-item{
    min-width: 800px;
  }
}

</style>
