1.项目初始化
效果图:
①: vue命令创建项目shopcar-demo
②: 下载bootstrap, less, less-loader@5.0.0③: main.js – 引入bootstrap样式import "bootstrap/dist/css/bootstrap.css" //引入第三方包里的某个css文件
④: 创建4个组件文件, 标签和样式(略)⑤: 把组件相应的引入到对应位置使用
2.头部自定义
需求:组件支持自定义
①: 背景色定义props里变量接收使用(利用了vue组件通信/父传子)
②: 文字颜色定义props里变量接收-设默认值③: 文字内容定义props里变量接收-设必传值可以对props的变量的值 进行校验!!!<div class="my-header" :style="{backgroundColor: background, color}">{{ title }}</div>
export default { props: { background: String, // 外部插入此变量的值, 必须是字符串类型, 否则报错 color: { type: String, // 约束color值的类型 default: "#fff" // color变量默认值(外部不给 我color传值, 使用默认值) }, title: { type: String, required: true // 必须传入此变量的值 } } }
④: 使用组件传入具体值
在app.vue中:
<MyHeader background="orange" title="购物车案例"></MyHeader>
3. 数据获取
需求:axios调用接口——获取购物车案例的数据
①: 下载axios, 在main.js引入 import axios from 'axios'
②: 配置基础地址, https://www.escook.cnaxios.defaults.baseURL = "http://www.escook.cn"③: axios挂载到Vue原型上, 作为全局属性 !!Vue.prototype.$axios = axios④: App.vue的created中使用全局属性axioscreated() { // 不必在自己引入axios变量, 而是直接使用全局属性$axios this.$axios({ url: "/api/cart", }).then((res) => { console.log(res); this.list = res.data.list; }); },
⑤: 接口地址为 /api/cart
4.数据渲染
需求:把数据使用MyGoods组件展示
①: App.vue把数据保存到data定义的变量上
data() { return { list: [], // 商品所有数据 }; },
②: 使用v-for循环数组使用组件③: 分别给商品组件传入数据对象
④: 商品组件接收后, 使用对象里字段数据展示( 其中涉及2次父向子传值 )MyGoods.vue(子):export default { props: { gObj: Object }, }
App.vue(父):
<MyGoods v-for="obj in list" :key="obj.id" :gObj="obj"></MyGoods>
MyGoods.vue(子):每个对象和组件都是独立的,因此用对象里的goods_state关联自己对应商品的复选框<div class="left"> <div class="custom-control custom-checkbox"> <!-- *重要: 每个对象和组件都是独立的 对象里的goods_state关联自己对应商品的复选框 --> <input type="checkbox" class="custom-control-input" id="input" v-model="gObj.goods_state" > <label class="custom-control-label" for="input"> <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>
MyCount.vue(Mygoods.vue的子):
<input type="number" class="form-control inp" v-model ="obj.goods_count">
export default { props: { obj: Object // 商品对象 }, }
5.商品选择
需求:点击复选框完成选中效果;点击商品图片完成复选框选中效果
①: 给v-model关联到小选框上, 数据关联对象的goods_state
②: 注意点图片用label关联的小选框③: label的for值要和表单标签id一致, 点label才触发小选框④: id属性分别使用商品的id即可<!-- 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>
此处bug原因:对象传值赋值传的是引用类型的堆内存地址,多处共同使用这个对象/数组!
6.数量控制
需求:点击或输入改变商品数量
①: 给MyCount组件传入数据对象关联②: 输入框v-model关联对象里数量属性③: 增加或减少修改对象数量属性④: 控制商品最少1件<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>
// 目标: 商品数量 - 控制 // 1. 外部传入数据对象 // 2. v-model关联对象的goods_count属性和输入框 (双向绑定) // 3. 商品按钮 +和-, 商品数量最少1件 // 4. 侦听数量改变, 小于1, 直接强制覆盖1 watch: { obj: { deep: true, handler(){ // 拿到商品数量, 判断小于1, 强制修改成1 if (this.obj.goods_count < 1) { this.obj.goods_count = 1 } } } }
7.全选
需求:点击全选影响所有小选(计算属性的set方法),点击小选影响全选(计算属性的get方法)
①: v-model用计算属性, 关联全选框
<input type="checkbox" class="custom-control-input" id="footerCheck" v-model="isAll" />
②: 在set方法里, 获取全选状态③: 回传到App.vue再同步给所有小选框页面(视频层)v(true) -> 数据层(变量-) 计算属性(完整写法)
把全选 true/false同步给所有小选框选中状态上(子传父)
MyFooter.vue(子):
computed: { isAll: { set(val) { // val就是关联表单的值(true/false) console.log(val); this.$emit("changeAll", val); }, }
App.vue(父):
<MyFooter @changeAll="allFn"></MyFooter>
methods: { allFn(bool) { this.list.forEach((obj) => (obj.goods_state = bool)); // 把MyFooter内的全选状态true/false同步给所有小选框的关联属性上 }, },
④: 在get方法里, 统计小选框给全选复制(父传子)
App.vue(父):
<MyFooter @changeAll="allFn" :arr="list"></MyFooter>
MyFooter.vue(子):
props: { arr: Array, }, computed: { isAll: { set(val) { // val就是关联表单的值(true/false) console.log(val); this.$emit("changeAll", val); }, get() { // 查找小选框关联的属性有没有不符合勾选的条件 // 直接原地false return this.arr.every((obj) => obj.goods_state === true); }, },
8.总数量
需求:统计已选中商品的总数量在右下角按钮显示
reduce累加数组对象数量!!
arr.reduce((sum, obj) => { }, 0); sum:总数 0:sum从0开始
①: 计算属性allCount变量
<button type="button" class="footer-btn btn btn-primary"> 结算 ( {{ allCount }} ) </button>
②: 在统计数组里数据时, 要判断勾选状态才累加数量③: 把累加好数量返回给allCount变量使用显示computed: { allCount() { return this.arr.reduce((sum, obj) => { if (obj.goods_state === true) { // 选中商品才累加数量 sum += obj.goods_count; } return sum; }, 0); },
④: 输入框的值为空字符串, 需要转换数值类型
9.总价
需求:统计已选中商品的总价格
①: 计算属性allPrice变量
<span class="price">¥ {{ allPrice }}</span>
②: 在统计数组里数据时, 要判断勾选状态才累加③: 把价格和数量先想乘, 再累加computed: { allPrice() { return this.arr.reduce((sum, obj) => { if (obj.goods_state) { sum += obj.goods_count * obj.goods_price; } return sum; }, 0); }, },
④: 累加好数据返回给allPrice变量使用显示