【vue基础】黑马vue视频笔记(五)

本文展示了一个使用Vue.js实现的购物车案例,涵盖了组件基本使用、Vue指令、计算属性、过滤器、生命周期函数和数据交互。通过父子组件、兄弟组件间的通信,实现了商品列表的显示、商品状态的切换、总价和数量的计算以及全选功能。同时,使用axios进行API数据获取,eventBus实现实时更新购物车商品数量。
摘要由CSDN通过智能技术生成

用vue做一个购物车案例

涉及知识点
  1. 组件基本使用
  2. vue指令(v-text、v-bind、v-on、v-for)
  3. 计算属性
  4. 过滤器函数
  5. 生命周期函数
  6. axios
  7. 父子组件,兄弟组件相互传信息(自定义属性,自定义事件,eventBus)

在这里插入图片描述

//APP.vue
<template>
  <div class="app-container">
    <Header title="购物车案例"></Header>
    <!-- 利用自定义属性向自组件传递信息 -->
    <Goods
      v-for="item in list"
      :key="item.id"
      :id="item.id"
      :title="item.goods_name"
      :pic="item.goods_img"
      :price="item.goods_price"
      :state="item.goods_state"
      :count="item.goods_count"
      @state-change="getNewState"
    ></Goods>
    <!-- <h1>App 根组件</h1> -->
    <Footer
      :isAllchecked="isAllstate"
      @all-change="getAllState"
      :amount="amt"
      :all="total"
    ></Footer>
  </div>
</template>

<script>
// 1.导入eventBus.js
import bus from '@/components/eventBus.js'
// 1.1安装npm i -S axios 导入
import axios from 'axios'
import Header from '@/components/Header/Header.vue'
import Footer from '@/components/Footer/Footer.vue'
import Goods from '@/components/Goods/Goods.vue'
export default {
  data() {
    return {
      // 存放商品信息。默认为空
      list: [],
    }
  },
  computed: {
    isAllstate() {
      return this.list.every((item) => item.goods_state)
    },
    // 已经勾选商品的总价格
    // 1.先filter过滤
    // 2.再reduce累加
    amt() {
      return this.list
        .filter((item) => item.goods_state)
        .reduce((t, item) => (t += item.goods_price * item.goods_count), 0)
    },
    // 已经勾选商品的总数量
    total() {
      return this.list
        .filter((item) => item.goods_state)
        .reduce((t, item) => (t += item.goods_count), 0)
    },
  },
  components: {
    Header,
    Footer,
    Goods,
  },
  methods: {
    // 1.2封装请求列表数据的方法
    async initCartList() {
      const { data: res } = await axios.get('https://www.escook.cn/api/cart')
      if (res.status === 200) {
        this.list = res.list
        // console.log(res.list)
      }
    },
    // 接收子组件传来单个选中信息
    // e的格式为{id,value}
    getNewState(e) {
      this.list.some((item) => {
        // 查找修改state的id是哪一个
        if (item.id === e.id) {
          item.goods_state = e.value
          // 终止后续循环
          return true
        }
      })
    },
    // 接收子组件传来的全选状态
    getAllState(val) {
      this.list.forEach((item) => (item.goods_state = val))
    },
  },
  //  1.3 在created调用请求数据的方法
  created() {
    // 1.3 调用请求数据的方法,而且如果渲染需要使用,要保存到data
    this.initCartList()
    //
    bus.$on('numShare', (obj) => {
      this.list.some((item) => {
        if (item.id === obj.id) {
          item.goods_count = obj.value
          return true
        }
      })
    })
  },
}
</script>

<style lang="less" scoped>
.app-container {
  padding-top: 45px;
  padding-bottom: 50px;
}
</style>

//  Header组件
<template>
  <div class="header-container">{{ title }}</div>
</template>

<script>
export default {
  props: ['title'],
  data() {
    return {}
  },
  methods: {},
}
</script>

<style lang="less" scoped>
.header-container {
  font-size: 12px;
  height: 45px;
  width: 100%;
  background-color: #1d7bff;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #fff;
  position: fixed;
  top: 0;
  z-index: 999;
}
</style>

// Goods组件
<template>
  <div class="goods-container">
    <!-- 左侧图片 -->
    <div class="thumb">
      <div class="custom-control custom-checkbox">
        <!-- 复选框 -->
        <input
          type="checkbox"
          class="custom-control-input"
          :id="'cd' + id"
          :checked="state"
          @change="stateChange"
        />
        <!-- for属性其实就是将label和表单控件绑定在一起,最主要的差别就是比如有个输入框,如果我想要选中输入框获得焦点,只有input情况下,我只能选中输入框,但加了label,把label和input绑定在一起后,点击label也可以选中输入框。 \ label中的for属性规定了label与哪个表单元素绑定。for属性的值和表单元素的id值一样,即可完成该label标签与该表单元素的绑定。-->
        <label class="custom-control-label" :for="'cd' + id">
          <!-- 商品的缩略图 -->
          <img :src="`${pic}`" alt="" />
        </label>
      </div>
    </div>
    <!-- 右侧信息区域 -->
    <div class="goods-info">
      <!-- 商品标题 -->
      <h6 class="goods-title">{{ title }}</h6>
      <div class="goods-info-bottom">
        <!-- 商品价格 -->
        <span class="goods-price">¥{{ price }}</span>
        <!-- 商品的数量 -->
        <Counter :num="count" :id="id"></Counter>
      </div>
    </div>
  </div>
</template>

<script>
import Counter from '@/components/Counter/Counter.vue'
export default {
  props: {
    // 商品的 id
    // 为啥在这里要封装一个 id 属性呢?
    // 原因:将来,子组件中商品的勾选状态变化之后, 需要通过子 -> 父的形式,
    // 通知父组件根据 id 修改对应商品的勾选状态。
    id: {
      required: true,
      type: Number,
    },
    // 要渲染的商品的标题
    title: {
      default: '',
      type: String,
    },
    // 要渲染的商品的图片
    pic: {
      default: '',
      type: String,
    },
    // 商品的单价
    price: {
      default: 0,
      type: Number,
    },
    // 商品的勾选状态
    state: {
      default: true,
      type: Boolean,
    },
    // 商品的购买数量
    count: {
      type: Number,
      default: 1,
    },
  },
  components: {
    Counter,
  },
  methods: {
    // 复选框选中状态发生了变化,就调用函数
    stateChange(e) {
      // this.state = !this.state
      // console.log(this.state)
      // props值 不可修改
      // target 属性规定哪个 DOM 元素触发了该事件。不会冒泡
      const newState = e.target.checked
      // 用自定义事件方法 向父组件传递信息
      this.$emit('state-change', { id: this.id, value: newState })
    },
  },
}
</script>

<style lang="less" scoped>
.goods-container {
  + .goods-container {
    border-top: 1px solid #efefef;
  }
  padding: 10px;
  display: flex;
  .thumb {
    display: flex;
    align-items: center;
    img {
      width: 100px;
      height: 100px;
      margin: 0 10px;
    }
  }

  .goods-info {
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    flex: 1;
    .goods-title {
      font-weight: bold;
      font-size: 12px;
    }
    .goods-info-bottom {
      display: flex;
      justify-content: space-between;
      .goods-price {
        font-weight: bold;
        color: red;
        font-size: 13px;
      }
    }
  }
}
</style>

//counter
<template>
  <div
    class="number-container d-flex justify-content-center align-items-center"
  >
    <!-- 减 1 的按钮 -->
    <button
      type="button"
      class="btn btn-light btn-sm"
      @click="sub"
      :disabled="isOk"
    >
      -
    </button>
    <!-- 购买的数量 -->
    <span class="number-box">{{ num }}</span>
    <!-- 加 1 的按钮 -->
    <button type="button" class="btn btn-light btn-sm" @click="add">+</button>
  </div>
</template>

<script>
// 1.导入eventBus.js
import bus from '@/components/eventBus.js'
export default {
  props: {
    id: {
      type: Number,
      require: 1,
    },
    num: {
      type: Number,
      default: 1,
    },
  },
  data() {
    return {
      isOk: false,
    }
  },
  methods: {
    sub() {
      if (this.num <= 1) {
        this.isOk = true
        return
      }
      const obj = {
        id: this.id,
        value: this.num - 1,
      }
      bus.$emit('numShare', obj)
    },
    // 点击按钮,数量加一
    add() {
      // 将更新之后的数据传送给app组件(异性兄弟节点),格式{id,value},用eventBus方法
      const obj = {
        id: this.id,
        value: this.num + 1,
      }
      this.isOk = false
      bus.$emit('numShare', obj)
    },
  },
  created() {
    if (this.num === 1) this.isOk = true
  },
}
</script>

<style lang="less" scoped>
.number-box {
  min-width: 30px;
  text-align: center;
  margin: 0 5px;
  font-size: 12px;
}

.btn-sm {
  width: 30px;
}
</style>

// Footer组件
<template>
  <div class="footer-container">
    <!-- 左侧的全选 -->
    <div class="custom-control custom-checkbox">
      <input
        type="checkbox"
        class="custom-control-input"
        id="cbFull"
        :checked="isAllchecked"
        @change="allChange"
      />
      <label class="custom-control-label" for="cbFull">全选</label>
    </div>

    <!-- 中间的合计 -->
    <div>
      <span>合计:</span>
      <span class="total-price">¥{{ amount }}</span>
    </div>

    <!-- 结算按钮 -->
    <button type="button" class="btn btn-primary btn-settle">
      结算({{ all }})
    </button>
  </div>
</template>

<script>
export default {
  props: {
    isAllchecked: {
      default: true,
      type: Boolean,
    },
    amount: {
      default: 0,
      type: Number,
    },
    all: {
      default: 0,
      type: Number,
    },
  },
  methods: {
    // 利用自定义事件向父组件传递全选变化状态
    allChange(e) {
      this.$emit('all-change', e.target.checked)
    },
  },
}
</script>

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

.custom-checkbox {
  display: flex;
  align-items: center;
}

#cbFull {
  margin-right: 5px;
}

.btn-settle {
  height: 80%;
  min-width: 110px;
  border-radius: 25px;
  font-size: 12px;
}

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值