Vue-Shopping Cart Case

1. Case Effect

2. Implementation steps

① Initialize the basic structure of the project

② Package EsHeader component

2.1 Create and register the EsHeader component

2.2 Package es-header component

③Request product list data based on axios (GET request, the address is https://www.escook.cn/api/cart)

 

④ Encapsulation of EsFooter components

4.1 Create and register EsFooter component

 

4.2 Package es-footer component

4.2.0 Packaging Requirements

4.2.1 The basic layout of rendering components

 

 4.2.2 Encapsulating the custom attribute amount

4.2.3 Encapsulating the custom attribute total

4.2.4 Encapsulate custom attribute isfull

4.2.5 Encapsulate custom event fullChange

⑤ Package EsGoods components

5.1 Create and register EsGoods component

 

5.2 Encapsulation of es-goods components

5.2.0 Packaging Requirements

5.2.1 The basic layout of rendering components

 

5.2.2 Encapsulate custom attribute id

 

5.2.3 Encapsulating other properties

5.2.4 Encapsulate custom event stateChange

⑥ Encapsulation of EsCounter components 

6.1 Dynamic statistics of the total price of the checked products

 

6.2 Dynamic statistics of the total number of checked commodities

6.3 Realize the function of selecting all

⑦ Encapsulation of es-counter components

 

 

 

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.view

<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>

 

Guess you like

Origin blog.csdn.net/a_xia_o/article/details/131834173