整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          手把手教你前端的各種文件上傳攻略和大文件斷點續(xù)傳



          在前面

          今年國慶假期終于可以憋在家里了不用出門了,不用出去看后腦了,真的是一種享受。這么好的光陰怎么浪費,睡覺、吃飯、打豆豆這怎么可能(耍多了也煩),完全不符合我們程序員的作風(fēng),趕緊起來把文章寫完。

          這篇文章比較基礎(chǔ),在國慶期間的業(yè)余時間寫的,這幾天又完善了下,力求把更多的前端所涉及到的關(guān)于文件上傳的各種場景和應(yīng)用都涵蓋了,若有疏漏和問題還請留言斧正和補充。

          自測讀不讀

          以下是本文所涉及到的知識點,break or continue ?

          • 文件上傳原理
          • 最原始的文件上傳
          • 使用 koa2 作為服務(wù)端寫一個文件上傳接口
          • 單文件上傳和上傳進度
          • 多文件上傳和上傳進度
          • 拖拽上傳
          • 剪貼板上傳
          • 大文件上傳之分片上傳
          • 大文件上傳之?dāng)帱c續(xù)傳
          • node 端文件上傳

          原理概述

          原理很簡單,就是根據(jù) http 協(xié)議的規(guī)范和定義,完成請求消息體的封裝和消息體的解析,然后將二進制內(nèi)容保存到文件。

          我們都知道如果要上傳一個文件,需要把 form 標簽的enctype設(shè)置為multipart/form-data,同時method必須為post方法。

          那么multipart/form-data表示什么呢?

          multipart互聯(lián)網(wǎng)上的混合資源,就是資源由多種元素組成,form-data表示可以使用HTML Forms 和 POST 方法上傳文件,具體的定義可以參考RFC 7578。

          multipart/form-data 結(jié)構(gòu)

          看下 http 請求的消息體



          • 請求頭:

          Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDCntfiXcSkPhS4PN 表示本次請求要上傳文件,其中boundary表示分隔符,如果要上傳多個表單項,就要使用boundary分割,每個表單項由———XXX開始,以———XXX結(jié)尾。

          • 消息體- Form Data 部分

          每一個表單項又由Content-Type和Content-Disposition組成。

          Content-Disposition: form-data 為固定值,表示一個表單元素,name 表示表單元素的 名稱,回車換行后面就是name的值,如果是上傳文件就是文件的二進制內(nèi)容。

          Content-Type:表示當(dāng)前的內(nèi)容的 MIME 類型,是圖片還是文本還是二進制數(shù)據(jù)。

          解析

          客戶端發(fā)送請求到服務(wù)器后,服務(wù)器會收到請求的消息體,然后對消息體進行解析,解析出哪是普通表單哪些是附件。

          可能大家馬上能想到通過正則或者字符串處理分割出內(nèi)容,不過這樣是行不通的,二進制buffer轉(zhuǎn)化為string,對字符串進行截取后,其索引和字符串是不一致的,所以結(jié)果就不會正確,除非上傳的就是字符串。

          不過一般情況下不需要自行解析,目前已經(jīng)有很成熟的三方庫可以使用。

          至于如何解析,這個也會占用很大篇幅,后面的文章在詳細說。

          最原始的文件上傳

          使用 form 表單上傳文件

          在 ie時代,如果實現(xiàn)一個無刷新的文件上傳那可是費老勁了,大部分都是用 iframe 來實現(xiàn)局部刷新或者使用 flash 插件來搞定,在那個時代 ie 就是最好用的瀏覽器(別無選擇)。

          DEMO



          這種方式上傳文件,不需要 js ,而且沒有兼容問題,所有瀏覽器都支持,就是體驗很差,導(dǎo)致頁面刷新,頁面其他數(shù)據(jù)丟失。

          HTML

           <form method="post" action="http://localhost:8100" enctype="multipart/form-data">
          
                  選擇文件:
                      <input type="file" name="f1"/> input 必須設(shè)置 name 屬性,否則數(shù)據(jù)無法發(fā)送<br/>
          <br/>
                      標題:<input type="text" name="title"/><br/><br/><br/>
          
                  <button type="submit" id="btn-0">上 傳</button>
          
          </form>
          
          復(fù)制代碼

          文件上傳接口

          服務(wù)端文件的保存基于現(xiàn)有的庫koa-body結(jié)合 koa2實現(xiàn)服務(wù)端文件的保存和數(shù)據(jù)的返回。

          在項目開發(fā)中,文件上傳本身和業(yè)務(wù)無關(guān),代碼基本上都可通用。

          在這里我們使用koa-body庫來實現(xiàn)解析和文件的保存。

          koa-body 會自動保存文件到系統(tǒng)臨時目錄下,也可以指定保存的文件路徑。



          然后在后續(xù)中間件內(nèi)得到已保存的文件的信息,再做二次處理。

          • ctx.request.files.f1 得到文件信息,f1為input file 標簽的 name
          • 獲得文件的擴展名,重命名文件

          NODE

          /**
           * 服務(wù)入口
           */
          var http = require('http');
          var koaStatic = require('koa-static');
          var path = require('path');
          var koaBody = require('koa-body');//文件保存庫
          var fs = require('fs');
          var Koa = require('koa2');
          
          var app = new Koa();
          var port = process.env.PORT || '8100';
          
          var uploadHost= `http://localhost:${port}/uploads/`;
          
          app.use(koaBody({
              formidable: {
                  //設(shè)置文件的默認保存目錄,不設(shè)置則保存在系統(tǒng)臨時目錄下  os
                  uploadDir: path.resolve(__dirname, '../static/uploads')
              },
              multipart: true // 開啟文件上傳,默認是關(guān)閉
          }));
          
          //開啟靜態(tài)文件訪問
          app.use(koaStatic(
              path.resolve(__dirname, '../static') 
          ));
          
          //文件二次處理,修改名稱
          app.use((ctx) => {
              var file = ctx.request.files.f1;//得道文件對象
              var path = file.path;
              var fname = file.name;//原文件名稱
              var nextPath = path+fname;
              if(file.size>0 && path){
                  //得到擴展名
                  var extArr = fname.split('.');
                  var ext = extArr[extArr.length-1];
                  var nextPath = path+'.'+ext;
                  //重命名文件
                  fs.renameSync(path, nextPath);
              }
              //以 json 形式輸出上傳文件地址
              ctx.body = `{
                  "fileUrl":"${uploadHost}${nextPath.slice(nextPath.lastIndexOf('/')+1)}"
              }`;
          });
          
          /**
           * http server
           */
          var server = http.createServer(app.callback());
          server.listen(port);
          console.log('demo1 server start ......   ');
          復(fù)制代碼

          CODE

          https://github.com/Bigerfe/fe-learn-code/

          013年,在Google工作的尤雨溪,開發(fā)出了一款輕量Javascript框架,最初命名為Seed,同年12月,更名為Vue,一經(jīng)推出發(fā)展迅速,如今已成為最時髦和炙手可熱前端框架,在Github上獲得了超過十萬的Star,國內(nèi)許多知名公司都使用Vue作為前端開發(fā)工具,例如餓了么、美團等,很多公司的招聘要求都會把會使用vue作為加分項。

          Vue的作者 - 尤雨溪

          001-Vue的定位是什么?

          Vue是一個JavaScript框架,類似的框架有React,Angular等等,所謂框架就是一個比較大型的庫,使用它能讓基于網(wǎng)頁的前端應(yīng)用程序開發(fā)起來更加方便。相對于完全采用原生JavaScript代碼來編寫前端代碼而言,使用框架的代碼量更少,開發(fā)效率也更高。

          需要注意的是,Vue并非UI框架,它的定位與Bootstrap、Frozen UI這一類專注于頁面呈現(xiàn)的框架不是一回事,或者說,UI框架關(guān)注點在HTML,而Vue這一類框架關(guān)注點是JavaScript。Vue可以跟很多UI框架搭配使用,比如說Bootstrap(vueBootstrap),和一些專門與Vue配合的例如餓了嗎團隊開發(fā)的element-ui等等。




          002-實現(xiàn)第一個VueJS應(yīng)用.html

          1.下載Vue: https://unpkg.com/vue/dist/vue.js

          2.將vue.js拷貝到任意一個目錄(工作目錄)

          3.在同一個目錄下用編輯軟件(隨便什么都行,記事本都可以)新建一個文件,輸入以下代碼:

             <script src="vue.js"></script>
             <div id="app">
                 <p>{{title}}</p>
             </div>
             <script>
                 new Vue({
                     el:"#app",
                     data:{
                         title: "Hello World!"
                     }
                 });


          003-擴展這個VueJS應(yīng)用.html

          • 代碼如下:
            <script src="vue.js"></script>
            <div id="app">
                <input type="text" v-on:input="changeTitle">
                <p>{{title}}</p>
            </div>
            <script>
                new Vue({
                    el: "#app",
                    data: {
                        title: "Hello World!"
                    },
                    methods: {
                        changeTitle:function (event) {
                            this.title = event.target.value;
                        }
                    }
                });
            </script>
          • 試試看,你輸入的每一個字符都會被克隆,很有意思的一個程序!

          009-VueJS的模板語法和實例.html

          • 在上面的例子中,通過創(chuàng)建這個新的Vue實例,注意,雖然沒有把它存入一個變量內(nèi),Vue實例還是被創(chuàng)建了。
          • 通過創(chuàng)建這個Vue實例,就跟我們的HTML代碼建立了一個聯(lián)系,Vue基于上面的HTML代碼,創(chuàng)建了一個模版,要特別注意的是,Vue在運行的時候,并不直接使用我們寫的HTML代碼。實際運行的網(wǎng)頁里面也沒有我們寫的這些命令(最終頁面你并看不到大括號之類的東西)
          • Vue根據(jù)HTML代碼創(chuàng)建的模版存儲在內(nèi)部,然后用這個模版,創(chuàng)建真正渲染成DOM的HTML代碼,我們寫的HTML代碼并不是最后在瀏覽器里面運行的那一份,中間有一層Vue實例把HTML代碼轉(zhuǎn)換成模版,然后渲染模版(比如花括號里面的{{title}}}等),最終再由它輸出渲染后的HTML代碼
          • Vue實例中存儲在data屬性內(nèi)的數(shù)據(jù),例如上面的title,可以直接在data屬性內(nèi)修改他們,也可以調(diào)用methods里的函數(shù)去生成內(nèi)容。例如:
            <script src="vue.js"></script>
            <div id="app">
                <!-- 注意此處并沒有調(diào)用Vue模板,而是調(diào)用了一個函數(shù) -->
                <p>{{sayHello()}}</p>
            </div>
            <script>
                new Vue({
                    el: "#app",
                    data: {
                        title: "Hello World!"
                    },
                    methods: {
                        //此處sayHello是一個方法(函數(shù)),但它返回的內(nèi)容被傳送到模版里去了
                        sayHello:function (event) {
                            return 'Hello!';
                        }
                    }
                });
            </script>

          010-訪問Vue實例里的數(shù)據(jù).html

          • 在vue實例中的任何地方,都可以用this來訪問所有的屬性和方法
            <script src="vue.js"></script>
            <div id="app">
                <p>{{sayHello()}}</p>
            </div>
            <script>
                new Vue({
                    el: "#app",
                    data: {
                        title: "Hello World!"
                    },
                    methods: {
                        sayHello:function (event) {
                        //注意此處,return的是Hello World!
                        //原生的JS是不允許這么調(diào)用的,Vue在中間層做了調(diào)劑
                            return this.title;
                        }
                    }
                });
            </script>

          011-屬性綁定.html

             <script src="vue.js"></script>
             <div id="app">
                 <!-- 注意:此處不能用<a href="{{link}}"> 這種形式來傳遞超鏈接值,只能用v-bind綁定并設(shè)置href的值 -->
                 <p>{{sayHello()}} - <a v-bind:href="link">Baidu</a> </p>
             </div>
             <script>
                 new Vue({
                     el: "#app",
                     data: {
                         title: "Hello World!",
                         link:"http://baidu.com"
                     },
                     methods: {
                         sayHello:function () {
                             return this.title;
                         }
                     }
                 });
             </script>
          

          012-理解和使用指令

          • 指令就是你放在代碼中的一些指示
          • 例如上面v-bind,它將一些東西和我們的數(shù)據(jù)(href值)綁定,一般來說如果需要在HTML內(nèi)直接顯示內(nèi)容的時候,我們采用花括號來解決,但在花括號不適用的時候,就要使用指令。
          • 格式: 指令 + 冒號 + 參數(shù),如 v-bind:href = "link" link 就是你想要的從Vue實例中綁定的內(nèi)容,可以是屬性或者函數(shù) 一般來說你不能給HTML屬性傳遞動態(tài)數(shù)據(jù),有了這些Vue指令,就可以了

          013-用v-once禁止二次渲染.html

          • 輸入以下代碼:
          <script src="vue.js"></script>
          <div id="app">
            <!-- 注意此處的v-once -->
            <h1 v-once>{{title}}</h1>
            <p>{{sayHello()}}</p>
          </div>
          <script>
            new Vue({
                el: "#app",
                data: {
                    title: "Hello World!",
                },
                methods: {
                    sayHello:function () {
                        this.title = 'Hello';
                        return this.title;
                    }
                }
            });
          </script>
          • 運行以上代碼,我們原意是想要一個Hello World!和Hello的,但兩個元素都被渲染成Hello了,怎么辦?答案是:使用v-once, 即在第一個元素h1那里加上一個v-once,則表示該元素只會被渲染一次,之后不能再改變。

          014-如何輸出基礎(chǔ)的HTML.html

          • 輸入以下代碼:
          <script src="vue.js"></script>
          <div id="app">
            <!-- 注意此處的v-once -->
            <h1 v-once>{{title}}</h1>
            <p>{{sayHello()}}</p>
            <!-- 注意:v-html指令讓finishedLink以渲染后的HTML格式輸出,而不是純文本
            如果以花括號{{finishedLink}} 來表示的話,將會是一個HTML形式的純文本。
            -->
            <p v-html = "finishedLink" ></p>
          </div>
          <script>
            new Vue({
                el: "#app",
                data: {
                    title: "Hello World!",
                    finishedLink:'<a href="http://baidu.com">Baidu.com</a>',
                },
                methods: {
          
                    sayHello:function () {
                        this.title = 'Hello';
                        return this.title;
                    }
                }
            });
          </script>
          • 以上這段代碼并不會輸出一個Baidu.com的超鏈接,而是連同HTML代碼一起全部輸出了。這樣的好處是,HTML元素不會被解析和渲染,而是被輸出為純文本,避免了跨站攻擊。
          • 但是,如果某些情況下你確實想輸出渲染后的HTML代碼,比如此處你確實希望看到一個百度的超鏈接,你就應(yīng)該使用v-html指令。在需要的元素內(nèi)嵌入v-html即可。請謹慎使用這個指令,它會把你暴露給XSS攻擊,比如此處的內(nèi)容可能是由用戶提交的,你是不能控制用戶上傳什么內(nèi)容的,若確定內(nèi)容安全,或者是由你自己合成的代碼,那就可以放心使用v-html

          015 作業(yè): 輸出數(shù)據(jù)到模板

          • 輸出你的姓名和年齡,兩者都作為屬性保存在data內(nèi),在P標簽里面輸出
          <script src="vue.js"></script>
          <div id="app">
            <p>I am {{name}}</h1>
            <p>I am {{age}} years old.</p>
          </div>
          <script>
            new Vue({
                el: "#app",
                data: {
                    name: "Rockage",
                    age: "40",
                }
            });
          </script>
          • 在插值語法中,即在大括號中, 使用JavaScript表達式,輸出的年齡乘以3 <script src="vue.js">
          <script src="vue.js"></script>
          <div id="app">
            <p>I am {{name}}</p>
            <p>I am {{age * 3}} years old.</p>
          </div>
          <script>
            new Vue({
                el: "#app",
                data: {
                    name: "Rockage",
                    age: "40",
                }
            });
          </script>
          • 調(diào)用函數(shù),輸出這個函數(shù)的返回值,函數(shù)返回一個0到1之間的隨機數(shù)
          <script src="vue.js"></script>
          <div id="app">
            <p>The Random Number is : {{sayRnd()}}</p>
          </div>
          <script>
            new Vue({
                el: "#app",
                methods: {
                    sayRnd: function () {
                        // random_number = Math.random(); // 產(chǎn)生一個0到1的隨機數(shù)
                        // random_number = random_number.toFixed(2) //保留兩位小數(shù)
                        // 產(chǎn)生一個1到100的隨機數(shù)
                        random_number = Math.floor(Math.random() * (100 - 1 + 1)) + 1
                        return random_number;
                    }
                }
            });
          </script>
          • 使用baidu, 讓一個img標簽顯示搜索到的圖片
          • 和超鏈接的href屬性一樣,也不允許直接將URL強填進img的src里面,應(yīng)該把URL存在data里面,綁定到img標簽的src屬性
          <script src="vue.js" xmlns:v-bind="http://www.w3.org/1999/xhtml"></script>
          <div id="app">
            <img  v-bind:src = "url">
          </div>
          <script>
            new Vue({
                el: "#app",
                data: {
                    url: "http://img.mp.itc.cn/upload/20170217/1f650c2645c541ad8cc70842d7d0bbe5_th.jpeg"
                }
            });
          </script>
          • 用名字預(yù)先填充一個TextBox,讓它默認顯示你的名字
          <script src="vue.js" xmlns:v-bind="http://www.w3.org/1999/xhtml"></script>
          <div id="app">
            Name <input type = "text" v-bind:value = "default_name" />
          </div>
          <script>
            new Vue({
                el: "#app",
                data: {
                    default_name: "Hua Yang"
                }
            });
          </script>

          017-監(jiān)聽事件.html

          • 通過指令v-on監(jiān)聽來自模版的事件
          • v-on 可以監(jiān)聽任何由這個按鈕產(chǎn)生的DOM事件
          • 格式: v-on:事件名稱 = "事件函數(shù)名",如v-on:click = "increase"
          <script src="vue.js"></script>
          <div id="app">
            <button v-on:click="increase">Click Me</button>
            <p>{{ counter}}</p>
          </div>
          <script>
            new Vue({
                el: "#app",
                data: {
                    counter: 0,
                    x: 0,
                    y: 0
                },
                methods: {
                    increase: function () {
                        this.counter++;
                    }
                },
          
            });
          </script>

          018-從事件對象里獲取事件數(shù)據(jù).html

          • 用v-on來獲取事件數(shù)據(jù)
          • 這個例子很好地說明了如何通過Vue傳遞事件對象
          <script src="vue.js"></script>
          <div id="app">
          <p v-on:mousemove="updateCoordinates">Coordonates : {{x}}/{{y}}</p>
          </div>
          <script>
          new Vue({
              el: "#app",
              data: {
                  counter: 0,
                  x: 0,
                  y: 0
              },
              methods: {
                  updateCoordinates: function (event) {
                      this.x = event.clientX;
                      this.y = event.clientY;
                  }
              },
          
          });
          </script>

          019-傳遞你自己的事件參數(shù).html

          • 給組件傳遞自定義參數(shù),方法很簡單,在調(diào)用函數(shù)或者說設(shè)置引用時傳入?yún)?shù)即可。
          • 加入不僅要傳遞自定義參數(shù),還要傳遞DOM生成的事件對象給方法,也很簡單,只需要再加一個參數(shù),但這個參數(shù)的命名需為$event,vue會自動捕捉這個默認的事件參數(shù),并復(fù)制給一個可以在函數(shù)中使用的變量。
          • 本例中不對$event做詳解,下面的例子會提到它
          <script src="vue.js"></script>
          <div id="app">
            <!--
            請注意, 調(diào)用increase時增加了兩個參數(shù)
            -->
            <button v-on:click="increase(2,$event)">Click Me</button>
            <p>{{ counter}}</p>
          </div>
          <script>
            new Vue({
                el: "#app",
                data: {
                    counter: 0,
                    x: 0,
                    y: 0
                },
                methods: {
                    // increase 函數(shù)用step和event接收參數(shù)
                    increase: function (step,event) {
                        this.counter += step;
                    }
                },
            });
          </script>

          020-用事件修飾符來修改事件.html

          • 現(xiàn)在我們用第11課(鼠標懸浮顯示XY坐標)那個為例
          • 我想設(shè)置一個區(qū)域,姑且稱為DEAD SPOT, 鼠標進入這個區(qū)域XY停止更新。
          • 常規(guī)方法是讓這個SPAN也綁定一個mousemove事件,然后調(diào)用一個事件函數(shù),事件函數(shù)不做任何處理。

          HTML部分:

          <span v-on:mousemove = "dummy">DEAD SPOT</span>


          JS部分:

          dummy: function(){
            event.stopPropagation();
          } 

          以上代碼確保事件不會傳播給綁定有這個屬性的SPAN上,當(dāng)然,我們還有更簡單的方法,就是使用事件修飾符,使用stop修飾符即可實現(xiàn)這個目的:

          <script src="vue.js"></script>
          <div id="app">
              <p v-on:mousemove="updateCoordinates">Coordonates : {{x}}/{{y}}
                  <!-- 請注意下面這個SPAN綁定的事件mousemove
                  采用了.stop修飾符,表示此元素的mousemove事件不做任何處理
                  -->
                  - <span v-on:mousemove.stop = "">DEAD SPOT</span>
              </p>
          </div>
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      counter: 0,
                      x: 0,
                      y: 0
                  },
                  methods: {
                      updateCoordinates: function (event) {
                          this.x = event.clientX;
                          this.y = event.clientY;
                      }
                  },
              });
          </script>
          

          021-監(jiān)聽鍵盤事件.html

          • 這個例子很簡單,在輸入框中監(jiān)控鍵盤事件
          • 一旦敲入回車鍵,則彈出Alert窗口。
          <script src="vue.js"></script>
          <div id="app">
              <input type = "text" v-on:keyup.enter="alertMe" />
          </div>
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      counter: 0,
                      x: 0,
                      y: 0
                  },
                  methods: {
                      alertMe: function () {
                          alert("Alert!");
                      }
                  },
              });
          </script>
          

          022-作業(yè):事件

          • 作業(yè)2-1:按下按鈕,彈出警告框。
          <script src="vue.js"></script>
          <div id="app">
            <button v-on:click="alertMe">Show Alert</button>
          </div>
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      counter: 0,
                      x: 0,
                      y: 0
                  },
                  methods: {
                      alertMe: function () {
                          alert("You Clicked me!");
                      }
                  },
              });
          </script>
          
          • 作業(yè)2-2:記錄鍵盤輸入的值,存到data里并立即顯示。
          • 特點1: 除了直接綁定到一個方法,也可以在內(nèi)聯(lián) JavaScript 語句中調(diào)用方法,即不再綁定一個函數(shù),直接在調(diào)用的時候執(zhí)行JS語句,這種方法適用于函數(shù)體很短,只有一行的函數(shù)。
          • 特點2:采用了Vue的關(guān)鍵字$event,它表示JavaScript自動生成的默認事件對象,它保存了許多重要信息,如事件的目標元素等。而這里,目標元素就是輸入框,輸入框有一個value屬性,它就是用戶輸入值的來源,可以借由這個事件對象($event)來訪問它的目標元素(target),再訪問它的屬性(value),并賦值給變量myValue.
          • 如果不采用內(nèi)聯(lián)的方式,也可以在此綁定一個事件函數(shù),然后將$event作為參數(shù)傳遞進去,以同樣的方法在事件函數(shù)中訪問value并賦值給myValue,效果一樣。
          <script src="vue.js"></script>
          <div id="app">
              <input v-on:keydown = "myValue = $event.target.value" type="text"/>
              <p>{{myValue}}</p>
          </div>
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      myValue: ''
                  }
              });
          </script>
          
          • 作業(yè)2-3:記錄鍵盤輸入的值,存到data里,但只有按下enter才更新顯示。
          • 這題很簡單,在上題的基礎(chǔ)上增加一個.enter就可以了
          <script src="vue.js"></script>
          <div id="app">
              <input v-on:keydown.enter = "myValue = $event.target.value" type="text"/>
              <p>{{myValue}}</p>
          </div>
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      myValue: ''
                  }
              });
          </script>
          

          024-在模板中編寫JS代碼.html

          • 在所有能訪問Vue實例的地方,無論是監(jiān)聽鼠標點擊事件還是輸出counter,只要訪問了Vue實例, 就可以使用任何合法的JavaScript代碼,只要它是一個簡單表達式即可,當(dāng)然這個表達式中不能有if或者for循環(huán)等復(fù)雜語句。這就是所謂的“內(nèi)聯(lián)”調(diào)用法
          • 例如上面那個鼠標點擊按鈕增加數(shù)字的例子,因為邏輯足夠簡單,我們甚至在v-onclick里都不需要綁定任何函數(shù),而是可以直接讓它執(zhí)行一條語句,也就是說在調(diào)用的時候就直接寫成counter++即可。當(dāng)然,如果程序邏輯比較復(fù)雜,不足以用一條語句表達,那么還是老老實實編寫一個函數(shù)并綁定到事件里為好。
          • 在模板中也可以直接插入表達式,例如像{{counter * 2}}這樣的表達式。
          • 當(dāng)然,我們還以用簡短的“三元表達式”,這種表達式是if語句的一個變種,能夠在一行內(nèi)完成對變量的判斷。
          <script src="vue.js"></script>
          <div id="app">
              <!-- 此處取消了事件函數(shù),直接用counter++代替 -->
              <button v-on:click="counter++">Click Me</button>
              <!-- 此處直接修改模板counter的值 -->
              <p>{{ counter * 2}}</p>
              <!-- 此處是一個三元表達式,用以判斷counter的值 -->
              <p>{{ counter * 2 > 10 ? 'Greate than 10' : 'Smaller than 10'}}</p>
          </div>
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      counter: 0
                  }
              });
          </script>
          

          025-使用雙向綁定.html

          • 某些情況下,我們需要一邊同時監(jiān)聽事件,一邊實施更新數(shù)據(jù)的時候就要用到雙向綁定。
          • Vue的雙向綁定真的非常簡單,只需要在監(jiān)聽的的元素上加一個v-model即可
          • 其實用v-bind + v-on加上事件函數(shù),也能夠?qū)崿F(xiàn)這個功能,但是顯然v-model要簡單、清晰許多。
          <script src="vue.js"></script>
          <div id="app">
              <input type = "text" v-model = "name">
              <p>{{name}}</p>
          </div>
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      name: ''
                  }
              });
          </script>
          

          026-用計算屬性來響應(yīng)改變.html

          • 目前為止,我們已經(jīng)學(xué)會了兩種Vue的屬性:data, methods,一個放置數(shù)據(jù),一個放置函數(shù),現(xiàn)在引入第三個屬性:computed
          • 先大致梳理一下本例子的效果:
          • 界面有3個按鈕,兩個計數(shù)器,第一個按鈕是計數(shù)器1增加,第二個按鈕是計數(shù)器1減少,第三個按鈕是計數(shù)器2增加
          • 另有1行文本,當(dāng)counter1大于5時候顯示:Greater than 5, 當(dāng)counter1小于5時顯示:Smaller than 5
          • 注意:counter2并沒有做這樣的判斷
          • 為了實時監(jiān)控counter1的值,這里運用了兩種同步方法,第一種是在methods里寫一個事件函數(shù)result
          • 另一個辦法是運用computed,寫一個事件函數(shù)output
          • 乍看上去,兩者代碼是相同的,并沒有什么區(qū)別,但實際區(qū)別是:
          • 1、在調(diào)用方法上,寫在methods的函數(shù)是帶括號調(diào)用的,即: result() 這樣的形式來調(diào)用
          • 2、寫在computed的函數(shù)并不需要像調(diào)用函數(shù)那樣調(diào)用,省略了括號,直接像調(diào)用data一樣:output
          • 事實上,對于最終的結(jié)果,兩種方式確實是相同的,那么問題來了,有了methods不就可以了嗎?我們?yōu)槭裁催€多此一舉冒出一個computed呢?
          • 答案揭曉:
          • 1、在Vue內(nèi)部,computed屬性是基于他們的依賴的數(shù)據(jù)進行緩存的,只有在它相關(guān)的依賴發(fā)生改變時才會重新求值,與之不相關(guān)的數(shù)據(jù)變化的時候它不會做出任何動作。
          • 2、method則不同,只要頁面有所變化(重新渲染),它總會被執(zhí)行,無論數(shù)據(jù)是否與之相關(guān)。
          • 為了驗證以上結(jié)論,我們在界面上再放一個按鈕,再增加一個變量counter2,注意,這個按鈕和變量和counter1八竿子打不著關(guān)系,完全是一個獨立的東西。
          • 按下F12鍵,進入瀏覽器調(diào)試模式,觀察console控制臺輸出
          • 現(xiàn)在按下Increase Counter 1或Decrease Counter 1
          • counter1的值開始發(fā)生了變化,并且控制臺出現(xiàn):Here is Computed 和 Here is Methods 兩行字
          • 這是因為改變counter1值的時候,同時觸發(fā)了基于methods的result函數(shù),和基于computed的output函數(shù)
          • 以上邏輯容易理解,說得通!
          • 現(xiàn)在,按下Increase Counter 2按鈕,這時候counter2的值開始增大了,但是詭異的是,控制臺顯示:Here is Methods
          • 這是為什么呢?明明result函數(shù)里沒有任何一處是跟counter2有關(guān)系的,那為什么每次改變counter2的時候程序會跳進result函數(shù)里呢?
          • 這正好印證了我們上面說過的一個結(jié)論:“只要頁面有所變化(重新渲染),它總會被執(zhí)行,無論數(shù)據(jù)是否與之相關(guān)”
          • 刷新的雖然是counter2,但是請注意:只要頁面被重新渲染,它總是會被執(zhí)行。
          • 不言而喻,如果是很復(fù)雜的頁面,反復(fù)去執(zhí)行一個與之無關(guān)的函數(shù),效率開銷肯定是很大的。
          • 所以我們需要computed屬性,這個屬性只處理與之相關(guān)的變量(例如本例中的counter1),而與之無關(guān)的變量(counter2)即使發(fā)生變化,即使頁面被重新渲染了,它也不會被執(zhí)行。
          • 大部分時候我們選擇computed就對了,但是有時候我們就是希望某些數(shù)據(jù)不要經(jīng)過緩存,就是想讓它直截了不停刷新并顯示的時候,選擇methods
          <script src="vue.js"></script>
          <div id="app">
              <button v-on:click="counter1++">Increase Counter 1</button>
              <button v-on:click="counter1--">Decrease Counter 1</button>
              <button v-on:click="counter2++">Increase Counter 2</button>
              <p>Counter:{{counter1}} | {{counter2}}</p>
              <!-- 注意兩種調(diào)用方法是不同的 -->
              <p>Counter 1 Result: {{result()}} | {{output}}</p>
          </div>
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      counter1: 0,
                      counter2: 0
                  },
                  computed: {
                      output: function () {
                          console.log('Here is Computed')
                          return this.counter1 > 5 ? "Greater than 5" : "Smaller than 5"
                      }
                  },
                  methods: {
                      result: function () {
                          console.log('Here is Methods')
                          return this.counter1 > 5 ? "Greater than 5" : "Smaller than 5"
                      }
                  }
              });
          </script>
          

          027-計算屬性的替代:觀察改變.html

          • 現(xiàn)在再加入一個新的屬性:watch
          • watch是Vue提供了另外一種處理數(shù)據(jù)依賴的方法,其最終效果其實和上例的methods和computed差不太多,不同的地方在于:
          • 在computed屬性里面我們設(shè)置需要計算的屬性(如上例的output),然后在函數(shù)體里設(shè)置計算這個屬性(output)的邏輯。
          • watch則采用的是另外一種機制,把想要偵聽的屬性名稱設(shè)置為鍵, 而這個鍵必須是在data(或者computed)里面能夠找到的,例如上例的counter1或counter2,鍵名必須與data里面的屬性名稱相同。
          • 在watch中定義一個和屬性名相同的函數(shù),即當(dāng)屬性值發(fā)生變化時需要執(zhí)行的代碼,Vue會自動把屬性變化之后的數(shù)值傳遞給該函數(shù)。
          • 從結(jié)果而言,既然說watch和computed的作用相當(dāng),那我們?yōu)樯斗且脀atch不可呢?因為watch是異步執(zhí)行的。
          • computed只能執(zhí)行同步任務(wù),也就是說在它return的時候,必須立即返回一個結(jié)果,中途不能執(zhí)行異步任務(wù)(例如定時器或者訪問服務(wù)器等等)。
          • 比如說,你需要一個值,在兩秒之后更新,就必須用 watch
          • 理論上,在Vue實例里,總是可以用this訪問其自身(請參見前面的內(nèi)容),但在回調(diào)閉包中卻不行,定時器函數(shù)(setTimer)是一個典型的回調(diào)閉包的例子,因此不能在其中直接訪問this,而是要用一個變量先保存起來,以便在setTimer函數(shù)中使用。
          • 當(dāng)然,你也可以嘗試一下把這段代碼中的 watch 改成 computed 去試試看,結(jié)果就是只會清0一次,然后代碼就不再執(zhí)行了。
          • 大部分情況下,建議使用computed,因為它擁有最佳的速度,但是在執(zhí)行異步任務(wù)的時候,我們只能選擇watch <script src="vue.js"></script> <div id="app"> <button v-on:click="counter1++">Increase Counter 1</button> <p>Counter:{{counter1}}</p> </div> <script> new Vue({ el: "#app", data: { counter1: 0, }, watch: { counter1: function (value) { // 注意:回調(diào)閉包中不允許直接使用this // 因此此處需要先用一個變量vm來保存this var vm = this; setTimeout(function () { vm.counter1 = 0 }, 2000) } } }); </script>

          028 - 用縮寫來節(jié)省時間

          • Vue里有兩個使用頻率很高的指令:v-on和v-bind, 這兩個指令可以用@符合和:符號來替代
          • 例如像這樣的代碼是完全符合語法的:
          • 用@號替代v-on: <button @click="counter1++">Increase Counter 1</button>
          • 用:號替代v-bind: <a :href="link">Baidu</a>

          029 - 作業(yè):響應(yīng)式屬性

          • 1. 用computed屬性完成對value的判斷,等于37的時候result顯示Done,不等于的時候顯示Not there yet
          <!-- 作業(yè)3問題:響應(yīng)式屬性 - 作業(yè)1 -->
          <script src="vue.js"></script>
          <div id="app">
              <button v-on:click="value += 5">Add 5</button>
              <button v-on:click="value += 1">Add 1</button>
              <p>{{result}}</p>
          </div>
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      value: 0,
                  },
                  computed: {
                      result: function () {
                          return this.value == 37 ? "Done" : "Not there yet"
                      }
                  }
              });
          </script>
          
          • 2. 監(jiān)視變量value,當(dāng)它有變化的時候觸發(fā)定時器,5秒后將它歸零
          • 這一題有幾個重點:
          • 1、watch 不僅可以監(jiān)控data里的屬性,也可以監(jiān)控computed里面的屬性(如result)
          • 2、通過vm可以修改data里的屬性(如value),但是不能修改computed里的result
          • 3、本例是一個循環(huán)監(jiān)控的例子:起初是按鈕改變了value的值,value==37改變了result的值,result觸發(fā)了定時器,定時器又改變了value的值,value的值又反過來影響了result的值
          <script src="vue.js"></script>
          <div id="app">
              <button v-on:click="value += 5">Add 5</button>
              <button v-on:click="value += 1">Add 1</button>
              <p>Current Value:{{value}} </p>
              <p>{{result}}</p>
          </div>
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      value: 0
                  },
                  computed: {
                      result: function () {
                          return this.value == 37 ? "Done" : "Not there yet"
                      }
                  },
                  watch: {
                      result: function () { //watch可以監(jiān)聽computed里的屬性
                          var vm = this;
                          setTimeout(function () {
                              vm.value = 0
                              // 但無法修改computed里定義的屬性,即如果寫成 vm.result = "XXX" 則程序會報錯
                          }, 5000)
                      }
                  }
              });
          </script>
          

          031-CSS類動態(tài)樣式-基礎(chǔ).html

          • 設(shè)置CSS,demo是一個div塊,red是顏色
          • 監(jiān)聽click事件,并修改attachRed的值(只有一行代碼,即將attachRed取反)
          • 設(shè)置class值,修改div的class屬性,相當(dāng)于在red: true和red: false之間互相切換。
          • 整個過程實際上由兩部分完成,@click事件更改attachRed的值,而:class則負責(zé)根據(jù)attachRed的值反復(fù)設(shè)置css
          <style>
              .demo {
                  width: 100px;
                  height: 100px;
                  background-color: gray;
                  display: inline-block;
                  margin: 10px;
              }
          
              .red {
                  background-color: red;
              }
              }
          </style>
          <script src="vue.js"></script>
          <div id="app">
              <div
                      class="demo"
                      @click="attachRed = !attachRed"
                      :class="{red: attachRed}"
          
              ></div>
          
          </div>
          
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      attachRed: false
                  }
              });
          </script>

          032-CSS類動態(tài)樣式-使用對象

          • 接上一個例子,如果我們想讓div塊在紅色和藍色之間切換怎么辦呢?
          • 首先在style里增加一個css的blue類: .blue { background-color: blue; } -然后把class屬性改成這樣: :class="{red: attachRed, blue: !attachRed}"
          • 即當(dāng)attachRed為True時采用red,而當(dāng)attachRed為False的時候采用blue類。
          • 現(xiàn)在,這個對象變長了,而且將來隨著頁面的復(fù)雜還將越來越長,如果我們不想把這一行代碼寫進HTML模板里該怎么辦呢?我們可以把樣式對象作為Vue實例內(nèi)的計算屬性來處理。定義一個computed(計算)函數(shù)名稱為:divClasses,然后用這個函數(shù)它來返回Javascript對象。
          • 完整代碼如下:
          <style>
            .demo {
                width: 100px;
                height: 100px;
                background-color: gray;
                display: inline-block;
                margin: 10px;
            }
            .red {
                background-color: red;
            }
          
            .blue {
                background-color: blue;
            }
          </style>
          <script src="vue.js"></script>
          <div id="app">
            <div
                    class="demo"
                    @click="attachRed = !attachRed"
                    :class="divClasses"
          
            ></div>
          </div>
          <script>
            new Vue({
                el: "#app",
                data: {
                    attachRed: false
                },
                computed: {
                    divClasses: function () {
                        return {
                            red: this.attachRed,
                            blue: !this.attachRed
                        }
                    }
                }
            });
          </script>

          033-CSS類動態(tài)樣式-使用命名

          • 傳統(tǒng)的HTML語法里,一個元素套用某個CSS類是用class = "類名"
          • 而在Vue里面,則使用 :class = "表達式",這個表達式可以是:
          • 1、 直接使用 CSS類名
          • 2、 使用對象,類名:布爾值,如red: true或red: false
          • 3、 computed里return的對象 (如同上一個例子那樣)
          • 4、 數(shù)組(CSS列表),Vue會自動解析列表中的對象
          • 原理:
          • 默認情況下,本例中第二個div塊為green, 現(xiàn)在清空輸入框,div框變灰色,因為它找不到對應(yīng)的css值了。
          • 這個輸入框是這樣設(shè)置的 :class="[color,{red: attachRed}]",數(shù)組中有兩個值:
          • 第一個元素color表示直接用css的類名來設(shè)置css的值
          • 第二個元素{red: attachRed}能否成立,要看布爾值attachRed是否為true?
          • 兩個值其中任何一個成立,css設(shè)置生效,在初始時:
          • 數(shù)組元素1:{red: attachRed}不成立,因為attachRed為false
          • 數(shù)組元素2:color成立,因為默認為green, 所以div塊為綠色
          • 清空輸入框后,數(shù)組元素 color 不成立了,加之?dāng)?shù)組元素 {red: attachRed} 本來就不成立,因此div塊變成灰色
          • 此時,如果我們點一下第一個div塊,則第二個div塊也跟著變成了紅色,分析:
          • 現(xiàn)在盡管 color 還是不成立,但是 {red: attachRed} 成立了,于是第二個div塊跟著變成了紅色
          <!-- 033-CSS類動態(tài)樣式-使用命名 -->
          <style>
              .demo {
                  width: 100px;
                  height: 100px;
                  background-color: gray;
                  display: inline-block;
                  margin: 10px;
              }
              .red {
                  background-color: red;
              }
              .blue {
                  background-color: blue;
              }
              .green {
                  background-color: green;
              }
          </style>
          <script src="vue.js"></script>
          <div id="app">
              <div
                      class="demo"
                      @click="attachRed = !attachRed"
                      :class="divClasses"
          
              ></div>
              <div
                      class="demo"
                      :class="[color,{red: attachRed}]"
              ></div>
              <hr>
              <!-- 此處輸入框雙向綁定了color屬性,即鍵盤輸入的內(nèi)容會即時作為CSS類名傳到HTML中去 -->
              <input type = "text" v-model = "color">
          </div>
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      attachRed: false,
                      color:'green' 
                      //color屬性默認為green,請注意由于雙向綁定的原因,green這個字符串也會同時傳遞到輸入框里
                  },
                  computed: {
                      divClasses: function () {
                          return {
                              red: this.attachRed,
                              blue: !this.attachRed
                          }
                      }
                  }
              });
          </script>

          章節(jié)2課時34-動態(tài)設(shè)置樣式(不使用CSS類)

          • 本課學(xué)習(xí)不用css的class直接設(shè)置css格式
          • 上例中,我們要事先定義三個css背景顏色的class,然后用 :class 這樣的方式去套用,現(xiàn)在轉(zhuǎn)換一種方法:
          • 直接在div中設(shè)置css屬性,例如 background-color
          • 可以看到,本例中完全沒有設(shè)置red, blue, green這三個class, 在div塊中是這么處理的:
          <div class="demo" :style="{'background-color':color}"></div>
          • 現(xiàn)在我們用 :style 來直接設(shè)置 background-color,注意格式,必須用單引號括起來,因為中間有連字符,如果不用單引號,那就必須改成 backgroundColor 這樣的駝峰命名法,那么color是怎么來的呢?請看這一行:
          data: { color: 'grey', width: 100}
          • 以及這一行:
          <input type="text" v-model="color">
          • vue實例中,我們定義了一個color變量(默認grey),再用v-model雙向綁定在一個input輸入框中,于是我們在輸入框中輸入任何符合css規(guī)則的顏色字符串可以同步生效在div框中(區(qū)別于上一個例子,我們只能輸入red,green和blue),本例中我們可以輸入yellow, gold, black等等等等,只要符合css語法的顏色都能生效而無需事前定義相對應(yīng)的class
          • 本課中的第二個div塊演示的是用類似的方法設(shè)置多個屬性,比如同時設(shè)置colorwidth
          • 完整代碼中,我們通過計算方法設(shè)置一個myStyle對象,第二個div塊直接用:style="myStyle"來套用這個對象,以達到同時設(shè)置背景顏色和寬度的目的。
          • 完整代碼如下:
          <!-- 章節(jié)2課時34-動態(tài)設(shè)置樣式(不使用CSS類) -->
          <style>
              .demo {
                  width: 100px;
                  height: 100px;
                  background-color: gray;
                  display: inline-block;
                  margin: 10px;
              }
          </style>
          <script src="vue.js"></script>
          <div id="app">
              <div class="demo" :style="{'background-color':color}"></div>
              <div class="demo" :style="myStyle"></div>
              <hr>
              <input type="text" v-model="color">
              <input type="text" v-model="width">
          </div>
          <script>
              new Vue({
                  el: "#app",
                  data: {
                      color: 'grey',
                      width: 100
                  },
                  computed: {
                      myStyle: function () {
                          return {
                              backgroundColor: this.color,
                              width: this.width + 'px'
                          }
          
                      }
                  }
              });
          </script>
          • 章節(jié)2課時35-用數(shù)組語法設(shè)置元素樣式 有了上一節(jié)課的鋪墊,這一節(jié)課理解起來會顯得很容易。 本例中我們增加了第三個div塊,而這個div塊是采用數(shù)組語法來設(shè)置css樣式的,參見代碼: <div class="demo" :style="[myStyle,{height: width + 'px'}]"></div> 這個div塊的 :style 是由一個數(shù)組構(gòu)成的,有兩個元素: myStyle{height: width + 'px'} myStyle 跟上一個例子一模一樣,不再贅述,第二個元素表示追加一個css屬性: height 這個例子中,我們并沒有在vue實例的計算屬性中添加 height 這個屬性,但它依然生效,也就是說,在一個div塊中,我們套用css樣式的方法是多種多樣的,現(xiàn)在總結(jié)一下: 第一個div框中,直接采用css關(guān)鍵字 'background-color' 第二個div框中,采用計算方法( computed )return 一個css樣式(多屬性) 第三個div框中,采用數(shù)組的方式設(shè)置css,在這個框中,我們同時采用了計算方法和css關(guān)鍵字直接設(shè)置法,相當(dāng)于第一個div框和第二個div框的合體。 完整代碼如下:

          讀目錄

          • 利用表單實現(xiàn)文件上傳
          • 表單異步上傳(jquery.form插件)
          • 模擬表單數(shù)據(jù)上傳(FormData)
          • 分片上傳
          • 使用HTML5 拖拽、粘貼上傳
          • 上傳插件(WebUploader)
          • 總結(jié)

          作為程序員的我們,經(jīng)常要用到文件的上傳和下載功能。到了需要用的時候,各種查資料。有木有..有木有...。為了方便下次使用,這里來做個總結(jié)和備忘。

          利用表單實現(xiàn)文件上傳

          最原始、最簡單、最粗暴的文件上傳。
          前端代碼:

          //方式1
          <form action="/Home/SaveFile1" method="post" enctype="multipart/form-data">
               <input type="file" class="file1" name="file1" />
               <button type="submit" class="but1">上傳</button>
          </form>
          
          

          【注意】

          • 1、需要post提交
          • 2、enctype="multipart/form-data" (傳輸文件)
          • 3、需要提交的表單元素需要設(shè)置 name 屬性

          后臺代碼:

          public ActionResult SaveFile1()
          {
              if (Request.Files.Count > 0)
              {
                  Request.Files[0].SaveAs(Server.MapPath("~/App_Data/") + Request.Files[0].FileName);
                  return Content("保存成功");
              }
              return Content("沒有讀到文件");
          }
          

          表單異步上傳(jquery.form插件)

          雖然上面的方式簡單粗暴,但是不夠友好。頁面必然會刷新。難以實現(xiàn)停留在當(dāng)前頁面,并給出文件上傳成功的提示。
          隨著時間的流逝,技術(shù)日新月異。ajax的出現(xiàn),使得異步文件提交變得更加容易。
          下面我們利用jquery.form插件來實現(xiàn)文件的異步上傳。
          首先我們需要導(dǎo)入
          jquery.jsjquery.form.js
          前端代碼:

          <form id="form2" action="/Home/SaveFile2" method="post" enctype="multipart/form-data">
              <input type="file" class="file1" name="file1" />
              <button type="submit" class="but1">上傳1</button>
              <button type="button" class="but2">上傳2</button>
          </form>
          
          
          //方式2(通過ajaxForm綁定ajax操作)
          $(function () {
              $('#form2').ajaxForm({
                  success: function (responseText) {
                      alert(responseText);
                  }
              });
          });
          
          //方式3(通過ajaxSubmit直接執(zhí)行ajax操作)
          $(function () {
              $(".but2").click(function () {
                  $('#form2').ajaxSubmit({
                      success: function (responseText) {
                          alert(responseText);
                      }
                  });
              });
          });
          
          

          后臺代碼:

          public string SaveFile2()
          {
              if (Request.Files.Count > 0)
              {                
                  Request.Files[0].SaveAs(Server.MapPath("~/App_Data/") + Path.GetFileName(Request.Files[0].FileName));
                  return "保存成功";
              }
              return "沒有讀到文件";
          }
          

          原理:
          我們很多時候使用了插件,就不管其他三七二十一呢。
          如果有點好奇心,想想這個插件是怎么實現(xiàn)的。隨便看了看源碼一千五百多行。我的媽呀,不就是個異步上傳的嗎,怎么這么復(fù)雜。
          難以看出個什么鬼來,直接斷點調(diào)試下吧。


          原來插件內(nèi)部有iframe和FormData不同方式來上傳,來適應(yīng)更多版本瀏覽器。

          模擬表單數(shù)據(jù)上傳(FormData)

          iframe這東西太惡心。我們看到上面可以利用FormData來上傳文件,這個是Html 5 才有的。下面我們自己也來試試吧。
          前端代碼:

          <input id="fileinfo" type="file" class="notFormFile" />
          <button type="button" class="btnNotForm">上傳4</button>
          
          //方式4
          $(".btnNotForm").click(function () {
              var formData = new FormData();//初始化一個FormData對象
              formData.append("files", $(".notFormFile")[0].files[0]);//將文件塞入FormData
              $.ajax({
                  url: "/Home/SaveFile2",
                  type: "POST",
                  data: formData,
                  processData: false,  // 告訴jQuery不要去處理發(fā)送的數(shù)據(jù)
                  contentType: false,   // 告訴jQuery不要去設(shè)置Content-Type請求頭
                  success: function (responseText) {
                      alert(responseText);
                  }
              });
          });
          
          

          后面的代碼:(不變,還是上例代碼)

          public string SaveFile2()
          {
              if (Request.Files.Count > 0)
              {                
                  Request.Files[0].SaveAs(Server.MapPath("~/App_Data/") + Path.GetFileName(Request.Files[0].FileName));
                  return "保存成功";
              }
              return "沒有讀到文件";
          }
          

          我們看到,F(xiàn)ormData對象也只是在模擬一個原始的表單格式的數(shù)據(jù)。那有沒有可能利用表單或表單格式來上傳文件呢?答案是肯定的。(下面馬上揭曉)
          前端代碼:

          <input type="file"  id="file5" multiple>
          <button type="button" class="btnFile5">上傳5</button>    
          
          
          //方式5
          $(".btnFile5").click(function () {
              $.ajax({
                  url: "/Home/SaveFile4",
                  type: "POST",
                  data: $("#file5")[0].files[0],
                  processData: false,  // 告訴jQuery不要去處理發(fā)送的數(shù)據(jù)
                  contentType: false,   // 告訴jQuery不要去設(shè)置Content-Type請求頭
                  success: function (responseText) {
                      alert(responseText);
                  }
              });;       
          });       
               
          

          后臺代碼:

          public string SaveFile4()
          {
              //這里發(fā)現(xiàn)只能得到一個網(wǎng)絡(luò)流,沒有其他信息了。(比如,文件大小、文件格式、文件名等)
              Request.SaveAs(Server.MapPath("~/App_Data/SaveFile4.data") + "", false);
              return "保存成功";
          }
          

          細心的你發(fā)現(xiàn)沒有了表單格式,我們除了可以上傳文件流數(shù)據(jù)外,不能再告訴后臺其他信息了(如文件格式)。
          到這里,我似乎明白了以前上傳文件為什么非得要用個form包起來,原來這只是和后臺約定的一個傳輸格式而已。
          其實我們單純地用jq的ajax傳輸文本數(shù)據(jù)的時候,最后也是組裝成了form格式的數(shù)據(jù),如:

           $.ajax({
              data: { "userName": "張三" } 
          

          分片上傳

          在知道了上面的各種上傳之后,我們是不是就滿于現(xiàn)狀了呢?no,很多時候我們需要傳輸大文件,一般服務(wù)器都會有一定的大小限制。
          某天,你發(fā)現(xiàn)了一個激情小電影想要分享給大家。無奈,高清文件太大傳不了,怎么辦?我們可以化整為零,一部分一部分的傳嘛,也就是所謂的分片上傳。
          前端代碼:

          <input type="file" id="file6" multiple>
          <button type="button" class="btnFile6">分片上傳6</button>
          <div class="result"></div>
          
          
          //方式6
           $(".btnFile6").click(function () { 
               var upload = function (file, skip) {
                   var formData = new FormData();//初始化一個FormData對象
                   var blockSize = 1000000;//每塊的大小
                   var nextSize = Math.min((skip + 1) * blockSize, file.size);//讀取到結(jié)束位置             
                   var fileData = file.slice(skip * blockSize, nextSize);//截取 部分文件 塊
                   formData.append("file", fileData);//將 部分文件 塞入FormData
                   formData.append("fileName", file.name);//保存文件名字
                   $.ajax({
                       url: "/Home/SaveFile6",
                       type: "POST",
                       data: formData,
                       processData: false,  // 告訴jQuery不要去處理發(fā)送的數(shù)據(jù)
                       contentType: false,   // 告訴jQuery不要去設(shè)置Content-Type請求頭
                       success: function (responseText) {
                           $(".result").html("已經(jīng)上傳了" + (skip + 1) + "塊文件");
                           if (file.size <= nextSize) {//如果上傳完成,則跳出繼續(xù)上傳
                               alert("上傳完成");
                               return;
                           }
                           upload(file, ++skip);//遞歸調(diào)用
                       }
                   });
               };
          
               var file = $("#file6")[0].files[0];
               upload(file, 0);
           }); 
          
          

          后臺代碼:

          public string SaveFile6()
          {
              //保存文件到根目錄 App_Data + 獲取文件名稱和格式
              var filePath = Server.MapPath("~/App_Data/") + Request.Form["fileName"];
              //創(chuàng)建一個追加(FileMode.Append)方式的文件流
              using (FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write))
              {
                  using (BinaryWriter bw = new BinaryWriter(fs))
                  {
                      //讀取文件流
                      BinaryReader br = new BinaryReader(Request.Files[0].InputStream);
                      //將文件留轉(zhuǎn)成字節(jié)數(shù)組
                      byte[] bytes = br.ReadBytes((int)Request.Files[0].InputStream.Length);
                      //將字節(jié)數(shù)組追加到文件
                      bw.Write(bytes);
                  }
              }
              return "保存成功";
          }
          

          相對而言,代碼量多了一點,復(fù)雜了一點。不過相對于網(wǎng)上的其他分片上傳的代碼應(yīng)該要簡單得多(因為這里沒有考慮多文件塊同時上傳、斷點續(xù)傳。那樣就需要在后臺把文件塊排序,然后上傳完成按序合并,然后刪除原來的臨時文件。有興趣的同學(xué)可以自己試試,稍候在分析上傳插件webuploader的時候也會實現(xiàn))。
          效果圖:


          【說明】:如果我們想要上傳多個文件怎么辦?其實H5中也提供了非常簡單的方式。直接在
          input里面標記multiple<input type="file" id="file6" multiple>,然后我們后臺接收的也是一個數(shù)組Request.Files

          使用HTML5 拖拽、粘貼上傳

          只能說H5真是強大啊,權(quán)限越來越大,操作越來越牛逼。
          前端代碼(拖拽上傳):

          <textarea class="divFile7" style="min-width:800px;height:150px" placeholder="請將文件拖拽或直接粘貼到這里"></textarea>
          
          //方式7
           $(".divFile7")[0].ondrop = function (event) {
          
               event.preventDefault();//不要執(zhí)行與事件關(guān)聯(lián)的默認動作
               var files = event.dataTransfer.files;//獲取拖上來的文件
          
               //以下代碼不變
               var formData = new FormData();//初始化一個FormData對象
               formData.append("files", files[0]);//將文件塞入FormData
               $.ajax({
                   url: "/Home/SaveFile2",
                   type: "POST",
                   data: formData,
                   processData: false,  // 告訴jQuery不要去處理發(fā)送的數(shù)據(jù)
                   contentType: false,   // 告訴jQuery不要去設(shè)置Content-Type請求頭
                   success: function (responseText) {
                       alert(responseText);
                   }
               });
           };
          
          

          后臺代碼:
          略(和之前的SaveFile2一樣)

          前端代碼(粘貼上傳 限圖片格式):

          //方式8
          $(".divFile7")[0].onpaste = function (event) {
              event.preventDefault();//不要執(zhí)行與事件關(guān)聯(lián)的默認動作
              var clipboard = event.clipboardData.items[0];//剪貼板數(shù)據(jù)
              if (clipboard.kind == 'file' || clipboard.type.indexOf('image') > -1) {//判斷是圖片格式
                  var imageFile = clipboard.getAsFile();//獲取文件
          
                  //以下代碼不變
                  var formData = new FormData;
                  formData.append('files', imageFile);
                  formData.append('fileName', "temp.png");//這里給文件命個名(或者直接在后臺保存的時候命名)
                  $.ajax({
                      url: "/Home/SaveFile8",
                      type: "POST",
                      data: formData,
                      processData: false,  // 告訴jQuery不要去處理發(fā)送的數(shù)據(jù)
                      contentType: false,   // 告訴jQuery不要去設(shè)置Content-Type請求頭
                      success: function (responseText) {
                          alert(responseText);
                      }
                  });
              }
          };
          
          

          后臺代碼:

          public string SaveFile8()
          {
              //保存文件到根目錄 App_Data + 獲取文件名稱和格式
              var filePath = Server.MapPath("~/App_Data/") + Request.Form["fileName"];      
              if (Request.Files.Count > 0)
              {
                  Request.Files[0].SaveAs(filePath);
                  return "保存成功";
              }
              return "沒有讀到文件";
          }
          
          

          效果圖:

          上傳插件(WebUploader)

          已經(jīng)列舉分析了多種上傳文件的方式,我想總有一種適合你。不過,上傳這個功能比較通用,而我們自己寫的可能好多情況沒有考慮到。接下來簡單介紹下百度的WebUploader插件。
          比起我們自己寫的簡單上傳,它的優(yōu)勢:穩(wěn)定、兼容性好(有flash切換,所以支持IE)、功能多、并發(fā)上傳、斷點續(xù)傳(主要還是靠后臺配合)。
          官網(wǎng):http://fex.baidu.com/webuploader/
          插件下載:https://github.com/fex-team/webuploader/releases/download/0.1.5/webuploader-0.1.5.zip
          下面開始對WebUploader的使用
          第一種,簡單粗暴
          前端代碼:

          <div id="picker">選擇文件</div>
          <button id="ctlBtn" class="btn btn-default">開始上傳</button>
          
          
          <!--引用webuploader的js和css-->
          <link href="~/Scripts/webuploader-0.1.5/webuploader.css" rel="stylesheet" />
          <script src="~/Scripts/webuploader-0.1.5/webuploader.js"></script>
          <script type="text/javascript">
              var uploader = WebUploader.create({
          
                  // (如果是新瀏覽器 可以不用 flash)
                  //swf: '/Scripts/webuploader-0.1.5/Uploader.swf',
          
                  // 文件接收服務(wù)端。
                  server: '/Webuploader/SaveFile',
          
                  // 選擇文件的按鈕。可選。
                  // 內(nèi)部根據(jù)當(dāng)前運行是創(chuàng)建,可能是input元素,也可能是flash.
                  pick: '#picker'
              });
          
              $("#ctlBtn").click(function () {
                  uploader.upload();
              });
          
              uploader.on('uploadSuccess', function (file) {
                  alert("上傳成功");
              });
          
          </script>
          
          

          后臺代碼:

          public string SaveFile()
          {
              if (Request.Files.Count > 0)
              {
                  Request.Files[0].SaveAs(Server.MapPath("~/App_Data/") + Path.GetFileName(Request.Files[0].FileName));
                  return "保存成功";
              }
              return "沒有讀到文件";
          }
          

          第二種,分片上傳。和我們之前自己寫的效果差不多。
          前端代碼:

          var uploader = WebUploader.create({ 
              //兼容老版本IE
              swf: '/Scripts/webuploader-0.1.5/Uploader.swf', 
              // 文件接收服務(wù)端。
              server: '/Webuploader/SveFile2', 
              // 開起分片上傳。
              chunked: true, 
              //分片大小
              chunkSize: 1000000, 
              //上傳并發(fā)數(shù)
              threads: 1,
              // 選擇文件的按鈕。 
              pick: '#picker'
          });
          
          // 點擊觸發(fā)上傳
          $("#ctlBtn").click(function () {
              uploader.upload();
          });
          
          uploader.on('uploadSuccess', function (file) {
              alert("上傳成功");
          });
          
          

          后臺代碼:

          public string SveFile2()
          {
              //保存文件到根目錄 App_Data + 獲取文件名稱和格式
              var filePath = Server.MapPath("~/App_Data/") + Path.GetFileName(Request.Files[0].FileName);
              //創(chuàng)建一個追加(FileMode.Append)方式的文件流
              using (FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write))
              {
                  using (BinaryWriter bw = new BinaryWriter(fs))
                  {
                      //讀取文件流
                      BinaryReader br = new BinaryReader(Request.Files[0].InputStream);
                      //將文件留轉(zhuǎn)成字節(jié)數(shù)組
                      byte[] bytes = br.ReadBytes((int)Request.Files[0].InputStream.Length);
                      //將字節(jié)數(shù)組追加到文件
                      bw.Write(bytes);
                  }
              }
              return "保存成功";
          }
          

          我們看到了有個參數(shù)threads: 1上傳并發(fā)數(shù),如果我們改成大于1會怎樣?前端會同時發(fā)起多個文件片上傳。后臺就會報錯,多個進程同時操作一個文件。
          那如果我們想要多線程上傳怎么辦?改代碼吧(主要是后臺邏輯)。
          前端代碼:

          //并發(fā)上傳(多線程上傳)
          var uploader = WebUploader.create({
              //兼容老版本IE
              swf: '/Scripts/webuploader-0.1.5/Uploader.swf',
              // 文件接收服務(wù)端。
              server: '/Webuploader/SveFile3',
              // 開起分片上傳。
              chunked: true,
              //分片大小
              chunkSize: 1000000,
              //上傳并發(fā)數(shù)
              threads: 10,
              // 選擇文件的按鈕。
              pick: '#picker'
          });
          
          // 點擊觸發(fā)上傳
          $("#ctlBtn").click(function () {
              uploader.upload();
          });
          
          uploader.on('uploadSuccess', function (file) {
              //上傳完成后,給后臺發(fā)送一個合并文件的命令
              $.ajax({
                  url: "/Webuploader/FileMerge",
                  data: { "fileName": file.name },
                  type: "post",
                  success: function () {
                      alert("上傳成功");
                  }
              });
          });
          
          

          后臺代碼:

          public string SveFile3()
          {
              var chunk = Request.Form["chunk"];//當(dāng)前是第多少片 
          
              var path = Server.MapPath("~/App_Data/") + Path.GetFileNameWithoutExtension(Request.Files
              if (!Directory.Exists(path))//判斷是否存在此路徑,如果不存在則創(chuàng)建
              {
                  Directory.CreateDirectory(path);
              }
              //保存文件到根目錄 App_Data + 獲取文件名稱和格式
              var filePath = path + "/" + chunk;
              //創(chuàng)建一個追加(FileMode.Append)方式的文件流
              using (FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write))
              {
                  using (BinaryWriter bw = new BinaryWriter(fs))
                  {
                      //讀取文件流
                      BinaryReader br = new BinaryReader(Request.Files[0].InputStream);
                      //將文件留轉(zhuǎn)成字節(jié)數(shù)組
                      byte[] bytes = br.ReadBytes((int)Request.Files[0].InputStream.Length);
                      //將字節(jié)數(shù)組追加到文件
                      bw.Write(bytes);
                  }
              }           
              return "保存成功";
          }
          
          /// <summary>
          /// 合并文件
          /// </summary>
          /// <param name="path"></param>
          /// <returns></returns>
          public bool FileMerge()
          {
              var fileName = Request.Form["fileName"];
              var path = Server.MapPath("~/App_Data/") + Path.GetFileNameWithoutExtension(fileName);
          
              //這里排序一定要正確,轉(zhuǎn)成數(shù)字后排序(字符串會按1 10 11排序,默認10比2小)
              foreach (var filePath in Directory.GetFiles(path).OrderBy(t => int.Parse(Path.GetFileNameWithoutExtension(t))))
              {
                  using (FileStream fs = new FileStream(Server.MapPath("~/App_Data/") + fileName, FileMode.Append, FileAccess.Write))
                  {
                      byte[] bytes = System.IO.File.ReadAllBytes(filePath);//讀取文件到字節(jié)數(shù)組
                      fs.Write(bytes, 0, bytes.Length);//寫入文件
                  }
                  System.IO.File.Delete(filePath);
              }
              Directory.Delete(path);
              return true;
          }
          

          到這里你以為就結(jié)束了嗎?錯,還有好多情況沒有考慮到。如果多個用戶上傳的文件名字一樣會怎樣?如何實現(xiàn)斷點續(xù)傳?還沒實現(xiàn)選擇多個文件?不過,這里不打算繼續(xù)貼代碼了(再貼下去,代碼量越來越多了),自己也來練習(xí)練習(xí)吧。
          提供一個思路,上傳前先往數(shù)據(jù)庫插入一條數(shù)據(jù)。數(shù)據(jù)包含文件要存的路徑、文件名(用GUID命名,防止同名文件沖突)、文件MD5(用來識別下次續(xù)傳和秒傳)、臨時文件塊存放路徑、文件是否完整上傳成功等信息。
          然后如果我們斷網(wǎng)后再傳,首先獲取文件MD5值,看數(shù)據(jù)庫里面有沒上傳完成的文件,如果有就實現(xiàn)秒傳。如果沒有,看是不是有上傳了部分的。如果有接著傳,如果沒有則重新傳一個新的文件。

          總結(jié)

          之前我一直很疑惑,為什么上傳文件一定要用form包起來,現(xiàn)在算是大概明白了。
          最開始在javascript還不流行時,我們就可以直接使用submit按鈕提交表單數(shù)據(jù)了。表單里面可以包含文字和文件。然后隨著js和ajax的流行,可以利用ajax直接異步提交部分表單數(shù)據(jù)。這里開始我就糾結(jié)了,為什么ajax可以提交自己組裝的數(shù)據(jù)。那為什么不能直接提交文件呢。這里我錯了,ajax提交的并不是隨意的數(shù)據(jù),最后還是組裝成了表單格式(因為后臺技術(shù)對表單格式數(shù)據(jù)的支持比較普及)。但是現(xiàn)有的技術(shù)還不能通過js組裝一個文件格式的表單數(shù)據(jù)。直到H5中的FormData出現(xiàn),讓前端js組裝一個包含文件的表單格式數(shù)據(jù)成為了可能。所以說表單只是為了滿足和后臺“約定”的數(shù)據(jù)格式而已。

          相關(guān)推薦

          • http://www.cnblogs.com/fish-li/archive/2011/07/17/2108884.html
          • http://javascript.ruanyifeng.com/htmlapi/file.html

          demo

          • https://github.com/zhaopeiym/BlogDemoCode/tree/master/上傳下載

          主站蜘蛛池模板: 国产日韩一区二区三免费高清| 亚洲国产一区二区三区| 福利片福利一区二区三区| 人妻无码一区二区三区| 97久久精品一区二区三区| 日韩成人无码一区二区三区| 亚洲字幕AV一区二区三区四区| 国产精品一区电影| 搡老熟女老女人一区二区| 精品91一区二区三区| 精品三级AV无码一区| 亚洲第一区精品日韩在线播放| 99久久无码一区人妻a黑| 本免费AV无码专区一区| 日韩伦理一区二区| 亚欧色一区W666天堂| 自慰无码一区二区三区| 人妻无码一区二区三区| 精品视频在线观看一区二区三区| 日韩内射美女人妻一区二区三区| 立川理惠在线播放一区| 日本精品一区二区在线播放| 99久久人妻精品免费一区| 国产福利视频一区二区 | 亚洲中文字幕一区精品自拍| 免费无码一区二区三区蜜桃| 日本一区中文字幕日本一二三区视频 | 精品视频一区二区观看| 中文字幕日韩欧美一区二区三区 | 亚洲午夜精品第一区二区8050| 国产福利在线观看一区二区| 免费无码一区二区三区蜜桃大| 国产一区二区影院| 四虎一区二区成人免费影院网址 | 色偷偷一区二区无码视频| 夜精品a一区二区三区| 日本一区二区三区不卡视频| 麻豆AV天堂一区二区香蕉| 国产伦精品一区二区三区免费迷| 国产伦精品一区二区三区在线观看| 全国精品一区二区在线观看|