【vue3.0】16.0 某东到家(十六)——底部购物车展开页(二)

接下来·进一步完善底部购物模块,完善之前需要准备2个图标,同样将图标按钮放到项目中。


image.png

image.png

修改src\views\shop\Cart.vue

<template>
  <div class="cart">
    <div class="product">
      <template v-for="item in productList" :key="item._id">
        <div class="product__item" v-if="item.count > 0">
          <div class="product__item__checked">
            <i class="custom-icon custom-icon-roundcheck"></i>
          </div>
          <img class="product__item__img" :src="item.imgUrl" />
          <div class="product__item__detail">
             ......
          </div>
        </div>
      </template>
    </div>
    <div class="check">
      ......
    </div>
  </div>
</template>
......
image.png

调整代码样式:

<template>
  <div class="cart">
    <div class="product">
      <template v-for="item in productList" :key="item._id">
        <div class="product__item" v-if="item.count > 0">
          <div class="product__item__checked">
            <i class="custom-icon  custom-icon-roundcheckfill"></i>
          </div>
          <img class="product__item__img" :src="item.imgUrl" />
          <div class="product__item__detail">
             ......
          </div>
        </div>
      </template>
    </div>
    <div class="check">
      ......
    </div>
  </div>
</template>
......
<style lang="scss" scoped>
@import '@/style/viriables.scss';
@import '@/style/mixins.scss';
.cart {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
}
.product {
  overflow-y: scroll;
  flex: 1;
  &__item {
    position: relative;
    display: flex;
    padding: 0.12rem 0.16rem;
    margin: 0 0.16rem;
    border-bottom: 0.01rem solid $content-bg-color;
    &__checked {
      line-height: 0.5rem;
      margin-right: 0.2rem;
      color: #0091ff;
      i {
        font-size: 0.25rem;
      }
    }
  }
}
......
</style>

image.png

接下来增加处理逻辑。
维护src\store\index.js

import { createStore } from 'vuex'

export default createStore({
  state: {
    cartList: {
      // 第一层级:商铺的id
      // 第二层内容是商品内容以及购物数量
      // shopId: {
      //   productID: {
      //     _id: '1',
      //     name: '番茄250g/份',
      //     imgUrl: '/i18n/9_16/img/tomato.png',
      //     sales: 10,
      //     price: 33.6,
      //     oldPrice: 39.6,
      //     count: 0
      //   }
      // }
    }
  },
  mutations: {
    changeItemToCart(state, payload) {
      const { shopId, productId, productInfo, num } = payload
      // console.log(shopId, productId, productInfo)
      let shopInfo = state.cartList[shopId]
      if (!shopInfo) {
        shopInfo = {}
      }

      let product = shopInfo[productId]
      if (!product) {
        product = productInfo // 初始化
        product.count = 0
      }

      product.count += num
      if (num > 0) {
        product.checked = true
      }
      if (product.count <= 0) {
        shopInfo[productId].count = 0
        delete state.cartList[shopId]
      }
      shopInfo[productId] = product
      // 赋值
      state.cartList[shopId] = shopInfo
    }
  },
  actions: {},
  modules: {}
})

调整``:

<template>
  <div class="cart">
    <div class="product">
      <template v-for="item in productList" :key="item._id">
        <div class="product__item" v-if="item.count > 0">
          <div class="product__item__checked">
            <i
              :class="[
                'custom-icon',
                item.checked == true
                  ? 'custom-icon-roundcheckfill'
                  : 'custom-icon-roundcheck'
              ]"
            ></i>
          </div>
          <img class="product__item__img" :src="item.imgUrl" />
          <div class="product__item__detail">
           ......
          </div>
        </div>
      </template>
    </div>
    <div class="check">
     ......
    </div>
  </div>
</template>
......

这样按钮随着绑定是否选中而变化。

接下来增加勾选的响应事件:

<template>
  <div class="cart">
    <div class="product">
      <template v-for="item in productList" :key="item._id">
        <div class="product__item" v-if="item.count > 0">
          <div
            class="product__item__checked"
            @click="changeCartItemChecked(shopId, item._id)"
          >
            <i
              :class="[
                'custom-icon',
                item.checked == true
                  ? 'custom-icon-roundcheckfill'
                  : 'custom-icon-roundcheck'
              ]"
            ></i>
             ......
          </div>
        </div>
      </template>
    </div>
    <div class="check">
      ......
    </div>
  </div>
</template>

<script>
import { computed } from 'vue'
import { useRoute } from 'vue-router' // 路由跳转方法
import { useStore } from 'vuex' // 路由跳转方法
import { useCommonCartEffect } from './commnCartEffect'
const useCartEffect = shopId => {
  const { changeCartItemInfo } = useCommonCartEffect()
  const store = useStore()
  // 单个勾选或者不勾选
  const changeCartItemChecked = (shopId, productId) => {
    store.commit('changeItemChecked', { shopId, productId })
  }

  // 计算shopId下所有cartList的商品数量total、价钱之和totalPrice
  const cartList = store.state.cartList // 加入购物车的商品列表
  const total = computed(() => {
    const productList = cartList[shopId]
    let count = 0
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        count += product.count
      }
    }
    return count
  })
  const totalPrice = computed(() => {
    const productList = cartList[shopId]
    let count = 0
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        if (product.checked === true) {
          count += product.count * product.price
        }
      }
    }
    return count.toFixed(2) // 保留2位小数
  })
  const productList = computed(() => {
    const productList = cartList[shopId] || [] // 不存在默认空数组
    return productList
  })
  return {
    total,
    totalPrice,
    productList,
    changeCartItemChecked,
    changeCartItemInfo
  }
}
export default {
  name: 'Cart',
  setup() {
    const route = useRoute()
    const shopId = route.params.id // 店铺id
    // 计算总价和加入购物车的总数量
    const {
      total,
      totalPrice,
      productList,
      changeCartItemChecked,
      changeCartItemInfo
    } = useCartEffect(shopId)

    return {
      total,
      totalPrice,
      productList,
      shopId,
      changeCartItemChecked,
      changeCartItemInfo
    }
  }
}
</script>
<style lang="scss" scoped>
......
</style>

调整src\store\index.js

import { createStore } from 'vuex'

export default createStore({
  state: {
    cartList: {
      // 第一层级:商铺的id
      // 第二层内容是商品内容以及购物数量
      // shopId: {
      //   productID: {
      //     _id: '1',
      //     name: '番茄250g/份',
      //     imgUrl: '/i18n/9_16/img/tomato.png',
      //     sales: 10,
      //     price: 33.6,
      //     oldPrice: 39.6,
      //     count: 0
      //   }
      // }
    }
  },
  mutations: {
    changeItemToCart(state, payload) {
      const { shopId, productId, productInfo, num } = payload
      // console.log(shopId, productId, productInfo)
      let shopInfo = state.cartList[shopId]
      if (!shopInfo) {
        shopInfo = {}
      }

      let product = shopInfo[productId]
      if (!product) {
        product = productInfo // 初始化
        product.count = 0
      }

      product.count += num
      if (num > 0) {
        product.checked = true
      }
      if (product.count <= 0) {
        shopInfo[productId].count = 0
        delete state.cartList[shopId]
      }
      shopInfo[productId] = product
      // 赋值
      state.cartList[shopId] = shopInfo
    },
    // 购物车勾选记录
    changeItemChecked(state, payload) {
      const { shopId, productId } = payload
      const product = state.cartList[shopId][productId]
      product.checked = !product.checked
    }
  },
  actions: {},
  modules: {}
})

接下来增加“全选”和“清空购物车”功能。
修改src\views\shop\Cart.vue:

<template>
  <div class="cart">
    <div class="product">
      <div class="product__header">
        <div class="product__header__all">
          <i :class="['custom-icon', 'custom-icon-roundcheckfill']">全选</i>
        </div>
        <div class="product__header__clear">清空购物车</div>
      </div>
      <template v-for="item in productList" :key="item._id">
      ......
      </template>
    </div>
    <div class="check">
      ......
    </div>
  </div>
</template>

<script>
......
</script>
<style lang="scss" scoped>
@import '@/style/viriables.scss';
@import '@/style/mixins.scss';
.cart {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
}
.product {
  overflow-y: scroll;
  flex: 1;
  background: #fff;
  &__header {
    display: flex;
    line-height: 0.52rem;
    border-bottom: 0.01rem solid #f1f1f1;
    &__all {
      width: 0.64rem;
    }
    &__clear {
      flex: 1;
      text-align: right;
    }
  }
}
......
</style>
image.png

‘’
以上代码优化如下:
src\store\index.js

import { createStore } from 'vuex'

export default createStore({
 state: {
   cartList: {
     // 第一层级:商铺的id
     // 第二层内容是商品内容以及购物数量
     // shopId: {
     //   productID: {
     //     _id: '1',
     //     name: '番茄250g/份',
     //     imgUrl: '/i18n/9_16/img/tomato.png',
     //     sales: 10,
     //     price: 33.6,
     //     oldPrice: 39.6,
     //     count: 0
     //   }
     // }
   }
 },
 mutations: {
   /**
    * 加入或减少购物车数量
    * @param {*} state
    * @param {String} shopId 店铺id
    * @param {String} productId 商品id
    * @param {Object} productInfo 商品信息集
    * @param {Number} num 加入购物车的数量
    * @param {*} payload
    */
   changeItemToCart(state, payload) {
     const { shopId, productId, productInfo, num } = payload
     // console.log(shopId, productId, productInfo)
     let shopInfo = state.cartList[shopId]
     if (!shopInfo) {
       shopInfo = {}
     }

     let product = shopInfo[productId]
     if (!product) {
       product = productInfo // 初始化
       product.count = 0
     }

     product.count += num
     if (num > 0) {
       product.checked = true
     }
     if (product.count <= 0) {
       shopInfo[productId].count = 0
       // delete state.cartList[shopId]
     }
     shopInfo[productId] = product
     // 赋值
     state.cartList[shopId] = shopInfo
   },
   // 购物车勾选记录
   changeItemChecked(state, payload) {
     const { shopId, productId } = payload
     const product = state.cartList[shopId][productId]
     product.checked = !product.checked
   },
   // 清除购物车
   changeCleanCartProducts(state, payload) {
     const { shopId } = payload
     state.cartList[shopId] = {}
   }
 },
 actions: {},
 modules: {}
})

src\views\shop\commnCartEffect.js

import { toRefs } from 'vue'
import { useStore } from 'vuex'
// 添加、减少到购物车功能
export const useCommonCartEffect = () => {
  const store = useStore()
  const { cartList } = toRefs(store.state)
  /**
   * 加入或减少购物车数量
   * @param {String} shopId 店铺id
   * @param {String} productId 商品id
   * @param {Object} productInfo 商品信息集
   * @param {Number} num 加入购物车的数量
   */
  const changeCartItemInfo = (shopId, productId, productInfo, num) => {
    console.log(
      'changeItemToCart:',
      'shopId:' + shopId,
      'productId:' + productId,
      'productInfo:' + JSON.stringify(productInfo),
      'num:' + num
    )
    // 更新vuex中的值
    store.commit('changeItemToCart', { shopId, productId, productInfo, num })
  }

  return { cartList, changeCartItemInfo }
}

src\views\shop\Cart.vue

<template>
  <div class="cart">
    <div class="product">
      <div class="product__header">
        <div class="product__header__all">
          <i
            :class="[
              'product__header__all__icon',
              'custom-icon',
              'custom-icon-roundcheckfill'
            ]"
          ></i>
          <span class="product__header__all__text">全选</span>
        </div>
        <div class="product__header__clear" @click="cleanCartProducts(shopId)">
          清空购物车
        </div>
      </div>
      <template v-for="item in productList" :key="item._id">
        <div class="product__item" v-if="item.count > 0">
          <div
            class="product__item__checked"
            @click="changeCartItemChecked(shopId, item._id)"
          >
            <i
              :class="[
                'custom-icon',
                item.checked == true
                  ? 'custom-icon-roundcheckfill'
                  : 'custom-icon-roundcheck'
              ]"
            ></i>
          </div>
          <img class="product__item__img" :src="item.imgUrl" />
          <div class="product__item__detail">
            <h4 class="product__item__title">{{ item.name }}</h4>
            <p class="product__item__price">
              <span class="product__item__yen"> &yen;{{ item.price }} </span>
              <span class="product__item__origin">
                &yen;{{ item.oldPrice }}
              </span>
            </p>
          </div>
          <div class="product__number">
            <span
              class="product__number__minus"
              @click="
                () => {
                  0
                  changeCartItemInfo(shopId, item._id, item, -1)
                }
              "
              >-</span
            >
            {{ cartList?.[shopId]?.[item._id]?.count || 0 }}
            <span
              class="product__number__plus"
              @click="
                () => {
                  changeCartItemInfo(shopId, item._id, item, 1)
                }
              "
              >+</span
            >
          </div>
        </div>
      </template>
    </div>
    <div class="check">
      <div class="check__icon">
        <img src="/i18n/9_16/img/basket.png" alt="" class="check__icon__img" />
        <div class="check__icon__tag">{{ total }}</div>
      </div>
      <div class="check__info">
        总计:<span class="check__info__price">&yen; {{ totalPrice }}</span>
      </div>
      <div class="check__btn">去结算</div>
    </div>
  </div>
</template>

<script>
import { computed } from 'vue'
import { useRoute } from 'vue-router' // 路由跳转方法
import { useStore } from 'vuex' // 路由跳转方法
import { useCommonCartEffect } from './commnCartEffect'

const useCartEffect = shopId => {
  const { changeCartItemInfo } = useCommonCartEffect()
  const store = useStore()
  // 单个勾选或者不勾选
  const changeCartItemChecked = (shopId, productId) => {
    store.commit('changeItemChecked', { shopId, productId })
  }
  // 清除购物车按钮
  const cleanCartProducts = shopId => {
    store.commit('changeCleanCartProducts', { shopId })
  }

  // 计算shopId下所有cartList的商品数量total、价钱之和totalPrice
  const cartList = store.state.cartList // 加入购物车的商品列表
  const total = computed(() => {
    const productList = cartList[shopId]
    let count = 0
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        count += product.count
      }
    }
    return count
  })
  const totalPrice = computed(() => {
    const productList = cartList[shopId]
    let count = 0
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        if (product.checked === true) {
          count += product.count * product.price
        }
      }
    }
    return count.toFixed(2) // 保留2位小数
  })

  const productList = computed(() => {
    const productInfoList = cartList[shopId] || [] // 不存在默认空数组
    return productInfoList
  })
  return {
    cartList,
    total,
    totalPrice,
    productList,
    changeCartItemChecked,
    changeCartItemInfo,
    cleanCartProducts
  }
}
export default {
  name: 'Cart',
  setup() {
    const route = useRoute()
    const shopId = route.params.id // 店铺id
    // 计算总价和加入购物车的总数量
    const {
      cartList,
      total,
      totalPrice,
      productList,
      changeCartItemChecked,
      changeCartItemInfo,
      cleanCartProducts
    } = useCartEffect(shopId)
    return {
      cartList,
      total,
      totalPrice,
      productList,
      shopId,
      changeCartItemChecked,
      changeCartItemInfo,
      cleanCartProducts
    }
  }
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables.scss';
@import '@/style/mixins.scss';
.cart {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
}
.product {
  overflow-y: scroll;
  flex: 1;
  background: #fff;
  &__header {
    display: flex;
    line-height: 0.52rem;
    border-bottom: 0.01rem solid #f1f1f1;
    font-size: 0.14rem;
    color: #333333;
    &__all {
      width: 0.64rem;
      margin-left: 0.18rem;
      &__icon {
        height: 0.52rem;
      }
      &__text {
        display: inline-block;
        margin-left: 0.04rem;
        line-height: 0.52rem;
      }
    }
    &__clear {
      flex: 1;
      text-align: right;
      margin-right: 0.16rem;
    }
  }
  &__item {
    position: relative;
    display: flex;
    padding: 0.12rem 0.16rem;
    margin: 0 0.16rem;
    border-bottom: 0.01rem solid $content-bg-color;
    &__checked {
      line-height: 0.5rem;
      margin-right: 0.2rem;
      color: #0091ff;
      i {
        font-size: 0.25rem;
      }
    }
    // 配合解决超出长度以省略号显示而不会出现换行
    &__detail {
      overflow: hidden;
    }
    &__img {
      width: 0.46rem;
      height: 0.46rem;
      margin-right: 0.16rem;
    }
    &__title {
      margin: 0;
      line-height: 0.2rem;
      font-size: 0.14rem;
      color: $content-font-color;
      // 超出长度以省略号显示而不会出现换行
      @include ellipsis;
    }
    &__price {
      margin: 0.06rem 0 0 0;
      line-height: 0.2rem;
      font-size: 0.14rem;
      color: $height-light-font-color;
    }
    &__yen {
      font-size: 0.12rem;
    }
    &__origin {
      margin-left: 0.06rem;
      line-height: 0.2rem;
      font-size: 0.12rem;
      color: $light-font-color;
      text-decoration: line-through; //中划线
    }
    // 购物车选购数量和加减号
    .product__number {
      position: absolute;
      right: 0rem;
      bottom: 0.12rem;
      &__minus,
      &__plus {
        display: inline-block;
        width: 0.2rem;
        height: 0.2rem;
        line-height: 0.16rem;
        border-radius: 50%;
        font-size: 0.2rem;
        text-align: center;
      }
      // 边框白色
      &__minus {
        border: 0.01rem solid $medium-font-color;
        color: $medium-font-color;
        margin-right: 0.05rem;
      }
      //无边框,背景蓝色
      &__plus {
        color: $bg-color;
        background: $btn-bg-color;
        margin-left: 0.05rem;
      }
    }
  }
}
.check {
  display: flex;
  box-sizing: border-box; //往内塞入border
  line-height: 0.49rem;
  height: 0.49rem;
  border-top: 0.01rem solid $content-bg-color;
  &__icon {
    width: 0.84rem;
    position: relative;
    &__img {
      margin: 0.12rem auto;
      display: block;
      width: 0.28rem;
      height: 0.28rem;
    }
    &__tag {
      // 乘以2然后等比例缩小
      position: absolute;
      left: 0.46rem;
      top: 0.04rem;
      padding: 0 0.04rem;
      min-width: 0.2rem;
      height: 0.2rem;
      line-height: 0.2rem;
      text-align: center;
      background-color: $height-light-font-color;
      border-radius: 0.1rem;
      font-size: 0.12rem;
      color: $bg-color;
      transform: scale(0.5);
      transform-origin: left center;
    }
  }
  &__info {
    flex: 1;
    color: $content-font-color;
    font-size: 0.12rem;
    &__price {
      line-height: 0.49rem;
      color: $height-light-font-color;
      font-size: 0.18rem;
    }
  }
  &__btn {
    width: 0.98rem;
    background-color: #4fb0f9;
    text-align: center;
    color: $bg-color;
    font-size: 0.14rem;
  }
}
</style>

以上代码,微调,修复一些bug。

实现全选功能:

<template>
  <div class="cart">
    <div class="product">
      <div class="product__header">
        <div class="product__header__all">
          <i
            :class="[
              'product__header__all__icon',
              'custom-icon',
              allChecked
                ? 'custom-icon-roundcheckfill'
                : 'custom-icon-roundcheck'
            ]"
          ></i>
          <span class="product__header__all__text">全选</span>
        </div>
        <div class="product__header__clear" @click="cleanCartProducts(shopId)">
          清空购物车
        </div>
      </div>
......
</template>

<script>
import { computed } from 'vue'
import { useRoute } from 'vue-router' // 路由跳转方法
import { useStore } from 'vuex' // 路由跳转方法
import { useCommonCartEffect } from './commnCartEffect'

const useCartEffect = shopId => {
......
  // 全选的计算属性
  const allChecked = computed(() => {
    const productList = cartList[shopId]
    let result = true
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        if (product.count > 0 && !product.checked) {
          result = false
          break
        }
      }
    }
    return result
  })

......
  return {
    cartList,
    total,
    totalPrice,
    productList,
    allChecked,
    changeCartItemChecked,
    changeCartItemInfo,
    cleanCartProducts
  }
}
export default {
  name: 'Cart',
  setup() {
    const route = useRoute()
    const shopId = route.params.id // 店铺id
    // 计算总价和加入购物车的总数量
    const {
      cartList,
      total,
      totalPrice,
      productList,
      allChecked,
      changeCartItemChecked,
      changeCartItemInfo,
      cleanCartProducts
    } = useCartEffect(shopId)
    return {
      cartList,
      total,
      totalPrice,
      productList,
      shopId,
      allChecked,
      changeCartItemChecked,
      changeCartItemInfo,
      cleanCartProducts
    }
  }
}
</script>
......

增加全选事件:

<template>
  <div class="cart">
    <div class="product">
      <div class="product__header">
        <div class="product__header__all" @click="setCartItemsChecked(shopId)">
          <i
            :class="[
              'product__header__all__icon',
              'custom-icon',
              allChecked
                ? 'custom-icon-roundcheckfill'
                : 'custom-icon-roundcheck'
            ]"
          ></i>
          <span class="product__header__all__text">全选</span>
        </div>
        <div class="product__header__clear">
          <span @click="cleanCartProducts(shopId)">清空购物车</span>
        </div>
      </div>
      <template v-for="item in productList" :key="item._id">
        <div class="product__item" v-if="item.count > 0">
          <div
            class="product__item__checked"
            @click="changeCartItemChecked(shopId, item._id)"
          >
            <i
              :class="[
                'custom-icon',
                item.checked == true
                  ? 'custom-icon-roundcheckfill'
                  : 'custom-icon-roundcheck'
              ]"
            ></i>
          </div>
          <img class="product__item__img" :src="item.imgUrl" />
          <div class="product__item__detail">
            <h4 class="product__item__title">{{ item.name }}</h4>
            <p class="product__item__price">
              <span class="product__item__yen"> &yen;{{ item.price }} </span>
              <span class="product__item__origin">
                &yen;{{ item.oldPrice }}
              </span>
            </p>
          </div>
          <div class="product__number">
            <span
              class="product__number__minus"
              @click="
                () => {
                  0
                  changeCartItemInfo(shopId, item._id, item, -1)
                }
              "
              >-</span
            >
            {{ cartList?.[shopId]?.[item._id]?.count || 0 }}
            <span
              class="product__number__plus"
              @click="
                () => {
                  changeCartItemInfo(shopId, item._id, item, 1)
                }
              "
              >+</span
            >
          </div>
        </div>
      </template>
    </div>
    <div class="check">
      <div class="check__icon">
        <img src="/i18n/9_16/img/basket.png" alt="" class="check__icon__img" />
        <div class="check__icon__tag">{{ total }}</div>
      </div>
      <div class="check__info">
        总计:<span class="check__info__price">&yen; {{ totalPrice }}</span>
      </div>
      <div class="check__btn">去结算</div>
    </div>
  </div>
</template>

<script>
import { computed } from 'vue'
import { useRoute } from 'vue-router' // 路由跳转方法
import { useStore } from 'vuex' // 路由跳转方法
import { useCommonCartEffect } from './commnCartEffect'

const useCartEffect = shopId => {
  const { changeCartItemInfo } = useCommonCartEffect()
  const store = useStore()
  // 单个勾选或者不勾选
  const changeCartItemChecked = (shopId, productId) => {
    store.commit('changeItemChecked', { shopId, productId })
  }
  // 清除购物车按钮
  const cleanCartProducts = shopId => {
    store.commit('changeCleanCartProducts', { shopId })
  }
  // 购物车全选或者取消全选
  const setCartItemsChecked = shopId => {
    store.commit('setCartItemsChecked', { shopId })
  }

  // 计算shopId下所有cartList的商品数量total、价钱之和totalPrice
  const cartList = store.state.cartList // 加入购物车的商品列表fv
  const total = computed(() => {
    const productList = cartList[shopId]
    let count = 0
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        count += product.count
      }
    }
    return count
  })
  const totalPrice = computed(() => {
    const productList = cartList[shopId]
    let count = 0
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        if (product.checked === true) {
          count += product.count * product.price
        }
      }
    }
    return count.toFixed(2) // 保留2位小数
  })

  // 全选的计算属性
  const allChecked = computed(() => {
    const productList = cartList[shopId]
    let result = true
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        if (product.count > 0 && !product.checked) {
          result = false
          break
        }
      }
    }
    return result
  })

  const productList = computed(() => {
    const productInfoList = cartList[shopId] || [] // 不存在默认空数组
    return productInfoList
  })
  return {
    cartList,
    total,
    totalPrice,
    productList,
    allChecked,
    changeCartItemChecked,
    changeCartItemInfo,
    cleanCartProducts,
    setCartItemsChecked
  }
}
export default {
  name: 'Cart',
  setup() {
    const route = useRoute()
    const shopId = route.params.id // 店铺id
    // 计算总价和加入购物车的总数量
    const {
      cartList,
      total,
      totalPrice,
      productList,
      allChecked,
      changeCartItemChecked,
      changeCartItemInfo,
      cleanCartProducts,
      setCartItemsChecked
    } = useCartEffect(shopId)
    return {
      cartList,
      total,
      totalPrice,
      productList,
      shopId,
      allChecked,
      changeCartItemChecked,
      changeCartItemInfo,
      cleanCartProducts,
      setCartItemsChecked
    }
  }
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables.scss';
@import '@/style/mixins.scss';
.cart {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
}
.product {
  overflow-y: scroll;
  flex: 1;
  background: #fff;
  &__header {
    display: flex;
    line-height: 0.52rem;
    border-bottom: 0.01rem solid #f1f1f1;
    font-size: 0.14rem;
    color: #333333;
    &__all {
      width: 0.64rem;
      margin-left: 0.18rem;
      &__icon {
        height: 0.52rem;
        color: #0091ff;
      }
      &__text {
        display: inline-block;
        margin-left: 0.04rem;
        line-height: 0.52rem;
      }
    }
    &__clear {
      flex: 1;
      text-align: right;
      margin-right: 0.16rem;
    }
  }
  &__item {
    position: relative;
    display: flex;
    padding: 0.12rem 0.16rem;
    margin: 0 0.16rem;
    border-bottom: 0.01rem solid $content-bg-color;
    &__checked {
      line-height: 0.5rem;
      margin-right: 0.2rem;
      color: #0091ff;
      i {
        font-size: 0.25rem;
      }
    }
    // 配合解决超出长度以省略号显示而不会出现换行
    &__detail {
      overflow: hidden;
    }
    &__img {
      width: 0.46rem;
      height: 0.46rem;
      margin-right: 0.16rem;
    }
    &__title {
      margin: 0;
      line-height: 0.2rem;
      font-size: 0.14rem;
      color: $content-font-color;
      // 超出长度以省略号显示而不会出现换行
      @include ellipsis;
    }
    &__price {
      margin: 0.06rem 0 0 0;
      line-height: 0.2rem;
      font-size: 0.14rem;
      color: $height-light-font-color;
    }
    &__yen {
      font-size: 0.12rem;
    }
    &__origin {
      margin-left: 0.06rem;
      line-height: 0.2rem;
      font-size: 0.12rem;
      color: $light-font-color;
      text-decoration: line-through; //中划线
    }
    // 购物车选购数量和加减号
    .product__number {
      position: absolute;
      right: 0rem;
      bottom: 0.12rem;
      &__minus,
      &__plus {
        display: inline-block;
        width: 0.2rem;
        height: 0.2rem;
        line-height: 0.16rem;
        border-radius: 50%;
        font-size: 0.2rem;
        text-align: center;
      }
      // 边框白色
      &__minus {
        border: 0.01rem solid $medium-font-color;
        color: $medium-font-color;
        margin-right: 0.05rem;
      }
      //无边框,背景蓝色
      &__plus {
        color: $bg-color;
        background: $btn-bg-color;
        margin-left: 0.05rem;
      }
    }
  }
}
.check {
  display: flex;
  box-sizing: border-box; //往内塞入border
  line-height: 0.49rem;
  height: 0.49rem;
  border-top: 0.01rem solid $content-bg-color;
  &__icon {
    width: 0.84rem;
    position: relative;
    &__img {
      margin: 0.12rem auto;
      display: block;
      width: 0.28rem;
      height: 0.28rem;
    }
    &__tag {
      // 乘以2然后等比例缩小
      position: absolute;
      left: 0.46rem;
      top: 0.04rem;
      padding: 0 0.04rem;
      min-width: 0.2rem;
      height: 0.2rem;
      line-height: 0.2rem;
      text-align: center;
      background-color: $height-light-font-color;
      border-radius: 0.1rem;
      font-size: 0.12rem;
      color: $bg-color;
      transform: scale(0.5);
      transform-origin: left center;
    }
  }
  &__info {
    flex: 1;
    color: $content-font-color;
    font-size: 0.12rem;
    &__price {
      line-height: 0.49rem;
      color: $height-light-font-color;
      font-size: 0.18rem;
    }
  }
  &__btn {
    width: 0.98rem;
    background-color: #4fb0f9;
    text-align: center;
    color: $bg-color;
    font-size: 0.14rem;
  }
}
</style>

src\store\index.js增加方法:

    // 购物车全选或者取消全选
    setCartItemsChecked(state, payload) {
      const { shopId } = payload
      const products = state.cartList[shopId]
      if (products) {
        for (const i in products) {
          const product = products[i]
          product.checked = true
        }
      }
    }

增加购物车打印和隐藏:

<template>
  <div class="cart">
    <div class="product" v-show="showCart">
      <div class="product__header">
        <div class="product__header__all" @click="setCartItemsChecked(shopId)">
          <i
            :class="[
              'product__header__all__icon',
              'custom-icon',
              allChecked
                ? 'custom-icon-roundcheckfill'
                : 'custom-icon-roundcheck'
            ]"
          ></i>
          <span class="product__header__all__text">全选</span>
        </div>
        <div class="product__header__clear">
          <span @click="cleanCartProducts(shopId)">清空购物车</span>
        </div>
      </div>
      <template v-for="item in productList" :key="item._id">
        <div class="product__item" v-if="item.count > 0">
          <div
            class="product__item__checked"
            @click="changeCartItemChecked(shopId, item._id)"
          >
            <i
              :class="[
                'custom-icon',
                item.checked == true
                  ? 'custom-icon-roundcheckfill'
                  : 'custom-icon-roundcheck'
              ]"
            ></i>
          </div>
          <img class="product__item__img" :src="item.imgUrl" />
          <div class="product__item__detail">
            <h4 class="product__item__title">{{ item.name }}</h4>
            <p class="product__item__price">
              <span class="product__item__yen"> &yen;{{ item.price }} </span>
              <span class="product__item__origin">
                &yen;{{ item.oldPrice }}
              </span>
            </p>
          </div>
          <div class="product__number">
            <span
              class="product__number__minus"
              @click="
                () => {
                  0
                  changeCartItemInfo(shopId, item._id, item, -1)
                }
              "
              >-</span
            >
            {{ cartList?.[shopId]?.[item._id]?.count || 0 }}
            <span
              class="product__number__plus"
              @click="
                () => {
                  changeCartItemInfo(shopId, item._id, item, 1)
                }
              "
              >+</span
            >
          </div>
        </div>
      </template>
    </div>
    <div class="check">
      <div class="check__icon" @click="handleCartShowChange">
        <img src="/i18n/9_16/img/basket.png" alt="" class="check__icon__img" />
        <div class="check__icon__tag">
          {{ total }}
        </div>
      </div>
      <div class="check__info">
        总计:<span class="check__info__price">&yen; {{ totalPrice }}</span>
      </div>
      <div class="check__btn">去结算</div>
    </div>
  </div>
</template>

<script>
import { ref, computed } from 'vue'
import { useRoute } from 'vue-router' // 路由跳转方法
import { useStore } from 'vuex' // 路由跳转方法
import { useCommonCartEffect } from './commnCartEffect'

const useCartEffect = shopId => {
  const { changeCartItemInfo } = useCommonCartEffect()
  const store = useStore()

  // 单个勾选或者不勾选
  const changeCartItemChecked = (shopId, productId) => {
    store.commit('changeItemChecked', { shopId, productId })
  }
  // 清除购物车按钮
  const cleanCartProducts = shopId => {
    store.commit('changeCleanCartProducts', { shopId })
  }
  // 购物车全选或者取消全选
  const setCartItemsChecked = shopId => {
    store.commit('setCartItemsChecked', { shopId })
  }

  // 计算shopId下所有cartList的商品数量total、价钱之和totalPrice
  const cartList = store.state.cartList // 加入购物车的商品列表fv
  const total = computed(() => {
    const productList = cartList[shopId]
    let count = 0
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        count += product.count
      }
    }
    return count
  })
  const totalPrice = computed(() => {
    const productList = cartList[shopId]
    let count = 0
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        if (product.checked === true) {
          count += product.count * product.price
        }
      }
    }
    return count.toFixed(2) // 保留2位小数
  })

  // 全选的计算属性
  const allChecked = computed(() => {
    const productList = cartList[shopId]
    let result = true
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        if (product.count > 0 && !product.checked) {
          result = false
          break
        }
      }
    }
    return result
  })

  const productList = computed(() => {
    const productInfoList = cartList[shopId] || [] // 不存在默认空数组
    return productInfoList
  })
  return {
    cartList,
    total,
    totalPrice,
    productList,
    allChecked,
    changeCartItemChecked,
    changeCartItemInfo,
    cleanCartProducts,
    setCartItemsChecked
  }
}
export default {
  name: 'Cart',
  setup() {
    const route = useRoute()
    const shopId = route.params.id // 店铺id
    const showCart = ref(false)
    // 显示隐藏购物车具体内容
    const handleCartShowChange = () => {
      showCart.value = !showCart.value
    }
    // 计算总价和加入购物车的总数量
    const {
      cartList,
      total,
      totalPrice,
      productList,
      allChecked,
      changeCartItemChecked,
      changeCartItemInfo,
      cleanCartProducts,
      setCartItemsChecked
    } = useCartEffect(shopId)
    return {
      cartList,
      total,
      totalPrice,
      productList,
      shopId,
      allChecked,
      showCart,
      handleCartShowChange,
      changeCartItemChecked,
      changeCartItemInfo,
      cleanCartProducts,
      setCartItemsChecked
    }
  }
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables.scss';
@import '@/style/mixins.scss';
.cart {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
}
.product {
  overflow-y: scroll;
  flex: 1;
  background: #fff;
  &__header {
    display: flex;
    line-height: 0.52rem;
    border-bottom: 0.01rem solid #f1f1f1;
    font-size: 0.14rem;
    color: #333333;
    &__all {
      width: 0.64rem;
      margin-left: 0.18rem;
      &__icon {
        display: inline-block;
        font-size: 0.2rem;
        color: #0091ff;
      }
      &__text {
        display: inline-block;
        margin-left: 0.04rem;
        line-height: 0.52rem;
      }
    }
    &__clear {
      flex: 1;
      text-align: right;
      margin-right: 0.16rem;
    }
  }
  &__item {
    position: relative;
    display: flex;
    padding: 0.12rem 0.16rem;
    margin: 0 0.16rem;
    border-bottom: 0.01rem solid $content-bg-color;
    &__checked {
      line-height: 0.5rem;
      margin-right: 0.2rem;
      color: #0091ff;
      i {
        font-size: 0.25rem;
      }
    }
    // 配合解决超出长度以省略号显示而不会出现换行
    &__detail {
      overflow: hidden;
    }
    &__img {
      width: 0.46rem;
      height: 0.46rem;
      margin-right: 0.16rem;
    }
    &__title {
      margin: 0;
      line-height: 0.2rem;
      font-size: 0.14rem;
      color: $content-font-color;
      // 超出长度以省略号显示而不会出现换行
      @include ellipsis;
    }
    &__price {
      margin: 0.06rem 0 0 0;
      line-height: 0.2rem;
      font-size: 0.14rem;
      color: $height-light-font-color;
    }
    &__yen {
      font-size: 0.12rem;
    }
    &__origin {
      margin-left: 0.06rem;
      line-height: 0.2rem;
      font-size: 0.12rem;
      color: $light-font-color;
      text-decoration: line-through; //中划线
    }
    // 购物车选购数量和加减号
    .product__number {
      position: absolute;
      right: 0rem;
      bottom: 0.12rem;
      &__minus,
      &__plus {
        display: inline-block;
        width: 0.2rem;
        height: 0.2rem;
        line-height: 0.16rem;
        border-radius: 50%;
        font-size: 0.2rem;
        text-align: center;
      }
      // 边框白色
      &__minus {
        border: 0.01rem solid $medium-font-color;
        color: $medium-font-color;
        margin-right: 0.05rem;
      }
      //无边框,背景蓝色
      &__plus {
        color: $bg-color;
        background: $btn-bg-color;
        margin-left: 0.05rem;
      }
    }
  }
}
.check {
  display: flex;
  box-sizing: border-box; //往内塞入border
  line-height: 0.49rem;
  height: 0.49rem;
  border-top: 0.01rem solid $content-bg-color;
  &__icon {
    width: 0.84rem;
    position: relative;
    &__img {
      margin: 0.12rem auto;
      display: block;
      width: 0.28rem;
      height: 0.28rem;
    }
    &__tag {
      // 乘以2然后等比例缩小
      position: absolute;
      left: 0.46rem;
      top: 0.04rem;
      padding: 0 0.04rem;
      min-width: 0.2rem;
      height: 0.2rem;
      line-height: 0.2rem;
      text-align: center;
      background-color: $height-light-font-color;
      border-radius: 0.1rem;
      font-size: 0.12rem;
      color: $bg-color;
      transform: scale(0.5);
      transform-origin: left center;
    }
  }
  &__info {
    flex: 1;
    color: $content-font-color;
    font-size: 0.12rem;
    &__price {
      line-height: 0.49rem;
      color: $height-light-font-color;
      font-size: 0.18rem;
    }
  }
  &__btn {
    width: 0.98rem;
    background-color: #4fb0f9;
    text-align: center;
    color: $bg-color;
    font-size: 0.14rem;
  }
}
</style>

实现蒙层效果:

<template>
  <!-- 蒙层 -->
  <div class="mask" v-if="showCart"></div>
  <div class="cart">
    <div class="product" v-show="showCart">
      <div class="product__header">
        <div class="product__header__all" @click="setCartItemsChecked(shopId)">
          <i
            :class="[
              'product__header__all__icon',
              'custom-icon',
              allChecked
                ? 'custom-icon-roundcheckfill'
                : 'custom-icon-roundcheck'
            ]"
          ></i>
          <span class="product__header__all__text">全选</span>
        </div>
        <div class="product__header__clear">
          <span @click="cleanCartProducts(shopId)">清空购物车</span>
        </div>
      </div>
      <template v-for="item in productList" :key="item._id">
        <div class="product__item" v-if="item.count > 0">
          <div
            class="product__item__checked"
            @click="changeCartItemChecked(shopId, item._id)"
          >
            <i
              :class="[
                'custom-icon',
                item.checked == true
                  ? 'custom-icon-roundcheckfill'
                  : 'custom-icon-roundcheck'
              ]"
            ></i>
          </div>
          <img class="product__item__img" :src="item.imgUrl" />
          <div class="product__item__detail">
            <h4 class="product__item__title">{{ item.name }}</h4>
            <p class="product__item__price">
              <span class="product__item__yen"> &yen;{{ item.price }} </span>
              <span class="product__item__origin">
                &yen;{{ item.oldPrice }}
              </span>
            </p>
          </div>
          <div class="product__number">
            <span
              class="product__number__minus"
              @click="
                () => {
                  0
                  changeCartItemInfo(shopId, item._id, item, -1)
                }
              "
              >-</span
            >
            {{ cartList?.[shopId]?.[item._id]?.count || 0 }}
            <span
              class="product__number__plus"
              @click="
                () => {
                  changeCartItemInfo(shopId, item._id, item, 1)
                }
              "
              >+</span
            >
          </div>
        </div>
      </template>
    </div>
    <div class="check">
      <div class="check__icon" @click="handleCartShowChange">
        <img src="/i18n/9_16/img/basket.png" alt="" class="check__icon__img" />
        <div class="check__icon__tag">
          {{ total }}
        </div>
      </div>
      <div class="check__info">
        总计:<span class="check__info__price">&yen; {{ totalPrice }}</span>
      </div>
      <div class="check__btn">去结算</div>
    </div>
  </div>
</template>

<script>
import { ref, computed } from 'vue'
import { useRoute } from 'vue-router' // 路由跳转方法
import { useStore } from 'vuex' // 路由跳转方法
import { useCommonCartEffect } from './commnCartEffect'

const useCartEffect = shopId => {
  const { changeCartItemInfo } = useCommonCartEffect()
  const store = useStore()

  // 单个勾选或者不勾选
  const changeCartItemChecked = (shopId, productId) => {
    store.commit('changeItemChecked', { shopId, productId })
  }
  // 清除购物车按钮
  const cleanCartProducts = shopId => {
    store.commit('changeCleanCartProducts', { shopId })
  }
  // 购物车全选或者取消全选
  const setCartItemsChecked = shopId => {
    store.commit('setCartItemsChecked', { shopId })
  }

  // 计算shopId下所有cartList的商品数量total、价钱之和totalPrice
  const cartList = store.state.cartList // 加入购物车的商品列表fv
  const total = computed(() => {
    const productList = cartList[shopId]
    let count = 0
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        count += product.count
      }
    }
    return count
  })
  const totalPrice = computed(() => {
    const productList = cartList[shopId]
    let count = 0
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        if (product.checked === true) {
          count += product.count * product.price
        }
      }
    }
    return count.toFixed(2) // 保留2位小数
  })

  // 全选的计算属性
  const allChecked = computed(() => {
    const productList = cartList[shopId]
    let result = true
    if (productList) {
      for (const i in productList) {
        const product = productList[i]
        if (product.count > 0 && !product.checked) {
          result = false
          break
        }
      }
    }
    return result
  })

  const productList = computed(() => {
    const productInfoList = cartList[shopId] || [] // 不存在默认空数组
    return productInfoList
  })
  return {
    cartList,
    total,
    totalPrice,
    productList,
    allChecked,
    changeCartItemChecked,
    changeCartItemInfo,
    cleanCartProducts,
    setCartItemsChecked
  }
}
export default {
  name: 'Cart',
  setup() {
    const route = useRoute()
    const shopId = route.params.id // 店铺id
    const showCart = ref(false)
    // 显示隐藏购物车具体内容
    const handleCartShowChange = () => {
      showCart.value = !showCart.value
    }
    // 计算总价和加入购物车的总数量
    const {
      cartList,
      total,
      totalPrice,
      productList,
      allChecked,
      changeCartItemChecked,
      changeCartItemInfo,
      cleanCartProducts,
      setCartItemsChecked
    } = useCartEffect(shopId)
    return {
      cartList,
      total,
      totalPrice,
      productList,
      shopId,
      allChecked,
      showCart,
      handleCartShowChange,
      changeCartItemChecked,
      changeCartItemInfo,
      cleanCartProducts,
      setCartItemsChecked
    }
  }
}
</script>
<style lang="scss" scoped>
@import '@/style/viriables.scss';
@import '@/style/mixins.scss';
.mask {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  top: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1;
}
.cart {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  background: #fff;
}
.product {
  overflow-y: scroll;
  flex: 1;
  background: #fff;
  &__header {
    display: flex;
    line-height: 0.52rem;
    border-bottom: 0.01rem solid #f1f1f1;
    font-size: 0.14rem;
    color: #333333;
    &__all {
      width: 0.64rem;
      margin-left: 0.18rem;
      &__icon {
        display: inline-block;
        font-size: 0.2rem;
        color: #0091ff;
      }
      &__text {
        display: inline-block;
        margin-left: 0.04rem;
        line-height: 0.52rem;
      }
    }
    &__clear {
      flex: 1;
      text-align: right;
      margin-right: 0.16rem;
    }
  }
  &__item {
    position: relative;
    display: flex;
    padding: 0.12rem 0.16rem;
    margin: 0 0.16rem;
    border-bottom: 0.01rem solid $content-bg-color;
    &__checked {
      line-height: 0.5rem;
      margin-right: 0.2rem;
      color: #0091ff;
      i {
        font-size: 0.25rem;
      }
    }
    // 配合解决超出长度以省略号显示而不会出现换行
    &__detail {
      overflow: hidden;
    }
    &__img {
      width: 0.46rem;
      height: 0.46rem;
      margin-right: 0.16rem;
    }
    &__title {
      margin: 0;
      line-height: 0.2rem;
      font-size: 0.14rem;
      color: $content-font-color;
      // 超出长度以省略号显示而不会出现换行
      @include ellipsis;
    }
    &__price {
      margin: 0.06rem 0 0 0;
      line-height: 0.2rem;
      font-size: 0.14rem;
      color: $height-light-font-color;
    }
    &__yen {
      font-size: 0.12rem;
    }
    &__origin {
      margin-left: 0.06rem;
      line-height: 0.2rem;
      font-size: 0.12rem;
      color: $light-font-color;
      text-decoration: line-through; //中划线
    }
    // 购物车选购数量和加减号
    .product__number {
      position: absolute;
      right: 0rem;
      bottom: 0.12rem;
      &__minus,
      &__plus {
        display: inline-block;
        width: 0.2rem;
        height: 0.2rem;
        line-height: 0.16rem;
        border-radius: 50%;
        font-size: 0.2rem;
        text-align: center;
      }
      // 边框白色
      &__minus {
        border: 0.01rem solid $medium-font-color;
        color: $medium-font-color;
        margin-right: 0.05rem;
      }
      //无边框,背景蓝色
      &__plus {
        color: $bg-color;
        background: $btn-bg-color;
        margin-left: 0.05rem;
      }
    }
  }
}
.check {
  display: flex;
  box-sizing: border-box; //往内塞入border
  line-height: 0.49rem;
  height: 0.49rem;
  border-top: 0.01rem solid $content-bg-color;
  &__icon {
    width: 0.84rem;
    position: relative;
    &__img {
      margin: 0.12rem auto;
      display: block;
      width: 0.28rem;
      height: 0.28rem;
    }
    &__tag {
      // 乘以2然后等比例缩小
      position: absolute;
      left: 0.46rem;
      top: 0.04rem;
      padding: 0 0.04rem;
      min-width: 0.2rem;
      height: 0.2rem;
      line-height: 0.2rem;
      text-align: center;
      background-color: $height-light-font-color;
      border-radius: 0.1rem;
      font-size: 0.12rem;
      color: $bg-color;
      transform: scale(0.5);
      transform-origin: left center;
    }
  }
  &__info {
    flex: 1;
    color: $content-font-color;
    font-size: 0.12rem;
    &__price {
      line-height: 0.49rem;
      color: $height-light-font-color;
      font-size: 0.18rem;
    }
  }
  &__btn {
    width: 0.98rem;
    background-color: #4fb0f9;
    text-align: center;
    color: $bg-color;
    font-size: 0.14rem;
  }
}
</style>

image.png

本文章由javascript技术分享原创和收集

发表评论 (审核通过后显示评论):