1. 程式人生 > >Django個人部落格開發之分頁與Vue分頁

Django個人部落格開發之分頁與Vue分頁

分頁介紹與邏輯

由於當前開發的需求當中各種管理平臺的增多,各種各樣的列表需求逐漸增多,而往往資料量有很多,很難實現也沒有必要將所有的資料都羅列到一個前端頁面上,使用者用不到,也很浪費資源。所以我們有了分頁的需求,將資料分頁顯示。
現在有1000條文章的資料需要展示在我們的計算機上。
1. 可以將1000條分為每10條一頁,這樣就形成了分頁。我們來看一下分頁的邏輯:

頁碼 資料
1 0 - 9
2 10 - 19
3 20 - 29
...... ......
n (n-1)*10 - n*10-1

那麼這種分頁,需要我們定義當前頁碼還有單頁資料長度。

2. 這樣的分頁,每次請求,都需要對1000條資料進行遍歷,也許瀏覽者只需要第20條資料,所以,我們是不是可以在上面的基礎上,對使用者進行”欺詐”呢?

分頁碼 頁碼 資料
0 - 29 1 0 - 9
2 10 - 19
3 20 - 29
30 - 59 4 30 - 39
5 40 - 49
6 50 - 59

這種邏輯,當用戶請求資料,先不去查詢所有資料,而是查詢前30條資料,然後,對這30條資料進行分頁,如果有人查詢到第4頁,我們就接著載入。

到這裡,分頁的邏輯並沒有結束,還需要想明白前端除了分頁的資料,還需要什麼:

  1. 分頁的資料
  2. 當前的頁面 用來搭建上一頁和下一頁
  3. 頁碼的序列

Vue介紹

上面我們瞭解了基本的分頁,我們今天主要用ajax+vue實現分頁的動態載入。當然在這裡先需要給大家介紹一下我們的vue.js, 是一套構建使用者介面的漸進式框架。只關注檢視層的目標是通過儘可能簡單的 API 實現響應的資料繫結和組合的檢視元件。

MVC (Model – View - Control)模式

MTV (Model – Template - Views)模式

Vue.js採用的是MVVM(ModelView-viewModel)

強調前端的雙向繫結邏輯

通俗的講,vue.js可以將我們從後端傳遞過來的json資料傳遞到HTML當中,進行動態的更新。

首先可以從vue的官網(https://vuejs.org/)上下載vue的指令碼,在這裡,我們使用已經下載好的檔案,將他們放入static下面。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuejsExample</title>
    <script src = "/static/js/jquery.js"></script>
    <script src = "/static/js/vue.min.js"></script>
</head>
<body>
    <h1>
        這個是我們vuejs的測試頁面
    </h1>
    <!--vue 繫結和Django的jinja2模板系統使用的標籤是一樣的,為了區分我們需要用verbatim將vue的標籤包圍起來-->
    {% verbatim myblock %}
    <div id = "vueExample">
        <p> 基本的文字繫結 </p>
        <p> hello world I am {{ name }}</p>
    </div>
    {% endverbatim myblock %}
    <script>
        new Vue( //例項化vue物件
             {
                 el: "#vueExample", //繫結的元素是一個id為vueExample的div
                 data:{
                     name: "while, I am so cool"
                 } //可供繫結的資料設定
             }
        )
    </script>
</body>
</html>

效果如下:

這個是vue最基礎的例子,在這裡我們要注意一下幾個點:

{% verbatim myblock %}
    <div id = "vueExample">
        <p> 基本的文字繫結:</p>
        <p> hello world I am {{ name }}</p>
    </div>
{% endverbatim myblock %}
vue物件在HTML當中呼叫,在js當中進行資料的宣告,其中有一些固定的點和結構需要記憶。首先需要宣告vue物件,指定繫結的HTML標籤,選擇器是jq選擇器,這裡用#veuExample來繫結。具體的資料都放在一個data的地方
{% verbatim myblock %}
    <div id = "vueExample">
        <p> 基本的文字繫結:</p>
        <p> hello world I am {{ name }}</p>
    </div>
{% endverbatim myblock %}
<script>
    new Vue(
         {
             el: "#vueExample", //繫結的物件
             data: { //繫結的具體資料
                 name : "while"
             }
         }
    )
</script>

上面是一個最簡單的文字繫結的例子,我們接下來再看幾種繫結

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuejsExample</title>
    <script src = "/static/js/jquery.js"></script>
    <script src = "/static/js/vue.min.js"></script>
</head>
<body>
    <h1>
        這個是我們vuejs的測試頁面
    </h1>
    <!--vue 繫結和Django的jinja2模板系統使用的標籤是一樣的,為了區分我們需要用verbatim將vue的標籤包圍起來-->
    {% verbatim myblock %}
        <div id = "vueExample">
        <p> 基本的迴圈繫結 </p>
        <p v-for = "p in project"> hello world I like {{ p }}</p>  <!--關注迴圈體-->
    </div>
    {% endverbatim myblock %}
    <script>
        new Vue( //例項化vue物件
             {
                 el: "#vueExample", //繫結的元素是一個id為vueExample的div
                 data:{
                     name: "while, I am so cool",
                     project: ["python", "linux", "php"]
                 } //可供繫結的資料設定
             }
        )
    </script>
</body>
</html>

效果如下:

帶有屬性和對映的迴圈繫結

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuejsExample</title>
    <script src = "/static/js/jquery.js"></script>
    <script src = "/static/js/vue.min.js"></script>
</head>
<body>
    <h1>
        這個是我們vuejs的測試頁面
    </h1>
    <!--vue 繫結和Django的jinja2模板系統使用的標籤是一樣的,為了區分我們需要用verbatim將vue的標籤包圍起來-->
    {% verbatim myblock %}
    <div id = "vueExample">
        <p> 帶有屬性和對映的迴圈繫結 </p>
        <ul>
            <li v-for = "p in projects">
                <span v-bind:id = "p.color">
                    {{ p.name }}
                </span>
            </li>
        </ul>
    </div>
    {% endverbatim myblock %}
    <script>
        new Vue( //例項化vue物件
             {
                 el: "#vueExample", //繫結的元素是一個id為vueExample的div
                 data:{
                     name: "while, I am so cool",
                     project: ["python", "linux", "php"],
                     projects:[
                         {name: "python", color: "red"},
                         {name: "linux", color: "green"},
                         {name: "php", color: "blue"}
                     ]
                 } //可供繫結的資料設定
             }
        )
    </script>
</body>
</html>

效果如下:

基本的事件繫結

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>vuejsExample</title>
    <script src = "/static/js/jquery.js"></script>
    <script src = "/static/js/vue.min.js"></script>
</head>
<body>
    <h1>
        這個是我們vuejs的測試頁面
    </h1>
    <!--vue 繫結和Django的jinja2模板系統使用的標籤是一樣的,為了區分我們需要用verbatim將vue的標籤包圍起來-->
    {% verbatim myblock %}
        <div id = "vueExample">
        <p> 基本的事件繫結 </p>
        <span v-on:click = "alertthing">按我呀!</span>
    </div>
    {% endverbatim myblock %}
    <script>
        new Vue( //例項化vue物件
             {
                 el: "#vueExample", //繫結的元素是一個id為vueExample的div
                 data:{
                     name: "while, I am so cool",
                     project: ["python", "linux", "php"],
                     projects:[
                         {name: "python", color: "red"},
                         {name: "linux", color: "green"},
                         {name: "php", color: "blue"}
                     ]
                 }, //可供繫結的資料設定
                 methods: {
                     alertthing: function () {
                         alert("按我幹啥?")
                     }
                 }
             }
        )
    </script>
</body>
</html>

效果如下:

基於Vue嘗試分頁

Vue後臺資料返回

/OurBlog/Aritcles/views.py

from Article.models import Article
from django.core.paginator import Paginator
from django.http import JsonResponse

def vuePageData(request):
    """
    提供分頁的ajax資料
    """
    if request.method == "GET": #如果是get請求
        page = request.GET.get("page") #嘗試獲取page
        if not page: #預設page 為1
            page = 1
        else:
            page = int(page) #get過來的page引數是字串
        aritcles = Article.objects.all() #查詢所有的資料
        paginator = Paginator(aritcles,3) #對資料進行分頁,每頁三條
        pageData = paginator.page(page) #獲取具體頁的資料
        page_data = [] #對資料進行json結構化,json只接受字典物件
        for data in pageData:
            classify = data.classify.all() #多對多欄位需要首先查詢出所有對應的欄位,查詢出來還是資料庫物件
            if classify:
                classify = [i.label for i in classify] #對欄位取指定的lable
            else:
                classify = "" #空類表不可以建json序列,所以,我們改完字串
            page_data.append(
                {
                    "title": data.title,
                    "author": data.author.name, #w外來鍵,必須呼叫具體的欄位
                    "time": data.time,
                    "description": data.description,
                    "picture": data.picture.name, #這裡的name是由於檔案物件有name屬性
                    "classify": classify,
                    "id": data.id
                }
            )
        result = {
            "pageData": page_data
        }
        return JsonResponse(result)

效果如下:

Html前端進行呼叫

/OurBlog/template/myArticle.html

{% extends "base.html" %}

{% block title %}
    我的部落格
{% endblock %}

{% block style %}
<link href="/static/css/main.css" rel="stylesheet">
<script src="/static/js/jquery.js"></script>
<script src="/static/js/vue.min.js"></script>
{% endblock %}

{% block content %}
<div class="container">
  <div class="con_content">
    <div class="about_box">
      <h2 class="nh1"><span>您現在的位置是:<a href="/" target="_blank">網站首頁</a>>><a href="#" target="_blank">個人日記</a></span><b>個人日記</b></h2>
      {% verbatim myblock %}
          <div class="dtxw box" id = "dataList">
            <li v-for = "page in page_data">
              <div class="dttext f_l">
                <ul>
                  <h2><a href="/">{{ page.title }}</a></h2>
                  <p v-html="page.description">{{ page.description }}</p> <!-- 注意和Django的safe標籤的區分 v-html == safe -->
                  <span>{{ page.time }}</span>
                </ul>
              </div>
              <div class="xwpic f_r"><a href="/"><img v-bind:src="['/static/'+page.picture]"></a></div> <!-- 注意繫結資料的拼接 -->
            </li>
          </div>
      {% endverbatim myblock %}

      <div class="pagelist">頁次:1/1 每頁25 總數10<a href="/">首頁</a><a href="/">上一頁</a><a href="/">下一頁</a><a href="/">尾頁</a></div>
    </div>
  </div>
  <div class="blank"></div>
  <!-- container程式碼 結束 -->
</div>
    <script>
        $(
             function () {
                $.ajax(
                     {
                         url: "/vuePageData/",
                         type: "GET",
                         data: "",
                         dataType: "json", //我們需要搶到我們的ajax請求需求資料是json
                         success: function (data) {
                             new Vue( //注意new地下和var
                                 {
                                   el:"#dataList", //繫結指定元素,注意選擇器,注意#
                                   data: {
                                       page_data: data["pageData"]                                   }
                                  }
                             );
                         },
                         error: function (error) {
                             console.log(error)
                         }
                     }
                )
             }
        );
    </script>
{% endblock %}

這裡我已經提前在admin後臺管理中對article進行了新增,所以看到的效果如下:

基於vue-resource嘗試分頁

上面的邏輯雖然可以實現分頁,但是有資料損耗。所以我們主要來實現第二種分頁,我們著重看的是分頁的後端邏輯

我們先來檢視分頁的邏輯

分頁碼 頁碼 資料
1 1 0 - 3             0,1,2
2 3 - 6             3,4 ,5
3 6 - 9              6,7,8
2 4 9 - 12            9,10,11
5 12 - 15          12,13,14
6 15 - 18          15,16,17

每次查詢,查詢3頁。每頁查詢 3條。當收到單頁碼請求:我們只查詢9條

首先完成第一步邏輯

1,2,3 --> 1

 4,5,6 --> 2

select_page = 3 #每次3頁
page_num = 3 #每頁3條
select_num = select_page*page_num #單次查詢9條
resutl_dict = {}

query_num = page/page_num #獲取查詢序列號
if query_num == int(query_num): #如果整除
    query_num = int(query_num)  #返回整形數
else: #否則
    query_num = int(query_num)+1 #返回整形數加1

首先完成第二步邏輯

1  查詢 0-9

2  查詢 9-18

所以

n  查詢 (n-1)*9-n*9

start = (query_num - 1)*select_num
end = query_num*select_num

然後,我們需要了解一個原理

Django模型的查詢返回的物件是quertset物件,之後我們需要重寫該物件,但是現在我們需要知道這種查詢有惰性。

articels = Article.objects.all() #現在當前查詢不會執行,直到呼叫

Good

articels = Article.objects.all()[start:end] #現在當前查詢不會執行,直到呼叫,當前寫法的查詢效率和資料庫limit語句相似

Bad

articels = Article.objects.all() #現在當前查詢不會執行,直到呼叫,當前寫法的查詢效率和資料庫limit語句相似
articles = articels[start:end]

然後我們檢視完整的分頁邏輯程式碼

def vuePageData1(request):
    """
    第二種分頁的檢視函式
    """
    if request.method == "GET": #如果是get請求
        page = request.GET.get("page") #嘗試獲取page
        if not page: #預設page 為1
            page = 1
        else:
            page = int(page) #get過來的page引數是字串

        select_page = 3 #每次3頁
        page_num = 3 #每頁3條
        select_num = select_page*page_num #單次查詢9條
        resutl_dict = {}

        query_num = page/page_num #獲取查詢序列號
        if query_num == int(query_num): #如果整除
            query_num = int(query_num)  #返回整形數
        else: #否則
            query_num = int(query_num)+1 #返回整形數加1

        start = (query_num - 1)*select_num
        end = query_num*select_num
        articels = Article.objects.all()[start:end] #現在當前查詢不會執行,只到呼叫,當前寫法的查詢效率和資料庫limit語句相似
        #進行分頁
        paginator = Paginator(articels,page_num) #分頁值分了9條資料
	page = paginator(page)
  1. 如果查詢1,2,3,我們請求的時候page是1,2,3

那如果page = 4 查詢的 1

query_num = 2

start = (query_num-1)*select_num = (2-1)*3 = 3

4 – 3 = 1

pageData = paginator(page-start)

分頁檢視完整程式碼    

\OurBlog\Article\views.py

def vuePageData_1(request):
    """
        這個函式是用來返回分頁資料的,一定要認真
        query_page 分頁量
        首先劃定邏輯
        每頁3條
        每次3頁
        到第3頁我們返回下5頁
        """
    select_page = 3  # 每次3頁
    page_num = 3  # 每頁3條
    select_num = select_page * page_num  # 單次查詢的總條數
    page_result = {}  # 返回的結果

    if request.method == "GET":  # 如果是get請求
        page = request.GET.get("page")  # 獲取page值,在這裡注意一定要用get,如果用[]容易導致如果page沒有引數會報錯
        if page and int(page) > 1:  # page存在,且大於1
            page = int(page)
        else:  # 設定預設為1
            page = 1

        query_num = page / page_num
        if query_num == int(query_num):
            query_num = int(query_num)
        else:
            query_num = int(query_num) + 1

        start = (query_num - 1) * select_num
        end = query_num * select_num + 3
        articles = Article.objects.all()[start:end]
        paginator = Paginator(articles, page_num)  # 進行每頁5條的分頁
        print(page)
        print(start)

        pageData = paginator.page(page-start/page_num) #注意加入頁面為4 我們的start是9

        page_data = []  # 對資料進行從新整合

        for data in pageData:
            page_data.append(
                {
                    "title": data.title,
                    "author": data.author.name,
                    "time": data.time,
                    "picture": data.picture.name,  # 這裡要注意,data.picture 返回的是圖片物件,我們獲取他的name
                    'description': data.description,
                    'id': data.id,
                }
            )
        page_result["pageData"] = page_data

        if page % select_page == 0 and len(paginator.page_range) > 3:
            page_result["page_range"] = [page + page_num * (query_num - 1) for page in paginator.page_range]
        else:
            page_result["page_range"] = [page + page_num * (query_num - 1) for page in paginator.page_range][:-1]

        return JsonResponse(page_result)

HTML前端

\OurBlog\template\myArticle_v1.html

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>個人部落格模板古典系列之――江南墨卷</title>
<meta name="keywords" content="個人部落格模板古典系列之――江南墨卷" />
<meta name="description" content="個人部落格模板古典系列之――江南墨卷" />
<link href="/static/css/base.css" rel="stylesheet">
<link href="/static/css/main.css" rel="stylesheet">

<!--[if lt IE 9]>
<script src="js/modernizr.js"></script>
<![endif]-->
<script type="text/javascript" src="/static/js/jquery.js"></script>
<script type="text/javascript" src="/static/js/vue.min.js"></script>
<script type="text/javascript" src="/static/js/vue-resource.js"></script>

</head>
<body>
<div id="wrapper">
<header>
  <div class="headtop"></div>
  <div class="contenttop">
    <div class="logo f_l">個人部落格模板古典系列之――江南墨卷</div>
    <div class="search f_r">
      <form action="/e/search/index.php" method="post" name="searchform" id="searchform">
        <input name="keyboard" id="keyboard" class="input_text" value="請輸入關鍵字" style="color: rgb(153, 153, 153);" onfocus="if(value=='請輸入關鍵字'){this.style.color='#000';value=''}" onblur="if(value==''){this.style.color='#999';value='請輸入關鍵字'}" type="text">
        <input name="show" value="title" type="hidden">
        <input name="tempid" value="1" type="hidden">
        <input name="tbname" value="news" type="hidden">
        <input name="Submit" class="input_submit" value="搜尋" type="submit">
      </form>
    </div>
    <div class="blank"></div>
    <nav>
      <div  class="navigation">
        <ul class="menu">
          <li><a href="index.html">網站首頁</a></li>
          <li><a href="#">關於我</a>
            <ul>
              <li><a href="article.html">個人簡介</a></li>
              <li><a href="listpic.html">個人相簿</a></li>
            </ul>
          </li>
          <li><a href="#">我的日記</a>
            <ul>
              <li><a href="articleList.html">個人日記</a></li>
              <li><a href="articleList.html">學習筆記</a></li>
            </ul>
          </li>
          <li><a href="articleList.html">技術文章</a> </li>
          <li><a href="#">給我留言</a> </li>
        </ul>
      </div>
    </nav>
    <SCRIPT type=text/javascript>
   // Navigation Menu
   $(function() {
      $(".menu ul").css({display: "none"}); // Opera Fix
      $(".menu li").hover(function(){
         $(this).find('ul:first').css({visibility: "visible",display: "none"}).slideDown("normal");
      },function(){
         $(this).find('ul:first').css({visibility: "hidden"});
      });
   });
</SCRIPT> 
  </div>
</header>
<body>
<div class="container">
  <div class="con_content">
    <div class="about_box" id = "vueData">
      <h2 class="nh1"><span>您現在的位置是:<a href="/" target="_blank">網站首頁</a>>><a href="#" target="_blank">個人日記</a></span><b>個人日記</b></h2>
        {% verbatim myblock %} <!--vue的格式化標籤-->
            <div class="dtxw box">
                <li v-for = "page in page_data">
                      <div class="dttext f_l">
                            <ul>
                                <!--注意vue的屬性繫結 兩個問題
                                    問題一:要用陣列型別
                                    問題二:要注意v-bind:後面不可以有空格 強迫症尤其小心
                                -->
                              <h2><a v-bind:href="['/article/?id='+page.id]">{{ page.title }}</a></h2>
                              <p v-html="page.description">{{ page.description }}</p>
                              <span>{{ page.time }}</span>
                            </ul>
                      </div>
                    <div class="xwpic f_r"><a href="/"><img v-bind:src="['/static/'+page.picture]"></a></div>
                </li>
          </div>
            <div class="pagelist">

                <span v-for="p in page_range" >
                    <a v-on:click="getPage(p)">{{ p }}</a> <!--注意vue的函式繫結和傳參-->
                </span>
          </div>
        {% endverbatim myblock %}
    </div>
  </div>
  <div class="blank"></div>
  <!-- container程式碼 結束 -->
  
  <footer>
    <div class="footer">
      <div class="f_l">
        <p>All Rights Reserved 版權所有:<a href="http://www.yangqq.com">楊青個人部落格</a> 備案號:蜀ICP備00000000號</p>
      </div>
      <div class="f_r textr">
        <p>Design by DanceSmile</p>
      </div>
    </div>
  </footer>
</div>
</body>
<script>
    var list = [];
    Vue.use(VueResource); //首先需要宣告一下vue物件變成了vueResource物件
    new Vue(
        {
            el: "#vueData",
            data: {
                page_data: "",
                page_range: []
            },
            // created方法在初始化的時候呼叫的,相當於我們預設的第一頁
            created: function () {
                var url = "/VP1/";
                this.$http.get(url).then(
                     function (data) {
                          this.page_data = data["data"]["pageData"]; //用ajax返回的資料更新到我們繫結好的資料當中
                          this.page_range = data["data"]["page_range"]; //用ajax返回的資料更新到我們繫結好的資料當中
                          for(var ds in data["data"]["page_range"]){
                              list.push(data["data"]["page_range"][ds])
                          }
                     },
                     function (error) {
                         console.log(error)
                     }
                )
            },
            methods: {
                getPage: function (page) {
                    var url = "/VP1/?page="+page;
                    this.$http.get(url).then(
                     function (data) {
                          page_range = data["data"]["page_range"];
                          this.page_data = data["data"]["pageData"];
                          for(var ds in data["data"]["page_range"]){
                              console.log("++++++++++++++++++++++++++++++++++++++++++++");
                              pageNumber = page_range[ds];
                              if($.inArray(pageNumber,list) == -1){
                                  list.push(pageNumber)
                              }
                              console.log(pageNumber);
                              console.log("++++++++++++++++++++++++++++++++++++++++++++");

                          }

                          this.page_range = list;
                         console.log(data)
                     },
                     function (error) {
                         console.log(error)
                     }
                )
                }
            }
        }
    )
</script>
</html>