Vue2中的购物车案例
1.项目准备,以及项目初始化
使用 vue create 项目名称 创建项目
执行 yarn add bootstrap 下载bootstrap库
执行 yarn add less less-loader@5.0.0 -D 下载less模块
清空components 文件夹中自动生成的组件
图示:
1.在components文件夹下创建项目组成的4个组件
2.在main.js入口文件中引入bootstrap库
import '../node_modules/bootstrap/dist/css/bootstrap.css'
- 搭建项目组件
// MyHeader组件
<template>
<div class="my-header">购物车案例</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.my-header {
height: 45px;
line-height: 45px;
text-align: center;
background-color: #1d7bff;
color: #fff;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 2;
}
</style>
// MyGoods组件
<template>
<div class="my-goods-item">
<div class="left">
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="input"
>
<label class="custom-control-label" for="input">
<img src="http://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" alt="">
</label>
</div>
</div>
<div class="right">
<div class="top">商品名字</div>
<div class="bottom">
<span class="price">¥ 100</span>
<span>
数量组件
</span>
</div>
</div>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.my-goods-item {
display: flex;
padding: 10px;
border-bottom: 1px solid #ccc;
.left {
img {
width: 120px;
height: 120px;
margin-right: 8px;
border-radius: 10px;
}
.custom-control-label::before,
.custom-control-label::after {
top: 50px;
}
}
.right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.top{
font-size: 14px;
font-weight: 700;
}
.bottom {
display: flex;
justify-content: space-between;
padding: 5px 0;
align-items: center;
.price {
color: red;
font-weight: bold;
}
}
}
}
</style>
//MyCount组件
<template>
<div class="my-counter">
<button type="button" class="btn btn-light" >-</button>
<input type="number" class="form-control inp" >
<button type="button" class="btn btn-light">+</button>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.my-counter {
display: flex;
.inp {
width: 45px;
text-align: center;
margin: 0 10px;
}
.btn, .inp{
transform: scale(0.9);
}
}
</style>
//MyFooter组件
<template>
<!-- 底部 -->
<div class="my-footer">
<!-- 全选 -->
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="footerCheck">
<label class="custom-control-label" for="footerCheck">全选</label>
</div>
<!-- 合计 -->
<div>
<span>合计:</span>
<span class="price">¥ 0</span>
</div>
<!-- 按钮 -->
<button type="button" class="footer-btn btn btn-primary">结算 ( 0 )</button>
</div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.my-footer {
position: fixed;
z-index: 2;
bottom: 0;
width: 100%;
height: 50px;
border-top: 1px solid #ccc;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 10px;
background: #fff;
.price {
color: red;
font-weight: bold;
font-size: 15px;
}
.footer-btn {
min-width: 80px;
height: 30px;
line-height: 30px;
border-radius: 25px;
padding: 0;
}
}
</style>
2.购物车案例--头部模块
思路
- 在MyHeader.vue中准备props里变量, 然后使用
- 在使用MyHeader.vue组件时, 传入相应的值 (color和backgroundColor)
// MyHeader.vue
<template>
<div class="my-header" :style="{backgroundColor: background, color}">{{ title }}</div>
</template>
<script>
// 目标: 让Header组件支持不同的项目 - 自定义
// 1. 分析哪些可以自定义 (背景色, 文字颜色, 文字内容)
// 2. (新) 可以对props的变量的值 进行校验
// 3. 内部使用props变量的值
// 4. 外部使用时, 遵守变量名作为属性名, 值的类型遵守
export default {
props: {
background: String, // 外部插入此变量的值, 必须是字符串类型, 否则报错
color: {
type: String, // 约束color值的类型
default: "#fff" // color变量默认值(外部不给color传值, 使用默认值)
},
title: {
type: String,
required: true // 必须传入此变量的值
}
}
}
</script>
<style lang="less" scoped>
.my-header {
height: 45px;
line-height: 45px;
text-align: center;
background-color: #1d7bff;
color: #fff;
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 2;
}
</style>
// 在App.vue组件中传入Props中title的值
<MyHeader title="购物车案例"></MyHeader>
3.购物车案例--使用Ajax请求数据
数据地址: www.escook.cn/api/cart (get方式)
3.1 使用yarn下载axios
yarn add axios
3.2 在main.js入口文件中 Vue原型上挂载axios
// 1. 下载axios库, main.js - 全局绑定属性 (确保任意.vue文件可以都访问到这个axios方法)
import axios from 'axios'
// 2. 基础地址
axios.defaults.baseURL = "https://www.escook.cn"
// 3. axios方法添加到Vue的原型上
Vue.prototype.$axios = axios
new Vue({
render: h => h(App),
}).$mount('#app')
3.3 在App中的created钩子函数中发起axios请求并请求数据
<script>
export default {
data(){
return {
// 商品所有数据
list: []
}
},
created(){
// 不必在自己引入axios变量, 而是直接使用全局属性$axios
this.$axios({
url: '/api/cart'
}).then(res => {
if (res.status === 200) {
console.log(res)
this.list = res.data.list
}
})
}
}
</script>
4.购物车案例--数据渲染
// 在App组件中
<MyGoods v-for="obj in list"
:key="obj.id"
:gObj="obj">
</MyGoods>
在MyGoods组件中
<template>
<div class="my-goods-item">
<div class="left">
<div class="custom-control custom-checkbox">
<!-- *重要:
每个对象和组件都是独立的
对象里的goods_state关联自己对应商品的复选框
-->
<!-- bug:
循环的所有label的for都是input, id也都是input - 默认只有第一个生效
解决: 每次对象里的id值(1, 2), 分别给id和for使用即可区分
-->
<input type="checkbox" class="custom-control-input" :id="gObj.id"
v-model="gObj.goods_state"
>
<label class="custom-control-label" :for="gObj.id">
<img :src="gObj.goods_img" alt="">
</label>
</div>
</div>
<div class="right">
<div class="top">{{ gObj.goods_name }}</div>
<div class="bottom">
<span class="price">¥ {{ gObj.goods_price }}</span>
<span>
<MyCount :obj="gObj"></MyCount>
</span>
</div>
</div>
</div>
</template>
<script>
import MyCount from './MyCount'
export default {
props: {
gObj: Object
},
components: {
MyCount
}
}
</script>
//在MyCount组件中
<template>
<div class="my-counter">
<button type="button" class="btn btn-light" >-</button>
<input type="number" class="form-control inp" v-model.number="obj.goods_count">
<button type="button" class="btn btn-light" >+</button>
</div>
</template>
<script>
export default {
props: {
obj: Object // 商品对象
}
}
</script>
5.购物车案例--解决商品选中不同步问题
<input type="checkbox" class="custom-control-input" :id="gObj.id"
v-model="gObj.goods_state"
>
<label class="custom-control-label" :for="gObj.id">
<img :src="gObj.goods_img" alt="">
</label>
6.购物车案例--实现数量操作
在MyCount组件中
<template>
<div class="my-counter">
<button type="button" class="btn btn-light" :disabled="obj.goods_count === 1" @click="obj.goods_count > 1 && obj.goods_count--">-</button>
<input type="number" class="form-control inp" v-model.number="obj.goods_count">
<button type="button" class="btn btn-light" @click="obj.goods_count++">+</button>
</div>
</template>
<script>
// 目标: 商品数量 - 控制
// 1. 外部传入数据对象
// 2. v-model关联对象的goods_count属性和输入框 (双向绑定)
// 3. 商品按钮 +和-, 商品数量最少1件
// 4. 侦听数量改变, 小于1, 直接强制覆盖1
export default {
props: {
obj: Object // 商品对象
},
// 因为数量控制要通过对象"互相引用的关系"来影响外面对象里的数量值, 所以最好传 对象进来
watch: {
obj: {
handler() {
console.log(this.obj.goods_count)
if (this.obj.goods_count < 1) {
this.obj.goods_count = 1
}
},
deep: true
}
}
</script>
7.购物车案例--实现全选功能
//在MyFooter组件中
<template>
<!-- 底部 -->
<div class="my-footer">
<!-- 全选 -->
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="footerCheck" v-model="isAll">
<label class="custom-control-label" for="footerCheck">全选</label>
</div>
<!-- 合计 -->
<div>
<span>合计:</span>
<span class="price">¥ {{ allPrice.toFixed(2) }}</span>
</div>
<!-- 按钮 -->
<button type="button" class="footer-btn btn btn-primary">结算 ( {{ allCount }} )</button>
</div>
</template>
<script>
// 目标: 全选
// 1. v-model关联全选-复选框(v-model后变量计算属性)
// 2. 页面(视频层)v(true) -> 数据层(变量-) 计算属性(完整写法)
// 3. 把全选 true/false同步给所有小选框选中状态上
// 小选 -> 全选
// App.vue里list数组 -> MyFooter.vue
// isAll的get方法里, 统计状态影响全选框
// 目标: 总数量统计
// 1. allCount计算属性用 数组reduce+判断统计数量并返回
// 目标: 总价
// allPrice计算属性, 数组reduce+单价*数量, 判断选中, 才累加后返回
export default {
props: {
arr: Array
},
computed: {
isAll: {
set(val){ // val就是关联表单的值(true/false)
this.$emit('changeAll', val)
},
get(){
// 查找小选框关联的属性有没有不符合勾选的条件
// 直接原地false
return this.arr.every(obj => obj.goods_state === true)
}
},
}
}
</script>
//在App组件中
<MyFooter @changeAll="allFn" :arr="list"></MyFooter>
<script>
methods: {
allFn(bool){
this.list.forEach(obj => obj.goods_state = bool)
// 把MyFooter内的全选状态true/false同步给所有小选框的关联属性上
}
}
</script>
8.购物车案例--计算选中商品总数量
// 在MyFooter组件中
allCount(){
return this.arr.reduce((sum, obj) => {
if (obj.goods_state === true) { // 选中商品才累加数量
sum += obj.goods_count;
}
return sum;
}, 0)
},
9.购物车案例--计算选中商品的总价格
// 在MyFooter组件中
allPrice(){
return this.arr.reduce((sum, obj) => {
if (obj.goods_state){
sum += obj.goods_count * obj.goods_price
}
return sum;
}, 0)
}
10.总结
1️⃣父传子(通过props属性)
props: [] - 只能声明变量和接收, 不能类型校验
props: {} - 声明变量和校验类型规则 - 外部传入值不对则报错
2️⃣created生命周期是发起axios请求的最好时机
3️⃣ lable的for值对应input的id, 点击label就能让对应input处于激活
4️⃣ 全选的v-model的值, 使用计算属性完整写法
5️⃣ 对象之间是引用关系, 对象值改变, 所有用到的地方都跟着改变
6️⃣计算商品总价或总数量使用数组的reduce方法更适合