1. 实现步骤
【备注】这里的接口地址目前(2022年6月)还是可以用的哦~
2. 实现
2.1 代码结构
使用的样式是Boostrap,需要npm i bootstrap,然后在main.js中引入bootstrap。
2.2 Header头部的代码
<template>
<div class="header-container">{{ title }}</div>
</template>
<script>
export default {
props: {
title: {
default: '',
type: String
}
}
};
</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;
left: 0;
}
</style>
2.3 Goods商品组件代码
代码:
<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 '../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: {
default: 1,
type: Number,
}
},
methods: {
// 复选框发生改变,调用这个事件
stateChange(e) {
// console.log(e);
const newState = e.target.checked;
// console.log(this.id);
// 触发自定义事件(子传父)
this.$emit('state-change', { id: this.id, value: newState})
}
},
components: {
Counter,
}
};
</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>
2.4 Footer组件代码
<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: {
default: true,
type: Boolean,
},
amount: {
default: 0,
type: Number,
},
all: {
default: 0,
type: Number,
}
},
methods: {
// 监听全选的状态变化
fullChange(e) {
// console.log(e.target.checked);
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;
color: #fff;
background-color: #1d7bff;
border: 0;
}
.total-price {
font-weight: bold;
font-size: 14px;
color: red;
}
</style>
2.5 Counter组件代码
<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: {
default: 1,
type: Number,
},
},
methods: {
// 点击按钮,数值加1
add() {
// 要发送给App的数据,id是商品的id,value是商品的数量
const obj = {
id : this.id,
value : this.num + 1,
}
// 通过eventBus把obj对象发送给App.vue组件
// console.log(obj);
bus.$emit('share', obj);
},
// 点击按钮,数值减1
sub() {
if(this.num-1 === 0) return;
const obj = {
id: this.id,
value: this.num - 1,
}
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>
2.6 App.vue根组件代码
<template>
<div id="app-container">
<hr>
<!-- 头部区域 -->
<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>
<br>
</div>
</template>
<script>
import axios from "axios";
import Header from "@/components/Header/Header";
import Goods from "./components/Goods/Goods.vue";
import Footer from "@/components/Footer/Footer";
import bus from '@/components/eventBus.js';
export default {
data() {
return {
// 用来存储购物车列表数据,默认为空数组
list: [],
};
},
methods: {
// 封装请求列表数据的方法
async initCardList() {
const { data: res } = await axios.get("https://www.escook.cn/api/cart");
// console.log(res);
if (res.status === 200) {
this.list = res.list;
}
// console.log(this.list);
},
// 接收子组件发来的数据
getNewState(e) {
// console.log(e);
this.list.some((item) => {
if (item.id === e.id) {
item.goods_state = e.value;
return true;
}
});
},
// 接收Footer子组件发来的全选状态
getFullState(val) {
// console.log(val);
this.list.forEach((item) => {
item.goods_state = val;
});
},
},
components: {
Header,
Goods,
Footer,
},
created() {
this.initCardList();
bus.$on('share', (val) => {
// console.log(val);
this.list.some(item => {
if(item.id === val.id) {
item.goods_count = val.value;
// 终止循环
return true;
}
})
})
},
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)=>{
return total += item.goods_price * item.goods_count;
},0)
},
// 已勾选的数量总数
total() {
return this.list.filter(item=>item.goods_state).reduce((t,item)=> {
return t += item.goods_count;
},0)
}
},
};
</script>
<style lang="less">
.app-container {
padding-top: 45px;
padding-bottom: 50px;
}
</style>
2.7 eventBus.js 代码
import Vue from 'vue';
export default new Vue()
2.8 效果
文章来源:https://www.toymoban.com/news/detail-483174.html
3. 总结
3.1 组件之间的嵌套关系
- App为根组件
- App有三个子组件:Header、Goods、Footer;
- Goods有一个子组件:Counter;
3.2 这个案例涉及到的知识点
- 使用axios的get方法获取商品信息列表;
- vue的组件化;
- v-bind、v-for、@事件绑定;
- 父组件向子组件传值:自定义属性;
- 子组件向父组件传值:自定义事件;
- 兄弟组件之间传值,使用eventBus.js;
- 计算属性的使用(注意一定要return);
- 数组的forEach、every、filter、some、reduce等方法的使用;
参考:黑马vue视频,感谢llb老师。文章来源地址https://www.toymoban.com/news/detail-483174.html
到了这里,关于【Vue实战】使用vue实现购物车案例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!