Vue3 + Element Plus 图片上传功能
完整的图片上传功能的案例
- 使用了 Element Plus 的 el-upload 实现头像上传预览、删除、校验、上传进度、查看大图等功能
完整代码
<template>
<!-- 头像上传 -->
<el-form-item
label="头像"
prop="avatar"
class="flex items-center"
>
/*
newFormInline.avatar 是一个 数组(Array),用来存储当前上传成功的文件对象列表
(每个对象包含 name、url 等属性)
file.url = `http://localhost:8084/api/files/${response.data}`;
这个操作会 修改当前上传成功的文件对象 file 的 url 字段。由于 file 是 fileList 中的一个引用对象,
而 fileList 又和 newFormInline.avatar 是响应式绑定的(通过 v-model:file-list),所以:
newFormInline.avatar 会自动更新,并包含这个带有 url 的文件对象
*/
<el-upload
v-model:file-list="newFormInline.avatar"
drag
multiple
class="pure-upload"
list-type="picture-card"
accept="image/jpeg,image/png,image/gif"
action="http://localhost:8084/api/files/upload"
:limit="1"
:on-exceed="onExceed"
:before-upload="onBefore"
:on-success="uploadSucess"
:on-error="uploadError"
:auto-uploa="false"
>
<EpPlus class="m-auto mt-4" />
<template #file="{ file }">
<div
v-if="file.status == 'ready' || file.status == 'uploading'"
class="mt-[35%]! m-auto"
>
<p class="font-medium">文件上传中</p>
<el-progress
class="mt-2!"
:stroke-width="2"
:text-inside="true"
:show-text="false"
:percentage="file.percentage"
/>
</div>
<div>
<!--v-else @mouseenter.stop="imgDrop(file.uid)"-->
<!--
动态绑定图片的 src 属性为 file.url
这里的 :src 的值必须是file.url
file.url 是 Element Plus 的 <el-upload> 在文件上传成功后自动填充的字段,用于回显已上传的图片地址
-->
<img
class="el-upload-list__item-thumbnail select-none"
:src="file.url"
/>
<span
id="pure-upload-item"
:class="[
'el-upload-list__item-actions',
newFormInline.avatar.length > 1 && 'cursor-move!'
]"
>
<span
title="查看"
class="hover:text-primary"
@click="handlePictureCardPreview(file)"
>
<IconifyIconOffline
:icon="Eye"
class="hover:scale-125 duration-100"
/>
</span>
<span
class="el-upload-list__item-delete"
@click="handleRemove(file)"
>
<span title="移除" class="hover:text-[var(--el-color-danger)]">
<IconifyIconOffline
:icon="Delete"
class="hover:scale-125 duration-100"
/>
</span>
</span>
</span>
</div>
</template>
</el-upload>
<el-image-viewer
v-if="dialogVisible"
:initialIndex="curOpenImgIndex"
:url-list="urlList"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
@close="dialogVisible = false"
@switch="index => (curOpenImgIndex = index)"
/>
</el-form-item>
</template>
// curOpenImgIndex 的值,以便跟踪当前查看的图片
const curOpenImgIndex = ref(0);
// 查看大图预览开关
const dialogVisible = ref(false);
// 包含图片路径
const urlList = computed(() => getKeyList(newFormInline.value.avatar, "url"));
// 文件上传错误处理
const uploadError = ()=> {
ElMessage.error('接口错误,请检查后端')
};
const uploadSucess = (response, file, fileList) => {
console.log("服务器返回:", response);
console.log("当前上传的文件对象:", file);
console.log("当前文件列表:", fileList);
if (response.code === "0") {
file.url = `http://localhost:8084/api/files/${response.data}`;
} else {
message("上传失败");
handleRemove(file); // 删除失败项
}
console.log(newFormInline.value.avatar);
};
/** 团片上传校验 */
const onExceed = () => {
message("最多上传1张图片,请先删除在上传");
};
/** 上传文件前校验 */
const onBefore = file => {
if (!["image/jpeg", "image/png", "image/gif"].includes(file.type)) {
message("只能上传图片");
return false;
}
const isExceed = file.size / 1024 / 1024 > 2;
if (isExceed) {
message(`单个图片大小不能超过2MB`);
return false;
}
};
/** 大图预览 */
const handlePictureCardPreview = (file: UploadFile) => {
console.log(file);
console.log("============");
console.log(newFormInline.value.avatar);
// 获取图片的index 索引
let index = newFormInline.value.avatar.findIndex(
(img: UploadFile) => img.uid === file.uid
);
console.log(index);
console.log("===========================================");
curOpenImgIndex.value = index;
dialogVisible.value = true;
};
/** 移除上传的文件 */
const handleRemove = (file: UploadFile) => {
const index = newFormInline.value.avatar.indexOf(file);
if (index > -1) {
newFormInline.value.avatar.splice(index, 1); // 第二个参数 1
}
};
</script>上传头像的表单项
<el-form-item
label="头像"
prop="avatar"
class="flex items-center"
>
<el-upload
v-model:file-list="newFormInline.avatar"
drag
multiple
class="pure-upload"
list-type="picture-card"
accept="image/jpeg,image/png,image/gif"
action="http://localhost:8084/api/files/upload"
:limit="1"
:on-exceed="onExceed"
:before-upload="onBefore"
:on-success="uploadSucess"
:on-error="uploadError"
:auto-upload="false"
>属性说明
| 属性 | 说明 |
|---|---|
v-model:file-list | 双向绑定上传后的文件列表 |
drag | 开启拖拽上传 |
multiple | 允许选择多个文件 |
list-type="picture-card" | 方形卡片风格,带缩略图 |
accept | 限制上传文件的 MIME 类型 |
action | 文件上传的后端 API 地址 |
limit="1" | 限制最大上传数量(这里限制 1 个头像) |
before-upload | 上传前校验(类型、大小) |
on-exceed | 超出数量限制的回调 |
on-success | 上传成功回调 |
on-error | 上传失败回调 |
auto-upload="false" | 不自动上传,需要手动控制(根据需求调整) |
上传成功处理逻辑
const uploadSucess = (response, file, fileList) => {
console.log("服务器返回:", response);
if (response.code === "0") {
file.url = `http://localhost:8084/api/files/${response.data}`;
} else {
message("上传失败");
handleRemove(file);
}
console.log(newFormInline.value.avatar);
};作用
后端返回的 response.data 是上传后的文件路径(例如文件名) 将该路径拼接成真实可访问的 URL → file.url el-upload 会自动根据 file.url 显示缩略图 如果上传失败自动删除该文件
上传失败处理
const uploadError = () => {
ElMessage.error('接口错误,请检查后端');
};上传文件的前置校验(类型 + 大小)
const onBefore = file => {
if (!["image/jpeg", "image/png", "image/gif"].includes(file.type)) {
message("只能上传图片");
return false;
}
const isExceed = file.size / 1024 / 1024 > 2;
if (isExceed) {
message(`单个图片大小不能超过2MB`);
return false;
}
};- 校验逻辑:
| 校验内容 | 说明 |
|---|---|
| 文件类型 | 只能上传 jpeg/png/gif |
| 文件大小 | 限制在 2MB 内 |
数量超限提醒
const onExceed = () => {
message("最多上传1张图片,请先删除在上传");
};原因
因为我们设置了 :limit="1",超过数量会走这里
删除文件功能
const handleRemove = (file: UploadFile) => {
const index = newFormInline.value.avatar.indexOf(file);
if (index > -1) {
newFormInline.value.avatar.splice(index, 1);
}
};使用
手动删除 file-list 中的文件
点击图片预览大图(el-image-viewer)
// 参数 index 是当前显示图片的索引位置,
// 通过这个回调函数更新 curOpenImgIndex 的值,以便跟踪当前查看的图片
const curOpenImgIndex = ref(0);
// 判断改页面是否进行展示
const dialogVisible = ref(false);
const handlePictureCardPreview = (file: UploadFile) => {
let index = newFormInline.value.avatar.findIndex(
(img: UploadFile) => img.uid === file.uid
);
curOpenImgIndex.value = index;
dialogVisible.value = true;
};<el-image-viewer
v-if="dialogVisible"
:initialIndex="curOpenImgIndex"
:url-list="urlList"
:zoom-rate="1.2"
:max-scale="7"
:min-scale="0.2"
@close="dialogVisible = false"
@switch="index => (curOpenImgIndex = index)"
/>属性说明
| 属性 / 事件 | 类型 | 示例值 | 说明 |
|---|---|---|---|
v-if | Boolean | dialogVisible | 控制图片查看器是否显示。为 true 时显示,为 false 时隐藏。 |
initialIndex | Number | curOpenImgIndex | 打开查看器时默认显示的图片索引(从 0 开始)。用于实现点击哪张显示哪张。 |
url-list | Array | urlList | 所有可预览的大图 URL 列表。查看器可以左右切换这些图片。 |
zoom-rate | Number | 1.2 | 每次放大或缩小时的速率。1.2 表示放大按钮让图片变为原来的 1.2 倍。 |
max-scale | Number | 7 | 最大放大倍数,限制图片不会无限放大。 |
min-scale | Number | 0.2 | 最小缩放倍数,限制图片不会缩小到不可见。 |
@close | Event | dialogVisible = false | 点击关闭按钮触发,用于关闭查看器。 |
@switch | Event | index => (curOpenImgIndex = index) | 用户切换上一张 / 下一张图片时触发,返回当前的图片索引。 |
获取所有图片 URL
const urlList = computed(() => getKeyList(newFormInline.value.avatar, "url"));作用
从上传文件列表中提取所有 url 字段,用于大图查看器
总结
实现的功能 完整代码案例
✔ 头像拖拽上传
✔ 上传前校验(文件类型 + 大小)
✔ 限制上传数量
✔ 上传进度条展示
✔ 上传成功后自动映射 URL
✔ 上传失败自动删除
✔ 缩略图展示
✔ 点击缩略图查看大图
✔ 大图可旋转 / 缩放 / 切换
✔ 支持手动删除图片
版权所有
版权归属:念宇
