用vue做一个购物车案例
涉及知识点
- 组件基本使用
- vue指令(v-text、v-bind、v-on、v-for)
- 计算属性
- 过滤器函数
- 生命周期函数
- axios
- 父子组件,兄弟组件相互传信息(自定义属性,自定义事件,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>
<Footer
:isAllchecked="isAllstate"
@all-change="getAllState"
:amount="amt"
:all="total"
>Footer>
div>
template>
<script>
import bus from '@/components/eventBus.js'
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)
},
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: {
async initCartList() {
const { data: res } = await axios.get('https://www.escook.cn/api/cart')
if (res.status === 200) {
this.list = res.list
}
},
getNewState(e) {
this.list.some((item) => {
if (item.id === e.id) {
item.goods_state = e.value
return true
}
})
},
getAllState(val) {
this.list.forEach((item) => (item.goods_state = val))
},
},
created() {
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>

- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
// 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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
// 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"
/>
<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: {
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) {
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>

- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
//counter
<template>
<div
class="number-container d-flex justify-content-center align-items-center"
>
<button
type="button"
class="btn btn-light btn-sm"
@click="sub"
:disabled="isOk"
>
-
button>
<span class="number-box">{{ num }}span>
<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: {
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() {
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
// 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
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91