上传图片组件封装(可重新上传不改变顺序)

<template>
  <div class="image-uploader">
    <el-space :size="20" :wrap="multiple">
      <template v-if="multiple">
        <el-upload v-for="(url, index) in modelValue" drag :key="index" class="uploader-item" :action="action"
          :show-file-list="false" :before-upload="beforeUpload" :on-success="(res) => handleSuccess(res, index)"
          :disabled="disabled" ref="uploadRefs">
          <div v-if="url" class="preview-image" :style="imageStyle" @click.stop="">
            <el-image :src="url" fit="cover" :preview-src-list="[url]" :style="imageStyle" />
            <div v-if="!disabled" class="image-actions">
              <el-button type="primary" link @click.stop="triggerUpload(index)">重新上传</el-button>
              <el-button type="danger" link @click.stop="handleDelete(index)">删除</el-button>
            </div>
          </div>
          <div v-else class="upload-placeholder" :style="imageStyle">
            <icon-ep-plus />
            <div class="text-sm mt-2">{{ placeholders[index] || '上传图片' }}</div>
          </div>
        </el-upload>
        
        <el-upload v-if="showAddButton" class="uploader-item" drag :action="action" :show-file-list="false" :before-upload="beforeUpload"
          :on-success="handleNewUpload" :disabled="disabled">
          <div class="upload-placeholder" :style="imageStyle">
            <icon-ep-plus />
            <div class="text-sm mt-2">添加图片</div>
          </div>
        </el-upload>
      </template>

      <el-upload v-else class="uploader-item" drag :action="action" :show-file-list="false" :before-upload="beforeUpload"
        :on-success="handleSingleSuccess" :disabled="disabled" ref="singleUploadRef">
        <div v-if="modelValue" class="preview-image" :style="imageStyle" @click.stop="">
          <el-image :src="modelValue" fit="cover" :preview-src-list="[modelValue]" :style="imageStyle" />
          <div v-if="!disabled" class="image-actions">
            <el-button type="primary" link @click.stop="triggerSingleUpload">重新上传</el-button>
            <el-button type="danger" link @click.stop="handleDeleteSingle">删除</el-button>
          </div>
        </div>
        <div v-else class="upload-placeholder" :style="imageStyle">
          <icon-ep-plus />
          <div class="text-sm mt-2">{{ placeholder }}</div>
        </div>
      </el-upload>
    </el-space>
    <div class="el-upload__tip mt-1" v-if="!isTip">支持 jpg/png 格式,大小不超过 10MB</div>
  </div>
</template>

<script setup>
import { computed, ref } from 'vue'
import { ElMessage } from 'element-plus'
import { getUploadUrl } from '#/utils/config'

const action = computed(() => getUploadUrl())
const props = defineProps({
  modelValue: {
    type: [String, Array],
    default: () => ''
  },
  multiple: {
    type: Boolean,
    default: false
  },
  disabled: {
    type: Boolean,
    default: false
  },
  // action: {
  //   type: String,
  //   default: UploadUrl
  // },
  width: {
    type: [String, Number],
    default: 150
  },
  height: {
    type: [String, Number],
    default: 150
  },
  placeholder: {
    type: String,
    default: '上传图片'
  },
  placeholders: {
    type: Array,
    default: () => []
  },
  type: {
    type: String,
    default: ''
  },
  isTip: {
    type: Boolean,
    default: false
  },
  limit: {
    type: [String, Number],
    default: ''
  }
})

const emit = defineEmits(['update:modelValue', 'uploadImgCallback'])

const imageStyle = computed(() => {
  const width = typeof props.width === 'number' ? `${props.width}px` : props.width
  const height = typeof props.height === 'number' ? `${props.height}px` : props.height
  return {
    width,
    height
  }
})

// 计算属性:是否显示添加按钮
const showAddButton = computed(() => {
  // 如果设置了limit,则根据当前数量和limit比较
  if (props.limit) {
    return Array.isArray(props.modelValue) && props.modelValue.length < Number(props.limit);
  }
  // 如果没有设置limit,则始终显示添加按钮
  return true;
});
// 文件上传前校验
const beforeUpload = (file) => {
  const isImage = file.type.startsWith('image/')
  if (!isImage) {
    ElMessage.error('只能上传图片文件!')
    return false
  }

  const isLt2M = file.size / 1024 / 1024 < 10
  if (!isLt2M) {
    ElMessage.error('图片大小不能超过 2MB!')
    return false
  }
  return true
}

// 单图片上传成功
const handleSingleSuccess = (res) => {
  emit('update:modelValue', `${res.data.host}/${res.data.url}`);
  if (props.type == 'businessLicense') {
    emit('uploadImgCallback', `${res.data.host}/${res.data.url}`)
  }
}

// 多图片上传成功
const handleSuccess = (res, index) => {
  const newValue = [...props.modelValue]
  newValue[index] = `${res.data.host}/${res.data.url}`
  emit('update:modelValue', newValue)
  if (props.type == 'idCardImages') {
    emit('uploadImgCallback', `${res.data.host}/${res.data.url}`)
  }
}

// 新增图片上传成功
const handleNewUpload = (res) => {
  const newValue = Array.isArray(props.modelValue) ? [...props.modelValue] : []
  newValue.push(`${res.data.host}/${res.data.url}`)
  emit('update:modelValue', newValue)
  if (props.type === 'idCardImages') {
    emit('uploadImgCallback', `${res.data.host}/${res.data.url}`)
  }
}

// 删除多图片中的某一张
const handleDelete = (index) => {
  const newValue = [...props.modelValue]
  newValue.splice(index, 1)
  emit('update:modelValue', newValue)
}

// 删除单图片
const handleDeleteSingle = () => {
  emit('update:modelValue', '')
}

// 添加ref用于获取上传组件实例
const uploadRefs = ref()
const singleUploadRef = ref(null)

// 触发多图片中某一项的上传
const triggerUpload = (index) => {
  if (Array.isArray(uploadRefs.value) && uploadRefs.value[index]) {
    uploadRefs.value[index].$el.querySelector('input').click()
  }
}

// 触发单图片上传
const triggerSingleUpload = () => {
  if (singleUploadRef.value) {
    singleUploadRef.value.$el.querySelector('input').click()
  }
}
</script>

<style lang="scss" scoped>
.image-uploader {
  .uploader-item {
    :deep(.el-upload) {
      border: 1px dashed #d9d9d9;
      border-radius: 6px;
      cursor: pointer;
      position: relative;
      overflow: hidden;
      transition: border-color 0.3s;

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

  .preview-image {
    position: relative;

    .image-actions {
      position: absolute;
      left: 0;
      right: 0;
      bottom: 0;
      height: 32px;
      background: rgba(0, 0, 0, 0.6);
      display: flex;
      justify-content: center;
      align-items: center;
      opacity: 0;
      transition: opacity 0.3s;
    }

    &:hover .image-actions {
      opacity: 1;
    }
  }

  :deep(.el-upload-dragger) {
    // width: 150px;
    // height: 150px;
    padding: 0 !important;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .upload-placeholder {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    color: #8c939d;
  }
}
</style>

可不改变顺序重新上传
可预览图片
使用方法

 <uploadImages v-model="formData.businessLicense" type="businessLicense" @uploadImgCallback="uploadImgCallback"
      :width="120" :height="120" placeholder="上传营业执照" :disabled="disabled" />
      
      width 和 height 设置高度
      multiple 允许上传多张
      limit  设置最多上传几张
      uploadImgCallback  上传成功回调 可不写,有需要可以写
      isTip 设置提示文字是否显示
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值