老师

老师

数据结构的变更这里,只把 shopName 相关的东西写完,点击增加会说 Cart 文件有问题 读取不了未定义的属性,Cart里面的东西我没有去做更改
这里的商铺名也传递给 Content 组件了
图片描述
图片描述
图片描述

<template>
  <div class="content">
    <div class="category">
      <div
        :class="{
          category__item: true,
          'category__item--active': currentTab === item.tab,
        }"
        v-for="item in categories"
        :key="item.tab"
        @click="() => handleTabClick(item.tab)"
      >
        {{ item.name }}
      </div>
    </div>
    <div class="product">
      <div class="product__item" v-for="item in list" :key="item._id">
        <img :src="`${item.imgUrl}`" class="product__item__img" />
        <div class="product__item__detail">
          <h4 class="product__item__title">{{ item.name }}</h4>
          <p class="product__item__sales">月售{{ item.sales }}件</p>
          <p class="product__item__price">
            <span class="product__item__yen">&yen;</span>
            <span class="product__item__bold">{{ item.price }}</span>
            <span class="product__item__origin">&yen;{{ item.oldPrice }}</span>
          </p>
        </div>
        <div class="product__number">
          <span
            class="product__number__minus iconfont"
            @click="
              () => {
                changeCartItem(shopId, item._id, item, -1, ShopName);
              }
            "
          >
            &#xe7fd;
          </span>
          <!-- 这里的 count 数据从购物车中去取,这样清空购物车的时候数据才能去跟着变化 -->
          <span class="product__number__fs">{{
            cartList?.[shopId]?.productList?.[item._id]?.count || 0
          }}</span>
          <span
            class="product__number__plus iconfont"
            @click="
              () => {
                changeCartItem(shopId, item._id, item, 1, ShopName);
              }
            "
          >
            &#xe614;
          </span>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { reactive, ref, toRefs, watchEffect } from "vue";
import { useRoute } from "vue-router";
import { useStore } from "vuex";
import { get } from "../../utils/request";
import { useCommonCartEffect } from "./commonCartEffect"; // 购物车相关的逻辑复用(上下购物车联动)

const categories = [
  { name: "全部商品", tab: "all" },
  { name: "秒杀", tab: "seckill" },
  { name: "新鲜水果", tab: "fruit" },
];

// Tab 切换相关的逻辑
const useTabEffect = () => {
  const currentTab = ref(categories[0].tab);
  // 接收调用函数 () => handleCategoryClick(item.tab) 时传递的 tab 值
  const handleTabClick = (tab) => {
    //   getContentData(tab); // 点击列表项重新获取对应的数据
    currentTab.value = tab; // 点击列表项将函数中接收到的 tab 值赋值给 data 中的 currentTab
  };
  return { currentTab, handleTabClick };
};

// 列表内容相关的逻辑
const useCurrentListEffect = (currentTab, shopId) => {
  const content = reactive({ list: [] });
  // 获取列表内容
  const getContentData = async () => {
    const result = await get(`/api/shop/${shopId}/products`, {
      tab: currentTab.value, //依赖于 Tab 切换
    });
    if (result?.errno === 0 && result?.data?.length) {
      content.list = result.data;
    }
  };

  // 首次页面加载以及监听的数据发生变化,watchEffect 会执行。
  watchEffect(() => {
    getContentData(); // 监听函数在 watchEffect 内部执行,内部会分析代码:监听函数依赖于 currentTab,即 currentTab 发生改变,监听函数会再次执行
  });
  const { list } = toRefs(content);

  return { list };
};

export default {
  name: "Content",
  props: ["shopName"],
  setup() {
    const route = useRoute();
    const store = useStore();
    const shopId = route.params.id;
    const { currentTab, handleTabClick } = useTabEffect();
    const { list } = useCurrentListEffect(currentTab, shopId);
    const { changeCartItemInfo, cartList } = useCommonCartEffect();
    const changeShopName = (shopId, ShopName) => {
      store.commit("changeShopName", { shopId, ShopName });
    };
    const changeCartItem = (shopId, productId, item, num, shopName) => {
      changeCartItemInfo(shopId, productId, item, num);
      changeShopName(shopId, shopName);
    };

    return {
      categories,
      currentTab,
      list,
      shopId,
      cartList,
      handleTabClick,
      changeCartItem,
    };
  },
};
</script>

<style lang="scss" scoped>
@import "../../style/variables.scss";
@import "../../style/mixins.scss";

.content {
  display: flex;
  position: absolute;
  left: 0;
  right: 0;
  top: 1.5rem;
  bottom: 0.5rem;
}

.category {
  overflow-y: scroll;
  width: 0.75rem;
  height: 100%;
  background-color: $search-bgColor;
  &__item {
    line-height: 0.4rem;
    text-align: center;
    font-size: 0.14rem;
    color: $content-fontColor;
    &--active {
      background-color: $bgColor;
    }
  }
}

.product {
  overflow-y: scroll;
  flex: 1;
  &__item {
    position: relative;
    display: flex;
    padding: 0.12rem 0;
    margin: 0 0.16rem; // 左右的间隙用 margin 去做,使用 padding 的话下边框会贯穿整个 padding 区域
    border-bottom: 0.02rem solid $content-bgColor;
    &__img {
      width: 0.68rem;
      height: 0.68rem;
      margin-right: 0.16rem;
    }
    &__detail {
      overflow: hidden;
    }
    &__title {
      margin: 0;
      line-height: 0.2rem;
      font-size: 0.14rem;
      color: $content-fontColor;
      // 超过规定行数展示省略号
      @include ellipsis;
    }
    &__sales {
      margin: 0.06rem 0;
      line-height: 0.16rem;
      font-size: 0.12rem;
      color: $content-fontColor;
    }
    &__price {
      margin: 0;
      line-height: 0.2rem;
      font-size: 0.14rem;
      color: $highlight-fontColor;
    }
    &__yen {
      font-size: 0.12rem;
    }
    &__bold {
      font-weight: bold;
    }
    &__origin {
      margin-left: 0.08rem;
      line-height: 0.2rem;
      font-size: 0.12rem;
      color: $light-fontColor;
      text-decoration: line-through;
    }
    .product__number {
      position: absolute;
      right: 0;
      bottom: 0.12rem;
      &__minus,
      &__plus {
        display: inline-block;
        border-radius: 50%;
        text-align: center;
      }
      &__minus {
        width: 0.18rem;
        line-height: 0.18rem;
        border: 0.01rem solid $medium-fontColor;
        color: $medium-fontColor;
        margin-right: 0.1rem;
      }
      &__fs {
        font-size: 0.16rem;
        pointer-events: none;
      }
      &__plus {
        width: 0.2rem;
        line-height: 0.2rem;
        background-color: $btn-bgColor;
        color: $bgColor;
        margin-left: 0.1rem;
      }
    }
  }
}
</style>

import { createStore } from "vuex";

export default createStore({
  state: {
    cartList: {
      // shopId: {
      //   // 商品的名字
      //   shopName: "沃尔玛",
      //   // 商品的商品列表
      //   productList: {
      //     productId: {
      //       _id: "1",
      //       name: "番茄250g/份",
      //       imgUrl: "http://www.com",
      //       sales: 10,
      //       price: 33.6,
      //       pldPrice: 39.6,
      //       count: 2,
      //     },
      //   },
      // },
    },
  },
  getters: {},
  mutations: {
    changeCartItemInfo(state, payload) {
      // 根据传入的参数,获取到商铺的id、商品id、商品详细信息、增加还是减少等数据
      const { shopId, productId, productInfo, num } = payload;
      let shopInfo = state.cartList[shopId] || {
        shopName: "",
        productList: {},
      };
      let product = shopInfo.productList[productId];
      if (!product) {
        productInfo.count = 0;
        product = productInfo;
      }
      product.count += num; // 获取到商品信息  ->  count 在原来的基础上进行加减操作(1/-1)
      num > 0 ? (product.check = true) : (product.check = false); // 添加的商品大于 0,就展示表示选中的图标

      // 约束:加减有上限和下限
      if (product.count < 0) return (product.count = 0);
      if (product.count > 99) return (product.count = 99);

      // 改变数据后重新赋值,添加到 state 下的 cartList 中
      shopInfo.productList[productId] = product;
      state.cartList[shopId] = shopInfo;
    },

    changeShopName(state, payload) {
      const { shopId, shopName } = payload;
      const shopInfo = state.cartList[shopId] || {
        shopName: "",
        productList: {},
      };
      shopInfo.shopName = shopName;
      state.cartList[shopId] = shopInfo;
    },

    // 购物车展开点击状态图标能进行 勾选/取消勾选 等操作
    changeCartItemChecked(state, payload) {
      const { shopId, productId } = payload;

      const product = state.cartList[shopId][productId];
      product.check = !product.check; // 对状态图标进行点击取反
    },

    // 点击清空购物车
    cleanCartProducts(state, payload) {
      const { shopId } = payload;
      state.cartList[shopId] = {};
    },

    // 点击全选按钮,即所有商品全部选中
    setCartItemsChecked(state, payload) {
      const { shopId } = payload;
      const products = state.cartList[shopId];
      if (products) {
        // 循环添加的每一项商品,使 check 的值为 true,一旦 check 为 true,此时全选按钮就是 true,点击就能够实现全选
        for (let key in products) {
          const product = products[key];
          product.check = true;
        }
      }
    },
  },
});

<template>
  <!--  默认不展示遮罩层 -->
  <div class="mask" v-if="showCart" @click="handleCartShowChange" />
  <div class="cart">
    <!-- 默认不展示购物车弹层 -->
    <div class="product" v-if="showCart">
      <div class="product__header">
        <div
          class="product__header__all"
          @click="() => setCartItemsChecked(shopId)"
        >
          <!-- allChecked 为 true,则展示全选图标,反之则展示未选中图标 -->
          <span
            class="product__header__icon iconfont"
            v-html="allChecked ? '&#xe605;' : '&#xe649;'"
          >
          </span>
          全选
        </div>
        <div class="product__header__clear">
          <span
            class="product__header__clear__btn"
            @click="() => cleanCartProducts(shopId)"
            >清空购物车</span
          >
        </div>
      </div>
      <!-- template 为占位符,不影响页面 -->
      <template v-for="item in productList" :key="item._id">
        <!-- 商品数量为空的时候,下方购物车信息不进行展示 -->
        <div class="product__item" v-if="item.count > 0">
          <!-- item.check 为true,则商品数量大于1,则展示选中状态的图标,反之则展示未选中状态的图标 -->
          <div
            class="product__item__checked iconfont"
            v-html="item.check ? '&#xe605;' : '&#xe649;'"
            @click="() => changeCartItemChecked(shopId, item._id)"
          ></div>
          <img :src="`${item.imgUrl}`" class="product__item__img" />
          <div class="product__item__detail">
            <h4 class="product__item__title">{{ item.name }}</h4>
            <p class="product__item__price">
              <span class="product__item__yen">&yen;</span>
              <span class="product__item__bold">{{ item.price }}</span>
              <span class="product__item__origin"
                >&yen;{{ item.oldPrice }}</span
              >
            </p>
          </div>
          <div class="product__number">
            <span
              class="product__number__minus iconfont"
              @click="
                () => {
                  // -1 代表减少项
                  changeCartItemInfo(shopId, item._id, item, -1);
                }
              "
            >
              &#xe7fd;
            </span>
            <!-- count 在点击的时候,vuex 会随之进行更新(vuex 内部定义了 count) -->
            <span class="product__number__fs">{{ item.count || 0 }}</span>
            <!-- <span class="product__number__fs">{{
              cartList?.[shopId]?.productList?.[item._id]?.count || 0
            }}</span> -->
            <span
              class="product__number__plus iconfont"
              @click="
                () => {
                  // 1 代表增加项
                  // 向哪个 shop 添加内容(1/2)? 详情页面各个商品对应的 id(1/2/3/4)? 整个商品的信息?
                  changeCartItemInfo(shopId, item._id, item, 1);
                }
              "
            >
              &#xe614;
            </span>
          </div>
        </div>
      </template>
    </div>
    <div class="check">
      <div class="check__icon">
        <img
          class="check__icon__img"
          src="http://www.dell-lee.com/imgs/vue3/basket.png"
          @click="handleCartShowChange"
        />
        <div class="check__icon__tag">{{ total }}</div>
      </div>
      <div class="check__info">
        总计:<span class="check__info__price">&yen;&nbsp;{{ price }}</span>
      </div>
      <div class="check__btn">
        <router-link to="/">去结算</router-link>
      </div>
    </div>
  </div>
</template>

<script>
import { computed, ref } from "vue";
import { useStore } from "vuex";
import { useRoute } from "vue-router";
import { useCommonCartEffect } from "./commonCartEffect"; // 购物车相关的逻辑复用(上下购物车联动)

// 购物车相关逻辑
const useCartEffect = (shopId) => {
  const { changeCartItemInfo } = useCommonCartEffect(); // 点击加减将商品以及 shopId 存到购物车
  const store = useStore();
  const cartList = store.state.cartList;

  // 计算总件数
  const total = computed(() => {
    const productList = cartList[shopId]; // 根据 shopId 获取店铺的所有商品
    let count = 0; // 存放累加的结果
    // 商品存在再进行循环
    if (productList) {
      // 遍历所有商品,将数量累加
      for (let i in productList) {
        const product = productList[i];
        count += product.count;
      }
    }
    return count; // 返回累加结果
  });

  // 计算总价格
  const price = computed(() => {
    const productList = cartList[shopId];
    let count = 0;
    if (productList) {
      for (let i in productList) {
        const product = productList[i];
        // 只有 check 为 true 的时候(商品选中状态)才会去计算价格
        if (product.check) {
          count += product.count * product.price;
        }
      }
    }
    return count.toFixed(2);
  });

  // 根据购物车中商品的选中与否 来决定全选按钮的展示
  const allChecked = computed(() => {
    const productList = cartList[shopId];
    // 全选按钮默认是 true
    let result = true;
    if (productList) {
      for (let i in productList) {
        const product = productList[i];
        // 购物车有商品且没有选中,就让全选按钮为 false
        if (product.count > 0 && !product.check) {
          result = false;
        }
      }
    }
    return result;
  });

  // 购物车展开列表功能
  const productList = computed(() => {
    const productList = cartList[shopId] || [];

    return productList;
  });

  // 购物车展开点击状态图标能进行 勾选/取消勾选 等操作
  const changeCartItemChecked = (shopId, productId) => {
    // 同步的改变 store 中的数据
    store.commit("changeCartItemChecked", { shopId, productId }); //数据传送到vuex
  };

  // 点击清空购物车
  const cleanCartProducts = (shopId) => {
    store.commit("cleanCartProducts", { shopId });
  };

  // 点击全选按钮,即所有商品全部选中
  const setCartItemsChecked = (shopId) => {
    store.commit("setCartItemsChecked", { shopId });
  };

  return {
    total,
    price,
    productList,
    allChecked,
    changeCartItemInfo,
    changeCartItemChecked,
    cleanCartProducts,
    setCartItemsChecked,
  };
};

// 展示隐藏购物车逻辑
const toggleCartEffect = () => {
  const showCart = ref(false);
  // 点击购物车按钮 控制购物车弹层、以及遮罩层的展示与否 / 点击遮罩层使遮罩层消失
  const handleCartShowChange = () => {
    showCart.value = !showCart.value;
  };

  return { showCart, handleCartShowChange };
};

export default {
  name: "Cart",
  setup() {
    const route = useRoute();
    const shopId = route.params.id;
    const { showCart, handleCartShowChange } = toggleCartEffect();
    const {
      total,
      price,
      productList,
      allChecked,
      changeCartItemInfo,
      changeCartItemChecked,
      cleanCartProducts,
      setCartItemsChecked,
    } = useCartEffect(shopId);

    return {
      showCart,
      total,
      price,
      shopId,
      productList,
      allChecked,
      changeCartItemInfo,
      changeCartItemChecked,
      cleanCartProducts,
      setCartItemsChecked,
      handleCartShowChange,
    };
  },
};
</script>

<style lang="scss" scoped>
@import "../../style/variables.scss";
@import "../../style/mixins.scss";

.mask {
  z-index: 1;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0.49rem;
  left: 0;
  background: rgba(0, 0, 0, 0.5);
}

.cart {
  z-index: 2;
  overflow-y: hidden;
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
}

.product {
  overflow-y: scroll;
  flex: 1;
  background-color: $bgColor;
  &__header {
    display: flex;
    line-height: 0.5rem;
    border-bottom: 1px solid $content-bgColor;
    font-size: 0.14rem;
    color: $content-fontColor;
    &__all {
      width: 0.74rem;
      margin-left: 0.18rem;
    }
    &__icon {
      margin-right: 0.1rem;
      font-size: 0.22rem;
      color: $btn-bgColor;
    }
    &__clear {
      flex: 1;
      margin-right: 0.18rem;
      text-align: right;
      &__btn {
        display: inline-block;
      }
    }
  }
  &__item {
    position: relative;
    display: flex;
    padding: 0.12rem 0;
    margin: 0 0.16rem; // 左右的间隙用 margin 去做,使用 padding 的话下边框会贯穿整个 padding 区域
    border-bottom: 0.02rem solid $content-bgColor;
    &__checked {
      width: 0.2rem;
      line-height: 0.5rem;
      margin: 0 0.16rem 0 0.03rem;
      font-size: 0.22rem;
      color: $btn-bgColor;
    }
    &__img {
      width: 0.46rem;
      height: 0.46rem;
      margin-right: 0.16rem;
    }
    &__detail {
      overflow: hidden;
    }
    &__title {
      margin: 0;
      line-height: 0.2rem;
      font-size: 0.14rem;
      color: $content-fontColor;
      // 超过规定行数展示省略号
      @include ellipsis;
    }
    &__price {
      margin: 0.06rem 0 0 0;
      line-height: 0.2rem;
      font-size: 0.14rem;
      color: $highlight-fontColor;
    }
    &__yen {
      font-size: 0.12rem;
    }
    &__bold {
      font-weight: bold;
    }
    &__origin {
      margin-left: 0.08rem;
      line-height: 0.2rem;
      font-size: 0.12rem;
      color: $light-fontColor;
      text-decoration: line-through;
    }
    .product__number {
      position: absolute;
      right: 0;
      bottom: 0.12rem;
      &__minus,
      &__plus {
        display: inline-block;
        border-radius: 50%;
        text-align: center;
      }
      &__minus {
        width: 0.18rem;
        line-height: 0.18rem;
        border: 0.01rem solid $medium-fontColor;
        color: $medium-fontColor;
        margin-right: 0.1rem;
      }
      &__fs {
        font-size: 0.16rem;
        pointer-events: none;
      }
      &__plus {
        width: 0.2rem;
        line-height: 0.2rem;
        background-color: $btn-bgColor;
        color: $bgColor;
        margin-left: 0.1rem;
      }
    }
  }
}

.check {
  display: flex;
  height: 0.49rem;
  line-height: 0.49rem;
  border-top: 0.01rem solid $bgColor;
  box-shadow: 0 -0.01rem 0.01rem 0 $content-bgColor;
  &__icon {
    position: relative;
    width: 0.84rem;
    &__img {
      display: block;
      margin: 0.12rem auto;
      width: 0.28rem;
      height: 0.26rem;
    }
    &__tag {
      position: absolute;
      left: 0.47rem;
      top: 0.01rem;
      padding: 0 0.05rem;
      // 设置最小宽度 .2rem,这样数字过大宽度能随之发生变化
      min-width: 0.2rem;
      height: 0.2rem;
      line-height: 0.2rem;
      background-color: $highlight-fontColor;
      color: $bgColor;
      border-radius: 0.1rem;
      font-size: 0.15rem;
      text-align: center;
      transform: scale(0.7);
      // 给一个缩放中心点,这样数字过大也能在固定的位置展示
      transform-origin: left center;
    }
  }
  &__info {
    flex: 1;
    font-size: 0.12rem;
    color: $content-fontColor;
    line-height: 0.49rem;
    &__price {
      font-size: 0.18rem;
      color: $highlight-fontColor;
      font-weight: bold;
    }
  }
  &__btn {
    width: 0.98rem;
    text-align: center;
    background-color: #4fb0f9;
    color: $bgColor;
    font-size: 0.14rem;
  }
  a {
    text-decoration: none;
    color: $bgColor;
  }
}
</style>

正在回答

登陆购买课程后可参与讨论,去登陆

1回答

同学你好,cart.vue中,productList获取的不对。同学的productList打印如下:

https://img1.sycdn.imooc.com//climg/639427220963ef2707230284.jpg

https://img1.sycdn.imooc.com//climg/6394272d0939785906670154.jpg

该数据中有两个键,当键是shopName时,shopName的值中没有count属性,所以代码会报错:

https://img1.sycdn.imooc.com//climg/6394276009cb538308120395.jpg

调整如下:

https://img1.sycdn.imooc.com//climg/639427b4090fad4207580156.jpg

同理,如下位置也需要调整:

https://img1.sycdn.imooc.com//climg/639427d6099f980a08240167.jpg

https://img1.sycdn.imooc.com//climg/639428210960143107120145.jpg

https://img1.sycdn.imooc.com//climg/6394283e09c8d31807830121.jpg

祝学习愉快!

  • 清夏_ 提问者 #1

    将store 里面数据变更的代码全部改写之后 shopName 还是拿不到店铺的名字,为undefined 麻烦老师再给看看

    Cart 里面也更改过了,代码能正常运行不报错,但是我没取到   shopName的值

    import { createStore } from "vuex";
    
    export default createStore({
      state: {
        // { shopId: { shopName: '', productList: { productId: {} } }}
        cartList: {},
      },
      getters: {},
      mutations: {
        changeCartItemInfo(state, payload) {
          // 根据传入的参数,获取到商铺的id、商品id、商品详细信息、增加还是减少等数据
          const { shopId, productId, productInfo, num } = payload;
          let shopInfo = state.cartList[shopId] || {
            shopName: "",
            productList: {},
          };
          let product = shopInfo.productList[productId];
          if (!product) {
            productInfo.count = 0;
            product = productInfo;
          }
          product.count += num; // 获取到商品信息  ->  count 在原来的基础上进行加减操作(1/-1)
          num > 0 ? (product.check = true) : (product.check = false); // 添加的商品大于 0,就展示表示选中的图标
    
          // 约束:加减有上限和下限
          if (product.count < 0) return (product.count = 0);
          if (product.count > 99) return (product.count = 99);
    
          // 改变数据后重新赋值,添加到 state 下的 cartList 中
          shopInfo.productList[productId] = product;
          state.cartList[shopId] = shopInfo;
          console.log(state.cartList);
        },
    
        changeShopName(state, payload) {
          const { shopId, shopName } = payload;
          const shopInfo = state.cartList[shopId] || {
            shopName: "",
            productList: {},
          };
          shopInfo.shopName = shopName;
          state.cartList[shopId] = shopInfo;
        },
    
        // 购物车展开点击状态图标能进行 勾选/取消勾选 等操作
        changeCartItemChecked(state, payload) {
          const { shopId, productId } = payload;
    
          const product = state.cartList[shopId].productList[productId];
          product.check = !product.check; // 对状态图标进行点击取反
        },
    
        // 点击清空购物车
        cleanCartProducts(state, payload) {
          const { shopId } = payload;
          state.cartList[shopId].productList = {};
        },
    
        // 点击全选按钮,即所有商品全部选中
        setCartItemsChecked(state, payload) {
          const { shopId } = payload;
          const products = state.cartList[shopId].productList;
          if (products) {
            // 循环添加的每一项商品,使 check 的值为 true,一旦 check 为 true,此时全选按钮就是 true,点击就能够实现全选
            for (let key in products) {
              const product = products[key];
              product.check = true;
            }
          }
        },
      },
    });


    2022-12-10 18:00:00
  • 好帮手慕久久 回复 提问者 清夏_ #2

    shopName大小写不一致。shop组件传过去的是小写:

    https://img1.sycdn.imooc.com//climg/63945f5909c2036b07650142.jpg

    content中接收的是大写:

    https://img1.sycdn.imooc.com//climg/63945f6809bac4d304570162.jpg

    将shop中的小写改成大写:

    https://img1.sycdn.imooc.com//climg/63945f860963580006950153.jpg

    2022-12-10 18:30:06
问题已解决,确定采纳
还有疑问,暂不采纳

恭喜解决一个难题,获得1积分~

来为老师/同学的回答评分吧

0 星
请稍等 ...
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号

在线咨询

领取优惠

免费试听

领取大纲

扫描二维码,添加
你的专属老师