<!-- 图片上传组件 -->
<template>
  <!-- 实际的上传组件（隐藏） -->
  <div style="display: none">
    <el-upload
      ref="uploadRef"
      v-model:file-list="fileList"
      :before-upload="handleBeforeUpload"
      :action="props.action"
      :headers="props.headers"
      :data="props.data"
      :name="props.name"
      :on-success="handleSuccessFile"
      :on-error="handleError"
      :accept="props.accept"
      :limit="props.limit"
    />
  </div>

  <!-- 自定义的显示区域 -->
  <div class="custom-upload-list">
    <!-- 已上传的图片列表 -->
    <div v-for="(path, index) in valFileList" :key="index" class="custom-upload-item">
      <img class="upload-thumbnail" :src="path" alt="" />
      <div class="upload-actions">
        <span class="action-item preview" @click="previewImg(path)">
          <el-icon><zoom-in /></el-icon>
        </span>
        <span v-if="props.showDelBtn" class="action-item delete" @click="handleRemove(path)">
          <el-icon><Delete /></el-icon>
        </span>
      </div>
    </div>

    <!-- 上传按钮 -->
    <div
      v-if="valFileList.length < props.limit && props.showUploadBtn"
      class="custom-upload-trigger"
      @click="triggerUpload"
    >
      <el-icon><Plus /></el-icon>
    </div>
  </div>

  <!-- 图片预览组件 -->
  <el-image-viewer
    v-if="viewVisible"
    :zoom-rate="1.2"
    :initialIndex="initialIndex"
    :url-list="viewFileList"
    @close="closePreview"
  />
</template>
<script setup lang="ts">
import { UploadRawFile, UploadUserFile, UploadFile } from "element-plus";
import FileAPI from "@/api/file";
import { getToken } from "@/utils/auth";
import { ResultEnum } from "@/enums/ResultEnum";

const emit = defineEmits(["update:modelValue", "change"]);

const props = defineProps({
  /**
   * 文件路径集合
   */
  modelValue: {
    type: [Array, String],
    default: () => [],
  },
  /**
   * 上传地址
   */
  action: {
    type: String,
    default: FileAPI.uploadUrl,
  },
  /**
   * 请求头
   */
  headers: {
    type: Object,
    default: () => {
      return {
        Authorization: getToken(),
      };
    },
  },
  /**
   * 请求携带的额外参数
   */
  data: {
    type: Object,
    default: () => {
      return {};
    },
  },
  /**
   * 上传文件的参数名
   */
  name: {
    type: String,
    default: "file",
  },
  /**
   * 文件上传数量限制
   */
  limit: {
    type: Number,
    default: 10,
  },
  /**
   * 是否显示删除按钮
   */
  showDelBtn: {
    type: Boolean,
    default: true,
  },
  /**
   * 是否显示上传按钮
   */
  showUploadBtn: {
    type: Boolean,
    default: true,
  },
  /**
   * 单张图片最大大小,单位MB
   */
  maxSize: {
    type: Number,
    default: 10,
  },
  /**
   * 上传文件类型
   */
  accept: {
    type: String,
    default: "image/*",
  },
  /**
   * 支持的文件类型,默认支持所有图片格式
   * eg:['png','jpg','jpeg','gif']
   */
  supportFileType: {
    type: Array<string>,
    default: () => [],
  },

  /**
   * 是否同步删除
   */
  isSyncDelete: {
    type: Boolean,
    default: true,
  },
  /**
   * 自定义样式
   */
  style: {
    type: Object,
    default: () => {
      return {
        width: "130px",
        height: "130px",
      };
    },
  },
});

const viewVisible = ref(false);
const initialIndex = ref(0);

const fileList = ref([] as UploadUserFile[]);
const valFileList = ref([] as string[]);
const viewFileList = ref([] as string[]);

// 添加一个ref来引用el-upload组件
const uploadRef = ref();

watch(
  () => props.modelValue,
  (newVal) => {
    if (typeof newVal === "string" && !newVal) {
      fileList.value = [];
      viewFileList.value = [];
      valFileList.value = [];
      return;
    }
    const modelValue = typeof newVal === "string" ? [newVal] : (newVal as string[]);
    const filePaths = fileList.value.map((file) => file.url);
    // 监听modelValue文件集合值未变化时，跳过赋值
    if (
      filePaths.length > 0 &&
      filePaths.length === modelValue.length &&
      filePaths.every((x) => modelValue.some((y) => y === x)) &&
      modelValue.every((y) => filePaths.some((x) => x === y))
    ) {
      return;
    }

    if (!modelValue || modelValue.length <= 0) {
      fileList.value = [];
      viewFileList.value = [];
      valFileList.value = [];
      return;
    }

    fileList.value = modelValue.map((filePath) => {
      return { url: filePath } as UploadUserFile;
    });
    valFileList.value = modelValue;
  },
  { immediate: true }
);

/**
 * 上传成功回调
 *
 * @param options
 */
const handleSuccessFile = (response: any, file: UploadFile) => {
  if (response.code === ResultEnum.SUCCESS) {
    ElMessage.success("上传成功");
    valFileList.value.push(response.data.url);
    if (props.limit === 1) {
      emit("update:modelValue", response.data.url);
      emit("change", response.data.url);
    } else {
      emit("update:modelValue", valFileList.value);
      emit("change", valFileList.value);
    }
    return;
  } else {
    ElMessage.error(response.msg || "上传失败");
  }
};

const handleError = (error: any) => {
  ElMessage.error("上传失败");
};

/**
 * 删除图片
 */
function handleRemove(path: string) {
  if (path) {
    if (props.isSyncDelete) {
      FileAPI.deleteByPath(path).then(() => {
        valFileList.value = valFileList.value.filter((x) => x !== path);
        // 删除成功回调
        if (props.limit === 1) {
          emit("update:modelValue", "");
          emit("change", "");
        } else {
          emit("update:modelValue", valFileList.value);
          emit("change", valFileList.value);
        }
      });
    } else {
      valFileList.value = valFileList.value.filter((x) => x !== path);
      // 删除成功回调
      if (props.limit === 1) {
        emit("update:modelValue", "");
        emit("change", "");
      } else {
        emit("update:modelValue", valFileList.value);
        emit("change", valFileList.value);
      }
    }
  }
}

/**
 * 限制用户上传文件的格式和大小
 */
function handleBeforeUpload(file: UploadRawFile) {
  // 限制文件大小
  if (file.size > props.maxSize * 1024 * 1024) {
    ElMessage.warning("上传图片不能大于" + props.maxSize + "M");
    return false;
  }
  // 判断文件类型
  // 获取文件后缀名
  const fileExt = file.name.split(".").pop();
  if (!fileExt) {
    ElMessage.warning("上传图片格式错误,支持的文件类型:" + props.supportFileType.join(","));
    return false;
  }
  // 给文件后缀名转换为小写
  const lowerCaseFileExt = fileExt.toLowerCase();
  // 判断文件后缀名是否在支持的文件类型中
  if (props.supportFileType.length > 0) {
    let isSupport = false;
    props.supportFileType.forEach((type) => {
      if (type.toLowerCase() === lowerCaseFileExt) {
        isSupport = true;
      }
    });
    if (!isSupport) {
      ElMessage.warning("上传图片格式错误,支持的文件类型:" + props.supportFileType.join(","));
      return false;
    }
  }
  return true;
}

/**
 * 预览图片
 */
const previewImg = (path: string) => {
  viewFileList.value = fileList.value.map((file) => file.url!);
  initialIndex.value = fileList.value.findIndex((file) => file.url === path);
  viewVisible.value = true;
};

/**
 * 关闭预览
 */
const closePreview = () => {
  viewVisible.value = false;
};

// 修改triggerUpload方法
const triggerUpload = () => {
  // 通过ref直接访问el-upload组件内的input元素
  const uploadEl = uploadRef.value.$el.querySelector(".el-upload__input");
  if (uploadEl) {
    uploadEl.click();
  }
};
</script>
<style lang="scss" scoped>
.custom-upload-list {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.custom-upload-item {
  position: relative;
  width: v-bind("props.style.width");
  height: v-bind("props.style.height");
  overflow: hidden;
  border-radius: 6px;

  .upload-thumbnail {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .upload-actions {
    position: absolute;
    top: 0;
    left: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    background-color: rgb(0 0 0 / 50%);
    opacity: 0;
    transition: opacity 0.3s;

    &:hover {
      opacity: 1;
    }

    .action-item {
      padding: 8px;
      color: #fff;
      cursor: pointer;

      &:hover {
        color: var(--el-color-primary);
      }
    }
  }
}

.custom-upload-trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: v-bind("props.style.width");
  height: v-bind("props.style.height");
  cursor: pointer;
  background-color: rgb(255 254 254 / 50%);
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  transition: var(--el-transition-duration);

  &:hover {
    color: var(--el-color-primary);
    border-color: var(--el-color-primary);
  }

  .el-icon {
    font-size: 20px;
    color: #999;
  }

  &:hover .el-icon {
    color: var(--el-color-primary);
  }
}
</style>
