<template>
<div>
  <el-upload
      ref="fileUpload"
      action=""
      :show-file-list="multiple"
      :auto-upload="false"
      :accept="accept"
      :http-request="fileUpload"
      :before-upload="beforeFileUpload"
      :multiple="multiple"
      :limit="limit"
      :on-exceed="multipleFileExceed"
      :on-error="fileError"
      :on-change="fileChange"
      :on-success="fileSuccess"
      :on-remove="multipleFileRemove"
      :file-list="fileList"
      :list-type="multiple?'picture-card':'text'">
    <div v-if="!multiple && fileType === 'image' && file.url" class="image" :style="size">
      <el-image :src="file.url" :style="size" :fit="fit" v-loading="loading"></el-image>
      <div class="preview-hover" @click.stop="e=>e.stopPropagation()">
        <el-link v-if="!disabled" class="link-button" icon="el-icon-download" @click="fileDownload(file)"></el-link>
        <el-link v-if="!disabled" class="link-button" icon="el-icon-delete" @click="e=> onFileRemove(e)"></el-link>
      </div>
    </div>

    <video v-else-if="!multiple && fileType === 'video' && file.url" :src="file.url" class="video" :style="size" controls="controls" v-loading="loading">
      您的浏览器不支持视频播放
    </video>
    <audio v-else-if="!multiple && fileType === 'audio' && file.url" :src="file.url" class="audio" :style="size" controls="controls" ref="audio" v-loading="loading">
      您的浏览器不支持音频播放
    </audio>
    <div v-else-if="!multiple && fileType === '*' && file.url" class="file border" :style="size">
      <div class="filename" @click="fileDownload(file)">{{ file.name }}</div>
      <div class="preview-hover" @click.stop="e=>e.stopPropagation()">
        <el-link v-if="!disabled" class="link-button" icon="el-icon-download" @click="fileDownload(file)"></el-link>
        <el-link v-if="!disabled" class="link-button" icon="el-icon-delete" @click="e=> onFileRemove(e)"></el-link>
      </div>
    </div>
    <div v-else slot="default" class="image-upload-icon border" :style="size" ref="default">
      <slot>
        <i class="el-icon-plus"></i>
        <div class="tips-text" v-html="tips"></div>
      </slot>
    </div>
    <div slot="file" slot-scope="{file}" :ref="file.uid">
      <template v-if="fileType === 'image'">
        <el-image class="el-upload-list__item-thumbnail w100p h100p" :src="file.url"></el-image>
        <span class="el-upload-list__item-actions">
         <span class="el-upload-list__item-preview" style="line-height: 21px;">
           <el-image class="el-upload-list__item-preview" style="line-height: 21px; display: inline;" :src="require('./zoom-in.png')" :preview-src-list="fileList.map(item => item.url)"></el-image>
         </span>

        <span v-if="!disabled" class="el-upload-list__item-delete" @click="fileDownload(file)">
          <i class="el-icon-download"></i>
        </span>
        <span v-if="!disabled" class="el-upload-list__item-delete" @click="multipleFileRemove(file, fileList)">
          <i class="el-icon-delete"></i>
        </span>
      </span>
      </template>
      <template v-else-if="fileType === '*'">
        <div class="file" :style="size">
          <div class="filename">{{ file.name }}</div>
        </div>
        <div class="el-upload-list__item-actions">
          <span v-if="!disabled" class="el-upload-list__item-delete" @click="fileDownload(file)">
            <i class="el-icon-download"></i>
          </span>
          <span v-if="!disabled" class="el-upload-list__item-delete" @click="multipleFileRemove(file, fileList)">
            <i class="el-icon-delete"></i>
          </span>
        </div>
      </template>
    </div>
  </el-upload>
  <cropper
      v-if="cropperDialogVisible"
      :visible.sync="cropperDialogVisible"
      :cropper-img="cropperImage"
      :options="options"
      @crop="onCrop"
  />
</div>
</template>

<script>
import Cropper from "./cropper.vue";
import {checkImage} from "@/utils/validate";
import {compressFile} from "@/utils/fileUtil";
import {downloadImage, OSSFileUpload, upload} from "@/utils/httpUtil";

export default {
  name: "Upload",
  components: {
    Cropper,
  },
  model: {
    prop: 'src',
    event: 'change',
  },
  props: {
    accept: { // 文件类型
      type: String,
      require: true
    },
    url: {
      type: String,
      default: '/v1/file/upload'
    },
    stsUrl: {
      type: String,
      default: '/v1/file/sts'
    },
    sts: Boolean,
    src: { // 图片地址
      type: [String, Object, Array],
      require: true
    },
    tips: { // 提示文字
      type: String,
      default: ''
    },
    width: { // 图片宽度
      type: Number,
      default: 160
    },
    height: { // 图片高度
      type: Number,
      default: 106
    },
    fit: { // 图片填充方式
      type: String,
      default: 'cover'
    },
    multiple: Boolean, // 多文件上传

    limit: { // 多文件上传，最大文件数量
      type: Number,
      default: 5
    },
    cropper: { // 是否裁剪
      type: Boolean,
      default: false
    },
    options: { // 裁剪选项
      type: Object,
      default: ()=>{
        return {}
      }
    }
  },

  computed: {
    size() { // 尺寸大小
      return {
        width: this.width + "px",
        height: this.height + "px",
      }
    }
  },

  watch: {
    accept: {
      handler: function (value) {
        this.fileType = value?.split('/')[0] || "*";
      },
      immediate: true
    },
    src: {
      handler() {
        if (this.multiple) {
          if (this.src && Array.isArray(this.src)) {
            this.fileList = this.src;
            this.$nextTick(() => {
              this.src.forEach(file => {
                this.$refs[file.uid].parentElement.style.transition = "none";
                this.$refs[file.uid].parentElement.style.width = this.width + 'px';
                this.$refs[file.uid].parentElement.style.height = this.height + 'px';
                this.$refs[file.uid].parentElement.style.display = "inline-block";
              })
              this.$refs["default"].parentElement.style.border = 'none';
              this.$refs["default"].parentElement.style.width = this.width + 'px';
              this.$refs["default"].parentElement.style.height = this.height + 'px';
            })
          } else {
            this.fileList = [];
          }
        } else {
          if (typeof this.src === "string") {
            this.file.url = this.src;
            this.file.name = this.src.substring(this.src.lastIndexOf('/') + 1)
          } else if (this.src instanceof Object) {
            this.file.url = this.src.url;
            this.file.name = this.src.name;
          }
        }
      },
      immediate: true
    }
  },

  data() {
    return {
      file: { // 文件
        url: undefined,
        name: undefined
      },
      fileType: undefined, // 文件类型
      fileList: [], // 文件列表，多文件时使用
      loading: false, // 加载中

      disabled: false,
      dialogImageUrl: '',
      dialogVisible: false,

      cropperDialogVisible: false, // 是否显示裁剪框
      cropperFile: "", // 需要裁剪的原始文件
      cropperImage: "", // 需要裁剪的图片
      uploadFile: "", // 裁剪后的文件
    }
  },

  mounted() {
  },
  methods: {
    // 多文件图片预览
    handlePictureCardPreview(file) {
      this.dialogImageUrl = file.url;
      this.dialogVisible = true;
    },

    // 多文件文件下载（每次一个）
    fileDownload(file) {
      downloadImage(file.url, file.name);
    },

    // 图片上传前检查
    beforeFileUpload(file) {
      this.fileType = this.fileType === "*" ? "*" : file.type.split("/")[0]; // fileType="*"时显示为文件
      return this.fileType === "image" ? checkImage(file) : true;
    },

    // 多文件上传时超出限制
    multipleFileExceed(files, fileList) {
      this.$message.warning(`当前限制选择 ${this.limit} 个文件，本次选择了 ${files.length} 个文件，共选择了 ${files.length + fileList.length} 个文件`);
    },

    // 多文件上传时出错
    fileError(err, file, fileList) {
      console.log("fileError", err, file, fileList);
    },

    // 多文件上传成功（多次调用）
    fileSuccess(response, file, fileList) {
      if (this.multiple) {
        const files = fileList.map(f => {
          return {
            url: f.uid === file.uid ? file.response : f.url,
            name: f.name,
            status: f.status,
            uid: f.uid,
          };
        })
        this.$emit("change", files); // 发送Url改变事件
      } else if (this.src instanceof Object) {
        this.$emit("change", {url: file.response, name: file.name}); // 发送Url改变事件
      } else {
        this.$emit("change", file.response); // 发送Url改变事件
      }
      this.$parent.$emit("el.form.change"); // 自定义组件的校验触发器
    },

    // 多文件上传删除
    multipleFileRemove(file, fileList) {
      const files = fileList.filter(item => item.uid !== file.uid);
      this.$emit("change", files); // 发送Url改变事件
      this.$parent.$emit("el.form.change"); // 自定义组件的校验触发器
    },

    // 文件上传
    fileUpload(request) {
      this.$nextTick(() => {
        if (this.$refs["preview"]) {
          this.$refs["preview"].parentElement.style.transition = "none";
          this.$refs["preview"].parentElement.style.width = this.width + 'px';
          this.$refs["preview"].parentElement.style.height = this.height + 'px';
          this.$refs["preview"].parentElement.style.display = "inline-block";
        }
      })
      return new Promise(async (resolve, reject) => {
        try {
          this.loading = true;

          if (!this.multiple) {
            if (this.src instanceof Object) {
              this.$emit("change", {url: URL.createObjectURL(request.file), name: request.file.name});
            } else {
              this.$emit("change", URL.createObjectURL(request.file)); // 单文件，更新本地Url
            }
          }

          // 图片压缩
          const file = this.fileType === "image" ? await compressFile(request.file) : request.file;

          // 文件上传
          const url = this.sts ? await OSSFileUpload(this.stsUrl, file) : await upload(this.url, file);

          if (!this.multiple) {
            // 获取音频时长
            if (this.fileType === "audio") {
              const audio = this.$refs.audio;
              audio.load(); // 因为source标签不能直接更改路径，所以整个audio标签必须重新加载一次
              audio.oncanplay = () => {
                this.$emit("duration", audio.duration);
              }
            }
          }
          resolve(url);
        } finally {
          this.loading = false;
        }
      });
    },

    // 当文件移除
    onFileRemove(e) {
      e.stopPropagation();

      // 单文件，发送Url改变事件
      this.$emit("change", this.src instanceof Object ? {} : "");
      // 自定义组件的校验触发器
      this.$parent.$emit("el.form.change");
    },


    // 文件改变
    fileChange(file) {
      if (file.status === "ready") {
        if (this.cropper) {
          this.cropperFile = file.raw;
          this.cropperImage = URL.createObjectURL(file.raw);
          this.cropperDialogVisible = true;
        } else {
          this.$refs.fileUpload.submit();
        }
      }
    },

    // 图片裁剪回调
    onCrop(blob) {
      this.cropperDialogVisible = false;
      const rawFile = new File([blob], this.cropperFile.name, {type: this.cropperFile.type});
      rawFile.uid = this.cropperFile.uid;
      let file = {
        status: 'ready',
        name: rawFile.name,
        size: rawFile.size,
        percentage: 0,
        uid: rawFile.uid,
        raw: rawFile
      };
      this.fileList = [file];
      this.$nextTick(() => {
        this.$refs.fileUpload.submit();
      })
    }
  }
}
</script>

<style scoped lang="scss">
@import "./src/styles/variables";

// 关闭动画
::v-deep .el-list-enter-active, .el-list-leave-active {
  transition: none !important;
}

::v-deep .el-list-enter, .el-list-leave {
  transition: none !important;
}

::v-deep .el-list-enter-to, .el-list-leave-to {
  transition: none !important;
}

::v-deep .el-list-move {
  transition: none !important;
}

// 默认隐藏，js修改尺寸后显示
::v-deep .el-upload-list--picture-card .el-upload-list__item {
  display: none;
}

::v-deep .el-upload--picture-card {
  border: none;
  width: auto;
  height: auto;
}

.image, .video, .audio {
  display: block;
  cursor: pointer;
  background-color: white;
  position: relative;

  ::v-deep .el-upload, .el-image {
    height: 100%;
  }

  &:hover {
    .preview-hover {
      display: flex;
    }
  }
}

.border {
  border: 1px dashed $--color-primary;
  border-radius: 6px;
  color: $--color-primary;
}

.file {
  background-color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 10px;
  height: 100%;
  color: $--color-primary;
  position: relative;

  .filename {
    //width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  &:hover {
    .preview-hover {
      display: flex;
    }
  }
}

.preview-hover {
  display: none;
  align-items: center;
  justify-content: center;
  background: #0000007f;
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  font-size: 16px;
}

.image-upload-icon {
  font-size: 28px;
  line-height: 50px;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;

  .tips-text {
    width: 100%;
    font-size: 14px;
    font-family: Microsoft YaHei;
    font-weight: 400;
    line-height: 24px;
  }

  &:hover {
    border-color: $--color-primary-light-7;
    background-color: $--color-primary-light-9;
  }
}

.link-button {
  font-size: 20px;
  margin: 0 12px;
  color: white;
}
</style>
