springboot+vue自定义上传图片及视频
说明:本例采用的是将图片视频全都先传到前端,再一次性提交给后端的思路,另外还做了一个上传前的预览效果图vue页面代码<el-drawersize="40%":visible.sync="collect_drawer":append-to-body="true":wrapperClosable="false"><template slot="title"><span c
·
说明:本例采用的是将图片视频全都先传到前端,再一次性提交给后端的思路,另外还做了一个上传前的预览
效果图
vue页面代码
<el-drawer
size="40%"
:visible.sync="collect_drawer"
:append-to-body="true"
:wrapperClosable="false"
>
<template slot="title">
<span class="drawer-title">{{ collect_drawer_title }}</span>
</template>
<el-form
ref="collect_form"
:rules="collectRules"
:model="collect"
label-width="138px"
style="width: 100%; padding: 0 20px 0 0; height: 85%"
>
<el-form-item>
<span slot="label"
><span style="color: #f56c6c; margin-right: 4px">*</span
>产品图片/视频</span
>
<div style="display: flex; flex-wrap: wrap">
<!-- 循环遍历 要不就是video 要不就是img -->
<div
v-for="(item, index) in pro_files"
:key="index + 'proimg'"
class="video_border"
>
<!-- 视频块 -->
<div
style="width: 130px; height: 130px; position: relative"
v-if="item.type == 'video'"
>
<i
class="iconfont icon-al-fill-right play-icon"
@click="getPlayer(index + 1)"
/>
<div class="crop">
<video
:ref="'video' + (index + 1)"
class="video_li"
style="
width: 130px;
height: 130px;
margin-right: 10px;
object-fit: cover;
"
></video>
<div class="delete-div">
<i
class="el-icon-delete delete_icon"
@click="deleteFile(index)"
></i>
</div>
</div>
</div>
<!-- 图片块 -->
<div
style="width: 130px; height: 130px; position: relative"
v-else-if="item.type == 'image'"
>
<div class="crop">
<img
@click="getImage(index + 1)"
:ref="'image' + (index + 1)"
class="rect_img"
/>
<div class="delete-div">
<i
class="el-icon-delete delete_icon"
@click="deleteFile(index)"
></i>
</div>
</div>
</div>
<!-- <el-progress
class="video-progress"
:percentage="item.loaded"
></el-progress> -->
</div>
<input
type="file"
style="position: absolute"
accept="image/jpg,image/jpeg,image/bmp,image/png,image/gif,video/mp4,video/webm,video/ogg"
ref="myfile"
name="file"
id="file"
@change="uploadMyFile"
hidden
/>
<label for="file" style="id">
<div id="file_up"><i class="el-icon-plus"></i></div>
</label>
</div>
</el-form-item>
<el-form-item label="产品报价" prop="price">
<el-input
v-model="collect.price"
size="medium"
placeholder="请输入产品报价(元)"
></el-input>
</el-form-item>
<el-form-item label="网址" prop="url">
<el-input
v-model="collect.url"
size="medium"
placeholder="请输入网址"
></el-input>
</el-form-item>
<el-form-item label="来源" prop="source">
<el-input
v-model="collect.source"
size="medium"
placeholder="请输入来源"
></el-input>
</el-form-item>
<el-form-item label="卖家" prop="seller">
<el-input
v-model="collect.seller"
size="medium"
placeholder="请输入卖家"
></el-input>
</el-form-item>
<el-form-item label="规格" prop="spec">
<el-input
v-model="collect.spec"
size="medium"
placeholder="请输入规格(cm)"
></el-input>
</el-form-item>
<el-form-item label="说明" prop="note">
<el-input
type="textarea"
rows="3"
resize="none"
v-model="collect.note"
size="medium"
placeholder="请输入说明"
></el-input>
</el-form-item>
</el-form>
<div style="text-align: center; height: 10%; padding: 10px">
<el-button type="primary" @click="saveOrUpdate">保存</el-button>
<el-button type="primary" @click="closeDrawer">取消</el-button>
</div>
</el-drawer>
<!-- 视频展示框 -->
<el-dialog
:visible.sync="videoVisible"
width="80%"
top="3vh"
:before-close="resetVideo"
>
<div style="height: 80vh; text-align: center">
<video
src=""
style="width: 100%; height: 100%; object-fit: contain"
ref="showVideo"
controls="controls"
></video>
</div>
</el-dialog>
<!-- 图片展示框 -->
<el-dialog
:visible.sync="dialogVisible"
append-to-body
width="80%"
top="3vh"
>
<div style="height: 80vh; text-align: center">
<img
:src="dialogImageUrl"
alt=""
style="height: 100%; width: 100%; object-fit: contain"
/>
</div>
</el-dialog>
data数据定义
data() {
//这里存放数据
return {
//视频展示框是否显示
videoVisible: false,
//图片展示框是否显示
dialogVisible: false,
//图片展示框对应url
dialogImageUrl: undefined,
//图片视频文件实际存放数组
pro_files: [],
//记录删除前的文件队列
update_src: [],
//控制抽屉
collect_drawer: false,
//抽屉标题
collect_drawer_title: undefined,
//实体类
collect: {
collect_id: undefined,
note: undefined,
price: undefined,
url: undefined,
source: undefined,
seller: undefined,
spec: undefined,
},
//表单校验
collectRules: {},
},
methods方法定义
methods: {
bindBeforeunLoad() {
window.onbeforeunload = this.perforresult;
},
unBindBeforeunLoad() {
window.onbeforeunload = null;
},
perforresult() {
return "";
},
deleteFile(index) {
//由于删除的时候
this.update_src = [];
for (var i = index + 1; i < this.pro_files.length; i++) {
if (this.pro_files[i].type === "video") {
this.update_src.push(this.$refs["video" + (i + 1)][0].src);
} else {
this.update_src.push(this.$refs["image" + (i + 1)][0].src);
}
}
this.pro_files.splice(index, 1);
this.$nextTick(() => {
for (var j = 0; j < this.update_src.length; j++) {
for (var i = index++; i < this.pro_files.length; i++) {
if (this.pro_files[i].type === "video") {
this.$refs["video" + (i + 1)][0].src = this.update_src[j];
} else {
this.$refs["image" + (i + 1)][0].src = this.update_src[j];
}
break;
}
}
});
this.$refs.myfile.value = null;
},
/**开启全屏loading */
startLoading() {
this.loading = this.$loading({
lock: true,
text: "加载中...",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.8)",
});
},
/**关闭loading**/
closeLoading() {
this.loading.close();
},
//用于关闭之后重置视频
resetVideo() {
this.$refs.showVideo.pause();
this.videoVisible = false;
},
//上传视频方法
uploadMyFile() {
const that = this;
//获取到 input file
let myfile = that.$refs.myfile;
//获取到上传的file文件
let file = myfile.files[0];
if (myfile.files[0]) {
//判断如果是视频
if (file.type.indexOf("video") != -1) {
//先将视频文件放到视频文件总数组
that.$set(that.pro_files, that.pro_files.length, {
file: file,
type: "video",
});
//因为我根据v-for总数组来渲染的,所以当我去设置数组,会有延迟渲染,取不到ref,用nextTick,等渲染结束再操作
that.$nextTick(() => {
//获取到当前上传的文件的虚拟dom,用filReader读取文件,并且把视频对应的file信息装填上去。
let video = that.$refs["video" + that.pro_files.length][0];
if (window.FileReader) {
var fr = new FileReader();
// fr.onprogress = function (e) {
// that.$set(
// that.pro_files[that.pro_files.length - 1],
// "loaded",
// (e.loaded / file.size).toFixed(2) * 100
// );
// };
fr.onloadend = function (e) {
//上传完成之后
video.src = e.target.result;
video.title = myfile.files[0].name;
};
fr.readAsDataURL(myfile.files[0]);
}
});
} else {
//与上传视频几乎同理,不再赘述
that.$set(that.pro_files, that.pro_files.length, {
file: file,
type: "image",
});
that.$nextTick(() => {
let image = that.$refs["image" + that.pro_files.length][0];
if (window.FileReader) {
var fr = new FileReader();
// fr.onprogress = function (e) {
// that.$set(
// that.pro_files[that.pro_files.length - 1],
// "loaded",
// (e.loaded / file.size).toFixed(2) * 100
// );
// };
fr.onloadend = function (e) {
image.src = e.target.result;
image.title = myfile.files[0].name;
};
fr.readAsDataURL(myfile.files[0]);
}
});
}
}
},
//预览视频
getPlayer(index) {
this.videoVisible = true;
this.$nextTick(() => {
console.log(window.height);
let source = this.$refs["video" + index][0].src;
this.$refs.showVideo.src = source;
this.$refs.showVideo.play();
});
},
//预览图片
getImage(index) {
this.dialogVisible = true;
this.$nextTick(() => {
let source = this.$refs["image" + index][0].src;
this.dialogImageUrl = source;
});
},
//打开抽屉
toAddCollect() {
this.collect_drawer = true;
this.collect_drawer_title = "新增款式";
},
//关闭抽屉
closeDrawer() {
this.collect_drawer = false;
},
/**
* 总体上传方法,思路是使用FormData来封装
* 文件:都在一个数组里,所以循环设置即可,用同一个名字后台好接收
* 表单:使用new Blob将json转成二进制文件
*/
saveOrUpdate() {
//我这里是因为图片或视频必填
if (this.pro_files.length == 0) {
this.$message.error({ message: "产品图片/视频文件不能为空" });
return;
}
var addData = new FormData();
this.pro_files.forEach((file) => {
addData.append("files", file.file);
});
addData.append(
"productCollect",
new Blob([JSON.stringify(this.collect)], { type: "application/json" })
);
//点上传之后打开全屏loading 不让操作
this.startLoading();
//防止用户关闭当前窗口,弹窗防止,如果你想改弹出来的文字说明...(oh,我也想改呢)
this.bindBeforeunLoad();
this.postRequest("/api/proCollect", addData, {
"Content-Type": "multipart/form-data",
}).then((res) => {
if (res.success) {
this.$message.success({ message: res.message });
this.collect_drawer = false;
}
this.closeLoading();
this.unBindBeforeunLoad();
});
},
}
css样式
<style scoped>
::v-deep .el-upload-list--picture-card .el-upload-list__item {
width: 100px;
height: 100px;
}
::v-deep .el-upload--picture-card {
width: 100px;
height: 100px;
line-height: 110px;
}
#file_up {
background-color: #fbfdff;
border: 1px dashed #c0ccda;
text-align: center;
border-radius: 6px;
box-sizing: border-box;
width: 130px;
height: 130px;
line-height: 130px;
cursor: pointer;
vertical-align: top;
display: inline-block;
}
#file_up:hover,
#file_up:focus {
border-color: #409eff;
color: #409eff;
}
#file_up i {
font-size: 28px;
color: #8c939d;
}
.play-icon {
background: grey;
font-size: 20px;
cursor: pointer;
z-index: 100;
border-radius: 50%;
position: absolute;
left: 50%;
top: 50%;
margin-left: -16px;
margin-top: -16px;
color: #fff;
border: 6px solid grey;
}
.play-icon:hover {
opacity: 0.8;
}
.video_border {
margin-right: 10px;
margin-bottom: 10px;
}
.delete_icon {
color: #fff;
line-height: inherit;
font-size: 17px;
cursor: pointer;
}
.delete_icon:hover {
color: #f56c6c;
line-height: inherit;
font-size: 17px;
cursor: pointer;
}
.delete-div {
height: 0px;
position: absolute;
width: 100%;
background: #000;
opacity: 0.7;
bottom: 0;
text-align: center;
transition: height 0.2s ease 0s;
}
.crop:hover .delete-div {
height: 30%;
}
.rect_img {
position: absolute;
clip: rect(0px 130px 130px 0px);
width: 130px;
height: 130px;
margin-right: 10px;
cursor: pointer;
object-fit: cover;
}
</style>
后台接口
@PostMapping
@ApiOperation("新增款式收集")
public Result addCollect(
@RequestPart(value = "productCollect") ProductCollect productCollect,
@RequestPart(value = "files", required = true) MultipartFile[] files)
{
return cProductCollectService.addCollectFlow(productCollect,files);
}
记录学习日常,欢迎交流 ~
补充说明
其实我还做了一个进度条,就是你看到注释掉的那些,进度通过 fileReader 类的 onProgress 可以获取到,但效果并不是很理想(
而且怪难看的)
这个例子里我没有做任何的文件处理(压缩、分片),可以自行完善
顺便一提,如果有很大的文件,这样上传要卡挺久的,所以我要改成一个个往后端传了,告辞
更多推荐
已为社区贡献2条内容
所有评论(0)