zoukankan      html  css  js  c++  java
  • 2/22 实现一个图片上传的 Vue 组件( 完善版本 )

    写在前面

      之前有一个关于这个的 随笔

      但是没有 优化 过

      这个是简单优化过的版本 

      暂时只有代码 


      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>
    Let it roll
  • 相关阅读:
    Delphi内JPG与BMP的互相转换
    通过设置数据单元格的hint和ToolTips属性,当移动鼠标到该单元格时,可以显示单元格容纳不下的文本内容...
    delphi内进行音量控制及静音
    误把TXT文件关联设成CMD的解决办法
    精通批处理教程
    我的Qzone第一天
    寻找第K大的数的方法总结
    算法的力量(李开复)
    HDOJ 1001
    添加收藏夹
  • 原文地址:https://www.cnblogs.com/WaterMealone/p/14431328.html
Copyright © 2011-2022 走看看