仿163網盤無刷新文件批量上傳系統

這個仿163網盤無刷新文件上傳系統,並沒有用使用.net的控件,完全的手工製作。前臺基本上是靜態的,跟後臺沒有關係,所以後臺用什麼語言做都可以(後面有各個版本的實例下載)。
本來覺得這個系統會很複雜,但把每個部分都分析清楚後,其實需要的技術並不高。不過當我把各個功能函數都整理好準備進行封裝時,卻發現要把程序封裝不是那 麼容易,因爲程序跟html的耦合度太高。然後我逐步把程序中操作html相關的部分分離出來,首先把簡單的分離,接着是文件列表,然後是file控件, 最後是一些提示性程序。經過幾次嘗試才把整個結構封裝好,現在程序結構應該算比較清晰,有什麼不明白的地方歡迎留言。


效果預覽

這裏的預覽只是前臺的效果,要整個系統測試請下載完整實例。


程序說明

【無刷新上傳

要實現文件上傳,form必須設置幾個屬性:
1.action:設爲要處理數據的頁面地址;
2.method:設爲"post";
3.enctype/encoding:必須設爲"multipart/form-data",這裏要注意的是在ie中用js修改form的enctype屬性是沒有效果的,只能修改encoding;

後面兩個屬性程序初始化時都有設置:

this .Form.method  =   " post " ;
this .Form.encoding  =   " multipart/form-data " ;


要注意這裏的無刷新不是ajax哦,而是利用“古老”的iframe。
由於ajax提交數據必須先獲取數據,而js(一般情況下)是不能操作客戶端文件,要獲取文件數據就更不用說了,所以只能用iframe來做。
先說說iframe實現無刷新上傳的原理:利用form的target屬性,把數據提交到頁面中一個(通常爲隱藏的)iframe上 直觀點說就是把“刷新”留給iframe。
其實原理跟一般用iframe實現無刷新提交表單是一樣的,只是這裏換成是文件。
這裏關鍵就是把form的target設爲iframe的name:

this .Form.target  =   this ._FrameName;


【iframe

如果沒有自定義iframe,程序在初始化時會自動創建無刷新所需的iframe的。
首先必須選擇一個iframe名,這在無刷新時是必須的,爲了每個實例能創建各自的iframe,這裏用了一個隨機數:

this ._FrameName  =   " uploadFrame_ "   +  Math.floor(Math.random()  *   1000 );


也可以用一個遞增的計算器來代替隨機數。

接着創建iframe,本以爲用document.createElement("iframe")創建再設置它的name屬性就行了。
卻發現這樣設置的name在ie居然不認(有說name是隻讀屬性),還好在網上找到一個方法:“IE 創建元素,還有一個特點,就是可以連同屬性一同創建”。
例如我想給動態創建的iframe設置name,可以這樣:

document.createElement( " <iframe name=/ ""  + this._FrameName +  " / " > " )

不過這個方式在ff會報錯:
uncaught exception: String contains an invalid character (NS_ERROR_DOM_INVALID_CHARACTER_ERR)
估計是用createElement時不能帶name,標準應該也是這樣,所以兼容的方式這樣寫:


var  oFrame  =  isIE  ?  document.createElement( " <iframe name=/ ""  + this._FrameName +  " / " > " ) : document.createElement( " iframe " );
// 爲ff設置name
oFrame.name  =   this ._FrameName;
oFrame.style.display 
=   " none " ;

關於這方面更詳細的內容請看這裏

創建完還需要插入到body中,一般的做法是使用document.body.appendChild,但在ie中會有“已終止操作”錯誤,可以用下面這段代碼測試:


< body >
< div >
< script >
document.body.appendChild(document.createElement(
" div " ));
< / script>
< / div>
< / body>

網上找到一個解析:“原來FF下的實現機制是當頁面還沒有完全讀取完時body元素就已經存在了,而IE只有頁面完全讀取結束body元素纔會存在,所以在頁面中插入上面這條語句在IE下就會出現錯誤”。
我在web開發未解之謎 中也提到了這個現象,我這裏使用了insertBefore代替:

document.body.insertBefore(oFrame, document.body.childNodes[ 0 ]);

在服務器端文件傳送完(或失敗)之後,怎麼通知客戶端呢?
這裏說說我的方法,首先我在客戶端定義一個函數:

function  Finish(msg){ alert(msg); location.href  =  location.href; }

很簡單,就是顯示提示並重新加載頁面(如果使用reload會導致ff中iframe重複加載數據)。
那服務器端如何通知客戶端的問題,就是iframe如何跟主頁面交互。
答案是通過window.parent或window.top,在iframe中parent和top屬性“分別返回立即父窗口和最上層的祖先窗口”。
例如我在服務器端處理完數據之後會輸出:

context.Response.Write( " <script>window.parent.Finish(' "   +  _msg  +   " ');</script> " );

就會執行主頁面的Finish函數了。


【多文件上傳】

對於多文件上傳,這裏的目的是如何做到163網盤那樣,只用一個file控件就實現多文件上傳。
這裏參考了163網盤的思路,下面說說如何實現:
首先必須有一個文件空間(我自己定的名字),例如程序中的"idFile"對象,這個空間不需要內容甚至一個div就可以,主要是用來存放file控件,程序中Folder屬性就是這個文件空間對象。
ps:這裏的要求是把file控件都控制在文件空間裏,即使不是單file控件的情況。
再說說Files屬性,這個屬性放的是file控件集合,方便獲取file控件,在下面“文件列表”就會用到。

處理這些file控件的程序主要在Ini函數中:
首先是處理文件空間中的file控件:


this .Files  =  [];
// 整理文件空間,把有值的file放入文件集合
Each( this .Folder.getElementsByTagName( " input " ), Bind( this function (o){
    
if (o.type  ==   " file " ){ o.value  &&   this .Files.push(o);  this .onIniFile(o); }
}))

可以看到這裏主要是把file控件放入到Files中,並執行附加函數onIniFile,我是這樣定義這個函數的:

onIniFile:  function (file){ file.value  ?  file.style.display  =   " none "  :  this .Folder.removeChild(file); }

這裏爲了實現單file控件,把原來有值的file都隱藏了,還有那個“單file控件”呢?
別急,接着就在文件空間插入一個新的file控件:


var  file  =  document.createElement( " input " );
file.name 
=   this .FileName; file.type  =   " file " ; file.onchange  =  Bind( this function (){  this .Check(file);  this .Ini(); });
this .Folder.appendChild(file);

可以看到file控件的name是FileName屬性的值,默認是空的,如果服務器端需要這個name的話就可以設置。
這裏可以看到每個file控件都有onchange來執行檢測函數Check,這樣每次選擇文件後都會用Check檢測一次,這裏說說這個Check函數:


// 檢測變量
var  bCheck  =   true ;
// 進行空值、文件數、後綴名、同值檢測
if ( ! file.value){
    bCheck 
=   false this .onEmpty();
else   if ( this .Limit  &&   this .Files.length  >=   this .Limit){
    bCheck 
=   false this .onLimite();
else   if ( !! this .ExtIn.length  &&   ! RegExp( " /.( "   +   this .ExtIn.join( " | " +   " )$ " " i " ).test(file.value)){
    
// 檢測是否允許後綴名
    bCheck  =   false this .onNotExtIn();
else   if ( !! this .ExtOut.length  &&  RegExp( " /.( "   +   this .ExtOut.join( " | " +   " )$ " " i " ).test(file.value)) {
    
// 檢測是否禁止後綴名
    bCheck  =   false this .onExtOut();
else   if ( !! this .Distinct) {
    Each(
this .Files,  function (o){  if (o.value  ==  file.value){ bCheck  =   false ; } })
    
if ( ! bCheck){  this .onSame(); }
}

裏面有一個檢測變量bCheck,然後進行空值、文件數限制、後綴名、相同文件的檢測,當其中一個步驟不通過bCheck就會設爲false,一個常用的檢測結構。
這裏說說檢測後綴名,由於js不能像後臺那樣獲取文件的文件類型,所以只能根據後綴名來判斷,例如用正則判斷:

/ /.(jpg|gif)$ / i.test(file.value)

這樣判斷顯然是不夠的,所以如果要做文件類型判斷的話一定要在後臺用ContentType再判斷一次。
最後如果沒有通過檢測就會執行onFail函數:

! bCheck  &&   this .onFail(file);

我在onFail函數中設定了移除沒有通過檢測的file控件:

onFail:  function (file){  this .Folder.removeChild(file); }


這樣就基本實現(正確的說是模擬)了單file控件上傳多個文件的效果了。


【文件列表】

在上面的Ini函數中,最後執行了一個附加函數onIni,這個函數是用戶自己定義的,我就在這個函數中添加文件列表。

在之前先說說添加文件列表的函數AddList,這個函數是用來把file控件的值列在一個table裏面。
函數的參數是一個二維數組,其中第一維是行(tr),第二維是列(td)。
首先獲取列表對象FileList,再定義一個文檔碎片oFragment來操作dom:

var  FileList  =  $( " idFileList " ), oFragment  =  document.createDocumentFragment();

然後用兩個Each把二維數組插入到文檔碎片中:


Each(rows,  function (cells){
    
var  row  =  document.createElement( " tr " );
    Each(cells, 
function (o){
        
var  cell  =  document.createElement( " td " );
        
if ( typeof  o  ==   " string " ){ cell.innerHTML  =  o; } else { cell.appendChild(o); }
        row.appendChild(cell);
    });
    oFragment.appendChild(row);
})

其中用了一個判斷if(typeof o == "string"),如果是文本就直接用innerHTML插入td,如果不是文本(這裏不是文本就是一個對象)就用appendChild插入到td。
當數據都插入到文檔碎片,就準備把文檔碎片插入到FileList中,不過還有一個步驟就是清空FileList中原有的數據。
本來把innerHTML設爲空來清空FileList會更有效率,但ie的table中只有td支持innerHTML,所以只好用removeChild來清空:

while (FileList.hasChildNodes()){ FileList.removeChild(FileList.firstChild); }

之後就可以把文檔碎片插入了:

FileList.appendChild(oFragment);


繼續看onIni函數,現在只需要把要顯示的數據組成一個二維數組,再用AddList就能顯示文件列表了,這時存放file控件集合的Files屬性就大有用處了。
首先定義一個放顯示數據的數組:

var  arrRows  =  [];

然後根據Files對這個數組賦值:


if ( this .Files.length){
    
var  oThis  =   this ;
    Each(
this .Files,  function (o){
        
var  a  =  document.createElement( " a " ); a.innerHTML  =   " 取消 " ; a.href  =   " javascript:void(0); " ;
        a.onclick 
=   function (){ oThis.Delete(o);  return   false ; };
        arrRows.push([o.value, a]);
    });
else  { arrRows.push([ " <font color='gray'>沒有添加文件</font> " " &nbsp; " ]); }
AddRow(arrRows);

當Files沒有控件時只是輸出“沒有添加文件”,有控件時就會把每個file控件的要顯示數據放到一個數組中,可以看到這個數組其實就是td內容的集合,接着把這個數組加入到arrRows中形成二維數組,最後把得到的arrRows給AddRow函數顯示數據就行了。
爲了能取消指定的file控件,這裏插入了一個a來觸發刪除函數Delete,這裏也有一個技巧,這裏把href設爲"javascript:void(0);",並在onclick中返回false,這樣能最大程度的實現僅僅執行js而不去跳轉。

在表單提交時也要重新顯示文件列表,表單提交後就不允許刪除文件了,只顯示文件路徑就行了:


$( " idBtnupload " ).onclick  =   function (){
    
// 顯示文件列表
     var  arrRows  =  [];
    Each(fu.Files, 
function (o){ arrRows.push([o.value,  " &nbsp; " ]); });
    AddList(arrRows);
    
    fu.Folder.style.display 
=   " none " ;
    $(
" idProcess " ).style.display  =   "" ;
    $(
" idMsg " ).innerHTML  =   " 正 在添加文件到您的網盤中,請稍候……<br />有可能因爲網絡問題,出現程序長時間無響應,請點擊“<a href='?'& gt;<font color='red'>取消</font></a>”重新上傳文件 " ;
    
    fu.Form.submit();
}

說到表單提交要注意一個問題,就是表單是不能嵌套的,最好是把表單放到服務器表單之外,沒有辦法才使用服務器表單作爲提交表單(由於程序會修改提交表單的屬性,所以儘量不要這樣使用)。

這樣文件列表就完成了,有興趣的話也可以自己封裝一下這個功能。


【file樣式】

到此,程序的功能都已經實現了,但在163網盤中還有一個特別的地方,就是file控件的樣式。
如果有用過163網盤上傳文件,就知道那個file控件就像一個按鈕,但功能確實是一個file控件。
但當自己嘗試修改file控件的樣式時,發現單單設置file控件的樣式並不能實現想要的效果。
於是我想了另一個辦法,用一個button來模擬,結果發現也不行,用js根本操作不了file控件,應該是考慮到安全問題吧。
最後是參考了163網盤和muxrwc模擬126附件添加的效果 ,總結了這個方法:
1.指定用一個容器(例如程序中的idFile)。
容器最好指定高和寬,並且overflow爲hidden,不是塊級元素的最好設display爲block(爲了高和寬的正確呈現);
2.在容器裏放一個file控件,並設置樣式,使能觸發彈出選擇文件框的部分覆蓋整個容器,並設置成全透明。
容器指定準確的高和寬就是爲了能通過file控件中不多的能設置的樣式來覆蓋整個容器;
3.現在已經把容器模擬成file控件了,可以直接設置容器的樣式來模擬設置file控件的樣式了。

在程序中主要用file控件的margin-left和font-size來實現覆蓋整個容器:


a.files input  {
    margin-left
: -350px ;
    font-size
: 30px ;
    cursor
: pointer ;
    filter
: alpha(opacity=0) ;
    opacity
: 0 ;
}

至於容器,我使用了有僞類hover的a元素(雖然CSS2中hover可以應用於任何對象,但ie6不支持)。
這裏用了一個常用的小技巧,就是用一張圖片作爲背景通過在hover時修改background-position來實現兩張圖片的效果:


a.files  {
    width
: 90px ;
    height
: 30px ;
    overflow
: hidden ;
    display
: block ;
    border
: 1px solid #BEBEBE ;
    background
: url(img/fu_btn.gif) left top no-repeat ;
    text-decoration
: none ;
}
a.files:hover 
{
    background-color
: #FFFFEE ;
    background-position
: 0 -30px ;
}

在點擊這個a時後會出現一個虛線框,在這裏顯然不太美觀,可以把outline設爲none來去掉,可是ie又不支持,在網上找到一個方法ie可以 把hideFocus設爲true來隱藏聚焦(即不顯示這個虛線框,hideFocus可以在js或html中設置,也可以通過expression放到 css中:


a.files, a.files input  {
    outline
: none ; /* ff* /
    hide-focus:expression(this.hideFocus=true);/*ie* /
}


這樣完全模擬了163網盤的效果了。


【後臺】

前臺基本完成了,就到後臺啦。後臺的功能很簡單,就是處理傳遞過來的文件數據。
這裏像js + .Net 圖片切割系統 那樣使用ashx文件處理IHttpHandler發送過來的數據。
程序很簡單,就直接貼代碼了:


int  iTotal  =  context.Request.Files.Count;

if  (iTotal  ==   0 )
{
    _msg 
=   " 沒有數據 " ;
}
else
{
    
int  iCount  =   0 ;

    
for  ( int  i  =   0 ; i  <  iTotal; i ++ )
    {
        HttpPostedFile file 
=  context.Request.Files[i];

        
if  (file.ContentLength  >   0   ||   ! string .IsNullOrEmpty(file.FileName))
        {
            
// 保存文件
            file.SaveAs(System.Web.HttpContext.Current.Server.MapPath( " ./file/ "   +  Path.GetFileName(file.FileName)));

            
// 這裏可以根據實際設置其他限制
             if  ( ++ iCount  >  UploadFileLimit)
            {
                _msg 
=   " 超過上傳限制: "   +  UploadFileLimit;
                
break ;
            }
        }
    }
}

這裏只檢測了有無文件和文件數限制,其他檢測如文件大小等可以自己擴展,應該不難。
處理完數據之後就通知客戶端:

context.Response.Write( " <script>window.parent.Finish(' "   +  _msg  +   " ');</script> " );

這個在上面iframe的內容中已經說明了。


使用說明

基本使用很簡單,實例化一個file對象,其中參數分別是form對象,文件空間對象:

new  FileUpload( " uploadForm " " idFile " )

這樣就實現了一個簡單的無刷新上傳文件表單。

還可以使用這幾個屬性:
Form//表單
Folder//文件控件存放空間
Files//文件集合

更多的功能可以選擇設置這些屬性:
屬性名:默認值//說明
FileName:"",//文件上傳控件的name,配合後臺使用
FrameName:"",//iframe的name,要自定義iframe的話這裏設置name
onIniFile:function(){},//整理文件時執行(其中參數是file對象)
onEmpty:function(){},//文件空值時執行
Limit:0,//文件數限制,0爲不限制
onLimite:function(){},//超過文件數限制時執行
Distinct:true,//是否不允許相同文件
onSame:function(){},//有相同文件時執行
ExtIn:[],//允許後綴名
onNotExtIn:function(){},//不是允許後綴名時執行
ExtOut:[],//禁止後綴名,當設置了ExtIn則ExtOut無效
onExtOut:function(){},//是禁止後綴名時執行
onFail:function(){},//文件不通過檢測時執行(其中參數是file對象)
onIni:function(){}//重置時執行

使用方法可以參考實例。

程序中提供了下面幾個方法:
Ini 整理空間
Check 檢測file對象
Delete 刪除指定file
Clear 刪除全部file


程序代碼


var  isIE  =  (document.all)  ?   true  :  false ;

var  $  =   function  (id) {
    
return   " string "   ==   typeof  id  ?  document.getElementById(id) : id;
};

var  Class  =  {
  create: 
function () {
    
return   function () {
      
this .initialize.apply( this , arguments);
    }
  }
}

var  Extend  =   function (destination, source) {
    
for  ( var  property  in  source) {
        destination[property] 
=  source[property];
    }
}

var  Bind  =   function (object, fun) {
    
return   function () {
        
return  fun.apply(object, arguments);
    }
}

var  Each  =   function (list, fun){
    
for  ( var  i  =   0 , len  =  list.length; i  <  len; i ++ ) { fun(list[i], i); }
};

//
var  FileUpload  =  Class.create();
FileUpload.prototype 
=  {
  
// 表單對象,文件控件存放空間
  initialize:  function (form, folder, options) {
    
    
this .Form  =  $(form); // 表單
     this .Folder  =  $(folder); // 文件控件存放空間
     this .Files  =  []; // 文件集合
    
    
this .SetOptions(options);
    
    
this .FileName  =   this .options.FileName;
    
this ._FrameName  =   this .options.FrameName;
    
this .Limit  =   this .options.Limit;
    
this .Distinct  =   !! this .options.Distinct;
    
this .ExtIn  =   this .options.ExtIn;
    
this .ExtOut  =   this .options.ExtOut;
    
    
this .onIniFile  =   this .options.onIniFile;
    
this .onEmpty  =   this .options.onEmpty;
    
this .onNotExtIn  =   this .options.onNotExtIn;
    
this .onExtOut  =   this .options.onExtOut;
    
this .onLimite  =   this .options.onLimite;
    
this .onSame  =   this .options.onSame;
    
this .onFail  =   this .options.onFail;
    
this .onIni  =   this .options.onIni;
    
    
if ( ! this ._FrameName){
        
// 爲每個實例創建不同的iframe
         this ._FrameName  =   " uploadFrame_ "   +  Math.floor(Math.random()  *   1000 );
        
// ie不能修改iframe的name
         var  oFrame  =  isIE  ?  document.createElement( " <iframe name=/ ""  + this._FrameName +  " / " > " ) : document.createElement( " iframe " );
        
// 爲ff設置name
        oFrame.name  =   this ._FrameName;
        oFrame.style.display 
=   " none " ;
        
// 在ie文檔未加載完用appendChild會報錯
        document.body.insertBefore(oFrame, document.body.childNodes[ 0 ]);
    }
    
    
// 設置form屬性,關鍵是target要指向iframe
     this .Form.target  =   this ._FrameName;
    
this .Form.method  =   " post " ;
    
// 注意ie的form沒有enctype屬性,要用encoding
     this .Form.encoding  =   " multipart/form-data " ;

    
// 整理一次
     this .Ini();
  },
  
// 設置默認屬性
  SetOptions:  function (options) {
    
this .options  =  { // 默認值
        FileName:     "" , // 文件上傳控件的name,配合後臺使用
        FrameName:     "" , // iframe的name,要自定義iframe的話這裏設置name
        onIniFile:     function (){}, // 整理文件時執行(其中參數是file對象)
        onEmpty:     function (){}, // 文件空值時執行
        Limit:         0 , // 文件數限制,0爲不限制
        onLimite:     function (){}, // 超過文件數限制時執行
        Distinct:     true , // 是否不允許相同文件
        onSame:         function (){}, // 有相同文件時執行
        ExtIn:        [], // 允許後綴名
        onNotExtIn:     function (){}, // 不是允許後綴名時執行
        ExtOut:        [], // 禁止後綴名,當設置了ExtIn則ExtOut無效
        onExtOut:     function (){}, // 是禁止後綴名時執行
        onFail:         function (){}, // 文件不通過檢測時執行(其中參數是file對象)
        onIni:         function (){} // 重置時執行
    };
    Extend(
this .options, options  ||  {});
  },
  
// 整理空間
  Ini:  function () {
    
// 整理文件集合
     this .Files  =  [];
    
// 整理文件空間,把有值的file放入文件集合
    Each( this .Folder.getElementsByTagName( " input " ), Bind( this function (o){
        
if (o.type  ==   " file " ){ o.value  &&   this .Files.push(o);  this .onIniFile(o); }
    }))
    
// 插入一個新的file
     var  file  =  document.createElement( " input " );
    file.name 
=   this .FileName; file.type  =   " file " ; file.onchange  =  Bind( this function (){  this .Check(file);  this .Ini(); });
    
this .Folder.appendChild(file);
    
// 執行附加程序
     this .onIni();
  },
  
// 檢測file對象
  Check:  function (file) {
    
// 檢測變量
     var  bCheck  =   true ;
    
// 空值、文件數限制、後綴名、相同文件檢測
     if ( ! file.value){
        bCheck 
=   false this .onEmpty();
    } 
else   if ( this .Limit  &&   this .Files.length  >=   this .Limit){
        bCheck 
=   false this .onLimite();
    } 
else   if ( !! this .ExtIn.length  &&   ! RegExp( " /.( "   +   this .ExtIn.join( " | " +   " )$ " " i " ).test(file.value)){
        
// 檢測是否允許後綴名
        bCheck  =   false this .onNotExtIn();
    } 
else   if ( !! this .ExtOut.length  &&  RegExp( " /.( "   +   this .ExtOut.join( " | " +   " )$ " " i " ).test(file.value)) {
        
// 檢測是否禁止後綴名
        bCheck  =   false this .onExtOut();
    } 
else   if ( !! this .Distinct) {
        Each(
this .Files,  function (o){  if (o.value  ==  file.value){ bCheck  =   false ; } })
        
if ( ! bCheck){  this .onSame(); }
    }
    
// 沒有通過檢測
     ! bCheck  &&   this .onFail(file);
  },
  
// 刪除指定file
  Delete:  function (file) {
    
// 移除指定file
     this .Folder.removeChild(file);  this .Ini();
  },
  
// 刪除全部file
  Clear:  function () {
    
// 清空文件空間
    Each( this .Files, Bind( this function (o){  this .Folder.removeChild(o); }));  this .Ini();
  }
}


【asp版本補充】

由於很多人問我asp版本的後臺該如何寫,所以決定寫一個給大家。
這裏我用了化境HTTP上傳程序2.1版(應該是最新版了)的無組件上傳類,但用的時候發現幾個問題(不知是我不會用還是asp本身的問題):
1,當file控件的name是空時,後臺會找不到文件;
2,文件名比較短時(例如我用"f"),後臺也找不到文件;
3,當有多個file控件,如果使用相同的name,後臺只會保存一個文件;
4,我在上傳文件後輸出的中文是亂碼(有時又正常)。

針對前3條,我加了一個RanName屬性,設爲true的話會自動生成隨機的file控件名,對於第4條,我發現如果字是直接寫在文檔上就不會亂碼,所以我這裏把輸出的文字都直接寫在文檔上沒有用變量。如果有兄弟知道怎麼解決這些問題記得告訴我哦。


下載完整測試代碼(.net)

下載完整測試代碼(asp)

感謝由csdn網友mengshan1986提供的php和jsp版,klniuer的php修正版:
下載完整測試代碼(php)
下載完整測試代碼(jsp)

ps:請注意程序中的文件保存路徑,很多人的錯誤都是沒有設置好文件保存路徑。

其他上傳系統:

簡便無刷新文件上傳系統

轉載請註明出處:http://www.cnblogs.com/cloudgamer/

程序中包含的js工具庫CJL.0.1.min.js,原文在這裏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章