用這個庫 3 分鐘實現讓你滿意的表格功能:Bootstrap-Table

本文作者:HelloGitHub-kalifun

這是 HelloGitHub 推出的系列,今天給大家推薦一個基於 Bootstrap 和 jQuery 的表格插件:Bootstrap-Table

一、介紹

從項目名稱就可以知道,這是一款 Bootstrap 的表格插件。表格的展示的形式所有的前端幾乎在工作中都有涉及過,Bootstrap Table 提供了快速的建表、查詢、分頁、排序等一系列功能。

項目地址:https://github.com/wenzhixin/bootstrap-table

可能 Bootstrap 和 jQuery 技術有些過時了,但如果因為歷史的技術選型或者舊的項目還在用這兩個庫的話,那這個項目一定會讓你的嘴角慢慢上揚,拿下錶格展示方面的需求易如反掌!

二、模式

Boostatrp Table 分為兩種模式:客戶端(client)模式、服務端(server)模式。

  • 客戶端:通過數據接口將服務器需要加載的數據一次性展現出來,然後裝換成 json 然後生成 table。我們可以自己定義显示行數,分頁等,此時就不再會向服務器發送請求了。

  • 服務器:根據設定的每頁記錄數和當前显示頁,發送數據到服務器進行查詢。

三、實戰操作

Tips: 解釋說明均在代碼中以註釋方式展示,請大家注意閱讀。

我們採用的是最簡單的 CDN 引入方式,代碼可直接運行。複製代碼並將配置好 json 文件的路徑即可看到效果。

3.1 快速上手

註釋中的星號表示該參數必寫,話不多說上代碼。示例代碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello, Bootstrap Table!</title>
    // 引入 css
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous">
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.3/css/all.css" integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/" crossorigin="anonymous">
    <link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.css">
</head>
<body>
    // 需要填充的表格
    <table id="tb_departments" data-filter-control="true" data-show-columns="true"></table>
// 引入js
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script>
<script src="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.js"></script>
<script>
        window.operateEvents = {
            // 當點擊 class=delete 時觸發
            'click .delete': function (e,value,row,index) {
                // 在 console 打印出整行數據
                console.log(row);
            }
        };

        $('#tb_departments').bootstrapTable({
            url: '/frontend/bootstrap-table/user.json',         //請求後台的 URL(*)
            method: 'get',                      //請求方式(*)
            // data: data,                      //當不使用上面的後台請求時,使用data來接收數據
            toolbar: '#toolbar',                //工具按鈕用哪個容器
            striped: true,                      //是否显示行間隔色
            cache: false,                       //是否使用緩存,默認為 true,所以一般情況下需要設置一下這個屬性(*)
            pagination: true,                   //是否显示分頁(*)
            sortable: false,                    //是否啟用排序
            sortOrder: "asc",                   //排序方式
            sidePagination: "client",           //分頁方式:client 客戶端分頁,server 服務端分頁(*)
            pageNumber:1,                       //初始化加載第一頁,默認第一頁
            pageSize: 6,                        //每頁的記錄行數(*)
            pageList: [10, 25, 50, 100],        //可供選擇的每頁的行數(*)
            search: true,                       //是否顯示錶格搜索,此搜索是客戶端搜索,不會進服務端,所以個人感覺意義不大
            strictSearch: true,                 //啟用嚴格搜索。禁用比較檢查。
            showColumns: true,                  //是否显示所有的列
            showRefresh: true,                  //是否显示刷新按鈕
            minimumCountColumns: 2,             //最少允許的列數
            clickToSelect: true,                //是否啟用點擊選中行
            height: 500,                        //行高,如果沒有設置 height 屬性,表格自動根據記錄條數覺得表格高度
            uniqueId: "ID",                     //每一行的唯一標識,一般為主鍵列
            showToggle:true,                    //是否显示詳細視圖和列表視圖的切換按鈕
            cardView: false,                    //是否显示詳細視圖
            detailView: false,                  //是否显示父子表
            showExport: true,                   //是否显示導出
            exportDataType: "basic",            //basic', 'all', 'selected'.
            columns: [{
                checkbox: true     //複選框標題,就是我們看到可以通過複選框選擇整行。
            }, {
                field: 'id', title: 'ID'       //我們取json中id的值,並將表頭title設置為ID
            }, {
                field: 'username', title: '用戶名'         //我們取 json 中 username 的值,並將表頭 title 設置為用戶名
            },{
                field: 'sex', title: '性別'                //我們取 json 中 sex 的值,並將表頭 title 設置為性別
            },{
                field: 'city', title: '城市'               //我們取 json 中 city 的值,並將表頭 title 設置為城市
            },{
                field: 'sign', title: '簽名'               //我們取 json 中 sign 的值,並將表頭 title 設置為簽名
            },{
                field: 'classify', title: '分類'           //我們取 json 中 classify 的值,並將表頭 title 設置為分類
            },{
                //ormatter:function(value,row,index) 對後台傳入數據 進行操作 對數據重新賦值 返回 return 到前台
                // events 觸發事件
                field: 'Button',title:"操作",align: 'center',events:operateEvents,formatter:function(value,row,index){
                    var del = '<button type="button" class="btn btn-danger delete">刪除</button>'
                    return del;
                }
            }
            ],
            responseHandler: function (res) {
                return res.data      //在加載遠程數據之前,處理響應數據格式.
                // 我們取的值在data字段中,所以需要先進行處理,這樣才能獲取我們想要的結果
            }
        });
</script>
</body>
</html>

上面的代碼展示通過基本 API 實現基礎的功能,示例代碼並沒有羅列所有的 API。該庫還有很多好玩的功能等着大家去發現,正所謂師父領進門修行靠個人~

3.2 拆解講解

下面對關鍵點進行闡述,為了更方便使用的小夥伴清楚插件的用法。

3.2.1 初始化部分

選擇需要初始化表格。
$('#tb_departments').bootstrapTable({})
這個就像table的入口一樣。
<table id="tb_departments" data-filter-control="true" data-show-columns="true"></table>

3.2.2 閱讀數據部分

columns:[{field: 'Key', title: '文件路徑',formatter: function(value,row,index){} }]
  • field json 中鍵值對中的 Key
  • title 是表格頭显示的內容
  • formatter 是一個函數類型,當我們對數據內容需要修改時會用它。例:編碼轉換

3.2.3 事件觸發器

events:operateEvents
 window.operateEvents = {
        'click .download': function (e,value,row,index) {
            console.log(row);
        }
   }

因為很多時候我們需要針對錶格進行處理,所以事件觸發器是一個不錯的選擇。比如:它可以記錄我們的行數據,可以利用觸發器進行定製函數的執行等。

四、擴展

介紹幾個擴展可以讓我們便捷的實現更多的表格功能,而不需要自己造輪子讓我們的工作更加高效(也可以進入官網查看擴展的具體使用方法,官方已經收集了大量的擴展)。老規矩直接上代碼:

4.1 表格導出

<script src="js/bootstrap-table-export.js"></script> 
showExport: true,                                           //是否显示導出
exportDataType: basic,                                      //導出數據類型,支持:'基本','全部','選中'
exportTypes:['json', 'xml', 'csv', 'txt', 'sql', 'excel']   //導出類型

4.2 自動刷新

<script src="extensions/auto-refresh/bootstrap-table-auto-refresh.js"></script>
autoRefresh: true,                              //設置 true 為啟用自動刷新插件。這並不意味着啟用自動刷新
autoRefreshStatus: true,                        //設置 true 為啟用自動刷新。這是表加載時狀態自動刷新
autoRefreshInterval: 60,                        //每次發生自動刷新的時間(以秒為單位)
autoRefreshSilent: true                         //設置為靜默自動刷新

4.3 複製行

<script src="extensions/copy-rows/bootstrap-table-copy-rows.js"></script>
showCopyRows: true,                                 //設置 true 為显示複製按鈕。此按鈕將所選行的內容複製到剪貼板
copyWithHidden: true,                               //設置 true 為使用隱藏列進行複製
copyDelimiter: ', ',                                //複製時,此分隔符將插入列值之間
copyNewline: '\n'                                   //複製時,此換行符將插入行值之間

五、總結

本篇文章只是簡單的闡述 Bootstrap-Table 如何使用,正在對錶格功能實現而憂愁的小夥伴,可以使用 HelloGitHub 推薦的這款插件。你會發現網頁製作表格還可以如此快捷,期待小夥伴挖掘出更加有意思的功能哦。

注:上面 js 部分並沒有採用函數形式,建議在使用熟悉之後還是採用函數形式,這樣也方便復用及讓代碼看起來更加規範。

六、參考資料

『講解開源項目系列』——讓對開源項目感興趣的人不再畏懼、讓開源項目的發起者不再孤單。跟着我們的文章,你會發現編程的樂趣、使用和發現參与開源項目如此簡單。歡迎留言聯繫我們、加入我們,讓更多人愛上開源、貢獻開源~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

動物皮草太殘忍 洛杉磯市議會全體贊成禁售

摘錄自2018年9月21日蘋果日報美國洛杉磯報導

洛杉磯市議會周二(18日)通過議案,將立法禁止銷售皮草衣飾。議會全體投下贊成票,立場堅定;洛杉磯將成為美國禁售皮草的最大城市,可望為其他時尚重鎮帶來示範作用。

洛杉磯市議會以12比0的票數,一致贊成禁止商業皮草。立法機構負責草擬法規,由市議會審核,正式法規將在通過審議的兩年後生效。預計這類法規會為宗教目的、合法漁獵執照持有者另闢途徑,允許合法使用或生產動物皮草。

加州舊金山、西好萊塢、柏克萊都已限制皮草,但像洛杉磯這麼大規模的城市還是首例。提出此議案的議員科瑞茲(Paul Koretz)表示,洛杉磯是世界時尚之都,期許此舉能成為世界典範,紐約、芝加哥和邁阿密等大城可以跟進。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

中國發佈EVOP電動汽車運營平臺 打造電動汽車網聯大腦

日前,中能工業智慧技術研究院發佈了EVOP電動汽車運營平臺,打造中國最強電動汽車網聯大腦。

從傳統意義上來說,電動汽車只是“行駛+充電”的物理組合,滿足人們最基本的代步需求;而EVOP在此基礎上,通過互聯網和智慧化平臺,將電動汽車和充電設備打造成為能源互聯網產業鏈的重要一環。

和其他智慧化汽車應用相比,EVOP平臺基於中國最強工業大腦DPEN而打造,將資料、資訊和互聯網相結合,讓電動汽車產業鏈變得更智慧。DPEN支持數千萬個採集節點。在DPEN的引領之下,源源不斷的資料進入EVOP平臺,分門別類進行存儲和分析,並通過互聯網傳遞到每一輛電動汽車或者充電設備上,指導設備智慧化、高效率運行,並實現充電網、互聯網、車聯網“三網融合”。

在充電端,EVOP可以輕鬆實現智慧充電功能,它可以即時檢測並調整充電狀態,加強電池的健康管理,引導智慧有序充電、計量計費,並讓車主通過手機隨時瞭解充電情況;而商業地產、物業管理公司、電動汽車廠商等運營商和服務商可以通過雲平臺實現充電樁和車載電池的智慧管理,提供良好的增值服務。

在行駛端,由EVOP平臺海量採集的資料經過精確梳理和分析,通過雲平臺提供給每一位車主,在EVOP營造的車聯網中智慧、高效、安全出行。人們不僅可以隨時瞭解車輛和電池的資訊,快速查詢身邊的充電設備、預約充電;也可以在EVOP的指導下獲得最佳行車路線和最佳能效使用方案;亦可以在EVOP的社交平臺中交流經驗、分享資訊、找到志同道合的朋友,享受 “大資料+互聯網”的時尚車生活。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

go中的關鍵字-select

1. select的使用

  定義:在golang裡頭select的功能與epoll(nginx)/poll/select的功能類似,都是堅挺IO操作,當IO操作發生的時候,觸發相應的動作。

1.1 一些使用規範

  在Go的語言規範中,select中的case的執行順序是隨機的,當有多個case都可以運行,select會隨機公平地選出一個執行,其他的便不會執行:

 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6     ch := make (chan int, 1)
 7 
 8     ch<-1
 9     select {
10     case <-ch:
11         fmt.Println("隨機一")
12     case <-ch:
13         fmt.Println("隨機二n")
14     }
15 }

  輸出內容為隨機一二里面的任意一個。

  case後面必須是channel操作,否則報錯;default子句總是可運行的,所以沒有default的select才會阻塞等待事件 ;沒有運行的case,那麼將會阻塞事件發生報錯(死鎖)。

1.2 select的應用場景

timeout 機制(超時判斷)
 1 package main
 2 
 3 import (
 4     "fmt"
 5     "time"
 6 )
 7 
 8 func main() {
 9     timeout := make (chan bool, 1)
10     go func() {
11         time.Sleep(1*time.Second) // 休眠1s,如果超過1s還沒I操作則認為超時,通知select已經超時啦~
12         timeout <- true
13     }()
14     ch := make (chan int)
15     select {
16     case <- ch:
17     case <- timeout:
18         fmt.Println("超時啦!")
19     }
20 }

  也可以這麼寫:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "time"
 6 )
 7 
 8 func main() {
 9     ch := make (chan int)
10     select {
11     case <-ch:
12     case <-time.After(time.Second * 1): // 利用time來實現,After代表多少時間后執行輸出東西
13         fmt.Println("超時啦!")
14     }
15 }

  判斷channel是否阻塞(或者說channel是否已經滿了)

 1 package main
 2 
 3 import (
 4     "fmt"
 5 )
 6 
 7 func main() {
 8     ch := make (chan int, 1)  // 注意這裏給的容量是1
 9     ch <- 1
10     select {
11     case ch <- 2:
12     default:
13         fmt.Println("通道channel已經滿啦,塞不下東西了!")
14     }
15 }

  退出機制

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "time"
 6 )
 7 
 8 func main() {
 9     i := 0
10     ch := make(chan string, 0)
11     defer func() {
12         close(ch)
13     }()
14 
15     go func() {
16         DONE: 
17         for {
18             time.Sleep(1*time.Second)
19             fmt.Println(time.Now().Unix())
20             i++
21 
22             select {
23             case m := <-ch:
24                 println(m)
25                 break DONE // 跳出 select 和 for 循環
26             default:
27             }
28         }
29     }()
30 
31     time.Sleep(time.Second * 4)
32     ch<-"stop"
33 }

2. select的實現

  select-case中的chan操作編譯成了if-else。如:

1  select {
2  case v = <-c:
3          ...foo
4  default:
5          ...bar
6  }

  會被編譯為:

1  if selectnbrecv(&v, c) {
2          ...foo
3  } else {
4          ...bar
5  }

  類似地

1  select {
2  case v, ok = <-c:
3      ... foo
4  default:
5      ... bar
6  }

  會被編譯為:

1  if c != nil && selectnbrecv2(&v, &ok, c) {
2      ... foo
3  } else {
4      ... bar
5  }

  selectnbrecv函數只是簡單地調用runtime.chanrecv函數,不過是設置了一個參數,告訴當runtime.chanrecv函數,當不能完成操作時不要阻塞,而是返回失敗。也就是說,所有的select操作其實都僅僅是被換成了if-else判斷,底層調用的不阻塞的通道操作函數。

  在Go的語言規範中,select中的case的執行順序是隨機的,那麼,如何實現隨機呢?

  select和case關鍵字使用了下面的結構體:

1 struct    Scase
2   {
3       SudoG    sg;            // must be first member (cast to Scase)
4       Hchan*    chan;        // chan
5       byte*    pc;            // return pc
6       uint16    kind;
7       uint16    so;            // vararg of selected bool
8       bool*    receivedp;    // pointer to received bool (recv2)
9   };
1  struct    Select
2      {
3      uint16    tcase;            // 總的scase[]數量
4      uint16    ncase;            // 當前填充了的scase[]數量
5      uint16*    pollorder;        // case的poll次序
6      Hchan**    lockorder;        // channel的鎖住的次序
7      Scase    scase[1];        // 每個case會在結構體里有一個Scase,順序是按出現的次序
8  };

  每個select都對應一個Select結構體。在Select數據結構中有個Scase數組,記錄下了每一個case,而Scase中包含了Hchan。然後pollorder數組將元素隨機排列,這樣就可以將Scase亂序了。

 3. select死鎖

  select不注意也會發生死鎖,分兩種情況:

  如果沒有數據需要發送,select中又存在接收通道數據的語句,那麼將發送死鎖

1 package main
2 func main() {  
3     ch := make(chan string)
4     select {
5     case <-ch:
6     }
7 }

  預防的話加default。

  空select,也會引起死鎖。

1 package main
2 
3 func main() {  
4     select {}
5 }

 4. select和switch的區別

select

select只能應用於channel的操作,既可以用於channel的數據接收,也可以用於channel的數據發送。如果select的多個分支都滿足條件,則會隨機的選取其中一個滿足條件的分支, 如規範中所述:

If multiple cases can proceed, a uniform pseudo-random choice is made to decide which single communication will execute.

`case`語句的表達式可以為一個變量或者兩個變量賦值。有default語句。

31 package main                                                                                                                                              32 import "time"
33 import "fmt"                                                                                                                                              
35 func main() {                                                                                                                                             36     c1 := make(chan string)
37     c2 := make(chan string)                                                                                                                               38     go func() {
39         time.Sleep(time.Second * 1)                                                                                                                       40         c1 <- "one"
41     }()                                                                                                                                                   42     go func() {
43         time.Sleep(time.Second * 2)                                                                                                                       44         c2 <- "two"
45     }()                                                                                                                                                   46     for i := 0; i < 2; i++ {
47         select {                                                                                                                                          48             case msg1 := <-c1:
49             fmt.Println("received", msg1)          
50 case msg2 := <-c2: 51 fmt.Println("received", msg2)
52 } 53 }

switch

  switch可以為各種類型進行分支操作, 設置可以為接口類型進行分支判斷(通過i.(type))。switch 分支是順序執行的,這和select不同。

 1 package main                  
 2 import "fmt"
 3 import "time"  
 4 
 5 func main() {                                                                                                                             
 6      i := 2
 7      fmt.Print("Write ", i, " as ")  
 8      switch i {
 9          case 1:
10          fmt.Println("one")
11          case 2:                                                                                                                                  
12          fmt.Println("two")
13          case 3:                                                                                                                      
14          fmt.Println("three")
15      }                                                                                                                                             
16      switch time.Now().Weekday() {
17          case time.Saturday, time.Sunday:
18          fmt.Println("It's the weekend")
19          default:                                                                                                                                      
20          fmt.Println("It's a weekday")
21      }                                                                                                                                                 
22      t := time.Now()
23      switch {                                                                                                                                         
24          case t.Hour() < 12:
25          fmt.Println("It's before noon")                                                                                                              
26          default:
27          fmt.Println("It's after noon")                                                                                                                  
28      }
29      whatAmI := func(i interface{}) {                                                                                                                   
30          switch t := i.(type) {
31              case bool:                                                                                                                              
32              fmt.Println("I'm a bool")
33              case int:                                                                                                                                 
34              fmt.Println("I'm an int")
35              default:                                                                                                                                 
36              fmt.Printf("Don't know type %T\n", t)
37          }
38      }
39      whatAmI(true)                                                                                                                                     
40      whatAmI(1)
41      whatAmI("hey")                                                                                                                                 
42  }

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?

全球首例 德氫動力火車上路零碳排

摘錄自2018年10月2日公視報導

全球第一列標榜零排放的氫動力火車,日前在德國正式上路。它所使用的燃料電池,把氫氣和氧氣結合產生電力,目前時速可達140公里。這是德國逐步淘汰柴油動力火車的重要里程碑,歐洲其他國家也紛紛表達高度興趣。

披著亮藍色外衣,造型簡單大方的火車,緩緩駛進德國北部的布雷默沃德車站,它就是全球第一列氫動力列車,由法國鐵路製造商阿爾斯通負責打造。

阿爾斯通經理施朗克說:「這部列車是(全球)第一列完全零碳排,而且車頂沒有電纜線的火車,它是電動車。」

催生氫動力火車的阿爾斯通計畫在2021年之前,在德國下薩克森邦再推出14列同款列車,德國其他城市和地區也可能起而仿效。此外包括英國荷蘭丹麥等國都對零污染,低噪音的氫動力火車表示濃厚興趣。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※想知道網站建置網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計後台網頁設計

※不管是台北網頁設計公司台中網頁設計公司,全省皆有專員為您服務

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※帶您來看台北網站建置台北網頁設計,各種案例分享

小三通物流營運型態?

※快速運回,大陸空運推薦?