外卖webAPP(三)

一,搭建商品详情信息页面

注;新建shop路由组件,然后点击点餐的路由子组件(shopgoods),点击评价的路由子组件(shopratings),点击商家的路由子组件(shopInfo),顶部有个固定组件(shopheader)

在views中新建shop组件,再建shopgoods子组件,shopratings, shopInfo子组件,然后在compoents中新建shopheader组件

配置路由组件

import Shop from '@/views/Shop/shop.vue'
import shopGoods from '@/views/Shop/shopGoods'
import shopInfo from '@/views/Shop/shopInfo'
import shopRatings from '@/views/Shop/shopRatings'


 {
    path: '/shop',
    component: Shop,
    children: [
      {
        path: '/shop/shopgoods',
        component: shopGoods
      },
      {
        path: '/shop/shopinfo',
        component: shopInfo
      },
      {
        path: '/shop/shopratings',
        component: shopRatings
      },
      {
        path:'',
        redirect: '/shop/goods'
      }
    ]
  }

在shop组件中

<template>
  <div>
    <ShopHeader></ShopHeader>

    <div class="item">
      <div class="tab-item">
        <router-link to="/shop/goods"> 点餐</router-link>
      </div>
      <div class="tab-item">
        <router-link to="/shop/shopratings"> 评价</router-link>
      </div>
      <div class="tab-item">
        <router-link to="/shop/shopinfo"> 商家</router-link>
      </div>
    </div>

    <router-view></router-view>
  </div>
</template>

<script>
import ShopHeader from '@/components/ShopHeader'
export default {
  name: 'Shop',
  data() {
    return {}
  },
  components: {
    ShopHeader
  }
}
</script>

<style scoped lang="stylus"></style>

1.1, mockjs模拟请求操作,组件获取数据

创建mock文件夹,创建data.json, mockServer.js(提供服务,模拟接口)    安装mockjs  npm  i  mockjs

mockServer.js 文件内容

/*
使用mockjs提供mock数据接口
 */
import Mock from 'mockjs'
import data from './data.json'

// 返回goods的接口
Mock.mock('/goods', { code: 0, data: data.goods })
// 返回ratings的接口
Mock.mock('/ratings', { code: 0, data: data.ratings })
// 返回info的接口
Mock.mock('/info', { code: 0, data: data.info })

// export default ???  不需要向外暴露任何数据, 只需要保存能执行即可

在入口文件main.js导入mock,   

import './mock/MockServer.js' // 加载mockServer即可

在 api文件夹中新建mockAjax.js, 二次封装关于mock的ajax

// 对axios的二次封装
import axios from 'axios'
const service = axios.create({
  
  timeout: 4000
})

// 请求拦截器
service.interceptors.request.use(config => {
  return config
})

// 响应拦截器
service.interceptors.response.use(
  response => {
    return response.data
  },
  error => {
    alert('请求出错' + error.message || '未知错误')
    //以后不允许用户继续处理: 中断promise链
    return new Promise(() => {}) //返回pending状态的promise 中断
  }
)

export default service

在api---index.js中,使用mock的接口去封装接口函数

import mockAjax from './mockAjax'

 * 获取商家信息
 */
export const reqShopInfo = () => mockAjax.get('/info')

/**
 * 获取商家评价数组
 */
export const reqShopRatings = () => mockAjax.get('/ratings')

/**
 * 获取商家商品数组
 */
export const reqShopGoods = () => mockAjax.get('/goods')

在vuex中发送请求,获取数据

  // 异步获取商家信息
  async getShopInfo({ commit }) {
    const result = await reqShopInfo()
    if (result.code === 0) {
      const info = result.data
      commit('RECEIVE_INFO', info)
    }
  },

  // 异步获取商家评价列表
  async getShopRatings({ commit }) {
    const result = await reqShopRatings()
    if (result.code === 0) {
      const ratings = result.data
      commit('RECEIVE_RATINGS', ratings)
    }
  },

  // 异步获取商家商品列表
  async getShopGoods({ commit }) {
    const result = await reqShopGoods()
    if (result.code === 0) {
      const goods = result.data
      commit('RECEIVE_GOODS', goods)
    }
  }
  RECEIVE_INFO(state, info) {
    state.info = info
  },

  RECEIVE_RATINGS(state, ratings) {
    state.ratings = ratings
  },

  RECEIVE_GOODS(state, goods) {
    state.goods = goods
  }
  goods: [], // 商品列表
  ratings: [], // 商家评价列表
  info: {}, // 商家信息

在shop组件中dispatch到vuex,

  mounted() {
    this.$store.dispatch('getShopInfo')
  }

1.2, 搭建shopHeader组件页面

首先dispatch到vuex中,获取info商家的数据

 mounted() {
    this.$store.dispatch('getShopInfo')
  },
 computed: {
    ...mapState(['info']),

顶部左侧的返回按钮,需要路由返回

  <div class="shop-header">
    <nav class="shop-nav" :style="{backgroundImage: `url(${info.bgImg})`}">
      <a class="back" @click="$router.back()">
        <i class="iconfont icon-arrow_left"></i>
      </a>

填充数据,关于a.b.c三级表达式获取不到值的问题

将颜色类名动态获取效果

 data() {
    return {
      supportClasses: ['activity-green', 'activity-red', 'activity-orange']
    }

报错提示

此时有两种解决方式

第一种:在他们的祖先元素,做个v-if判断即可

<div class="shop-header-discounts" v-if="info.supports" >
      <div class="discounts-left">
        <div class="activity" :class="supportClasses[info.supports[0].type]">
          <span class="content-tag">
            <span class="mini-tag">{{info.supports[0].name}}</span>
          </span>
          <span class="activity-content ellipsis">{{info.supports[0].content}}</span>
        </div>

 第二种,在vuex的getters中直接定义supports数据

const getters = {
  info: state => state.info || {},
  supports: state => state.info.supports || []
}

在shopHeader中获取数据

  computed: {
    ...mapState(['info']),
    ...mapGetters(['supports']),
    // 对于a.b.c三级表达式未取到值的做法
    supportOne() {
      return this.supports[0] ? this.supports[0] : {}
    }
  }
<div class="shop-header-discounts">
      <div class="discounts-left">
        <div class="activity" :class="supportClasses[supportOne.type]">
          <span class="content-tag">
            <span class="mini-tag">{{ supportOne.name }}</span>
          </span>
          <span class="activity-content ellipsis">{{
            supportOne.content
          }}</span>
        </div>

一共有两个下拉框,一个时品牌信息下拉框,一个时优惠下拉框

此时需要用到变量标识(布尔值),点击按钮,v-show 动态显示弹框

  data() {
    return {
      supportClasses: ['activity-green', 'activity-red', 'activity-orange'],
      shopShow: false,
       supportShow: false
    }
  <div class="shop-header-discounts"  @click="toggleSupportShow">
 <div class="shop-content" @click="toggleShopShow">
  <div class="activity-sheet" v-show="supportShow">
      <div class="activity-sheet-content">
        <h2 class="activity-sheet-title">优惠活动</h2>
<div class="shop-brief-modal" v-show="shopShow">
      <div class="brief-modal-content">
        <h2 class="content-title">
          <span class="content-tag">
            <span class="mini-tag">品牌</span>
 methods:{
     toggleShopShow () {
        this.shopShow = !this.shopShow
      },

      toggleSupportShow () {
        this.supportShow = !this.supportShow
      }

关闭按钮也需要点击事件

   <div class="mask-footer" >
          <span class="iconfont icon-close"  @click="toggleShopShow"></span>
<div class="activity-sheet-close" @click="toggleSupportShow">
          <span class="iconfont icon-close"></span>
        </div>

两个弹框需要一个过渡

样式

   .shop-brief-modal
      position fixed
      top 0
      left 0
      right 0
      bottom 0
      display flex
      justify-content center
      align-items center
      z-index 52
      flex-direction column
      color #333
      &.fade-enter-active,&.fade-leave-active
        transition opacity 1s
      &.fade-enter,&.fade-leave-to
        opacity 0
      .brief-modal-cover

弹框数据填充

 <transition name="fade">
      <div class="activity-sheet" v-show="supportShow">
        <div class="activity-sheet-content">
          <h2 class="activity-sheet-title">优惠活动</h2>
          <ul class="list">
            <li class="activity-item " v-for="(support, index) in supports" :key="support.type" :class="supportClasses[support.type]">
              <span class="content-tag">
                <span class="mini-tag">{{support.name}}</span>
              </span>
              <span class="activity-content"
                >{{support.content}}</span
              >
            </li>

1.3,shopgoods组件页面搭建

先dispatch到vuex中,获取数据

  mounted() {
    this.$store.dispatch('getShopGoods'
  computed: {
    ...mapState(['goods'])
  }

数据填充到模板

实现2个列表滑动, 1.当前分类,  2.当滑动右侧列表时,更新当前分类

需要用到better-scroll第三方插件, http://ustbhuangyi.github.io/better-scroll/

https://better-scroll.github.io/docs/zh-CN/guide/base-scroll-options.html  (重要)

安装; npm install better-scroll -S  # 安装带有所有插件的 BetterScroll

引入,import BetterScroll from 'better-scroll'

better-scroll插件和swiper插件一样,需要先监视数据,然后在this,$nextTick()
需要传一个回调函数,只要vuex中数据获取到了,即可通知组件更新better-scroll的创建
<div class="menu-wrapper" ref="menuWrapper">
        <ul>
          <li
            class="menu-item current"
            v-for="(good, index) in goods"
            :key="index"
          >
            <span class="text bottom-border-1px">
              <img class="icon" :src="good.icon" v-if="good.icon" />
              {{ good.name }}
            </span>
          </li>
        </ul>
      </div>

.menu-wrapper为包裹容器的div

  mounted() {
    this.$store.dispatch('getShopGoods', () => {// 数据更新后执行
      this.$nextTick(() => { // 页面更新显示后执行
        new BetterScroll('.menu-wrapper')
        new BetterScroll('.foods-wrapper')
      })
    })
  },
  // 异步获取商家商品列表
  async getShopGoods({ commit },callback) {
    const result = await reqShopGoods()
    if (result.code === 0) {
      const goods = result.data
      commit('RECEIVE_GOODS', goods)
      // 数据更新了, 通知一下组件
      callback && callback()
    }
  }

 1.4,创建BetterScroll,给创建BetterScroll绑定滑动监听(scroll),已经结束监听(scroll-End),计算滚动的Y轴位置

 

 逻辑分析,滑动右侧食物的列表,自动定位到左侧食物类别的名称。。右侧每个li类别有个颜色类名current,我们计算一个索引变量(currentIndex),条件(tops,scrollY)

tops数组的findIndex()遍历,条件,找到滚动在当前的位置是否在当前li和下一个li之间,找到li的索引值, 然后左侧食物类别,该currentIndex和模板遍历的li的index判断,

current类名是否生效。

 

1.获取右侧食物滑动的滚动的y轴位置,

2.获取每个右侧食物类别的高度(li),将他们组成一个数组

3.计算滚动到当前位置时右侧食物类别(li)的索引,此时的索引就是左侧食物类别的索引

有个小bug,如果左侧食物快速滚动,右侧食物类别不会自动滚动,如要监听scrollEnd事件,让滚动强行停止,计算scrollY坐标

  data() {
    return {
      scrollY: 0,
      tops: []
     
    }
  },

  mounted() {
    this.$store.dispatch('getShopGoods', () => {
      // 数据更新后执行
      this.$nextTick(() => {
        // 页面更新显示后执行
        this._initScroll()
        this._initTops()
      })
    })
  },

  computed: {
    ...mapState(['goods']),
    currentIndex() {
      // 得到条件数据
      const { tops, scrollY } = this
      // 根据条件计算产生一个结果,计算鼠标的y轴位置在哪个li区间,返回的是符合条件的第一个索引
      const index = tops.findIndex((item, index) => {
        // scrollY>=当前top && scrollY<下一个top,  不能小于等于下一个
        return scrollY >= item && scrollY < tops[index + 1]
      })
      return index
    }
  },

  methods: {
    // 计算滚动后的位置
    _initScroll() {
      // 列表显示之后创建BetterScroll
      new BetterScroll('.menu-wrapper')
      this.foodsScroll = new BetterScroll('.foods-wrapper', {
        probeType: 2, // 因为惯性滑动不会触发
        click: true
      })

      // 给右侧列表绑定scroll监听,获取y轴坐标
      this.foodsScroll.on('scroll', ({ x, y }) => {
        // 绝对值
        this.scrollY = Math.abs(y)
        // console.log(x, this.scrollY)
      })

      // 给右侧列表绑定scroll结束的监听
      this.foodsScroll.on('scrollEnd', ({ x, y }) => {
        console.log('scrollEnd', x, y)
        this.scrollY = Math.abs(y)
      })
    },

    //计算li的top值,组成一个数组
    _initTops() {
      const tops = []
      let top = 0
      tops.push(top)
      // 找到所有分类的li,获取他们的top值
      const lis = this.$refs.foodsUl.getElementsByClassName('food-list-hook')
      // const lis = this.$refs.foodsUl.children
      // 伪数组转换成真数组
      Array.from(lis).forEach(item => {
        top += item.clientHeight
        tops.push(top)
      })

      this.tops = tops
      console.log(this.tops)
    }

模板,左侧食物类别名称

  <div class="menu-wrapper" ref="menuWrapper">
        <ul>
          <li
            class="menu-item"
            v-for="(good, index) in goods"
            :key="index"
            :class="{ current: index === currentIndex }"
          >
            <span class="text bottom-border-1px">
              <img class="icon" :src="good.icon" v-if="good.icon" />
              {{ good.name }}
            </span>
          </li>
        </ul>

右侧食物信息

  <div class="foods-wrapper" ref="foodsWrapper">
        <ul ref="foodsUl">
          <li
            class="food-list-hook"
            v-for="(good, index) in goods"
            :key="index"
          >

此时,滑动右侧食物的列表,自动定位到左侧食物类别的名称。

点击左侧食物类别名称,左侧的食物信息自动滑动到当前位置

  <div class="menu-wrapper" ref="menuWrapper">
        <ul>
          <li
            class="menu-item"
            v-for="(good, index) in goods"
            :key="index"
            :class="{ current: index === currentIndex }"
            @click="clickMenuItem(index)"
          >
    // 点击左侧食物类别名称,自动滑动右侧的食物信息
    clickMenuItem(index) {
      // 获取当前的食物信息位置
      const scrollY = this.tops[index]
      // 立即更新scrollY(让点击的分类项成为当前分类)
      this.scrollY = scrollY
      // 让左侧食物信息自动滚动到当前食物
      this.foodsScroll.scrollTo(0, -scrollY, 300)
    }

 1.5,在右侧食物信息中,将加减数量功能拆分一个组件CartControl,

 在shopGoods中定义子组件CartControl

    <ul ref="foodsUl">
          <li class="food-list-hook" v-for="(good, index) in goods" :key="index">
            <h1 class="title">{{good.name}}</h1>
            <ul>
              <li class="food-item bottom-border-1px" v-for="(food, index) in good.foods"
                  :key="index" @click="showFood(food)">
                <div class="icon">
                  <img width="57" height="57" :src="food.icon">
                </div>
                <div class="content">
                  <h2 class="name">{{food.name}}</h2>
                  <p class="desc">{{food.description}}</p>
                  <div class="extra">
                    <span class="count">月售{{food.sellCount}}份</span>
                    <span>好评率{{food.rating}}%</span>
                  </div>
                  <div class="price">
                    <span class="now">¥{{food.price}}</span>
                    <span class="old" v-if="food.oldPrice">¥{{food.oldPrice}}</span>
                  </div>
                  <div class="cartcontrol-wrapper">
                    <CartControl :food="food"/>
                  </div>

 在cartControl组件中,

1.页面渲染时,默认是没有商品数量的,food中没有count数量属性,此时,点击加减号按钮,需要dispatch到vuex中去,在vuex中更改数据count

需要用到Vue.set(),来动态绑定自定义属性。起初没有数量,默认是没有减号按钮的

2.判断是否是加减按钮,需要传递一个标识来判断,布尔值

 props: {
    food: Object
  },
<template>
  <div class="cartcontrol">
    <!-- 减号 -->
    <div class="iconfont icon-remove_circle_outline" v-if="food.count"  @click="updateFoodCount(false)"></div>
    <div class="cart-count" v-if="food.count">{{food.count}}</div>
    <!-- 加号 -->
    <div class="iconfont icon-add_circle" @click="updateFoodCount(true)"></div>
  </div>
</template>
 methods:{
    updateFoodCount(isAdd){
      // 分发到vuex,去更改food.count数量
      this.$store.dispatch('updateFoodCount',{food:this.food, isAdd})
    }
  }

在vuex中

// 同步更新food中的count值
  updateFoodCount({ commit }, { isAdd, food }) {
    // 判断加减
    if (isAdd) {
      commit('INCREMENT_FOOD_COUNT', food)
    } else {
      // 减号
      commit('DECREMENT_FOOD_COUNT', food)
    }
  }
  INCREMENT_FOOD_COUNT(state, food) {
    if(!food.count){ // 第一次增加判断,不然count是Nan
      // food.conut= 1 // 新增属性(没有数据绑定)
      /*
      对象
      属性名
      属性值
       */
      Vue.set(food,'count',1) // 让新增的属性也有数据绑定
    }else{
      food.count++
    }
    
  },

  DECREMENT_FOOD_COUNT(state, food) {
    if (food.count) {  // 只有有值才去减
      food.count--
    }
  }

3. 当点击加号按钮,然后点击减号数量,知道数量为0,那么减号和数量都会消失,v-if, 给减号的消失加一个动画,旋转向右消失

<template>
  <div class="cartcontrol">
    <transition name="move">
      <!-- 减号 -->
       <div class="iconfont icon-remove_circle_outline" v-if="food.count"  @click="updateFoodCount(false)"></div>
    </transition>
    
    <div class="cart-count" v-if="food.count">{{food.count}}</div>
    <!-- 加号 -->
    <div class="iconfont icon-add_circle" @click="updateFoodCount(true)"></div>
  </div>
</template>
 .icon-remove_circle_outline
     display: inline-block
     padding 6px
     line-height 24px
     font-size 24px
     color $green
     &.move-enter-active, &.move-leave-active
       transition all .3s
     &.move-enter, &.move-leave-to
       opacity 0
       transform translateX(15px) rotate(180deg)

1.6,食物信息弹框功能在shopGoods中定义子组件food

食物信息弹框功在shopGoods中定义子组件food,点击每个食物li,弹框,点击返回按钮,弹框消失

给子组件绑定一个监听事件,当子组件点击返回按钮,通知父组件,更改isFoodShow

 data() {
    return {
      scrollY: 0,
      tops: [],
      food:{},
      isFoodShow: false,

    }
    // 点击右侧每个食物,弹框
    showFood(food){
      // 数据赋值
      this.food = food
     this.isFoodShow = true
    },

在子组件food中,点击返回按钮,

  props:{
    food: Object
  },
  <div class="back" @click="toggleShow">
            <i class="iconfont icon-arrow_left"></i>
          </div>
  // 点击返回按钮
    toggleShow(){
      // 给父组件传递信息,让他修改变量isShowFood
      this.$emit('foodShow')
    }

父组件shopGoods

 // 子组件向父组件传递信息
    foodShow(){
      this.isFoodShow = false
    }

foods模板填充数据,也有一个加减数量的组件

    <div class="food">
      <div class="food-content">
        <div class="image-header">
          <img
           :src="food.image" 
          />
          <p class="foodpanel-desc">{{food.info}}</p>
          <div class="back" @click="toggleShow">
            <i class="iconfont icon-arrow_left"></i>
          </div>
        </div>
        <div class="content">
          <h1 class="title">{{food.name}}</h1>
          <div class="detail">
            <span class="sell-count">月售 {{food.sellCount}} 份</span>
            <span class="rating">好评率 {{food.rating}}%</span>
          </div>
          <div class="price">
            <span class="now">¥{{food.price}}</span>
            <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
          </div>
          <div class="cartcontrol-wrapper">
            <CartControl :food="food"></CartControl>
          </div>
        </div>
      </div>
      <div class="food-cover"  @click="toggleShow"></div>
    </div>

此时有个小bug,点击右侧食物列表的li的加号按钮,应该是出现加减号,以及数量,但是food弹框出现了

因为点击加号的时候(updateFoodCount),事件冒泡出来了,传递到了li的点击事件showFood

解决方法;将点击加减号的click事件设置阻止冒泡
  <ul>
              <li
                class="food-item bottom-border-1px"
                v-for="(food, index) in good.foods"
                :key="index"
                @click="showFood(food)"
              >
                <div class="icon">
                  <img width="57" height="57" :src="food.icon" />
                </div>
                <div class="content">
                  <h2 class="name">{{ food.name }}</h2>
                  <p class="desc">{{ food.description }}</p>
                  <div class="extra">
                    <span class="count">月售 {{ food.sellCount }} 份</span>
                    <span>好评率 {{ food.rating }}%</span>
                  </div>
                  <div class="price">
                    <span class="now">¥{{ food.price }}</span>
                    <span class="old" v-if="food.oldPrice"
                      >¥{{ food.oldPrice }}</span
                    >
                  </div>
                  <div class="cartcontrol-wrapper">
                    <CartControl :food="food"></CartControl>
                  </div>
                </div>
              </li>
CartControl组件
<template>
  <div class="cartcontrol">
    <transition name="move">
      <!-- 减号 -->
       <div class="iconfont icon-remove_circle_outline" v-if="food.count"  @click="updateFoodCount(false)"></div>
    </transition>
    
    <div class="cart-count" v-if="food.count">{{food.count}}</div>
    <!-- 加号 -->
    <div class="iconfont icon-add_circle" @click="updateFoodCount(true)"></div>
  </div>
</template>

解决方式

<template>
  <div class="cartcontrol">
    <transition name="move">
      <!-- 减号 -->
       <div class="iconfont icon-remove_circle_outline" v-if="food.count"  @click.stop="updateFoodCount(false)"></div>
    </transition>
    
    <div class="cart-count" v-if="food.count">{{food.count}}</div>
    <!-- 加号 -->
    <div class="iconfont icon-add_circle" @click.stop="updateFoodCount(true)"></div>
  </div>

1.7,底部购物车功能实现shopCart, shopGoods的子组件

新建shopCart组件,功能逻辑

1.在vuex中自定义一个购物车商品空数组cartFoods,当点击食物添加按钮,此时需要将该food对象push到cartFood数组中

2.然后在vuex中getters中计算中食物总数量totalCount, 食物总价格totalPrice,

3.在shopCart中,从vuex获取info,totalCount,totalPrice

4.highlight类名只要有食物数量才会高亮,并且有才会显示, 右下角的的起送费有三种不同的文本,并且有个enough类名显示高亮,not-enough不显示高亮

判断依据是总价格是否大于最小的配送价格

5.点击食物减号,当见到食物数量为0,需要将当前food对象从cartFoods移除
 
vuex中state
 info: {}, // 商家信息
  cartFoods: [], // 购物车中食物的列表

vuex中getters

const getters = {
  info: state => state.info || {},
  supports: state => state.info.supports || [],
  totalCount: state =>
    state.cartFoods.reduce((pre, item) => pre + item.count, 0),

  // totalCount(state){
  //   return state.cartFoods.reduce((pre,item)=> pre + item.count, 0)
  // },

  totalPrice: state =>
    state.cartFoods.reduce((pre, item) => pre + item.count * item.price, 0)
}
 
 在shopCart获取
  computed: {
    ...mapGetters(['totalCount', 'totalPrice']),
    ...mapState(['info', 'cartFoods']),
    // 类名判断
    PayClass(){
      const {totalPrice} = this
      const {minPrice} = this.info
      return totalPrice < minPrice ? ' not-enough' : ' enough'
      
    },

    // 支付文本判断,等于0, 小于minPrice  大于minPrice
    PayText(){
      const {totalPrice} = this
      const {minPrice} = this.info
      if(totalPrice ===0){
        return `¥${minPrice}元起送`
      }else if(totalPrice < minPrice){
        return `还差${minPrice - totalPrice}元起送`
      }else{
        return `结算`
      }
    }

模板填充

<div class="shopcart">
      <div class="content">
        <div class="content-left">
          <div class="logo-wrapper">
            <div class="logo " :class="{ highlight :totalCount}">
              <i class="iconfont icon-shopping_cart " :class="{ highlight :totalCount}"></i>
            </div>
            <div class="num" v-if="totalCount">{{totalCount}}</div>
          </div>
          <div class="price " :class="{highlight: totalCount}">¥{{totalPrice}}</div>
          <div class="desc">另需配送费¥{{info.deliveryPrice}}元</div>
        </div>
        <div class="content-right">
          <div class="pay not-enough" :class="PayClass">
            {{PayText}}
          </div>
        </div>
      </div>

在vuex的添加和减少逻辑中,添加food,移除food

  INCREMENT_FOOD_COUNT(state, food) {
    if (!food.count) {
      // 第一次增加判断,不然count是Nan
      // food.conut= 1 // 新增属性(没有数据绑定)
      /*
      对象
      属性名
      属性值
       */
      Vue.set(food, 'count', 1) // 让新增的属性也有数据绑定
       // 将food添加到cartFoods中
      state.cartFoods.push(food)
    } else {
      food.count++
    }
  },

  DECREMENT_FOOD_COUNT(state, food) {
    if (food.count) {
      // 只有有值才去减
      food.count--
      if(food.count ===0){
        // 将food从cartFoods中移除,找到当前食物的索引
        state.cartFoods.splice(state.cartFoods.indexOf(food),1)
      }
    }
  }
 1.8, 购物车列表显示功能

点击购物车黑色方块,显示购物车列表,再次点击购物车黑色方块,清空按钮,外部的遮罩层购物车列表消失

 data() {
    return {
      isShow:false,
    }
 <div class="shopcart">
      <div class="content">
    购物车黑色部分 <div class="content-left" @click="toggleShow">
   <div class="shopcart-list" >
        <div class="list-header">
          <h1 class="title">购物车</h1>
          <span class="empty" @click="toggleShow">清空</span>
        </div>
        <div class="list-content" v-show="isShow">
          <ul>
            <li class="food" v-for="(cartFood, index) in cartFoods" :key="index">
              <span class="name">{{cartFood.name}}</span>
              <div class="price"><span>¥{{cartFood.price}}</span></div>
              <div class="cartcontrol-wrapper">
                <div class="cartcontrol">
                  <!-- 加减按钮 -->
                  <CartControl :food="cartFood"></CartControl>
                </div>
              </div>
            </li>
          </ul>
        </div>
      </div>
    </div>
  遮罩层, <div class="list-mask" @click="toggleShow" v-show="isShow" ></div>
  methods:{
    toggleShow(){
      this.isShow = !this.isShow
    }
 1.9,此时会出现3个bug
1.当购物车列表的总数为0,弹框依然会存在,修改,当总数为0时,切换变量标识为false, 如果总数大于0,为true
2. 此时当购物车列表总数为0,弹框不会出现,当下一次添加食物数量,购物车弹框自动出来, 因为第一次的isShow为true,而且有数量了,就会自动出现购物车弹框
修改,当购物车数量为0,isShow设置为false即可
3. 当点击黑色方块时,在添加一个食物数量,购物车弹框自动出现,应为点击黑色方框,isShow为true, 改正,在点击toggleShow, 只要有数量才可以改变isShow状态
      <div class="shopcart-list" v-show="listShow">
        <div class="list-header" >
          <h1 class="title">购物车</h1>
          <span class="empty" @click="toggleShow">清空</span>
        </div>
        <div class="list-content" >
          <ul>
            <li class="food" v-for="(cartFood, index) in cartFoods" :key="index">
              <span class="name">{{cartFood.name}}</span>
              <div class="price"><span>¥{{cartFood.price}}</span></div>
              <div class="cartcontrol-wrapper">
                <div class="cartcontrol">
                  <!-- 加减按钮 -->
                  <CartControl :food="cartFood"></CartControl>
                </div>
              </div>
            </li>
          </ul>
        </div>
      </div>
    </div>
    <div class="list-mask" @click="toggleShow"  v-show="listShow" ></div>
  data() {
    return {
      isShow:false,
    }
  },

计算显示和影藏的标识

  // 购物车显示与影藏
    listShow(){
      if(this.totalCount ===0){ //总数量为0,关闭列表弹框
        //关闭最后一个食物后,让isShow为false,防止下一次再增加食物后,自动弹框
        this.isShow = false
        return false
      }

      // 总数量大于0
      return this.isShow
    }
 methods:{
    toggleShow(){
      // 只有当总数量大于0时切换
      if(this.totalCount){
        this.isShow = !this.isShow
      }
      
    }

1.10, 实现购物车列表的滚动功能

在购物车列表显示的时候创建scroll实例,注,必须创建单个实例scroll,不然对加减按钮有影响,需要判断之前是否有scroll实例

   // 购物车显示与影藏
    listShow(){
      if(this.totalCount ===0){ //总数量为0,关闭列表弹框
        //关闭最后一个食物后,让isShow为false,防止下一次再增加食物后,自动弹框
        this.isShow = false
        return false
      }

      if(this.isShow){
        this.$nextTick(()=>{
          // 实现BScroll的实例是一个单例
          if(!this.scroll){
            this.scroll =   new BScroll('.list-content',{
            click : true
            })
          }else{
            // 已经创建滚动实例了
            this.scroll.refresh() // 让滚动条刷新一下: 重新统计内容的高度

          }
        })

      }

      // 总数量大于0
      return this.isShow
    }

1.11,点击清空按钮,清空购物车的食物

在vuex中,清空每个food的count属性,清空cartFoods数组

   <div class="shopcart-list" v-show="listShow">
          <div class="list-header">
            <h1 class="title">购物车</h1>
            <span class="empty" @click="clearCart">清空</span>
          </div>
   // 点击清空按钮
    async clearCart() {
      let result = await this.$MessageBox
        .confirm('确定执行此操作?')
        .catch(error => error)
      if (result === 'confirm') {
        this.$store.dispatch('clearCart')
      } else if (result === 'cancel') {
        console.log('已取消')
      }
    }
clearCart({commit}){
    commit('CLEAR_CART')
  }
  CLEAR_CART(state){
    // 清除food中的count
    state.cartFoods.forEach((item) =>  Vue.delete(item, 'count'))  
    // 不行,自定义属性不能这样删除
    // state.cartFoods.forEach((item) =>  delete item.count)  
    // 移除购物车中所有购物项
    state.cartFoods = []
  }
原文地址:https://www.cnblogs.com/fsg6/p/14320349.html