Django项目-商品详情页

在这里插入图片描述

商品详情页分析和准备
1.商品分类
2.面包屑
3.热销排行
4.商品名字、价格、数量、规格(颜色,内存,) 总价(和数量有关系)
5.商品详情 规格与包装 售后服务
6. 商品评价(完成下单后)

商品详情页组成结构分析
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.请求方式
选项 方案
请求方法 GET
请求地址 /detail/(?P<sku_id>\d+)/

2.请求参数:路径参数
参数名 类型 是否必传 说明
sku_id string 是 商品SKU编号

3.响应结果:HTML
detail.html

4.接口定义

商品详情页
goods/views.py

class DetailDoodsView(View):
    """商品详情页"""

    def get(self, request, sku_id):
        # 验证
        try:
            sku = SKU.objects.get(id=sku_id)
        except Exception as e:
            # return http.HttpResponseForbidden('参数sku_id不存在')
            return render(request, '404.html')

        # 查询商品分类
        categories = get_categories()
        # 查询面包屑
        breadcrumb = get_breadcrumb(sku.category)

        # 以下代码为整个项目最难理解的内容
        # 构建当前商品的规格键
        sku_specs = SKUSpecification.objects.filter(sku__id=sku_id).order_by('spec_id')
        sku_key = []
        for spec in sku_specs:
            sku_key.append(spec.option.id)
        # [1, 4, 7]   [8, 11]
        # print(sku_key)

        # 获取当前商品的所有SKU
        spu_id = sku.spu_id
        skus = SKU.objects.filter(spu_id=spu_id)

        # 构建不同规格参数(选项)的sku字典
        spec_sku_map = {
    
    }
        for s in skus:
            # 获取sku的规格参数
            s_specs = s.specs.order_by('spec_id')
            # print(s_specs.query)
            # 用于形成规格参数-sku字典的键
            key = []
            for spec in s_specs:
                key.append(spec.option.id)
            # 向规格参数-sku字典添加记录
            spec_sku_map[tuple(key)] = s.id
        # print(spec_sku_map)

        # 获取当前商品的规格信息
        goods_specs = SPUSpecification.objects.filter(spu_id=spu_id).order_by('id')
        # print(goods_specs)
        # 若当前sku的规格信息不完整,则不再继续
        # if len(sku_key) < len(goods_specs):
        #     return
        for index, spec in enumerate(goods_specs):
            # print(index, spec)
            # 复制当前sku的规格键
            key = sku_key[:]
            # 该规格的选项
            spec_options = spec.options.all()
            for option in spec_options:
                # 在规格参数sku字典中查找符合当前规格的sku
                key[index] = option.id
                option.sku_id = spec_sku_map.get(tuple(key))
            spec.spec_options = spec_options


        context = {
    
    
            "sku": sku,
            "categories": categories,
            "breadcrumb": breadcrumb,
            "specs": goods_specs
        }
        return render(request, 'detail.html', context=context)

detail.html
渲染SKU详情信息

<div class="goods_detail_con clearfix">
    <div class="goods_detail_pic fl"><img src="{
    
    { sku.default_image.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>
        {
    
    #...商品规格...#}
        <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>

为了实现用户选择商品数量的局部刷新效果,我们将商品单价从模板传入到Vue.js

<script type="text/javascript">
		let category_id = {
    
    {
    
     sku.category.id }};
		let sku_price = {
    
    {
    
     sku.price }};
        let sku_id = {
    
    {
    
     sku.id }};
    </script>

统计分类商品的访问量
goodes/views.py

class DetailvisitView(View):
    """统计分类商品的访问量"""

    def post(self, request, category_id):
    	
        # 校验参数
        try:
            category = GoodsCategory.objects.get(id=category_id)
        except Exception as e:
            return http.HttpResponseForbidden('参数category_id不存在')

        t = timezone.localtime()
        # print(t)    # 2021-03-28 14:43:34.388257+08:00
        # 获取当前的时间字符串
        today_str = '%d-%02d-%02d' % (t.year, t.month, t.day)
        # print(today_str)
        
		# 保存
        # 需要在models.py中创建一张与访问量有关系的表
        # 分析字段应该包含:count(访问量)  category_id(商品) time (某一天)  user(可选)
        try:
            # 存在记录 修改记录 count
            counts_data = GoodsVisitCount.objects.get(date=today_str, category=category.id)
        except GoodsVisitCount.DoesNotExist:
            # 不存在记录 新增
            counts_data = GoodsVisitCount()
        try:
            counts_data.category = category
            counts_data.count += 1
            counts_data.date = today_str
            counts_data.save()
        except Exception as e:
            return http.HttpResponseServerError('统计失败')

        # 返回结果
        return http.JsonResponse({
    
    'code': RETCODE.OK, 'errmsg': 'OK'})
        

models.py

class GoodsVisitCount(BaseModel):
    """统计分类商品访问量模型类"""
    category = models.ForeignKey(GoodsCategory, on_delete=models.CASCADE, verbose_name='商品分类')
    count = models.IntegerField(verbose_name='访问量', default=0)
    date = models.DateField(auto_now_add=True, verbose_name='统计日期')

    class Meta:
        db_table = 'tb_goods_visit'
        verbose_name = '统计分类商品访问量'
        verbose_name_plural = verbose_name

浏览记录 browse_histories/

当登录用户在浏览商品的详情页时,可以把详情页这件商品信息存储起来,作为该登录用户的浏览记录

存储数据说明:
浏览记录界面上要展示商品的一些SKU信息,但是在存储时没有必要存很多SKU信息。
选择存储SKU的唯一编号(sku_id)来表示该件商品浏览记录
存储数据:sku_id

存储位置说明
用户浏览记录时临时数据,且经常变化,数据量不大,所以选择内存型数据库进行存储
存储位置:Redis数据库 3号数据库

CACHES = {
    
    
    "history": {
    
     # 用户浏览记录
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/3",
        "OPTIONS": {
    
    
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
}
选择数据类型分析:
 	 最新浏览的排在前面,如果保存浏览的个数是固定的,访问相同的商品前需删除先访问浏览过的商品,保留最近浏览品
 	Redis数据类型有5种:String(字符串) Hash(哈希表)  List列表  Set集合  SortedSet(有序集合)
 	要保留数据类型一定是key value(多个),排除用String(字符串),Hash(哈希表) 特点也是key value,只是value是一个字典,比如key是某一个用户,value如何设计?还要有序,添加,删除。List列表 :如同空心的竹子,从右边进是LPUSH,从左边进是RPUSH, 本项目存储可选择列表来处理:将sku_id往里面添加,遵守LPUSH和RPUSH就可以了,

存储类型说明
• 由于用户浏览记录跟用户浏览商品详情的顺序有关,所以我们选择使用Redis中的list类型存储 sku_id
• 每个用户维护一条浏览记录,且浏览记录都是独立存储的,不能共用。所以我们需要对用户的浏览记录进行唯一标识。
• 我们可以使用登录用户的ID来唯一标识该用户的浏览记录。
• 存储类型:‘history_user_id’ : [sku_id_1, sku_id_2, …]
在这里插入图片描述
Redis 命令参考
http://doc.redisfans.com/

LREM
LREM key count value
count 的值可以是以下几种:

count > 0 : 从表头开始向表尾搜索,移除与 value 相等的元素,数量为 count 。
count < 0 : 从表尾开始向表头搜索,移除与 value 相等的元素,数量为 count 的绝对值。
count = 0 : 移除表中所有与 value 相等的值

根据参数 count 的值,移除列表中与参数 value 相等的元素
用第三种 count = 0 : 移除表中所有与 value 相等的值

LTRIM
LTRIM key start stop

对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

举个例子,执行命令 LTRIM list 0 2 ,表示只保留列表 list 的前三个元素,其余元素全部删除。

下标(index)参数 start 和 stop 都以 0 为底,也就是说,以 0 表示列表的第一个元素,以 1 表示列表的第二个元素,以此类推。

你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。

当 key 不是列表类型时,返回一个错误。

总结存储逻辑说明:
• SKU信息不能重复。
• 最近一次浏览的商品SKU信息排在最前面,以此类推。
• 每个用户的浏览记录最多存储五个商品SKU信息。
• 存储逻辑:先去重,再存储,最后截取。

在这里插入图片描述

保存和查询浏览记录

users/views.py

class UserBrowseHistory(LoginRequiredJSONMixin, View):
    """用户浏览记录"""

    def post(self, request):
        """保存用户的商品浏览记录"""
        json_str = request.body.decode()
        json_dict = json.loads(json_str)
        sku_id = json_dict.get('sku_id')

        # 校验参数
        try:
            SKU.objects.get(id=sku_id)
        except SKU.DoesNotExist:
            return http.HttpResponseForbidden('sku_id不存在')

        redis_conn = get_redis_connection('history')
        user = request.user
        pl = redis_conn.pipeline()
        # 去重复
        pl.lrem('history_%s' % user.id, 0, sku_id)
        # 保存
        pl.lpush('history_%s' % user.id, sku_id)
        # 截取 需求是保存5个商品 0, 4
        pl.ltrim('history_%s' % user.id, 0, 4)
        # 执行
        pl.execute()

        # 响应结果
        return http.JsonResponse({
    
    'code': RETCODE.OK, 'errmsg': 'OK'})

    def get(self, request):
        """查询用户商品浏览记录"""

        redis_conn = get_redis_connection('history')
        user = request.user
        sku_ids = redis_conn.lrange('history_%s' % user.id, 0, -1)
        # print(sku_ids)

        skus = []
        for sku_id in sku_ids:
            sku = SKU.objects.get(id=sku_id)
            skus.append({
    
    
                "id": sku.id,
                "name": sku.name,
                "price": sku.price,
                "default_image_url": sku.default_image.url
            })

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

user_conter_info.html

<h3 class="common_title2">最近浏览</h3>
            <div class="has_view_list" v-cloak>
                <ul class="goods_type_list clearfix">
                    <li v-for="sku in histories">
                        <a :href="sku.url"><img :src="sku.default_image_url"></a>
                        <h4><a :href="sku.url">[[ sku.name ]]</a></h4>
                        <div class="operate">
                            <span class="price">[[ sku.price ]]</span>
                            <span class="unit"></span>
                            <a href="javascript:;" class="add_goods" title="加入购物车"></a>
                        </div>

user_conter_info.js

      // 请求浏览历史记录
        browse_histories(){
    
    
            let url = '/users/browse_histories/';
            axios.get(url, {
    
    
                responseType: 'json'
            })
                .then(response => {
    
    
                    this.histories = response.data.skus;
                    for(let i=0; i<this.histories.length; i++){
    
    
                        this.histories[i].url = '/detail/' + this.histories[i].id + '/';
                    }
                })
                .catch(error => {
    
    
                    console.log(error.response);
                })
        },

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45905671/article/details/115263803