写在前面
之前有一个关于这个的 随笔
但是没有 优化 过
这个是简单优化过的版本
暂时只有代码
1 <template> 2 <div id="app"> 3 <div class="container"> 4 <div class="title-container"> 5 <span>上传文件</span> 6 </div> 7 8 <div class="upload-box"> 9 <!-- 上传的那个框框 --> 10 <div 11 class="drag-box" 12 ref="dragBox" 13 :class="isDrag ? 'draging-drag-box' : ''" 14 > 15 <label> 16 <div class="icon-container"> 17 <i 18 class="fa fa-file" 19 :class="isDrag ? 'draging-fa-file' : ''" 20 ></i> 21 </div> 22 <div class="label-text-container"> 23 <span :class="isDrag ? 'draging-label-text-container' : ''" 24 >请将文件拖动至此</span 25 > 26 <br /> 27 <span :class="isDrag ? 'draging-label-text-container' : ''" 28 >或者单击上传</span 29 > 30 </div> 31 <input 32 type="file" 33 accept="image/jepg, image/png" 34 name="pictures" 35 @change="storepreFiles($event.target)" 36 multiple 37 /> 38 </label> 39 </div> 40 41 <div class="show-file-box" v-if="preFiles.length == 0 ? false : true"> 42 <div class="text-container"> 43 <span>上传列表</span> 44 <span>{{ updateState }} / {{ preFilesLength }}</span> 45 </div> 46 <!-- 渲染出来的情况 有一个进度条 --> 47 <div class="pre-upload-bar"> 48 <div> 49 <span 50 >列表加载进度 51 {{ (updateState / preFilesLength).toFixed(1) * 100 }} %</span 52 > 53 </div> 54 <div 55 class="pre-upload-bar-done" 56 :style="{ (updateState / preFilesLength) * 100 + '%' }" 57 ></div> 58 </div> 59 <!-- <div v-if="updateState === preFilesLength ? true : false"> --> 60 61 <ul class="pre-upload-list"> 62 <transition-group tag="li" name="list"> 63 <li 64 class="pre-upload-file" 65 v-for="file in preFiles" 66 :key="file.name" 67 > 68 <div class="review"> 69 <!-- 图片预览 --> 70 <div class="review-image-container"> 71 <img class="review-image" :src="file.src" /> 72 </div> 73 74 <div class="review-file-name-container"> 75 <span class="file-name"> {{ file.name }}</span> 76 </div> 77 <div class="progress"> 78 <div 79 class="progress-done" 80 :style="{ file.index + '%' }" 81 ></div> 82 </div> 83 <span class="percent">{{ file.index }}%</span> 84 <input 85 v-if="uploadStatu === 0 ? true : false" 86 class="select-box" 87 check 88 type="checkbox" 89 name="select" 90 :value="file.name" 91 :checked="file.isChecked" 92 @change="doSelect(file.name)" 93 ref="select" 94 :id="file.name" 95 /> 96 <label :for="file.name" style="margin-left:40px"></label> 97 </div> 98 </li> 99 </transition-group> 100 </ul> 101 102 <hr color="lightpink" style="margin: 10px 8px" /> 103 <!-- 选择按钮 --> 104 <div class="bottom"> 105 <div 106 class="bottom-shelter" 107 v-if="updateState != preFilesLength" 108 ></div> 109 <div class="bottom-content"> 110 <div class="bottom-select"> 111 <span 112 class="bottom-select-button" 113 @click="seletAll" 114 v-if="uploadStatu === 0 ? true : false" 115 > 116 {{ select }}</span 117 > 118 <div class="bottom-select-info"> 119 <div> 120 <span>已选</span> 121 <span 122 class="span-special" 123 :style="{ color: showSize() > maxSize ? 'red' : 'green' }" 124 >{{ showSelect() }} 125 </span> 126 <span>/{{ preFilesLength }}</span> 127 </div> 128 <div> 129 <span 130 class="span-special" 131 :style="{ color: showSize() > maxSize ? 'red' : 'green' }" 132 >{{ showSize() }} 133 </span> 134 <span>/ {{ showAllSize() }} mb </span> 135 </div> 136 </div> 137 </div> 138 139 <div 140 class="upload-statu-info" 141 v-if="uploadStatu == 0 ? false : true" 142 > 143 <span>{{ uploadStatu == 1 ? "上传ing" : "" }}</span> 144 <span>{{ uploadStatu == 2 ? "上传完成" : "" }}</span> 145 </div> 146 147 <div 148 class="bottom-upload" 149 @click="upload" 150 v-if="uploadStatu === 0 ? true : false" 151 > 152 <span>上传</span> 153 </div> 154 155 <div 156 class="bottom-upload" 157 @click="continueUpload" 158 v-if="uploadStatu === 2 ? true : false" 159 > 160 <span>清空列表</span> 161 </div> 162 </div> 163 </div> 164 </div> 165 </div> 166 </div> 167 </div> 168 </template> 169 170 <script> 171 // import { delete } from 'vue/types/umd'; 172 export default { 173 name: "upload", 174 props: { 175 userId: { 176 type: String, 177 validator(value) { 178 // 判断String是否为空 179 return value === "" ? false : true; 180 }, 181 default: "", 182 }, 183 184 maxSize: { 185 type: Number, 186 validator(value) { 187 return value > 0 ? true : false; 188 }, 189 default: 10, 190 }, 191 }, 192 data() { 193 return { 194 // 预展示的图片对象数组 195 preFiles: [], 196 197 // 渲染列表 198 preFilesLength: 0, 199 200 // 这个是渲染list的过程 渲染到第几个li 201 updateState: 0, 202 203 // 真正要上传的图片文件 204 files: [], 205 206 // 选择上传的文件 之前想到了更改 但是没有成功 就留下一个响应的变量 可以删除的 207 select: "全选", 208 209 // 是否进行拖动事件 210 isDrag: false, 211 212 // 上传进度 0 表示还未开始 1 表示开始上传 2 表示结束上传 213 uploadStatu: 0, 214 }; 215 }, 216 updated() {}, 217 mounted: function () { 218 var dragBox = this.$refs.dragBox; 219 dragBox.addEventListener("dragenter", this.onDrag, false); 220 dragBox.addEventListener("dragover", this.onDrag, false); 221 dragBox.addEventListener( 222 "dragleave", 223 () => { 224 this.isDrag = false; 225 }, 226 false 227 ); 228 dragBox.addEventListener("drop", this.onDrop, false); 229 }, 230 watch: { 231 // 通过对 preFiles 监听 实现简单加载文件进度条 232 preFiles: { 233 handler: "updateS", 234 }, 235 }, 236 methods: { 237 storepreFiles(obj) { 238 // 如果当前的上传状态是 0-未上传 239 if (this.uploadStatu === 0) { 240 // 获取input里面的文件组 241 var fileList = obj.files; 242 // 先判定有没有重复提交的文件 可以加判断条件 243 for (let i = 0; i < fileList.length; i++) { 244 for (let j = 0; j < this.preFiles.length; j++) { 245 if (this.preFiles[j].name === fileList[i].name) { 246 alert("有文件重复"); 247 return; 248 } 249 } 250 } 251 252 // 这个是处理 显示 有多少个文件在列表的情况 ( 因为可以在未上传的时候 继续添加 ) 253 this.preFilesLength = this.preFilesLength + fileList.length; 254 255 // 进行整合 ( 如果直接用push的话 这样会变成 两个Filelist 对象 组合 ) 256 for (var i = 0; i < fileList.length; i++) { 257 this.files = this.files.concat(fileList[i]); 258 } 259 260 // 下面的作用域会变 261 var vue = this; 262 263 function uploadFile(i) { 264 return new Promise(function (resolve, reject) { 265 let reader = new FileReader(); 266 reader.readAsDataURL(fileList[i]); 267 reader.onload = function () { 268 console.log("readed"); 269 resolve(this.result); 270 }; 271 }); 272 } 273 doUpload(); 274 async function doUpload() { 275 for (let i = 0; i < fileList.length; i++) { 276 let result = await uploadFile(i); 277 console.group(i); 278 console.groupEnd(); 279 vue.preFiles.push({ 280 name: fileList[i].name, 281 size: fileList[i].size, 282 index: 0, 283 src: result, 284 isChecked: true, 285 }); 286 } 287 } 288 } else { 289 alert("已经上传完毕!"); 290 } 291 }, 292 293 // 渲染列表的参数 294 updateS() { 295 if (this.updateState >= this.preFilesLength) { 296 // this.preFilesLength = this.preFilesLength - this.preFiles.filter(item => item.isChecked == false).length; 297 this.updateState = 298 this.updateState - (this.preFilesLength - this.preFiles.length); 299 } else { 300 this.updateState = this.updateState + 1; 301 } 302 }, 303 304 findPreFile(name) { 305 let that = this; 306 return this.preFiles.find((item, index) => { 307 return item.name === name; 308 }); 309 }, 310 311 // 点击 选择框 和 全选 312 doSelect(name) { 313 this.preFiles.forEach((element) => { 314 if (element.name == name) { 315 element.isChecked = !element.isChecked; 316 } 317 }); 318 }, 319 seletAll() { 320 if (this.$refs.select.find((item) => item.checked == false)) { 321 console.log(this.$refs.select); 322 this.preFiles.forEach((element) => { 323 element.isChecked = true; 324 }); 325 } 326 }, 327 328 // 显示 选中个数 和 显示 选中的文件大小 329 showSelect() { 330 let num = 0; 331 this.preFiles.forEach((element) => { 332 if (element.isChecked == true) { 333 num++; 334 } 335 }); 336 return num; 337 }, 338 339 showSize() { 340 let size = 0; 341 this.preFiles.forEach((element) => { 342 if (element.isChecked == true) { 343 size = size + element.size; 344 } 345 }); 346 return (size / Math.pow(2, 20)).toFixed(2); 347 }, 348 349 showAllSize() { 350 let size = 0; 351 this.preFiles.forEach((element) => { 352 size = size + element.size; 353 }); 354 return (size / Math.pow(2, 20)).toFixed(2); 355 }, 356 357 // 拖动文件上传 358 onDrag: function (e) { 359 this.isDrag = true; 360 // 取消默认事件 361 e.stopPropagation(); 362 e.preventDefault(); 363 }, 364 365 onDragLeave(e) {}, 366 367 onDrop: function (e) { 368 this.isDrag = false; 369 e.stopPropagation(); 370 e.preventDefault(); 371 var dt = e.dataTransfer; 372 this.storepreFiles(dt); 373 }, 374 375 // 继续文件上传 慎重使用这个方法 376 continueUpload() { 377 this.uploadStatu = 0; 378 this.preFiles = []; 379 this.files = []; 380 this.preFilesLength = this.files.length; 381 this.updateState = 0; 382 }, 383 // 文件上传 384 // 确定上传文件 通过 preFiles 的 isChecked 属性 取得每个文件的 name 和 真正要上传的文件的 name 交叉对比 385 checkFiles() { 386 console.log(this.preFiles); 387 let vue = this; 388 this.preFiles.forEach((element) => { 389 console.log(element.isChecked); 390 if (element.isChecked === false) { 391 delete vue.files[vue.preFiles.indexOf(element)]; 392 console.log("doing check"); 393 } 394 }); 395 console.log("doing do"); 396 this.files = this.files.filter((item) => item != "undefined"); 397 console.log(this.preFiles.filter((item) => item.isChecked == true)); 398 // this.preFiles = this.preFiles.filter((item)=>{ item.isChecked == true}) 399 this.preFilesLength = this.files.length; 400 this.updateState = this.files.length; 401 }, 402 upload() { 403 if (this.showSize() == 0) { 404 alert("您 啥也没选 选个毛"); 405 } 406 if (this.maxSize < this.showSize()) { 407 alert("文件太大!请重新选择!"); 408 } else { 409 let vue = this; 410 function firstDo() { 411 return new Promise(function (resolve, reject) { 412 if (vue.preFilesLength != 0) { 413 vue.checkFiles(); 414 vue.preFilesLength = vue.files.length; 415 console.log("updating " + vue.preFiles); 416 } 417 resolve(); 418 }); 419 } 420 421 firstDo().then(() => { 422 vue.files.forEach((element) => { 423 vue.uploadSingleFile(element); 424 }); 425 vue.uploadStatu = 2; 426 }); 427 } 428 }, 429 430 uploadSingleFile(file) { 431 // 先找到匹配的 预览文件 位置 方便写入 index 432 433 let position = 0; 434 let vue = this; 435 this.preFiles.forEach((element) => { 436 if (element.name == file.name) { 437 position = vue.preFiles.indexOf(element); 438 } 439 }); 440 441 this.uploadStatu = 1; 442 443 let param = new FormData(); // 创建form对象 444 param.append("file", file); // 通过append向form对象添加数据 445 console.log(param.get("file")); // FormData私有类对象,访问不到,可以通过get判断值是否传进去 446 vue.axios 447 .post("/demo/upload_test", param, { 448 // 给个请求头,让后端知道应该怎么处理数据 449 headers: { 450 "Content-Type": "multipart/form-data", 451 }, 452 onUploadProgress: (progressEvent) => { 453 let processStatu = 454 ((progressEvent.loaded / progressEvent.total) * 100) | 0; 455 vue.preFiles[position].index = processStatu; 456 }, 457 }) 458 .then((response) => { 459 vue.preFiles = vue.preFiles.filter((item) => item.isChecked == true); 460 console.log(response.data); 461 }); 462 }, 463 }, 464 }; 465 </script> 466 467 <style scoped> 468 span { 469 font-weight: 700; 470 font: bolder; 471 } 472 473 .container { 474 width: 450px; 475 /* background-color: rgb(195, 209, 228); */ 476 box-shadow: 2px 2px 2px 2px rgba(68, 68, 68, 0.2); 477 border-radius: 10px; 478 padding: 20px; 479 } 480 481 .upload-box { 482 /* 400px; */ 483 /* height: 400px; */ 484 } 485 486 .title-container { 487 margin: 10px 8px 20px 8px; 488 font-size: 20px; 489 490 border-radius: 5px; 491 background: linear-gradient(to right, rgb(112, 158, 242), rgb(255, 255, 255)); 492 color: white; 493 } 494 495 .drag-box { 496 padding: 20px; 497 margin: 10px auto; 498 width: 380px; 499 border-radius: 10px; 500 border: 5px dashed rgba(112, 155, 248, 0.7); 501 text-align: center; 502 transition: all linear 0.1s; 503 } 504 505 .label-text-container { 506 margin: 20px 8px; 507 font-size: 20px; 508 } 509 510 .text-container { 511 margin: 20px 8px; 512 font-size: 20px; 513 border-radius: 5px; 514 background: linear-gradient(to right, rgb(112, 158, 242), rgb(255, 255, 255)); 515 color: white; 516 } 517 518 .drag-box .label-text-container { 519 color: rgba(190, 190, 190, 0.8); 520 transition: all linear 0.1s; 521 } 522 523 .icon-container { 524 margin: 15px; 525 } 526 527 .fa-file { 528 color: rgba(247, 187, 219, 0.8); 529 font-size: 100px; 530 transition: all linear 0.1s; 531 } 532 533 /* 当进入文件的时候添加css */ 534 .drag-box:hover { 535 border: 5px dashed rgba(112, 155, 248, 1); 536 } 537 538 .drag-box:hover .fa-file { 539 color: rgb(245, 124, 188); 540 } 541 542 .drag-box:hover .label-text-container { 543 color: rgb(68, 68, 68); 544 } 545 546 /* 当拖动文件的时候添加css */ 547 .draging-drag-box { 548 border: 5px dashed rgba(112, 155, 248, 1); 549 } 550 551 .draging-fa-file { 552 color: rgb(245, 124, 188); 553 } 554 555 .draging-label-text-container { 556 color: rgb(68, 68, 68); 557 } 558 559 /* 点击上传文件的时候的那个input */ 560 label input { 561 display: none; 562 } 563 564 .review { 565 border: 1px solid transparent; 566 border-radius: 5px; 567 color: #777; 568 display: flex; 569 font-size: 12px; 570 align-items: center; 571 padding: 10px; 572 margin: 5px 8px; 573 } 574 575 .review:hover { 576 cursor: pointer; 577 /* border: 1px solid #ddd; */ 578 box-shadow: 0 3px 10px -5px rgba(0, 0, 0, 0.7); 579 } 580 581 .pre-upload-bar { 582 margin: 10px 10px; 583 height: 20px; 584 } 585 586 .pre-upload-bar span { 587 font-size: 12px; 588 font-weight: 20px; 589 color: #777; 590 } 591 592 .pre-upload-bar-done { 593 background: linear-gradient(to left, rgb(112, 158, 242), rgb(119, 140, 255)); 594 box-shadow: 0 3px 3px -5px rgb(100, 115, 143), rgb(134, 138, 165); 595 border-radius: 5px; 596 height: 3px; 597 width: 0; 598 transition: width ease 0.2s; 599 } 600 601 .review-file-name-container { 602 width: 100px; 603 } 604 605 .review-image-container { 606 margin: 0 8px 0 0; 607 } 608 609 .review-image { 610 width: 50px; 611 } 612 613 .pre-upload-list { 614 list-style: none; 615 /* 取消缩进 */ 616 margin: 0px; 617 padding: 0px; 618 } 619 620 .file-name { 621 width: 100%; 622 float: left; 623 overflow: hidden; 624 text-overflow: ellipsis; 625 white-space: normal; 626 } 627 628 .progress { 629 background-color: rgba(100, 100, 100, 0.2); 630 border-radius: 5px; 631 position: relative; 632 margin: 0 10px; 633 height: 10px; 634 width: 150px; 635 } 636 637 .progress-done { 638 background: linear-gradient(to left, rgb(242, 112, 156), rgb(255, 148, 114)); 639 box-shadow: 0 3px 3px -5px rgb(242, 112, 156), 0 2px 5px rgb(242, 112, 156); 640 border-radius: 5px; 641 height: 10px; 642 width: 0; 643 transition: width ease 0.1s; 644 } 645 646 .select-box { 647 width: 20px; 648 height: 20px; 649 margin: 0 0 0 36px; 650 } 651 652 /* list的transition group */ 653 .list-enter, 654 .list-leave-to { 655 opacity: 0; 656 } 657 658 .list-enter-active { 659 animation: moveIn 1s; 660 } 661 662 .list-leave-active { 663 animation: moveOut 1s; 664 /* transition: all 1s linear; */ 665 } 666 667 @keyframes moveIn { 668 0% { 669 opacity: 0; 670 transform: translate(30px, 15px); 671 } 672 673 30% { 674 opacity: 0.5; 675 transform: translate(0px, 15px); 676 } 677 678 100% { 679 opacity: 1; 680 transform: translate(0, 0px); 681 } 682 } 683 684 @keyframes moveOut { 685 0% { 686 opacity: 1; 687 transform: translate(0, 0px); 688 } 689 690 30% { 691 opacity: 0.5; 692 transform: translate(0px, 15px); 693 } 694 695 100% { 696 opacity: 0; 697 transform: translate(30px, 15px); 698 } 699 } 700 .upload-statu-info { 701 padding: 0 10px; 702 margin: 3px 8px; 703 } 704 .upload-statu-info span { 705 letter-spacing: 5px; 706 font-weight: 300px; 707 } 708 709 .bottom-select { 710 margin: 5px 8px; 711 padding: 0 10px; 712 height: 40px; 713 } 714 715 .bottom-select .bottom-select-button { 716 cursor: pointer; 717 color: white; 718 margin: 5px; 719 padding: 7px; 720 float: right; 721 transition: all linear 0.1s; 722 background-color: rgb(247, 159, 172); 723 border-radius: 5px; 724 } 725 726 .bottom-select-info span { 727 font-size: 15px; 728 font-weight: 200; 729 } 730 731 .bottom-select-info .span-special { 732 font-weight: 1000; 733 } 734 735 .bottom-upload { 736 margin: 5% auto; 737 width: 160px; 738 height: 40px; 739 text-align: center; 740 padding: 5px; 741 border-radius: 5px; 742 background-color: lightpink; 743 color: white; 744 cursor: pointer; 745 } 746 747 .bottom-upload span { 748 letter-spacing: 5px; 749 margin: auto; 750 font-size: 30px; 751 } 752 753 .bottom { 754 position: relative; 755 width: 450px; 756 height: 150px; 757 } 758 759 .bottom-shelter { 760 position: absolute; 761 width: 100%; 762 height: 150px; 763 cursor: not-allowed; 764 z-index: 1; 765 } 766 767 .bottom-content { 768 width: 100%; 769 position: absolute; 770 z-index: 0; 771 } 772 773 /* 复选框 */ 774 input[type="checkbox"] + label::before { 775 content: "a0"; /* non-break space */ 776 display: inline-block; 777 width: 20px; 778 height: 20px; 779 border-radius: 3px; 780 border: solid 2px rgba(0, 117, 255, 0.4); 781 background: rgb(255, 255, 255); 782 line-height: 20px; 783 vertical-align: middle; 784 } 785 786 input[type="checkbox"]:checked + label::before { 787 /* content: "2713"; */ 788 content: "✔"; 789 text-align: center; 790 top: 20px; 791 font-size: 24px; 792 background: lightpink; 793 display: table-cell; 794 color: aliceblue; 795 } 796 797 input[type="checkbox"] { 798 position: absolute; 799 clip: rect(0, 0, 0, 0); 800 } 801 </style>