Vue 购物车案例 黑马学院

本文详细介绍了如何使用Vue.js创建一个购物车应用,包括项目初始化、组件划分、数据获取、商品展示、选中状态控制、数量增减、全选功能、总数量与总价计算等关键步骤,提供完整的代码示例。

项目初始化:

需求1:从0新建项目

需求2:分拆组件,创建组件文件

分析:

①:vue命令创建项目(在根目录下运行)

vue create shopcar-deom

②:下载需要得配置:

yarn add less less-loader bootstrap

③:main.js - 引入bootst 样式

// 直接引入 bootstrap 的css文件样式
import 'bootstrap/dist/css/bootstrap.css'

补充:下载时候可能会因为网络出问题

//查看当前源
yarn config get registry
//设置淘宝源或内网源
yarn config set registry https://registry.npm.taobao.org --global
//恢复源
yarn config set registry https://registry.yarnpkg.com --global

准备代码:

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>
          <MyCount></MyCount>
        </span>
      </div>

    </div>
  </div>
</template>
<script>
import MyCount from './MyCount';
export default {
  components: {
    MyCount
  }
}
</script>

<style lang="less">
.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="" class="form-control inp" placeholder="10">
    <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">¥ 100</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>

App.vue盒子优化:

<template>
  <div>
    <MyHeader></MyHeader>
    <div class="main">
      <MyGoods></MyGoods>
    </div>
    <MyFooter></MyFooter>
  </div>
</template>
<script>
import MyHeader from './components/MyHeader'
import MyGoods from './components/MyGoods';
import MyFooter from './components/MyFooter';
export default {
  data() {
    return {}
  },
  components: {
    MyHeader,
    MyGoods,
    MyFooter,
  }
}
</script>

<style scoped>
.main {
  padding: 45px 0 50px;
}
</style>

头部自定义

我们的组件要自定义(大致样式一样,颜色,字体我们要变化的来适应不同场景)

分析:

①:背景色定义props里变量接收使用

②:文字颜色定义props里变量接收-设默认值

③:文字内容定义props里变量接收-设必传值

④:使用组件传入具体值(父传子)

props有两种定义方式

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

props: { } - 声明变量和校验类型规则 - 不对就会报错

数据获取

分析:

①:下载axios,在main,jsl入(以前是单独引入一个vue文件里面)

yarn add axios

②:配置基础地址,https:/www.escook.cn

③:axios挂载到Vue原型上,作为全局属性

④:App.vue的created中使用全局属性axios

⑤:接口地址为/api/cart

// 目标:请求数据 - 打印
// 1. 下载axios库,main.js - 全局绑定属性(所有 .vue 文件可以访问到axios方法)
import axios from 'axios'
// 2. 设置基础地址
axios.defaults.baseURL = 'https://www.escook.cn'
// 3. axios方法添加到原型上($axios这个名字是自定义的)
Vue.prototype.$axios = axios

数据铺设

需求:把数据使用MyGoods组件展示

分析:

①:App.vue把数据保存到data定义的变量上

②:使用v-for循环数组使用组件

③:分别给商品组件传入数据对象

④:商品组件接收后,使用对象里字段数据展示

商品选中

分析:

①:给v-model关联到小选框上,数据关联对象的goods_state

②:注意点图片用label关联的小选框

③:label的for值要和表单标签id一致,点label才触发小选框

④:id属性分别使用商品的id即可

  • label标签关联表单,点击label标签上,相当于点击表单
  • 对象传值赋值传的是引用类型的堆内存地址,多处使用共同的这个对象/数组

数量控制

分析:

①:给MyCount组件传入数据对象关联

②:输入框v-model关联对象里数量属性

③:增加或减少修改对象数量属性

④:控制商品最少1件

先控制按钮,然后侦听输入框值,强制覆盖

全选

分析:

①:v-model用计算属性,关联全选框

②:在set方法里,获取全选状态

③:回传到App.vue再同步给所有小选框

④:在get方法里,统计小选框给全选复制

全选和小选框,互相影响的思路:

  • 全选-关联计算属性-set方法-同步所有小选
  • 小选-触发计算属性的get方法-统计后返回全选状态

总数量

分析:

①:计算属性allCount变量

②:在统计数组里数据时,要判断勾选状态才累加数量(判断)

③:把累加好数量返回给allCount变量使用显示

// 核心代码-计算属性
allCount() {
      // reduce() 的计算方法
      return this.arr.reduce((sum, obj) => { //整体返回计算的结果值
        if (obj.goods_state == true) {// 选中商品才累加数量
          sum += obj.goods_count;
        }
        return sum //内部返回供下次使用
      }, 0)
    },

总价

分析:

①:计算属性allPrice变量

②:在统计数组里数据时,要判断勾选状态才累加

③:把价格和数量先想乘,再累加

④:累加好数据返回给allPrice变量使用显示

// 核心代码-计算属性
  allPrice() {
      return this.arr.reduce((sum, obj) => {
        if (obj.goods_state == true) {
          sum += obj.goods_count * obj.goods_price
        }
        return sum
      }, 0)
    }

最终代码

App.vue:

<template>
  <div>
    <!-- 标签内属性加冒号是变量,不加是固定字符串 -->
    <MyHeader background="skyblue" title="HB的购物车"></MyHeader>
    <div class="main">
      <MyGoods v-for="obj in list" :key="obj.id" :obj="obj"></MyGoods>
    </div>
    <MyFooter style="boredr:10px" @changeAllFn="allFn" :arr="list"></MyFooter>
  </div>
</template>

<script>
//目标:数据铺设到MyGoods组件上
//l.数据在data保存一下(页面只能用data里值)
//2.页面v-for循环MyGoods:组件
//3.分别传入obj数据对象(一对一关系)I
//4.内部使用数据对象值
import MyHeader from './components/MyHeader'
import MyGoods from './components/MyGoods';
import MyFooter from './components/MyFooter';

export default {
  data() {
    return {
      list: [] // 商品所有数据
    }
  },
  components: {
    MyHeader,
    MyGoods,
    MyFooter,
  },
  created() {
    // 思考这个用法这是调用函数
    this.$axios({
      url: '/api/cart'
    }).then(res => {
      this.list = res.data.list //这里必须得用箭头函数才能这样用 this
      console.log(this.list);
    })
  },
  methods: {
    allFn(val) {
      // this.list.goods_state = val
      this.list.forEach(obj => obj.goods_state = val)
      // 把MyFooter内的全选状态值同步给所有小选框的关联属性上
    }
  }
}
</script>

<style scoped>
.main {
  padding: 45px 0 50px;
}
</style>

MyCount.vue:

<template>
  <div class="my-counter">
    <!-- obj.goods_count > 1 && obj.goods_count-- 大于一才能减一操作 -->
    <button type="button" class="btn btn-light" @click="obj.goods_count > 1 && obj.goods_count--"
      :disabled='obj.goods_count==1'>-</button>
    <input 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. 商品按钮 +和- ,商品数量最小一件, 直接用表达式
// 4. 侦听数量改变,小于1,直接强制覆盖1
export default {
  props: {
    //  因为数量控制要通过对象“互相引用的关系”来影响外面对象里的数量值,所以最好传 对象进来
    obj: Object // 商品对象
  },
  watch: {
    obj: {
      deep: true,
      handler() {
        // 为啥三元表达不可?
        // this.obj.goods_count < 1 ? 1 : this.obj.goods_count
        if (this.obj.goods_count < 1) {
          this.obj.goods_count = 1
        }
        console.log(this.obj.goods_count);
      }
    }
  }
}
</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.vue:

<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 }}</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. 把全选的选中状态同步给所有的小选框的选中状态上

// 小选
// 从App里面把数组传过来 ,
// 看数组里面的 选中状态是不是全部满足条件,用 every()方法
// 在计算属性的 get() 里面

// 目标:总数量的统计
export default {
  props: {
    arr: Array,
  },
  computed: {
    isAll: {
      set(val) { // 所关联表单的值(true/false)
        console.log(val);
        this.$emit('changeAllFn', val)
      },
      get() {
        // 查找小选框关联的属性有没有不符合勾选的条件 读下面语句
        return this.arr.every(obj => obj.goods_state == true)
      }
    },
    // 只需要统计的话,不用完整写法也可以
    allCount() {
      // reduce() 的计算方法
      return this.arr.reduce((sum, obj) => { //整体返回计算的结果值
        if (obj.goods_state == true) {// 选中商品才累加数量
          sum += obj.goods_count;
        }
        return sum //内部返回供下次使用
      }, 0)
    },
    allPrice() {
      return this.arr.reduce((sum, obj) => {
        if (obj.goods_state == true) {
          sum += obj.goods_count * obj.goods_price
        }
        return sum
      }, 0)
    }
  }
}
</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>

MyGoods.vue:

<template>
  <div class="my-goods-item">
    <div class="left">
      <div class="custom-control custom-checkbox">
        <!-- 每个组件和选择状态都是独立的
        对象不一样,它的属性也不一样,渲染组件也就不一样了
        -->
        <input type="checkbox" class="custom-control-input" :id="obj.id" v-model="obj.goods_state">
        <label class="custom-control-label" :for="obj.id">
          <img :src="obj.goods_img" alt="">
        </label>
      </div>
    </div>
    <div class="right">
      <div class="top">{{ obj.goods_name }}</div>
      <div class="bottom">
        <span class="price">¥{{ obj.goods_price
        }}</span>
        <span>
          <MyCount :obj="obj"></MyCount>
        </span>
      </div>
    </div>
  </div>
</template>
<script>
import MyCount from './MyCount';
export default {
  components: {
    MyCount
  },
  props: {
    obj: Object
  }
}
</script>

<style lang="less">
.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>

MyHeader.vue:

<template>
  <!-- 像color,这样值和名一样,可以简写 color: color => color-->
  <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'// 默认的值(如果外部不传,就用默认值)
    },
    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>

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

编程武六七

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

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

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

打赏作者

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

抵扣说明:

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

余额充值