您现在的位置是: 网站首页 >Django >Vue+Django REST framework前后端分离生鲜电商 Django

【Vue+DRF生鲜电商】25.商品添加购物车接口功能,Vue和购物车联调

admin2019年7月19日 19:10 Django | Html | JavaScript | Vue 736人已围观

Vue+Django REST framework前后端分离生鲜电商简介 Vue+Django REST framework 打造前后端分离的生鲜电商项目(慕课网视频)。 Github地址:https://github.com/xyliurui/DjangoOnlineFreshSupermarket ; Django版本:2.2、djangorestframework:3.9.2。 前端Vue模板可以直接联系我拿。

商品详情页点击加入购物车,就可以把商品加入到数据库【创建】。 对于购物车已存在的商品,如果将商品重复加入购物车,或者减少商品数量,就将它的数量进行加减【更新】。 购物车也支持对商品进行删除【删除】,以及列表展示【查询】。 综上,购物车就需要用到mixins中的所有功能。 ## 商品添加到购物车功能实现 实现购物车视图,在 apps/trade/views.py 添加代码 ```python from rest_framework import mixins, viewsets from rest_framework.permissions import IsAuthenticated from rest_framework.authentication import SessionAuthentication from rest_framework_simplejwt.authentication import JWTAuthentication from utils.permissions import IsOwnerOrReadOnly class ShoppingCartViewSet(viewsets.ModelViewSet): """ 购物车功能实现 list: 获取购物车列表 create: 添加商品到购物车 update: 更新购物车商品数量 delete: 从购物车中删除商品 """ # 权限问题:购物车和用户权限关联,这儿和用户操作差不多 permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) # 用户必须登录才能访问 authentication_classes = (JWTAuthentication, SessionAuthentication) # 配置登录认证:支持JWT认证和DRF基本认证 ``` 以上就是购物车的权限和认证相关的问题,接下来开始完成Serializer的编写。 对比需要继承的`serializers.ModelSerializer`和`serializers.Serializer`,根据项目逻辑需求,这儿用`serializers.Serializer`比较合适,因为它比较灵活。 如果用户对某个商品添加到购物车,之后再对该商品进行重复添加,那么是需要将购物车中的数据进行修改,也就是当前用户的购物车中的每一种商品都是唯一的,那么修改一下models,为其增加联合唯一的限定。 ```python # apps/trade/models.py class ShoppingCart(models.Model): """ 购物车 """ user = models.ForeignKey(User, verbose_name='用户', help_text='用户', on_delete=models.CASCADE, related_name='shopping_carts') goods = models.ForeignKey(Goods, verbose_name='商品', help_text='商品', on_delete=models.CASCADE) nums = models.IntegerField(default=0, verbose_name='购买数量', help_text='购买数量') add_time = models.DateTimeField(auto_now_add=True, verbose_name='添加时间') class Meta: verbose_name_plural = verbose_name = '购物车' unique_together = ['user', 'goods'] # 用户和商品联合唯一 def __str__(self): return "{}({})".format(self.goods.name, self.nums) ``` 那么试想下,如果 user 和 goods 已经存在了,再向购物车添加同一个商品,不希望得到验证说这个商品已经存在,而是希望在当前数量上加1。 如果使用的是`serializers.ModelSerializer`,模型中定义了`unique_together = ['user', 'goods']`,进行*validate*时就会抛出异常,就算是重写`def create(self, validated_data)`方法也是无效的,因为在进行该方法前,就已经进行了*validate*。继续解释下,`ShoppingCartViewSet`继承的是`viewsets.ModelViewSet`,在这里面有个`mixins.CreateModelMixin` ![BLOG_20190719_191509_29](/media/blog/images/2019/07/BLOG_20190719_191509_29.png "博客图集BLOG_20190719_191509_29.png") 这个`CreateModelMixin`在`create`的时候首先会调用`serializer.is_valid(raise_exception=True)`,接下来会调用`self.perform_create(serializer)`执行数据保存`serializer.save()`,在验证的时候就已经报错了,就不会调用`create`方法了,所以是无法保存成功的。 当然,我们也可以重写`CreateModelMixin`中的`create`的方法,就可以控制该方法的所有步骤。 如果不用`serializer`,那么它的验证功能就享受不到了,不用`serializer`做,就需要在views中去完成;另外生成文档的时候,字段是从`serializer`中取得,如果不用那么文档的功能就缺失了。 用了`serializer`,但用的是`serializers.ModelSerializer`,那么刚才的验证是通不过的。所以这儿用底层的`serializers.Serializer`,自己来做*validate*。 ### 购物车序列化ShoppingCartSerializer 下面一步一步去完成,`ShoppingCart`模型中用到了`user`、`goods`、`nums`这几个重要的字段,`user`和之前用户操作中一样,直接复制过来即可。 ```python # apps/trade/serializers.py from rest_framework import serializers from goods.models import Goods class ShoppingCartSerializer(serializers.Serializer): user = serializers.HiddenField( default=serializers.CurrentUserDefault() # 表示user为隐藏字段,默认为获取当前登录用户 ) nums = serializers.IntegerField(required=True, min_value=1, label='商品数量', help_text='商品数量', error_messages={ 'min_value': '商品数量不能小于1', 'required': '请选择购买数量' }) goods = serializers.PrimaryKeyRelatedField(queryset=Goods.objects.all(), required=True, label='商品') ``` 分析:访问 https://www.django-rest-framework.org/api-guide/fields/ 可以看到DRF提供的`Serializer`字段;而像外键这个关系型的,可以查看 https://www.django-rest-framework.org/api-guide/relations/ , `PrimaryKeyRelatedField`就是需要在这`goods`用到的 参数说明: - `queryset`:在验证字段输入时用于模型实例查找的`queryset`。关系必须显式设置`queryset`,或者设置`read_only=True`。 - `many`:如果应用于多对多关系,则应将此参数设置为`True`。 - `allow_null`:如果设置为`True`,该字段将接受`None`值或空字符串,用于可空关系。默认值为`False`。 - `pk_field`:设置为一个字段来控制主键值的序列化/反序列化。例如,`pk_field=UUIDField(format='hex')`将把UUID主键序列化为紧凑的十六进制表示形式。 ### ShoppingCartSerializer添加商品重写create()方法 由于使用的是`serializers.Serializer`,没有定义`create()`方法,需要自己去重写。而`ModelSerializer`是写好了该方法。 用户在添加购物车时,也就是在数据库中添加一条记录。还要分为两种情况,当购物车数据库中没有这个商品时,执行添加;当已存在该商品时,执行数量更新。 `validated_data`也就是上方定义的字段传过来之前已经处理好的数据,例如,`nums`如果数量为负数,那么该数据到不了`create()`方法。 ```python # apps/trade/serializers.py from rest_framework import serializers from goods.models import Goods from trade.models import ShoppingCart class ShoppingCartSerializer(serializers.Serializer): user = serializers.HiddenField( default=serializers.CurrentUserDefault() # 表示user为隐藏字段,默认为获取当前登录用户 ) nums = serializers.IntegerField(required=True, min_value=1, label='商品数量', help_text='商品数量', error_messages={ 'min_value': '商品数量不能小于1', 'required': '请选择购买数量' }) goods = serializers.PrimaryKeyRelatedField(queryset=Goods.objects.all(), required=True, label='商品') def create(self, validated_data): user = self.context['request'].user # serializer中获取当前用户,而views是直接从request中获取 nums = validated_data['nums'] goods = validated_data['goods'] # 查询记录是否存在,存在,则进行数量加,不存在则新创建 shopping_cart = ShoppingCart.objects.filter(user=user, goods=goods) if shopping_cart: shopping_cart = shopping_cart.first() shopping_cart.nums += nums shopping_cart.save() else: shopping_cart = ShoppingCart.objects.create(**validated_data) # 最后要返回创建后的结果 return shopping_cart ``` ### ShoppingCartViewSet中获取当前登录用户购物车 首先引入上方创建好的序列化类,并且指定`queryset`,只能显示当前登录用户的购物车列表 ```python # apps/trade/views.py from .serializers import ShoppingCartSerializer from .models import ShoppingCart class ShoppingCartViewSet(viewsets.ModelViewSet): """ 购物车功能实现 list: 获取购物车列表 create: 添加商品到购物车 update: 更新购物车商品数量 delete: 从购物车中删除商品 """ # 权限问题:购物车和用户权限关联,这儿和用户操作差不多 permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) # 用户必须登录才能访问 authentication_classes = (JWTAuthentication, SessionAuthentication) # 配置登录认证:支持JWT认证和DRF基本认证 serializer_class = ShoppingCartSerializer queryset = ShoppingCart.objects.all() def get_queryset(self): # 只能显示当前用户的购物车列表 return self.queryset.filter(user=self.request.user) ``` ![BLOG_20190719_191447_50](/media/blog/images/2019/07/BLOG_20190719_191447_50.png "博客图集BLOG_20190719_191447_50.png") 选择某个商品添加到购物车,会返回该商品的id和添加的数量。 ![BLOG_20190719_191440_59](/media/blog/images/2019/07/BLOG_20190719_191440_59.png "博客图集BLOG_20190719_191440_59.png") 再访问 http://127.0.0.1:8000/shoppingcart/ 可以看到购物车已有的商品id及数量。 ## 修改购物车中商品数量 ### ShoppingCartSerializer指定商品更新重写update()方法 由于每个用户的购物车商品都是唯一的,所以只需要传递商品的id,就可以获取到该记录。 ```python # apps/trade/serializers.py class ShoppingCartViewSet(viewsets.ModelViewSet): """ 购物车功能实现 list: 获取购物车列表 create: 添加商品到购物车 update: 更新购物车商品数量 delete: 从购物车中删除商品 """ # 权限问题:购物车和用户权限关联,这儿和用户操作差不多 permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) # 用户必须登录才能访问 authentication_classes = (JWTAuthentication, SessionAuthentication) # 配置登录认证:支持JWT认证和DRF基本认证 serializer_class = ShoppingCartSerializer queryset = ShoppingCart.objects.all() lookup_field = 'goods' def get_queryset(self): # 只能显示当前用户的购物车列表 return self.queryset.filter(user=self.request.user) ``` 比如上方商品id为105的购物车,可以直接访问 http://127.0.0.1:8000/shoppingcart/105/ 获取该购物车详情 ![BLOG_20190719_191429_74](/media/blog/images/2019/07/BLOG_20190719_191429_74.png "博客图集BLOG_20190719_191429_74.png") 在这就可以对商品进行删除,也可以对商品数量进行更新。测试下更新,修改商品数量,然后点击**PUT**,就报错了 ```python raise NotImplementedError('`update()` must be implemented.') NotImplementedError: `update()` must be implemented. ``` 意思就是没有`update()`方法。 可以查看rest的源码 `rest_framework.serializers.Serializer`,这个类继承的`BaseSerializer`有这样一个方法: ```python # 源码:rest_framework.serializers.BaseSerializer#update def update(self, instance, validated_data): raise NotImplementedError('`update()` must be implemented.') ``` 且`Serializer`没有重写`update()`方法,使用时会抛出上面的异常。而`ModelSerializer`是有该方法的`rest_framework.serializers.ModelSerializer#update`,所以直接拿来用不会报错。 ![BLOG_20190719_191421_50](/media/blog/images/2019/07/BLOG_20190719_191421_50.png "博客图集BLOG_20190719_191421_50.png") 现在数量新的商品数量后,在点击**PUT**就不会报错了,数量也能正常更新。 ### 测试指定商品删除 测试删除功能,可以指定商品进行删除,删除没有返回结果。也无须重写删除方法 ![BLOG_20190719_191414_18](/media/blog/images/2019/07/BLOG_20190719_191414_18.png "博客图集BLOG_20190719_191414_18.png") ![BLOG_20190719_191409_62](/media/blog/images/2019/07/BLOG_20190719_191409_62.png "博客图集BLOG_20190719_191409_62.png") 访问 http://127.0.0.1:8000/docs/#shoppingcart-list ,在文档页面也会出现刚才添加的接口 ![BLOG_20190719_191401_74](/media/blog/images/2019/07/BLOG_20190719_191401_74.png "博客图集BLOG_20190719_191401_74.png") ## Vue和购物车接口联调 ### ShoppingCartListSerializer购物车显示商品列表 现在购物车中只有商品的id,但是一般还需要显示商品的图片、名称、单价等信息。 而现在`ShoppingCartSerializer`中`goods=serializers.PrimaryKeyRelatedField(queryset=Goods.objects.all(), required=True, label='商品')`关联商品的主键id,所以要动态设置`Serializer`,这是个`ModelSerializer` 新建一个`Serializer` ```python # apps/trade/serializers.py from goods.serializers import GoodsSerializer class ShoppingCartListSerializer(serializers.ModelSerializer): goods = GoodsSerializer(many=False) # 一个购物车的记录只会对应一个商品,默认many=False,也就是可以不写 class Meta: model = ShoppingCart fields = "__all__" ``` ### ShoppingCartViewSet购物车动态使用序列化类 然后在`ViewSet`中动态获取要使用的`Serializer` ```python # apps/trade/views.py from .serializers import ShoppingCartSerializer, ShoppingCartListSerializer class ShoppingCartViewSet(viewsets.ModelViewSet): """ 购物车功能实现 list: 获取购物车列表 create: 添加商品到购物车 update: 更新购物车商品数量 delete: 从购物车中删除商品 """ # 权限问题:购物车和用户权限关联,这儿和用户操作差不多 permission_classes = (IsAuthenticated, IsOwnerOrReadOnly) # 用户必须登录才能访问 authentication_classes = (JWTAuthentication, SessionAuthentication) # 配置登录认证:支持JWT认证和DRF基本认证 # serializer_class = ShoppingCartSerializer # 使用get_serializer_class(),这个就不需要了 queryset = ShoppingCart.objects.all() lookup_field = 'goods' def get_serializer_class(self): if self.action == 'list': # 当获取购物车列表时,使用ModelSerializer,可以显示购物车商品详情 return ShoppingCartListSerializer else: return ShoppingCartSerializer def get_queryset(self): # 只能显示当前用户的购物车列表 return self.queryset.filter(user=self.request.user) ``` 现在访问 http://127.0.0.1:8000/shoppingcart/ 可以看到购物车商品列表 ![BLOG_20190719_191347_89](/media/blog/images/2019/07/BLOG_20190719_191347_89.png "博客图集BLOG_20190719_191347_89.png") 只有在显示购物车列表时才会用到这个序列化类,显示某个商品详情,或更新就是用之前的类 ### Vue中配置购物车接口 修改接口,添加购物车的相关接口 ```JavaScript // src/api/api.js //获取购物车商品 export const getShopCarts = params => { return axios.get(`${local_host}/shoppingcart/`) }; // 添加商品到购物车 export const addShopCart = params => { return axios.post(`${local_host}/shoppingcart/`, params) }; //更新购物车商品信息 export const updateShopCart = (goodsId, params) => { return axios.patch(`${local_host}/shoppingcart/` + goodsId + '/', params) }; //删除某个商品的购物记录 export const deleteShopCart = goodsId => { return axios.delete(`${local_host}/shoppingcart/` + goodsId + '/') }; ``` 选择某一商品,点击添加到购物车 ![BLOG_20190719_191339_17](/media/blog/images/2019/07/BLOG_20190719_191339_17.png "博客图集BLOG_20190719_191339_17.png") 访问 http://127.0.0.1:8080/#/app/shoppingcart/cart 购物车可以查看到列表 ![BLOG_20190719_191332_29](/media/blog/images/2019/07/BLOG_20190719_191332_29.png "博客图集BLOG_20190719_191332_29.png") #### Vue加入购物车逻辑分析 加入购物车按钮位置 ```html <!-- src/views/productDetail/productDetail.vue--> <a class="btn" id="buy_btn" @click="addShoppingCart"> <i class="iconfont">&#xe600;</i> 加入购物车 </a> ``` 当点击加入购物车按钮时,会调用 ```JavaScript // src/views/productDetail/productDetail.vue addShoppingCart() { //加入购物车 addShopCart({ goods: this.productId, // 商品id nums: this.buyNum, // 购买数量 }).then((response) => { this.$refs.model.setShow(); // 更新store数据 this.$store.dispatch('setShopList'); }).catch(function (error) { console.log(error); }); }, ``` 这里面会请求`addShopCart`传递商品的id和购买数量,这个数量和输入框的值一样,直接请求api中创建购物车的接口 ```JavaScript // src/api/api.js // 添加商品到购物车 export const addShopCart = params => { return axios.post(`${local_host}/shoppingcart/`, params) }; ``` 当提交成功后,会请示`this.$refs.model.setShow()`显示添加成功的弹框。 #### 页面顶部显示购物车简单列表 然后`this.$store.dispatch('setShopList');`更新vuex,点击`setShopList`可以访问到`src/store/actions.js`:`export const setShopList = makeAction(types.SET_SHOPLIST);`,实际上就是`src/store/mutations.js`中 ```JavaScript // src/store/mutations.js [types.SET_SHOPLIST](state) { //设置购物车数据 // token = cookie.getCookie('token') if (cookie.getCookie('token') != null) { getShopCarts().then((response) => { // 更新store数据 state.goods_list.goods_list = response.data; //console.log(response.data); var totalPrice = 0; response.data.forEach(function (entry) { totalPrice += entry.goods.shop_price * entry.nums }); state.goods_list.totalPrice = totalPrice; }).catch(function (error) { console.log(error); }); } }, ``` 这一段的意思就是调用`getShopCarts()`函数,获取到购物车的商品,将这些商品的数据填充到`state`中,更新`src/store/store.js`中`goods_list.goods_list` ```JavaScript const goods_list = { totalPrice:'', goods_list:[] } ``` 经过Vue的处理,显示到顶部 ![BLOG_20190719_191321_46](/media/blog/images/2019/07/BLOG_20190719_191321_46.png "博客图集BLOG_20190719_191321_46.png") ```html <!-- src/views/head/head.vue--> <div class="hd_cart" id="ECS_CARTINFO" @mouseover="overShopCar" @mouseout="outShopCar"> <router-link class="tit" :to="'/app/shoppingcart/cart'" target=_blank> <b class="iconfont">&#xe600;</b>去购物车结算<span><i class="iconfont">&#xe645;</i></span> <em class="num" id="hd_cartnum" style="visibility: visible;">{{goods_list.goods_list.length}}</em> </router-link> <div class="list" v-show="showShopCar"> <div class="data"> <dl v-for="(item,index) in goods_list.goods_list"> <dt> <router-link :to="'/app/home/productDetail/'+item.goods.id" target=_blank><img :src="item.goods.goods_front_image"></router-link> </dt> <dd> <h4> <router-link :to="'/app/home/productDetail/'+item.goods.id" target=_blank> {{item.goods.name}} </router-link> </h4> <p><span class="red">{{item.goods.shop_price}}</span>&nbsp;<i>X</i>&nbsp;{{item.nums}} </p> <a title="删除" class="iconfont del" @click="deleteGoods(index,item.goods.id)">×</a> </dd> </dl> </div> <div class="count">共<span class="red" id="hd_cart_count">{{goods_list.goods_list.length}}</span>件商品哦~ <p>总价:<span class="red"><em id="hd_cart_total">{{goods_list.totalPrice}}</em></span> <router-link class="btn" :to="'/app/shoppingcart/cart'" target=_blank>去结算 </router-link> </p> </div> </div> </div> ``` #### 购物车列表显示功能 当进入购物车页面时 http://127.0.0.1:8080/#/app/shoppingcart/cart 组件创建 ```JavaScript //src/views/cart/cart.vue created() { // 请求购物车商品 getShopCarts().then((response) => { //console.log(response.data); // 更新store数据 //this.goods_list = response.data; var totalPrice = 0; this.goods.goods_list = response.data; response.data.forEach(function (entry) { totalPrice += entry.goods.shop_price * entry.nums //console.log(entry.goods.shop_price); }); this.goods.totalPrice = totalPrice this.totalPrice = totalPrice }).catch(function (error) { }); this.getAllAddr() }, ``` 然后向html中填充数据 ```html <!-- src/views/cart/cart.vue --> <li class="cle hover" style="border-bottom-style: none;" v-for="(item,index) in goods.goods_list"> <div class="pic"> <a target="_blank"> <img :alt="item.goods.name" :src="item.goods.goods_front_image"></a> </div> <div class="name"> <a target="_blank">{{item.goods.name}}</a> <p></p> </div> <div class="price-xj"> <p><em>¥{{item.goods.shop_price}}元</em></p> </div> <div class="nums" id="nums"> <span class="minus" title="减少1个数量" @click="reduceCartNum(index, item.goods.id);">-</span> <input type="text" v-model="item.nums"> <span class="add" title="增加1个数量" @click="addCartNum(index, item.goods.id);">+</span> </div> <div class="price-xj"><span></span> <em id="total_items_3137">¥{{item.goods.shop_price * item.nums}}元</em> </div> <div class="del"> <a class="btn-del" @click="deleteGoods(index, item.goods.id)">删除</a> </div> </li> ``` #### 购物车中删除商品功能 当点击删除时,执行`deleteGoods(index, item.goods.id)`,传递商品的id ```JavaScript // src/views/cart/cart.vue deleteGoods(index, id) { //移除购物车 alert('您确定把该商品移除购物车吗'); deleteShopCart(id).then((response) => { //console.log(response.data); this.goods.goods_list.splice(index, 1); // 更新store数据 this.$store.dispatch('setShopList'); }).catch(function (error) { console.log(error); }); }, ``` 接下俩调用删除`deleteShopCart(id)`的api ```JavaScript // src/api/api.js //删除某个商品的购物记录 export const deleteShopCart = goodsId => { return axios.delete(`${local_host}/shoppingcart/` + goodsId + '/') }; ``` 删除成功后更新Vue中显示的购物车商品列表数据 #### 购物车更新商品数量功能 在购物车中有一个增加数量和减少数量的按钮 ```html <!-- src/views/cart/cart.vue --> <div class="nums" id="nums"> <span class="minus" title="减少1个数量" @click="reduceCartNum(index, item.goods.id);">-</span> <input type="text" v-model="item.nums"> <span class="add" title="增加1个数量" @click="addCartNum(index, item.goods.id);">+</span> </div> ``` 点击`reduceCartNum(index, item.goods.id),`减少一个数量 ```JavaScript // src/views/cart/cart.vue reduceCartNum(index, id) { //删除数量 if (this.goods.goods_list[index].nums <= 1) { this.deleteGoods(index, id) } else { updateShopCart(id, { nums: this.goods.goods_list[index].nums - 1 }).then((response) => { this.goods.goods_list[index].nums = this.goods.goods_list[index].nums - 1; // 更新store数据 this.$store.dispatch('setShopList'); //更新总价 this.setTotalPrice(); }).catch(function (error) { console.log(error); }); } }, ``` 当数量减小到0,就从购物车删除该商品。 更新购物车`updateShopCart()`指定商品的id,传递数量(当前的数量-1)提交到api ```JavaScript // src/api/api.js //更新购物车商品信息 export const updateShopCart = (goodsId, params) => { return axios.patch(`${local_host}/shoppingcart/` + goodsId + '/', params) }; ``` 提交成功后更新购物车商品数据。 当点击`addCartNum(index, item.goods.id)`增加1个数量时,调用 ```JavaScript // src/views/cart/cart.vue addCartNum(index, id) { //添加数量 updateShopCart(id, { nums: this.goods.goods_list[index].nums + 1 }).then((response) => { this.goods.goods_list[index].nums = this.goods.goods_list[index].nums + 1; // 更新store数据 this.$store.dispatch('setShopList'); //更新总价 this.setTotalPrice(); }).catch(function (error) { console.log(error); }); }, ``` 请求`updateShopCart()`指定商品的id,传递数量(当前的数量+1)提交到api,提交成功后Vue中将当前商品数量+1,并更新购物车中的数据。 #### 购物车收货地址获取 ```html <li v-for="item in addrInfo" :class="{'addressActive':addressActive==item.id}" @click="selectAddr(item.id)"> <p class="item">地址:{{item.province}} {{item.city}} {{item.district}} {{item.address}}</p> <p class="item">电话:{{item.signer_mobile}}</p> <p class="item">姓名:{{item.signer_name}}</p> </li> ``` 组件创建时,获取所有的收货地址,遍历显示到页面 ```JavaScript // src/views/cart/cart.vue getAllAddr() { //获得所有配送地址 getAddress().then((response) => { this.addrInfo = response.data; }).catch(function (error) { console.log(error); }); }, ```

很赞哦! (0)

文章交流

  • emoji
0人参与,0条评论

当前用户

未登录,点击   登录

站点信息

  • 建站时间:网站已运行1163天
  • 系统信息:Linux
  • 后台程序:Python: 3.8.10
  • 网站框架:Django: 3.2.6
  • 文章统计:256 篇
  • 文章评论:54 条
  • 腾讯分析网站概况-腾讯分析
  • 百度统计网站概况-百度统计
  • 公众号:微信扫描二维码,关注我们
  • QQ群:QQ加群,下载网站的学习源码
返回
顶部
标题 换行 登录
网站