拿到的框架没有包node-modules,先装包执行命令--->npm install,实时监听终端的变化 ---> npm run serve,打开浏览器窗口。
这个案例主要注重功能的实现,样式什么的没详细的写 ^_^
组件的导入注册和使用,页面的头部header区域vue模块。
导入Header组件
App 根组件
( GET请求,地址为商品列表数据)
缺少axios包,需要另外打开一个终端执行命令 -->npm install axios -- save,执行完毕关闭这个终端。就可以在script标签下导入axios请求库,然后请求数据。
由于axios.get返回的是promise,这时候需要await,并且给函数名加上asynic,返回值是一个大对象,包含六个属性,但是我们只需要里面的data,用到解构赋值。
methods: {
//封装请求数据的方法
async initCarList() {
//调用axios的get方法,请求列表数据
const { data: res } = await axios.get("https://www.escook.cn/api/cart");
console.log(res);
},
},
created() {
//调用请求数据的方法
this.initCarList();
},
在data函数的return中定义一个list空数组,用来存放请求回来的数据。
data() {
return {
//用来存储购物车的列表数据,默认是一个空数组
list: [],
};
},
methods: {
//封装请求数据的方法
async initCarList() {
//调用axios的get方法,请求列表数据
const { data: res } = await axios.get("https://www.escook.cn/api/cart");
// console.log(res);
//只要请求回来的数据,在页面渲染期间,转存到data中
if (res.status === 200) {
this.list = res.list;
}
},
},
每循环一次,对应的生成一个购物车页面的item项目
方法与Header相同。尽量首字母大写,为了视觉区分小写的内置标签。
这个用到组件之间的关系,子组件通过props接收父组件传递过来的数据,也就是在Goods组件中写一个自定义属性。在父组件App的循环中动态绑定title的goods_name和goods_pic属性。
方法同上。商品的单价(默认值为0)和商品当前的选中状态(默认选中)
父组件App中
以上动态绑定的属性用的v-bind而不是双向数据绑定v-model,是因为子组件props自定义属性接收过来的数据是只读的。
需要在子组件中封装一个id属性, 是因为子组件中的商品勾选状态发生改变需要通过子-->父的形式,使父组件根据id修改对应的商品的勾选状态,id中包含require,是一个必填项。
1.在子组件中,要监听复选框状态变化的事件。拿到最新的勾选状态。只要复选框的勾选状态发生改变,就会自动触发input表单的change事件。
2.当监听到勾选状态变化之后,应该立即把最新的状态,通过自定义事件的形式,发送给父组件。
其中,id表示当前这件商品的id, value的值是最新的勾选状
methods: {
//商品的选中状态发生改变就会触发这个函数
stateChange(e) {
// console.log("ok");
const newState = e.target.checked;
// 触发自定义事件
this.$emit("state-change", { id: this.id, value: newState });
},
},
3.父组件改变元素的选中状态
methods: {
//接收子组件传递过来的数据
getNewState(e) {
this.list.some((item) => {
if (item.id === e.id) {
item.goods_state = e.value;
//终止循环
return true;
}
},
4.防止点击其他的复选框只有第一个做出变化,使用动态拼接字符串的方式。
这个组件包括页面底部的全选按钮,合计表单,结算按钮。首先同样的导入注册使用这个Footer组件。
在App父组件中定义一个计算属性动态计算商品是否全部选中拿到一个布尔值,给Footer组件中的全选复选框使用。可以使用数组的every方法判断商品是否全部选中。
//计算属性
computed: {
//动态计算出全选状态是true还是false
fullstate() {
return this.list.every((item) => item.goods_state);
},
},
计算属性定义的时候用作方法,使用的时候当作普通的属性。
这里又用到了组件之间的关系,父-->子传值,首先在子组件中定义自定义属性。
同时Footer组件中的input标签的样式也要随之改变
在父组件App中的使用
子-->父,用到自定义事件,拿到底部全选框的状态
methods: {
//监听全选的状态变化
fullChange(e) {
this.$emit("full-change", e.target.checked);
},
},
父组件利用foreach遍历每一个项,给其加上对应的布尔值,改变当前的复选框状态。
//接收子组件传递过来的全选按钮的状态
getFullState(val) {
this.list.forEach((item) => (item.goods_state = val));
},
在App父组件中通过计算属性首先算出来总价,
amount() {
//1.先filter过滤
//2.再reduce累加
return this.list
.filter((item) => item.goods_state)
.reduce(
(total, item) => (total += item.goods_price * item.goods_count),
0
);
},
然后通过组件父向子传值传递给子组件Footer
Counter组件在Goods组件中,首先导入注册使用。数据存在App中,需要两次子向父传值,Goods-->App Counter-->Goods
点击数量加减按钮,只是在Counter子组件中产生变化,所以通过子传父的方式,只是传递给了Goods组件,最终要实现在App中的体现。这就比较麻烦,可以使用eventBus方案,深层嵌套也可以利用这种组件之间的兄弟关系。
接收商品的id 值,使用 EventBus方案,把数量传递到App. vue的时候,需要通知App 组件,更新哪个商品的数量。
创建eventBus.js,创建vue实例对象并且共享出去。数据的发送方和接收方都需要导入bus,数据的接收方需要在created生命周期函数中声明事件。
created() {
//调用请求数据的方法
this.initCarList();
bus.$on("share", (val) => {
this.list.some((item) => {
if (item.id === val.id) {
item.goods_count = val.value;
return true;
}
});
});
},
//已勾选的商品数量
totalNum() {
return this.list
.filter((item) => item.goods_state)
.reduce((t, item) => (t += item.goods_count), 0);
},
methods: {
add() {
// this.num++,这是错误的写法 num在自定义属性里面是只读属性
//要发送的App数据的格式为{ id, value},id是商品的id,value是最新的购买数量
const obj = { id: this.id, value: this.num + 1 };
bus.$emit("share", obj);
},
sub() {
if (this.num - 1 === 0) return;
const obj = { id: this.id, value: this.num - 1 };
bus.$emit("share", obj);
},
},