<template>
  <div
    class="czr-form-column-upload-com"
    :class="{
      'czr-form-column-upload-com_view': isValue($attrs.disabled)
        ? $attrs.disabled
        : isLimitCpt || isViewCpt,
      'czr-form-column-upload-com_list': uploadLayout === 'list',
      'czr-form-column-upload-com_card': uploadLayout === 'card',
      'czr-form-column-upload-com_limit-no-upload':
        isLimitCpt && limitNoUpload !== false,
    }"
    v-loading="state.loading"
    :element-loading-background="state.elementLoadingBackground"
  >
    <el-upload
      class="el-upload-com"
      ref="ref_upload"
      v-bind="$attrs"
      :file-list="state.paramVal"
      action="#"
      :list-type="uploadLayout === 'card' ? 'picture-card' : 'text'"
      :http-request="handleRequest"
      :before-upload="handleBeforeUpload"
      :accept="acceptTypeCpt"
      :limit="limit > 0 ? limit : null"
      :on-remove="handleRemove"
      :disabled="
        isValue($attrs.disabled) ? $attrs.disabled : isLimitCpt || isViewCpt
      "
    >
      <el-tooltip
        :content="acceptTooltipCpt"
        placement="top"
        popper-class="upload-tooltip-popper"
      >
        <template v-if="uploadLayout === 'card'">
          <div
            class="upload-layout-card __hover"
            :class="{
              'limit-disabled':
                isLimitCpt || (isViewCpt && state.paramVal.length === 0),
            }"
          >
            <SvgIcon name="add" color="#0062E9" size="24" />
            <template v-if="title">
              <div>{{ title }}</div>
            </template>
            <template v-else>
              <div>选择{{ acceptTypeFormatCpt }}</div>
              <div v-if="limit > 0">(最多{{ limit }}个)</div>
            </template>
          </div>
        </template>
        <template v-else>
          <div
            class="upload-layout-list_button"
            :class="{
              'limit-disabled':
                isLimitCpt || (isViewCpt && state.paramVal.length === 0),
            }"
          >
            <SvgIcon name="add" color="#ffffff" size="14" />
            <template v-if="title">
              {{ title }}
            </template>
            <template v-else>
              选择{{ acceptTypeFormatCpt }}
              <template v-if="limit > 0">(最多{{ limit }}个)</template>
            </template>
          </div>
        </template>
      </el-tooltip>
      <template #file="{ file }">
        <template v-if="uploadLayout === 'card'">
          <el-tooltip
            placement="top"
            :content="file[nameKey] ?? file[urlKey]"
            :disabled="true"
          >
            <div
              class="upload-layout-card_item"
              :class="{ 'item-view': isViewCpt || !delRule(file) }"
              @mouseenter="file.hover = true"
              @mouseleave="file.hover = false"
            >
              <template v-if="validImgByUrl(file[urlKey])">
                <img
                  class="img __hover"
                  :src="file[urlKey]"
                  @click="viewImg(file[urlKey])"
                />
              </template>
              <template v-else-if="validVideoByUrl(file[urlKey])">
                <video class="video __hover" controls :src="file[urlKey]" />
              </template>
              <template v-else>
                <img
                  class="file __hover"
                  :src="getFileImgByUrl(file[urlKey])"
                  @click="
                    downloadFileByUrl(
                      file[urlKey],
                      file[nameKey] ?? file[urlKey],
                    )
                  "
                />
              </template>
              <img
                class="del __hover"
                src="../imgs/file-del.png"
                v-if="file.hover && !isViewCpt && delRule(file)"
                @click.stop="ref_upload.handleRemove(file)"
              />
            </div>
          </el-tooltip>
        </template>
        <template v-else>
          <div
            class="upload-layout-list_item __hover"
            :class="{ 'item-view': isViewCpt || !delRule(file) }"
            @click="
              validImgByUrl(file[urlKey])
                ? viewImg(file[urlKey])
                : downloadFileByUrl(file[urlKey], file[nameKey] ?? file[urlKey])
            "
          >
            <img class="file-type-img" :src="getFileImgByUrl(file[urlKey])" />
            <CzrEllipsis
              v-if="file[nameKey] ?? file[urlKey]"
              class="label"
              :value="file[nameKey] ?? file[urlKey]"
            />
            <SvgIcon
              v-if="!isViewCpt && delRule(file)"
              class="close"
              name="close_3"
              size="12"
              @click.stop="ref_upload.handleRemove(file)"
            />
          </div>
        </template>
      </template>
    </el-upload>
    <el-image-viewer
      v-if="state.currentImg.show"
      :zoom-rate="2"
      :max-scale="10"
      :min-scale="0.2"
      :hide-on-click-modal="true"
      :url-list="[state.currentImg.url]"
      :initial-index="0"
      @close="imageClose"
      :teleported="true"
      :crossorigin="null"
    />
  </div>
</template>

<script setup lang="ts">
import {
  defineComponent,
  computed,
  onMounted,
  ref,
  reactive,
  watch,
  getCurrentInstance,
  ComponentInternalInstance,
  toRefs,
  nextTick,
  onBeforeMount,
  inject,
} from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { ElMessage } from 'element-plus'
import FileDefaultImg from '../imgs/file-type/file-type_default.png'
import FilePDFImg from '../imgs/file-type/file-type_pdf.png'
import FilePPTImg from '../imgs/file-type/file-type_ppt.png'
import FileRARImg from '../imgs/file-type/file-type_rar.png'
import FileTXTImg from '../imgs/file-type/file-type_txt.png'
import FileWORDImg from '../imgs/file-type/file-type_word.png'
import FileEXCELImg from '../imgs/file-type/file-type_excel.png'
import { isValue } from '@/utils/czr-util'
import { fileUploadFile } from '@/api/modules/global/upload'

const emit = defineEmits(['emitParam'])
const props = defineProps({
  param: {},
  label: {},
  limit: {
    default: 0,
  },
  type: {
    default: 'all',
    validator(val: string) {
      return ['all', 'img', 'file'].includes(val)
    },
  },
  uploadLayout: {
    default: 'list',
    validator(val: string) {
      return ['list', 'card'].includes(val)
    },
  },
  acceptType: { default: '', type: String },
  acceptMax: { default: 0, type: Number },
  acceptFunc: { default: null },
  view: { default: null },
  delRule: { default: () => () => false },
  cardWidth: { default: '8rem' },
  cardHeight: { default: '8rem' },
  urlKey: { default: 'url' },
  nameKey: { default: 'name' },
  limitNoUpload: { default: false },
  title: {},
})
const state = reactive({
  paramVal: <any>props.param,
  loading: false,
  elementLoadingBackground: inject('element-loading-background', null),
  currentImg: {
    show: false,
    url: '',
  },
  formView: inject('form-view', false),
  config: {
    fileMax: 50,
    fileType: '.doc,.docx,.ppt,.pptx,.pdf,.xls,.xlsx,.txt,.rar,.zip',
    imgMax: 50,
    imgType: '.jpg,.jpeg,.png,.gif,.svg,.bmp,.webp',
  },
})
watch(
  () => state.paramVal,
  (n) => {
    emit('emitParam', n)
  },
)
watch(
  () => props.param,
  (n) => {
    state.paramVal = n
  },
)
const isViewCpt = computed(() => {
  return isValue(props.view) ? props.view : state.formView
})
const ref_upload = ref()
const validImgByUrl = (url) => {
  if (url) {
    const imgList = [
      'bmp',
      'jpg',
      'jpeg',
      'png',
      'tif',
      'gif',
      'pcx',
      'tga',
      'exif',
      'fpx',
      'svg',
      'psd',
      'cdr',
      'pcd',
      'dxf',
      'ufo',
      'eps',
      'ai',
      'raw',
      'WMF',
      'webp',
      'avif',
      'apng',
      'jfif',
    ]
    //进行图片匹配
    let result =
      [
        ...imgList.map((v) => v.toLowerCase()),
        ...imgList.map((v) => v.toUpperCase()),
      ].some((t) => url.includes('.' + t)) ||
      url.includes('/ngx/proxy') ||
      url.includes('/pic?')
    return result
  }
  return false
}
const validVideoByUrl = (url) => {
  if (url) {
    const videoList = ['mp4']
    //进行图片匹配
    let result = [
      ...videoList.map((v) => v.toLowerCase()),
      ...videoList.map((v) => v.toUpperCase()),
    ].some((t) => url.includes('.' + t))
    return result
  }
  return false
}
const acceptTypeCpt = computed(() => {
  let str = ''
  if (isValue(props.acceptType)) {
    str += props.acceptType
  } else {
    if (props.type === 'all') {
      str += state.config.imgType
      str += ',' + state.config.fileType
    } else if (props.type === 'img') {
      str += state.config.imgType
    } else if (props.type === 'file') {
      str += state.config.fileType
    }
  }
  return str
})
const acceptTypeFormatCpt = computed(() => {
  let str = ''
  if (isValue(props.acceptType)) {
    str += `自定义文件`
  } else {
    if (props.type === 'all') {
      str += `图片、文件`
    } else if (props.type === 'img') {
      str += `图片`
    } else if (props.type === 'file') {
      str += `文件`
    }
  }
  return str
})
const acceptTooltipCpt = computed(() => {
  let str = ''
  if (isValue(props.acceptType)) {
    str += `只支持大小在0~${props.acceptMax}M内，格式为${props.acceptType}的自定义文件`
  } else {
    if (props.type === 'all') {
      str += `只支持大小在0~${state.config.imgMax}M内，格式为${state.config.imgType}的图片，`
      str += `或大小在0~${state.config.fileMax}M内，格式为${state.config.fileType}的文件`
    } else if (props.type === 'img') {
      str += `只支持大小在0~${state.config.imgMax}M内，格式为${state.config.imgType}的图片`
    } else if (props.type === 'file') {
      str += `只支持大小在0~${state.config.fileMax}M内，格式为${state.config.fileType}的文件`
    }
  }
  if (isLimitCpt.value) {
    str = `最多上传${props.limit}个${acceptTypeFormatCpt.value}`
  }
  return str
})
const isLimitCpt = computed(() => {
  return props.limit > 0 && props.limit <= state.paramVal.length
})
const handleBeforeUpload = (file) => {
  if (isValue(props.acceptType)) {
    if (file.size / (1024 * 1024) > props.acceptMax) {
      ElMessage.warning(`自定义文件大小不可超过${props.acceptMax}M`)
      return false
    } else if (
      !props.acceptType.split(',').some((v) => file.name.includes(v))
    ) {
      ElMessage.warning(`自定义文件类型仅支持${props.acceptType}`)
      return false
    }
  } else {
    if (props.type === 'img') {
      if (file.size / (1024 * 1024) > state.config.imgMax) {
        ElMessage.warning(`图片大小不可超过${state.config.imgMax}M`)
        return false
      } else if (
        !state.config.imgType.split(',').some((v) => file.name.includes(v))
      ) {
        ElMessage.warning(`图片类型仅支持${state.config.imgType}`)
        return false
      }
    } else if (props.type === 'file') {
      if (file.size / (1024 * 1024) > state.config.fileMax) {
        ElMessage.warning(`文件大小不可超过${state.config.fileMax}M`)
        return false
      } else if (
        !state.config.fileType.split(',').some((v) => file.name.includes(v))
      ) {
        ElMessage.warning(`文件类型仅支持${state.config.fileType}`)
        return false
      }
    } else if (props.type === 'all') {
      if (!acceptTypeCpt.value.split(',').some((v) => file.name.includes(v))) {
        ElMessage.warning(`文件或图片类型仅支持${acceptTypeCpt.value}`)
        return false
      } else {
        if (
          state.config.imgType.split(',').some((v) => file.name.includes(v))
        ) {
          if (file.size / (1024 * 1024) > state.config.imgMax) {
            ElMessage.warning(`图片大小不可超过${state.config.imgMax}M`)
            return false
          }
        } else if (
          state.config.fileType.split(',').some((v) => file.name.includes(v))
        ) {
          if (file.size / (1024 * 1024) > state.config.fileMax) {
            ElMessage.warning(`文件大小不可超过${state.config.fileMax}M`)
            return false
          }
        }
      }
    }
  }
  return file
}
const handleRequest = async (options) => {
  state.loading = true
  if (isValue(props.acceptFunc)) {
    const result: any = await props.acceptFunc?.(options)
    if (result?.[props.urlKey] && result?.[props.nameKey]) {
      state.paramVal = [...state.paramVal, result]
    } else {
      state.paramVal = [...state.paramVal]
    }
    state.loading = false
  } else if (
    [
      ...state.config.imgType.split(','),
      ...state.config.fileType.split(','),
    ].some((v) => options.file.name.includes(v))
  ) {
    const formData = new FormData()
    formData.append('file', options.file)
    fileUploadFile(formData)
      .then(({ data }: any) => {
        state.paramVal = [
          ...state.paramVal,
          {
            [props.urlKey]: data.path,
            [props.nameKey]: data.filename.replace(/,/g, '_'),
          },
        ]
      })
      .catch(() => {})
      .finally(() => {
        state.loading = false
      })
  }
}
const fileTypeMapper = new Map([
  ['doc', FileWORDImg],
  ['docx', FileWORDImg],
  ['ppt', FilePPTImg],
  ['pptx', FilePPTImg],
  ['pdf', FilePDFImg],
  ['xls', FileEXCELImg],
  ['xlsx', FileEXCELImg],
  ['txt', FileTXTImg],
  ['rar', FileRARImg],
  ['zip', FileRARImg],
])
const getFileImgByUrl = (url) => {
  if (url) {
    const regex = /\.([^.]*)$/
    const match = url.match(regex)
    const t = match ? match[1] : ''
    if (t && fileTypeMapper.has(t)) {
      return fileTypeMapper.get(t)
    }
  }
  return FileDefaultImg
}
const handleRemove = (file, files) => {
  state.paramVal = files
}
const downloadFileByUrl = (url, name) => {
  ElMessage.info('开始下载！')
  const xhr = new XMLHttpRequest()
  xhr.open('GET', url, true)
  xhr.responseType = 'blob'
  xhr.onload = function () {
    if (xhr.status === 200) {
      const blob = xhr.response
      const url = window.URL.createObjectURL(blob)
      const a = document.createElement('a')
      a.href = url
      a.download = name || 'default'
      a.click()
      ElMessage.success('下载成功！')
      window.URL.revokeObjectURL(url)
    }
  }
  xhr.send()
}
const viewImg = (url) => {
  state.currentImg.url = url
  state.currentImg.show = true
}
const imageClose = () => {
  state.currentImg.show = false
  state.currentImg.url = ''
}
</script>

<style scoped lang="scss">
.czr-form-column-upload-com {
  $uploadWidth: v-bind(cardWidth);
  $uploadHeight: v-bind(cardHeight);
  width: 100%;
  position: relative;
  .el-upload-com {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    :deep(.el-upload) {
      .upload-layout-list_button {
        height: 32px;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 0 12px;
        background-color: var(--czr-main-color);
        border-radius: 4px;
        font-size: 14px;
        font-family: Microsoft YaHei;
        font-weight: 400;
        color: #ffffff;
        .svg-icon {
          margin-right: 7px;
        }
        &.limit-disabled {
          cursor: no-drop;
          opacity: 0.5;
        }
      }

      .upload-layout-card {
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        line-height: 1;
        align-items: center;
        justify-content: center;
        background-color: rgba(46, 129, 255, 0.04);
        .svg-icon {
          margin-bottom: 10px;
          opacity: 0.5;
        }
        > div {
          font-size: 14px;
          font-family:
            PingFang SC-Regular,
            PingFang SC;
          font-weight: 400;
          color: rgba(0, 98, 233, 0.5);
        }
        &.limit-disabled {
          cursor: no-drop;
          opacity: 0.5;
        }
      }
    }
    :deep(.el-upload-list) {
      margin: 0;
      .el-upload-list__item {
        transition: none;
        .upload-layout-list_item {
          display: flex;
          align-items: center;
          padding: 0 6px;
          .file-type-img {
            width: 14px;
            margin: 0 6px 0 6px;
          }
          .label {
            width: calc(100% - 6px - 6px - 14px - 30px);
          }
          .close {
            margin-left: auto;
          }
          &.item-view {
            .label {
              width: calc(100% - 6px - 6px - 14px);
            }
          }
        }
        .upload-layout-card_item {
          width: 100%;
          height: 100%;
          position: relative;
          .img {
            width: 100%;
            height: 100%;
          }
          .video {
            width: 100%;
            height: 100%;
            object-fit: fill;
          }
          .file {
            width: 100%;
            height: 100%;
          }
          .del {
            position: absolute;
            top: 0;
            right: 0;
            z-index: 2;
            width: 20px;
            height: 20px;
          }
        }
      }
    }
  }
  &.czr-form-column-upload-com_view,
  &.czr-form-column-upload-com_limit-no-upload {
    .el-upload-com {
      :deep(.el-upload) {
        display: none;
      }
    }
  }
  &.czr-form-column-upload-com_card {
    .el-upload-com {
      :deep(.el-upload) {
        width: $uploadWidth;
        height: $uploadHeight;
      }
      :deep(.el-upload-list) {
        .el-upload-list__item {
          width: $uploadWidth;
          height: $uploadHeight;
        }
      }
    }
  }
}
</style>
