<template>
  <section class="uploader">
    <el-upload
        action
        multiple
        ref="upload"
        :class="{validError:!validateState}"
        :http-request="submitUpload"
        :on-remove="handleRemove"
        :on-change="handleChange"
        :before-upload="onBeforeUpload"
        :file-list="fileList"
        :limit="limit"
        :disabled="disabled"
        :list-type="isImg ? 'picture-card' : 'text'"
        :accept="accept"
        :show-file-list="!isSingle"
        :on-preview="handlePictureCardPreview"
        :auto-upload="isAutoUpload"
        :on-exceed="handleExceed">
      <template v-if="!disabled">
        <template v-if="hasUploaded&&isSingle">
          <div class="fileItem">
            <div class="uploadFile" @click.stop>
              <ai-icon type="svg" :icon="uploadFile.icon"/>
              <div class="info">
                <span v-text="uploadFile.name"/>
                <span class="size" v-text="uploadFile.size"/>
              </div>
            </div>
            <el-button>重新选择</el-button>
            <el-button v-if="clearable" plain type="danger" @click.stop="handleClear">删除</el-button>
          </div>
        </template>
        <template v-else-if="limit > fileList.length">
          <slot v-if="hasTriggerSlot" name="trigger"/>
          <div v-else class="uploaderBox">
            <span class="iconfont" :class="isImg ? 'iconPhoto' : 'iconAdd'"/>
            <p>上传{{ isImg ? '图片' : '附件' }}</p>
          </div>
        </template>
        <div slot="tip" class="el-upload__tip" v-if="showTips">
          <p v-if="fileType === 'img' && !acceptType && !hasTipsSlot">最多上传{{
              limit
            }}张图片,单个文件最大10MB，支持jpg、jpeg、png格式</p>
          <p v-if="fileType === 'file' && !acceptType && !hasTipsSlot">最多上传{{ limit }}个附件,单个文件最大10MB</p>
          <p v-if="fileType === 'file' && !acceptType && !hasTipsSlot">
            支持.zip、.rar、.doc、.docx、.xls、.xlsx、.ppt、.pptx、.pdf、.txt、.jpg、.png格式</p>
          <p>
            <slot name="tips" v-if="hasTipsSlot"></slot>
          </p>
        </div>
      </template>
    </el-upload>
    <el-dialog :visible.sync="dialog" title="图片预览编辑器" :modal="false" :show-close="false" append-to-body>
      <vue-cropper
          ref="cropper"
          style="height: 400px;"
          :img="fileList.length ? fileList[0].url : ''" v-bind="crop"/>
      <div style="text-align: center;margin-top: 10px;">
        <el-radio-group v-if="crop.fixed" size="small" v-model="currFixedIndex" @change="onFixedChange"
                        style="margin-right: 8px;">
          <el-radio-button
              :label="0"
              size="small">
            1.6:1
          </el-radio-button>
          <el-radio-button
              :label="1"
              size="small">
            4:3
          </el-radio-button>
        </el-radio-group>
        <el-button size="small" circle icon="el-icon-refresh-right" @click="$refs.cropper.rotateRight()"></el-button>
        <el-button size="small" circle icon="el-icon-refresh-left" @click="$refs.cropper.rotateLeft()"></el-button>
      </div>
      <div slot="footer">
        <el-popconfirm title="是否关闭图片预览器,并上传图片?" @confirm="previewCrop">
          <el-button slot="reference" type="primary">保存</el-button>
        </el-popconfirm>
        <el-button @click="onClose">关闭</el-button>
      </div>
    </el-dialog>
    <div class="images" v-viewer="{movable: true}" v-show="false">
      <img v-for="(item, index) in imgList" :src="item" :key="index" alt="">
    </div>
  </section>
</template>

<script>
import {VueCropper} from 'vue-cropper'

export default {
  name: 'AiUploader',
  components: {VueCropper},
  inject: {
    elFormItem: {default: ""},
    elForm: {default: ''},
  },
  model: {
    prop: 'value',
    event: 'change'
  },
  props: {
    value: {default: () => []},
    url: {
      type: String,
      default: '/admin/file/add'
    },
    isShowTip: {
      type: Boolean,
      default: false
    },
    isWechat: {
      type: Boolean,
      default: false
    },
    maxSize: {
      type: Number,
      default: 10
    },
    instance: Function,
    acceptType: {type: String},
    fileType: {type: String, default: 'img'},
    limit: {type: Number, default: 9},
    disabled: {type: Boolean, default: false},
    isCrop: {type: Boolean, default: false},
    cropOps: Object,
    isImport: {
      type: Boolean,
      default: false
    },
    clearable: {default: true},
    valueIsUrl: Boolean,
    showLoading: Boolean
  },
  data() {
    return {
      fileList: [],
      dialog: false,
      currFixedIndex: 0,
    }
  },
  watch: {
    value: {
      handler(v) {
        this.dispatch('ElFormItem', 'el.form.change', [v]);
        if (v?.length > 0) {
          this.fileList = this.valueIsUrl ? v?.split(",")?.map(url => ({url})) : [...v]
        }
      },
      immediate: true,
      deep: true
    }
  },
  computed: {
    isImg() {
      return this.fileType === 'img'
    },
    validateState() {
      return ['', 'success'].includes(this.elFormItem?.validateState)
    },
    isAutoUpload() {
      return !(this.isCrop || this.isImport);
    },
    hasTipsSlot() {
      return this.$slots.tips
    },
    hasTriggerSlot() {
      return this.$slots.trigger
    },
    accept() {
      if (this.acceptType) {
        return this.acceptType
      }

      return this.isImg ? '.jpg,.png,.jpeg' : '.zip,.rar,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.pdf,.txt,.jpg,.png,.mp4'
    },
    crop() {
      return {
        autoCrop: true,
        outputType: 'png',
        fixedBox: false,
        fixed: true,
        fixedNumber: [1.6, 1],
        width: 0,
        height: 0,
        ...this.cropOps
      }
    },
    imgList() {
      return this.fileList.map(v => v.url)
    },
    isSingle() {
      return this.limit == 1 && this.isImport
    },
    showTips() {
      return this.isShowTip || this.$slots.tips
    },
    hasUploaded() {
      return this.fileList?.length > 0
    },
    uploadFile() {
      let file = this.fileList?.[0],
          size = Number(file.size),
          icon = "iconTxt"
      //显示大小
      if (size > Math.pow(1024, 2)) {
        size = (size / Math.pow(1024, 2)).toFixed(1) + 'MB'
      } else {
        size = (size / 1024).toFixed(1) + 'KB'
      }
      //显示图标
      if (/\.(xls|xlsx)$/.test(file.name)) {
        icon = "iconExcel"
      } else if (/\.(zip)$/.test(file.name)) {
        icon = "iconZip"
      } else if (/\.(rar)$/.test(file.name)) {
        icon = "iconRar"
      } else if (/\.(png)$/.test(file.name)) {
        icon = "iconPng"
      } else if (/\.(pptx|ppt)$/.test(file.name)) {
        icon = "iconPPT"
      } else if (/\.(doc|docx)$/.test(file.name)) {
        icon = "iconWord"
      }

      return {...file, size, icon}
    }
  },
  methods: {
    onFixedChange(e) {
      this.fixedNumber = e === 0 ? [1.6, 1] : [4, 3]

      this.$nextTick(() => {
        this.$refs.cropper.goAutoCrop()
      })
    },
    handleChange(file, fileList) {
      if (this.isImport) {
        if (!this.onOverSize(file)) {
          this.fileList = []
          return false
        }
        this.fileList = fileList
        this.emitChange(fileList)
        return false
      }
      if (this.isCrop) {
        if (file.raw.type === 'image/gif') {
          this.$message.error(`不支持gif格式的图片`)
          this.fileList = []

          return false
        }

        if (!this.onOverSize(file)) {
          this.fileList = []

          return false
        }
        this.dialog = true
        this.fileList = fileList
      } else {
        this.fileList = fileList
      }
    },
    handleExceed(files) {
      if (this.isSingle && files[0]) {
        this.$refs.upload?.clearFiles()
        this.$refs.upload?.handleStart(files[0])
      } else this.$message.warning(`最多上传${this.limit}个${this.isImg ? '图片' : '文件'}`)
    },
    handlePictureCardPreview(file) {
      if (this.fileType !== 'img') return
      const index = this.imgList.indexOf(file.url)
      const viewer = this.$el.querySelector('.images').$viewer
      viewer.view(index)
    },
    handleRemove(file, fileList) {
      this.fileList = fileList
      this.emitChange(fileList)
    },
    emitChange(files) {
      this.$emit('change', this.valueIsUrl ? files?.map(e => e.url)?.toString() : files)
    },
    handleClear() {
      this.fileList = []
    },
    getExtension(name) {
      return name.substring(name.lastIndexOf('.'))
    },

    onOverSize(e) {
      const isLt10M = e.size / 1024 / 1024 < this.maxSize
      const suffixName = this.getExtension(e.name)
      const suffixNameList = this.accept.split(',')

      if (suffixNameList.indexOf(`${suffixName.toLowerCase()}`) === -1) {
        this.$message.error(`不支持该格式`)
        return false
      }

      if (!isLt10M) {
        this.$message.error(`${this.isImg ? '图片' : '文件'}大小不超过${this.maxSize}MB!`)
        return false
      }

      return true
    },

    onBeforeUpload(event) {
      return this.onOverSize(event)
    },

    onClose() {
      this.fileList = []
      this.dialog = false
    },

    submitUpload(file) {
      let formData = new FormData()
      formData.append('file', file.file)
      let loading
      if (this.showLoading) {
        loading = this.$loading({lock: true, text: '上传中...', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)'})
      }
      this.instance.post(this.url, formData, {
        withCredentials: false
      }).then(res => {
        if (res?.code == 0) {
          if (this.isWechat) {
            this.emitChange([{
              ...res.data.file,
              media: res.data.media
            }])
            this.fileList.forEach(item => {
              if (item.uid === file.file.uid) {
                item.id = res.data.file.id
                item.path = res.data.file.url
                item.url = res.data.file.url,
                    item.media = res.data.media
              }
            })
            this.emitChange(this.fileList)
            this.$message.success('上传成功')
            return false
          }
          if (Object.prototype.toString.call(res.data) === '[object Object]') {
            this.fileList.forEach(item => {
              if (item.uid === file.file.uid) {
                item.id = res.data.id
                item.path = res.data.url
                item.url = res.data.url
                item.url = res.data.url
                item.fileSizeStr = res.data.fileSizeStr
                item.fileSize = res.data.size
                item.postfix = res.data.postfix
              }
            })
          } else {
            let data = res.data[0].split(';')
            this.fileList.forEach(item => {
              if (item.uid === file.file.uid) {
                item.id = data[1]
                item.path = data[0]
                item.url = data[0]
              }
            })
          }
          this.emitChange(this.fileList)
          this.$message.success('上传成功')
        }
      }).finally(() => loading?.close())
    },
    previewCrop() {
      this.$refs.cropper.getCropBlob(data => {
        data.name = this.fileList[0].name;
        this.fileList[0].file = new window.File([data], data.name, {type: data.type})
        this.fileList[0].file.uid = this.fileList[0].uid
        this.submitUpload(this.fileList[0])
        this.dialog = false
      })
    },
    /**
     * 表单验证
     * @param componentName
     * @param eventName
     * @param params
     */
    dispatch(componentName, eventName, params) {
      let parent = this.$parent || this.$root;
      let name = parent.$options.componentName;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.componentName;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
  },
}
</script>

<style lang="scss" scoped>
.uploader {
  line-height: 1;

  :deep(.el-upload ) {
    width: 100%;
    text-align: start;
  }

  :deep(.validError ) {
    .el-button {
      border-color: #f46;
      color: #f46;
    }
  }

  :deep(.el-upload--picture-card ) {
    border: none;
  }

  :deep( .el-list-leave-active), :deep( .el-upload-list__item ) {
    transition: all 0s !important;
  }

  :deep(.el-upload-list--picture-card .el-upload-list__item ) {
    width: 120px;
    height: 120px;
    border-radius: 4px;
  }

  :deep(.el-upload--picture-card ) {
    width: auto;
    height: auto;
  }

  .el-upload__tip p {
    color: #999;
    line-height: 16px;
  }

  :deep(.fileItem ) {
    width: 100%;
    height: 60px;
    background: #FFFFFF;
    border-radius: 2px;
    border: 1px solid #D0D4DC;
    display: flex;
    align-items: center;
    padding: 0 16px;
    cursor: default;

    .uploadFile {
      text-align: start;
      flex: 1;
      min-width: 0;
      display: flex;
      color: #222;

      .AiIcon {
        width: 40px;
        height: 40px;
      }

      .info {
        flex: 1;
        min-width: 0;
        display: flex;
        flex-direction: column;
        margin-left: 8px;
        margin-right: 40px;
        line-height: 22px;
      }

      .size {
        color: #888;
      }
    }
  }

  .uploaderBox {
    width: 120px;
    height: 120px;
    line-height: 1;
    background: #F3F4F7;
    border-radius: 2px;
    border: 1px solid #D0D4DC;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;

    &:hover {
      opacity: 0.6;
    }

    span {
      font-size: 32px;
      color: #8899bb;

      &:hover {
        color: #8899bb;
      }
    }

    p {
      margin: 0;
      padding-top: 4px;
      color: #555;
      font-size: 12px;
      text-align: center;
      line-height: 1;
    }
  }
}
</style>
