index.vue 4.4 KB
<script setup lang="ts">
/**
 * 多图上传组件
 * @module Photowall
 */
import { ref, reactive } from "vue";
import { useLoginStore } from "@/pinia/login";
import { ElMessage } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import type {
  UploadFile,
  ElFile,
  ElUploadProgressEvent,
} from "element-plus/es/components/upload/src/upload.type";
/**
 * @description 组件属性
 * @prop {string | null} tagName 标签
 * @prop {string} moduleName 上传接口需要传的module值
 * @prop {number} limit 图片数量上限
 * @prop {string} tip 上传须知
 * @prop {array} fileList 默认显示的图片
 * @prop {array<string>} formats 允许上传的图片格式
 * @prop {number | null} maxSize 允许上传的最大图片尺寸 default = 1 means 1MB
 * @prop {boolean} r 是否只读(用来判断+按钮是否显示)
 */
interface Props {
  tagName?: string;
  moduleName?: string;
  limit?: number;
  maxSize?: number;
  formats?: string[];
  tip?: string;
  fileList?: string[];
  r?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
  tagName: "",
  moduleName: "test",
  limit: 9,
  maxSize: 1,
  r: false,
  fileList: () => [],
  formats: () => [
    "image/jpeg",
    "image/gif",
    "image/png",
    "image/x-icon",
    "image/svg+xml",
    "image/webp",
  ],
  tip: "最多可以上传9张图片,单张图片大小不可超过1MB",
});
const dialogImageUrl = ref("");
const dialogVisible = ref(false);
/**
 * @description 上传图片接口配置
 */
const uploadSettings = reactive({
  uploadUrl: `${import.meta.env.VITE_BASE_URL}upload/uploadPicSource?module=${
    props.moduleName
  }`,
  headers: {
    type: "formData",
    "X-Token": useLoginStore()?.getToken,
  },
});
/**
 * @method handlePictureCardPreview
 * @description 已上传图片大图预览
 * @param {UploadFile} file 文件对象
 */
const handlePictureCardPreview = (file: UploadFile) => {
  dialogImageUrl.value = file?.url;
  dialogVisible.value = true;
};
/**
 * @description 图片数量超上限提示
 */
const handleExceed = () => {
  ElMessage.error(`最多可以传${props.limit}张图片`);
};
/**
 * @method beforeAvatarUpload
 * @description 图片上传之前检查格式、最大尺寸
 * @param {ElFile} file 文件对象
 */
const beforeAvatarUpload = (file: ElFile) => {
  const isFormatAllowed = props.formats.includes(file.type);
  const isSizeAllowed = file.size / 1024 / 1024 < props.maxSize;
  if (!isFormatAllowed) {
    ElMessage.error("图片格式不支持");
  }
  if (!isSizeAllowed) {
    ElMessage.error("图片尺寸过大");
  }
  return isFormatAllowed && isSizeAllowed;
};
/**
 * @method handleAvatarSuccess
 * @description 图片上传成功
 * @param {object} res 接口返回信息
 * @param {object} file 当前文件
 * @param {object} fileList 上传成功的文件列表
 */
const handleAvatarSuccess = (
  res: ElUploadProgressEvent,
  file: ElFile,
  fileList: ElFile[]
) => {
  console.log(fileList);
  emit(
    "finish",
    fileList.map((item) => (item.response ? item.response.data : item.url)),
    props.tagName
  );
};
/**
 * @method handleRemove
 * @description 图片移除
 * @param {object} file 当前文件
 * @param {object} fileList 上传成功的文件列表
 */
const handleRemove = (file: ElFile, fileList: ElFile[]) => {
  emit(
    "finish",
    fileList.map((item) => (item.response ? item.response.data : item.url)),
    props.tagName
  );
};
const emit = defineEmits<{
  (e: "finish", images: string, tagName: string): void;
}>();
</script>

<template>
  <el-upload
    :action="uploadSettings.uploadUrl"
    :headers="uploadSettings.headers"
    :with-credentials="true"
    :limit="props.limit"
    name="multipartFile"
    list-type="picture-card"
    :file-list="props.fileList"
    :on-preview="handlePictureCardPreview"
    :on-exceed="handleExceed"
    :on-success="handleAvatarSuccess"
    :on-remove="handleRemove"
    :before-upload="beforeAvatarUpload"
    :class="props.r ? 'photo-wall' : ''"
  >
    <el-icon><plus /></el-icon>
  </el-upload>
  <div class="tip">{{ props.tip }} {{ props.r }}</div>
  <el-dialog v-model="dialogVisible" title="图片预览">
    <div style="text-align: center">
      <el-image
        :src="dialogImageUrl"
        fit="contain"
        style="height: 500px"
      ></el-image>
    </div>
  </el-dialog>
</template>

<style lang="scss">
// 开启只读属性,不显示+按钮
.photo-wall {
  .el-upload--picture-card {
    display: none;
  }
}
</style>