2025-12-05 10:17:26 +08:00
|
|
|
|
<!-- 单图上传组件 -->
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<el-upload
|
|
|
|
|
|
class="single-upload"
|
|
|
|
|
|
list-type="picture-card"
|
|
|
|
|
|
:show-file-list="false"
|
|
|
|
|
|
:accept="props.accept"
|
|
|
|
|
|
:before-upload="handleBeforeUpload"
|
|
|
|
|
|
:http-request="handleUpload"
|
|
|
|
|
|
:on-success="onSuccess"
|
|
|
|
|
|
:on-error="onError"
|
|
|
|
|
|
>
|
|
|
|
|
|
<template #default>
|
|
|
|
|
|
<template v-if="modelValue">
|
|
|
|
|
|
<el-image
|
|
|
|
|
|
class="single-upload__image"
|
|
|
|
|
|
:src="modelValue"
|
|
|
|
|
|
:preview-src-list="[modelValue]"
|
|
|
|
|
|
@click.stop="handlePreview"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<el-icon class="single-upload__delete-btn" @click.stop="handleDelete">
|
|
|
|
|
|
<CircleCloseFilled />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<el-icon>
|
|
|
|
|
|
<Plus />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-upload>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-12-11 17:13:37 +08:00
|
|
|
|
import { UploadRawFile, UploadRequestOptions } from 'element-plus'
|
|
|
|
|
|
import FileAPI, { FileInfo } from '@/api/file-api'
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 请求携带的额外参数
|
|
|
|
|
|
*/
|
|
|
|
|
|
data: {
|
|
|
|
|
|
type: Object,
|
|
|
|
|
|
default: () => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
return {}
|
|
|
|
|
|
}
|
2025-12-05 10:17:26 +08:00
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传文件的参数名
|
|
|
|
|
|
*/
|
|
|
|
|
|
name: {
|
|
|
|
|
|
type: String,
|
2025-12-11 17:13:37 +08:00
|
|
|
|
default: 'file'
|
2025-12-05 10:17:26 +08:00
|
|
|
|
},
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 最大文件大小(单位:M)
|
|
|
|
|
|
*/
|
|
|
|
|
|
maxFileSize: {
|
|
|
|
|
|
type: Number,
|
2025-12-11 17:13:37 +08:00
|
|
|
|
default: 10
|
2025-12-05 10:17:26 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传图片格式,默认支持所有图片(image/*),指定格式示例:'.png,.jpg,.jpeg,.gif,.bmp'
|
|
|
|
|
|
*/
|
|
|
|
|
|
accept: {
|
|
|
|
|
|
type: String,
|
2025-12-11 17:13:37 +08:00
|
|
|
|
default: 'image/*'
|
2025-12-05 10:17:26 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 自定义样式,用于设置组件的宽度和高度等其他样式
|
|
|
|
|
|
*/
|
|
|
|
|
|
style: {
|
|
|
|
|
|
type: Object,
|
|
|
|
|
|
default: () => {
|
|
|
|
|
|
return {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
width: '150px',
|
|
|
|
|
|
height: '150px'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const modelValue = defineModel('modelValue', {
|
2025-12-05 10:17:26 +08:00
|
|
|
|
type: String,
|
2025-12-11 17:13:37 +08:00
|
|
|
|
default: () => ''
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 限制用户上传文件的格式和大小
|
|
|
|
|
|
*/
|
|
|
|
|
|
function handleBeforeUpload(file: UploadRawFile) {
|
|
|
|
|
|
// 校验文件类型:虽然 accept 属性限制了用户在文件选择器中可选的文件类型,但仍需在上传时再次校验文件实际类型,确保符合 accept 的规则
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const acceptTypes = props.accept.split(',').map((type) => type.trim())
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 检查文件格式是否符合 accept
|
|
|
|
|
|
const isValidType = acceptTypes.some((type) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
if (type === 'image/*') {
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 如果是 image/*,检查 MIME 类型是否以 "image/" 开头
|
2025-12-11 17:13:37 +08:00
|
|
|
|
return file.type.startsWith('image/')
|
|
|
|
|
|
} else if (type.startsWith('.')) {
|
2025-12-05 10:17:26 +08:00
|
|
|
|
// 如果是扩展名 (.png, .jpg),检查文件名是否以指定扩展名结尾
|
2025-12-11 17:13:37 +08:00
|
|
|
|
return file.name.toLowerCase().endsWith(type)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 如果是具体的 MIME 类型 (image/png, image/jpeg),检查是否完全匹配
|
2025-12-11 17:13:37 +08:00
|
|
|
|
return file.type === type
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
if (!isValidType) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.warning(`上传文件的格式不正确,仅支持:${props.accept}`)
|
|
|
|
|
|
return false
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 限制文件大小
|
|
|
|
|
|
if (file.size > props.maxFileSize * 1024 * 1024) {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.warning('上传图片不能大于' + props.maxFileSize + 'M')
|
|
|
|
|
|
return false
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
2025-12-11 17:13:37 +08:00
|
|
|
|
return true
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
* 上传图片
|
|
|
|
|
|
*/
|
|
|
|
|
|
function handleUpload(options: UploadRequestOptions) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const file = options.file
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
2025-12-11 17:13:37 +08:00
|
|
|
|
const formData = new FormData()
|
|
|
|
|
|
formData.append(props.name, file)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 处理附加参数
|
|
|
|
|
|
Object.keys(props.data).forEach((key) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
formData.append(key, props.data[key])
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
FileAPI.upload(formData)
|
|
|
|
|
|
.then((data) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
resolve(data)
|
2025-12-05 10:17:26 +08:00
|
|
|
|
})
|
|
|
|
|
|
.catch((error) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
reject(error)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 预览图片
|
|
|
|
|
|
*/
|
|
|
|
|
|
function handlePreview() {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
console.log('预览图片,停止冒泡')
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除图片
|
|
|
|
|
|
*/
|
|
|
|
|
|
function handleDelete() {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
modelValue.value = ''
|
2025-12-05 10:17:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传成功回调
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param fileInfo 上传成功后的文件信息
|
|
|
|
|
|
*/
|
|
|
|
|
|
const onSuccess = (fileInfo: FileInfo) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
ElMessage.success('上传成功')
|
|
|
|
|
|
modelValue.value = fileInfo.url
|
|
|
|
|
|
}
|
2025-12-05 10:17:26 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传失败回调
|
|
|
|
|
|
*/
|
|
|
|
|
|
const onError = (error: any) => {
|
2025-12-11 17:13:37 +08:00
|
|
|
|
console.log('onError')
|
|
|
|
|
|
ElMessage.error('上传失败: ' + error.message)
|
|
|
|
|
|
}
|
2025-12-05 10:17:26 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
|
|
|
:deep(.el-upload--picture-card) {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: v-bind("props.style.width ?? '150px'");
|
|
|
|
|
|
height: v-bind("props.style.height ?? '150px'");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.single-upload {
|
|
|
|
|
|
&__image {
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&__delete-btn {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 1px;
|
|
|
|
|
|
right: 1px;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
color: #ff7901;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
background: #fff;
|
|
|
|
|
|
border-radius: 100%;
|
|
|
|
|
|
|
|
|
|
|
|
:hover {
|
|
|
|
|
|
color: #ff4500;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|