upload 上传组件源码简单分享,主要从以下几个方面:
1、upload 组件页面结构。
2、upload 组件属性。
3、upload 组件 slot。
4、upload 组件方法。
一、组件页面结构。
二、组件属性。
2.1 action 必选参数,上传的地址,类型 string,默认无。
XMLHttpRequest 对象的 api 地址:XMLHttpRequest - Web API | MDN
2.2 headers 设置上传的请求头部,类型 object,无默认值。
2.3 multiple 是否支持多选文件,类型 boolean,无默认值。
2.4 data 上传时附带的额外参数,类型 object,无默认值。
2.5 name 上传的文件字段名,类型 string,默认 file。
2.6 with-credentials 支持发送 cookie 凭证信息,类型 boolean,默认 false。
2.7 show-file-list 是否显示已上传文件列表,类型 boolean,默认 true。
2.8 drag 是否启用拖拽上传,类型 boolean,默认 false。
2.9 accept 接受上传的文件类型(thumbnail-mode 模式下此参数无效),类型 string,无默认值。
2.10 on-preview 点击文件列表中已上传的文件时的钩子,类型 function(file),无默认值。
2.11 on-remove 文件列表移除文件时的钩子,类型 function(file, fileList),无默认值。
2.12 on-success 文件上传成功时的钩子,类型 function(response, file, fileList),无默认值。
// on-success 回调函数主要针对于自动上传使用,调用 upload 组件默认上传方法
// 以下是使用手动上传时,on-success 回调函数的使用
<template>
<div>
<el-upload
ref="uploadRef"
:auto-upload="false"
:on-change="handleAvatarChange"
:show-file-list="true"
class="upload-avatar-wrapper"
accept="image/*"
:action="uploadUrl"
:on-preview="onPreview"
:on-remove="onRemove"
:on-success="onSuccess"
>
<!-- 显示当前已选/已上传的图片 -->
<div class="avatar-preview-list">
<img
v-if="tempPreviews.url"
:src="tempPreviews.url"
class="avatar"
style="width:60px;height:60px;margin-right:10px;border:1px #ddd solid;object-fit: cover;"
/>
</div>
<div
v-if="!tempPreviews.url"
class="avatar-uploader-icon"
style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
>
<i class="el-icon-plus"></i>
</div>
</el-upload>
<el-button @click="customUpload">上传</el-button>
</div>
</template>
<script>
export default {
data() {
return {
token:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ3Mjc5NzA2LCJleHAiOjE3NDc4ODQ1MDZ9.KfSi5zAq3c45Gjv6tkH5is5LtDx_74X99bb5fR_YXPc',
form: {
operName: 'super001',
password: '123456',
avatar: '',
},
tempPreviews: {}, // 存储预览图片对象
selectedFile: null, // 当前选择的文件
serverUrl: 'http://localhost:3000',
uploadUrl: '/api/user/uploadAvatar',
};
},
methods: {
onSuccess(response, file, fileList) {
console.log('onSuccess response::', response);
this.$message.success('上传成功');
this.form.avatar = `${this.serverUrl}${response.url}`;
},
onRemove(file, fileList) {
this.tempPreviews = {};
this.selectedFile = null;
},
onPreview(file) {
console.log('onPreview file::', file);
},
handleAvatarChange(file, fileList) {
// 如果已经有文件被选中,则阻止继续选择
if (this.selectedFile) {
this.$message.warning('只能选择一个文件');
return false;
}
const reader = new FileReader();
reader.onload = (e) => {
this.tempPreviews = {
url: e.target.result,
uid: file.uid,
};
};
if (file.raw) {
reader.readAsDataURL(file.raw);
this.selectedFile = file.raw;
}
},
customUpload() {
if (!this.selectedFile) {
this.$message.warning('请先选择一个文件');
return;
}
const formData = new FormData();
formData.append('avatar', this.selectedFile); // 单个文件
formData.append('operName', this.form.operName);
formData.append('password', this.form.password);
fetch(this.uploadUrl, {
method: 'POST',
headers: {
Authorization: `Bearer ${this.token}`,
},
body: formData,
})
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log('Success:', data);
this.onSuccess(data, { raw: this.selectedFile }, [
{ raw: this.selectedFile },
]);
this.selectedFile = null;
this.tempPreviews = {};
})
.catch((error) => {
console.error('Error:', error);
this.$message.error('上传失败');
});
},
},
};
</script>
<style scoped>
.upload-avatar-wrapper {
width: 60px;
height: 60px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.upload-avatar-wrapper:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
}
.avatar {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
// 自动上传,调用upload 默认的上传方法
<template>
<div>
<el-upload
ref="uploadRef"
:auto-upload="true"
:on-change="handleAvatarChange"
:show-file-list="false"
class="upload-avatar-wrapper"
accept="image/*"
action="/api/user/uploadAvatar"
name="avatar"
:headers="{
'Authorization': `Bearer ${token}`
}"
:data="{
operName: 'super001',
password: '123456',
}"
:multiple="false"
:limit="1"
:on-exceed="handleExceed"
:with-credentials="false"
:on-success="handleSuccess"
:on-error="handleError"
:on-remove="handleRemove"
>
<!-- 显示当前头像 -->
<div class="avatar-preview">
<img
v-if="form.avatar || tempPreview"
:src="form.avatar || tempPreview"
class="avatar"
style="width:60px;height:60px;margin-right:10px;border:1px #ddd solid;object-fit: cover;"
/>
</div>
<!-- 触发上传按钮 -->
<div
class="avatar-uploader-icon"
style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
>
<i class="el-icon-plus"></i>
</div>
</el-upload>
</div>
</template>
<script>
export default {
data() {
return {
token:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ3Mjc5NzA2LCJleHAiOjE3NDc4ODQ1MDZ9.KfSi5zAq3c45Gjv6tkH5is5LtDx_74X99bb5fR_YXPc',
serverUrl: 'http://localhost:3000',
form: {
operName: 'super001',
password: '123456',
avatar: '', // 单个字符串存储头像URL
},
tempPreview: null, // 临时存放本地预览
};
},
methods: {
handleExceed(files, fileList) {
this.$message.warning('只能选择一个文件');
},
handleAvatarChange(file, fileList) {
const reader = new FileReader();
reader.onload = (e) => {
// 设置临时预览图
this.tempPreview = e.target.result;
};
if (file.raw) {
reader.readAsDataURL(file.raw);
}
},
handleSuccess(response, file, fileList) {
this.$message.success('上传成功');
const uploadedUrl = response.url;
if (uploadedUrl) {
this.form.avatar = `${this.serverUrl}${uploadedUrl}`;
this.tempPreview = null; // 清除临时预览图
}
},
handleError(err, file, fileList) {
console.error('Upload Error:', err);
this.$message.error('上传失败');
this.tempPreview = null; // 出错时清除临时预览图
},
handleRemove(file, fileList) {
this.form.avatar = ''; // 清空 form.avatar
this.tempPreview = null; // 清除临时预览图
},
},
};
</script>
<style scoped>
.avatar-preview {
display: flex;
justify-content: flex-start;
margin-bottom: 10px;
}
.avatar {
width: 60px;
height: 60px;
object-fit: cover;
border: 1px solid #ddd;
margin-right: 10px;
}
.upload-avatar-wrapper {
width: 60px;
height: 60px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.upload-avatar-wrapper:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
}
</style>
2.13 on-error 文件上传失败时的钩子,类型 function(err, file, fileList),无默认值。
2.14 on-progress 文件上传时的钩子,类型 function(event, file, fileList),无默认值。
// 增加上传进度条
<template>
<div>
<!-- 文件上传组件 -->
<el-upload
ref="uploadRef"
:auto-upload="true"
:on-change="handleAvatarChange"
:show-file-list="false"
class="upload-avatar-wrapper"
accept="image/*"
action="/api/user/uploadAvatar"
name="avatar"
:headers="{
'Authorization': `Bearer ${token}`
}"
:data="{
operName: 'super001',
password: '123456',
}"
:multiple="false"
:limit="1"
:on-exceed="handleExceed"
:with-credentials="false"
:on-success="handleSuccess"
:on-error="handleError"
:on-remove="handleRemove"
:on-progress="handleProgress"
>
<!-- 显示当前头像 -->
<div class="avatar-preview">
<img
v-if="form.avatar || tempPreview"
:src="form.avatar || tempPreview"
class="avatar"
style="width:60px;height:60px;margin-right:10px;border:1px #ddd solid;object-fit: cover;"
/>
</div>
<!-- 触发上传按钮 -->
<div
class="avatar-uploader-icon"
style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
>
<i class="el-icon-plus"></i>
</div>
</el-upload>
<!-- 进度条 -->
<el-progress v-if="percentage > 0" :percentage="percentage"></el-progress>
</div>
</template>
<script>
export default {
data() {
return {
token:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ3Mjc5NzA2LCJleHAiOjE3NDc4ODQ1MDZ9.KfSi5zAq3c45Gjv6tkH5is5LtDx_74X99bb5fR_YXPc', // 替换为实际的Token
serverUrl: 'http://localhost:3000',
form: {
operName: 'super001',
password: '123456',
avatar: '', // 单个字符串存储头像URL
},
tempPreview: null, // 临时存放本地预览
percentage: 0, // 新增:用于存储上传进度百分比
};
},
methods: {
handleProgress(event, file, fileList) {
// 更新进度百分比
this.percentage = Math.floor(event.percent);
console.log(`正在上传 ${file.name}: ${this.percentage}%`);
},
handleExceed(files, fileList) {
this.$message.warning('只能选择一个文件');
},
handleAvatarChange(file, fileList) {
const reader = new FileReader();
reader.onload = (e) => {
// 设置临时预览图
this.tempPreview = e.target.result;
};
if (file.raw) {
reader.readAsDataURL(file.raw);
}
},
handleSuccess(response, file, fileList) {
this.$message.success('上传成功');
const uploadedUrl = response.url;
if (uploadedUrl) {
this.form.avatar = `${this.serverUrl}${uploadedUrl}`;
this.tempPreview = null; // 清除临时预览图
this.percentage = 100; // 完成时设置为100%
setTimeout(() => {
this.percentage = 0; // 成功后两秒隐藏进度条
}, 2000);
}
},
handleError(err, file, fileList) {
console.error('Upload Error:', err);
this.$message.error('上传失败');
this.tempPreview = null; // 出错时清除临时预览图
this.percentage = 0; // 错误时重置进度条
},
handleRemove(file, fileList) {
this.form.avatar = ''; // 清空 form.avatar
this.tempPreview = null; // 清除临时预览图
this.percentage = 0; // 删除文件时重置进度条
},
},
};
</script>
<style scoped>
.avatar-preview {
display: flex;
justify-content: flex-start;
margin-bottom: 10px;
}
.avatar {
width: 60px;
height: 60px;
object-fit: cover;
border: 1px solid #ddd;
margin-right: 10px;
}
.upload-avatar-wrapper {
width: 60px;
height: 60px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.upload-avatar-wrapper:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
}
</style>
2.15 on-change 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用,类型 function(file, fileList),无默认值。
2.16 before-upload 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。类型 function(file),无默认值。
<template>
<div>
<!-- 文件上传组件 -->
<el-upload
ref="uploadRef"
:auto-upload="true"
:on-change="handleAvatarChange"
:show-file-list="false"
class="upload-avatar-wrapper"
accept="image/*"
action="/api/user/uploadAvatar"
name="avatar"
:headers="{
'Authorization': `Bearer ${token}`
}"
:data="{
operName: 'super001',
password: '123456',
}"
:multiple="false"
:limit="1"
:on-exceed="handleExceed"
:with-credentials="false"
:on-success="handleSuccess"
:on-error="handleError"
:on-remove="handleRemove"
:on-progress="handleProgress"
:before-upload="beforeUpload"
>
<!-- 显示当前头像 -->
<div class="avatar-preview">
<img
v-if="form.avatar || tempPreview"
:src="form.avatar || tempPreview"
class="avatar"
style="width:60px;height:60px;margin-right:10px;border:1px #ddd solid;object-fit: cover;"
/>
</div>
<!-- 触发上传按钮 -->
<div
class="avatar-uploader-icon"
style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
>
<i class="el-icon-plus"></i>
</div>
</el-upload>
<!-- 进度条 -->
<el-progress v-if="percentage > 0" :percentage="percentage"></el-progress>
</div>
</template>
<script>
export default {
data() {
return {
token:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ3Mjc5NzA2LCJleHAiOjE3NDc4ODQ1MDZ9.KfSi5zAq3c45Gjv6tkH5is5LtDx_74X99bb5fR_YXPc', // 替换为实际的Token
serverUrl: 'http://localhost:3000',
form: {
operName: 'super001',
password: '123456',
avatar: '', // 单个字符串存储头像URL
},
tempPreview: null, // 临时存放本地预览
percentage: 0, // 新增:用于存储上传进度百分比
};
},
methods: {
beforeUpload(file) {
const isJPG = file.name.endsWith('.jpg');
if (!isJPG) {
this.$message.error('您只能上传 JPG 格式的图片!');
}
return isJPG;
},
handleProgress(event, file, fileList) {
// 更新进度百分比
this.percentage = Math.floor(event.percent);
console.log(`正在上传 ${file.name}: ${this.percentage}%`);
},
handleExceed(files, fileList) {
this.$message.warning('只能选择一个文件');
},
handleAvatarChange(file, fileList) {
const reader = new FileReader();
reader.onload = (e) => {
// 设置临时预览图
this.tempPreview = e.target.result;
};
if (file.raw) {
reader.readAsDataURL(file.raw);
}
},
handleSuccess(response, file, fileList) {
this.$message.success('上传成功');
const uploadedUrl = response.url;
if (uploadedUrl) {
this.form.avatar = `${this.serverUrl}${uploadedUrl}`;
this.tempPreview = null; // 清除临时预览图
this.percentage = 100; // 完成时设置为100%
setTimeout(() => {
this.percentage = 0; // 成功后两秒隐藏进度条
}, 2000);
}
},
handleError(err, file, fileList) {
console.error('Upload Error:', err);
this.$message.error('上传失败');
this.tempPreview = null; // 出错时清除临时预览图
this.percentage = 0; // 错误时重置进度条
},
handleRemove(file, fileList) {
this.form.avatar = ''; // 清空 form.avatar
this.tempPreview = null; // 清除临时预览图
this.percentage = 0; // 删除文件时重置进度条
},
},
};
</script>
<style scoped>
.avatar-preview {
display: flex;
justify-content: flex-start;
margin-bottom: 10px;
}
.avatar {
width: 60px;
height: 60px;
object-fit: cover;
border: 1px solid #ddd;
margin-right: 10px;
}
.upload-avatar-wrapper {
width: 60px;
height: 60px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.upload-avatar-wrapper:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
}
</style>
2.17 before-remove 删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被 reject,则停止删除。类型 function(file, fileList),无默认值。
// before-remove 回调函数使用
<template>
<div>
<!-- 文件上传组件 -->
<el-upload
ref="uploadRef"
:auto-upload="true"
:on-change="handleAvatarChange"
:show-file-list="true"
class="upload-avatar-wrapper"
accept="image/*"
action="/api/user/uploadAvatar"
name="avatar"
:headers="{
'Authorization': `Bearer ${token}`
}"
:data="{
operName: 'super001',
password: '123456',
}"
:multiple="false"
:limit="1"
:on-exceed="handleExceed"
:with-credentials="false"
:on-success="handleSuccess"
:on-error="handleError"
:on-remove="handleRemove"
:on-progress="handleProgress"
:before-upload="beforeUpload"
:before-remove="beforeRemove"
>
<!-- 显示当前头像 -->
<div class="avatar-preview">
<img
v-if="form.avatar || tempPreview"
:src="form.avatar || tempPreview"
class="avatar"
style="width:60px;height:60px;margin-right:10px;border:1px #ddd solid;object-fit: cover;"
/>
</div>
<!-- 触发上传按钮 -->
<div
class="avatar-uploader-icon"
style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
>
<i class="el-icon-plus"></i>
</div>
</el-upload>
<!-- 进度条 -->
<el-progress v-if="percentage > 0" :percentage="percentage"></el-progress>
</div>
</template>
<script>
export default {
data() {
return {
token:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ3Mjc5NzA2LCJleHAiOjE3NDc4ODQ1MDZ9.KfSi5zAq3c45Gjv6tkH5is5LtDx_74X99bb5fR_YXPc', // 替换为实际的Token
serverUrl: 'http://localhost:3000',
form: {
operName: 'super001',
password: '123456',
avatar: '', // 单个字符串存储头像URL
},
tempPreview: null, // 临时存放本地预览
percentage: 0, // 新增:用于存储上传进度百分比
};
},
methods: {
beforeRemove(file, fileList) {
console.log('beforeRemove file::', file, 'fileList::', fileList);
// 可以在此添加任何逻辑,比如询问用户是否确定删除
return this.$confirm(`确定要移除 ${file.name} 吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
// 如果用户点击了“确定”,则返回 true 允许移除
return true;
})
.catch(() => {
// 如果用户点击了“取消”,则返回 false 阻止移除
return false;
});
},
beforeUpload(file) {
const isJPG = file.name.endsWith('.jpg');
if (!isJPG) {
this.$message.error('您只能上传 JPG 格式的图片!');
return false;
}
return true;
},
handleProgress(event, file, fileList) {
// 更新进度百分比
this.percentage = Math.floor(event.percent);
console.log(`正在上传 ${file.name}: ${this.percentage}%`);
},
handleExceed(files, fileList) {
this.$message.warning('只能选择一个文件');
},
handleAvatarChange(file, fileList) {
const reader = new FileReader();
reader.onload = (e) => {
// 设置临时预览图
this.tempPreview = e.target.result;
};
if (file.raw) {
reader.readAsDataURL(file.raw);
}
},
handleSuccess(response, file, fileList) {
this.$message.success('上传成功');
const uploadedUrl = response.url;
if (uploadedUrl) {
this.form.avatar = `${this.serverUrl}${uploadedUrl}`;
this.tempPreview = null; // 清除临时预览图
this.percentage = 100; // 完成时设置为100%
setTimeout(() => {
this.percentage = 0; // 成功后两秒隐藏进度条
}, 2000);
}
},
handleError(err, file, fileList) {
console.error('Upload Error:', err);
this.$message.error('上传失败');
this.tempPreview = null; // 出错时清除临时预览图
this.percentage = 0; // 错误时重置进度条
},
handleRemove(file, fileList) {
console.log('handleRemove file::', file, 'fileList::', fileList);
this.form.avatar = ''; // 清空 form.avatar
this.tempPreview = null; // 清除临时预览图
this.percentage = 0; // 删除文件时重置进度条
},
},
};
</script>
<style scoped>
.avatar-preview {
display: flex;
justify-content: flex-start;
margin-bottom: 10px;
}
.avatar {
width: 60px;
height: 60px;
object-fit: cover;
border: 1px solid #ddd;
margin-right: 10px;
}
.upload-avatar-wrapper {
width: 60px;
height: 60px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.upload-avatar-wrapper:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
}
</style>
2.1 list-type 文件列表的类型,类型 string,可选值 text/picture/picture-card,默认 text。
2.19 auto-upload 是否在选取文件后立即进行上传,类型 boolean,默认 true。
2.20 file-list 上传的文件列表, 例如: [{name: 'food.jpg', url: 'https://xxx.cdn.com/xxx.jpg'}]。类型 array,默认 []。
2.21 http-request 覆盖默认的上传行为,可以自定义上传的实现,类型 function,无默认值。
<template>
<div>
<el-upload
ref="uploadRef"
:auto-upload="true"
:on-change="handleAvatarChange"
:show-file-list="true"
class="upload-avatar-wrapper"
accept="image/*"
:action="uploadUrl"
:http-request="httpRequest"
:on-preview="onPreview"
:on-remove="onRemove"
:on-success="onSuccess"
>
<!-- 显示当前已选/已上传的图片 -->
<div class="avatar-preview-list">
<img
v-if="tempPreviews.url"
:src="tempPreviews.url"
class="avatar"
style="width:60px;height:60px;margin-right:10px;border:1px #ddd solid;object-fit: cover;"
/>
</div>
<div
v-if="!tempPreviews.url"
class="avatar-uploader-icon"
style="width:60px;height:60px;text-align:center;line-height:60px;border:1px #ddd solid;"
>
<i class="el-icon-plus"></i>
</div>
</el-upload>
</div>
</template>
<script>
export default {
data() {
return {
token:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ4MzExMjUyLCJleHAiOjE3NDg5MTYwNTJ9.B8PwiMxPfpyQELGLdBBdFrMuNxdJvpFwtsE9-6jGAWk',
form: {
operName: 'super001',
password: '123456',
avatar: '',
},
tempPreviews: {}, // 存储预览图片对象
selectedFile: null, // 当前选择的文件
serverUrl: 'http://localhost:3000',
uploadUrl: '/api/user/uploadAvatar',
};
},
methods: {
onSuccess(response, file, fileList) {
console.log('onSuccess response::', response);
this.$message.success('上传成功');
this.form.avatar = `${this.serverUrl}${response.url}`;
},
onRemove(file, fileList) {
this.tempPreviews = {};
this.selectedFile = null;
},
onPreview(file) {
console.log('onPreview file::', file);
},
handleAvatarChange(file, fileList) {
if (fileList.length > 1) {
this.$message.warning('只能选择一个文件');
fileList.splice(0, 1); // 清除多余的文件
return false;
}
const reader = new FileReader();
reader.onload = (e) => {
this.tempPreviews = {
url: e.target.result,
uid: file.uid,
};
};
if (file.raw) {
reader.readAsDataURL(file.raw);
this.selectedFile = file.raw;
}
},
httpRequest(option) {
console.log('httpRequest option::', option);
const formData = new FormData();
formData.append('avatar', option.file);
formData.append('operName', this.form.operName);
formData.append('password', this.form.password);
fetch(option.action, {
method: 'POST',
headers: {
Authorization: `Bearer ${this.token}`,
},
body: formData,
})
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log('Upload success:', data);
this.form.avatar = `${this.serverUrl}${data.url}`;
option.onSuccess(data); // 告诉 el-upload 成功
})
.catch((error) => {
console.error('Upload failed:', error);
this.$message.error('上传失败');
option.onError(error); // 告诉 el-upload 出错
});
},
},
};
</script>
<style scoped>
.upload-avatar-wrapper {
width: 60px;
height: 60px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.upload-avatar-wrapper:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
}
.avatar {
width: 100%;
height: 100%;
object-fit: cover;
}
</style>
2.22 disabled 是否禁用,类型 boolean,默认 false。
2.23 limit 最大允许上传个数,类型 number,无默认值。
2.24 on-exceed 文件超出个数限制时的钩子,类型 function(files, fileList),无默认值。
<template>
<div>
<el-upload
ref="uploadRef"
:auto-upload="true"
:limit="2"
:multiple="true"
:on-change="handleAvatarChange"
:show-file-list="false"
class="upload-avatar-wrapper"
accept="image/*"
:action="uploadUrl"
:http-request="httpRequest"
:on-preview="onPreview"
:on-remove="onRemove"
:on-success="onSuccess"
:on-exceed="handleExceed"
>
<!-- 显示当前已选/已上传的图片 -->
<div class="avatar-preview-list">
<img
v-for="(preview, index) in tempPreviews"
:key="index"
:src="preview.url"
class="avatar"
style="
width:60px;
height:60px;
margin-right:10px;
border:1px #ddd solid;
object-fit: cover;"
/>
</div>
<div
v-if="tempPreviews.length < 2"
class="avatar-uploader-icon"
style="
width:60px;
height:60px;
text-align:center;
line-height:60px;
border:1px #ddd solid;"
>
<i class="el-icon-plus"></i>
</div>
</el-upload>
</div>
</template>
<script>
export default {
data() {
return {
token:
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJvcGVyQ29kZSI6InN1cGVyMDAxIiwiaWF0IjoxNzQ4MzExMjUyLCJleHAiOjE3NDg5MTYwNTJ9.B8PwiMxPfpyQELGLdBBdFrMuNxdJvpFwtsE9-6jGAWk',
form: {
operName: 'super001',
password: '123456',
avatar: '',
},
tempPreviews: [], // 存储多个预览图
selectedFiles: [], // 存储选中的多个文件
serverUrl: 'http://localhost:3000',
uploadUrl: '/api/user/uploadAvatar',
};
},
methods: {
onSuccess(response, file, fileList) {
console.log('onSuccess response::', response);
this.$message.success('上传成功');
this.form.avatar = `${this.serverUrl}${response.url}`;
},
onRemove(file, fileList) {
// 从预览列表中移除对应文件
this.tempPreviews = this.tempPreviews.filter(
(preview) => preview.uid !== file.uid
);
this.selectedFiles = this.selectedFiles.filter(
(f) => f.uid !== file.uid
);
},
onPreview(file) {
console.log('onPreview file::', file);
},
handleAvatarChange(file, fileList) {
const limit = 2;
// 如果文件总数超过限制,只保留前两个
if (fileList.length > limit) {
this.$message.warning('最多只能选择两个文件');
this.$refs.uploadRef.clearFiles(); // 清空所有文件
this.$refs.uploadRef.handleStart(fileList[0]);
this.$refs.uploadRef.handleStart(fileList[1]);
// 同步更新本地预览和文件数组
this.tempPreviews = [];
this.selectedFiles = [];
// 重新添加前两个文件的预览
for (let i = 0; i < 2 && i < fileList.length; i++) {
const f = fileList[i];
const reader = new FileReader();
reader.onload = (e) => {
this.tempPreviews.push({
url: e.target.result,
uid: f.uid,
});
};
if (f.raw) {
reader.readAsDataURL(f.raw);
this.selectedFiles.push(f.raw);
}
}
return false;
}
// 正常处理每个文件的预览
const reader = new FileReader();
reader.onload = (e) => {
const preview = {
url: e.target.result,
uid: file.uid,
};
// 避免重复添加
if (!this.tempPreviews.some((p) => p.uid === file.uid)) {
this.tempPreviews.push(preview);
}
};
if (file.raw) {
reader.readAsDataURL(file.raw);
// 避免重复保存原始文件
if (!this.selectedFiles.some((f) => f.uid === file.raw.uid)) {
this.selectedFiles.push(file.raw);
}
}
},
// 超出数量时提示
handleExceed(files, fileList) {
this.$message.warning('最多只能上传两个文件');
},
httpRequest(option) {
console.log('httpRequest option::', option);
const formData = new FormData();
formData.append('avatar', option.file);
formData.append('operName', this.form.operName);
formData.append('password', this.form.password);
fetch(option.action, {
method: 'POST',
headers: {
Authorization: `Bearer ${this.token}`,
},
body: formData,
})
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then((data) => {
console.log('Upload success:', data);
this.form.avatar = `${this.serverUrl}${data.url}`;
option.onSuccess(data); // 告诉 el-upload 成功
})
.catch((error) => {
console.error('Upload failed:', error);
this.$message.error('上传失败');
option.onError(error); // 告诉 el-upload 出错
});
},
},
};
</script>
<style scoped>
.upload-avatar-wrapper {
width: auto;
min-width: 60px;
height: 60px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
gap: 10px;
}
.upload-avatar-wrapper:hover {
border-color: #409eff;
}
.avatar-uploader-icon {
font-size: 28px;
}
.avatar {
width: 60px;
height: 60px;
object-fit: cover;
}
</style>