本文实例讲述了Vue + Node.js + MongoDB图片上传组件实现图片预览和删除功能。分享给大家供大家参考,具体如下:公司要写一些为自身业务量身定制的的组件,要基于Vue,写完后扩展了一下功能,选择写图片上传是因为自己之前一直对这个功能比较迷糊,所以这次好好了解了一下。演示在网址打开后的show.gif中。使用技术:Vue.js | node.js | express | MongoDB。使用技术:Vue.js | node.js | express | MongoDB。github网址:https://github.com/neroneroffy/private-project/tree/master/vue_uploaderhttps://github.com/neroneroffy/private-project/tree/master/vue_uploader功能

单图多图上传

图片上传预览

上传进度条

分组上传,分组查询

新建分组,删除分组

删除图片

选择图片
单图多图上传图片上传预览上传进度条分组上传,分组查询新建分组,删除分组删除图片选择图片目录结构前端利用Vue搭建,Entry.vue中引入子组件Upload.vue。在Upload.vue中,使用input标签,上传图片,form表单提交数据,但是from让人很头疼,提交后刷新页面,所以给它绑定了一个隐藏的iframe标签来实现无刷新提交表单。Dom中:

















调用上传函数提交完数据后:
upload();
document.forms[0].target="rfFrame";


upload();
document.forms[0].target="rfFrame";

图片预览图片预览利用html5的fileReader对象

let count = 0;//上传函数外定义变量,记录文件的数量,即递归的次数
/*-----------------------此段代码在上传函数中-------------------------------*/

let fileReader = new FileReader();

//解析图片路径,实现预览

fileReader.readAsDataURL(file.files[count]);

fileReader.onload=()=>{

previewData = {

url:fileReader.result,//图片预览的img标签的src

name:file.files[count].name,

size:file.files[count].size,

};

//这段代码在上传函数中,所以在外面定义了一个_this = this,fileList为vue的data中定义的状态

_this.fileList.push(previewData);

};



let count = 0;//上传函数外定义变量,记录文件的数量,即递归的次数
/*-----------------------此段代码在上传函数中-------------------------------*/

let fileReader = new FileReader();

//解析图片路径,实现预览

fileReader.readAsDataURL(file.files[count]);

fileReader.onload=()=>{

previewData = {

url:fileReader.result,//图片预览的img标签的src

name:file.files[count].name,

size:file.files[count].size,

};

//这段代码在上传函数中,所以在外面定义了一个_this = this,fileList为vue的data中定义的状态

_this.fileList.push(previewData);

};

进度条实现进度条实现在axios的配置中定义onUploadProgress函数,接收参数:progressEvent,利用它的两个属性:progressEvent.total和progressEvent.loaded(上传的文件总字节数和已上传的字节数)
node写接口,实现图片的接收、查询、删除。实现分组的新增、查询、删除。利用Formidable模块接收并处理前端传过来的表单数据。利用fs模块实现删除文件功能。
let progress = 0;
let config = {
headers: {'Content-Type': 'multipart/form-data'},
onUploadProgress (progressEvent){


if(progressEvent.lengthComputable){

progress = progressEvent.total/progressEvent.loaded;

_this.$refs.progress[_this.$refs.progress.length-1].style.width = Number(progress).toFixed(2)*100+"%";

}
}
};


let progress = 0;
let config = {
headers: {'Content-Type': 'multipart/form-data'},
onUploadProgress (progressEvent){


if(progressEvent.lengthComputable){

progress = progressEvent.total/progressEvent.loaded;

_this.$refs.progress[_this.$refs.progress.length-1].style.width = Number(progress).toFixed(2)*100+"%";

}
}
};

向formData中插入文件向formData中插入文件
formData = new FormData();
if(file.files[count]){
formData.append('file',file.files[count],file.files[count].name);


formData = new FormData();
if(file.files[count]){
formData.append('file',file.files[count],file.files[count].name);

对于上传方式,我这里统一采用依次上传,无论是单图多图,单图上传一次就好,多图则递归调用上传函数,直到递归次数等于图片数量,停止递归。上传函数上传函数
let file=$event.target,
formData = new FormData();
//递归调用自身,实现多文件依次上传
let _this = this;
let count = 0;
let previewData = {};
uploadImage($event){

let file=$event.target,

formData = new FormData();

//递归调用自身,实现多文件依次上传

let _this = this;

let count = 0;

let previewData = {};


function upload(){

//开始上传时,滚到底部

_this.$refs.picWrapper.scrollTop = _this.$refs.picWrapper.scrollHeight;

//定义axios配置信息

let progress = 0;

let config = {

headers: {'Content-Type': 'multipart/form-data'},

onUploadProgress (progressEvent){

console.log(`进度条的数量${_this.$refs.progress.length -1}`);

if(progressEvent.lengthComputable){

progress = progressEvent.total/progressEvent.loaded;

//进度条

_this.$refs.progress[_this.$refs.progress.length-1].style.width = Number(progress).toFixed(2)*100+"%";

}

}

};

//向formData中插入文件

if(file.files[count]){

formData.append('file',file.files[count],file.files[count].name);

let fileReader = new FileReader();

//解析图片路径,实现预览

fileReader.readAsDataURL(file.files[count]);

fileReader.onload=()=>{

previewData = {

url:fileReader.result,

name:file.files[count].name,

size:file.files[count].size,

};

_this.fileList.push(previewData);

_this.progressShow = true

};

fileReader.onloadend=()=>{

//检测图片大小是否超出限制

if(formData.get('file').size>_this.maxSize){

formData.delete('file');

//当图片全部上传完毕,停止递归

count++;

if(count > file.files.length-1){

return

}

upload()

}else{

//发送数据

axios.post(`/upload?mark=${_this.group}`,formData,config).then((response)=>{

formData.delete('file');

let res = response.data;

console.log(res);

if(res.result){

//如果是新建上传

if(_this.group === 'new'){

_this.fileList.push(res.data);

_this.fileList.forEach((item,index)=>{

if(!item.newName){

_this.fileList.splice(index,1)

}

})


}else{

//如果是选择其他组上传,直接把返回数据赋值到文件数组

_this.fileList = res.data;

}


_this.newUpload = false

}else{

alert('上传失败');

return;

}

_this.noPic = false;

count++;

if(count > file.files.length-1){

return

}

upload()

}).catch((err)=>{

alert('上传失败123');

});

}

};

}

}

//第一次调用

upload();

document.forms[0].target="rfFrame";
}


let file=$event.target,
formData = new FormData();
//递归调用自身,实现多文件依次上传
let _this = this;
let count = 0;
let previewData = {};
uploadImage($event){

let file=$event.target,

formData = new FormData();

//递归调用自身,实现多文件依次上传

let _this = this;

let count = 0;

let previewData = {};


function upload(){

//开始上传时,滚到底部

_this.$refs.picWrapper.scrollTop = _this.$refs.picWrapper.scrollHeight;

//定义axios配置信息

let progress = 0;

let config = {

headers: {'Content-Type': 'multipart/form-data'},

onUploadProgress (progressEvent){

console.log(`进度条的数量${_this.$refs.progress.length -1}`);

if(progressEvent.lengthComputable){

progress = progressEvent.total/progressEvent.loaded;

//进度条

_this.$refs.progress[_this.$refs.progress.length-1].style.width = Number(progress).toFixed(2)*100+"%";

}

}

};

//向formData中插入文件

if(file.files[count]){

formData.append('file',file.files[count],file.files[count].name);

let fileReader = new FileReader();

//解析图片路径,实现预览

fileReader.readAsDataURL(file.files[count]);

fileReader.onload=()=>{

previewData = {

url:fileReader.result,

name:file.files[count].name,

size:file.files[count].size,

};

_this.fileList.push(previewData);

_this.progressShow = true

};

fileReader.onloadend=()=>{

//检测图片大小是否超出限制

if(formData.get('file').size>_this.maxSize){

formData.delete('file');

//当图片全部上传完毕,停止递归

count++;

if(count > file.files.length-1){

return

}

upload()

}else{

//发送数据

axios.post(`/upload?mark=${_this.group}`,formData,config).then((response)=>{

formData.delete('file');

let res = response.data;

console.log(res);

if(res.result){

//如果是新建上传

if(_this.group === 'new'){

_this.fileList.push(res.data);

_this.fileList.forEach((item,index)=>{

if(!item.newName){

_this.fileList.splice(index,1)

}

})


}else{

//如果是选择其他组上传,直接把返回数据赋值到文件数组

_this.fileList = res.data;

}


_this.newUpload = false

}else{

alert('上传失败');

return;

}

_this.noPic = false;

count++;

if(count > file.files.length-1){

return

}

upload()

}).catch((err)=>{

alert('上传失败123');

});

}

};

}

}

//第一次调用

upload();

document.forms[0].target="rfFrame";
}

node.js写后端
//引入表单处理模块
let Formidable = require("formidable");


//引入表单处理模块
let Formidable = require("formidable");

一系列定义....
form.encoding = 'utf-8';
form.uploadDir = '/project/vue/vue_uploader/my-server/public/images';//定义文件存放地址
form.keepExtensions = true;
form.multiples = false;//以单文件依次上传的方式,实现多文件上传
form.maxFieldsSize = 1*1024;
//解析图片,重命名图片名称,返回给前端。
let fileData = "";
let fileDir = "images";//定义文件的存放路径
let route = 'upload_';//定义路由
let serverIp = 'http://localhost:3002/';//定义服务器IP


form.encoding = 'utf-8';
form.uploadDir = '/project/vue/vue_uploader/my-server/public/images';//定义文件存放地址
form.keepExtensions = true;
form.multiples = false;//以单文件依次上传的方式,实现多文件上传
form.maxFieldsSize = 1*1024;
//解析图片,重命名图片名称,返回给前端。
let fileData = "";
let fileDir = "images";//定义文件的存放路径
let route = 'upload_';//定义路由
let serverIp = 'http://localhost:3002/';//定义服务器IP

对文件数据进行处理,存入本地并存入数据库(由于涉及到分组上传。。。所以比较复杂)解析文件函数:
function handleFile (file){

let filename = file.name;

let nameArray = filename.split('.');

let type = nameArray[nameArray.length-1];

let name = '';

for (let i = 0;i
name = name + nameArray[i];

}

let date = new Date();

let time = '_' + date.getFullYear() + "_" + date.getMonth() + "_" + date.getDay() + "_" + date.getHours() + "_" + date.getMinutes() +"_"+ date.getSeconds()+"_"+date.getMilliseconds();

let picName = name + time + '.' + type;

let newPath = form.uploadDir + "/" + picName;

let oldPath = form.uploadDir + "/"+ file.path.substring(file.path.indexOf(route));


fs.renameSync(oldPath, newPath); //重命名

fileData = {

id:`${new Date().getTime()}`,

url:serverIp + newPath.substring(newPath.indexOf(fileDir)),

name:file.name,

size:file.size,

isSelected:false,

newName:picName,

};

UploadData.findOne({group:group},(err,doc)=>{

if(err){

res.json({

result:false,

msg:err.message

})

}else{

if(doc){

doc.picList.push(fileData);

doc.save((err,saveResult)=>{


if(err){

return res.json({

result:false,

});

}else{

let length= doc.picList.length;

console.log(doc.picList.length)

if(groupMark === 'all'){

UploadData.find({},(err,queryResult)=>{

if(err){

res.json({

result:false,

mgs:'发生错误了'

})

}else{

let allPic = [];

queryResult.forEach((item)=>{

if(item.group !=='default'){

allPic = allPic.concat(item.picList)

}

});

res.json({

result:true,

data:allPic.concat(queryResult[1].picList)

})


}

})

}else if(groupMark === 'new'){


UploadData.findOne({group:'default'},(err,queryResult)=>{

if(err){

return res.json({

result:false,

msg:err.message

});

}else{

return res.json({

result:true,

data:queryResult.picList[queryResult.picList.length-1]

})

}

});


}else{

UploadData.findOne({group:group},(err,queryResult)=>{

if(err){

return res.json({

result:false,

msg:err.message

});

}else{

return res.json({

result:true,

data:queryResult.picList

})

}

});

}

}

})


}


}


})
}


function handleFile (file){

let filename = file.name;

let nameArray = filename.split('.');

let type = nameArray[nameArray.length-1];

let name = '';

for (let i = 0;i
name = name + nameArray[i];

}

let date = new Date();

let time = '_' + date.getFullYear() + "_" + date.getMonth() + "_" + date.getDay() + "_" + date.getHours() + "_" + date.getMinutes() +"_"+ date.getSeconds()+"_"+date.getMilliseconds();

let picName = name + time + '.' + type;

let newPath = form.uploadDir + "/" + picName;

let oldPath = form.uploadDir + "/"+ file.path.substring(file.path.indexOf(route));


fs.renameSync(oldPath, newPath); //重命名

fileData = {

id:`${new Date().getTime()}`,

url:serverIp + newPath.substring(newPath.indexOf(fileDir)),

name:file.name,

size:file.size,

isSelected:false,

newName:picName,

};

UploadData.findOne({group:group},(err,doc)=>{

if(err){

res.json({

result:false,

msg:err.message

})

}else{

if(doc){

doc.picList.push(fileData);

doc.save((err,saveResult)=>{


if(err){

return res.json({

result:false,

});

}else{

let length= doc.picList.length;

console.log(doc.picList.length)

if(groupMark === 'all'){

UploadData.find({},(err,queryResult)=>{

if(err){

res.json({

result:false,

mgs:'发生错误了'

})

}else{

let allPic = [];

queryResult.forEach((item)=>{

if(item.group !=='default'){

allPic = allPic.concat(item.picList)

}

});

res.json({

result:true,

data:allPic.concat(queryResult[1].picList)

})


}

})

}else if(groupMark === 'new'){


UploadData.findOne({group:'default'},(err,queryResult)=>{

if(err){

return res.json({

result:false,

msg:err.message

});

}else{

return res.json({

result:true,

data:queryResult.picList[queryResult.picList.length-1]

})

}

});


}else{

UploadData.findOne({group:group},(err,queryResult)=>{

if(err){

return res.json({

result:false,

msg:err.message

});

}else{

return res.json({

result:true,

data:queryResult.picList

})

}

});

}

}

})


}


}


})
}

最后,调用解析文件函数
form.parse(req,(err,fields,files)=>{

//传多个文件

if(files.file instanceof Array){

return

}else{

//传单个文件

handleFile(files.file)

}
});


form.parse(req,(err,fields,files)=>{

//传多个文件

if(files.file instanceof Array){

return

}else{

//传单个文件

handleFile(files.file)

}
});

数据库结构:剩下的还有文件删除,新增分组,删除分组,分组查询的功能,由于篇幅有限,这些功能可以去看源码看源码第一次用node和mongoDB写后台业务,还有很多地方需要完善,代码会继续更新~希望本文所述对大家vue.js程序设计有所帮助。