一、商品类别数据接口
由之前的效果图和需求分析可知,首页全部商品分类需要展示一级、二级和三级分类,而在搜索结果页只展示一级和二级分类,分类有两个Vue组件,即Header中的全部商品分类和左侧的某以及类别对应的分类导航栏,也对应两个数据接口。
先在apps/goods/views.py中定义商品类别数据View如下:
from rest_framework import mixins, viewsets, filters from rest_framework.pagination import PageNumberPagination from django_filters.rest_framework import DjangoFilterBackend from .models import Goods, GoodsCategory from .serializers import GoodsSerializer, CategorySerializer from .filters import GoodsFilter # Create your views here. class GoodsPagination(PageNumberPagination): page_size = 10 page_size_query_param = 'page_size' page_query_param = 'p' max_page_size = 100 class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): '''商品列表页,并实现分页、搜索、过滤、排序''' queryset = Goods.objects.all() serializer_class = GoodsSerializer pagination_class = GoodsPagination filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] filter_class = GoodsFilter search_fields = ['name', 'goods_brief', 'goods_desc'] ordering_fields = ['sold_num', 'market_price'] class CategoryViewSet(mixins.ListModelMixin, viewsets.GenericViewSet): '''商品分类列表数据''' queryset = GoodsCategory.objects.all() serializer_class = CategorySerializer
再在urls.py中配置路由如下:
from django.conf.urls import url, include from django.views.static import serve from rest_framework.documentation import include_docs_urls from rest_framework.routers import DefaultRouter import xadmin from .settings import MEDIA_ROOT from goods.views import GoodsListViewSet, CategoryViewSet # Create a router and register our viewsets with it. router = DefaultRouter() # 配置goods的路由 router.register(r'goods', GoodsListViewSet, basename='goods') # 配置categories的路由 router.register(r'categories', CategoryViewSet, basename='categories')
访问http://127.0.0.1:8000/categories/,显示:
显然,将所有的数据都发送到前端,但是根据前端的要求,不同级之间的类别具有附属和依赖的关系,而不是平级的关系,显然还没有达到效果,需要进行改进。
此时需要用到才定义模型GoodsCategory的字段parent_category时指定的related_name属性,即related_name='sub_cat',此属性表示可以反向引用,即通过夫类别可以通过该属性查询子类别,利用该属性实现Serializer的三层嵌套引用,从而实现类别的嵌套显示,serializers.py 如下:
from rest_framework import serializers from .models import Goods, GoodsCategory class TerCategorySerializer(serializers.ModelSerializer): '''三级商品子类别序列化''' class Meta: model = GoodsCategory fields = '__all__' class SecCategorySerializer(serializers.ModelSerializer): '''二级商品子类别序列化''' sub_cat = TerCategorySerializer(many=True) class Meta: model = GoodsCategory fields = '__all__' class CategorySerializer(serializers.ModelSerializer): '''一级商品类别序列化''' sub_cat = SecCategorySerializer(many=True) class Meta: model = GoodsCategory fields = '__all__' class GoodsSerializer(serializers.ModelSerializer): '''商品序列化''' category = CategorySerializer() class Meta: model = Goods fields = '__all__'
此时再访问http://127.0.0.1:8000/categories/,显示:
此时,以嵌套的形式在父类别中显示出子类别,并且属于三层嵌套。
现在需要实现获取某一个具体类别的详情(包括其基本信息和子类别),此时需要在路由中加入商品对应的id,只要使CategoryViewSet继承自mixins.RetrieveModelMixin,即可自动配置路由,无需再额外配置,即views.py如下:
class CategoryViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet): '''商品分类列表数据''' queryset = GoodsCategory.objects.all() serializer_class = CategorySerializer
显示:
显然,此时地址中传入指定的id,只显示该id对应的类别的信息和其子类别的信息。
二、Vue展示商品分类
在测试前需要先在fresh_online目录下执行命令cnpm run dev
启动前端项目,启动后访问http://127.0.0.1:8080,可以看到:
显然,商品分类展示完整。
为了只是单独测试某一类数据而不影响其他数据的显示,因此其他数据都通过线上接口显示、待测试的数据通过本地接口测试。
在前端项目fresh_online中,在src/api/api.js中定义了数据API接口:
import axios from 'axios'; let host = 'http://shop.projectsedu.com'; let local_host = 'http://127.0.0.1:8000'; //获取商品类别信息 export const queryCategorygoods = params => { return axios.get(`${host}/indexgoods/`) } //获取首页中的新品 export const newGoods = params => { return axios.get(`${host}/newgoods/`) } //获取轮播图 export const bannerGoods = params => { return axios.get(`${host}/banners/`) } //获取商品类别信息 export const getCategory = params => { if('id' in params){ return axios.get(`${local_host}/categorys/`+params.id+'/'); } else { return axios.get(`${local_host}/categorys/`, params); } }; //获取热门搜索关键词 export const getHotSearch = params => { return axios.get(`${host}/hotsearchs/`) } //获取商品列表 export const getGoods = params => { return axios.get(`${host}/goods/`, { params: params }) } //商品详情 export const getGoodsDetail = goodId => { return axios.get(`${host}/goods/${goodId}`+'/') } //获取购物车商品 export const getShopCarts = params => { return axios.get(`${host}/shopcarts/`) } // 添加商品到购物车 export const addShopCart = params => { return axios.post(`${host}/shopcarts/`, params) } //更新购物车商品信息 export const updateShopCart = (goodsId, params) => { return axios.patch(`${host}/shopcarts/`+goodsId+'/', params) } //删除某个商品的购物记录 export const deleteShopCart = goodsId => { return axios.delete(`${host}/shopcarts/`+goodsId+'/') } //收藏 export const addFav = params => { return axios.post(`${host}/userfavs/`, params) } //取消收藏 export const delFav = goodsId => { return axios.delete(`${host}/userfavs/`+goodsId+'/') } export const getAllFavs = () => { return axios.get(`${host}/userfavs/`) } //判断是否收藏 export const getFav = goodsId => { return axios.get(`${host}/userfavs/`+goodsId+'/') } //登录 export const login = params => { return axios.post(`${host}/login/`, params) } //注册 export const register = parmas => { return axios.post(`${host}/users/`, parmas) } //短信 export const getMessage = parmas => { return axios.post(`${host}/code/`, parmas) } //获取用户信息 export const getUserDetail = () => { return axios.get(`${host}/users/1/`) } //修改用户信息 export const updateUserInfo = params => { return axios.patch(`${host}/users/1/`, params) } //获取订单 export const getOrders = () => { return axios.get(`${host}/orders/`) } //删除订单 export const delOrder = orderId => { return axios.delete(`${host}/orders/`+orderId+'/') } //添加订单 export const createOrder = params => {return axios.post(`${host}/orders/`, params)} //获取订单详情 export const getOrderDetail = orderId => {return axios.get(`${host}/orders/`+orderId+'/')} //获取留言 export const getMessages = () => {return axios.get(`${host}/messages/`)} //添加留言 export const addMessage = params => {return axios.post(`${host}/messages/`, params, {headers:{ 'Content-Type': 'multipart/form-data' }})} //删除留言 export const delMessages = messageId => {return axios.delete(`${host}/messages/`+messageId+'/')} //添加收货地址 export const addAddress = params => {return axios.post(`${host}/address/`, params)} //删除收货地址 export const delAddress = addressId => {return axios.delete(`${host}/address/`+addressId+'/')} //修改收货地址 export const updateAddress = (addressId, params) => {return axios.patch(`${host}/address/`+addressId+'/', params)} //获取收货地址 export const getAddress = () => {return axios.get(`${host}/address/`)}
其中,获取商品分类的接口为:
export const getCategory = params => { if('id' in params){ return axios.get(`${host}/categorys/`+params.id+'/'); } else { return axios.get(`${host}/categorys/`, params); } };
显然,可以看到,如果参数中传入了id,则返回单个类别,否则返回所有类别。
而负责将类别数据显示到前端的是head.vue组件,位于src/views/head目录下,其通过赋值和循环将类别遍历出来:
<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 style="background:url(../images/1449088788518670880.png) 20px center no-repeat;"> <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="list in item.sub_cat"> <dl> <dt> <router-link :to="'/app/home/list/'+list.id">{{list.name}}</router-link> </dt> <dd> <router-link v-for="childrenList in list.sub_cat" :key="childrenList.id" :to="'/app/home/list/'+childrenList.id">{{childrenList.name}}</router-link> </dd> </dl> <div class="clear"></div> </div> </div> </div> </li> </ul> </div> // ... getMenu(){//获取菜单 getCategory({ params:{} }).then((response)=> { console.log(response) this.allMenuLabel = response.data }) .catch(function (error) { console.log(error); }); },