(根据居然老师直播课内容整理)
一、商品列表页面分析
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实现
class GoodsListView(View):
"""商量列表页面"""
def get(self,request,category_id,page_num):
"""提供商品列表页"""
return render(request, 'list.html')
2.2 接口定义
2.2.1 子路由:
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
三、商品频道分类封装
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:
group_id = channel.group_id
if group_id not in categories:
categories[group_id] = {
'channels': [], 'sub_cats': []}
cat1 = channel.category
categories[group_id]['channels'].append(
{
"id": cat1.id,
"name": cat1.name,
"url": channel.url
}
)
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 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:
contents[context_category.key]=Content.objects.filter(category_id=context_category.id,status=True).all().order_by("sequence")
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、后端实现
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、后端实现
pageinator = Paginator(skus, PER_PAGE_SKU)
try:
page_skus = pageinator.page(page_num)
except Exception as e:
return http.HttpResponseNotFound('Empty Page')
total_page = pageinator.num_pages
2、前端实现
2.1 商品展示
<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 分页器
# 导入样式时放在最前面导入
<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):
"""提供商品列表页"""
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)
pageinator = Paginator(skus, PER_PAGE_SKU)
try:
page_skus = pageinator.page(page_num)
except Exception as e:
return http.HttpResponseNotFound('Empty Page')
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):
str_key="hotgood_"+category_id
hot_skus = cache.get(str_key)
if not hot_skus:
hot_skus=[]
print("-"*100)
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(){
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>