Element Plus 的 el-upload 图片上传组件使用指南
Element Plus 的 el-upload 组件是一个功能强大的文件上传组件,支持图片上传、拖拽上传、预览等功能。下面详细介绍其使用方式。
基本安装与引入
npm install element-plus
// 完整引入
import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
const app = createApp(App)
app.use(ElementPlus)
// 或按需引入
import { ElUpload, ElButton, ElIcon } from 'element-plus'
import { UploadFilled } from '@element-plus/icons-vue'
基本图片上传示例
<template>
<el-upload
action="https://jsonplaceholder.typicode.com/posts/"
:on-success="handleSuccess"
:on-error="handleError"
list-type="picture"
>
<el-button type="primary">点击上传</el-button>
</el-upload>
</template>
<script setup>
const handleSuccess = (response, file, fileList) => {
console.log('上传成功', response, file)
}
const handleError = (error, file, fileList) => {
console.error('上传失败', error)
}
</script>
完整功能示例
<template>
<div class="upload-demo">
<h3>图片上传示例</h3>
<!-- 基础图片上传 -->
<div class="section">
<h4>1. 基础图片上传</h4>
<el-upload
class="upload-demo"
action="https://jsonplaceholder.typicode.com/posts/"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:on-exceed="handleExceed"
:on-success="handleSuccess"
:on-error="handleError"
:before-upload="beforeUpload"
list-type="picture"
:limit="3"
:file-list="fileList"
:headers="headers"
:data="uploadData"
accept=".jpg,.jpeg,.png,.gif"
>
<el-button type="primary">点击上传</el-button>
<template #tip>
<div class="el-upload__tip">
只能上传jpg/png/gif文件,且不超过2MB
</div>
</template>
</el-upload>
</div>
<!-- 拖拽上传 -->
<div class="section">
<h4>2. 拖拽上传</h4>
<el-upload
class="upload-dragger"
action="https://jsonplaceholder.typicode.com/posts/"
drag
multiple
:on-success="handleSuccess"
list-type="picture"
accept="image/*"
>
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
<div class="el-upload__text">
将文件拖到此处,或<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
支持拖拽上传jpg/png/gif文件
</div>
</template>
</el-upload>
</div>
<!-- 头像上传 -->
<div class="section">
<h4>3. 头像上传</h4>
<el-upload
class="avatar-uploader"
action="https://jsonplaceholder.typicode.com/posts/"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
>
<img v-if="imageUrl" :src="imageUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
</div>
<!-- 自定义上传行为 -->
<div class="section">
<h4>4. 自定义上传(手动上传)</h4>
<el-upload
ref="uploadRef"
class="upload-demo"
action="#"
:auto-upload="false"
:on-change="handleChange"
:file-list="customFileList"
list-type="picture"
multiple
>
<template #trigger>
<el-button type="primary">选择文件</el-button>
</template>
<el-button class="ml-3" type="success" @click="submitUpload">
上传到服务器
</el-button>
<template #tip>
<div class="el-upload__tip">
选择文件后需要手动点击上传按钮
</div>
</template>
</el-upload>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { UploadFilled, Plus } from '@element-plus/icons-vue'
// 响应式数据
const fileList = ref([
{
name: 'food.jpeg',
url: 'https://fuss10.elemecdn.com/3/63/4e7f3a15429bfda99bce42a18cdd1jpeg.jpeg'
}
])
const customFileList = ref([])
const uploadRef = ref()
const imageUrl = ref('')
// 请求头和数据
const headers = reactive({
Authorization: 'Bearer your-token-here'
})
const uploadData = reactive({
userId: 123,
category: 'avatar'
})
// 事件处理函数
const handleRemove = (file, fileList) => {
console.log(file, fileList)
}
const handlePreview = (file) => {
console.log(file)
// 实际项目中可以打开图片预览弹窗
// previewImage(file.url)
}
const handleExceed = (files, fileList) => {
ElMessage.warning(
`当前限制选择 3 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.length} 个文件`
)
}
const beforeRemove = (file, fileList) => {
return ElMessageBox.confirm(
`确定删除 ${file.name}?`
).then(() => true).catch(() => false)
}
const handleSuccess = (response, file, fileList) => {
console.log('上传成功:', response)
ElMessage.success('上传成功')
// 实际项目中,可能需要从响应中获取图片URL
// if (response.code === 200) {
// file.url = response.data.url
// }
}
const handleError = (error, file, fileList) => {
console.error('上传失败:', error)
ElMessage.error('上传失败')
}
const beforeUpload = (file) => {
const isImage = file.type.startsWith('image/')
const isLt2M = file.size / 1024 / 1024 < 2
if (!isImage) {
ElMessage.error('只能上传图片文件!')
return false
}
if (!isLt2M) {
ElMessage.error('图片大小不能超过 2MB!')
return false
}
return true
}
// 头像上传处理
const handleAvatarSuccess = (response, file) => {
imageUrl.value = URL.createObjectURL(file.raw)
// 实际项目中应使用服务器返回的URL
// imageUrl.value = response.data.url
}
const beforeAvatarUpload = (file) => {
const isImage = file.type.startsWith('image/')
const isLt1M = file.size / 1024 / 1024 < 1
if (!isImage) {
ElMessage.error('只能上传图片文件!')
return false
}
if (!isLt1M) {
ElMessage.error('头像图片大小不能超过 1MB!')
return false
}
return true
}
// 自定义上传
const handleChange = (file, fileList) => {
customFileList.value = fileList
}
const submitUpload = () => {
uploadRef.value.submit()
// 实际项目中可能需要自己实现上传逻辑
// customFileList.value.forEach(file => {
// const formData = new FormData()
// formData.append('file', file.raw)
// // 发送请求
// axios.post('/upload', formData).then(response => {
// console.log('上传成功:', response)
// })
// })
}
</script>
<style scoped>
.upload-demo {
padding: 20px;
}
.section {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
}
h3 {
color: #303133;
margin-bottom: 20px;
}
h4 {
color: #606266;
margin-bottom: 15px;
}
.avatar-uploader .avatar {
width: 178px;
height: 178px;
display: block;
}
.avatar-uploader .el-upload {
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
line-height: 178px;
}
.upload-dragger {
width: 360px;
}
</style>
实际项目中与后端API集成
<template>
<el-upload
ref="uploadRef"
class="avatar-uploader"
:action="uploadUrl"
:headers="headers"
:data="uploadParams"
:show-file-list="false"
:on-success="handleSuccess"
:on-error="handleError"
:before-upload="beforeUpload"
:on-progress="handleProgress"
>
<!-- 上传区域内容 -->
</el-upload>
</template>
<script setup>
import { ref, computed } from 'vue'
import { useUserStore } from '@/stores/user'
import axios from 'axios'
const userStore = useUserStore()
const uploadRef = ref()
// 计算属性:动态获取上传URL
const uploadUrl = computed(() => {
return import.meta.env.VITE_API_BASE_URL + '/api/upload/image'
})
// 请求头(携带token)
const headers = computed(() => {
return {
'Authorization': `Bearer ${userStore.token}`,
'Content-Type': 'multipart/form-data'
}
})
// 上传参数
const uploadParams = {
type: 'avatar',
timestamp: Date.now()
}
// 自定义上传函数(不使用action属性)
const customUpload = async (options) => {
const { file, onProgress, onSuccess, onError } = options
const formData = new FormData()
formData.append('file', file)
formData.append('type', 'avatar')
try {
const response = await axios.post('/api/upload/image', formData, {
headers: {
'Authorization': `Bearer ${userStore.token}`,
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
)
onProgress({ percent })
}
})
onSuccess(response.data)
} catch (error) {
onError(error)
}
}
// 分片上传示例
const chunkUpload = async (file) => {
const chunkSize = 2 * 1024 * 1024 // 2MB
const totalChunks = Math.ceil(file.size / chunkSize)
const fileId = Date.now() + '-' + Math.random().toString(36).substr(2)
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize
const end = Math.min(start + chunkSize, file.size)
const chunk = file.slice(start, end)
const formData = new FormData()
formData.append('file', chunk)
formData.append('chunkIndex', i)
formData.append('totalChunks', totalChunks)
formData.append('fileId', fileId)
formData.append('fileName', file.name)
await axios.post('/api/upload/chunk', formData, {
headers: {
'Authorization': `Bearer ${userStore.token}`
}
})
// 更新进度
const progress = Math.round(((i + 1) / totalChunks) * 100)
// 更新进度显示
}
// 所有分片上传完成后,通知服务器合并
await axios.post('/api/upload/merge', {
fileId,
fileName: file.name,
totalChunks
}, {
headers: {
'Authorization': `Bearer ${userStore.token}`
}
})
}
</script>
常见属性说明
| 属性 |
说明 |
类型 |
默认值 |
|---|
| action |
上传的地址 |
string |
- |
| headers |
设置上传的请求头部 |
object |
- |
| data |
上传时附带的额外参数 |
object |
- |
| multiple |
是否支持多选文件 |
boolean |
false |
| accept |
接受上传的文件类型 |
string |
- |
| limit |
最大允许上传个数 |
number |
- |
| auto-upload |
是否在选取文件后立即上传 |
boolean |
true |
| file-list |
上传的文件列表 |
array |
[] |
| list-type |
文件列表的类型 |
string |
text |
| drag |
是否启用拖拽上传 |
boolean |
false |
| disabled |
是否禁用 |
boolean |
false |
| on-preview |
点击文件列表中已上传的文件时的钩子 |
function |
- |
| on-remove |
文件列表移除文件时的钩子 |
function |
- |
| on-success |
文件上传成功时的钩子 |
function |
- |
| on-error |
文件上传失败时的钩子 |
function |
- |
| on-progress |
文件上传时的钩子 |
function |
- |
| on-change |
文件状态改变时的钩子 |
function |
- |
| before-upload |
上传文件之前的钩子 |
function |
- |
| before-remove |
删除文件之前的钩子 |
function |
- |
注意事项
跨域问题:如果上传地址与当前站点不同域,需要后端支持CORS
文件大小限制:需要在
before-upload中手动校验
文件类型限制:使用
accept属性或
before-upload中校验
上传进度:需要后端支持返回进度信息
安全性:上传的文件需要做安全检查,防止恶意文件上传
图片预览:大图片建议使用缩略图,避免性能问题
总结
Element Plus 的 el-upload 组件提供了丰富的图片上传功能,通过合理配置属性和事件处理函数,可以满足大多数图片上传需求。在实际项目中,通常需要结合后端API进行适当的定制和优化。