使用环境 vue3+element-ui plus
需要根据后端返回结构修改的函数:onPreview onRemove onSuccess
组件使用
基本使用
源代码:
<script setup>
import AutoUploadFile from '@/components/auto-upload-file/index.vue'
function change(urls){
console.log(urls)
}
</script>
<template>
<AutoUploadFile @change="change"/>
</template>
<style lang="scss" scoped>
</style>
初始文件列表回显
源代码:
<script setup>
import AutoUploadFile from '@/components/auto-upload-file/index.vue'
import {ref} from "vue";
const initUrls = ref([
'http://127.0.0.1:9090/file/local-plus/6700f235df13a72064bf9167.png'
])
function change(urls) {
console.log(urls)
}
</script>
<template>
<AutoUploadFile :init-file-urls="initUrls" accept=".jpg,.jpeg,.png" @change="change">
</AutoUploadFile>
</template>
<style lang="scss" scoped>
</style>
定义上传格式
如果有mp4类型,文件展示列表就会变为text
源代码:
<script setup>
import AutoUploadFile from '@/components/auto-upload-file/index.vue'
function change(urls){
console.log(urls)
}
</script>
<template>
<AutoUploadFile accept=".jpg,.jpeg,.png,.mp4" :max-size="100" @change="change"/>
</template>
<style lang="scss" scoped>
</style>
自定义上传按钮样式
源代码:
<script setup>
import AutoUploadFile from '@/components/auto-upload-file/index.vue'
function change(urls){
console.log(urls)
}
</script>
<template>
<AutoUploadFile accept=".jpg,.jpeg,.png,.mp4" :max-size="100" @change="change">
<el-button>自定义上传样式</el-button>
</AutoUploadFile>
</template>
<style lang="scss" scoped>
</style>
组件属性
属性名 | 属性类型 | 默认值 | 是否必填 | 说明 |
---|---|---|---|---|
initFileUrls | Array | [] | 否 | 用于上传列表的回显,接收外部初始化url数组 |
listType | String | picture-card | 否 | 上传列表展示格式。可选项:picture-card/picture/text , 但是如果accept允许mp4,那么listType就会自动转化为text |
action | String | # | 是 | 上传文件时的接口服务 |
headers | Object | {} | 否 | 请求头 |
name | String | file | 否 | 提交文件时的字段名 |
withCredentials | Boolean | true | 否 | 是否支持cookie凭证信息 |
showFileList | Boolean | true | 否 | 是否展示上传列表 |
accept | String | “.jpg,.jpeg,.png” | 否 | 可以上传的文件类型 |
limit | Number | 5 | 否 | 允许上传的最大文件数量 |
maxSize | Number | 5 | 否 | 最大文件大小,单位MB |
tipPrefix | String | “” | 否 | 提示信息前缀 |
showTip | Boolean | true | 否 | 是否展示提示信息 |
showOverflowTooltip | Boolean | true | 否 | tip过长是否使用省略号显示 |
multiple | Boolean | true | 否 | 可以多选文件 |
autoUpload | Boolean | true | 否 | 默认自动上传 |
size | String | 100px | 否 | picture-card的尺寸大小 |
组件事件
事件名 | 事件参数列表 | 说明 |
---|---|---|
onProgress | (e,file,fileList) | 用于监控文件上传进度 |
change | (urls) | 上传列表改变时的回调 |
组件暴露方法
方法名 | 参数列表 | 说明 |
---|---|---|
clearFiles | () | 清空文件列表 |
submit | () | 用于手动提交 |
组件插槽
插槽名 | 插槽回显参数 | 说明 |
---|---|---|
default | 无 | 上传文件时的点击区域,用于自定义样式 |
file | { file } | 文件列表样式,用于自定义缩略图 |
trigger | 无 | 用于手动提交,只选择文件,不发起提交请求的插槽 |
组件源码:(auto-upload-file.vue)
<!--需要根据后端返回结构修改的函数:onPreview onRemove onSuccess -->
<script setup>
import {computed, onMounted, onUpdated, ref} from "vue";
import {ElMessage} from "element-plus";
const prop = defineProps({
// 文件列表
initFileUrls: {
type: Array,
default: []
},
// 展示格式
listType: {
type: String,
default: 'picture-card'
},
// 上传文件的默认接口
action: {
type: String,
default: 'http://localhost:9090/upload/file'
},
// 请求头(添加token等)
headers: {
type: Object,
default: {}
},
// 上传时的文件名(需要与后端接收时的字段保持一致)
name: {
type: String,
default: 'file'
},
// 默认支持cookie凭证信息
withCredentials: {
type: Boolean,
default: true
},
// 默认展示上传文件列表
showFileList: {
type: Boolean,
default: true
},
// 可接受文件的类型
accept: {
type: String,
default: '.jpg,.jpeg,.png'
},
// 允许上传的最大文件数量
limit: {
type: Number,
default: 5
},
// 单位MB
maxSize: {
type: Number,
default: 5
},
// 提示前缀
tipPrefix: {
type: String,
default: ''
},
// 是否展示提示
showTip: {
type: Boolean,
default: true
},
// tip过长使用省略号显示
showOverflowTooltip: {
type: Boolean,
default: true
},
// 可以多选文件
multiple: {
type: Boolean,
default: true
},
// 默认自动上传
autoUpload: {
type: Boolean,
default: true
},
// picture-card尺寸大小
size: {
type: String,
default: '100px'
}
})
const emit = defineEmits(['onProgress', 'change'])
defineExpose({
clearFiles,
submit
})
// el-upload使用的文件列表
const fileList = ref(prop.initFileUrls.map(item => {
return {
name: getFileName(item),
url: item
}
}))
const uploadRef = ref()
// 存放后端返回的url,及初始化url
const urls = ref([...prop.initFileUrls])
// 如果允许上传视频,则默认只能使用text显示上传列表
const listTypeCmp = computed(() => {
return prop.accept.indexOf('mp4') !== -1 ? 'text' : prop.listType
})
// 提示信息
const tip = computed(() => {
return `${prop.tipPrefix ? prop.tipPrefix + ',' : ''}文件类型:
${prop.accept.replaceAll(',', '/').replaceAll('.', '')}
,文件大小不能超过:${prop.maxSize}MB`
})
// 文件上传之前的钩子
function beforeUpload(e) {
const MB = e.size / (1024 * 1024)
if (MB > prop.maxSize) {
ElMessage.error(`文件的大小不能超过:${prop.maxSize}MB`)
return false
}
}
// 上传成功的回调(根据后端返回值不同需要略作修改)
function onSuccess(e, file) {
urls.value.push(e)
emit('change', urls.value)
}
const dialogFileUrl = ref()
const dialogVisible = ref(false)
// 预览图片(根据后端返回值不同需要略作修改)
function onPreview(e) {
dialogFileUrl.value = e.response || e.url
dialogVisible.value = true
}
// 移除文件(根据后端返回值不同需要略作修改)
function onRemove(e) {
urls.value = urls.value.filter(item => item !== (e.response || e.url))
emit('change', urls.value)
}
// 超出最大文件限制时,执行的钩子函数
function onExceed(e) {
ElMessage.error(`超出要求的文件最大数量:${prop.limit}`)
}
// 文件上传失败时的回调
function onError() {
ElMessage.error('文件上传失败')
}
// 上传进度回调
function onProgress(e, file, fileList) {
emit('onProgress', e, file, fileList)
}
// 清空文件列表
function clearFiles() {
uploadRef.value.clearFiles()
urls.value = []
emit('change', urls.value)
}
// 手动提交
function submit() {
uploadRef.value.submit()
}
// 获取后缀
function getSuffix(url) {
return url.substring(url.lastIndexOf('.') + 1)
}
// 获取文件名
function getFileName(url) {
return url.substring(url.lastIndexOf('/') + 1)
}
// 阻止点击tip时触发的上传行为
function preventClick(event) {
event.stopPropagation()
}
// 初始化picture-card大小
function initSize() {
const uploadListItem = document.querySelector('.el-upload-list--picture-card')
const uploadPictureCard = document.querySelector('.el-upload--picture-card')
if (uploadListItem) {
uploadListItem.style.setProperty('--el-upload-list-picture-card-size', prop.size)
}
if (uploadPictureCard) {
uploadPictureCard.style.setProperty('--el-upload-picture-card-size', prop.size)
}
}
// 动态处理展示样式
function handleStyle() {
initSize()
const dom = document.querySelector('.el-upload-list')
if (prop.showTip && dom && listTypeCmp !== 'picture-card') {
dom.style.margin = '30px 0 0'
} else {
dom.style.margin = '10px 0 0'
}
}
onUpdated(() => {
handleStyle()
})
onMounted(() => {
handleStyle()
})
</script>
<template>
<el-upload
ref="uploadRef"
class="upload-box"
v-model:file-list="fileList"
:list-type="listTypeCmp"
:action="action"
:headers="headers"
:with-credentials="withCredentials"
:name="name"
:show-file-list="showFileList"
:before-upload="beforeUpload"
:on-success="onSuccess"
:on-remove="onRemove"
:on-preview="onPreview"
:accept="accept"
:limit="limit"
:on-exceed="onExceed"
:on-error="onError"
:on-progress="onProgress"
:auto-upload="autoUpload"
:multiple="multiple"
>
<template #default v-if="autoUpload">
<div class="upload">
<div class="upload-default">
<slot>
<el-icon v-if="listTypeCmp==='picture-card'"
style="width: 100%;height: 100%;font-size: 30px;color: #888888">
<Plus/>
</el-icon>
<el-button v-else>上传文件</el-button>
</slot>
</div>
<div class="upload-tip" v-if="showTip" @click="preventClick">
<div class="tip-div" :title="tip"
:class="{'text-overflow-ellipsis':showOverflowTooltip}">
<span>{{ tip }}</span>
</div>
</div>
</div>
</template>
<!-- 自定义缩略图-->
<template #file="{file}">
<slot name="file" :file="file"></slot>
</template>
<template #trigger v-if="!autoUpload">
<slot name="trigger"></slot>
</template>
</el-upload>
<!--文件预览-->
<el-dialog v-model="dialogVisible" width="80%">
<video style="height: 100%;width: 100%" v-if="getSuffix(dialogFileUrl)==='mp4'" :src="dialogFileUrl" controls/>
<el-image v-else style="height: 80vh" w-full :src="dialogFileUrl" alt="Preview Image"/>
</el-dialog>
</template>
<style lang="scss" scoped>
.upload-box {
box-sizing: border-box;
height: 50px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.upload {
position: relative;
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
.upload-tip {
width: 100%;
position: absolute;
bottom: -30px;
left: 0;
.tip-div {
width: 100%;
cursor: pointer;
color: red;
font-weight: 200;
font-size: 12px;
}
.text-overflow-ellipsis {
display: inline-block;
overflow: hidden;
white-space: nowrap; /* 不换行 */
text-overflow: ellipsis; /* 超出部分显示为省略号 */
}
}
}
}
</style>