预览:
代码:
<template>
<el-upload v-bind="getBindValue" multiple v-model:file-list="fileList" class="multiple_uploader" action="#"
:before-upload="beforeUpload" :on-change="handleChange" :on-exceed="handleExceed">
<!-- 自定义上传按钮 -->
<slot v-if="hasBtn" name="btnSlot"></slot>
<el-button v-else class="upload_btn" :icon="Plus">Upload Files</el-button>
<template #tip>
<div v-if="finalTips" class="el-upload__tip">{{ finalTips }}</div>
</template>
</el-upload>
</template>
<script setup>
import { ref, useAttrs, nextTick, computed, toRefs, onMounted, watchEffect, useSlots } from 'vue'
import { ElMessage } from 'element-plus'
import { multipleUpload } from '@/api/common'
import { ALLOW_CONTENT_TYPE_DICT, ONE_Megabit } from './contants'
import { Plus } from '@element-plus/icons-vue'
import { cloneDeep, isEmpty, isString } from 'lodash-es'
const props = withDefaults(defineProps(), {
accept: '.jpg,.jpeg,.png,.webp',
disabled: false,
drag: false,
fileSize: 5, // MB
isPrivate: false,
isVideo: false,
limit: 1,
listType: 'picture-card',
showRemoveIcon: false,
splitSymbol: ',',
tips: ''
})
// 外部传入的文件 url
// 如果是多个文件, 用逗号分割;
// 私域文件只有后面半截, 需要一个拼接流程
const model = defineModel({ required: true })
const modelIsString = computed(() => isString(model.value))
const initialModelValue = cloneDeep(model.value)
const slots = useSlots()
const hasBtn = computed(() => slots?.btnSlot)
const attrs = useAttrs()
const getBindValue = computed(() => {
const obj = {
...attrs,
...props,
}
return obj
})
const { accept, fileSize, isVideo, limit, listType, tips, splitSymbol } = toRefs(getBindValue.value)
const isText = computed(() => listType?.value === 'text')
const fileTypeText = computed(() => isText.value ? '文件' : isVideo.value ? '视频' : '图片')
const emits = defineEmits(['success'])
const fileList = ref([
// {
// name: 'food.jpeg',
// url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg?imageMogr2/thumbnail/360x360/format/webp/quality/100',
// }
])
const contentTypeArr = computed(() => {
let tmpArr = []
if (accept.value) {
const acceptArr = accept.value.split(',').map(str => str.toLowerCase())
tmpArr = acceptArr.map((x) => ALLOW_CONTENT_TYPE_DICT[x])
}
return tmpArr
})
// 默认提示, 用户可以覆盖
const finalTips = computed(() => {
console.log(777, tips);
// 如果存在 tips
if (tips?.value) {
// 进一步判断是否为字符串
if (isString(tips.value)) {
return tips.value
}
if (accept.value === '*') {
return `支持任意格式,所选${fileTypeText.value}大小不能超过 ${fileSize.value} MB,最多上传 ${limit.value} 个文件`
}
return `支持扩展名: ${accept.value},所选${fileTypeText.value}大小不能超过 ${fileSize.value} MB,最多上传 ${limit.value} 个文件`
}
return false
})
// 文件超出数量限制回调
const handleExceed = () => {
ElMessage.error(`最多上传${limit.value}个文件!`)
}
// 限制文件数量
const handleChange = (uploadFile, uploadFiles) => {
fileList.value = fileList.value.slice(-3)
}
// 判断文件类型
const checkFileType = (fileType) => {
if (!contentTypeArr.value.includes(fileType)) {
ElMessage.error(`类型不正确,接收类型为 ${accept.value}`)
return true
}
return false
}
// 判断文件超大
const checkOverSize = (paramsSize) => {
if (paramsSize / ONE_Megabit > fileSize.value) {
ElMessage.error(`所选${fileTypeText.value}大小不能超过 ${fileSize.value} MB`)
return true
}
return false
}
const beforeUpload = (rawFile) => {
console.log(666, rawFile);
doUpload(rawFile)
// 文件限制大小
if (checkOverSize(rawFile.size))
return false
// 文件限制类型
if (accept.value === '*') {
return true
}
else {
if (checkFileType(rawFile.type))
return false
}
// if (checkFileType(rawFile.type)) {
// ElMessage.error('Avatar picture must be JPG format!')
// return false
// } else if (rawFile.size / 1024 / 1024 > 2) {
// ElMessage.error('Avatar picture size can not exceed 2MB!')
// return false
// }
return true
}
const doUpload = (file) => {
multipleUpload(file).then(res => {
if (res.code == 200) {
handleUploadSuccess(res.data, file)
}
})
}
// 上传成功后的回调
const handleUploadSuccess = (res, uploadFile) => {
const uid = uploadFile.uid
const idx = fileList.value.findIndex(item => item?.uid === uid)
const target = fileList.value.find(item => item?.uid === uid)
if (target) {
fileList.value.splice(idx, 1, { ...target, name: res.fileOriginName, url: res.url })
} else {
fileList.value.push({ name: res.fileOriginName, url: res.url })
}
nextTick(() => {
model.value = cloneDeep(fileList.value)
emits('success', uploadFile)
})
}
watchEffect(() => {
// 如果传入的值为空, 清掉 fileList, 这在新建表单的取消过程中会十分有用
if (isEmpty(model.value)) {
fileList.value = []
}
})
// 初始传入的图片回显
onMounted(() => {
const vStr = initialModelValue
const vArr = initialModelValue
if (modelIsString.value) {
const urlList = vStr ? vStr.split(splitSymbol.value) : []
fileList.value = urlList.map(
(fullPath) => ({
name: fullPath,
url: fullPath,
validUrl: fullPath,
}),
)
}
else {
fileList.value = vArr && vArr.map(
(item) => ({
name: item.name,
url: item.url,
validUrl: item.url,
}),
)
}
})
</script>
<style scoped lang="less">
.multiple_uploader {
width: 100%;
.upload_btn {
color: #4E88FF;
background: #F9F9F9;
border: 1px solid #DDD;
}
}
</style>