【Vue-cli】组件化实现购物车,包括单选按钮全选按钮联动

在这里插入图片描述

代码实例

App.vue

<template>
  <div id="app">
      <index></index>
  </div>
</template>

<script>
// 引入主页index
import Index from './pages/Index';
export default {
  name: 'App',
  components: {
    Index
  }
};
</script>

<style>
*{
  margin: 0;
  padding: 0;
}
ul,li{
  list-style: none;
}
a{
  text-decoration: none;
  color:#333333;
}
em{
  font-style: normal;
}
</style>

Pages/Index.vue

<template>
  <div id="wrap">
    <s-header></s-header>
    <s-main
      :list="cartList"
      @changestate="handleCheck"
      @addnum="addNumber"
      @reducenum="reduceNumber"
      @delete="deleteItem"
    >
    </s-main>
    <s-pay :list="cartList" @change="checkAll"></s-pay>
    <s-footer></s-footer>
  </div>
</template>

<script>
// 引入头部文件, 主页文件, 底部文件
import SHeader from './components/Header';
import SMain from './components/Main';
import SPay from './components/Pay';
import SFooter from './components/Footer';
export default {
  components: {
    SHeader,
    SMain,
    SPay,
    SFooter
  },

  data() {
    return {
      cartList: [
        {
          id: 1,
          img: '/static/images/product1.jpg',
          name: '左右鞋店春夏爆款凉拖',
          color: '粉',
          size: '35',
          price: 149,
          number: 1,
          checked: true
        },
        {
          id: 2,
          img: '/static/images/product2.jpg',
          name: '左右鞋店春夏爆款福利',
          color: '黑',
          size: '35',
          price: 169,
          number: 1,
          checked: true
        },
        {
          id: 3,
          img: '/static/images/product3.jpg',
          name: '左右鞋店春夏2021爆款男士包',
          color: '黑',
          size: 'XL',
          price: 499,
          number: 1,
          checked: true
        }
      ]
    };
  },

  methods: {
    handleCheck(id) {
      // console.log(id)
      const selectedGood = this.cartList.find((item) => item.id === id);
      selectedGood.checked = selectedGood.checked;
      // console.log(selectedGood.checked)
    },
    checkAll(state) {
      // console.log(state)
      // 全选控制每一项单选
      this.cartList.map((item) => {
        item.checked = state;
      });
    },
    addNumber(id) {
      // console.log(id)
      const selectedGood = this.cartList.find((item) => item.id === id);
      selectedGood.number++;
    },
    reduceNumber(id) {
      // console.log(id)
      const selectedGood = this.cartList.find((item) => item.id === id);
      if (selectedGood.number > 1) selectedGood.number--;
    },
    deleteItem(id) {
      if (confirm('确定要删除吗?')) {
        // 删除id对应的数据
        const index = this.cartList.findIndex((item) => item.id === id);
        this.cartList.splice(index, 1);
      }
    }
  }
};
</script>

<style scoped>
#wrap {
  max-width: 750px;
  width: 7.5rem;
  height: 15rem;
  border: 1px solid #333;
  margin: 0 auto;
}
</style>

Pages/Components/Header.vue

<template>
  <div class="header">购物车</div>
</template>

<script>
export default {};
</script>

<style scoped>
.header {
  width: 100%;
  height: 1rem;
  background: #ff5e46;
  color: #ffffff;
  font: 0.45rem/1rem "微软雅黑";
  text-align: center;
}
</style>

Pages/Components/Main.vue

<template>
  <div class="main">
    <ul class="list">
      <li v-for="item of list" :key="item.id" :item="item">
        <input
          type="checkbox"
          id="checkbox"
          v-model="item.checked"
          @change="changeState(item.id)"
        />
        <label for="checkbox"></label>
        <div class="img">
          <img :src="item.img" />
        </div>
        <div class="desc">
          <h4>{{ item.name }}</h4>
          <div class="spec">
            <span>{{ item.color }}</span>
            <span>{{ item.size }}</span>
            <span class="iconfont icon-icon1"></span>
          </div>
          <p class="price">{{ item.price.toFixed(2) }}</p>
          <div class="shoppingnum">
            <span
              class="iconfont icon-jian minus"
              @click="reduceNumber(item.id)"
            ></span>
            <span class="num">{{ item.number }}</span>
            <span
              class="iconfont icon-hao plus"
              @click="addNumber(item.id)"
            ></span>
          </div>
        </div>
        <div class="del" @click="deleteItem(item.id)">删除</div>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  // 接收cartList属性
  props: {
    list: {
      type: Array,
      required: true
    }
  },
  methods: {
    changeState(id) {
      // console.log(id)
      this.$emit('changestate', id);
    },
    // 通知父组件修改处理
    addNumber(id) {
      // console.log(id)
      this.$emit('addnum', id);
    },
    reduceNumber(id) {
      this.$emit('reducenum', id);
    },
    deleteItem(id) {
      this.$emit('delete', id);
    }
  },
  // 生命周期
  mounted() {
    console.log(this.list);
  }
};
</script>

<style scoped>
.list li {
  width: 7.5rem;
  height: 2.4rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
li > input[type="checkbox"] {
  width: 0.25rem;
  height: 0.25rem;
  outline: none;
  -webkit-appearance: none; /*清除默认样式 */
  border: 0.01rem solid #ff5e46;
  border-radius: 50%;
  margin: 0 0.2rem 0 0.2rem;
}
li > input[type="checkbox"]:checked {
  background: url("../../assets/images/selected.jpg") no-repeat center center;
}
.img img {
  width: 1.5rem;
  height: 1.5rem;
}
.desc {
  width: 4.5rem;
  height: 2rem;
  margin-left: 0.1rem;
  position: relative;
}
.desc h4 {
  padding-top: 0.1rem;
  font-size: 0.25rem;
  color: #666666;
}
.desc .spec {
  margin-top: 0.5rem;
  width: 0.9rem;
  height: 0.3rem;
  background: #eeeeee;
  display: flex;
  justify-content: space-between;
}
.desc .spec span {
  display: inline-block;
  width: 0.3rem;
  height: 0.3rem;
  text-align: center;
  font: 0.18rem/0.3rem "微软雅黑";
  color: #999999;
}
.desc .price {
  font: bold 0.25rem "Arial";
  padding-top: 0.17rem;
  color: #ff5e46;
}
.desc .shoppingnum {
  width: 1.6rem;
  height: 0.4rem;
  border: 0.01rem solid #e4e4e4;
  display: flex;
  position: absolute;
  right: 0.4rem;
  bottom: 0.25rem;
}
.desc .shoppingnum span {
  text-align: center;
}
.desc .shoppingnum .plus,
.desc .shoppingnum .minus {
  width: 0.4rem;
  height: 0.4rem;
  font: 0.2rem/0.4rem "微软雅黑";
  color: #333333;
}
.desc .shoppingnum .num {
  width: 0.8rem;
  height: 0.4rem;
  border-left: 0.01rem solid #e4e4e4;
  border-right: 0.01rem solid #e4e4e4;
  font: bold 0.3rem "宋体";
}
li .del {
  width: 0.8rem;
  height: 2.4rem;
  background: #f51f24;
  color: #ffffff;
  font: 0.25rem/2.4rem "微软雅黑";
  text-align: center;
  cursor: pointer;
  border-top: 0.01rem solid #908e68;
}
</style>

Pages/Components/Pay.vue

<template>
  <div class="check">
    <div class="selectall">
      <input type="checkbox" v-model="allCheck" @change="getAllChecked" />全选
    </div>
    <div class="sum">
      合计:
      <span>{{ total }}</span>
    </div>
    <button>去结算</button>
  </div>
</template>

<script>
// let state;
export default {
  // 接收cartList属性
  props: {
    list: {
      type: Array,
      required: true
    }
  },
  // 计算属性
  computed: {
    total() {
      let total = 0;
      this.list.forEach((item) => {
        if (item.checked) {
          total += item.price * item.number;
        }
      });
      return total.toFixed(2);
    },
    allCheck: {
      get() {
        return this.list.every((item) => item.checked);
      },

      set(value) {
        // state = value;
        // console.log('all check:', value);
      }
    }
  },
  methods: {
    // 点击全选和反选
    getAllChecked() {
      // console.log('check function', state);
      // 将this.allCheck作为参数传递到父元素
      this.$emit('change', !this.allCheck);
    }
  }
};
</script>

<style scoped>
.check {
  width: 7.5rem;
  height: 0.9rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: fixed;
  bottom: 1.6rem;
  left: 50%;
  transform: translateX(-50%);
  background: #ffffff;
}
.check .selectall {
  width: 1.6rem;
  font-size: 0.2rem;
  margin-left: 0.1rem;
}
.check .selectall input[type="checkbox"] {
  width: 0.25rem;
  height: 0.25rem;
  outline: none;
  -webkit-appearance: none; /*清除默认样式 */
  border: 0.01rem solid #ff5e46;
  border-radius: 50%;
  margin: 0 0.2rem 0 0.2rem;
  vertical-align: bottom;
}
.check .selectall input[type="checkbox"]:checked {
  background: url("../../assets/images/selected.jpg") no-repeat center center;
}
.check .sum {
  width: 3.5rem;
  font-size: 0.2rem;
}
.check .sum span {
  font: bold 0.24rem "微软雅黑";
  color: #ff5e46;
}
.check button {
  width: 2.2rem;
  height: 0.9rem;
  border: none;
  outline: none;
  cursor: pointer;
  font-size: 0.26rem;
  color: #ffffff;
  background: #ff5e46;
}
</style>

Pages/Components/Footer.vue

<template>
  <footer class="footer">
    <a href="#">
      <i class="iconfont icon-yemian"></i>
      <em>首页</em>
    </a>
    <a href="#">
      <i class="iconfont icon-leimupinleifenleileibie2"></i>
      <em>分类</em>
    </a>
    <a href="#">
      <i class="iconfont icon-gouwuche"></i>
      <em>购物车</em>
    </a>
    <a href="#">
      <i class="iconfont icon-mine"></i>
      <em>我的</em>
    </a>
  </footer>
</template>

<script>
export default {};
</script>

<style scoped>
.footer {
  max-width: 750px;
  width: 100%;
  height: 1.6rem;
  background: #ffffff;
  border-top: 1px solid #908e68;
  position: fixed;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  justify-content: space-around;
  align-items: center;
}
.footer a {
  display: inline-block;
  width: 0.6rem;
  height: 0.85rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-around;
}
.footer a i {
  display: block;
  font-size: 0.4rem;
  color: #8a8a8a;
}
.footer a em {
  font: 0.18rem/1 "微软雅黑";
  color: #878787;
}
.footer a:nth-of-type(3) i,
.footer a:nth-of-type(3) em {
  color: #ff5e46;
}
</style>

main.js

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue';
import App from './App';
import '../static/font/iconfont.css';

Vue.config.productionTip = false;

/* eslint-disable no-new */
new Vue({
  el: '#app',
  components: { App },
  template: '<App/>'
});

代码实例2

mock/cart.js

const cart = [
	{
		id: 1,
		gid: 11,
		number: 1,
		selected: true,
		name: '【冰点清仓】天然淡水珍珠耳钉/耳环/网红爆款纯银饰品',
		price: 83,
		img: 'https://h2.appsimg.com/a.appsimg.com/upload/merchandise/pdcvis/2020/09/09/193/7f280391-2ca8-437b-bb42-c5a876ed8782_356x356_90.jpg'
	},
	{
		id: 2,
		gid: 12,
		number: 1,
		selected: true,
		name: '18K金进口塑胶耳背透明硅胶耳堵耳帽防过敏耳塞耳迫耳饰配件',
		price: 29,
		img: 'https://h2.appsimg.com/a.appsimg.com/upload/merchandise/pdcvis/2020/12/26/199/660e0794-c693-47aa-baa7-e9632cfe0655_356x356_90.jpg'
	},
	{
		id: 3,
		gid: 13,
		number: 1,
		selected: true,
		name: '【冰点清仓】气质款 7-8MM稀有混彩天然淡水珍珠项链',
		price: 143,
		img: 'https://h2.appsimg.com/a.appsimg.com/upload/merchandise/pdcvis/2020/09/09/12/018ea256-c179-4259-99ab-959d869074ba_356x356_90.jpg'
	},
	{
		id: 4,
		gid: 13,
		number: 1,
		selected: true,
		name: '热卖爆款 四季百搭款 小蜜蜂胸针一款两戴珍珠胸针/珍珠项链',
		price: 79,
		img: 'https://h2.appsimg.com/a.appsimg.com/upload/merchandise/pdcvis/2020/09/08/193/5f91e56a-5624-4092-8b15-695b16842911_356x356_90.jpg'
	}
]

export default cart

App.vue

<template>
<div>
  <u-cart />
</div>
</template>

<script>
import UCart from './pages/cart/Index'
export default {
  components: {
    UCart
  }
}
</script>
<style scoped>

</style>

Pages/Index.vue

<template>
<div>
  <h1>购物车</h1>
  <u-list :list="cartList" @update="updateCart" @select="toggleSelect"/>
  <hr>
  <u-count :selectAll="selectAll" :total="total" @select="toggleSelectAll"/>
</div>
</template>

<script>
import UList from './components/List'
import UCount from './components/Count'
import cartList from '@/mock/cart.js'
export default {
  components: {
    UList,
    UCount
  },
  data () {
    return {
      cartList: [],
      selectAll: true,
      total: 0
    }
  },
  mounted () {
    // 初始化数据
    this.cartList = [...cartList]
    // 处理购物车的统计
    this.countCart()
  },
  methods: {
    countCart () {
      // 统计购物车 是否全选 合计
      let total = 0
      let selectAll = true
      if (this.cartList.length > 0) {
        // 统计选中的商品的合计
        this.cartList.forEach(item => {
          if (item.selected) {
            total += item.number * item.price
          } else {
            selectAll = false
          }
        })
      } else {
        selectAll = false
      }
      
      this.total = total
      this.selectAll = selectAll
    },
    updateCart (res) {
      // 修改商品的购买数量(加或者减)
      const cart = this.cartList.find(item => item.id === res.id)
      if (res.type === 'add') { // 数量+1
        cart.number += 1
      } else if (res.type === 'reduce') { // 数量-1
        if (cart.number > 1) {
          cart.number -= 1
        }
      }
      // 处理购物车的统计
      this.countCart()
    },
    toggleSelect (id) {
      // 修改对应商品的是否选中
      const cart = this.cartList.find(item => item.id === id)
      cart.selected = !cart.selected
      // 处理购物车的统计
      this.countCart()
    },
    toggleSelectAll () {
      // 全选按钮的切换
      this.selectAll = !this.selectAll
      this.cartList = this.cartList.map(item => {
        item.selected = this.selectAll
        return item
      })
      // 处理购物车的统计
      this.countCart()
    }
  }
}
</script>
<style scoped>

</style>

Pages/Compoments/List.vue

<template>
<ul>
  <li v-for="item of list" :key="item.id" style="margin-bottom:20px">
    <img @click="toggleSelect(item.id)" :src="item.selected ? selectImg : notSelectImg" />
    <img :src="item.img" height="120"/>
    <span>{{item.price}}</span>
    <button @click="reduceCart(item.id)">-</button>
    <span>{{item.number}}</span>
    <button @click="addCart(item.id)">+</button>
  </li>
</ul>
</template>

<script>
export default {
  props: {
    list: {
      type: Array,
      required: true
    }
  },
  data () {
    return {
      selectImg: require('@/assets/img/selected.png'),
      notSelectImg: require('@/assets/img/not-select.png'),
    }
  },
  methods: {
    addCart (id) {
      // 通知父组件修改购物车对应商品的购买数量
      this.$emit('update', { id, type: 'add' })
    },
    reduceCart (id) {
      // 通知父组件修改购物车对应商品的购买数量
      this.$emit('update', { id, type: 'reduce' })
    },
    toggleSelect (id) {
      // 通知父组件修改购物车对应商品的是否选中
      this.$emit('select', id)
    }
  }
}
</script>
<style scoped>

</style>

Pages/Components/Count.vue

<template>
<h1>
  <img @click="toggleSelectAll" :src="selectAll ? selectImg: notSelectImg" />
  <span>合计:¥{{total}}</span>
</h1>
</template>

<script>
export default {
  props: {
    selectAll: Boolean,
    total: Number
  },
  data () {
    return {
      selectImg: require('@/assets/img/selected.png'),
      notSelectImg: require('@/assets/img/not-select.png'),
    }
  },
  methods: {
    toggleSelectAll () {
      this.$emit('select')
    }
  }
}
</script>
<style scoped>

</style>

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值