天天看點

Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁

1、清單頁熱銷排行

根據路徑參數

category_id

查詢出該類型商品銷量前二的商品。

使用

Ajax

實作局部重新整理的效果。

查詢清單頁熱銷排行資料

  1. 請求方式
    Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁
  2. 請求參數:路徑參數
    Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁
  3. 響應結果:JSON
    Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁
{
    "code":"0",
    "errmsg":"OK",
    "hot_skus":[
        {
            "id":6,
            "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRbI2ARekNAAFZsBqChgk3141998",
            "name":"Apple iPhone 8 Plus (A1864) 256GB 深空灰色 移動聯通電信4G手機",
            "price":"7988.00"
        },
        {
            "id":14,
            "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRdMSAaDUtAAVslh9vkK04466364",
            "name":"華為 HUAWEI P10 Plus 6GB+128GB 玫瑰金 移動聯通電信4G手機 雙卡雙待",
            "price":"3788.00"
        }
    ]
}
           
  1. 接口定義和實作
class HotGoodsView(View):
    """商品熱銷排行"""
    def get(self, request, category_id):
        """提供商品熱銷排行JSON資料"""
        # 根據銷量倒序
        skus = models.SKU.objects.filter(category_id=category_id, is_launched=True).order_by('-sales')[:2]
        # 序列化
        hot_skus = []
        for sku in skus:
            hot_skus.append({
                'id':sku.id,
                'default_image_url':sku.default_image.url,
                'name':sku.name,
                'price':sku.price
            })
        return http.JsonResponse({'code':RETCODE.OK, 'errmsg':'OK', 'hot_skus':hot_skus})
           

渲染清單頁熱銷排行資料

  1. 模闆資料category_id傳遞到Vue.js
<script type="text/javascript">
    let category_id = "{{ category.id }}";
</script>
           
data: {
    category_id: category_id,
},
           
  1. Ajax請求商品熱銷排行JSON資料
get_hot_skus(){
    if (this.category_id) {
        let url = '/hot/'+ this.category_id +'/';
        axios.get(url, {
            responseType: 'json'
        })
            .then(response => {
                this.hot_skus = response.data.hot_skus;
                for(let i=0; i<this.hot_skus.length; i++){
                    this.hot_skus[i].url = '/detail/' + this.hot_skus[i].id + '/';
                }
            })
            .catch(error => {
                console.log(error.response);
            })
    }
},
           
  1. 渲染商品熱銷排行界面
<div class="new_goods" v-cloak>
    <h3>熱銷排行</h3>
    <ul>
        <li v-for="sku in hot_skus">
            <a :href="sku.url"><img :src="sku.default_image_url"></a>
            <h4><a :href="sku.url">[[ sku.name ]]</a></h4>
            <div class="price">¥[[ sku.price ]]</div>
        </li>
    </ul>
</div>
           

項目執行個體代碼

商品視圖檔案:

apps/goods/views.py

"""
商品視圖檔案:apps/goods/views.py
"""
from django.shortcuts import render
from django.views import View
from contents.utils import get_categories         # 導入封裝方法
from .utils import get_breadcrumb                 # 導入面包屑封裝方法
from .models import GoodsCategory, SKU            # 導入模型
from django.views.generic import ListView          # 分頁
from django.core.paginator import Paginator        # 分頁
from django import http
from utils.response_code import RETCODE


# 熱銷商品視圖
class HotGoodsView(View):
    """熱銷排行"""
    def get(self, request, category_id):
        """傳回兩條熱銷商品"""
        skus = SKU.objects.filter(category_id=category_id, is_launched=True).order_by('-sales')[:2]  # 根據sales的逆序排序,并取前兩個資料
        
        # 拼接資料,序列化
        hot_skus = []
        for sku in skus:
            sku_dict = {
                "id": sku.id,
                "default_image_url": sku.default_image_url.url,
                "name": sku.name,
                "price": sku.price,
            }
            hot_skus.append(sku_dict)          # 資料添加
        # print(hot_skus)
        """ 拼接資料之後的形式
        {
            "code":"0",
            "errmsg":"OK",
            "hot_skus":[
                {
                    "id":6,
                    "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRbI2ARekNAAFZsBqChgk3141998",
                    "name":"Apple iPhone 8 Plus (A1864) 256GB 深空灰色 移動聯通電信4G手機",
                    "price":"7988.00"
                },
                {
                    "id":14,
                    "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRdMSAaDUtAAVslh9vkK04466364",
                    "name":"華為 HUAWEI P10 Plus 6GB+128GB 玫瑰金 移動聯通電信4G手機 雙卡雙待",
                    "price":"3788.00"
                }
            ]
        }
        """
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'ok', 'hot_skus': hot_skus})


class GoodsListView(View):
    """商品清單頁面"""
    def get(self, request, category_id, page_num):
        """查詢并渲染商品"""
        # category_id 是商品分類的id
        
        try:
            # 查詢面包屑
            category = GoodsCategory.objects.get(id=category_id)
        except Exception as e:
            return render(request, '404.html')
        
        # 擷取排序規則  接收sort參數:如果使用者不傳,就是預設的排序規則
        sort = request.GET.get('sort', 'default')     # 根據字段sort查詢
        if sort == 'price':                # 按照排序規則查詢該分類商品SKU資訊
            sort_field = 'price'           # 根據字段price的正序排列,價格由低到高
        elif sort == 'hot':
            sort_field = '-sales'          # 根據字段sales的倒序排列,銷量由高到低
        else:                              # 當傳輸的排序方式不是上面的兩種,需要讓排序方式為預設的
            sort = 'default'
            sort_field = 'create_time'     # 根據create_time字段排序
            
        # 排序      根據category.id篩選,以sort_field排序
        skus = SKU.objects.filter(is_launched=True, category_id=category.id).order_by(sort_field)
        # print(skus)
        
        # 分頁  Paginator('分頁面的記錄', '每頁記錄的條數')  建立分頁器
        paginator = Paginator(skus, 5)
        # 擷取page_num的頁碼的資料
        page_skus = paginator.page(page_num)
        
        # 總的頁面數
        tatal_page = paginator.num_pages
        
        # 查詢商品的類别
        categories = get_categories()
        
        breadcrumb = get_breadcrumb(category)                     # 調用封裝的方法
        # print(breadcrumb)
        
        context = {
            "categories": categories,       # 頻道分類
            "breadcrumb": breadcrumb,       # 面包屑導航
            "page_skus": page_skus,         # 分頁後資料
            "tatal_page": tatal_page,       # 總頁數
            "category_id": category_id,     # 商品類别id
            "sort": sort,                   # 排序字段,以何種方式排序
            "page_num": page_num,           # 目前頁碼
            
        }
        
        return render(request, 'list.html', context=context)        # 傳遞資料到前端界面list.html

           

商品子產品的路由:

apps/goods/urls.py

# -*- encoding: utf-8 -*-
"""
@File    : urls.py
@Time    : 2020/9/10 10:03
@Author  : chen

商品子產品的路由:apps/goods/urls.py
"""
from django.urls import path, include, re_path
from . import views

app_name = 'goods'

urlpatterns = [
    re_path(r'^list/(?P<category_id>\d+)/(?P<page_num>\d+)/$', views.GoodsListView.as_view(), name='list'),
    re_path(r'^hot/(?P<category_id>\d+)/$', views.HotGoodsView.as_view()),
]

           

商品清單界面:

templates/list.html

<h3>熱銷排行</h3>
				<ul>
            {#       熱銷産品的循環播放       vue的文法        #}
                    <li v-for="sku in hot_skus">
                        <a href="detail.html"><img :src="sku.default_image_url"></a>
						<h4><a href="detail.html">[[  sku.name ]]</a></h4>
						<div class="price">[[ sku.price ]]</div>
					</li>
				</ul>
           

2、商品搜尋

全文檢索方案Elasticsearch

全文檢索和搜尋引擎原理

商品搜尋 需求

  • 當使用者在搜尋框輸入商品關鍵字後,我們要為使用者提供相關的商品搜尋結果。

商品搜尋 實作

  • 可以選擇使用模糊查詢

    like

    關鍵字實作。
  • 但是

    like

    關鍵字的效率極低。
  • 查詢需要在多個字段中進行,使用

    like

    關鍵字也不友善。

全文檢索方案

  • 我們引入全文檢索的方案來實作商品搜尋。
  • 全文檢索即在指定的任意字段中進行檢索查詢。
  • 全文檢索方案需要配合搜尋引擎來實作。

搜尋引擎原理

  • 搜尋引擎進行全文檢索時,會對資料庫中的資料進行一遍預處理,單獨建立起一份索引結構資料。
  • 索引結構資料類似新華字典的索引檢索頁,裡面包含了關鍵詞與詞條的對應關系,并記錄詞條的位置。
  • 搜尋引擎進行全文檢索時,将關鍵字在索引資料中進行快速對比查找,進而找到資料的真實存儲位置。

結論:

搜尋引擎建立索引結構資料,類似新華字典的索引檢索頁,全文檢索時,關鍵字在索引資料中進行快速對比查找,進而找到資料的真實存儲位置。

Elasticsearch

介紹

實作全文檢索的搜尋引擎,首選的是

Elasticsearch

  • Elasticsearch

    是用

    Java

    實作的,開源的搜尋引擎。
  • 它可以快速地儲存、搜尋和分析海量資料。維基百科、

    Stack Overflow

    Github

    等都采用它。
  • Elasticsearch

    的底層是開源庫

    Lucene

    。但是,沒法直接使用

    Lucene

    ,必須自己寫代碼去調用它的接口。

分詞說明

  • 搜尋引擎在對資料建構索引時,需要進行分詞處理。
  • 分詞是指将一句話拆解成多個單字 或 詞,這些字或詞便是這句話的關鍵詞。
  • 比如:我是中國人; 分詞後:我、是、中、國、人、中國等等都可以是這句話的關鍵字。
  • Elasticsearch

    不支援對中文進行分詞建立索引,需要配合擴充

    elasticsearch-analysis-ik

    來實作中文分詞處理。

使用Docker安裝Elasticsearch

  1. 擷取Elasticsearch-ik鏡像
# 從倉庫拉取鏡像
sudo docker image pull delron/elasticsearch-ik:2.4.6-1.0

# 解壓資料中本地鏡像
sudo docker load -i elasticsearch-ik-2.4.6_docker.tar
           
Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁
  1. 配置

    Elasticsearch-ik

  • 将資料中的

    elasticsearc-2.4.6

    目錄拷貝到

    home

    目錄下。
  • 修改

    /home/xxx/elasticsearc-2.4.6/config/elasticsearch.yml

    第54行。
  • 更改

    ip

    位址為本機真實

    ip

    位址。
Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁

3. 使用Docker運作Elasticsearch-ik

sudo docker run -dti --name=elasticsearch --network=host -v /home/xx/elasticsearch-2.4.6/config:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0
           

注意上面的檔案路徑需要根據自己的虛拟機路徑來修改

Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁

Haystack擴充建立索引

Elasticsearch

的底層是開源庫

Lucene

。但是沒法直接使用

Lucene

,必須自己寫代碼去調用它的接口。

思考:

  • 我們如何對接

    Elasticsearch

    服務端?

解決方案:

  • Haystack

Haystack介紹和安裝配置

  1. Haystack

    介紹

    Haystack

    是在

    Django

    中對接搜尋引擎的架構,搭建了使用者和搜尋引擎之間的溝通橋梁。

    • 我們在

    Django

    中可以通過使用

    Haystack

    來調用

    Elasticsearch

    搜尋引擎。

    Haystack

    可以在不修改代碼的情況下使用不同的搜尋後端(比如

    Elasticsearch

    Whoosh

    Solr

    等等)。
  2. Haystack

    安裝

安裝版本要确定,與上面的Elasticsearch-ik比對

pip install django-haystack
pip install elasticsearch==2.4.1
           
Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁
  1. Haystack

    注冊應用和路由
INSTALLED_APPS = [
    'haystack', # 全文檢索
]
           
  1. 總路由
  1. Haystack

    配置

    • 在配置檔案中配置

    Haystack

    為搜尋引擎後端
# Haystack
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
        'URL': 'http://192.168.232.142:9200/', # Elasticsearch伺服器ip位址,端口号固定為9200
        'INDEX_NAME': 'lgshop', # Elasticsearch建立的索引庫的名稱
    },
}
# 當添加、修改、删除資料時,自動生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
           

HAYSTACK_SIGNAL_PROCESSOR

配置項保證了在

Django

運作起來後,有新的資料産生時,

Haystack

仍然可以讓

Elasticsearch

實時生成新資料的索引。

項目執行個體代碼

開發環境配置檔案:

dev.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # 'apps.users',                     # 在apps檔案夾下的users子產品進行app注冊
    'users',                            # 使用者子產品
    'contents',                         # 網頁首頁子產品,
    'verifications',                    # 驗證碼子產品app
    'oauth',                            # 第三方登陸QQ的子產品
    'areas',                            # 使用者位址子產品
    'goods',                            # 商品子產品
    'haystack',                         # 全文檢索
]


# Haystack
HAYSTACK_CONNECTIONS = {
    'default': {
        'ENGINE': 'haystack.backends.elasticsearch_backend.ElasticsearchSearchEngine',
        'URL': 'http://192.168.232.142:9200/',          # Elasticsearch伺服器ip位址,端口号固定為9200
        'INDEX_NAME': 'lgshop',                         # Elasticsearch建立的索引庫的名稱
    },
}
# 當添加、修改、删除資料時,自動生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
           

項目總路由檔案:

shop/urls.py

'''
項目總路由檔案:shop/urls.py
'''

from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('admin/', admin.site.urls),
    
    path('users/', include('users.urls')),    # 如果當時注冊users子產品時候沒有使用sys.path.insert導入路徑,這裡就需要改為 'apps.users.urls'
    path('', include('contents.urls')),       # 首頁路由
    path('', include('verifications.urls')),  # 驗證碼路由
    path('', include('oauth.urls')),          # QQ登陸路由
    path('', include('areas.urls')),          # 使用者位址子產品路由
    path('', include('goods.urls')),           # 商品子產品總路由
    path(r'search/', include('haystack.urls')),     # 全文檢索的總路由
]

           

商品搜尋結果界面:

templates/search/search.html

{#    商品搜尋結果界面:templates/search/search.html   #}
{% load static %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>LG商城-商品搜尋</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/jquery.pagination.css' %}">
    <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
    <script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body>
    <div id="app">
	<div class="header_con">
		<div class="header" v-cloak>
            <div class="welcome fl">歡迎來到LG商城!</div>
            <div class="fr">
                <div v-if="username" class="login_btn fl">
                    歡迎您:<em>[[ username ]]</em>
                    <span>|</span>
                    <a href="{{ url('users:logout' %}">退出</a>
                </div>
                <div v-else class="login_btn fl">
                    <a href="{{ url('users:login' %}">登入</a>
                    <span>|</span>
                    <a href="{{ url('users:register' %}">注冊</a>
                </div>
                <div class="user_link fl">
                    <span>|</span>
                    <a href="{{ url('users:info' %}">使用者中心</a>
                    <span>|</span>
                    <a href="cart.html">我的購物車</a>
                    <span>|</span>
                    <a href="user_center_order.html">我的訂單</a>
                </div>
            </div>
        </div>
	</div>
	<div class="search_bar clearfix">
		<a href="{{ url('contents:index' %}" class="logo fl"><img src="{% static 'images/1.png' %}"></a>
		<div class="search_wrap fl">
			<form method="get" action="/search/" class="search_con">
                <input type="text" class="input_text fl" name="q" placeholder="搜尋商品">
                <input type="submit" class="input_btn fr" name="" value="搜尋">
            </form>
			<ul class="search_suggest fl">
				<li><a href="#">索尼微單</a></li>
				<li><a href="#">優惠15元</a></li>
				<li><a href="#">美妝個護</a></li>
				<li><a href="#">買2免1</a></li>
			</ul>
		</div>
	</div>
    <div class="main_wrap clearfix">
        <div class=" clearfix">
            <ul class="goods_type_list clearfix">
            {#      搜尋的商品結果循環顯示             #}
                {% for result in page %}
                <li>
                    {# object取得才是sku對象 #}
                    <a href="/detail/{{ result.object.id }}/"><img src="{{ result.object.default_image.url }}"></a>
                    <h4><a href="/detail/{{ result.object.id }}/">{{ result.object.name }}</a></h4>
                    <div class="operate">
                        <span class="price">¥{{ result.object.price }}</span>
                        <span>{{ result.object.comments }}評價</span>
                    </div>
                </li>
                {% empty %}
                    <p>沒有找到您要查詢的商品。</p>
                {% endfor %}

            </ul>
            <div class="pagenation">
                <div id="pagination" class="page"></div>
            </div>
        </div>
    </div>
	<div class="footer">
		<div class="foot_link">
			<a href="#">關于我們</a>
			<span>|</span>
			<a href="#">聯系我們</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情連結</a>
		</div>
		<p>CopyRight © 2016 北京LG商業股份有限公司 All Rights Reserved</p>
		<p>電話:010-****888    京ICP備*******8号</p>
	</div>
    </div>
    <script type="text/javascript" src="{% static 'js/common.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/search.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/jquery.pagination.min.js' %}"></script>
    <script type="text/javascript">
        $(function () {
            $('#pagination').pagination({
                currentPage: 1,
                totalPage: 3,
                callback:function (current) {
                    {#location.href = '/search/?q=iphone&page=' + current;#}
                }
            })
        });
    </script>
</body>
</html>
           

3、Haystack建立資料索引

  1. 建立索引類

    • 通過建立索引類,來指明讓搜尋引擎對哪些字段建立索引,也就是可以通過哪些字段的關鍵字來檢索資料。

    • 本項目中對

    SKU

    資訊進行全文檢索,是以在

    goods

    應用中建立

    search_indexes.py

    檔案,用于存放索引類。
from haystack import indexes
from .models import SKU
class SKUIndex(indexes.SearchIndex, indexes.Indexable):
    """SKU索引資料模型類"""
    text = indexes.CharField(document=True, use_template=True)
    
    def get_model(self):
        """傳回建立索引的模型類"""
        return SKU
    
    def index_queryset(self, using=None):
        """傳回要建立索引的資料查詢集"""
        return self.get_model().objects.filter(is_launched=True)
           
  • 索引類

    SKUIndex

    說明:

    • 在

    SKUIndex

    建立的字段,都可以借助

    Haystack

    Elasticsearch

    搜尋引擎查詢。

    • 其中

    text

    字段我們聲明為

    document=True

    ,表名該字段是主要進行關鍵字查詢的字段。

    text

    字段的索引值可以由多個資料庫模型類字段組成,具體由哪些模型類字段組成,我們用

    use_template=True

    表示後續通過模闆來指明。
  1. 建立

    text

    字段索引值模闆檔案

    • 在

    templates

    目錄中建立

    text

    字段使用的模闆檔案

    • 具體在

    templates/search/indexes/goods/sku_text.txt

    檔案中定義
{{ object.id }}
{{ object.name }}
{{ object.caption }}
           
Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁
  • 模闆檔案說明:當将關鍵詞通過

    text

    參數名傳遞時

    • 此模闆指明

    SKU

    id

    name

    caption

    作為

    text

    字段的索引值來進行關鍵字索引查詢。
  1. 手動生成初始索引
python manage.py rebuild_index
           
Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁

全文檢索測試

  1. 準備測試表單

    • 請求方法:GET

    • 請求位址:/search/

    • 請求參數:q

    Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁
    結論:錯誤提示告訴我們在templates/search/⽬錄中缺少⼀個search.html⽂件 search.html⽂件作⽤就是接收和渲染全⽂檢索的結果。

檢索測試期間遇到的BUG問題及解決方案

  1. Ubuntu虛拟機固定IP方法

    https://my.oschina.net/u/2556916/blog/1509644

  2. 開啟虛拟機的

    storage

    tracker

    服務
    Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁
  3. django 報錯:urllib3.exceptions.ConnectTimeoutError 問題解決方法

    解答:之前運作docker容器還沒有退出,導緻出現容器重名情況:

    https://blog.csdn.net/u012129607/article/details/79616547

    https://blog.csdn.net/qq_36662478/article/details/91044571

# 檢視所有存在的程序
sudo docker container ls -all
# 删掉已經存在的elasticsearch程序号
sudo docker rm xxxxxx
# 開啟storage
sudo docker start storage
# 開啟tracker
sudo docker start tracker
# 重新建立elasticsearch程序
sudo docker run -dti --name=elasticsearch --network=host -v /home/ch/elasticsearch-2.4.6/config:/usr/share/elasticsearch/config delron/elasticsearch-ik:2.4.6-1.0

# 檢視最新的所有程序
sudo docker container ls
           

4、渲染商品搜尋結果

準備商品搜尋結果頁面

Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁

渲染商品搜尋結果

Haystack傳回的資料包括:

  • query:搜尋關鍵字
  • paginator:分頁paginator 對象
  • page:目前頁的page對象(周遊page中的對象,可以得到result對象)
  • result.objects: 目前周遊出來的SKU對象

Haystack搜尋結果分頁

  1. 設定每頁傳回資料條數
  • 通過

    HAYSTACK_SEARCH_RESULTS_PER_PAGE

    可以控制每頁顯示數量
  • 每頁顯示五條資料:

    HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5

開發環境配置檔案:

dev.py

# 開發環境配置檔案:dev.py

# es搜尋設定的分頁每頁資料條數
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 5
           
  1. 準備搜尋頁分頁器

搜尋結果頁面:

templates/search/search.html

{#  搜尋結果頁面:templates/search/search.html  #}
{% load static %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>LG商城-商品搜尋</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/jquery.pagination.css' %}">
    <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
    <script type="text/javascript" src="{% static 'js/jquery-1.12.4.min.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body>
    <div id="app">
	<div class="header_con">
		<div class="header" v-cloak>
            <div class="welcome fl">歡迎來到LG商城!</div>
            <div class="fr">
                <div v-if="username" class="login_btn fl">
                    歡迎您:<em>[[ username ]]</em>
                    <span>|</span>
                    <a href="{{ url('users:logout' %}">退出</a>
                </div>
                <div v-else class="login_btn fl">
                    <a href="{{ url('users:login' %}">登入</a>
                    <span>|</span>
                    <a href="{{ url('users:register' %}">注冊</a>
                </div>
                <div class="user_link fl">
                    <span>|</span>
                    <a href="{{ url('users:info' %}">使用者中心</a>
                    <span>|</span>
                    <a href="cart.html">我的購物車</a>
                    <span>|</span>
                    <a href="user_center_order.html">我的訂單</a>
                </div>
            </div>
        </div>
	</div>
	<div class="search_bar clearfix">
		<a href="{{ url('contents:index' %}" class="logo fl"><img src="{% static 'images/1.png' %}"></a>
		<div class="search_wrap fl">
			<form method="get" action="/search/" class="search_con">
                <input type="text" class="input_text fl" name="q" placeholder="搜尋商品">
                <input type="submit" class="input_btn fr" name="" value="搜尋">
            </form>
			<ul class="search_suggest fl">
				<li><a href="#">索尼微單</a></li>
				<li><a href="#">優惠15元</a></li>
				<li><a href="#">美妝個護</a></li>
				<li><a href="#">買2免1</a></li>
			</ul>
		</div>
	</div>
    <div class="main_wrap clearfix">
        <div class=" clearfix">
            <ul class="goods_type_list clearfix">
                {% for result in page %}

                    <li>
                        {# object取得才是sku對象 #}
                        <a href="/detail/{{ result.object.id }}/"><img src="{{ result.object.default_image_url.url }}"></a>
                        <h4><a href="/detail/{{ result.object.id }}/">{{ result.object.name }}</a></h4>
                        <div class="operate">
                            <span class="price">¥{{ result.object.price }}</span>
                            <span>{{ result.object.comments }}評價</span>
                        </div>
                    </li>

                {% empty %}
                    <p>沒有找到您要查詢的商品。</p>
                {% endfor %}
            </ul>
            <div class="pagenation">
                <div id="pagination" class="page"></div>
            </div>
        </div>
    </div>
	<div class="footer">
		<div class="foot_link">
			<a href="#">關于我們</a>
			<span>|</span>
			<a href="#">聯系我們</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情連結</a>
		</div>
		<p>CopyRight © 2016 北京LG商業股份有限公司 All Rights Reserved</p>
		<p>電話:010-****888    京ICP備*******8号</p>
	</div>
    </div>
    <script type="text/javascript" src="{% static 'js/common.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/search.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/jquery.pagination.min.js' %}"></script>
    <script type="text/javascript">
        $(function () {
            $('#pagination').pagination({
                currentPage: {{ page.number }},
                totalPage: {{ paginator.num_pages }},
                callback:function (current) {
                    location.href = '/search/?q={{ query }}&page=' + current;
                }
            })
        });
    </script>
</body>
</html>
           

搜尋頁面樣式檔案:

static/js/search.js

檔案

// 搜尋頁面樣式檔案: static/js/search.js檔案
let vm = new Vue({
    el: '#app',
    delimiters: ['[[', ']]'],
    data: {
        username: getCookie('username'),
    },
    mounted(){
    },
    methods: {
    }
});
           

項目總路由檔案:

shop/urls.py

'''
項目總路由檔案:shop/urls.py
'''

from django.contrib import admin
from django.urls import path, include


urlpatterns = [
    path('admin/', admin.site.urls),
    
    path('users/', include('users.urls')),    # 如果當時注冊users子產品時候沒有使用sys.path.insert導入路徑,這裡就需要改為 'apps.users.urls'
    path('', include('contents.urls')),       # 首頁路由
    path('', include('verifications.urls')),  # 驗證碼路由
    path('', include('oauth.urls')),          # QQ登陸路由
    path('', include('areas.urls')),          # 使用者位址子產品路由
    path('', include('goods.urls')),           # 商品子產品總路由
    path(r'search/', include('haystack.urls')),     # 全文檢索的總路由,es固定的寫法
]

           

5、商品詳情頁

商品詳情頁分析和準備

商品詳情頁組成結構分析

  1. 商品頻道分類

    已經提前封裝在contents.utils.py⽂件中,直接調用方法即可。

  2. 面包屑導航

    已經提前封裝在goods.utils.py⽂件中,直接調用方法即可。

  3. 熱銷排行

    該接口已經在商品清單頁中實作完畢,前端直接調用接口即可。

  4. 商品

    SKU

    資訊(詳情資訊)

    通過

    sku_id

    可以找到

    SKU

    資訊,然後渲染模闆即可。

    使用

    Ajax

    實作局部重新整理效果。
  5. SKU

    規格資訊

    通過

    SKU

    可以找到

    SPU

    規格和

    SKU

    規格資訊。
  6. 商品詳情介紹、規格與包裝、售後服務

    通過

    SKU

    可以找到

    SPU

    資訊,

    SPU

    中可以查詢出商品詳情介紹、規格與包 裝、售後服務。
  7. 商品評價

    商品評價需要在生成了訂單,對訂單商品進行評價後再實作,商品評價資訊是動态資料。

    使用

    Ajax

    實作局部重新整理效果。

商品詳情頁接口設計和定義

  1. 請求方式
    Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁
  2. 請求參數:路徑參數
    Django項目實戰——14—(清單頁熱銷排行、商品搜尋、Haystack建立資料索引、渲染商品搜尋結果、商品詳情頁)1、清單頁熱銷排行2、商品搜尋3、Haystack建立資料索引4、渲染商品搜尋結果5、商品詳情頁
  3. 響應結果:HTML

    detail.html

  4. 接⼝定義

渲染商品頻道分類、面包屑導航、商品熱銷排行

  • 将原先在商品清單頁實作的代碼拷貝到商品詳情頁即可。
  • 添加

    detail.js

# 商品詳情類
class DetailGoodsView(View):
    """商品詳情"""
    def get(self, request, sku_id):
        try:
            sku = SKU.objects.get(id=sku_id)          # 異常處理,sku_id擷取的值異常時候
        except Exception as e:
            return render(request, '404.html')
        
        # 查詢商品類别
        categories = get_categories()
        
        # 查詢面包屑
        breadcrumb = get_breadcrumb(sku.category)
        
        # 傳遞資料
        context = {
            'categories': categories,
            'breadcrumb': breadcrumb,
            'sku': sku,
            'category_id': sku.category.id,
        }
        return render(request, 'detail.html', context=context)
    
           

為了讓前端在擷取商品熱銷排行資料時,能夠拿到商品分類

ID

,我們将商品分類

ID

從模闆傳⼊到

Vue.js

商品詳情頁面檔案:

templates/detail.html

{#   商品詳情頁面檔案:templates/detail.html        #}
{% load static %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>LG商城-商品詳情</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
	<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body>
	<div id="app">
	<div class="header_con">
		<div class="header" v-cloak>
			<div class="welcome fl">歡迎來到LG商城!</div>
			<div class="fr">
                <div v-if="username" class="login_btn fl">
                    歡迎您:<em>[[ username ]]</em>
                    <span>|</span>
                    <a href="{{ url('users:logout' %}">退出</a>
                </div>
                <div v-else class="login_btn fl">
                    <a href="{{ url('users:login' %}">登入</a>
                    <span>|</span>
                    <a href="{{ url('users:register' %}">注冊</a>
                </div>
				<div class="user_link fl">
					<span>|</span>
					<a href="{{ url('users:info' %}">使用者中心</a>
					<span>|</span>
					<a href="cart.html">我的購物車</a>
					<span>|</span>
					<a href="user_center_order.html">我的訂單</a>
				</div>
			</div>
		</div>		
	</div>
	<div class="search_bar clearfix">
		<a href="{{ url('contents:index' %}" class="logo fl"><img src="{% static 'images/logo.png' %}"></a>
		<div class="search_wrap fl">
			<form method="get" action="/search/" class="search_con">
                <input type="text" class="input_text fl" name="q" placeholder="搜尋商品">
                <input type="submit" class="input_btn fr" name="" value="搜尋">
            </form>
			<ul class="search_suggest fl">
				<li><a href="#">索尼微單</a></li>
				<li><a href="#">優惠15元</a></li>
				<li><a href="#">美妝個護</a></li>
				<li><a href="#">買2免1</a></li>
			</ul>
		</div>
		<div class="guest_cart fr">
			<a href="cart.html" class="cart_name fl">我的購物車</a>
			<div class="goods_count fl" id="show_count">2</div>
			<ul class="cart_goods_show">
				<li>
					<img src="{% static 'images/goods/goods001.jpg' %}" alt="商品圖檔">
					<h4>華為 HUAWEI P10 Plus 6GB+64GB 鑽雕金 移動聯通電信4G手機 雙卡雙待</h4>
					<div>1</div>
				</li>
				<li>
					<img src="{% static 'images/goods/goods002.jpg' %}" alt="商品圖檔">
					<h4>Apple iPhoneX 64GB 深空灰色 移動聯通電信4G手機</h4>
					<div>1</div>
				</li>
			</ul>			
		</div>
	</div>
	<div class="navbar_con">
		<div class="navbar">
			<div class="sub_menu_con fl">
				<h1 class="fl">商品分類</h1>

				{#  apps/goods/views.py檔案中傳遞的categories資料進行渲染	#}
                <ul class="sub_menu">
                    {% for group in categories.values %}
                        <li>
                        {#      一級查詢      #}
                        <div class="level1">
                            {% for channel in group.channels %}
                                <a href="{{ channel.url }}">{{ channel.name }}</a>
                            {% endfor %}
                        </div>
                        <div class="level2">
                            {#      二級查詢      #}
                            {% for cat2 in group.sub_cats %}
                                <div class="list_group">
                                    <div class="group_name fl">{{ cat2.name }} &gt;</div>
                                    <div class="group_detail fl">
                                        {#      三級查詢      #}
                                        {% for cat3 in cat2.sub_cats %}
                                            <a href="/list/{{ cat3.id }}/1/">{{ cat3.name }}</a>
                                        {% endfor %}
                                    </div>
                                </div>
                            {% endfor %}
                        </div>
                    </li>
                    {% endfor %}
                </ul>

			</div>
			<ul class="navlist fl">
				<li><a href="">首頁</a></li>
				<li class="interval">|</li>
				<li><a href="">真劃算</a></li>
				<li class="interval">|</li>
				<li><a href="">抽獎</a></li>
			</ul>
		</div>
	</div>

	 {#  面包屑标題展示:傳輸資料breadcrumb是由商品視圖檔案:apps/goods/views.py檔案傳輸過來  #}
    <div class="breadcrumb">
        {#   breadcrumb.cat1.name  一級标題     #}
		<a href="http://shouji.jd.com/">{{ breadcrumb.cat1.name }}</a>
        {% if breadcrumb.cat2.name %}
            <span>></span>
            <a href="javascript:;">{{ breadcrumb.cat2.name }}</a>
        {% endif %}
        {#   三級标題     #}
        {% if breadcrumb.cat3.name %}
            <span>></span>
            <a href="javascript:;">{{ breadcrumb.cat3.name }}</a>
        {% endif %}
	</div>

        <div class="goods_detail_con clearfix">
		<div class="goods_detail_pic fl"><img src="{{ sku.default_image_url.url }}"></div>
		<div class="goods_detail_list fr">
			<h3>{{ sku.name }}</h3>
			<p>{{ sku.caption }}</p>
			<div class="price_bar">
				<span class="show_pirce">¥<em>{{ sku.price }}</em></span>
				<a href="javascript:;" class="goods_judge">18人評價</a>
			</div>
			<div class="goods_num clearfix">
				<div class="num_name fl">數 量:</div>
				<div class="num_add fl">
					<input v-model="sku_count" @blur="check_sku_count" type="text" class="num_show fl">
                    <a @click="on_addition" class="add fr">+</a>
                    <a @click="on_minus" class="minus fr">-</a>
				</div> 
			</div>
			{% for spec in specs %}
				<div class="type_select">
					<label>{{ spec.name }}:</label>
					{% for option in spec.spec_options %}
						{% if option.sku_id == sku.id %}
						<a href="javascript:;" class="select">{{ option.value }}</a>
						{% elif option.sku_id %}
						<a href="{% url 'goods:detail' option.sku_id %}">{{ option.value }}</a>
						{% else %}
						<a href="javascript:;">{{ option.value }}</a>
						{% endif %}
					{% endfor %}
				</div>
        	{% endfor %}
			<div class="total" v-cloak>總價:<em>[[ sku_amount ]]元</em></div>
			<div class="operate_btn">
				<a href="javascript:;" class="add_cart" id="add_cart">加入購物車</a>				
			</div>
		</div>
	</div>
	<div class="main_wrap clearfix">
		<div class="l_wrap fl clearfix">
			<div class="new_goods">
				<h3>熱銷排行</h3>
				<ul>
            {#       熱銷産品的循環播放       vue的文法        #}
                    <li v-for="sku in hot_skus">
                        <a href="detail.html"><img :src="sku.default_image_url"></a>
						<h4><a href="detail.html">[[  sku.name ]]</a></h4>
						<div class="price">[[ sku.price ]]</div>
					</li>

				</ul>
			</div>
		</div>
		<div class="r_wrap fr clearfix">
			<ul class="detail_tab clearfix">
				<li @click="on_tab_content('detail')" :class="tab_content.detail?'active':''">商品詳情</li>
				<li @click="on_tab_content('pack')" :class="tab_content.pack?'active':''">規格與包裝</li>
				<li @click="on_tab_content('service')" :class="tab_content.service?'active':''">售後服務</li>
                <li @click="on_tab_content('comment')" :class="tab_content.comment?'active':''">商品評價(18)</li>
			</ul>
			<div @click="on_tab_content('detail')" class="tab_content" :class="tab_content.detail?'current':''">
				<dl>
					<dt>商品詳情:</dt>
					<dd>{{ sku.spu.desc_detail|safe }}</dd>
				</dl>
			</div>
			<div @click="on_tab_content('pack')" class="tab_content" :class="tab_content.pack?'current':''">
				<dl>
					<dt>規格與包裝:</dt>
					<dd>{{ sku.spu.desc_pack|safe }}</dd>
				</dl>
			</div>
			<div @click="on_tab_content('service')" class="tab_content" :class="tab_content.service?'current':''">
				<dl>
					<dt>售後服務:</dt>
					<dd>{{ sku.spu.desc_service|safe }}</dd>
				</dl>
			</div>
            <div @click="on_tab_content('comment')" class="tab_content" :class="tab_content.comment?'current':''">
				<ul class="judge_list_con">
					<li class="judge_list fl">
						<div class="user_info fl">
							<img src="{% static 'images/cat.jpg' %}">
							<b>張***廚</b>
						</div>
						<div class="judge_info fl">
							<div class="stars_five"></div>
							<div class="judge_detail">派送非常快,第二天上午就收到。2天使用初步總結,前一部手機也是華為P9plus.MATE10pro包裝原封未拆精緻大氣。拆開後第一眼就看到寶石藍的手機,非常驚豔;然後就是配件一應俱全。開機各方面設定,把通訊錄、短信等同步好,同品牌手機同步很快。和P9plus一樣的後置指紋識别很友善。錄制指紋容易,解鎖非常快,秒開!螢幕完好,預設分辨率顯示效果很好。</div>
						</div>
					</li>			
				</ul>
			</div>
		</div>
	</div>
	<div class="footer">
		<div class="foot_link">
			<a href="#">關于我們</a>
			<span>|</span>
			<a href="#">聯系我們</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情連結</a>		
		</div>
		<p>CopyRight © 2016 北京LG商業股份有限公司 All Rights Reserved</p>
		<p>電話:010-****888    京ICP備*******8号</p>
	</div>
	</div>
    <script type="text/javascript">
		let category_id = "{{ category_id }}";
		let sku_price = "{{ sku.price }}";
        let sku_id = "";
    </script>
	<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/detail.js' %}"></script>
</body>
</html>

           

項目執行個體代碼

商品視圖檔案:

apps/goods/views.py

"""
商品視圖檔案:apps/goods/views.py
"""
from django.shortcuts import render
from django.views import View
from contents.utils import get_categories         # 導入封裝方法
from .utils import get_breadcrumb                 # 導入面包屑封裝方法
from .models import GoodsCategory, SKU            # 導入模型
from django.views.generic import ListView          # 分頁
from django.core.paginator import Paginator        # 分頁
from django import http
from utils.response_code import RETCODE


# 商品詳情類
class DetailGoodsView(View):
    """商品詳情"""
    def get(self, request, sku_id):
        try:
            sku = SKU.objects.get(id=sku_id)          # 異常處理,sku_id擷取的值異常時候
        except Exception as e:
            return render(request, '404.html')
        
        # 查詢商品類别
        categories = get_categories()
        
        # 查詢面包屑
        breadcrumb = get_breadcrumb(sku.category)

        # 建構目前商品的規格鍵
        sku_specs = sku.specs.order_by('spec_id')      # specs外鍵,根據spec_id排序,從sku_specification表中查詢
        # print(sku_specs)
        sku_key = []
        for spec in sku_specs:
            sku_key.append(spec.option.id)             # 添加預設選項
            
        # 擷取目前商品的所有SKU
        skus = sku.spu.sku_set.all()                   # 查詢tb_sku表
        # print(skus)
        
        # 建構不同規格參數(選項)的sku字典
        spec_sku_map = {}
        for s in skus:
            # 擷取sku的規格參數
            s_specs = s.specs.order_by('spec_id')      # 查詢的是tb_sku_specification表中資料
            print(s_specs)
            
            # ⽤于形成規格參數-sku字典的鍵
            key = []
            for spec in s_specs:
                key.append(spec.option.id)              # 添加option.id
            
            # 向規格參數-sku字典添加記錄
            spec_sku_map[tuple(key)] = s.id             # key為字典,value值為s.id     {(1,4,7):1, (1,3,7):2}
            
        # 擷取目前商品的規格資訊
        goods_specs = sku.spu.specs.order_by('id') # spu為外鍵,specs是商品SPU規格模型中的related_name字段,查詢tb_spu_specification表中資料
        
        # 若目前sku的規格資訊不完整,則不再繼續
        if len(sku_key) < len(goods_specs):
            return
        
        for index, spec in enumerate(goods_specs):       # enumerate()枚舉    index是索引值,從0開始,spec是商品SPU規格内容
            # 複制目前sku的規格鍵
            key = sku_key[:]
            # 該規格的選項
            spec_options = spec.options.all()            # 查詢的是tb_specification_option表中的資料
            
            for option in spec_options:
                # 在規格參數sku字典中查找符合目前規格的sku
                key[index] = option.id
                option.sku_id = spec_sku_map.get(tuple(key))        # spec_sku_map是上面拼接的字典,字典根據元祖key取值,将值添加到屬性sku_id
            spec.spec_options = spec_options            # 添加屬性,spec是對象
        
        # 傳遞資料
        context = {
            'categories': categories,
            'breadcrumb': breadcrumb,
            'sku': sku,
            'category_id': sku.category.id,
            'goods_specs': goods_specs,             # 商品規格
        }
        return render(request, 'detail.html', context=context)
    

# 熱銷商品視圖
class HotGoodsView(View):
    """熱銷排行"""
    def get(self, request, category_id):
        """傳回兩條熱銷商品"""
        skus = SKU.objects.filter(category_id=category_id, is_launched=True).order_by('-sales')[:2]  # 根據sales的逆序排序,并取前兩個資料
        
        # 拼接資料,序列化
        hot_skus = []
        for sku in skus:
            sku_dict = {
                "id": sku.id,
                "default_image_url": sku.default_image_url.url,
                "name": sku.name,
                "price": sku.price,
            }
            hot_skus.append(sku_dict)          # 資料添加
        # print(hot_skus)
        """ 拼接資料之後的形式
        {
            "code":"0",
            "errmsg":"OK",
            "hot_skus":[
                {
                    "id":6,
                    "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRbI2ARekNAAFZsBqChgk3141998",
                    "name":"Apple iPhone 8 Plus (A1864) 256GB 深空灰色 移動聯通電信4G手機",
                    "price":"7988.00"
                },
                {
                    "id":14,
                    "default_image_url":"http://image.meiduo.site:8888/group1/M00/00/02/CtM3BVrRdMSAaDUtAAVslh9vkK04466364",
                    "name":"華為 HUAWEI P10 Plus 6GB+128GB 玫瑰金 移動聯通電信4G手機 雙卡雙待",
                    "price":"3788.00"
                }
            ]
        }
        """
        return http.JsonResponse({'code': RETCODE.OK, 'errmsg': 'ok', 'hot_skus': hot_skus})


class GoodsListView(View):
    """商品清單頁面"""
    def get(self, request, category_id, page_num):
        """查詢并渲染商品"""
        # category_id 是商品分類的id
        
        try:
            # 查詢面包屑
            category = GoodsCategory.objects.get(id=category_id)
        except Exception as e:
            return render(request, '404.html')
        
        # 擷取排序規則  接收sort參數:如果使用者不傳,就是預設的排序規則
        sort = request.GET.get('sort', 'default')     # 根據字段sort查詢
        if sort == 'price':                # 按照排序規則查詢該分類商品SKU資訊
            sort_field = 'price'           # 根據字段price的正序排列,價格由低到高
        elif sort == 'hot':
            sort_field = '-sales'          # 根據字段sales的倒序排列,銷量由高到低
        else:                              # 當傳輸的排序方式不是上面的兩種,需要讓排序方式為預設的
            sort = 'default'
            sort_field = 'create_time'     # 根據create_time字段排序
            
        # 排序      根據category.id篩選,以sort_field排序
        skus = SKU.objects.filter(is_launched=True, category_id=category.id).order_by(sort_field)
        # print(skus)
        
        # 分頁  Paginator('分頁面的記錄', '每頁記錄的條數')  建立分頁器
        paginator = Paginator(skus, 5)
        # 擷取page_num的頁碼的資料
        page_skus = paginator.page(page_num)
        
        # 總的頁面數
        tatal_page = paginator.num_pages
        
        # 查詢商品的類别
        categories = get_categories()
        
        breadcrumb = get_breadcrumb(category)                     # 調用封裝的方法
        # print(breadcrumb)
        
        context = {
            "categories": categories,       # 頻道分類
            "breadcrumb": breadcrumb,       # 面包屑導航
            "page_skus": page_skus,         # 分頁後資料
            "tatal_page": tatal_page,       # 總頁數
            "category_id": category_id,     # 商品類别id
            "sort": sort,                   # 排序字段,以何種方式排序
            "page_num": page_num,           # 目前頁碼
            
        }
        
        return render(request, 'list.html', context=context)        # 傳遞資料到前端界面list.html

           

商品詳情頁面檔案:

templates/detail.html

{#   商品詳情頁面檔案:templates/detail.html        #}
{% load static %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
	<title>LG商城-商品詳情</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/reset.css' %}">
	<link rel="stylesheet" type="text/css" href="{% static 'css/main.css' %}">
	<script type="text/javascript" src="{% static 'js/vue-2.5.16.js' %}"></script>
    <script type="text/javascript" src="{% static 'js/axios-0.18.0.min.js' %}"></script>
</head>
<body>
	<div id="app">
	<div class="header_con">
		<div class="header" v-cloak>
			<div class="welcome fl">歡迎來到LG商城!</div>
			<div class="fr">
                <div v-if="username" class="login_btn fl">
                    歡迎您:<em>[[ username ]]</em>
                    <span>|</span>
                    <a href="{{ url('users:logout' %}">退出</a>
                </div>
                <div v-else class="login_btn fl">
                    <a href="{{ url('users:login' %}">登入</a>
                    <span>|</span>
                    <a href="{{ url('users:register' %}">注冊</a>
                </div>
				<div class="user_link fl">
					<span>|</span>
					<a href="{{ url('users:info' %}">使用者中心</a>
					<span>|</span>
					<a href="cart.html">我的購物車</a>
					<span>|</span>
					<a href="user_center_order.html">我的訂單</a>
				</div>
			</div>
		</div>		
	</div>
	<div class="search_bar clearfix">
		<a href="{{ url('contents:index' %}" class="logo fl"><img src="{% static 'images/logo.png' %}"></a>
		<div class="search_wrap fl">
			<form method="get" action="/search/" class="search_con">
                <input type="text" class="input_text fl" name="q" placeholder="搜尋商品">
                <input type="submit" class="input_btn fr" name="" value="搜尋">
            </form>
			<ul class="search_suggest fl">
				<li><a href="#">索尼微單</a></li>
				<li><a href="#">優惠15元</a></li>
				<li><a href="#">美妝個護</a></li>
				<li><a href="#">買2免1</a></li>
			</ul>
		</div>
		<div class="guest_cart fr">
			<a href="cart.html" class="cart_name fl">我的購物車</a>
			<div class="goods_count fl" id="show_count">2</div>
			<ul class="cart_goods_show">
				<li>
					<img src="{% static 'images/goods/goods001.jpg' %}" alt="商品圖檔">
					<h4>華為 HUAWEI P10 Plus 6GB+64GB 鑽雕金 移動聯通電信4G手機 雙卡雙待</h4>
					<div>1</div>
				</li>
				<li>
					<img src="{% static 'images/goods/goods002.jpg' %}" alt="商品圖檔">
					<h4>Apple iPhoneX 64GB 深空灰色 移動聯通電信4G手機</h4>
					<div>1</div>
				</li>
			</ul>			
		</div>
	</div>
	<div class="navbar_con">
		<div class="navbar">
			<div class="sub_menu_con fl">
				<h1 class="fl">商品分類</h1>
                    {#  apps/goods/views.py檔案中傳遞的categories資料進行渲染	#}
                    <ul class="sub_menu">
                        {% for group in categories.values %}
                        <li>
                            {#      一級查詢      #}
                            <div class="level1">
                                {% for channel in group.channels %}
                                    <a href="{{ channel.url }}">{{ channel.name }}</a>
                                {% endfor %}
                            </div>
                            <div class="level2">
                                {#      二級查詢      #}
                                {% for cat2 in group.sub_cats %}
                                    <div class="list_group">
                                        <div class="group_name fl">{{ cat2.name }} &gt;</div>
                                        <div class="group_detail fl">
                                            {#      三級查詢      #}
                                            {% for cat3 in cat2.sub_cats %}
                                                <a href="/list/{{ cat3.id }}/1/">{{ cat3.name }}</a>
                                            {% endfor %}
                                        </div>
                                    </div>
                                {% endfor %}
                            </div>
                        </li>
                    {% endfor %}
                    </ul>

			</div>
			<ul class="navlist fl">
				<li><a href="">首頁</a></li>
				<li class="interval">|</li>
				<li><a href="">真劃算</a></li>
				<li class="interval">|</li>
				<li><a href="">抽獎</a></li>
			</ul>
		</div>
	</div>

	 {#  面包屑标題展示:傳輸資料breadcrumb是由商品視圖檔案:apps/goods/views.py檔案傳輸過來  #}
    <div class="breadcrumb">
        {#   breadcrumb.cat1.name  一級标題     #}
		<a href="http://shouji.jd.com/">{{ breadcrumb.cat1.name }}</a>
        {% if breadcrumb.cat2.name %}
            <span>></span>
            <a href="javascript:;">{{ breadcrumb.cat2.name }}</a>
        {% endif %}
        {#   三級标題     #}
        {% if breadcrumb.cat3.name %}
            <span>></span>
            <a href="javascript:;">{{ breadcrumb.cat3.name }}</a>
        {% endif %}
	</div>

        <div class="goods_detail_con clearfix">
		<div class="goods_detail_pic fl"><img src="{{ sku.default_image_url.url }}"></div>
		<div class="goods_detail_list fr">
			<h3>{{ sku.name }}</h3>
			<p>{{ sku.caption }}</p>

            <div class="price_bar">
				<span class="show_pirce">¥<em>{{ sku.price }}</em></span>
				<a href="javascript:;" class="goods_judge">18人評價</a>
			</div>

            <div class="goods_num clearfix">
				<div class="num_name fl">數 量:</div>
				<div class="num_add fl">
					<input v-model="sku_count" @blur="check_sku_count" type="text" class="num_show fl">
                    <a @click="on_addition" class="add fr">+</a>
                    <a @click="on_minus" class="minus fr">-</a>
				</div> 
			</div>

            {% for spec in goods_specs %}
				<div class="type_select">
					<label>{{ spec.name }}:</label>
					{% for option in spec.spec_options %}
						{% if option.sku_id == sku.id %}
						<a href="javascript:;" class="select">{{ option.value }}</a>
						{% elif option.sku_id %}
						<a href="{% url 'goods:detail' option.sku_id %}">{{ option.value }}</a>
						{% else %}
						<a href="javascript:;">{{ option.value }}</a>
						{% endif %}
					{% endfor %}
				</div>
        	{% endfor %}

            {#  sku_amount是detail.js檔案中計算的價格,傳輸過來的 ;v-cloak是為了解決頁面加載顯示順序的bug    #}
			<div class="total" v-cloak>總價:<em>[[ sku_amount ]]元</em></div>
			<div class="operate_btn">
				<a href="javascript:;" class="add_cart" id="add_cart">加入購物車</a>				
			</div>
		</div>
	</div>
	<div class="main_wrap clearfix">
		<div class="l_wrap fl clearfix">
			<div class="new_goods">
				<h3>熱銷排行</h3>
				<ul>
            {#       熱銷産品的循環播放       vue的文法        #}
                    <li v-for="sku in hot_skus">
                        <a href="detail.html"><img :src="sku.default_image_url"></a>
						<h4><a href="detail.html">[[  sku.name ]]</a></h4>
						<div class="price">[[ sku.price ]]</div>
					</li>

				</ul>
			</div>
		</div>
		<div class="r_wrap fr clearfix">
			<ul class="detail_tab clearfix">
				<li @click="on_tab_content('detail')" :class="tab_content.detail?'active':''">商品詳情</li>
				<li @click="on_tab_content('pack')" :class="tab_content.pack?'active':''">規格與包裝</li>
				<li @click="on_tab_content('service')" :class="tab_content.service?'active':''">售後服務</li>
                <li @click="on_tab_content('comment')" :class="tab_content.comment?'active':''">商品評價(18)</li>
			</ul>
			<div @click="on_tab_content('detail')" class="tab_content" :class="tab_content.detail?'current':''">
				<dl>
					<dt>商品詳情:</dt>
                    {#     desc_detail 、desc_pack、desc_service是apps/goods/models.py 檔案中的SPU模型字段内容    #}
					<dd>{{ sku.spu.desc_detail|safe }}</dd>
				</dl>
			</div>
			<div @click="on_tab_content('pack')" class="tab_content" :class="tab_content.pack?'current':''">
				<dl>
					<dt>規格與包裝:</dt>
					<dd>{{ sku.spu.desc_pack|safe }}</dd>
				</dl>
			</div>
			<div @click="on_tab_content('service')" class="tab_content" :class="tab_content.service?'current':''">
				<dl>
					<dt>售後服務:</dt>
					<dd>{{ sku.spu.desc_service|safe }}</dd>
				</dl>
			</div>
            <div @click="on_tab_content('comment')" class="tab_content" :class="tab_content.comment?'current':''">
				<ul class="judge_list_con">
					<li class="judge_list fl">
						<div class="user_info fl">
							<img src="{% static 'images/cat.jpg' %}">
							<b>張***廚</b>
						</div>
						<div class="judge_info fl">
							<div class="stars_five"></div>
							<div class="judge_detail">派送非常快,第二天上午就收到。2天使用初步總結,前一部手機也是華為P9plus.MATE10pro包裝原封未拆精緻大氣。拆開後第一眼就看到寶石藍的手機,非常驚豔;然後就是配件一應俱全。開機各方面設定,把通訊錄、短信等同步好,同品牌手機同步很快。和P9plus一樣的後置指紋識别很友善。錄制指紋容易,解鎖非常快,秒開!螢幕完好,預設分辨率顯示效果很好。</div>
						</div>
					</li>			
				</ul>
			</div>
		</div>
	</div>
	<div class="footer">
		<div class="foot_link">
			<a href="#">關于我們</a>
			<span>|</span>
			<a href="#">聯系我們</a>
			<span>|</span>
			<a href="#">招聘人才</a>
			<span>|</span>
			<a href="#">友情連結</a>		
		</div>
		<p>CopyRight © 2016 北京LG商業股份有限公司 All Rights Reserved</p>
		<p>電話:010-****888    京ICP備*******8号</p>
	</div>
	</div>
    <script type="text/javascript">
		let category_id = "{{ category_id }}";
		let sku_price = "{{ sku.price }}";
        let sku_id = "";
    </script>
	<script type="text/javascript" src="{% static 'js/common.js' %}"></script>
	<script type="text/javascript" src="{% static 'js/detail.js' %}"></script>
</body>
</html>