<template>
  <div class="z-upload" :class="[size]">
    <ul class="el-upload-list el-upload-list--picture-card">
      <!-- 已上传图片列表，支持拖拽改变顺序 -->
      <drag-field v-model="imageList" @change="onDragFile" :draggable="draggable">
        <div v-for="(url, index) in imageList" :key="index" class="el-upload-list__item-wrapper">
          <li class="el-upload-list__item">
            <template v-if="type === 'all'">
              <img v-if="isImage(url)" :src="url" alt class="el-upload-list__item-thumbnail" />
              <div v-else alt class="el-upload-list__item-thumbnail--file" :class="url | fileTypeFilter"></div>
            </template>
            <template v-else>
              <img v-if="type === 'image'" :src="url" alt class="el-upload-list__item-thumbnail" />
              <div v-else alt class="el-upload-list__item-thumbnail--file" :class="url | fileTypeFilter"></div>
            </template>
            <div v-if="!cornerClose" class="el-upload-list__item-actions">
              <span v-if="isImage(url)" class="el-upload-list__item-preview" @click="onPreview(url, index)">
                <i class="el-icon-zoom-in"></i>
              </span>
              <span v-else class="el-upload-list__item-preview" @click="onDownload(url)">
                <i class="el-icon-download"></i>
              </span>
              <span v-if="!disabled" class="el-upload-list__item-delete" @click="onRemove(index)">
                <i class="el-icon-delete"></i>
              </span>
            </div>
            <div v-else class="el-upload-list__item-actions">
              <div v-if="isImage(url)" class="block-preview" @click="onPreview(url, index)">
                <i class="el-icon-view"></i>
              </div>
              <div v-else class="block-download" @click="onDownload(url)">
                <i class="el-icon-download"></i>
              </div>
            </div>
          </li>
          <div v-if="cornerClose && !disabled" class="corner-close" @click="onRemove(index)">
            <i class="el-icon-close"></i>
          </div>
        </div>
      </drag-field>
      <!-- 默认上传按钮 -->
      <el-upload
        v-if="!limit || imageList.length < limit"
        :action="action"
        :data="data"
        :name="name"
        :headers="headers"
        :file-list="fileList"
        :disabled="disabled"
        :multiple="multiple"
        :limit="limit"
        :drag="drag"
        :show-file-list="false"
        list-type="picture-card"
        :on-exceed="onExceed"
        :on-success="onSuccess"
        :on-error="onError"
        :before-upload="onBeforeUpload"
        :http-request="isCustomRequest ? onHttpRequest : undefined"
      >
        <i class="el-icon-plus"></i>
      </el-upload>
    </ul>
    <!-- 图片查看器 -->
    <slot
      v-if="$scopedSlots['image-viewer'] || $slots['image-viewer']"
      name="image-viewer"
      :show="isImageViewerShow"
      :index="defaultIndex"
      :list="filteredImageList"
      :close="closeViewer"
      :open="openViewer"
    ></slot>
    <template v-else>
      <el-image-viewer v-if="isImageViewerShow" :initial-index="defaultIndex" :on-close="closeViewer" :url-list="filteredImageList" />
    </template>
  </div>
</template>

<script>
import ElImageViewer from './image-viewer';

const imgExtensions = ['.bmp', '.jpg', '.png', '.tif', '.gif', '.pcx', '.tga', '.exif', '.fpx', '.svg'];

// 获取文件类型
const getFileType = function(url) {
  return `${/\.[^.]+$/.exec(url)[0]}`.toLowerCase();
};

export default {
  name: 'Upload',
  components: {
    ElImageViewer,
    DragField: {
      props: { value: Array, draggable: Boolean },
      render(h) {
        // 是否开启可拖拽改变图片顺序，需要项目安装vuedraggable
        if (this.draggable) {
          return h(
            'draggable',
            {
              props: { value: this.value },
              on: {
                input: e => this.$emit('input', e),
                change: e => this.$emit('change', e),
              },
            },
            this.$slots.default,
          );
        } else {
          return h('div', this.$slots.default);
        }
      },
    },
  },
  props: {
    value: String,
    // 是否禁用
    disabled: Boolean,
    // 是否支持多选文件
    multiple: Boolean,
    // 最大上传数量
    limit: Number,
    // 是否启用拖拽上传
    drag: Boolean,
    // 是否启用拖拽改变顺序
    draggable: Boolean,
    // 上传地址
    action: String,
    // 自定义请求头
    headers: {
      type: Object,
      default: () => ({}),
    },
    // 删除图片时确认提示
    deleteConfirm: Boolean,
    // 上传时附带的额外参数
    data: {
      type: Object,
      default: () => ({}),
    },
    // 上传文件字段名
    name: {
      type: String,
      default: 'file',
    },
    // 返回值处理
    responseFilter: Function,
    // 上传文件之前的钩子
    beforeUpload: Function,
    // 上传类型
    type: {
      type: String,
      default: 'all', // all, image, file
    },
    // 上传文件类型限制
    fileType: String,
    // 大小
    size: {
      type: String,
      default: 'large', // mini, small, large
    },
    // HTTP请求库
    http: Function,
    // 自定义请求方法
    httpRequest: Function,
    // 是否开启角标删除按钮
    cornerClose: Boolean,
  },
  data() {
    return {
      fileList: [], // ElementUI Upload 默认文件列表
      imageList: [], // 图片url列表
      isImageViewerShow: false, // 图片预览显示状态
      currentIndex: 0, // 当前预览图片下标
    };
  },
  computed: {
    // 请求自定义
    isCustomRequest() {
      return Boolean(this.http) || Boolean(this.httpRequest) || Boolean(this.$axios);
    },
    // 预览图片列表，去除文件
    filteredImageList() {
      if (this.type === 'all') {
        return this.imageList.filter(url => imgExtensions.some(key => `${url}`.toLowerCase().includes(key)));
      }
      return this.imageList;
    },
    // 预览文件列表默认下标
    defaultIndex() {
      if (this.type === 'all') {
        const currentUrl = this.imageList[this.currentIndex];
        return this.filteredImageList.findIndex(url => url === currentUrl);
      } else {
        return this.currentIndex;
      }
    },
  },
  filters: {
    // 处理文件拓展名对应的class
    fileTypeFilter(val) {
      const fileType = getFileType(val);
      if (['.doc', '.docx'].includes(fileType)) {
        return 'word';
      }
      if (['.xls', '.xlsx', '.csv'].includes(fileType)) {
        return 'excel';
      }
      if (['.ppt', '.pptx'].includes(fileType)) {
        return 'ppt';
      }
      if (['.pdf'].includes(fileType)) {
        return 'pdf';
      }
      if (['.zip', '.rar', '.7z'].includes(fileType)) {
        return 'zip';
      }
      return '';
    },
  },
  watch: {
    value: {
      handler(val) {
        if (val) {
          let imageList = [];
          let fileList = [];
          val.split(',').forEach(url => {
            imageList.push(url);
            fileList.push({ url });
          });
          this.imageList = imageList;
          this.fileList = fileList;
        } else {
          this.fileList = [];
          this.imageList = [];
        }
      },
      immediate: true,
    },
  },
  methods: {
    // 判断url是否为图片
    isImage(url) {
      return imgExtensions.some(key => `${url}`.toLowerCase().includes(key));
    },
    // 删除图片
    onRemove(index) {
      if (this.deleteConfirm) {
        this.$confirm('确定删除当前图片吗?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
        })
          .then(() => {
            this.removeImage(index);
          })
          .catch(() => {});
      } else {
        this.removeImage(index);
      }
    },
    // 移除图片
    removeImage(index) {
      this.imageList.splice(index, 1);
      this.fileList.splice(index, 1);
      this.$emit('input', this.imageList.join(','));
    },
    // 预览图片
    onPreview(url, index) {
      this.currentIndex = index;
      this.$nextTick(this.openViewer);
    },
    // 打开图片预览
    openViewer() {
      this.isImageViewerShow = true;
    },
    // 关闭图片预览
    closeViewer() {
      this.isImageViewerShow = false;
    },
    // 下载文件
    onDownload(url) {
      window.open(url);
    },
    // 上传之前校验
    onBeforeUpload(file) {
      if (this.type === 'all') {
        if (this.beforeUpload) {
          return this.beforeUpload(file);
        }
        return true;
      }
      const fileType = getFileType(file.name);
      if (this.type === 'image') {
        if (!`${file.type}`.toLowerCase().includes('image')) {
          this.$message.warning('请上传图片');
          return false;
        }
      } else if (this.type === 'file') {
        if (`${file.type}`.toLowerCase().includes('image') || (this.fileType && !fileType.includes(this.fileType))) {
          this.$message.warning(`请上传${this.fileType || ''}文件`);
          return false;
        }
      }
      if (this.beforeUpload) {
        return this.beforeUpload(file);
      }
      return true;
    },
    // 自定义上传
    onHttpRequest(option) {
      const formData = new FormData();
      if (option.data) {
        Object.keys(option.data).forEach(key => {
          formData.append(key, option.data[key]);
        });
      }
      formData.append(option.filename, option.file, option.file.name);
      if (this.httpRequest) {
        this.httpRequest({ ...option, formData });
      } else {
        const request = this.http || this.$axios;
        if (request && request.post) {
          request
            .post(option.action, formData, { headers: option.headers })
            .then(response => {
              this.onSuccess(response.data);
            })
            .catch(error => {
              this.onError(error);
            });
        }
      }
    },
    // 上传失败
    onError(err) {
      this.$message.error('上传失败, 系统异常！');
    },
    // 上传成功
    onSuccess(response) {
      if (response.success) {
        const { result } = response || {};
        if (this.responseFilter) {
          this.imageList.push(this.responseFilter(response));
        } else if (result) {
          this.imageList.push(result[0]);
        }
        this.$emit('input', this.imageList.join(','));
      } else {
        if (response.businessException) {
          this.$message.error('上传失败!' + response.message);
        } else {
          this.$message.error('上传失败!');
        }
      }
      this.$emit('upload', response);
    },
    // 文件超出个数限制
    onExceed() {
      this.$message.warning('文件个数超出限制！');
    },
    // 拖动图片
    onDragFile() {
      this.$emit('input', this.imageList.join(','));
    },
  },
};
</script>

<style lang="scss">
.z-upload {
  &,
  .el-upload-list.el-upload-list--picture-card {
    display: inline-flex;
    flex-wrap: wrap;
  }
  /* 当上传内容为文件时，默认显示对应的图标 */
  .el-upload-list__item-thumbnail--file {
    height: 100%;
    width: 100%;
    background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='60' height='60'%3E%3Cpath d='M176 160h304a472.432 472.432 0 0 1 64 64l128 208a64 64 0 0 1-64 64H176a64 64 0 0 1-64-64V224a64 64 0 0 1 64-64z' fill='%23A1CBFE'/%3E%3Cpath d='M176 288h688q64 0 64 64v432q0 64-64 64H176q-64 0-64-64V352q0-64 64-64z' fill='%234799FE'/%3E%3Cpath d='M336 688h368q32 0 32 32t-32 32H336q-32 0-32-32t32-32z' fill='%23FFF'/%3E%3Cpath d='M832 336h16q32 0 32 32v16q0 32-32 32h-16q-32 0-32-32v-16q0-32 32-32z' fill='%23A1CBFE'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: center;
    background-size: 60%;
    &.word {
      background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='81' height='81'%3E%3Cpath d='M665.6 0l256 256v716.8a51.2 51.2 0 0 1-51.2 51.2H153.6a51.2 51.2 0 0 1-51.2-51.2V51.2A51.2 51.2 0 0 1 153.6 0zM284.16 360.96l94.72 330.24h56.32l64-248.32h2.56l64 248.32h56.32l94.72-330.24h-61.44l-61.44 250.88h-2.56l-64-250.88h-56.32L409.6 611.84h-2.56L345.6 360.96z' fill='%234E97FF'/%3E%3Cpath d='M665.6 0l256 256h-256z' fill='%23A4D2FF'/%3E%3C/svg%3E");
    }
    &.excel {
      background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='81' height='81'%3E%3Cpath d='M139.456 0c-8.896 0-22.24 4.48-35.616 13.344-13.344 8.928-13.344 26.72-13.344 35.616v921.6c0 13.376 4.48 26.72 13.344 35.648 13.376 13.344 26.72 17.792 35.616 17.792h752.448c13.344 0 26.688-4.48 35.616-13.344 8.896-8.928 8.896-22.272 8.896-35.616V289.376L651.488 0h-512z' fill='%235ACC9B'/%3E%3Cpath d='M936.416 289.376h-231.52c-13.344 0-26.72-4.448-35.616-13.344-8.896-8.896-13.344-22.272-13.344-35.616V0l280.48 289.376z' fill='%23BDEBD7'/%3E%3Cpath d='M477.824 538.72L362.08 378.432h80.128l75.712 115.776 80.128-115.776h75.68L557.984 538.72l124.64 173.632H598.08L517.952 587.68l-84.608 124.672h-80.16z' fill='%23FFF'/%3E%3C/svg%3E");
    }
    &.ppt {
      background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='81' height='81'%3E%3Cpath d='M880.085 1017.43h-736.17a58.539 58.539 0 0 1-58.582-58.283V58.283C85.333 26.368 111.445 0 143.915 0h513.024l281.728 288.768v670.379c0 32.298-26.112 58.282-58.582 58.282z' fill='%23FF9540'/%3E%3Cpath d='M644.437 0a15.36 15.36 0 0 0-1.152 6.059v229.888c0 32.554 27.179 59.434 60.928 59.434h234.454v-.384L645.632 0h-1.195z' fill='%23FFF' fill-opacity='.496'/%3E%3Cpath d='M511.403 573.355h-55.51v115.882h-75.178V361.045h137.898c80.086 2.219 121.6 36.608 124.63 103.894 1.152 72.149-42.667 108.373-131.84 108.373zm-6.016-154.368h-49.494v97.92h49.494c38.912-.768 58.922-17.195 60.074-48.982-1.152-31.744-21.162-47.829-60.074-48.938z' fill='%23FFF'/%3E%3C/svg%3E");
    }
    &.pdf {
      background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='81' height='81'%3E%3Cpath d='M136.533 0a49.12 49.12 0 0 0-35.84 15.36C91.307 25.6 85.333 38.4 85.333 51.2v921.6a49.12 49.12 0 0 0 15.36 35.84 50.547 50.547 0 0 0 35.84 15.36h750.933a49.12 49.12 0 0 0 35.84-15.36 50.547 50.547 0 0 0 15.36-35.84V290.133L648.533 0z' fill='%23FF5562'/%3E%3Cpath d='M938.666 290.133H699.733a52.493 52.493 0 0 1-51.2-51.2V0z' fill='%23FFBBC0'/%3E%3Cpath d='M708.266 865.333c-53.76 0-101.6-92.213-127.146-152-42.667-17.92-89.6-34.133-134.827-45.226-40.106 26.56-107.52 65.76-159.626 65.76-32.427 0-55.467-16.214-64-44.374-6.827-23.04-.854-39.253 5.973-47.786q20.48-28.16 84.48-28.16c34.133 0 77.653 5.973 126.293 17.92a762.026 762.026 0 0 0 91.307-75.094c-12.8-59.733-26.453-156.16 8.533-200.533 17.067-21.333 43.52-28.16 75.094-18.773 34.986 10.24 47.786 31.573 52 47.786 14.506 58.027-52 136.534-97.334 182.667 10.24 40.107 23.04 81.92 39.254 120.32 65.066 28.96 141.813 71.627 150.4 118.56 3.413 16.213-1.707 31.573-14.507 44.373-11.094 9.333-23.04 14.507-35.84 14.507zm-79.36-129.706C661.334 801.333 692 832 708.267 832c2.56 0 5.974-.853 11.094-5.12 5.973-5.973 5.973-10.24 5.12-13.653-3.414-17.067-30.667-45.227-95.573-77.654zm-315.733-87.894c-41.813 0-53.76 10.24-57.173 14.507-.853 1.707-4.267 5.973-.853 17.92 2.56 10.24 9.333 20.48 31.573 20.48 27.307 0 66.56-15.36 112.64-42.667-33.333-6.826-62.293-10.24-86.187-10.24zm168.96-5.12a921.586 921.586 0 0 1 81.92 27.307c-9.333-24.747-17.066-50.347-23.893-75.093-18.773 16.213-38.4 32.426-58.027 47.786zM588 366.933c-9.333 0-16.213 3.414-22.187 10.24-17.92 22.187-19.626 78.507-5.973 150.187 52-55.467 80.213-106.667 73.333-133.973-.853-4.267-4.266-16.214-28.16-23.04A46.413 46.413 0 0 0 588 366.933z' fill='%23FFF'/%3E%3C/svg%3E");
    }
    &.zip {
      background-image: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='81' height='81'%3E%3Cpath d='M61.156 685.511h910.222V921.6c0 28.444-24.178 52.622-52.622 52.622H113.778c-28.445 0-52.622-24.178-52.622-52.622V685.511z' fill='%2389D543'/%3E%3Cpath d='M61.156 352.711h910.222v332.8H61.156z' fill='%23FA6A68'/%3E%3Cpath d='M113.778 64h804.978c28.444 0 52.622 24.178 52.622 52.622v236.09H61.156v-236.09C61.156 88.178 85.333 64 113.778 64z' fill='%2360CEF8'/%3E%3Cpath d='M391.111 64H652.8v910.222H391.111z' fill='%23FDB84B'/%3E%3Cpath d='M760.889 442.311v-28.444H270.222v193.422H760.89V442.31zm-35.556 8.533v122.312H305.778V450.844h419.555z' fill='%23FFF'/%3E%3C/svg%3E");
    }
  }
  /* 上传项外部类，用于撑开布局，显示角标按钮 */
  .el-upload-list__item-wrapper {
    position: relative;
    display: inline-block;
    margin-right: 10px;
  }
  /* 自定义角标删除按钮样式 */
  .el-upload-list__item {
    margin-right: 0 !important;
  }
  /* 自定义角标删除按钮样式 */
  .corner-close {
    position: absolute;
    top: 0;
    right: 0;
    transform: translateX(50%) translateY(-50%);
    height: 30px;
    width: 30px;
    min-width: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(lighten($red, 15%), 0.5);
    color: #fff;
    border-radius: 50%;
    z-index: 20;
    font-size: 16px;
    cursor: pointer;
    &:hover {
      background-color: $red;
    }
  }
  /* 修改卡片样式 */
  .el-upload--picture-card {
    line-height: 1.5;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  /* 修改图片按钮样式 */
  .el-upload-list--picture-card .el-upload-list__item-actions {
    display: flex;
    align-items: center;
    justify-content: space-around;
    .el-upload-list__item-preview,
    .el-upload-list__item-delete {
      display: inline-flex;
      align-items: center;
      justify-content: center;
    }
    span + span {
      margin-left: 0;
    }
    &::after {
      display: none;
    }
    .block-preview,
    .block-download {
      height: 100%;
      width: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
    }
  }
  /* 修改拖拽上传的默认样式 */
  .el-upload-dragger {
    height: 100%;
    width: 100%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: none;
    background-color: transparent;
    &.is-dragover {
      border-color: transparent;
    }
  }
  /* 修改图片上传组件卡片模式默认样式 */
  &.mini {
    .el-upload--picture-card,
    .el-upload-list--picture-card .el-upload-list__item {
      height: 60px;
      width: 60px;
    }
    .el-upload--picture-card i {
      font-size: 20px;
    }
    .corner-close {
      height: 20px;
      width: 20px;
      min-width: 20px;
      font-size: 12px;
    }
  }
  &.small {
    .el-upload--picture-card,
    .el-upload-list--picture-card .el-upload-list__item {
      height: 100px;
      width: 100px;
    }
    .corner-close {
      height: 24px;
      width: 24px;
      min-width: 24px;
      font-size: 14px;
    }
  }
  /* 图片缩略图 */
  .el-upload-list--picture-card .el-upload-list__item-thumbnail {
    object-fit: cover;
  }
}
</style>
