购物街项目(4)

五、实现购物车页面功能

5.1 完成购物车顶部导航的功能

  1. 在购物页面引入顶部导航组件 Navbar.vue 注册和使用
  2. 由于顶部导航只有中间部分显示信息,因此只需要使用中间插槽部分
  3. 在顶部导航显示购物商品数量
    • 使用 vuex 中的 getters 方法将 cartList 数据进行计算长度后返回
    • 将返回的长度存储到 cartListLength
    • 再在购物车组件 Cart.vue 中的 computed 中拿到 getter 中的 cartListLength 返回给 cartLength
    • 再在页面上渲染商品数量的数据 {{cartLength}}
  4. 完成顶部导航的样式

5.2 使用 mapGetters 函数改造5.1中的内容

  1. 抽离 getters 中的内容到 getters.js
    • 获取存储到 statecartList 的数组长度 cartLength
    • 获取存储到 state 中的商品数据 cartList
  2. Cart 组件中引入 mapGetters
    import { mapGetters } from 'vuex'
  3. 使用对象展开运算符将 getter 混入 computed 对象中
       computed: {
         ...mapGetters(["cartLength"])
       }
    

5.3 完成购物车列表商品卡片

  1. 使用 Vant 组件中的 CardCheckboxCheckboxGroup 来实现完整商品卡片
  2. 创建商品列表组件 CartList.vue 并将其引入 Cart 父组件中,注册并使用
  3. CartList 组件中使用 mapGetters 来获取 vuex 中的商品数据
  4. CheckboxGroup 组件将 CheckboxCard 包裹在一起,使用 v-for 进行遍历来显示每个商品卡片,实现可选择的商品卡片
  5. 修改并调试样式,使其满足页面布局
  6. 实现商品列表的滚动区域,引入 Scroll 组件,来让替换原生滚动
    • CartList 组件中将 scroll 的父标签上设置高度
    • 再在 scroll 标签上设置滚动范围,即外层高度减去头部和底部的高度
    • 由于添加了购物车数据后可滚动区域的高度发生了变化,因此需要调用已 scroll 的刷新
           activated() {
             this.$refs.scroll.refresh();
           },
      
    • 由于使用了 keep-alive 保持状态的功能,需要在 activated 生命周期函数中去调用该刷新方法,这样在每次进入购物车页面时,由于滚动区域高度有变化重新刷新计算一下

5.4 实现添加购物车商品时,已经存在的商品自动加一

  1. 先查找之前的购物车列表中是否有该商品
    • 使用 find 函数查找 cartList 中与商品 iid 相符的数据,并返回该商品信息
         let oldProduct = state.cartList.find(item => item.iid === payload.iid)
      
  2. 然后判断 oldProduct 是否为空,即 oldProduct 是否为 true
    • 使用 if else 来判断,当 oldProducttrue 时,oldProduct.count +=1
    • 否则 payload.count = 1,并往 cartList 中插入一条新的商品数据,并且该商品中带有 count 属性
         if (oldProduct){
             oldProduct.count += 1
         } else {
             payload.count = 1
             state.cartList.push(payload)
         }
      
  3. 对 vuex 中的 store 进行重构
    • mutations 中的方法尽可能完成单一的事件
    • actions 中来完成判断逻辑复杂和异步等操作
      • 在添加购物车时,采用 dispatch() 方法来发送操作
      • 将原本 mutations 中的 addToCartList 方法放到 actions
      • 而且接受一个与 store 实例具有相同方法和属性的 context 对象
      • 因此将 if 判断逻辑中的加1操作和push操作通过 commit 提交
         mutations: {
             addCounter(state, payload){
                 payload.count++
             },
             addToCart(state, payload){
                 state.cartList.push(payload)
             }
         }
         actions: {
             addToCartList(context, payload){
                 let oldProduct = constext.state.cartList.find(item => item.iid === payload.iid)
             
                if (oldProduct){
                    context.commit("addCounter", oldProduct)
                } else {
                    payload.count = 1
                    context.commit("addToCart", payload)
                }
             }
         }
      
    • mutations 中的内容进行抽离放到 mutations.js 文件中
    • actions 中的内容进行抽离放到 actions.js 文件中

5.5 完成底部提交商品内容

  1. 创建组件 CartBottomBar 组件,并在父组件购物车 CartList 中引入、注册和使用
  2. 使用UI Vant 组件中的 SubmitBar 提交订单栏组件
  3. 将添加到购物车的商品价格计算总数显示在 CartBottomBar 组件上的 :price
    • 在父组件 CartList 中的计算属性 computed 中计算并存储总价格 totalPrice
    • 此处通过 reduce 计算累加,返回一个累加函数的结果
    • 注意:由于 reduce 对空数组不执行回调,当result数组为空时,会报错
    • 因此给 result 数组一个初始值0
      data() {
        return {
          result: [0]
        };
      },
      computed: {
        totalPrice() {
          // 此处通过 reduce 计算累加,返回一个累加函数的结果
          // 注意:由于 reduce 对空数组不执行回调,当result数组为空时,会报错
          return this.result.reduce((preValue, item) => {
            return preValue + item.price * item.count;
          });
        }
      },
      
    • 再将 totalPrice 通过父子组件传值的方式传给 CartBottomBarprops 中的 totalPrice
    • 最后需要将 totalPrice100 给到price中 :price="totalPrice100"
  4. 实现全选反选各种场景功能
    • 全选按钮场景分析:

      1. 全选按钮为选中时,所有商品全部选中
      2. 当商品全部选中时,全选按钮自动选中
      3. 全选中后,再次点击全选按钮,所有商品取消选中
    • 在子组件的全选按钮上绑定 checkAll 方法,将其发送给父组件 CartList

    • 再在父组件的 <cart-bottom-bar/> 上绑定发送过来的事件 checkAllChange ,通过该事件方法触发全选和反选效果(实现了场景1、3)

         checkAllChange() {
           // 通过判断 result 数组的长度与 cartList 数组的长度是否一致来进行取反
           if (this.result.length < this.cartList.length) {
             this.$refs.checkboxGroup.toggleAll(true);
           } else {
             this.$refs.checkboxGroup.toggleAll();
           }
         }
      
    • 在计算属性 isTotalchecked 中判断,当商品全部选中时,将 isTotalchecked 传递给子组件 <cart-bottom-bar/>props 中的 totalChecked,并在复选框的 v-model 指令上使用(实现了场景2)

         // 判断当商品一一勾选后,全选按钮自动勾选
         getTotalChecked() {
           return this.result.length === this.cartList.length &&
             this.result.length > 0
             ? true
             : false;
         },
      
    • 注意: 1)如果将子组件中 propstotalChecked 直接在 v-model 指令上使用会出现(第一个vue的告警),虽然不影响功能
      [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "totalChecked"

      2) 因此需要采用计算属性 computedtotalChecked 的值返回给一个新属性值 _totalChecked 在在 v-model 中使用

         _totalChecked() {
           return this.totalChecked;
         }
      

      3)但是这又会产生新(第二个 vue 的告警)
      [Vue warn]: Computed property "_totalChecked" was assigned to but it has no setter.
      4)因此需要给 _totalChecked 设置 set ,之后再在 v-model 中引用就不会保存

         _totalChecked: {
           get: function() {
             return this.totalChecked;
           },
           set: function() {}
         }
      
  5. 改造第3步中的计算已勾选商品的价格总数
     getTotalPrice() {
           let arr = this.result;
           let total = 0;
           if (arr.length === 0) return 0;
           for (let j = 0; j < arr.length; j++) {
             total += arr[j].price * arr[j].count;
           }
           return total;
         },
    
    • 并将计算的结果返回给 totalPrice 计算属性
    • 通过属性绑定父子组件传值的方式,传递到子组件的 totalPrice 中,并在界面渲染
  6. 上面的第 4 步使用另外一种方式避免出现第一个vue告警的情况:
    • CartBottomBar 组件内的代码直接写在父组件 CartList 中,就会避免采用父子组件的传值方式,也就不会出现直接使用 props 中的 totalChecked 而导致的第一个vue告警。
    • 但第二个告警任然会出现,不过只需要像注意事项的第 4) 条中设置 Set 就可以了。

5.6 优化添加购物车方法,并引入提示

  1. 添加购物车成功后要有 toast 提示,因此需要进行异步回调,来提示不同的内容
  2. addToCartList 方法中使用 Promise 函数进行回调
    • 当添加商品后,若是新增商品则回调 resolve('添加新的商品成功')
    • 若是只是商品 +1 则回调 resolve('当前的商品数量+1')
       addToCartList(context, payload) {
         return new Promise((resolve, reject) => {
           // 2. 先查找之前的购物车列表中是否有该商品
           let oldProduct = context.state.cartList.find(item => item.iid === payload.iid)
       
           // 3. 然后判断 oldProduct 是否为空,即 oldProduct 是否为 true,
           // 不为空就将原本商品的数量加1,为空就往 cartList 插入一条带有 count = 1 属性的新的数据,
           if (oldProduct) {
             context.commit("addCounter", oldProduct)
             resolve('当前的商品数量+1')
           } else {
             payload.count = 1
             context.commit("addToCart", payload)
             resolve('添加新的商品成功')
           }
         })
       }
      
  3. Detail 组件中的 addToCart 方法中对 dispatch 进行回调的内容用弹窗 Toast 提示
     this.$store.dispatch("addToCartList", products).then(res => {
             Toast.success(res);
           });
    
    注意: 引入 Toast 组件时,若已经在 main.js 中已经引入,但直接使用任然会报错。因此需要在当前组件中再引入一次

5.7 优化图片懒加载的功能

  1. 需要使用 vue-lazyload 组件
  2. 引入组件 VueLazyload 并使用
     import VueLazyload from 'vue-lazyload'
     
     Vue.use(VueLazyload, {
       loading: require('./assets/img/common/placeholder.png')
     })
    
  3. 在组件 GoodsItem 组件中的图片标签中使用 v-lazy 指令,这样就可以使用懒加载的图片了
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容