用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>
// 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"
/>
<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>
//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>
// 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>