小卷的胡言亂語

隨心。隨興。隨喜。隨緣



Vue自訂分頁-以MVC C#實做

近期在學習並使用Vue.js,剛好有做資料分頁的需求,雖說網路上流傳著不少好用元件以及範例程式碼,但因為傻露的需求可能比較特別,老是找不到合意的,故最後就決定自己刻啦。

註:傻露想要的效果類似shield ui醬子

使用語言、資源及相關架構


Vue.js 2.4.2
jQuery 1.9.1
MVC 5(VS2013)
C#
Json.net(不知道第幾版但版號應該沒差)
bootstrap 3.3.7
awesome font 4.7

以下是正文XD


設有一群的資料要依分頁顯示,並依照按下分頁時才至資料庫取得該分頁的資料集合。
假設頁碼範圍1-4頁,分頁長像如下:
[|<][<][1][2][3][4][…][>][>|]

以下就上面所示撰寫範例。

後端

1.web.config初始化設定
在web.config內設定初始顯示的頁碼、每頁顯示的資料量、以及畫面上要顯示的頁碼範圍(即本例的4頁)

<configuration>
  <appSettings>  
    <!--取得資料的起始頁數-->
    <add key="INIPAGE" value="1"/>
    <!--每頁顯示資料筆數-->
    <add key="CNT_PAGEITEM" value="7"/>
    <!--page顯示頁碼集合數-->
    <add key="CNT_PAGEITEM_MAX" value="4"/>    
  </appSettings>
</configuration>

2.資料
這邊為了方便起見,不連接資料庫
使用資料來源: https://www.json-generator.com/api/json/get/cknklDscqG?indent=2
以下程式碼因篇幅關係故僅顯示部分資料

public class SimpleData
{        
    public string GetSimpleData()
    {
        return data;
    }
    private string data = @"[{                               
                                    'index': 0, 
                                    'age': 56,     
                                    'name': 'Flowers Harmon', 
                                    'gender': 'male'
                                 }, 
                                 {
                                    'index': 1, 
                                    'age': 60,     
                                    'name': 'Angie Matthews', 
                                    'gender': 'female'
                                 }, 
                                 {
                                    'index': 2, 
                                    'age': 28,                                     
                                    'name': 'Gina Randolph', 
                                    'gender': 'female'
                                 }]";
 
}

3.資料模型
定義顯示的資料model

public class ModelData
{
    public int index { get; set; }
    public string name { get; set; }
    public int age { get; set; }
    public string gender { get; set; }
    public string TOTPAGE { get; set; }
    public string PAGED { get; set; } 
}

4.分頁資料模型
定義分頁的資料model

public class PageData
{
    //pager model
    //往前
    public bool EnableLeft { get; set; }
    //往後
    public bool EnableRight { get; set; }       
    //省略符號(前)
    public bool DisplayLeftEllipsesZone { get; set; }
    //省略符號(後)
    public bool DisplayRightEllipsesZone { get; set; }
    //省略符號index(前)
    public string LeftEllipsIndex { get; set; }
    //省略符號index(後)
    public string RightEllipsIndex { get; set; }
    //顯示頁碼
    public List<string> DisplayPageZone { get; set; }
    //總頁數
    public string TotalPage { get; set; }
    //現在頁數
    public string CurrentPage { get; set; }
    
    public PageData()
    {
        this.EnableLeft = false;
        this.EnableRight = false;
        this.DisplayLeftEllipsesZone = false;
        this.DisplayRightEllipsesZone = false;
        this.LeftEllipsIndex = "1";
        this.RightEllipsIndex = "1";
        this.TotalPage = "1";
        this.CurrentPage = "1";
        this.DisplayPageZone = new List<string>();            
    }
}

5.分頁資料邏輯
這部分是參考網友的程式碼,再依據自己的需求調整

public class CommonTool
{
    /// <summary>
    /// 自訂分頁物件
    /// </summary>
    /// <param name="currPage">所在頁碼</param>
    /// <param name="cntPage">顯示頁碼maxValue(每頁顯示資料量)</param>
    /// <param name="totPage">資料總頁數</param>
    /// <returns></returns>
    public PageData GetDisplayPages(int currPage, int cntPage, int totPage)
    {
        PageData pgdata = new PageData();                                        
        int ppSize = totPage / cntPage;
        if (totPage % cntPage > 0)
            ppSize++;            
        int currppSize = 1;
        if (currPage % cntPage != 0)
            currppSize = (currPage / cntPage) + 1;  
        else            
            currppSize = (currPage / cntPage);                            
        //判斷是否顯示省略符號            
        int pStart = 1;
        if (currPage % cntPage != 0)
            pStart = ((currPage / cntPage) * cntPage + 1); 
        else
            pStart = ((currPage - 1) / cntPage) * cntPage+1;
        int pEnd = pStart + (cntPage - 1);
        if (pEnd > totPage)            
            pEnd = totPage;
        if (currppSize > 1)
        {
            pgdata.DisplayLeftEllipsesZone = true;
            pgdata.LeftEllipsIndex = (pStart - 1).ToString();
        }
        if (currppSize < ppSize)
        {
            pgdata.DisplayRightEllipsesZone = true;
            pgdata.RightEllipsIndex = (pEnd + 1).ToString();
        }                
        //畫面上顯示之頁碼
        for (int i = pStart; i <= pEnd; i++)
        {
            pgdata.DisplayPageZone.Add(i.ToString());
        }
                    
        //首頁末頁
        if (currPage == 1)            
        {
            if (totPage > 1)
                pgdata.EnableRight = true; 
        }                           
        else
        {
            if (currPage == totPage)             
                pgdata.EnableLeft = true;                   
            else
            {
                pgdata.EnableLeft = true;
                pgdata.EnableRight = true;                    
            }
        }
        pgdata.TotalPage = totPage.ToString();
        pgdata.CurrentPage = currPage.ToString();

        return pgdata;
    }
}

6.撰寫取資料、取得分頁相關設定值的Service

public class DataService
{
    /// <summary>
    /// 取得資料結果
    /// </summary>
    /// <param name="pagnum">頁碼</param>
    /// <param name="pagedata">每頁資料量</param>
    /// <returns></returns>
    public dynamic GetResult(string pagnum, string pagedata)
    {
        List<ModelData> result = new List<ModelData>();
        List<ModelData> data = JsonConvert.DeserializeObject<List<ModelData>>(new SimpleData().GetSimpleData());
        //計算該筆資料所在頁數
        int i = 0;
        foreach (var t in data)
        {
            i++;
            t.PAGED = Math.Ceiling(Convert.ToDouble(i) / Convert.ToDouble(pagedata)).ToString(); 
        }
        //計算總頁數
        var totpage = Math.Ceiling(Convert.ToDouble(data.Count) / Convert.ToDouble(pagedata));
        result = data.Where(t => t.PAGED == pagnum.ToString()).ToList();
        result[0].TOTPAGE = totpage.ToString();
            
        return result;
    }
    
    //取得分頁資料結果
    public dynamic GetPager(string currpage, string countpage, string totpage)
    {
        CommonTool tool = new CommonTool();
        return tool.GetDisplayPages(Convert.ToInt32(currpage), Convert.ToInt32(countpage), Convert.ToInt32(Convert.ToDouble(totpage)));
    }
}

7.HomeController.cs
設定View初始值

public ActionResult Index()
{
    SetInitial();
    return View();
}
private void SetInitial()
{
    ViewBag.Inipage = System.Configuration.ConfigurationManager.AppSettings["INIPAGE"].ToString();
    ViewBag.Pageitem = System.Configuration.ConfigurationManager.AppSettings["CNT_PAGEITEM"].ToString();
    ViewBag.Pagemax = System.Configuration.ConfigurationManager.AppSettings["CNT_PAGEITEM_MAX"].ToString();
}

再來增加撰寫取得資料並拋回前端的程式碼

DataService service = new DataService();

[HttpPost]
public ActionResult GetData(string pagnum, string pagedata)
{
    string maxnum = System.Configuration.ConfigurationManager.AppSettings["CNT_PAGEITEM_MAX"].ToString();
    //資料
    var result = service.GetResult(pagnum, pagedata);
    if (result != null)
    {
        //分頁資料
        var PagerData = service.GetPager(pagnum, maxnum, result[0].TOTPAGE);
        return Json(new { rtncode = "1", rtnmsg = "success", data = result, pager = PagerData });
    }
    else
        return Json(new { rtncode = "0", rtnmsg = "failure" });            
}

前端

8.Index.cshtml

@{
    ViewBag.Title = "Index";
}

@Html.Hidden("getinipage", (object)ViewBag.Inipage)
@Html.Hidden("getpageitem", (object)ViewBag.Pageitem)
@Html.Hidden("getpagemax", (object)ViewBag.Pagemax)

<html>
    <head>
        <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">                
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
        <link href="~/Content/styles.css" rel="stylesheet" />
    </head>
    <body>        
        <div id="app" v-cloak>  
            <table class="table table-condensed">
                <tr>
                    <th>#</th>
                    <th>name</th>
                    <th>age</th>
                    <th>gender </th>
                </tr>
                <tr v-for="(r, index) in dataVal">                    
                    <td>{{ r.index+1 }}</td>
                    <td>{{ r.name }}</td>
                    <td>{{ r.age }}</td>
                    <td>{{ r.gender }}</td>
                </tr>
            </table> 

            <div class="page-footer">
                <div class="container-fluid text-center py-3">
                    <div class="row">
                        @{ Html.RenderPartial("Partial_Page");}
                    </div>
                </div>                        
            </div>            

        </div>

        <script src="http://code.jquery.com/jquery-1.9.1.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
        <script src="~/Scripts/index.js"></script>
    </body>
</html>

分頁Partial_Page.cshtml

<div class="pagination">
    <ul class="pagination pagination-sm">   
        <li class="page-item" v-bind:class="[dataPage.EnableLeft ? '' : 'btndisabled']" >        
            <a class="page-link" href="#" v-on:click="setPage(1)" title="第一頁"><i class="fa fa-angle-double-left fa-lg"></i></a>  
        </li>
        <li class="page-item" v-bind:class="[dataPage.EnableLeft ? '' : 'btndisabled']" >        
            <a class="page-link" href="#" v-on:click="setPage(parseInt(dataPage.CurrentPage)-1)" title="上一頁"><i class="fa fa-angle-left fa-lg" ></i></a>        
        </li>
        <li class="page-item">
            <a class="page-link" href="#" v-show="dataPage.DisplayLeftEllipsesZone" v-on:click="setPage(dataPage.LeftEllipsIndex)" title="更多...">...</a>
        </li>
        <li class="page-item" v-for="pageNum in dataPage.DisplayPageZone" v-bind:class="[(parseInt(pageNum) === parseInt(dataPage.CurrentPage)) ? 'active' : '']">
            <a class="page-link" href="#" v-on:click="setPage(pageNum)" >{{ pageNum }}</a>
        </li>   
        <li class="page-item">
            <a class="page-link" href="#" v-show="dataPage.DisplayRightEllipsesZone" v-on:click="setPage(dataPage.RightEllipsIndex)" title="更多...">...</a>
        </li>
        <li class="page-item" v-bind:class="[dataPage.EnableRight ? '' : 'btndisabled']">
            <a class="page-link" href="#" v-on:click="setPage(parseInt(dataPage.CurrentPage)+1)" title="下一頁"><i class="fa fa-angle-right fa-lg"></i></a>
        </li> 
        <li class="page-item" v-bind:class="[dataPage.EnableRight ? '' : 'btndisabled']">
            <a class="page-link" href="#" v-on:click="setPage(dataPage.TotalPage)" title="最末頁"><i class="fa fa-angle-double-right fa-lg" ></i></a>
        </li>      
    </ul>
</div>

9.分頁css

body {
    padding-top: 70px; 

    /*font-family*/    
    font-family: "Helvetica", "Arial","LiHei Pro","黑體-繁","微軟正黑體", sans-serif;
    line-height: 2;
    font-size: 15px;
    font-weight: 300;
}
/*pagination style*/

/*控制首末按鈕disabled*/
.btndisabled a{
    pointer-events: none;
    cursor:not-allowed;
    background-color:#fafafa !important;
    color:#b3b6bc !important;
}

/*覆寫bootstrap*/
.pagination-sm > li > a, .pagination-sm > li > span {
    font-size:15px !important;
    line-height:1 !important;
}

10.Javascript(index.js)
先撰寫Ajax取得資料

//startPageIndex:頁數
//eachPageNum:每頁顯示資料筆數
function GetAll(startPageIndex, eachPageNum)
{
    var serviceURL = '../Home/GetData';

    $.ajax({
        type: 'post',
        contentType: "application/json; charset=utf-8",
        url: serviceURL,
        data: JSON.stringify({ 'pagnum': startPageIndex, 'pagedata': eachPageNum }),
        dataType: "json",
        cache: false,
        success: function (Result) {
            if (Result.rtncode === '1') {
                app.dataVal = Result.data;
                app.dataPage = Result.pager;
            }
            else {
                app.dataVal = [];
                app.dataPage = [];
            }            
        },
        error: function () { /*alert(data.d.rtnmsg);*/ }
    });
}

Ajax取得資料後餵給Vue

$(document).ready(function () {
    //取得後端設定的相關資料
    var startPageIndex = $("#getinipage").val();
    var eachPageNum = $("#getpageitem").val();
    //取資料
    GetAll(startPageIndex, eachPageNum);
});

var app = 
    new Vue({
            el: '#app',
            data: {
                dataVal: [],
                dataPage: []
            },
            methods: {
                setPage: function (page) {
                    var eachPageNum = $("#getpageitem").val();
                    GetAll(page, eachPageNum);
                }
            }
        });

這樣就完成分頁啦,後來因重複使用需要,傻露也改寫成component,下回有機會再分享囉~。

參考資料

[ASP.net MVC 4] 自己打造分頁Pager範例程式碼 (數字版,Ajax)
Vue Document