小言_互联网的博客

八、购物车案例

425人阅读  评论(0)

一、购物车案例

1.1、使用npm i命令,安装依赖

1.2、编写页面布局

App.vue

<template>
  <div class="app-container">
    <Header></Header>
    <Goods></Goods>
    <Footer></Footer>
  </div>
</template>

<script>
import Header from '@/components/Header/Header.vue'
import Goods from '@/components/Goods/Goods.vue'
import Footer from '@/components/Footer/Footer.vue'
export default {
     
  components: {
     
    Header,
    Goods,
    Footer
  }
}
</script>

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

1.3、获取购物车列表数据

使用npm i axios命令,安装 axios 请求库。

App.vue

<template>
  <div class="app-container">
    <Header></Header>
    <Goods></Goods>
    <Footer></Footer>
  </div>
</template>

<script>
// 导入 axios 请求库
import axios from 'axios'
// 导入需要的组件
import Header from '@/components/Header/Header.vue'
import Goods from '@/components/Goods/Goods.vue'
import Footer from '@/components/Footer/Footer.vue'
export default {
     
  data(){
     
    return {
     
      // 用来存储购物车的列表数据,默认为空数组
      list: []
    }
  },
  methods: {
     
    // 封装请求列表数据的方法
    async initCartList() {
     
      // 调用 axios 的 get 方法,请求列表数据
      const {
      data: res } = await axios.get('https://www.escook.cn/api/cart')
      // 只要请求回来的数据,在页面渲染期间要用到,则必须转存到 data 中
      if(res.status === 200){
     
        this.list = res.list
      }
    }
  },
  created(){
     
    // 调用请求数据的方法
    this.initCartList()
  },
  components: {
     
    Header,
    Goods,
    Footer
  }
}
</script>

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

1.4、父传子,渲染购物车列表

App.vue —> Goods.vue 父传子,采用自定义属性的方式将数据由父组件传递给子组件。
App.vue

<template>
  <div class="app-container">
    <!-- Header 头部区域 -->
    <Header></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"
    ></Goods>
    <Footer></Footer>
  </div>
</template>

<script>
// 导入 axios 请求库
import axios from 'axios'
// 导入需要的组件
import Header from '@/components/Header/Header.vue'
import Goods from '@/components/Goods/Goods.vue'
import Footer from '@/components/Footer/Footer.vue'
export default {
     
  data(){
     
    return {
     
      // 用来存储购物车的列表数据,默认为空数组
      list: []
    }
  },
  methods: {
     
    // 封装请求列表数据的方法
    async initCartList() {
     
      // 调用 axios 的 get 方法,请求列表数据
      const {
      data: res } = await axios.get('https://www.escook.cn/api/cart')
      // 只要请求回来的数据,在页面渲染期间要用到,则必须转存到 data 中
      if(res.status === 200){
     
        this.list = res.list
      }
    }
  },
  created(){
     
    // 调用请求数据的方法
    this.initCartList()
  },
  components: {
     
    Header,
    Goods,
    Footer
  }
}
</script>

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

Goods.vue

<template>
  <div class="goods-container">
    <!-- 左侧图片 -->
    <div class="thumb">
      <div class="custom-control custom-checkbox">
        <!-- 复选框 -->
        <input type="checkbox" class="custom-control-input" :id="'cb' + id" :checked="state" />
        <label class="custom-control-label" :for="'cb' + 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>
        <!-- 商品的数量 -->
      </div>
    </div>
  </div>
</template>

<script>
export default {
     
  props: {
     
    // 商品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
    }
  }
}
</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>

1.5、子传父、修改商品勾选状态

Goods.vue

<template>
  <div class="goods-container">
    <!-- 左侧图片 -->
    <div class="thumb">
      <div class="custom-control custom-checkbox">
        <!-- 复选框 -->
        <input type="checkbox" class="custom-control-input" :id="'cb' + id" 
          :checked="state" @change="stateChange"/>
        <label class="custom-control-label" :for="'cb' + 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>
      </div>
    </div>
  </div>
</template>

<script>

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
    }
  },
  methods: {
     
    // 只要复选框的选中状态发生了变化,就会调用这个处理函数
    stateChange(e) {
     
      const newState = e.target.checked
      // 触发自定义事件
      this.$emit('state-change', {
      id: this.id, value: newState })
    }
  }
}
</script>

App.vue

<template>
  <div class="app-container">
    <!-- Header 头部区域 -->
    <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>

    <!-- Footer 区域 -->
    <Footer></Footer>
  </div>
</template>

<script>
// 导入 axios 请求库
import axios from 'axios'
// 导入需要的组件
import Header from '@/components/Header/Header.vue'
import Goods from '@/components/Goods/Goods.vue'
import Footer from '@/components/Footer/Footer.vue'
export default {
     
  data() {
     
    return {
     
      // 用来存储购物车的列表数据,默认为空数组
      list: []
    }
  },
  created() {
     
    // 调用请求数据的方法
    this.initCartList()
  },
  methods: {
     
    // 封装请求列表数据的方法
    async initCartList() {
     
      // 调用 axios 的 get 方法,请求列表数据
      const {
      data: res } = await axios.get('https://www.escook.cn/api/cart')
      // 只要请求回来的数据,在页面渲染期间要用到,则必须转存到 data 中
      if (res.status === 200) {
     
        this.list = res.list
      }
    },
    // 接收子组件传递过来的数据
    // e 的格式为 { id, value }
    getNewState(e) {
     
      this.list.some(item => {
     
        if (item.id === e.id) {
     
          item.goods_state = e.value
          // 终止后续的循环
          return true
        }
      })
    }
  },
  components: {
     
    Header,
    Goods,
    Footer
  }
}
</script>

1.6、计算属性、子传父实现全选功能


Footer.vue

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

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

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

<script>
export default {
     
  props: {
     
    // 全选的状态
    isfull: {
     
      type: Boolean,
      default: true
    }
  },
  methods: {
     
    // 监听到了全选的状态变化
    fullChange(e) {
     
      this.$emit('full-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>

App.vue

<template>
  <div class="app-container">
    <!-- Header 头部区域 -->
    <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>

    <!-- Footer 区域 -->
    <Footer :isfull="fullState" @full-change="getFullState"></Footer>
  </div>
</template>

<script>
// 导入 axios 请求库
import axios from 'axios'
// 导入需要的组件
import Header from '@/components/Header/Header.vue'
import Goods from '@/components/Goods/Goods.vue'
import Footer from '@/components/Footer/Footer.vue'
export default {
     
  data() {
     
    return {
     
      // 用来存储购物车的列表数据,默认为空数组
      list: []
    }
  },
  // 计算属性
  computed: {
     
    // 动态计算出全选的状态是 true 还是 false
    fullState() {
     
      return this.list.every(item => item.goods_state)
    }
  },
  created() {
     
    // 调用请求数据的方法
    this.initCartList()
  },
  methods: {
     
    // 封装请求列表数据的方法
    async initCartList() {
     
      // 调用 axios 的 get 方法,请求列表数据
      const {
      data: res } = await axios.get('https://www.escook.cn/api/cart')
      // 只要请求回来的数据,在页面渲染期间要用到,则必须转存到 data 中
      if (res.status === 200) {
     
        this.list = res.list
      }
    },
    // 接收子组件传递过来的数据
    // e 的格式为 { id, value }
    getNewState(e) {
     
      this.list.some(item => {
     
        if (item.id === e.id) {
     
          item.goods_state = e.value
          // 终止后续的循环
          return true
        }
      })
    },
    // 接收 Footer 子组件传递过来的全选按钮的状态
    getFullState(val) {
     
      this.list.forEach(item => (item.goods_state = val))
    }
  },
  components: {
     
    Header,
    Goods,
    Footer
  }
}
</script>

1.7、计算属性、父传子实现商品总价和总数量


App.vue

<template>
  <div class="app-container">
    <!-- Header 头部区域 -->
    <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>

    <!-- Footer 区域 -->
    <Footer :isfull="fullState" :amount="amt" :all="total" @full-change="getFullState"></Footer>
  </div>
</template>

<script>
// 导入 axios 请求库
import axios from 'axios'
// 导入需要的组件
import Header from '@/components/Header/Header.vue'
import Goods from '@/components/Goods/Goods.vue'
import Footer from '@/components/Footer/Footer.vue'
export default {
     
  data() {
     
    return {
     
      // 用来存储购物车的列表数据,默认为空数组
      list: []
    }
  },
  // 计算属性
  computed: {
     
    // 动态计算出全选的状态是 true 还是 false
    fullState() {
     
      return this.list.every(item => item.goods_state)
    },
    // 已勾选商品的总价格
    amt() {
     
      // 1. 先 filter 过滤
      // 2. 再 reduce 累加
      return this.list
        .filter(item => item.goods_state)
        .reduce((total, item) => (total += item.goods_price * item.goods_count), 0)
    },
    // 已勾选商品的总数量
    total() {
     
      return this.list.filter(item => item.goods_state).reduce((t, item) => (t += item.goods_count), 0)
    }
  },
  created() {
     
    // 调用请求数据的方法
    this.initCartList()
  },
  methods: {
     
    // 封装请求列表数据的方法
    async initCartList() {
     
      // 调用 axios 的 get 方法,请求列表数据
      const {
      data: res } = await axios.get('https://www.escook.cn/api/cart')
      // 只要请求回来的数据,在页面渲染期间要用到,则必须转存到 data 中
      if (res.status === 200) {
     
        this.list = res.list
      }
    },
    // 接收子组件传递过来的数据
    // e 的格式为 { id, value }
    getNewState(e) {
     
      this.list.some(item => {
     
        if (item.id === e.id) {
     
          item.goods_state = e.value
          // 终止后续的循环
          return true
        }
      })
    },
    // 接收 Footer 子组件传递过来的全选按钮的状态
    getFullState(val) {
     
      this.list.forEach(item => (item.goods_state = val))
    }
  },
  components: {
     
    Header,
    Goods,
    Footer
  }
}
</script>

Footer.vue

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

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

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

<script>
export default {
     
  props: {
     
    // 全选的状态
    isfull: {
     
      type: Boolean,
      default: true
    },
    // 总价格
    amount: {
     
      type: Number,
      default: 0
    },
    // 已勾选的商品的总数量
    all: {
     
      type: Number,
      default: 0
    }
  },
  methods: {
     
    // 监听到了全选的状态变化
    fullChange(e) {
     
      this.$emit('full-change', e.target.checked)
    }
  }
}
</script>

1.8、使用 eventBus 实现数量动态增减及价格变化



Goods.vue

<template>
  <div class="goods-container">
    <!-- 左侧图片 -->
    <div class="thumb">
      <div class="custom-control custom-checkbox">
        <!-- 复选框 -->
        <input type="checkbox" class="custom-control-input" :id="'cb' + id" 
          :checked="state" @change="stateChange"/>
        <label class="custom-control-label" :for="'cb' + 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
    }
  },
  methods: {
     
    // 只要复选框的选中状态发生了变化,就会调用这个处理函数
    stateChange(e) {
     
      const newState = e.target.checked
      // 触发自定义事件
      this.$emit('state-change', {
      id: this.id, value: newState })
    }
  },
  components: {
     
    Counter
  }
}
</script>

Counter.vue

<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">-</button>
    <!-- 购买的数量 -->
    <span class="number-box">{
  { num }}</span>
    <!-- 加 1 的按钮 -->
    <button type="button" class="btn btn-light btn-sm" @click="add">+</button>
  </div>
</template>

<script>
import bus from '@/components/eventBus.js'
export default {
     
  props: {
     
    // 接收商品的 id值,将来使用 EventBus 方案
    // 把数量传递到App.vue的时候,需要通知App组件,更新哪个商品的数量
    id: {
     
      type: Number,
      required: true
    },
    // 接收到的 num 数量值
    num: {
     
      type: Number,
      default: 1
    }
  },
  methods: {
     
    // 点击按钮,数值 + 1
    add() {
     
      // 要发送给 App 的数据格式为 {id,value}
      // 其中,id是商品的id;value是商品最新的购买数量
      const obj = {
     id: this.id, value: this.num + 1}
      // 要做的事情:通过 EventBus 把 obj 对象,发送给 App.vue 组件
      bus.$emit('share', obj)
    },
    sub() {
     
      if (this.num - 1 === 0) return
      // 要发送给 App 的数据格式为 { id, value }
      // 其中,id 是商品的 id; value 是商品最新的购买数量
      const obj = {
      id: this.id, value: this.num - 1 }
      // 要做的事情:通过 EventBus 把 obj 对象,发送给 App.vue 组件
      bus.$emit('share', obj)
    }
  }
}
</script>

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

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

App.vue

<template>
  <div class="app-container">
    <!-- Header 头部区域 -->
    <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>

    <!-- Footer 区域 -->
    <Footer :isfull="fullState" :amount="amt" :all="total" @full-change="getFullState"></Footer>
  </div>
</template>

<script>
// 导入 axios 请求库
import axios from 'axios'
// 导入需要的组件
import Header from '@/components/Header/Header.vue'
import Goods from '@/components/Goods/Goods.vue'
import Footer from '@/components/Footer/Footer.vue'

import bus from '@/components/eventBus.js'
export default {
     
  data() {
     
    return {
     
      // 用来存储购物车的列表数据,默认为空数组
      list: []
    }
  },
  // 计算属性
  computed: {
     
    // 动态计算出全选的状态是 true 还是 false
    fullState() {
     
      return this.list.every(item => item.goods_state)
    },
    // 已勾选商品的总价格
    amt() {
     
      // 1. 先 filter 过滤
      // 2. 再 reduce 累加
      return this.list
        .filter(item => item.goods_state)
        .reduce((total, item) => (total += item.goods_price * item.goods_count), 0)
    },
    // 已勾选商品的总数量
    total() {
     
      return this.list.filter(item => item.goods_state).reduce((t, item) => (t += item.goods_count), 0)
    }
  },
  created() {
     
    // 调用请求数据的方法
    this.initCartList()

    bus.$on('share', val => {
     
      this.list.some(item => {
     
        if (item.id === val.id) {
     
          item.goods_count = val.value
          return true
        }
      })
    })
  },
  methods: {
     
    // 封装请求列表数据的方法
    async initCartList() {
     
      // 调用 axios 的 get 方法,请求列表数据
      const {
      data: res } = await axios.get('https://www.escook.cn/api/cart')
      // 只要请求回来的数据,在页面渲染期间要用到,则必须转存到 data 中
      if (res.status === 200) {
     
        this.list = res.list
      }
    },
    // 接收子组件传递过来的数据
    // e 的格式为 { id, value }
    getNewState(e) {
     
      this.list.some(item => {
     
        if (item.id === e.id) {
     
          item.goods_state = e.value
          // 终止后续的循环
          return true
        }
      })
    },
    // 接收 Footer 子组件传递过来的全选按钮的状态
    getFullState(val) {
     
      this.list.forEach(item => (item.goods_state = val))
    }
  },
  components: {
     
    Header,
    Goods,
    Footer
  }
}
</script>

eventBus.js

import Vue from 'vue'
export default new Vue()


转载:https://blog.csdn.net/btt2013/article/details/128375588
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场