图片上传通常不只是把文件传到接口:用户还需要在提交前看到预览图,前端需要拦住错误格式和过大的文件,替换图片或离开页面时也要释放浏览器临时创建的地址。
下面用 el-upload 做一个单图上传的基础版本。
先在选择文件时做校验
把校验抽成一个函数,既可以在选择文件时调用,也可以在真正请求前再执行一次。前端校验主要是避免用户走到无效的提交步骤。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| import { ElMessage } from 'element-plus'
import type { UploadRawFile } from 'element-plus'
const validateImage = (rawFile: UploadRawFile) => {
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp']
const isImage = allowedTypes.includes(rawFile.type)
const isLt2M = rawFile.size / 1024 / 1024 < 2
if (!isImage) {
ElMessage.error('请上传 JPG、PNG 或 WebP 图片')
return false
}
if (!isLt2M) {
ElMessage.error('图片大小不能超过 2MB')
return false
}
return true
}
|
前端校验只是体验层。后端仍然要校验文件类型、大小和实际内容,不能只相信文件扩展名。
用 Object URL 做本地预览
URL.createObjectURL 可以在不上传文件的情况下生成浏览器可访问的临时地址。替换图片时先释放旧地址,避免长时间操作后积累无用的内存引用。选择无效文件时,同时清空上传列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| <script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue'
import type { UploadFile, UploadRawFile, UploadUserFile } from 'element-plus'
const previewUrl = ref('')
const fileList = ref<UploadUserFile[]>([])
let objectUrl = ''
const revokePreviewUrl = () => {
if (!objectUrl) return
URL.revokeObjectURL(objectUrl)
objectUrl = ''
}
const handleChange = (uploadFile: UploadFile) => {
const file = uploadFile.raw
if (!file) return
if (!validateImage(file)) {
fileList.value = []
previewUrl.value = ''
return
}
revokePreviewUrl()
objectUrl = URL.createObjectURL(file)
previewUrl.value = objectUrl
}
onBeforeUnmount(revokePreviewUrl)
</script>
<template>
<el-upload
:auto-upload="false"
:limit="1"
v-model:file-list="fileList"
accept="image/jpeg,image/png,image/webp"
:on-change="handleChange"
>
<el-button type="primary">选择图片</el-button>
</el-upload>
<img v-if="previewUrl" :src="previewUrl" alt="上传图片预览" class="preview" />
</template>
<style scoped>
.preview {
display: block;
width: 160px;
height: 160px;
margin-top: 16px;
object-fit: cover;
border-radius: 8px;
}
</style>
|
如果页面只需要展示已有的远程头像,直接赋值接口返回的 URL 即可;只有本地刚选择的文件才需要 createObjectURL。
当表单同时包含文本和文件时,使用 FormData。浏览器会自动补充 multipart/form-data 的边界,因此不要手动给请求头设置 Content-Type。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| const submitAvatar = async (file: File) => {
const formData = new FormData()
formData.append('avatar', file)
formData.append('nickname', '林达')
const response = await fetch('/api/profile/avatar', {
method: 'POST',
body: formData,
})
if (!response.ok) {
throw new Error('上传失败,请稍后重试')
}
return response.json()
}
|
接口成功后,应使用服务端返回的正式图片地址替换本地预览地址。这样刷新页面后,用户仍然能看到已保存的图片。
容易忽略的细节
- 用
accept 限制文件选择器,但不要把它当成安全校验。 - 单图上传要设置
limit="1",替换时同步清理旧预览。 - 预览图建议设置固定宽高和
object-fit: cover,避免不同尺寸图片撑坏布局。 - 上传过程中禁用重复提交,接口失败时给出明确提示。
小结
一个稳定的图片上传流程,至少包括选择前校验、选择后预览、提交时使用 FormData、成功后替换正式 URL,以及离开页面时释放临时地址。把这些细节补齐后,头像、封面图和后台图片字段都可以复用同一套思路。