购物街项目(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 指令,这样就可以使用懒加载的图片了
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335