在现代 Web 开发中,图片上传功能是一个非常常见的需求。本文将介绍如何使用 Vue 3 和 Element Plus 实现一个功能丰富的图片上传组件。我们将涵盖文件选择、图片预览、文件大小和类型验证、批量上传、以及错误处理等功能。
1. 项目结构
首先,我们需要引入 Vue 3 和 Element Plus 的相关依赖。以下是所有代码:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>图片上传</title>
<link rel="stylesheet" href="https://unpkg.com/element-plus/dist/index.css">
<style>
body {
margin: 0;
padding: 0;
background-color: #f0f2f5;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
min-height: 100vh;
}
.upload-container {
padding: 24px;
background: #fff;
border-radius: 16px;
margin: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.upload-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
}
.upload-title {
margin: 0;
color: #1a1a1a;
font-size: 20px;
font-weight: 600;
}
.upload-area {
margin-bottom: 24px;
}
.button-group {
display: flex;
gap: 12px;
margin-top: 24px;
}
.upload-btn {
flex: 1;
height: 40px;
font-size: 15px;
}
.clear-btn {
height: 40px;
font-size: 15px;
}
/* 调整上传组件样式 */
:deep(.el-upload--picture-card) {
--el-upload-picture-card-size: 90px;
border-radius: 8px;
border: 1px dashed #d9d9d9;
transition: all 0.3s;
}
:deep(.el-upload--picture-card:hover) {
border-color: #409eff;
}
:deep(.el-upload-list--picture-card) {
--el-upload-list-picture-card-size: 90px;
}
:deep(.el-upload-list__item) {
border-radius: 8px;
}
:deep(.el-upload-list__item-thumbnail) {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 8px;
}
.upload-tip {
margin-top: 8px;
color: #909399;
font-size: 13px;
}
.file-count {
color: #606266;
font-size: 14px;
}
/* 添加删除按钮样式 */
.el-upload-list__item-delete {
font-size: 16px;
color: #fff;
background-color: #ff4d4f;
border-radius: 50%;
padding: 4px;
cursor: pointer;
z-index: 9999;
position: absolute;
right: 0;
top: 0;
display: block;
}
/* 添加图片错误样式 */
.upload-error-item {
position: relative;
background-color: #fef0f0;
border: 1px solid #fde2e2;
}
.upload-error-text {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: rgba(255, 77, 79, 0.8);
color: #fff;
font-size: 12px;
padding: 2px 4px;
text-align: center;
}
</style>
</head>
<body>
<div id="app">
<div class="upload-container">
<div class="upload-header">
<h2 class="upload-title">图片上传</h2>
<span class="file-count" v-if="fileList.length > 0">
已选择 {{ fileList.length }} 张图片
</span>
</div>
<el-upload
v-model:file-list="fileList"
class="upload-area"
action="#"
:auto-upload="false"
:on-change="handleFileChange"
:before-remove="handleBeforeRemove"
multiple
accept="image/*"
:limit="100"
list-type="picture-card">
<template #default>
<el-icon style="font-size: 24px; color: #909399;"><Plus /></el-icon>
<div style="margin-top: 8px; color: #909399;">点击上传</div>
</template>
<template #file="{ file }">
<div :class="{'upload-error-item': file.status === 'error'}">
<img
v-if="file.status !== 'error'"
class="el-upload-list__item-thumbnail"
:src="file.url"
alt=""
@error="handleImageError(file)"
/>
<div v-else class="upload-error-text">图片加载失败</div>
<el-icon
class="el-upload-list__item-delete"
v-if="file.status !== 'error'"
@click.stop="handleRemoveFile(file)">
<Close />
</el-icon>
</div>
</template>
</el-upload>
<p class="upload-tip">支持上传JPG/PNG格式图片,单次最多可上传100张</p>
<div class="button-group">
<el-button
type="primary"
class="upload-btn"
:disabled="fileList.length === 0"
@click="handleUpload">
<el-icon style="margin-right: 4px"><Upload /></el-icon>
开始上传
</el-button>
<el-button
type="danger"
class="clear-btn"
:disabled="fileList.length === 0"
@click="handleClearFiles">
<el-icon style="margin-right: 4px"><Delete /></el-icon>
清空
</el-button>
</div>
</div>
<el-loading
v-model:visible="loading"
lock
text="上传中..."
background="rgba(255, 255, 255, 0.9)"/>
</div>
<!-- 引入依赖 -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script src="https://unpkg.com/element-plus"></script>
<script src="https://unpkg.com/@element-plus/icons-vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
const { createApp, ref } = Vue
const { ElMessage, ElMessageBox } = ElementPlus
const app = createApp({
setup() {
// 获取URL参数
const urlParams = new URLSearchParams(window.location.search)
const uploadParams = {
batchNo: urlParams.get('batchNo') || '',
SN: urlParams.get('SN') || ''
}
const fileList = ref([])
const loading = ref(false)
const handleClearFiles = () => {
ElMessageBox.confirm(
'确定要清空所有已选择的图片吗?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
fileList.value = []
ElMessage.success('已清空所有图片')
}).catch(() => {})
}
const handleUpload = async () => {
if (fileList.value.length === 0) return
try {
loading.value = true
const uploadPromises = fileList.value.map(file => {
const formData = new FormData()
formData.append('file', file.raw)
let url = '你的请求地址'
return axios.post(url + uploadParams.batchNo, formData, {
headers: {
'Content-Type': 'multipart/form-data',
'SN': uploadParams.SN
}
})
})
let flag = true
await Promise.all(uploadPromises).then((res) => {
for (let i in res) {
if (res[i].data.code !== 1001) {
flag = false
ElMessage({
type: 'error',
message: res[i].data.msg,
duration: 2000
})
}
}
})
if (flag) {
ElMessage({
type: 'success',
message: '上传成功',
duration: 2000
})
}
fileList.value = []
} catch (error) {
ElMessage({
type: 'error',
message: '上传失败,请重试',
duration: 3000
})
} finally {
loading.value = false
}
}
// 处理图片加载错误
const handleImageError = (file) => {
file.status = 'error'
}
// 处理删除单个文件
const handleRemoveFile = (file) => {
ElMessageBox.confirm(
'确定要删除这张图片吗?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
const index = fileList.value.findIndex(item => item.uid === file.uid)
if (index !== -1) {
fileList.value.splice(index, 1)
ElMessage.success('已删除')
}
}).catch(() => {})
}
// 阻止默认的删除行为
const handleBeforeRemove = () => {
return false
}
// 修改文件验证逻辑
const handleFileChange = (uploadFile) => {
// 检查文件类型
if (!uploadFile.raw.type.includes('image/')) {
ElMessage.error('只能上传图片文件!')
const index = fileList.value.findIndex(item => item.uid === uploadFile.uid)
if (index !== -1) {
fileList.value.splice(index, 1)
}
return false
}
// 检查文件大小(限制为5MB)
if (uploadFile.raw.size / 1024 / 1024 > 5) {
ElMessage.error('图片大小不能超过5MB!')
const index = fileList.value.findIndex(item => item.uid === uploadFile.uid)
if (index !== -1) {
fileList.value.splice(index, 1)
}
return false
}
// 验证图片是否可以正常加载
const img = new Image()
img.src = URL.createObjectURL(uploadFile.raw)
img.onerror = () => {
ElMessage.error('图片格式错误或已损坏!')
const index = fileList.value.findIndex(item => item.uid === uploadFile.uid)
if (index !== -1) {
fileList.value.splice(index, 1)
}
}
}
return {
fileList,
loading,
handleFileChange,
handleBeforeRemove,
handleRemoveFile,
handleImageError,
handleClearFiles,
handleUpload
}
}
})
// 注册Element Plus
app.use(ElementPlus)
// 注册需要的图标
app.component('Plus', ElementPlusIconsVue.Plus)
app.component('Upload', ElementPlusIconsVue.Upload)
app.component('Delete', ElementPlusIconsVue.Delete)
app.component('Close', ElementPlusIconsVue.Close)
// 挂载应用
app.mount('#app')
</script>
</body>
</html>
2. 实现图片上传功能
2.1 初始化 Vue 应用
我们使用 Vue 3 的 createApp
方法来初始化应用,并引入 Element Plus 的组件和图标。
javascript
const { createApp, ref } = Vue
const { ElMessage, ElMessageBox } = ElementPlus
const app = createApp({
setup() {
// 逻辑部分省略
}
})
// 注册Element Plus
app.use(ElementPlus)
// 注册需要的图标
app.component('Plus', ElementPlusIconsVue.Plus)
app.component('Upload', ElementPlusIconsVue.Upload)
app.component('Delete', ElementPlusIconsVue.Delete)
app.component('Close', ElementPlusIconsVue.Close)
// 挂载应用
app.mount('#app')
2.2 文件选择与预览
我们使用 Element Plus 的 el-upload
组件来实现文件选择和预览功能。通过 v-model:file-list
绑定文件列表,并使用 list-type="picture-card"
来显示图片预览。
html
<el-upload
v-model:file-list="fileList"
class="upload-area"
action="#"
:auto-upload="false"
:on-change="handleFileChange"
:before-remove="handleBeforeRemove"
multiple
accept="image/*"
:limit="100"
list-type="picture-card">
<template #default>
<el-icon style="font-size: 24px; color: #909399;"><Plus /></el-icon>
<div style="margin-top: 8px; color: #909399;">点击上传</div>
</template>
<template #file="{ file }">
<div :class="{'upload-error-item': file.status === 'error'}">
<img
v-if="file.status !== 'error'"
class="el-upload-list__item-thumbnail"
:src="file.url"
alt=""
@error="handleImageError(file)"
/>
<div v-else class="upload-error-text">图片加载失败</div>
<el-icon
class="el-upload-list__item-delete"
v-if="file.status !== 'error'"
@click.stop="handleRemoveFile(file)">
<Close />
</el-icon>
</div>
</template>
</el-upload>
2.3 文件验证
在上传文件之前,我们需要对文件进行验证,确保文件类型为图片且大小不超过 5MB。
javascript
const handleFileChange = (uploadFile) => {
// 检查文件类型
if (!uploadFile.raw.type.includes('image/')) {
ElMessage.error('只能上传图片文件!')
const index = fileList.value.findIndex(item => item.uid === uploadFile.uid)
if (index !== -1) {
fileList.value.splice(index, 1)
}
return false
}
// 检查文件大小(限制为5MB)
if (uploadFile.raw.size / 1024 / 1024 > 5) {
ElMessage.error('图片大小不能超过5MB!')
const index = fileList.value.findIndex(item => item.uid === uploadFile.uid)
if (index !== -1) {
fileList.value.splice(index, 1)
}
return false
}
// 验证图片是否可以正常加载
const img = new Image()
img.src = URL.createObjectURL(uploadFile.raw)
img.onerror = () => {
ElMessage.error('图片格式错误或已损坏!')
const index = fileList.value.findIndex(item => item.uid === uploadFile.uid)
if (index !== -1) {
fileList.value.splice(index, 1)
}
}
}
2.4 批量上传
我们使用 axios
来发送上传请求,并通过 Promise.all
实现批量上传。
javascript
const handleUpload = async () => {
if (fileList.value.length === 0) return
try {
loading.value = true
const uploadPromises = fileList.value.map(file => {
const formData = new FormData()
formData.append('file', file.raw)
let url = '你的请求地址'
return axios.post(url + uploadParams.batchNo, formData, {
headers: {
'Content-Type': 'multipart/form-data',
'SN': uploadParams.SN
}
})
})
let flag = true
await Promise.all(uploadPromises).then((res) => {
for (let i in res) {
if (res[i].data.code !== 1001) {
flag = false
ElMessage({
type: 'error',
message: res[i].data.msg,
duration: 2000
})
}
}
})
if (flag) {
ElMessage({
type: 'success',
message: '上传成功',
duration: 2000
})
}
fileList.value = []
} catch (error) {
ElMessage({
type: 'error',
message: '上传失败,请重试',
duration: 3000
})
} finally {
loading.value = false
}
}
2.5 错误处理
在上传过程中,可能会遇到各种错误,如网络错误、文件格式错误等。我们通过 ElMessage
组件来显示错误信息。
javascript
const handleImageError = (file) => {
file.status = 'error'
}
3. 总结
通过本文的介绍,我们实现了一个功能丰富的图片上传组件。该组件支持文件选择、图片预览、文件大小和类型验证、批量上传以及错误处理等功能。使用 Vue 3 和 Element Plus 可以大大简化开发过程,提高开发效率。
希望本文对你有所帮助,欢迎在评论区留言讨论!如果对你有帮助,请帮忙点个👍