在现代 Web 开发中,图片上传功能是一个非常常见的需求。本文将介绍如何使用 Vue 3 和 Element Plus 实现一个功能丰富的图片上传组件。我们将涵盖文件选择、图片预览、文件大小和类型验证、批量上传、以及错误处理等功能。
1. 项目结构
首先,我们需要引入 Vue 3 和 Element Plus 的相关依赖。以下是所有代码:
<!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-uploadv-model:file-list="fileList"class="upload-area"action="#":auto-upload="false":on-change="handleFileChange":before-remove="handleBeforeRemove"multipleaccept="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"locktext="上传中..."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 } = Vueconst { ElMessage, ElMessageBox } = ElementPlusconst 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) returntry {loading.value = trueconst 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 = trueawait Promise.all(uploadPromises).then((res) => {for (let i in res) {if (res[i].data.code !== 1001) {flag = falseElMessage({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 Plusapp.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 的组件和图标。
const { createApp, ref } = Vue
const { ElMessage, ElMessageBox } = ElementPlusconst 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"
来显示图片预览。
<el-uploadv-model:file-list="fileList"class="upload-area"action="#":auto-upload="false":on-change="handleFileChange":before-remove="handleBeforeRemove"multipleaccept="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。
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
实现批量上传。
const handleUpload = async () => {if (fileList.value.length === 0) returntry {loading.value = trueconst 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 = trueawait Promise.all(uploadPromises).then((res) => {for (let i in res) {if (res[i].data.code !== 1001) {flag = falseElMessage({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
组件来显示错误信息。
const handleImageError = (file) => {file.status = 'error'
}
3. 总结
通过本文的介绍,我们实现了一个功能丰富的图片上传组件。该组件支持文件选择、图片预览、文件大小和类型验证、批量上传以及错误处理等功能。使用 Vue 3 和 Element Plus 可以大大简化开发过程,提高开发效率。
希望本文对你有所帮助,欢迎在评论区留言讨论!如果对你有帮助,请帮忙点个👍