Django项目实践(商城):十五、商品列表页面

在这里插入图片描述

(根据居然老师直播课内容整理)

一、商品列表页面分析

在这里插入图片描述

1、商品频道分类

-已经在“首页广告”"首页商品频道分类"中实现,将相关代码封装到contents.utils.py文件中,直接调用即可

2、面包屑导航

  • 可以使用三级分类ID,查询出该类型商品的三级分类数据。

3、排序、商品展示、分页

  • 无论如何排序和分页,商品的分类不能变。
  • 排序时需要知道当前排序方式。
  • 分页时需要知道当前分页的页码,且每页五条商品记录。
  • 展示的商品都是属于该商品分类下的商品

4、热销排行

  • 热销排行中的商品分类要和排序、分页的商品分类一致。
  • 热销排行是查询出指定分类商品销量前二的商品。
  • 热销排行使用Ajax实现局部刷新的效果。
  • 热销排行会实时变化的,一般会放在redis中,提高读取效率

二、商品列表页显示

  • 在首页选中某商种类商品后,就会跳转到该页面

1、商品列表页接口设计和定义

1.1 请求方式

选项 方案
请求方法 GET
请求地址 /list/(?P<category_id>\d+)/(?P<page_num>\d+)/?sort=排序方式
# 按照商品创建时间排序
http://www.meiduo.site:8000/list/115/1/?sort=default
# 按照商品价格由低到高排序
http://www.meiduo.site:8000/list/115/1/?sort=price
# 按照商品销量由高到低排序
http://www.meiduo.site:8000/list/115/1/?sort=hot
# 用户随意传排序
http://www.meiduo.site:8000/list/115/1/?sort=juran

1.2 请求参数 : 路径参数 和 查询参数

参数名 类型 是否必传 说明
category_id string 商品分类ID,第三级分类
page_num string 当前页码
sort string 排序方式

1.3 响应结果 : HTML

2、后端实现

2.1 后端view实现

# apps/goods/views.py
class GoodsListView(View):
    """商量列表页面"""
    def get(self,request,category_id,page_num):
		"""提供商品列表页"""
        return render(request, 'list.html')

2.2 接口定义

2.2.1 子路由:

# apps/goods/urls.py 
from django.urls import path,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')
]

2.2.2 总路由:

  • lgshop/urls.spy 在这里插入图片描述

3、页面访问: http://127.0.0.1:8000/115/1/?sort=default

在这里插入图片描述

三、商品频道分类封装

# apps/contents/utils.py 
from collections import OrderedDict
from goods.models import GoodsCategory, GoodsChannel

def  get_categories():
    # 查询并展示商品分类

    categories = OrderedDict()
    # 查询所有的商品频道
    channels = GoodsChannel.objects.order_by('group_id', 'sequence')

    for channel in channels:
        # 37个频道 11个组
        group_id = channel.group_id
        # print(group_id)
        if group_id not in categories:
            categories[group_id] = {
    
    'channels': [], 'sub_cats': []}

        # print(categories)
        cat1 = channel.category
        categories[group_id]['channels'].append(
            {
    
    
                "id": cat1.id,
                "name": cat1.name,
                "url": channel.url
            }
        )
        # print(categories)  # 打印结果一级分类完成
        # 查询二级和三级类别
        # 查询二级 parent_id = cat1.id
        # for cat2 in cat1.subs.all():  此行简写代码可替换下面一行代码  models.py中定义了related_name=subs
        for cat2 in GoodsCategory.objects.filter(parent_id=cat1.id).all():
            cat2.sub_cats = []
            categories[group_id]["sub_cats"].append(
                {
    
    
                    "id": cat2.id,
                    "name": cat2.name,
                    "sub_cats": cat2.sub_cats
                }
            )

            # for cat3 in cat2.subs.all():
            for cat3 in GoodsCategory.objects.filter(parent_id=cat2.id).all():
                cat2.sub_cats.append(
                    {
    
    
                        "id": cat3.id,
                        "name": cat3.name,
                    }
                )

    return categories
  • 原apps.contents.views.py将改成如下:
from django.shortcuts import render
from django.views import View
from .models import ContentCategory,Content
from .utils import get_categories


class IndexView(View):
    """商城首页"""

    def get(self,request):
        """提供首页页面"""

        # 查询并展示商品分类
        categories=get_categories()

        # 查询所有的首页广告

        # 查询所有广告类别
        context_categories = ContentCategory.objects.all()
        contents = {
    
    }

        for context_category in context_categories:
            # temp1 = Content.objects.filter(category_id=context_category.id, status=True).all().order_by("sequence")
            # print(type(temp1))
            contents[context_category.key]=Content.objects.filter(category_id=context_category.id,status=True).all().order_by("sequence")

        # print(contents)

        context = {
    
    
            "categories": categories,
            'contents': contents,
        }
        return render(request, 'index.html', context=context)

四、查询列表页面包屑导航

  • 重要提示:路径参数category_id是商品第三级分类

1、后端实现

  • 此功能相对独立,建议独立到utils.py中,定义get_breadcrumb()函数,商品类别对象category为参数
  • 定义一个字典breadcrumb:分别存放三级类别导航对象
  • 判断类别对象的parent是否为空,即是否是一级类别
    • 如果是一级类别,将对象category赋值给breadcrumb的一级类别,二、三两级都空着
  • 判断类别表中parent是否存在category.id(三级类别不会存在parent中)
    • 如果是三级类别,将对象category赋值给breadcrumb的三级类别
    • 将category.parent赋值组breadcrumb的二级类别
    • 将category.parent.parent赋值组breadcrumb的一级类别
  • 剩下的情况,都是二级类别
    • 将对象category赋值给breadcrumb的二级类别
    • 将category.parent赋值组breadcrumb的一级类别
  • 返回字典对象
def get_breadcrumb(category):
    """
        获取面包屑导航
        :param category: 商品类别
        :return: 面包屑导航字典
    """
    breadcrumb = dict(
        cat1='',
        cat2='',
        cat3=''
    )
    if category.parent is None:
        # 当前类别为一级类别
        breadcrumb['cat1'] = category
    elif category.subs.count() == 0:      # 查看外键使用方法
        # 当前类别为三级
        breadcrumb['cat3'] = category
        cat2 = category.parent
        breadcrumb['cat2'] = cat2
        breadcrumb['cat1'] = cat2.parent
    else:
        # 当前类别为二级
        breadcrumb['cat2'] = category
        breadcrumb['cat1'] = category.parent
    return breadcrumb

2、前端实现

  • 面包屑数据传回前端是一个字典,数据有限,无需循环,直接用key 取valu分别取出三级内容即可
  • 如果二级或三级不存在,前面的">"不需要显示,故 if 判断一下名称是否存在即可
<div class="breadcrumb">
		<a href="{
     
     { breadcrumb.cat1.url }}">{
   
   { 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>

五、商品排序

  • 可设置多种方式显示排序方式,

1、后端实现

  • 读取参数
  • 判断排序字段
  • 查询SKU表获取显示数据
        sort= request.GET.get("sort","default")
        if sort=="price":
            sort_field="price"
        elif sort=="hot":
            sort_field = "sales"
        else:
            sort_field = "create_time"

        skus=SKU.objects.filter(is_launched=True,category_id=category.id).order_by(sort_field)

2、前端展示

  • 通过对参数sort的值比较,相等时,添加 class="active"达到突出显示当前排序选择的情况
  • url设置: app名称:路由 ,args=(路由参数列表)?其它参数
	<div class="sort_bar">
        <a href="{% url 'goods:list'  category_id=category_id page_num=page_num %}?sort=default" {% if sort == 'default' %}class="active"{% endif %}>默认</a>
        <a href="{% url 'goods:list' category_id page_num  %}?sort=price" {% if sort == 'price' %}class="active"{% endif %}>价格</a>
        <a href="{% url 'goods:list' category_id page_num  %}?sort=hot" {% if sort == 'hot' %}class="active"{% endif %}>人气</a>
    </div>

六、商品展示及分页

1、后端实现

        # Paginator('要分页的数据', '每页记录的条数')
        pageinator = Paginator(skus, PER_PAGE_SKU)  # 把skus进行分页 ,每页5条记录

        try:
            # 获取用户当前要看的那一页数据
            page_skus = pageinator.page(page_num)
        except Exception as e:
            return http.HttpResponseNotFound('Empty Page')

        # print(page_skus)
        # 总页数
        total_page = pageinator.num_pages

2、前端实现

2.1 商品展示

  • 用for 循环将page+skus数据展示出来
	<!-- 展示数据-->
    <ul class="goods_type_list clearfix">
        {% for sku in page_skus %}
        <li>
        <a href="detail.html"><img src="{
     
     { sku.default_image.url }}"></a>
        <h4><a href="detail.html">{
   
   { sku.name }}</a></h4>
        <div class="operate">
            <span class="price">¥{
   
   { sku.price }}</span>
            <span class="unit"></span>
            <a href="#" class="add_goods" title="加入购物车"></a>
        </div>
        </li>
        {% endfor %}
    </ul>
	<!-- 分页器标签 -->

2.2 分页器

  • 导入分页CSS:建议在head中导入
	# 导入样式时放在最前面导入
	<link rel="stylesheet" type="text/css" href="{% static 'css/jquery.pagination.css' %}">
  • 导入分页器
    <div class="pagenation">
        <div id="pagination" class="page"></div>
    </div>
  • 分页器交互
<script type="text/javascript" src="{% static 'js/jquery.pagination.min.js' %}"></script>
<script type="text/javascript">
    $(function () {
     
     
        $('#pagination').pagination({
     
     
            currentPage: {
     
     {
     
      page_num }},	<!-- 当前页-->
            totalPage: {
     
     {
     
      total_page }},	<!-- 总页数-->
            callback:function (current) {
     
     
                {
     
     #location.href = '/list/115/1/?sort=default';#}
                location.href = '/list/{
     
     { category.id }}/' + current + '/?sort={
     
     { sort }}';  <!-- 跳转链接 -->
            }
        })
    });
</script>

七、商品列表页完善

class GoodsListView(View):
    """商品列表页"""
    def get(self, request, category_id, page_num):
        """提供商品列表页"""
        # 判断category_id是否正确
        print("category_id=",category_id)
        try:
            category = GoodsCategory.objects.get(id=category_id)
        except GoodsCategory.DoesNotExist:
            return http.HttpResponseNotFound('GoodsCategory does not exist')

        sort= request.GET.get("sort","default")
        if sort=="price":
            sort_field="price"
        elif sort=="hot":
            sort_field = "sales"
        else:
            sort_field = "create_time"

        skus=SKU.objects.filter(is_launched=True,category_id=category.id).order_by(sort_field)
        # print(skus)

        # Paginator('要分页的数据', '每页记录的条数')
        pageinator = Paginator(skus, PER_PAGE_SKU)  # 把skus进行分页 ,每页5条记录

        try:
            # 获取用户当前要看的那一页数据
            page_skus = pageinator.page(page_num)
        except Exception as e:
            return http.HttpResponseNotFound('Empty Page')

        # print(page_skus)
        # 总页数
        total_page = pageinator.num_pages

        # 查询商品频道分类
        categories = get_categories()
        # 查询面包屑导航
        breadcrumb = get_breadcrumb(category)
        # 渲染页面
        context = {
    
    
            'categories':categories,        # 商品频道分类
            'breadcrumb':breadcrumb,        # 面包屑导航
            'page_skus': page_skus,
            'total_page': total_page,
            'sort': sort,
            'category_id': category_id,
            'page_num': page_num
        }
        return render(request, 'list.html', context)


八、热销排行

  • 采用ajax方式获取数据
  • 当前商品种类的热销商品,最多两条,按销量排序
  • 热销商品必须是上架商品
  • 热销商品是属于频繁访问数据,建议放到redis中

1、接口设计和定义

1.1 请求方式

选项 方案
请求方法 GET
请求地址 /hot/(?P<category_id>\d+)/

1.2 请求参数 : 路径参数

参数名 类型 是否必传 说明
category_id string 商品分类ID,第三级分类

1.3 响应结果 : JSON

响应结果 响应内容
code 状态码
errmsg 错误信息
hot_skus [{id:SKU.id,name:名称,price:单价,default_image_url:默认图片},]
{
    
    
    "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"
        }
    ]
}

2、后端实现

  • 检查redis中是否存在热销商品
  • 如果不存在
    • 根据category_id、is_launched=True查询SKU数据,按销量sales倒序,取2条
    • 循环从查询结果集中取出数据
    • 按字典格式,将每一条SKU数据生成一个字典,添加到返回列表中
    • 将生成的列表保存到redis中
  • 按要求向前前端返回数据
class HotGoodsView(View):
    """热销排行"""

    def get(self, request, category_id):
        # 检查cache中是否有此商品热销数据
        str_key="hotgood_"+category_id
        # print("category_id=",category_id,"str_key=",str_key)
        hot_skus = cache.get(str_key)

        # print("hot_skus=",hot_skus)
        if not hot_skus:
            hot_skus=[]

            print("-"*100)

            # 查询指定分类(category_id), 必须是上架商品 按照销量从高到底排序 取前2位
            try:
                skus = SKU.objects.filter(category_id=category_id, is_launched=True).order_by('-sales')[:2]
            except SKU.DoesNotExist:
                return http.JsonResponse({
    
    'code': RETCODE.NODATAERR, 'errmsg': '无该类别商品数据'})
            # 模型转字典列表

            for sku in skus:
                sku_dict = {
    
    
                    'id': sku.id,
                    'name': sku.name,
                    'price': sku.price,
                    'default_image_url': sku.default_image_url.url
                }
                hot_skus.append(sku_dict)

            cache.set(str_key,hot_skus, HOTGOODS_EXPIRES)


        return http.JsonResponse({
    
    'code': RETCODE.OK, 'errmsg': 'OK', 'hot_skus': hot_skus})

3、前端实现

3.1 模板数据category_id传递到Vue.js

<script type="text/javascript">
    let category_id = {
    
    {
    
     category.id }};
</script>
data: {
    
    
	......
    category_id: category_id,
},

3.2 Ajax请求商品热销排行JSON数据

    mounted(){
    
    
        // 获取热销商品数据
        this.get_hot_skus();
    },
    methods: {
    
    
    	// 获取热销商品数据
        get_hot_skus(){
    
    
             // alert("category_id=",this.category_id)
            if (this.category_id) {
    
    
                let url = '/hot/'+ this.category_id +'/';
                 alert("category_id=",this.category_id," |  url=",url)
                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);
                    })
            }
        },

3.3 渲染商品热销排行界面

			<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>

猜你喜欢

转载自blog.csdn.net/laoluobo76/article/details/115195619
今日推荐