vue3 ,element-plus,js多文件上传组件

预览:

代码: 

<template>
  <el-upload v-bind="getBindValue" multiple v-model:file-list="fileList" class="multiple_uploader" action="#"
    :before-upload="beforeUpload" :on-change="handleChange" :on-exceed="handleExceed">

    <!-- 自定义上传按钮 -->
    <slot v-if="hasBtn" name="btnSlot"></slot>
    <el-button v-else class="upload_btn" :icon="Plus">Upload Files</el-button>

    <template #tip>
      <div v-if="finalTips" class="el-upload__tip">{{ finalTips }}</div>
    </template>
  </el-upload>
</template>

<script setup>
import { ref, useAttrs, nextTick, computed, toRefs, onMounted, watchEffect, useSlots } from 'vue'
import { ElMessage } from 'element-plus'
import { multipleUpload } from '@/api/common'
import { ALLOW_CONTENT_TYPE_DICT, ONE_Megabit } from './contants'
import { Plus } from '@element-plus/icons-vue'
import { cloneDeep, isEmpty, isString } from 'lodash-es'

const props = withDefaults(defineProps(), {
  accept: '.jpg,.jpeg,.png,.webp',
  disabled: false,
  drag: false,
  fileSize: 5, // MB
  isPrivate: false,
  isVideo: false,
  limit: 1,
  listType: 'picture-card',
  showRemoveIcon: false,
  splitSymbol: ',',
  tips: ''
})

// 外部传入的文件 url
// 如果是多个文件, 用逗号分割;
// 私域文件只有后面半截, 需要一个拼接流程
const model = defineModel({ required: true })

const modelIsString = computed(() => isString(model.value))
const initialModelValue = cloneDeep(model.value)

const slots = useSlots()
const hasBtn = computed(() => slots?.btnSlot)

const attrs = useAttrs()
const getBindValue = computed(() => {
  const obj = {
    ...attrs,
    ...props,
  }
  return obj
})

const { accept, fileSize, isVideo, limit, listType, tips, splitSymbol } = toRefs(getBindValue.value)
const isText = computed(() => listType?.value === 'text')
const fileTypeText = computed(() => isText.value ? '文件' : isVideo.value ? '视频' : '图片')
const emits = defineEmits(['success'])

const fileList = ref([
  // {
  //   name: 'food.jpeg',
  //   url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100',
  // }
])

const contentTypeArr = computed(() => {
  let tmpArr = []
  if (accept.value) {
    const acceptArr = accept.value.split(',').map(str => str.toLowerCase())
    tmpArr = acceptArr.map((x) => ALLOW_CONTENT_TYPE_DICT[x])
  }

  return tmpArr
})

// 默认提示, 用户可以覆盖
const finalTips = computed(() => {
  console.log(777, tips);

  // 如果存在 tips
  if (tips?.value) {
    // 进一步判断是否为字符串
    if (isString(tips.value)) {
      return tips.value
    }

    if (accept.value === '*') {
      return `支持任意格式,所选${fileTypeText.value}大小不能超过 ${fileSize.value} MB,最多上传 ${limit.value} 个文件`
    }
    return `支持扩展名: ${accept.value},所选${fileTypeText.value}大小不能超过 ${fileSize.value} MB,最多上传 ${limit.value} 个文件`
  }

  return false
})

// 文件超出数量限制回调
const handleExceed = () => {
  ElMessage.error(`最多上传${limit.value}个文件!`)
}

// 限制文件数量
const handleChange = (uploadFile, uploadFiles) => {
  fileList.value = fileList.value.slice(-3)
}

// 判断文件类型
const checkFileType = (fileType) => {
  if (!contentTypeArr.value.includes(fileType)) {
    ElMessage.error(`类型不正确,接收类型为 ${accept.value}`)
    return true
  }

  return false
}

// 判断文件超大
const checkOverSize = (paramsSize) => {
  if (paramsSize / ONE_Megabit > fileSize.value) {
    ElMessage.error(`所选${fileTypeText.value}大小不能超过 ${fileSize.value} MB`)
    return true
  }

  return false
}

const beforeUpload = (rawFile) => {
  console.log(666, rawFile);
  doUpload(rawFile)
  // 文件限制大小
  if (checkOverSize(rawFile.size))
    return false
  // 文件限制类型
  if (accept.value === '*') {
    return true
  }
  else {
    if (checkFileType(rawFile.type))
      return false
  }

  // if (checkFileType(rawFile.type)) {
  //   ElMessage.error('Avatar picture must be JPG format!')
  //   return false
  // } else if (rawFile.size / 1024 / 1024 > 2) {
  //   ElMessage.error('Avatar picture size can not exceed 2MB!')
  //   return false
  // }
  return true
}

const doUpload = (file) => {
  multipleUpload(file).then(res => {
    if (res.code == 200) {
      handleUploadSuccess(res.data, file)
    }
  })
}

// 上传成功后的回调
const handleUploadSuccess = (res, uploadFile) => {

  const uid = uploadFile.uid
  const idx = fileList.value.findIndex(item => item?.uid === uid)
  const target = fileList.value.find(item => item?.uid === uid)

  if (target) {
    fileList.value.splice(idx, 1, { ...target, name: res.fileOriginName, url: res.url })
  } else {
    fileList.value.push({ name: res.fileOriginName, url: res.url })
  }

  nextTick(() => {
    model.value = cloneDeep(fileList.value)

    emits('success', uploadFile)
  })
}

watchEffect(() => {
  // 如果传入的值为空, 清掉 fileList, 这在新建表单的取消过程中会十分有用
  if (isEmpty(model.value)) {
    fileList.value = []
  }
})

// 初始传入的图片回显
onMounted(() => {
  const vStr = initialModelValue
  const vArr = initialModelValue
  if (modelIsString.value) {
    const urlList = vStr ? vStr.split(splitSymbol.value) : []
    fileList.value = urlList.map(
      (fullPath) => ({
        name: fullPath,
        url: fullPath,
        validUrl: fullPath,
      }),
    )
  }
  else {
    fileList.value = vArr && vArr.map(
      (item) => ({
        name: item.name,
        url: item.url,
        validUrl: item.url,
      }),
    )
  }
})
</script>

<style scoped lang="less">
.multiple_uploader {
  width: 100%;

  .upload_btn {
    color: #4E88FF;
    background: #F9F9F9;
    border: 1px solid #DDD;
  }
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值