1. 程式人生 > 其它 >【Vue-cli】元件化實現購物車,包括單選按鈕全選按鈕聯動

【Vue-cli】元件化實現購物車,包括單選按鈕全選按鈕聯動

技術標籤:程式碼例項Vue.jsvue.jses6

在這裡插入圖片描述

程式碼例項

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>