vue基于element封装上传图片,拖拽

发布于:2024-04-25 ⋅ 阅读:(35) ⋅ 点赞:(0)

基于elementPlus封装上传图片,可延伸上传文件等,基础代码记录,具体上传事件未处理,此处个人记录保存一下,可拖拽改变顺序

第一种

<template>
  <div class="img-upload-layout">
    
    <!-- 文件列表 -->
    <transition-group class="img-upload-list" name="el-fade-in-linear" tag="ul">
      <li 
        class="img-upload-list__item img-upload-list__img"
        v-for="(file, index) in fileLists" 
        :key="file.uid" 
        @dragstart="handleDragStart($event,file,index)" 
        @dragenter="handleDragEnter($event,file,index)" 
        @dragover.prevent="handleDragOver($event,file,index)" 
        @dragend="handleDragEnd($event, file,index)" 
        :draggable="true"
      >
        <el-image
          style="width: 120px; height: 120px"
          :src="file.url"
          :preview-src-list="[file.url]"
          fit="contain"
        />
        <div class="operation-item-wrap">
          <span class="item-delete operation-item" @click="handleRemove(file,index)" >
            <el-icon><Delete /></el-icon>
          </span>
        </div>
      </li>
      <li class="img-upload-list__item img-upload-list__btn">
        <el-upload
          ref="imgUploadRef"
          class="upload-img-uploader"
          list-type="picture-card"
          accept="image/*"
          :show-file-list="false"
          :auto-upload="false"
          :multiple="props.multiple"
          :action="uploadFileUrl"
          :headers="headers"
          :file-list="fileLists"
          :limit="props.limit"
          :on-exceed="handleExceed"
          :on-change="handleUploadChange"
          :before-upload="handleBeforeUpload"
          :on-success="handleUploadSuccess"
          :on-error="handleUploadError"
        >
          <!-- 上传按钮 -->
          <el-icon ><Plus /></el-icon>
        </el-upload>
      </li>
    </transition-group>
  </div>
</template>

<script setup>

const props = defineProps({
  modelValue: [String, Object, Array],
  // 上传按钮文字
  btnText: {
    type: String,
    default: '选取文件',
  },
  // 数量限制
  limit: {
    type: Number,
    default: 300,
  },
  multiple: {
    type: Boolean,
    default: true,
  },
  // 大小限制(MB)
  fileSize: {
    type: Number,
    default: 5,
  },
});

const { proxy } = getCurrentInstance();
const emit = defineEmits();

const imgUploadRef = ref(null)
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); 
const uploadFileUrlAuth = ref(import.meta.env.VITE_APP_BASE_API + "/system/uploadPrivate"); 
// 上传header传参
const headers = ref({ Authorization: "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoiYWI2ODE0ZjItMGExZC00MzEzLTllM2UtMDBlYjQ2YjJiOGFiIiwidXNlcl9rZXkiOiI5ZTEyNThjMS03NzFjLTQ1NWItYWYxNy03YzcwNzVjZmU5ZWMiLCJ1c2VybmFtZSI6InN6YWRtaW4ifQ.z3gy7ogoZU80Qdp2oOHOl6ZTSWmfbTUpC_NPBphVCTkj9Grku8Q0QcN0R_VS3OWLuopsXqsZ1129bb5v7Wqzqg" });
const fileLists = ref([]);
const uploadList = ref([]);

// 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
const handleUploadChange = (file,files) => {
  // status = 'ready' | 'uploading' | 'success' | 'fail'
  fileLists.value.push(file)
  console.log(fileLists.value,'=========handleUploadChange=====',files,file);
}
// 上传前校检 
const handleBeforeUpload = (file) => {
  console.log('=========handleBeforeUpload=====',file);
  // // 校检文件大小
  // if (props.fileSize) {
  //   const isLt = file.size / 1024 / 1024 < props.fileSize;
  //   if (!isLt) {
  //     proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
  //     return false;
  //   }
  // }
  return true;
};
// 上传成功回调
const handleUploadSuccess = (res, file) => {
  console.log(res,'=======================handleUploadSuccess=======================',file);
  if(res && res.code == 200){
    uploadList.value.push(file);
    if (uploadList.value.length === number.value) {
      fileLists.value = fileLists.value.filter(f => f.fileUrl !== undefined).concat(uploadList.value);
      uploadList.value = [];
      number.value = 0;
      emit("update:modelValue", fileLists.value );
    }
  }else{
    proxy.$modal.msgError(res && res.msg ? res.msg : '上传文件失败');
  }
  
};
// 上传失败
const handleUploadError = (err) => {
  console.log('=======================handleUploadError=======================',err);
  proxy.$modal.msgError("上传文件失败");
};
// 文件个数超出
const handleExceed = () => {
  console.log('=======================handleExceed=======================');
  proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
};
const handleRemove = (file,index) => {
  fileLists.value.splice(index, 1);

  // fileLists.value = fileLists.value.filter(item => item.uid != file.uid)
  console.log(fileLists.value,'=======================handleRemove=======================',file);
};
const handlePreview = (file) => {
  console.log('=======================handlePreview=======================',file);
};
// 拖动事件处理
const draggingItem = ref(null)
const draggingIdx = ref(-1)
const handleDragStart = (e,item,index) => {
  console.log(e,item,'============================handleDragStart======================', index);
  draggingItem.value = item
  draggingIdx.value = index
};
// 首先把元素变成可以放置的元素,即重写dragenter/dragover
const handleDragEnter = (e,item,index) => {
  console.log(e,item,'============================handleDragEnter======================', index);
  e.preventDefault();
  if (draggingItem.value && item.uid == draggingItem.value.uid) {
    return;
  }
  const newListItems = [...fileLists.value];
  const indexOfFlag = newListItems.indexOf(draggingItem.value);
  const dst = newListItems.indexOf(item);
  newListItems.splice(dst, 0, ...newListItems.splice(indexOfFlag, 1));
  fileLists.value = JSON.parse(JSON.stringify(newListItems));
};
const handleDragOver = (e,item,index) => {
  console.log(e,item,'============================handleDragOver======================', index);
  e.preventDefault();
};
const handleDragEnd = (e,item,index) => {
  console.log(e,item,'============================handleDragEnd======================', index);
  draggingItem.value = null
  draggingIdx.value = -1
};

</script>

<style lang="scss">
.img-upload-layout{
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  .upload-img-uploader{
    .el-upload--picture-card{
      width: 120px;
      height: 120px;
    }
  }
  .img-upload-list{
    display: flex;
    justify-content: flex-start;
    align-items: flex-start;
    flex-wrap: wrap;
    &__item{
      width: 120px;
      height: 120px;
      &:hover{
        .operation-item-wrap{
          display: flex;
        }
      }
    }
    &__img{
      border: 1px solid #e5e5e5;
      border-radius: 8px;
      overflow: hidden;
      margin: 0 6px 12px 6px !important;
      position: relative;
    }
    .operation-item-wrap{
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      width: 100%;
      height: 24px;
      background-color: rgb(0 0 0 / 40%);
      padding: 0 4px;
      box-sizing: border-box;
      display: flex;
      justify-content: flex-end;
      display: none;
      .el-icon{
        font-size: 20px;
        color: #ffffff;
      }
      .operation-item{
        cursor: pointer;
        margin-right: 4px;
        display: flex;
        align-items: center;
        justify-content: center;
        &:hover{
          opacity: .85;
        }
      }
    }
  }
}
</style>

第二种

<template>
  <div class="img-upload-layout">
    <div class="draggable-img-btn">
      <el-upload
        ref="imgUploadRef"
        class="upload-img-uploader"
        list-type="picture-card"
        accept="image/*"
        :show-file-list="false"
        :auto-upload="false"
        :multiple="props.multiple"
        :action="uploadFileUrl"
        :headers="headers"
        :file-list="fileLists"
        :limit="props.limit"
        :on-exceed="handleExceed"
        :on-change="handleUploadChange"
        :before-upload="handleBeforeUpload"
        :on-success="handleUploadSuccess"
        :on-error="handleUploadError"
      >
        <!-- 上传按钮 -->
        <el-icon ><Plus /></el-icon>
        <template #tip>
          <div class="el-upload__tip">
            请选择大小在{{props.fileSize}}M内的图片
          </div>
        </template>
      </el-upload>
    </div>
    <!-- 文件列表 -->
    <draggable
      class="draggable-img-group"
      tag="draggable-img-group" 
      item-key="uid" 
      v-model="fileLists"
      :component-data="{name:'fade'}" 
      @change="handleDragChange" 
      @update="handleDragUpdate" 
      @end="handleDragEnd" 
    >
      <template #item="{element,index}">
        <div class="draggable-img-group__item">
          <el-image
            style="width: 120px; height: 120px"
            :src="element.url"
            :preview-src-list="[element.url]"
            fit="contain"
          />
          <div class="operation-item-wrap">
            <span class="item-delete operation-item" @click="handleRemove(element,index)" >
              <el-icon><Delete /></el-icon>
            </span>
          </div>
        </div>
        
      </template>
    </draggable>  
    
  </div>
</template>

<script setup>
import draggable from 'vuedraggable'

const props = defineProps({
  modelValue: [String, Object, Array],
  // 上传按钮文字
  btnText: {
    type: String,
    default: '选取文件',
  },
  // 数量限制
  limit: {
    type: Number,
    default: 300,
  },
  multiple: {
    type: Boolean,
    default: true,
  },
  // 大小限制(MB)
  fileSize: {
    type: Number,
    default: 5,
  },
});

const { proxy } = getCurrentInstance();
const emit = defineEmits();

const imgUploadRef = ref(null)
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); 
const uploadFileUrlAuth = ref(import.meta.env.VITE_APP_BASE_API + "/system/uploadPrivate"); 
// 上传header传参
const headers = ref({ Authorization: "Bearer eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoiYWI2ODE0ZjItMGExZC00MzEzLTllM2UtMDBlYjQ2YjJiOGFiIiwidXNlcl9rZXkiOiI5ZTEyNThjMS03NzFjLTQ1NWItYWYxNy03YzcwNzVjZmU5ZWMiLCJ1c2VybmFtZSI6InN6YWRtaW4ifQ.z3gy7ogoZU80Qdp2oOHOl6ZTSWmfbTUpC_NPBphVCTkj9Grku8Q0QcN0R_VS3OWLuopsXqsZ1129bb5v7Wqzqg" });
const fileLists = ref([]);
const uploadList = ref([]);

// 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用
const handleUploadChange = (file,files) => {
  // status = 'ready' | 'uploading' | 'success' | 'fail'
  fileLists.value.push(file)
  console.log(fileLists.value,'=========handleUploadChange=====',files,file);
}
// 上传前校检 
const handleBeforeUpload = (file) => {
  console.log('=========handleBeforeUpload=====',file);
  // // 校检文件大小
  if (props.fileSize) {
    const isLt = file.size / 1024 / 1024 < props.fileSize;
    if (!isLt) {
      proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
      return false;
    }
  }
  return true;
};
// 上传成功回调
const handleUploadSuccess = (res, file) => {
  console.log(res,'=======================handleUploadSuccess=======================',file);
  if(res && res.code == 200){
    uploadList.value.push(file);
    if (uploadList.value.length === number.value) {
      fileLists.value = fileLists.value.filter(f => f.fileUrl !== undefined).concat(uploadList.value);
      uploadList.value = [];
      number.value = 0;
      emit("update:modelValue", fileLists.value );
    }
  }else{
    proxy.$modal.msgError(res && res.msg ? res.msg : '上传文件失败');
  }
  
};
// 上传失败
const handleUploadError = (err) => {
  console.log('=======================handleUploadError=======================',err);
  proxy.$modal.msgError("上传文件失败");
};
// 文件个数超出
const handleExceed = () => {
  console.log('=======================handleExceed=======================');
  proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
};
const handleRemove = (file,index) => {
  fileLists.value.splice(index, 1);
  console.log(fileLists.value,index,'=======================handleRemove=======================',file);
};

const handleDragEnd = (e) => {
  console.log(fileLists.value,'==============handleDragEnd===============',e);
};

 
</script>

<style lang="scss">
.img-upload-layout{
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  flex-direction: column;
  .draggable-img-btn{
  }
  .upload-img-uploader{
    .el-upload--picture-card{
      width: 56px;
      height: 56px;
    }
  }
  .draggable-img-group{
    display: flex;
    justify-content: flex-start;
    align-items: flex-start;
    flex-wrap: wrap;
    &__item{
      width: 120px;
      height: 120px;
      border: 1px solid #e5e5e5;
      border-radius: 8px;
      overflow: hidden;
      margin: 0 6px 12px 6px !important;
      position: relative;
      &:hover{
        .operation-item-wrap{
          display: flex;
        }
      }
      .operation-item-wrap{
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        width: 100%;
        height: 24px;
        background-color: rgb(0 0 0 / 40%);
        padding: 0 4px;
        box-sizing: border-box;
        display: flex;
        justify-content: flex-end;
        display: none;
        .el-icon{
          font-size: 20px;
          color: #ffffff;
        }
        .operation-item{
          cursor: pointer;
          margin-right: 4px;
          display: flex;
          align-items: center;
          justify-content: center;
          &:hover{
            opacity: .85;
          }
        }
      }
    }
  }
}
</style>