美文网首页Django+Vue生鲜电商
【Vue+DRF生鲜电商】10.商品分类层级获取,Vue跨域请求

【Vue+DRF生鲜电商】10.商品分类层级获取,Vue跨域请求

作者: 吾星喵 | 来源:发表于2019-04-26 17:45 被阅读56次

    欢迎访问我的博客专题

    源码可访问 Github 查看

    DRF实现商品分类获取

    实现商品分类层级结构显示

    商品类别ViewSet

    在商品类别中,不需要对类别进行分页,因为类别的数据量不大,只要在数据量很大的时候才能分页,所以前面需要去掉全局分页配置,否则会影响这儿的结果。

    修改 views.py 增加分类的视图

    class CategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
        # 注释很有用,在drf文档中,可以自动提取出来
        """
        list:
            商品分类列表
        """
        queryset = GoodsCategory.objects.all()  # 取出所有分类,没必要分页,因为分类数据量不大
        serializer_class = CategorySerializer  # 使用商品类别序列化类,写商品的分类外键已有,直接调用
    

    该序列化类可以直接使用之前商品列表分类外键已经创建好的,不用再新建,下方是已有的代码内容

    class CategorySerializer(serializers.ModelSerializer):
        class Meta:
            model = GoodsCategory
            fields = '__all__'
    

    商品类别URL配置

    修改主 urls.py ,在router中注册分类的url

    from rest_framework.routers import DefaultRouter
    from goods.views import GoodsListView, GoodsListViewSet, CategoryViewSet
    
    # 创建一个路由器并注册我们的视图集
    router = DefaultRouter()
    router.register(r'goods', GoodsListViewSet, base_name='goods')  # 配置goods的url
    router.register(r'categories', CategoryViewSet, base_name='categories')  # 配置分类的url
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('api-auth/', include('rest_framework.urls')),  # drf 认证url
        path('ckeditor/', include('ckeditor_uploader.urls')),  # 配置富文本编辑器url
    
        path('', include(router.urls)),  # API url现在由路由器自动确定。
    
        # DRF文档
        path('docs/', include_docs_urls(title='DRF文档')),
    ]
    

    这时访问 http://127.0.0.1:8000/categories/ 即可查看到所有的分类列表

    image.png

    但是,这些分类数据都是一起显示出来的,没有层次结构。

    层次化商品类别

    首先访问 http://127.0.0.1:8000/goods/ 记录下每一种商品的分类外键数据,因为之后要修改分类的序列化类,可以做下对比

    image.png

    分析:在前端页面上,拿分类数据时,首先是拿到一类分类,二级分类是在一级分类下面的,三级分类是二级分类下面的,可以看出,分类数据是有个层次结构的。

    image.png

    ViewSet只获取一级分类

    既然这样,我们就可以在视图中指定只获取一级分类数据

    修改 views.py

    class CategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
        # 注释很有用,在drf文档中
        """
        list:
            商品分类列表
        """
        # queryset = GoodsCategory.objects.all()  # 取出所有分类,没必要分页,因为分类数据量不大
        queryset = GoodsCategory.objects.filter(category_type=1)  # 只获取一级分类数据
        serializer_class = CategorySerializer  # 使用商品类别序列化类,写商品的分类外键已有,直接调用
    

    访问 http://127.0.0.1:8000/categories/ 即显示一级分类数据

    image.png

    那怎么才能通过一级分类获取到二级分类数据呢?之前写商品列表需要显示分类外键内容时,通过覆写category自定义序列化类实现。

    序列化二级分类

    在分类模型中,有个parent_category = models.ForeignKey('self', null=True, blank=True, verbose_name='父级目录', help_text='父级目录', on_delete=models.CASCADE, related_name='sub_category')字段,也就是可以通过sub_category字段获取子类对象。序列化类的字段也需要和这个字段保持一样。

    修改 serializers.py

    class CategorySerializer2(serializers.ModelSerializer):
        class Meta:
            model = GoodsCategory
            fields = '__all__'
    
    
    class CategorySerializer(serializers.ModelSerializer):
        sub_category = CategorySerializer2(many=True)  # 通过一级分类获取到二级分类,由于一级分类下有多个二级分类,需要设置many=True
    
        class Meta:
            model = GoodsCategory
            fields = '__all__'
    

    这就实现了一级分类下显示二级分类,访问 http://127.0.0.1:8000/categories/?format=json 即可看到,使用json方便收起

    image.png

    序列化三级分类

    三级分类也是使用一样的办法,因为分类数据的格式都是一样的

    修改 serializers.py ,增加三级分类序列化

    class CategorySerializer3(serializers.ModelSerializer):
        class Meta:
            model = GoodsCategory
            fields = '__all__'
    
    
    class CategorySerializer2(serializers.ModelSerializer):
        sub_category = CategorySerializer3(many=True)  # 通过二级分类获取三级分类
    
        class Meta:
            model = GoodsCategory
            fields = '__all__'
    
    
    class CategorySerializer(serializers.ModelSerializer):
        sub_category = CategorySerializer2(many=True)  # 通过一级分类获取到二级分类,由于一级分类下有多个二级分类,需要设置many=True
    
        class Meta:
            model = GoodsCategory
            fields = '__all__'
    

    刷新 http://127.0.0.1:8000/categories/?format=json

    image.png

    通过以上修改后,商品列表页也会跟着发生变化,显示的商品分类,会增加子类的显示

    image.png

    显示某个分类详情(包含其子类)

    类似于获取详情页

    修改视图,增加mixins.RetrieveModelMixin这样一个mixins

    class CategoryViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
        # 注释很有用,在drf文档中
        """
        list:
            商品分类列表
        """
        # queryset = GoodsCategory.objects.all()  # 取出所有分类,没必要分页,因为分类数据量不大
        queryset = GoodsCategory.objects.filter(category_type=1)  # 只获取一级分类数据
        serializer_class = CategorySerializer  # 使用商品类别序列化类,写商品的分类外键已有,直接调用
    

    这个继承主要是获取某个对象的详情,且这只需要这样写即可。

    Restful Api规范中要获取某个对象的详情,只需要在其后面添加某个对象的ID即可。

    DRF已经做好了,不需要手动配置获取详情的URL,只需要在ViewSe中继承mixins.RetrieveModelMixin即可,其他什么都不需要

    访问 http://127.0.0.1:8000/categories/ 可以显示分类列表,加上一级分类ID可以获得某个具体的分类,比如: http://127.0.0.1:8000/categories/121/?format=json ,这个一级分类详情数据主要是显示在前端左侧的分类列表。

    image.png

    由于分类的ViewSet中只会获取一级分类,所以,查询二级分类的ID是没有结果的,比如: http://127.0.0.1:8000/categories/122/?format=json

    image.png

    在Vue中获取分类API显示前端

    vue中存放分类菜单的位置在 src/views/head/head.vue 中

    没有数据,按 F12 打开打开Console可以看到错误内容

    Access to XMLHttpRequest at 'http://localhost:8000/categories/' from origin 'http://127.0.0.1:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    

    也就是不允许跨域请求。因为服务器启动的端口是8000,而Vue启动的是8080端口。就需要配置服务器的跨域(前端也可以配置跨域)。

    跨域处理django-cors-headers

    解决办法: 查看 https://github.com/ottoyiu/django-cors-headers (Django应用程序处理跨源资源共享(CORS)所需的服务器头),下方有说明使用方法

    使用pip安装包,pip install django-cors-headers

    corsheaders应用添加到APPS中

    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        # 添加drf应用
        'rest_framework',
        'django_filters',
        # Django跨域解决
        'corsheaders',
        # 注册富文本编辑器ckeditor
        'ckeditor',
        # 注册富文本上传图片ckeditor_uploader
        'ckeditor_uploader',
        'users.apps.UsersConfig',
        'goods.apps.GoodsConfig',
        'trade.apps.TradeConfig',
        'user_operation.apps.UserOperationConfig'
    ]
    

    还需要添加一个中间件类来监听响应:

    MIDDLEWARE = [
        'corsheaders.middleware.CorsMiddleware',  # corsheaders跨域
        'django.middleware.common.CommonMiddleware',  # corsheaders跨域
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
    

    CorsMiddleware应该放在尽可能前面的位置,尤其是在任何能够生成响应的中间件之前,比如Django的CommonMiddleware或Whitenoise的WhiteNoiseMiddleware。如果不在这之前,它将不能将CORS头添加到这些响应中。

    另外,如果正在使用CORS_REPLACE_HTTPS_REFERER,那么它应该放在Django的CsrfViewMiddleware之前。

    全局配置:

    Django设置中配置中间件的行为。必须将允许执行跨站点请求的主机添加到CORS_ORIGIN_WHITELIST,或者将CORS_ORIGIN_ALLOW_ALL设置为True以允许所有主机。

    • CORS_ORIGIN_ALLOW_ALL:如果是True,白名单将不被使用,所有的连接将被接受。默认值为False。
    • CORS_ORIGIN_WHITELIST:授权发出跨站点HTTP请求的源主机名列表。值“null”也可以出现在这个列表中,并将与“隐私敏感上下文”中使用的Origin: null头匹配,例如当客户机从file:// domain运行时。默认为[]

    修改 settings.py ,增加CORS配置

    # 跨域CORS设置
    # CORS_ORIGIN_ALLOW_ALL = False  # 默认为False,如果为True则允许所有连接
    CORS_ORIGIN_WHITELIST = (  # 配置允许访问的白名单
        'localhost:8080',
        '127.0.0.1:8080',
    )
    

    至此,再次刷新前端页面 http://127.0.0.1:8080/#/app/home/index 就正常显示分类数据了

    image.png

    Vue显示分类菜单流程分析

    1. 使用axios请求分类数据的api: src/api/api.js
    //获取商品类别信息
    export const getCategory = params => {
        if ('id' in params) {
            return axios.get(`${local_host}/categories/` + params.id + '/');
        }
        else {
            return axios.get(`${local_host}/categories/`, params);
        }
    };
    

    在这个代码中,如果没有获取到id,则请求所有的分类,否则请求指定分类id的详情。

    1. getCategory拿到数据之后,取results的值(也就是Django Api的是数据部分)赋值给,allMenuLabel,默认为[], head/head.vue
    getMenu() {//获取菜单
        getCategory({
            params: {}
        }).then((response) => {
            console.log('输出分类列表请求结果:');
            console.log(response.data);
            this.allMenuLabel = response.data.results
        })
            .catch(function (error) {
                console.log(error);
            });
    },
    
    image.png
    1. 进行allMenuLabel在vue中 for循环的遍历,显示类别的name
    <div class="main_nav_link" @mouseover="overAllmenu" @mouseout="outAllmenu">
        <a class="meunAll">全部商品分类
            <i class="iconfont">&#xe643;</i>
        </a>
        <div class="main_cata" id="J_mainCata" v-show="showAllmenu">
            <ul>
                <li class="first" v-for="(item,index) in allMenuLabel" @mouseover="overChildrenmenu(index)" @mouseout="outChildrenmenu(index)">
                    <h3>
                        <router-link :to="'/app/home/list/'+item.id">{{item.name}}</router-link>
                        <!--一级分类名称-->
                    </h3>
                    <div class="J_subCata" id="J_subCata" v-show="showChildrenMenu ===index" style=" left: 215px; top: 0px;">
                        <div class="J_subView">
                            <div v-for="second_item in item.sub_category">
                                <dl>
                                    <dt>
                                        <router-link :to="'/app/home/list/'+second_item.id">{{second_item.name}}</router-link>
                                        <!--二级分类名称-->
                                    </dt>
    
                                    <dd>
                                        <router-link v-for="third_item in second_item.sub_category" :key="third_item.id" :to="'/app/home/list/'+third_item.id">{{third_item.name}}</router-link>
                                        <!--三级分类-->
                                    </dd>
                                </dl>
                                <div class="clear"></div>
                            </div>
                        </div>
                    </div>
                </li>
            </ul>
        </div>
    </div>
    

    遍历三级,显示每一级的分类信息。

    分类显示到导航

    到目前为止,分类可以正常显示,但是上方的tab没有显示一项,就只有首页,对应源码如下, head/head.vue

    <ul class="sub_nav cle" id="sub_nav">
        <li>
            <router-link to="/app/home/index">首页</router-link>
        </li>
        <template v-for="(item,index) in allMenuLabel">
            <li>
                <div v-if="item.is_tab">
                    <router-link :to="'/app/home/list/'+item.id">{{item.name}}</router-link>
                    <!--is_tab为True的一级分类-->
                </div>
            </li>
        </template>
    </ul>
    

    在后台中,所有的分类对象is_tab字段都是为False,默认不显示在导航中。在Django Admin中设置一些一级分类is_tabTrue

    修改 goods/admin.py ,设置商品类别后台

    from django.contrib import admin
    from .models import GoodsCategory, Goods
    from django.apps import apps
    
    
    @admin.register(GoodsCategory)
    class GoodsCategoryAdmin(admin.ModelAdmin):
        list_display = ['name', 'category_type', 'is_tab', 'parent_category']  # 列表页显示
        list_display_links = ('name', 'parent_category',)  # 列表页外键链接,字段需在list_display中
        list_editable = ('is_tab',)  # 列表页可编辑
        list_filter = ('category_type',)  # 列表页可筛选
        search_fields = ('name', 'desc')  # 列表页可搜索
    
    
    all_models = apps.get_app_config('goods').get_models()
    for model in all_models:
        try:
            admin.site.register(model)
        except:
            pass
    

    访问 http://127.0.0.1:8000/admin/goods/goodscategory/?category_type__exact=1 既可以进行类别过滤

    image.png

    刷新 http://127.0.0.1:8080/#/app/home/index 即可正常显示导航

    image.png

    相关文章

      网友评论

        本文标题:【Vue+DRF生鲜电商】10.商品分类层级获取,Vue跨域请求

        本文链接:https://www.haomeiwen.com/subject/bzpqnqtx.html