1.实现原理:
- 原生的上传文件组件: <input ref="uploadFileRef" style="display: none" type="file"/>
- 自定义上传区域:
-
拖拽事件添加(dragover,dragenter,drop),点击事件添加(click)
- 调用原生上传组件的click事件:uploadFileRef.value.click()
- 监听元素上传组件的值回传事件:change
- 进度监控利用axios中的回调函数onUploadProgress实时或是上传文件大小
2.源码:
<template> <el-dialog v-bind="$attrs" :title="props.type === '2' ? '分期结算' : '上传售后明细'" custom-class="dialog-s select-brand" destroy-on-close :close-on-click-modal="false" @open="handleOpen" @closed="handleClose" > <div> <span v-if="props.type === '2'" class="upload_title">请先在微信支付平台下载需要结算时间段的明细数据并上传</span> <div class="upload_box"> <input ref="uploadFileRef" style="display: none" type="file" :accept="props.accept" name="file" @change="uploadChange" /> <!-- 上传 --> <div class="upload_content " ref="dropArea" @click="uploadFileClick" @dragover.prevent="handleDragOver" @dragenter.prevent="handleDragLeave" @drop.prevent="handleDrop" > <div v-if="props.type === '1'" class="content_box"> <img class="imge" src="@/assets/upload.png" /> <p class="content">将文件拖到此处,或<el-button type="text">点击上传</el-button></p> <p class="content desc">支持格式:.csv、.xls、.xlsx格式,<el-button type="text" @click.capture.stop="downLoadModule">下载填写模板</el-button></p> </div> <div v-if="props.type === '2'" class="content_box"> <img class="imge mb4" src="@/assets/adtop.png" /> <p class="content mb4"><el-button>选择文件</el-button></p> <p class="content desc">可直接将文件拖拽到此处进行上传,支持格式:.csv、.xls、.xlsx</p> </div> </div> <!-- 遮罩层--> <div v-if="maskedShow" class="masked_box"></div> </div> <!-- 文件 --> <div class="upload_files" v-for="item in data.files" :key="item.uid"> <!-- 文件名称 --> <a @click="downLoadItem(item)">{{ item.name }}</a> <!-- 文件状态 --> <div class="upload_files_statue"> <svg-icon :icon="statueList[item.status].icon" :class="item.status == 'ready' ? 'loading' : ''" class="mr4"></svg-icon> <span >{{ statueList[item.status].msg }} <span v-if="item.status == 'ready'">{{ item.percentage }}%</span> <span v-if="item.status == 'exception'"> <el-icon class="ml4" @click="refresh(item)"><Refresh /></el-icon> </span> </span> </div> <!-- 删除 --> <div class="ml4" v-if="props.eidtState == 'edit'"> <el-button type="text" @click="deleteItem(item)">删除</el-button> </div> </div> </div> <template #footer> <div class="footer-wrap"> <el-button @click="handleClose">取消</el-button> <el-button :loading="submitLoading" type="primary" @click="handleSubmit()">确定</el-button> </div> </template> </el-dialog> </template> <script setup lang="ts" name="EditUser"> import { A_createOrEditUser } from '@/api/system/sysuser' import { A_getRoleList } from '@/api/system/sysrole' import { reactive, watchEffect, onMounted, computed, nextTick, ref } from 'vue' import { ElMessage } from 'element-plus' import axios from 'axios' import cache from '@/utils/cache' import { downloadExcel, handleUploadFile } from '@/utils/upload' import { A_getOssFileById } from '@/api/oss' const props = defineProps({ curInfo: [Object], accept: { type: String, default: '.csv,.xls,.xlsx' }, type: { type: String, default: '2' }, size: { type: Number, default: 5 }, limt: { type: Number, default: 1 }, eidtState: { type: String, default: 'edit' } }) const emits = defineEmits<{ (e: 'update:modelValue', value: any): void (e: 'update'): void }>() const maskedShow = computed(() => { return props.eidtState !== 'edit' || data.files.length >= props.limt }) //打开 const handleOpen = () => { data.files = [] props.curInfo.id && getOssFileById([props.curInfo.id]) } let submitLoading = ref<Boolean>(false) //确认上传核销明细 const handleSubmit = () => { submitLoading.value = true } //关闭 const handleClose = () => { emits('update:modelValue', false) } /** * * 手撸文件上传功能 * 1.实现拖拽功能 * 2.拖拽区域可以下载上传模板功能 * 3.上传文件进度监控 * 4.上传中的文件也可以直接下载 * * * */ const uploadFileRef = ref(null) const dropArea = ref(null) const statueList = { ready: { icon: 'loading', msg: '上传中' }, succeed: { icon: 'succfull', msg: '上传成功' }, exception: { icon: 'fail', msg: '上传失败' } } const data = reactive({ files: [] }) const handleDragOver = (e: any) => { e.preventDefault() dropArea.value.classList.add('dragover') } const handleDragLeave = (e: any) => { e.preventDefault() dropArea.value.classList.remove('dragover') } const handleDrop = (e: any) => { e.preventDefault() dropArea.value.classList.remove('dragover') const files = e.dataTransfer.files for (let i = 0; i < files.length; i++) { const curryFile = handleStart(files[i]) addFilde(curryFile) } } //点击上传事件 const uploadFileClick = () => { uploadFileRef.value.click() } const addFilde = (curryFile: any) => { if (checkFilesSize(curryFile)) { data.files.push(curryFile) uploadFiles(curryFile) } } //原生上传事件 const uploadChange = (e: any) => { const chooseFile = e.target.files[0] e.target.value = '' const curryFile = handleStart(chooseFile) addFilde(curryFile) } //检测文件大小 const checkFilesSize = (rawFile: any): boolean => { if (data.files.length >= props.limt) { ElMessage.warning(`最多上传${props.limt}个文件`) return false } console.log(rawFile,'acceptList') if (!['application/vnd.ms-excel','application/vnd.openxmlformats-officedocument.spreadsheetml.sheet','text/csv'].includes(rawFile.raw.type)) { ElMessage.error(`上传文件格式错误,仅支持${props?.accept}`) return false } if (rawFile.size / 1024 / 1024 > props.size) { ElMessage.error(`文件大小不能超过${props.size}MB!`) return false } return true } //上传文件准备 const handleStart = (rawFile: any) => { rawFile.uid = Date.now() return { status: 'ready', name: rawFile.name, size: rawFile.size, percentage: 0, uid: rawFile.uid, raw: rawFile, serviceFilesUrl: '', serviceFlieName: '', serviceId: -1 } } //上传文件 const uploadFiles = (rawFile: any) => { const formData = new FormData() formData.append('file', rawFile.raw) axios({ method: 'POST', url: '/backend-platform/sys/oss/upload', data: formData, headers: { token: cache.getToken() }, onUploadProgress: function (progressEvent) { let cuurFile = data.files.find(item => item.uid == rawFile.uid) cuurFile && (cuurFile.percentage = Number(((progressEvent.loaded / progressEvent.total) * 95).toFixed(2))) } }) .then(res => { let cuurFile = data.files.find(item => item.uid == rawFile.uid) const { data: row } = res if (row.code === 0 && cuurFile) { cuurFile.percentage = 100 cuurFile.serviceFilesUrl = row.data[0]?.ossUrl || '' cuurFile.serviceFlieName = row.data[0]?.fileName || '' cuurFile.id = row.data[0]?.id cuurFile.status = 'succeed' } else { ElMessage.error(row.msg) cuurFile.percentage = 100 cuurFile.serviceFilesUrl = '' cuurFile.id = '' cuurFile.serviceFlieName = '' cuurFile.status = 'exception' } }) .catch(err => { rawFile.percentage = 100 rawFile.serviceFilesUrl = '' rawFile.id = '' rawFile.serviceFlieName = '' rawFile.status = 'exception' }) } //下载文件 const downLoadItem = (file: any) => { if (file.serviceFilesUrl) { downloadExcel(file.serviceFilesUrl, file.serviceFlieName) } else { handleUploadFile(file.raw, file.name) } } //删除 const deleteItem = (detail: any) => { const index = data.files.findIndex(item => item.uui == detail.uui) if (index > -1) { data.files.splice(index, 1) } } //重新上传 const refresh = (item: any) => { uploadFiles(item) } //下载模版 const downLoadModule = () => { downloadExcel('https://ycbsaas-bucket.oss-cn-hangzhou.aliyuncs.com/images/20231101/b87532037f354340bc632e52a348f633.xls', '模版.xls') } //查询文件信息 const getOssFileById = (ids: any) => { ids.length && A_getOssFileById({ ids }).then(res => { const imageDatas = res.data as [] imageDatas.forEach((item: any) => { let curryFile = { status: 'succeed', name: item.fileName, size: '', percentage: 0, uid: item.id, raw: '', serviceFilesUrl: item.ossUrl, serviceFlieName: item.fileName, serviceId: item.id } data.files.push(curryFile) }) }) } </script> <style scoped lang="scss"> .upload_title { display: inline-block; padding: 0 8px; margin-bottom: 8px; } .upload_box { position: relative; z-index: 99999; .masked_box { position: absolute; width: 100%; height: 100%; top: 0; background-color: rgba(#9999, 0.2); cursor: no-drop; } .upload_content { border: 1px dashed #cccc; height: 172px; cursor: pointer; .content_box { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%; } .imge { width: 70px; height: 70px; } .desc { padding: 0 40px; } } .upload_content:hover { border-color: var(--el-color-primary); } .dragover { border-color: var(--el-color-primary); border-width: 2px; } } .upload_files { display: flex; justify-content: space-between; align-items: center; margin-top: 8px; padding: 0 16px; border-bottom: 1px solid rgba(#9999, 0.2); > a { color: #02a7f0; } .upload_files_statue { display: flex; justify-content: center; align-items: center; flex-shrink: 0; ::v-deep(.el-icon) { vertical-align: middle; } } .loading { animation: loading 1s linear infinite; } @keyframes loading { 0% { transform: rotateZ(0deg); } 8% { transform: rotateZ(30deg); } 16% { transform: rotateZ(60deg); } 24% { transform: rotateZ(90deg); } 32% { transform: rotateZ(120deg); } 40% { transform: rotateZ(150deg); } 48% { transform: rotateZ(180deg); } 56% { transform: rotateZ(210deg); } 64% { transform: rotateZ(240deg); } 72% { transform: rotateZ(270deg); } 81% { transform: rotateZ(300deg); } 89% { transform: rotateZ(330deg); } 100% { transform: rotateZ(360deg); } } } </style>
标签:files,const,data,vue3,item,rawFile,手写,上传,拖拽 From: https://www.cnblogs.com/shibiao9999/p/17805288.html