Vue-购物车案例

1.案例效果

2.实现步骤

①初始化项目基本结构

②封装EsHeader组件

2.1创建并注册EsHeader组件

2.2封装es-header组件

③基于axios请求商品列表数据(GET请求,地址为https://www.escook.cn/api/cart)

 

④封装EsFooter组件

4.1创建并注册EsFooter组件

 

4.2封装es-footer组件
4.2.0封装需求

4.2.1渲染组件的基础布局

 

 4.2.2封装自定义属性 amount

4.2.3封装自定义属性 total

4.2.4封装自定义属性 isfull

4.2.5封装自定义事件 fullChange

⑤封装EsGoods组件

5.1创建并注册EsGoods组件

 

5.2封装es-goods组件
5.2.0封装需求

5.2.1渲染组件的基础布局

 

5.2.2封装自定义属性 id

 

5.2.3封装其他属性

5.2.4封装自定义事件 stateChange

⑥封装EsCounter组件 

6.1动态统计已勾选商品的总价格

 

6.2动态统计已勾选商品的总数量

6.3实现全选功能
⑦封装es-counter组件

 

 

 

main.js

import { createApp } from 'vue'
import App from './App.vue'
import './assets/bootstrap-3.4.1-dist/css/bootstrap.css'
import './index.css'
//导入axios
import axios from 'axios'
const app = createApp(App);
//配置请求的根路径
axios.defaults.baseURL = 'https://applet-base-api-t.itheima.net';
//将axios挂栽为全局的$http自定义属性
app.config.globalProperties.$http = axios
app.mount('#app')

App.vue 

<template>
  <div class="app-container">
    <es-header title="购物车案例"></es-header>
    <EsGoods
      v-for="item in goodslist"
      :key="item.goods_id"
      :id="item.goods_id"
      :thumb="item.goods_img"
      :title="item.goods_name"
      :price="item.goods_price"
      :count="item.goods_count"
      :checked="item.goods_state"
      @stateChange="onGoodsStateChange"
      @countChange="onGoodsCountChange"
    ></EsGoods>
    <EsFooter
      @fullChange="onFullStateChange"
      :amount="amount"
      :total="total"
    ></EsFooter>
  </div>
</template>

<script>
import EsHeader from "./components/es-header/EsHeader.vue";
import EsFooter from "./components/es-footer/EsFoote.vue";
import EsGoods from "./components/es-goods/EsGoods.vue";

export default {
  name: "MyApp",
  components: { EsHeader, EsFooter, EsGoods },
  data() {
    return {
      //商品列表数据
      goodslist: [],
    };
  },
  created() {
    this.getGoodsList();
  },
  methods: {
    //获取商品列表数据的方法
    async getGoodsList() {
      //1.通过组件实例this访问到全局挂栽的$http属性,并发起Ajax数据请求
      const { data: res } = await this.$http.get("/api/cart");

      if (res.status !== 200) return alert("数据请求失败!");
      this.goodslist = res.list;
      console.log(this.goodslist);
    },
    //监听选中状态变化的事件
    onFullStateChange(isFull) {
      this.goodslist.forEach((x) => (x.goods_state = isFull));
    },
    onGoodsStateChange(e) {
      const findResult = this.goodslist.find((x) => x.goods_id === e.id);
      if (findResult) {
        findResult.goods_state = e.value;
      }
    },
    //监听商品数量变化的事件
    onGoodsCountChange(e) {
      const findResult = this.goodslist.find((x) => x.goods_id === e.id);
      if (findResult) {
        findResult.goods_count = e.value;
      }
    },
  },
  computed: {
    //已勾选商品的总价
    amount() {
      //1.定义商品总价格
      let a = 0;
      //2.循环累加商品总价格
      this.goodslist
        .filter((x) => x.goods_state)
        .forEach((x) => {
          a += x.goods_price * x.goods_count;
        });
      //3.返回累加的结果
      return a;
    },
    //已勾选商品的总数量
    total() {
      let t = 0;
      this.goodslist
        .filter((x) => x.goods_state)
        .forEach((x) => {
          t += x.goods_count;
        });
      return t;
    },
  },
};
</script>
<style lang="less" scoped>
.app-container {
  padding-top: 45px;
}
</style>

 EsHeader.vue

<template>
  <div
    :style="{ backgroundColor: bgcolor, color: color, fontSize: fsize + 'px' }"
    class="header-container"
  >
    {{ title }}
  </div>
</template>

<script>
export default {
  name: "EsHeader",
  props: {
    title: {
      type: String,
      default: "es-header",
    },
    bgcolor: {
      type: String,
      default: "#007BFF",
    },
    color: {
      type: String,
      default: "#ffffff",
    },
    fsize: {
      type: Number,
      default: 12,
    },
  },
};
</script>

<style lang="less" scoped>
.header-container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 45px;
  text-align: center;
  line-height: 45px;
  z-index: 999;
}
</style>

 EsFooter.vue

<template>
  <div class="footer-container">
    <!-- 全选区域 -->
    <div class="custom-control custom-checkbox">
      <input
        type="checkbox"
        class="custom-control-input aaa"
        id="fullCheck"
        :checked="isfull"
        @change="onCheckBoxChange"
      />
      <label class="custom-control-label" for="fullCheck">全选</label>
    </div>
    <!-- 合计区域 -->
    <div>
      <span>合计:</span>

      <span class="amount">¥{{ amount.toFixed(2) }}</span>
    </div>
    <!-- 结算按钮 -->
    <button
      type="button"
      class="btn btn-primary btn-settle"
      :disabled="total === 0"
    >
      结算({{ total }})
    </button>
  </div>
</template>

<script>
export default {
  name: "EsFooter",
  emits: ["fullChange"],
  props: {
    //以勾选商品的总价格
    amount: {
      type: Number,
      default: 0,
    },
    //已勾选商品的总数量
    total: {
      type: Number,
      default: 0,
    },
    //全选按钮的选中状态
    isfull: {
      type: Boolean,
      default: false,
    },
  },
  methods: {
    onCheckBoxChange(e) {
      this.$emit("fullChange", e.target.checked);
    },
  },
};
</script>

<style lang="less" scoped>
.footer-container {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 50px;
  align-items: center;
  background-color: #fff;
  border-top: 1px solid #efefef;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 10px;
}

.amount {
  font-weight: bold;
  color: #f10404;
}

.btn-settle {
  min-width: 90px;
  height: 38px;
  border-radius: 19px;
}
</style>

 EsGoods.vue

<template>
  <div>
    <div class="goods-container">
      <div class="left">
        <div class="custom-control custom-checkbox">
          <input
            type="checkbox"
            class="custom-control-input"
            :id="id"
            :checked="checked"
            @change="onCheckBoxChange"
          />
          <label class="custom-control-label" :for="id">
            <img :src="thumb" alt="商品图片" class="thumb"
          /></label>
        </div>
      </div>

      <!-- 右侧信息区域 -->
      <div class="right">
        <!-- 商品名称 -->
        <div class="top">{{ title }}</div>
        <div class="bottom">
          <!-- 商品价格 -->
          <div class="price">¥{{ price.toFixed(2) }}</div>
          <!-- 商品数量 -->
          <div class="count">
            <EsCounter :num="count" :min="1" @numChange="getNumbe"></EsCounter>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import EsCounter from "../es-counter/EsCounter.vue";
export default {
  name: "EsGoods",
  props: {
    //商品的id
    id: {
      type: [String, Number],
      required: true,
    },
    //商品的缩略图
    thumb: {
      type: String,
      required: true,
    },
    //商品的名称
    title: {
      type: String,
      required: true,
    },
    //单价
    price: {
      type: Number,
      required: true,
    },
    //数量
    count: {
      type: Number,
      required: true,
    },
    //勾选的状态
    checked: {
      type: Boolean,
      required: true,
    },
  },
  emits: ["stateChange", "countChange"],
  methods: {
    onCheckBoxChange(e) {
      this.$emit("stateChange", {
        id: this.id,
        value: e.target.checked,
      });
    },
    //监听数量值的变化,
    getNumbe(num) {
      this.$emit("countChange", {
        id: this.id,
        value: num,
      });
    },
  },
  components: {
    EsCounter,
  },
};
</script>

<style lang="less" scoped>
.goods-container {
  border-bottom: 1px solid #efefef;

  display: flex;
  padding: 10px;

  .left {
    margin-right: 10px;

    //商品图片
    .thumb {
      display: block;
      width: 100px;
      height: 100px;
      background-color: #efefef;
    }
  }

  //右侧商品名称、单价、数量的样式
  .right {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    flex: 1;

    .top {
      font-weight: bold;
    }

    .bottom {
      display: flex;
      justify-content: space-between;
      align-items: center;

      .price {
        color: red;
        font-weight: bold;
      }
    }
  }
}
</style>

 EsCounter.vue

<template>
  <div class="counter-container">
    <button type="button" class="btn btn-light btn-sm" @click="onSubClick">
      -
    </button>
    <input
      type="number"
      class="form-control form-control-sm ipt-num"
      v-model.number.lazy="number"
    />
    <button type="button" class="btn btn-light btn-sm" @click="onAddClick">
      +
    </button>
  </div>
</template>

<script>
export default {
  name: "EsCounter",
  data() {
    return {
      number: this.num,
    };
  },
  watch: {
    number(newVal) {
      const parseResult = parseInt(newVal);
      if (isNaN(parseResult) || parseResult < 1) {
        this.number = 1;
        return;
      }
      if (String(newVal).indexOf(".") != -1) {
        this.number = parseResult;
        return;
      }
      //触发自定义事件,把最新的number数值传递给组件的使用者
      this.$emit("numChange", this.number);
    },
  },
  emits: ["numChange"],
  props: {
    num: {
      type: Number,
      default: 0,
    },
    min: {
      type: Number,
      //min属性的值默认为NaN,表示不限制最小值
      default: NaN,
    },
  },
  methods: {
    onSubClick() {
      if (!isNaN(this.min) && this.number - 1 < this.min) return;
      this.number--;
    },
    onAddClick() {
      this.number++;
    },
  },
};
</script>

<style lang="less" scoped>
.counter-container {
  display: flex;
  .btn {
    width: 25px;
  }
  //输入框的样式
  .ipt-num {
    width: 34px;
    text-align: center;
    margin: 0 4px;
  }
}
</style>

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

再学习一点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值