Vue3.0 组合式api简单购物车案例

关于购物车案例的实现

一般来说,实现购物车案例依赖的是大量的父子组件之间数据的相互传递

那么Vue3.0的组合式apiVue2.0的选项式api关于父子组件通信有哪些不同呢

- 如果是父组件向子组件进行传递数据
  1. 选项式 API
    a. 我们可以提供props选项来声明接收传递的数据
    b. 在JS中可使用this.$props来访问声明的自定义的属性
    c. 在视图模板中,可直接访问props中声明的自定义属性

  2. 组合式 API
    a. 我们可以采用defineProps来声明接收传递的数据
    b. 在JS中可使用defineProps返回的对象来访问声明的自定义的属性
    c. 在视图模板中,可直接访问defineProps中声明的自定义属性

- 如果是子组件向父组件进行传递数据
  1. 子组件声明自定义的事件

    • 在选项式 API 中,子组件可通过emits选项来声明自定义的事件
    • 在组合式 API 中,子组件可通过defineEmits()宏来声明自定义的事件
  2. 子组件中触发自定义事件(可传值)
    在选项式 API 中,可通过组件当前实例this.$emit(event, …args)来触发当前组件自定义的事件
    在组合式 API 中,可调用defineEmits宏返回的emit(event, …args)函数来触发当前组件自定义的事件
    其中上方两个参数分别为:
    ● event:触发事件名,字符串类型
    ● …args:传递参数,可没有,可多个

  3. 父组件使用子组件时监听对应的自定义事件,并执行父组件中的函数(获取子组件传递的值)
    使用v-on:event="callback"或者@event="callback"来监听子组件是否触发了该事件
    (1).event:事件名字(camelCase 形式命名的事件,在父组件中可以使用kebab-case形式来监听)
    (2) callback:回调函数,如果子组件触发该事件,那么在父组件中执行对应的回调函数,回调函数声明参数可自动接收到触发事件传来的值

代码实现

- App.vue
<template>
  <div>
    <!-- 产品展示 -->
    <Product
      v-for="product in shopCar"
      :key="product.id"
      :id="product.id"
      :picture="product.picture"
      :title="product.title"
      :subtitle="product.subtitle"
      :image="product.image"
      :price="product.price"
      :count="product.count"
      :is-checked="product.selected"
      @change-product-count="productCount"
      @change-product-checked="productChecked"
    >
    </Product>
    <hr />

    <!-- 控制器 -->
    <Collect
      :is-all-checked="isSelectProduct"
      @change-all-checked-state="AllProductChecked"
      :all-total="total"
      :all-count="AllCount"
    ></Collect>
  </div>
</template>

<script setup>
import Product from "@/views/gwc/Product.vue";
import Collect from "@/views/gwc/Collect.vue";
import { computed, ref } from "vue";
let shopCar = ref(
  // 购物车信息
  [
    {
      id: 89,
      title:
        "四川爱媛38号果冻橙 当季时令应季彩箱装甜桔橘子新鲜水果专区 净重2斤小果尝鲜装(力荐大果,口感更好更实惠)",
      subtitle:
        "由初逐旗舰店从 四川眉山市 发货, 并提供售后服务. 现在至明日17:00前完成下单,预计11月15日19:30前发货",
      image:
        "https://img12.360buyimg.com/n1/jfs/t1/39198/22/19565/188868/634a3bc4Ea15f2eee/2bb232b36cdd285c.jpg",
      price: 10,
      count: 1,
      selected: false,
    },
    {
      id: 102,
      title:
        "【现货速发】新鲜四季青柠檬 无籽香水柠檬当季生鲜小青柠檬奶茶店水果 有籽青柠檬1斤装试吃【50-80克】",
      subtitle:
        "由朵艾美水果旗舰店发货, 并提供售后服务. 现在至明日16:00前完成下单,预计11月16日23:30前发货",
      image:
        "https://img12.360buyimg.com/n1/jfs/t1/191077/5/6346/108268/60beea0dEc3a6d2ad/15db7dd619a0bc4f.jpg",
      price: 9,
      count: 3,
      selected: true,
    },
    {
      id: 108,
      title:
        "新疆阿克苏冰糖心苹果 新鲜时令水果 阿克苏苹果红富士 10斤礼盒装 单果75-85mm 净重9斤多",
      subtitle:
        "由阿克苏苹果旗舰店发货, 并提供售后服务. 现在至明日16:00前完成下单,预计11月16日20:30前发货",
      image:
        "https://img13.360buyimg.com/n1/jfs/t1/64647/33/22918/106322/6360afb1E9bab1003/a82bda0aeae6e953.png",
      price: 80,
      count: 2,
      selected: false,
    },
  ]
);

//修改单个商品数量 参数  数量 iD
function productCount(count, id) {
  // 寻找具体是哪个商品  some循环
  shopCar.value.some((product) => {
    // 判断更改的是哪个商品
    if (id === product.id) {
      product.count += count;
      return true; // 找到即停止循环
    }
  });
}

// 修改单个商品的选中状态
function productChecked(checked, id) {
  // 寻找具体是哪个商品  some循环
  shopCar.value.some((product) => {
    // 判断更改的是哪个商品
    if (id === product.id) {
      product.selected = checked;
      return true; // 找到即停止循环
    }
  });
}

// 改变所有商品的选中状态
function AllProductChecked(checked) {
  console.log(checked);
  shopCar.value.forEach((product) => (product.selected = checked));
}

// 当前组件内computed可以和data中数据一样 能直接拿来用
// 是否全选
let isSelectProduct = computed(() => {
  // every
  return shopCar.value.every((product) => product.selected);
});

// 计算总金额
let total = computed(() => {
  return shopCar.value
    .filter((item) => item.selected) // 过滤掉被选中得产品
    .reduce((money, item) => (money += item.price * item.count), 0); // 累加器
});

// 总价和总数量写在根组件是因为:
// 1.子组件修改不了父组件传过来的数据 因为是单向数据流
// 2.父组件参数比较好修改

// 计算总数量
let AllCount = computed(() => {
  return shopCar.value
    .filter((item) => item.selected)
    .reduce((allcount, item) => (allcount += item.count), 0);
});
</script>


- Product.vue
<template>
  <!-- 产品容器 -->
  <div class="box">
    <!-- 选项框 -->
    <input
      type="checkbox"
      class="p_checkbox"
      :checked="isChecked"
      @change="changeCheckState"
    />
    <!-- 产品图 -->
    <img :src="image" alt="" class="p_image" />
    <!-- 产品内容 -->
    <div class="p_content">
      <!-- 标题 -->
      <h3 class="p_title" v-text="title"></h3>
      <!-- 副标题 -->
      <span class="p_subtitle" v-text="subtitle"></span>
      <!-- 价格 -->
      <h2 class="p_price">{{ price }}</h2>
      <!-- 数量区域 -->
      <div class="p_count_area">
        <!-- disabled 禁用 -->
        <button
          :disabled="count <= 1"
          @click="emits('changeProductCount', -1, id)"
        >
          -
        </button>
        <!-- 商品的数量 -->
        <span v-text="count"></span>
        <button @click="emits('changeProductCount', 1, id)">+</button>
      </div>
    </div>
  </div>
</template>

<script setup>
let propsData = defineProps({
  id: { type: Number, required: true },
  title: { type: String, required: true },
  subtitle: { type: String, required: true },
  price: { type: Number, required: true, default: 0 },
  count: { type: Number, required: true, default: 0 },
  isChecked: Boolean,
  image: { type: String, required: true },
});

//声明 自定义事件
let emits = defineEmits([
  "changeProductChecked", //改变复选框
  "changeProductCount", //改变产品数量
]);

// 改变商品的选中状态
function changeCheckState(e) {
  // 目标源
  // console.log(e);
  let newCheckState = e.target.checked;
  emits("changeProductChecked", newCheckState, propsData.id);
}
</script>

<style>
.box {
  box-shadow: 0 0 8px gray;
  padding: 20px;
  margin: 15px;
  display: flex;
  align-items: center;
}

.p_checkbox {
  width: 25px;
  height: 25px;
}
.p_image {
  width: 120px;
  height: 120px;
  margin: 0 20px;
}
.p_content {
  align-self: start;
  width: 100%;
  position: relative;
}
.p_title {
  margin-bottom: 8px;
}
.p_subtitle {
  font-size: 14px;
  color: gray;
}
.p_price {
  margin-top: 20px;
  color: rgb(201, 67, 67);
}
.p_count_area {
  position: absolute;
  bottom: 0;
  right: 0;
}
</style>

- Collect.vue
<template>
  <div class="container">
    <label>
      <!-- label 受击面积更大 -->

      <!-- 全选按钮 -->
      <input
        type="checkbox"
        :checked="isAllChecked"
        @change="emits('changeAllCheckedState', $event.target.checked)"
      />
      全选
    </label>

    <!-- 总金额 -->
    <span
      >合计:
      <strong>{{ allTotal }}</strong>
    </span>

    <!-- 数量 -->
    <span>
      数量:
      <strong>{{ allCount }}</strong>
    </span>
  </div>
</template>

<script setup>
defineProps({
  isAllChecked: Boolean, // 是否全选
  allTotal: { type: Number, default: 0 }, // 总金额
  allCount: { type: Number, default: 0 }, // 总数量
});

let emits = defineEmits(["changeAllCheckedState"]);
</script>

<style>
.container {
  padding: 20px;
  margin: auto 15px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.container input {
  width: 25px;
  height: 25px;
}

.container label {
  display: flex;
  align-items: center;
  width: 70px;
  justify-content: space-between;
}

.container strong {
  color: red;
}

.container button {
  border: none;
  padding: 15px 25px;
  background-color: rgb(50, 140, 192);
  color: white;
  border-radius: 8px;
  box-shadow: 0 0 5px gray;
}
</style>

效果演示

文章总结

简单归纳了Vue3.0 组合式api实现购物车案例的与Vue2.0 组合式api的几点区别,并用代码实现了既定效果,希望能对你有所帮助。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值