Vue2之购物车案例

1,076 阅读2分钟

Vue2中的购物车案例

1.项目准备,以及项目初始化

 使用 vue create 项目名称  创建项目
 执行 yarn add bootstrap 下载bootstrap库
 执行 yarn add less less-loader@5.0.0 -D 下载less模块
 清空components 文件夹中自动生成的组件

图示:

image-20210307092110985.png

1.在components文件夹下创建项目组成的4个组件

Snipaste_2022-05-22_15-51-24.png

2.在main.js入口文件中引入bootstrap库

import '../node_modules/bootstrap/dist/css/bootstrap.css' 
  1. 搭建项目组件
// MyHeader组件
<template>
  <div class="my-header">购物车案例</div>
</template><script>
  export default {
​
  }
</script><style lang="less" scoped>
  .my-header {
  height: 45px;
  line-height: 45px;
  text-align: center;
  background-color: #1d7bff;
  color: #fff;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 2;
  }
</style>
// MyGoods组件
<template>
  <div class="my-goods-item">
    <div class="left">
      <div class="custom-control custom-checkbox">
        <input type="checkbox" class="custom-control-input" id="input"
               >
          <label class="custom-control-label" for="input">
            <img src="http://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" alt="">
              </label>
            </div>
          </div>
        <div class="right">
          <div class="top">商品名字</div>
          <div class="bottom">
            <span class="price">¥ 100</span>
            <span>
              数量组件
            </span>
          </div>
        </div>
      </div>
      </template>
​
    <script>
      export default {
​
      }
    </script>
​
    <style lang="less" scoped>
      .my-goods-item {
      display: flex;
      padding: 10px;
      border-bottom: 1px solid #ccc;
      .left {
      img {
      width: 120px;
      height: 120px;
      margin-right: 8px;
      border-radius: 10px;
      }
      .custom-control-label::before,
      .custom-control-label::after {
      top: 50px;
      }
      }
      .right {
      flex: 1;
      display: flex;
      flex-direction: column;
      justify-content: space-between;
      .top{
      font-size: 14px;
      font-weight: 700;
      }
      .bottom {
      display: flex;
      justify-content: space-between;
      padding: 5px 0;
      align-items: center;
      .price {
      color: red;
      font-weight: bold;
      }
      }
      }
      }
​
    </style>
//MyCount组件
<template>
  <div class="my-counter">
    <button type="button" class="btn btn-light" >-</button>
    <input type="number" class="form-control inp" >
      <button type="button" class="btn btn-light">+</button>
      </div>
    </template>
​
  <script>
    export default {
    }
  </script>
​
  <style lang="less" scoped>
    .my-counter {
    display: flex;
    .inp {
    width: 45px;
    text-align: center;
    margin: 0 10px;
    }
    .btn, .inp{
    transform: scale(0.9);
    }
    }
  </style>
//MyFooter组件
<template>
  <!-- 底部 -->
  <div class="my-footer">
    <!-- 全选 -->
    <div class="custom-control custom-checkbox">
      <input type="checkbox" class="custom-control-input" id="footerCheck">
        <label class="custom-control-label" for="footerCheck">全选</label>
        </div>
      <!-- 合计 -->
      <div>
        <span>合计:</span>
        <span class="price">¥ 0</span>
      </div>
      <!-- 按钮 -->
      <button type="button" class="footer-btn btn btn-primary">结算 ( 0 )</button>
    </div>
    </template>
​
  <script>
    export default {
​
    }
  </script>
​
  <style lang="less" scoped>
    .my-footer {
    position: fixed;
    z-index: 2;
    bottom: 0;
    width: 100%;
    height: 50px;
    border-top: 1px solid #ccc;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0 10px;
    background: #fff;
​
    .price {
    color: red;
    font-weight: bold;
    font-size: 15px;
    }
    .footer-btn {
    min-width: 80px;
    height: 30px;
    line-height: 30px;
    border-radius: 25px;
    padding: 0;
    }
    }
  </style>

2.购物车案例--头部模块

思路

  1. 在MyHeader.vue中准备props里变量, 然后使用
  2. 在使用MyHeader.vue组件时, 传入相应的值 (color和backgroundColor)
// MyHeader.vue
<template>
  <div class="my-header" :style="{backgroundColor: background, color}">{{ title }}</div>
</template><script>
  // 目标: 让Header组件支持不同的项目 - 自定义
  // 1. 分析哪些可以自定义 (背景色, 文字颜色, 文字内容)
  // 2. (新) 可以对props的变量的值 进行校验
  // 3. 内部使用props变量的值
  // 4. 外部使用时, 遵守变量名作为属性名, 值的类型遵守
  export default {
  props: {
  background: String, // 外部插入此变量的值, 必须是字符串类型, 否则报错
  color: {
  type: String, // 约束color值的类型
  default: "#fff" // color变量默认值(外部不给color传值, 使用默认值)
  },
  title: {
  type: String,
  required: true // 必须传入此变量的值
  }
  }
  }
</script><style lang="less" scoped>
  .my-header {
  height: 45px;
  line-height: 45px;
  text-align: center;
  background-color: #1d7bff;
  color: #fff;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  z-index: 2;
  }
</style>
// 在App.vue组件中传入Props中title的值
<MyHeader title="购物车案例"></MyHeader>

3.购物车案例--使用Ajax请求数据

数据地址: www.escook.cn/api/cart (get方式)

3.1 使用yarn下载axios

yarn add axios

3.2 在main.js入口文件中 Vue原型上挂载axios

// 1. 下载axios库, main.js - 全局绑定属性 (确保任意.vue文件可以都访问到这个axios方法)
import axios from 'axios'
// 2. 基础地址
axios.defaults.baseURL = "https://www.escook.cn"
// 3. axios方法添加到Vue的原型上
Vue.prototype.$axios = axios
​
new Vue({
    render: h => h(App),
}).$mount('#app')

3.3 在App中的created钩子函数中发起axios请求并请求数据

<script>
  export default {
  data(){
  return {
  // 商品所有数据
  list: [] 
  }
  },
  created(){
  // 不必在自己引入axios变量, 而是直接使用全局属性$axios
  this.$axios({
  url: '/api/cart'
  }).then(res => {
  if (res.status === 200) {
  console.log(res)
  this.list = res.data.list
  }
  })
  }
  }
</script>

4.购物车案例--数据渲染

// 在App组件中
<MyGoods v-for="obj in list" 
         :key="obj.id"
         :gObj="obj">
</MyGoods>
在MyGoods组件中
<template>
  <div class="my-goods-item">
    <div class="left">
      <div class="custom-control custom-checkbox">
        <!-- *重要:
          每个对象和组件都是独立的
          对象里的goods_state关联自己对应商品的复选框
         -->
        <!-- bug:
          循环的所有label的for都是input, id也都是input - 默认只有第一个生效
          解决: 每次对象里的id值(1, 2), 分别给id和for使用即可区分
          -->
        <input type="checkbox" class="custom-control-input" :id="gObj.id"
               v-model="gObj.goods_state"
               >
          <label class="custom-control-label" :for="gObj.id">
            <img :src="gObj.goods_img" alt="">
              </label>
            </div>
          </div>
        <div class="right">
          <div class="top">{{ gObj.goods_name }}</div>
          <div class="bottom">
            <span class="price">¥ {{ gObj.goods_price }}</span>
            <span>
              <MyCount :obj="gObj"></MyCount>
            </span>
          </div>
        </div>
      </div>
      </template>
​
    <script>
      import MyCount from './MyCount'
      export default {
      props: {
      gObj: Object
      },
      components: {
      MyCount
      }
      }
    </script>
//在MyCount组件中
<template>
  <div class="my-counter">
    <button type="button" class="btn btn-light" >-</button>
    <input type="number" class="form-control inp" v-model.number="obj.goods_count">
      <button type="button" class="btn btn-light" >+</button>
      </div>
    </template>
​
  <script>
    export default {
    props: {
    obj: Object // 商品对象
    }
    }
  </script>

5.购物车案例--解决商品选中不同步问题

<input type="checkbox" class="custom-control-input" :id="gObj.id"
       v-model="gObj.goods_state"
       >
  <label class="custom-control-label" :for="gObj.id">
    <img :src="gObj.goods_img" alt="">
      </label>

6.购物车案例--实现数量操作

在MyCount组件中
<template>
  <div class="my-counter">
    <button type="button" class="btn btn-light" :disabled="obj.goods_count === 1" @click="obj.goods_count > 1 && obj.goods_count--">-</button>
    <input type="number" class="form-control inp" v-model.number="obj.goods_count">
      <button type="button" class="btn btn-light" @click="obj.goods_count++">+</button>
      </div>
    </template>
​
  <script>
    // 目标: 商品数量 - 控制
    // 1. 外部传入数据对象
    // 2. v-model关联对象的goods_count属性和输入框 (双向绑定)
    // 3. 商品按钮 +和-, 商品数量最少1件
    // 4. 侦听数量改变, 小于1, 直接强制覆盖1
    export default {
    props: {
    obj: Object // 商品对象
    },
    // 因为数量控制要通过对象"互相引用的关系"来影响外面对象里的数量值, 所以最好传 对象进来
   watch: {
    obj: {
      handler() {
        console.log(this.obj.goods_count)
        if (this.obj.goods_count < 1) {
          this.obj.goods_count = 1
        }
      },
      deep: true
    }
  }
  </script>

7.购物车案例--实现全选功能

//在MyFooter组件中
<template>
  <!-- 底部 -->
  <div class="my-footer">
    <!-- 全选 -->
    <div class="custom-control custom-checkbox">
      <input type="checkbox" class="custom-control-input" id="footerCheck" v-model="isAll">
      <label class="custom-control-label" for="footerCheck">全选</label>
    </div>
    <!-- 合计 -->
    <div>
      <span>合计:</span>
      <span class="price">¥ {{ allPrice.toFixed(2) }}</span>
    </div>
    <!-- 按钮 -->
    <button type="button" class="footer-btn btn btn-primary">结算 ( {{ allCount }} )</button>
  </div>
</template><script>
// 目标: 全选
// 1. v-model关联全选-复选框(v-model后变量计算属性)
// 2. 页面(视频层)v(true) -> 数据层(变量-) 计算属性(完整写法)
// 3. 把全选 true/false同步给所有小选框选中状态上// 小选  -> 全选
// App.vue里list数组 -> MyFooter.vue
// isAll的get方法里, 统计状态影响全选框// 目标: 总数量统计
// 1. allCount计算属性用 数组reduce+判断统计数量并返回// 目标: 总价
// allPrice计算属性, 数组reduce+单价*数量, 判断选中, 才累加后返回export default {
  props: {
    arr: Array
  },
  computed: {
    isAll: {
      set(val){ // val就是关联表单的值(true/false)
        this.$emit('changeAll', val)
      },
      get(){
        // 查找小选框关联的属性有没有不符合勾选的条件
        // 直接原地false
        return this.arr.every(obj => obj.goods_state === true)
      }
    },
    
    
  }
}
</script>
//在App组件中
<MyFooter @changeAll="allFn" :arr="list"></MyFooter><script>
methods: {
    allFn(bool){
      this.list.forEach(obj => obj.goods_state = bool)
      // 把MyFooter内的全选状态true/false同步给所有小选框的关联属性上
    }
  }
</script>

8.购物车案例--计算选中商品总数量

// 在MyFooter组件中
allCount(){
    return this.arr.reduce((sum, obj) => {
        if (obj.goods_state === true) { // 选中商品才累加数量
            sum += obj.goods_count;
        }
        return sum;
    }, 0)
},

9.购物车案例--计算选中商品的总价格

// 在MyFooter组件中
allPrice(){
      return this.arr.reduce((sum, obj) => {
        if (obj.goods_state){
          sum += obj.goods_count * obj.goods_price
        }
        return sum;
      }, 0)
    }

10.总结

1️⃣父传子(通过props属性)

props: [] - 只能声明变量和接收, 不能类型校验

props: {} - 声明变量和校验类型规则 - 外部传入值不对则报错

2️⃣created生命周期是发起axios请求的最好时机

3️⃣ lable的for值对应input的id, 点击label就能让对应input处于激活

4️⃣ 全选的v-model的值, 使用计算属性完整写法

5️⃣ 对象之间是引用关系, 对象值改变, 所有用到的地方都跟着改变

6️⃣计算商品总价或总数量使用数组的reduce方法更适合