公司要写一些为自身业务量身定制的的组件,要基于Vue,趁着这个机会,自己在业余时间也写了个组件,选择写图片上传是因为自己之前一直对这个功能比较迷糊,所以这次好好了解了一下。演示在网址打开后的show.gif中。
使用技术:Vue.js | node.js | express | MongoDB。
github网址:https://github.com/capslocktao/private-project/tree/master/vue_uploader
功能
- 单图多图上传
- 图片上传预览
- 上传进度条
- 分组上传,分组查询
- 新建分组,删除分组
- 删除图片
- 选择图片
目录结构
前端利用Vue搭建,Entry.vue中引入子组件Upload.vue。在Upload.vue中,使用input标签,上传图片,form表单提交数据,但是from让人很头疼,提交后刷新页面,所以给它绑定了一个隐藏的iframe标签来实现无刷新提交表单。
Dom中:
1
2
3
4
5
6 1<form class="upload-content-right-top" enctype="multipart/form-data" ref="formSubmit" >
2 <label class="upload-content-right-top-btn">上传图片</label>
3 <input type="file" @change="uploadImage($event)" multiple="multiple" accept="image/gif, image/jpeg, image/png">
4</form>
5<iframe id="rfFrame" name="rfFrame" src="about:blank" style="display:none;"></iframe>
6
调用上传函数提交完数据后:
1
2
3 1upload();
2document.forms[0].target="rfFrame";
3
图片预览
利用html5的fileReader对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 1 let count = 0;//上传函数外定义变量,记录文件的数量,即递归的次数
2
3/*-----------------------此段代码在上传函数中-------------------------------*/
4 let fileReader = new FileReader();
5 //解析图片路径,实现预览
6 fileReader.readAsDataURL(file.files[count]);
7 fileReader.onload=()=>{
8 previewData = {
9 url:fileReader.result,//图片预览的img标签的src
10 name:file.files[count].name,
11 size:file.files[count].size,
12 };
13 //这段代码在上传函数中,所以在外面定义了一个_this = this,fileList为vue的data中定义的状态
14 _this.fileList.push(previewData);
15 };
16
进度条实现
在axios的配置中定义onUploadProgress函数,接收参数:progressEvent,利用它的两个属性:progressEvent.total和progressEvent.loaded(上传的文件总字节数和已上传的字节数)
node写接口,实现图片的接收、查询、删除。实现分组的新增、查询、删除。利用Formidable模块接收并处理前端传过来的表单数据。利用fs模块实现删除文件功能。
1
2
3
4
5
6
7
8
9
10
11
12 1let progress = 0;
2let config = {
3 headers: {'Content-Type': 'multipart/form-data'},
4 onUploadProgress (progressEvent){
5
6 if(progressEvent.lengthComputable){
7 progress = progressEvent.total/progressEvent.loaded;
8 _this.$refs.progress[_this.$refs.progress.length-1].style.width = Number(progress).toFixed(2)*100+"%";
9 }
10 }
11};
12
向formData中插入文件
formData = new FormData();
if(file.files[count]){
formData.append(‘file’,file.files[count],file.files[count].name);
对于上传方式,我这里统一采用依次上传,无论是单图多图,单图上传一次就好,多图则递归调用上传函数,直到递归次数等于图片数量,停止递归。
上传函数
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 1 let file=$event.target,
2 formData = new FormData();
3 //递归调用自身,实现多文件依次上传
4 let _this = this;
5 let count = 0;
6 let previewData = {};
7
8
9 uploadImage($event){
10 let file=$event.target,
11 formData = new FormData();
12 //递归调用自身,实现多文件依次上传
13 let _this = this;
14 let count = 0;
15 let previewData = {};
16
17 function upload(){
18 //开始上传时,滚到底部
19 _this.$refs.picWrapper.scrollTop = _this.$refs.picWrapper.scrollHeight;
20 //定义axios配置信息
21 let progress = 0;
22 let config = {
23 headers: {'Content-Type': 'multipart/form-data'},
24 onUploadProgress (progressEvent){
25 console.log(`进度条的数量${_this.$refs.progress.length -1}`);
26 if(progressEvent.lengthComputable){
27 progress = progressEvent.total/progressEvent.loaded;
28 //进度条
29 _this.$refs.progress[_this.$refs.progress.length-1].style.width = Number(progress).toFixed(2)*100+"%";
30 }
31 }
32 };
33 //向formData中插入文件
34 if(file.files[count]){
35 formData.append('file',file.files[count],file.files[count].name);
36 let fileReader = new FileReader();
37 //解析图片路径,实现预览
38 fileReader.readAsDataURL(file.files[count]);
39 fileReader.onload=()=>{
40 previewData = {
41 url:fileReader.result,
42 name:file.files[count].name,
43 size:file.files[count].size,
44 };
45 _this.fileList.push(previewData);
46 _this.progressShow = true
47 };
48 fileReader.onloadend=()=>{
49 //检测图片大小是否超出限制
50 if(formData.get('file').size>_this.maxSize){
51 formData.delete('file');
52 //当图片全部上传完毕,停止递归
53 count++;
54 if(count > file.files.length-1){
55 return
56 }
57 upload()
58 }else{
59 //发送数据
60 axios.post(`/upload?mark=${_this.group}`,formData,config).then((response)=>{
61 formData.delete('file');
62 let res = response.data;
63 console.log(res);
64 if(res.result){
65 //如果是新建上传
66 if(_this.group === 'new'){
67 _this.fileList.push(res.data);
68 _this.fileList.forEach((item,index)=>{
69 if(!item.newName){
70 _this.fileList.splice(index,1)
71 }
72 })
73
74 }else{
75 //如果是选择其他组上传,直接把返回数据赋值到文件数组
76 _this.fileList = res.data;
77 }
78
79 _this.newUpload = false
80 }else{
81 alert('上传失败');
82 return;
83 }
84 _this.noPic = false;
85 count++;
86 if(count > file.files.length-1){
87 return
88 }
89 upload()
90 }).catch((err)=>{
91 alert('上传失败123');
92 });
93 }
94 };
95 }
96 }
97 //第一次调用
98 upload();
99 document.forms[0].target="rfFrame";
100
node.js后端实现
1
2
3 1//引入表单处理模块
2let Formidable = require("formidable");
3
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 1一系列定义….
2form.encoding = ‘utf-8’;
3form.uploadDir = ‘/project/vue/vue_uploader/my-server/public/images’;//定义文件存放地址
4form.keepExtensions = true;
5form.multiples = false;//以单文件依次上传的方式,实现多文件上传
6form.maxFieldsSize = 1*1024;
7//解析图片,重命名图片名称,返回给前端。
8let fileData = “”;
9let fileDir = “images”;//定义文件的存放路径
10let route = ‘upload_’;//定义路由
11let serverIp = ‘http://localhost:3002/‘;//定义服务器IP
12
13对文件数据进行处理,存入本地并存入数据库(由于涉及到分组上传。。。所以比较复杂)
14
15解析文件函数:
16function handleFile (file){
17let filename = file.name;
18let nameArray = filename.split(‘.’);
19let type = nameArray[nameArray.length-1];
20let name = ”;
21for (let i = 0;i< nameArray.length - 1;i++){
22name = name + nameArray[i];
23}
24let date = new Date();
25let time = ‘’ + date.getFullYear() + “” + date.getMonth() + “” + date.getDay() + “” + date.getHours() + “” + date.getMinutes() +”“+ date.getSeconds()+”_”+date.getMilliseconds();
26let picName = name + time + ‘.’ + type;
27let newPath = form.uploadDir + “/” + picName;
28let oldPath = form.uploadDir + “/”+ file.path.substring(file.path.indexOf(route));
29
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89 1 fs.renameSync(oldPath, newPath); //重命名
2 fileData = {
3 id:`${new Date().getTime()}`,
4 url:serverIp + newPath.substring(newPath.indexOf(fileDir)),
5 name:file.name,
6 size:file.size,
7 isSelected:false,
8 newName:picName,
9 };
10 UploadData.findOne({group:group},(err,doc)=>{
11 if(err){
12 res.json({
13 result:false,
14 msg:err.message
15 })
16 }else{
17 if(doc){
18 doc.picList.push(fileData);
19 doc.save((err,saveResult)=>{
20
21 if(err){
22 return res.json({
23 result:false,
24 });
25 }else{
26 let length= doc.picList.length;
27 console.log(doc.picList.length)
28 if(groupMark === 'all'){
29 UploadData.find({},(err,queryResult)=>{
30 if(err){
31 res.json({
32 result:false,
33 mgs:'发生错误了'
34 })
35 }else{
36 let allPic = [];
37 queryResult.forEach((item)=>{
38 if(item.group !=='default'){
39 allPic = allPic.concat(item.picList)
40 }
41 });
42 res.json({
43 result:true,
44 data:allPic.concat(queryResult[1].picList)
45 })
46
47 }
48 })
49 }else if(groupMark === 'new'){
50
51 UploadData.findOne({group:'default'},(err,queryResult)=>{
52 if(err){
53 return res.json({
54 result:false,
55 msg:err.message
56 });
57 }else{
58 return res.json({
59 result:true,
60 data:queryResult.picList[queryResult.picList.length-1]
61 })
62 }
63 });
64
65 }else{
66 UploadData.findOne({group:group},(err,queryResult)=>{
67 if(err){
68 return res.json({
69 result:false,
70 msg:err.message
71 });
72 }else{
73 return res.json({
74 result:true,
75 data:queryResult.picList
76 })
77 }
78 });
79 }
80 }
81 })
82
83 }
84
85 }
86
87 })
88}
89
最后,调用解析文件函数
1
2
3
4
5
6
7
8
9
10 1form.parse(req,(err,fields,files)=>{
2//传多个文件
3if(files.file instanceof Array){
4return
5}else{
6//传单个文件
7handleFile(files.file)
8}
9});
10
数据库结构: