vue3自定义上传功能;手写上传组件;input实现上传

发布于:2025-02-11 ⋅ 阅读:(38) ⋅ 点赞:(0)

一、拖拽上传-未调用上传接口

1.效果

支持多选、拖拽、删除

## 二级目录

2.代码

far.vue

<!-- 导入组件 -->
<UploadDrag :accept="'.xls,.xlsx'" :length="2" :fileListVal="fileList || []" @setFile="setFile"></UploadDrag>



import UploadDrag from '@/components/uploadDrag.vue'
let fileList = ref([]) // 上传的文件
// 上传附件
const setFile = (val) => { fileList.value = val || [] }

uploadDrag.vue

<template>
  <div class="page-view wbg">
    <div class="drop-area" @dragenter="highlight" @dragover="highlight" @dragleave="unHighlight" @drop="handleDrop"
      @click="handleClick">
      <el-icon :size="67" color="#9ea1a9" class="el-icon--upload"><upload-filled /></el-icon>
      <div class="desc" v-if="accept">请将待上传文件拖至该区域,仅支持上传:{{ accept }}文档类型</div>
      <input type="file" ref="fileInput" @change="handleFiles" :multiple="multipleFlag" :accept="accept"
        style="display: none;">
    </div>
    <div class="mt">
      <span>已上传文档:{{ fileList.length ? '' : '无' }}</span>
      <div v-for="(item, index) in fileList" :key="index">
        <el-space>
          <span>{{ item.fileName }}</span>
          <el-button :icon="Delete" @click="deleteFile(index)" link type="danger"></el-button>
        </el-space>
      </div>
    </div>
    <!-- <pre style="max-width: 100px;">{{ fileList }}</pre>
    <pre style="max-width: 100px;">{{ inputList }}</pre> -->
  </div>
</template>

<script setup>
import { watch } from 'vue'
import { Delete, UploadFilled } from '@element-plus/icons-vue'
// import { singleFileUpload, } from '@/service/index.js'
import _ from 'lodash'
import { ElMessage } from 'element-plus'
import { getIpAndPort } from '@/utils/common.js'

defineOptions({
  name: "JgUploadDrag"
})

const props = defineProps({
  fileListVal: { type: Array, default: () => [] }, // 父组件传递的文件集合
  accept: { type: String, default: () => '' }, // 默认不限制 .jpg,.jpeg,.png,.gif,.bmp,.JPG,.JPEG,.PBG,.GIF
  length: { type: Number, default: () => 999999 },
  canEdit: { type: Boolean, default: () => true },
})
const emit = defineEmits(["setFile"])
const fileList = ref([]) // 当前组件内的文件集合
const inputList = ref([]) // 这个是input拿到的list 不一定调用上传接口
const fileInput = ref(null); // 上传ref
const multipleFlag = ref(false) // 是否多选

watch([() => props.fileListVal], ([fileListVal]) => {
  // console.log('监听', fileListVal);
  fileList.value = _.cloneDeep(fileListVal)
  inputList.value = _.cloneDeep(fileListVal)
}, { deep: true, immediate: true })

const highlight = (e) => {
  e.preventDefault();
  e.stopPropagation();
  e.target.classList.add('hover');
};

const unHighlight = (e) => {
  e.target.classList.remove('hover');
};

// 拖拽
const handleDrop = (e) => {
  // console.log(e.dataTransfer.files);
  e.preventDefault();
  e.stopPropagation();
  unHighlight(e);
  const files = e.dataTransfer.files;
  if (fileList.value.length >= props.length) {
    return ElMessage.warning(`最多允许上传${props.length}个文件`)
  }
  let flagResult = beforeUpload(files[0])
  if (!flagResult) {
    return false
  }
  inputList.value = [] // 必须这几步都置空 不然无效
  let flag = true
  for (let i = 0; i < files.length; i++) {
    inputList.value.push({
      file: files[i],
      name: files[i].name,
      size: files[i].size,
      type: files[i].type
    });
    if (!beforeUpload(files[i])) {
      flag = false
    }
  }
  if (flag) {
    uploadFile();
  }
};

// 点击
const handleClick = () => {
  if (fileList.value.length >= props.length) {
    return ElMessage.warning(`最多允许上传${props.length}个文件`)
  }
  // console.log(fileInput.value);
  fileInput.value.click(); // 使用 ref 的 value 直接访问 DOM 元素
};

// 获取点击文件
const handleFiles = (e) => {
  const files = e.target.files || e;
  // console.log('files', files, typeof files, files[0]);
  inputList.value = [] // 必须这几步都置空 不然无效
  let flag = true
  for (let i = 0; i < files.length; i++) {
    inputList.value.push({
      file: files[i],
      name: files[i].name,
      size: files[i].size,
      type: files[i].type
    });
    if (!beforeUpload(files[i])) {
      flag = false
    }
  }
  if (flag) {
    uploadFile();
  }
};

const beforeUpload = (file) => {
  // 1、控制文件数量
  if (props.length && fileList.value > props.length) {
    ElMessage.warning(`最多允许上传${props.length}个文件`)
    // 设置上传的文件为错误状态
    inputList.value = [] // 必须这几步都置空 不然无效
    return false
  }

  // 2、控制上传的文件大小
  if (file.size > 209715200) {
    ElMessage.warning('文件大小超过最大限度200M')
    inputList.value = [] // 必须这几步都置空 不然无效
    return false
  }

  // 3、控制上传文件不能为空
  if (file.size === 0) {
    ElMessage.warning('所选信息中存在空文件或目录,请重新选择')
    inputList.value = [] // 必须这几步都置空 不然无效
    return false
  }

  // 4、控制已上传文件不重复
  // for (let i = 0; i <= this.fileList.length; i++) {
  //   const item = this.fileList[i]
  //   if (item.name === file.name) {
  //     inputList.value = [] // 必须这几步都置空 不然无效
  //     ElMessage.warning('不允许重复上传')
  //     return false
  //   }
  // }

  // 5、控制上传文件的类型 arr是上传类型的白名单
  if (props.accept && props.accept.length) {
    const type = file.name.slice(file.name.lastIndexOf('.') + 1).toLowerCase()
    const arr = props.accept.split(',')
    if (arr.includes('.' + type)) {
      return true
    } else {
      ElMessage.warning(`不支持以 .${type} 扩展类型的文件上传!`)
      inputList.value = [] // 必须这几步都置空 不然无效
      return false
    }
  }

  return true
}

// 上传文件
const uploadFile = () => {
  // console.log('inputList', inputList.value);
  const file = inputList.value[0].file
  // console.log('上传接口的文件', file);
  // singleFileUpload({ file }).then(v => {
  //   fileList.value.push({
  //     fileName: v.fileName,
  //     uuidName: v.uuidName,
  //     url: '/file/download?fileName=' + v.uuidName,
  //     file: file,
  //   })
  //   inputList.value = [] // 必须这几步都置空 不然无效
  //   emit('setFile', fileList.value)
  // })
  fileList.value.push({
    file: file,
    fileName: file.name,
  })
  emit('setFile', fileList.value)
};

// 手动删除
const deleteFile = (index) => {
  fileList.value = fileList.value.filter((item, i) => i !== index)
  inputList.value = _.cloneDeep(fileList.value)
  emit('setFile', fileList.value)
}

</script>


<style lang="less" scoped>
.page-view {
  width: 100%;
  height: 100%;
}
.wbg {
  background-color: #fff;
}
.mt {
  margin-top: 16px;
}
.file-box {
  display: inline-block;
  margin-right: 30px;
}

.drop-area {
  width: 440px;
  height: 185px;
  border: 1px dashed #dcdfe6;
  border-radius: 6px;
  text-align: center;
  cursor: pointer;
  padding-top: 40px;
}

.drop-area:hover {
  border-color: #11716f;
}
</style>

二、按钮上传-点击即调上传接口

1.效果

在这里插入图片描述

2.代码

far.vue

 <el-col :span="24">
    <el-form-item label="成绩附件:" prop="testScoreFileInfoList">
          
       <Upload :accept="'.jpg,.jpeg,.png'" :fileListVal="formData.testScoreFileInfoList"
              @setFile="setFile($event, 'testScoreFileInfoList')">
       </Upload>
            
    </el-form-item>
 </el-col>


import Upload from '@/components/upload.vue'
// 上传附件
const setFile = (val, key) => {
  formData.value[key] = val
  ruleFormRef.value.validateField(key)  // 手动触发验证
}

Upload.vue

<script setup>
import { ref, onMounted, watch } from 'vue'
import { Delete, UploadFilled } from '@element-plus/icons-vue'
import { singleFileUpload, } from '@/service/index.js'
import _ from 'lodash'
import { ElMessage } from 'element-plus'
import { getIpAndPort } from '@/utils/common.js'

defineOptions({
  name: "JgUpload"
})

const props = defineProps({
  fileListVal: { type: Array, default: () => [] }, // 父组件传递的文件集合
  accept: { type: String, default: () => '' }, // 默认不限制 .jpg,.jpeg,.png,.gif,.bmp,.JPG,.JPEG,.PBG,.GIF
  length: { type: Number, default: () => 999999 },
  canEdit: { type: Boolean, default: () => true },
})
const emit = defineEmits(["setFile"])
const fileList = ref([]) // 当前组件内的文件集合
const inputList = ref([]) // 这个是input拿到的list 不一定调用上传接口
const fileInput = ref(null); // 上传ref
const multipleFlag = ref(false) // 是否多选
let ipPortUrl = ref(getIpAndPort()) // 前端获取ip端口用于拼接文件地址

watch([() => props.fileListVal], ([fileListVal]) => {
  // console.log('监听', fileListVal);
  fileList.value = _.cloneDeep(fileListVal)
  inputList.value = _.cloneDeep(fileListVal)
}, { deep: true, immediate: true })

const highlight = (e) => {
  e.preventDefault();
  e.stopPropagation();
  e.target.classList.add('hover');
};

const unHighlight = (e) => {
  e.target.classList.remove('hover');
};

// 拖拽
const handleDrop = (e) => {
  // console.log(e.dataTransfer.files);
  e.preventDefault();
  e.stopPropagation();
  unHighlight(e);
  const files = e.dataTransfer.files;
  if (fileList.value.length >= props.length) {
    return ElMessage.warning(`最多允许上传${props.length}个文件`)
  }
  let flagResult = beforeUpload(files[0])
  if (!flagResult) {
    return false
  }
  inputList.value = [] // 必须这几步都置空 不然无效
  let flag = true
  for (let i = 0; i < files.length; i++) {
    inputList.value.push({
      file: files[i],
      name: files[i].name,
      size: files[i].size,
      type: files[i].type
    });
    if (!beforeUpload(files[i])) {
      flag = false
    }
  }
  if (flag) {
    uploadFile();
  }
};

// 点击
const handleClick = () => {
  if (fileList.value.length >= props.length) {
    return ElMessage.warning(`最多允许上传${props.length}个文件`)
  }
  // console.log(fileInput.value);
  fileInput.value.click(); // 使用 ref 的 value 直接访问 DOM 元素
};

// 获取点击文件
const handleFiles = (e) => {
  const files = e.target.files || e;
  // console.log('files', files, typeof files, files[0]);
  inputList.value = [] // 必须这几步都置空 不然无效
  let flag = true
  for (let i = 0; i < files.length; i++) {
    inputList.value.push({
      file: files[i],
      name: files[i].name,
      size: files[i].size,
      type: files[i].type
    });
    if (!beforeUpload(files[i])) {
      flag = false
    }
  }
  if (flag) {
    uploadFile();
  }
};

const beforeUpload = (file) => {
  console.log(1111, file);
  // 0.控制名称长度
  let fileName = file.name.split('.')?.[0] || ''
  if (fileName.length > 20) {
    ElMessage.warning(`文件名称最长为20`)
    // 设置上传的文件为错误状态
    inputList.value = [] // 必须这几步都置空 不然无效
    return false
  }

  // 1、控制文件数量
  if (props.length && fileList.value > props.length) {
    ElMessage.warning(`最多允许上传${props.length}个文件`)
    // 设置上传的文件为错误状态
    inputList.value = [] // 必须这几步都置空 不然无效
    return false
  }

  // 2、控制上传的文件大小
  if (file.size > 209715200) {
    ElMessage.warning('文件大小超过最大限度200M')
    inputList.value = [] // 必须这几步都置空 不然无效
    return false
  }

  // 3、控制上传文件不能为空
  if (file.size === 0) {
    ElMessage.warning('所选信息中存在空文件或目录,请重新选择')
    inputList.value = [] // 必须这几步都置空 不然无效
    return false
  }

  // 4、控制已上传文件不重复
  // for (let i = 0; i <= this.fileList.length; i++) {
  //   const item = this.fileList[i]
  //   if (item.name === file.name) {
  //     inputList.value = [] // 必须这几步都置空 不然无效
  //     ElMessage.warning('不允许重复上传')
  //     return false
  //   }
  // }

  // 5、控制上传文件的类型 arr是上传类型的白名单
  if (props.accept && props.accept.length) {
    const type = file.name.slice(file.name.lastIndexOf('.') + 1).toLowerCase()
    const arr = props.accept.split(',')
    if (arr.includes('.' + type)) {
      return true
    } else {
      ElMessage.warning(`不支持以 .${type} 扩展类型的文件上传!`)
      inputList.value = [] // 必须这几步都置空 不然无效
      return false
    }
  }

  return true
}

// 上传文件
const uploadFile = () => {
  console.log('inputList', inputList.value);
  // const file = params.file;
  const file = inputList.value[0].file
  // console.log('上传接口的文件', file);
  singleFileUpload({ file }).then(v => {
    // fileList.value[fileList.value.length - 1] = {
    //   fileName: v.fileName,
    //   uuidName: v.uuidName,
    //   url: ipPortUrl.value + '/file/download?fileName=' + v.uuidName,
    // }
    fileList.value.push({
      fileName: v.fileName,
      uuidName: v.uuidName,
      url: ipPortUrl.value + '/file/download?fileName=' + v.uuidName,
    })
    inputList.value = [] // 必须这几步都置空 不然无效
    emit('setFile', fileList.value)
  })
};

// 手动删除
const deleteFile = (index) => {
  fileList.value = fileList.value.filter((item, i) => i !== index)
  inputList.value = _.cloneDeep(fileList.value)
  emit('setFile', fileList.value)
}

onMounted(() => {
})


</script>

<template>
  <div class="page-view wbg">
    <div v-if="(!(props.length === 1 && fileList.length === 1) && props.canEdit)" class="drop-area"
      @dragenter="highlight" @dragover="highlight" @dragleave="unHighlight" @drop="handleDrop" @click="handleClick">
      <el-button :icon="UploadFilled">上传文件</el-button>
      <div class="desc" v-if="accept">支持文件格式:{{ accept }}</div>
      <input type="file" ref="fileInput" @change="handleFiles" :multiple="multipleFlag" :accept="accept"
        style="display: none;">
    </div>
    <div class="file-box" v-for="(item, index) in fileList" :key="index">
      <el-space v-if="item.uuidName">
        <a v-download="ipPortUrl + '/file/download?fileName=' + item.uuidName" class="green"
          style="color:#007d7b;cursor:pointer">{{ item.fileName }}</a>
        <el-button v-if="props.canEdit" :icon="Delete" @click="deleteFile(index)" link type="danger"></el-button>
      </el-space>
    </div>
    <!-- <pre style="max-width: 100px;">{{ fileList }}</pre>
    <pre style="max-width: 100px;">{{ inputList }}</pre> -->
  </div>
</template>

<style lang="scss" scoped>
.page-view {
  width: 100%;
  height: 100%;
}
.wbg {
  background-color: #fff;
}
.mt {
  margin-top: 16px;
}
.file-box {
  display: inline-block;
  margin-right: 30px;
}

.drop-area {
  text-align: left;
  cursor: pointer;
}

.desc {
  font-size: 12px;
  color: #606266;
}
</style>