11-购物车案例笔记

325 阅读3分钟

购物车案例

Cart.vue

<template>
  <div>
    <h2>{{title}}</h2>
    <table border="1">
      <tr>
        <th>#</th>
        <th>课程</th>
        <th>单价</th>
        <th>数量</th>
        <th>总价</th>
      </tr>
      <tr v-for="(item,index) in cart" :key="item.id">
        <td>
          <!-- 通过v-model绑定active,判断是否选中 -->
          <input type="checkbox" v-model="item.active" />
        </td>
        <td>{{item.title}}</td>
        <td>{{item.price}}</td>
        <td>
          <!-- 给button绑定的函数传入index,用于区分是对哪一行数据进行修改 -->
          <button @click="handleSub(index)">-</button>
          {{item.count}}
          <button @click="handleAdd(index)">+</button>
        </td>
        <td>¥{{item.count*item.price}}</td>
      </tr>
      <tr>
        <td></td>
        <!-- activeCount是选中的项,count是购物车中的项,通过computed计算属性能够产生缓存 -->
        <td colspan="2">{{activeCount}}/{{count}}</td>
        <!-- 总价 -->
        <td colspan="2">{{total}}</td>
      </tr>
    </table>
  </div>
</template>

<script>
export default {
  name: "cart",
  //通过props接收父组件中传入的数据
  props: ["title", "cart"],
  methods: {
    remove(i) {
      if (window.confirm("是否确定删除")) {
        this.cart.splice(i, 1);
      }
    },
    handleSub(i) {
      let count = this.cart[i].count;
      count > 1 ? this.cart[i].count-- : this.remove(i);
    },
    handleAdd(i) {
      this.cart[i].count++;
    },
  },
  computed: {
    count() {
      //返回购物车中的项
      return this.cart.length;
    },
    activeCount() {
      //通过过滤器,将购物车中选中的项筛选出来
      return this.cart.filter((v) => v.active).length;
    },
    total() {
      //通过forEach遍历
      /* let sum = 0;
      this.cart.forEach((c) => {
        if (c.active) {
          sum += c.count * c.price;
        }
      });
      return sum; */
  
      //通过reduce遍历,传入的初始值0会直接给sum,后面的c就是当前调用reduce的数组中正在处理的值
      return this.cart.reduce((sum, c) => {
        if (c.active) {
          sum += c.count * c.price;
        }
        return sum;
      }, 0);//传入一个初始值
    },
  },
};
</script>

<style scoped>
</style>

App.vue

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png" />
    <h1>{{title}}</h1>
    <ul>
      <li v-for="item in cartList" :key="item.id">
        <h2>{{item.title}}</h2>
        <p>¥{{item.price}}</p>
      </li>
    </ul>
    <!-- 绑定传入给子组件的数据 -->
    <my-cart :title="title" :cart="cartList"></my-cart>
  </div>
</template>

<script>
  //导入Cart组件,一定要注意,这边导入之后还要在components进行挂载
import MyCart from "./components/Cart";
export default {
  name: "App",
  data() {
    return {
      cartList: [
        { id: 1, title: "Vue实战开发", price: 188, active: true, count: 1 },
        { id: 2, title: "React实战开发", price: 288, active: true, count: 1 },
      ],
      title: "购物车",
    };
  },
  //挂载
  components: {
    MyCart,
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

用到的一些方法

filter、forEach和reduce

1.filter

filter() 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。

语法

arr.filter(callback(element,[index],[array]))

callback 被调用时传入三个参数:

  1. 数组中当前正在处理的元素。
  2. 正在处理的元素在数组中的索引。(可选)
  3. 调用了 filter 的数组本身。(可选)

返回值

一个新的、由通过测试的元素组成的数组,如果没有任何数组元素通过测试,则返回空数组。

2.forEach

forEach() 方法对数组的每个元素执行一次给定的函数。

语法

arr.forEach(callback(currentValue,[index],[array]))

可依次向 callback 函数传入三个参数:

  1. 数组中正在处理的当前元素。
  2. 数组中正在处理的当前元素的索引。(可选)
  3. forEach() 方法正在操作的数组。(可选)

返回值

undefined。

3.reduce

reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。

语法

arr.reduce(callback(acc,cur,[idx],[src]),[initialValue])

callback 函数接收4个参数:

  1. 累计器累计回调的返回值; 它是上一次调用回调时返回的累积值,或initialValue
  2. 数组中正在处理的元素。
  3. 数组中正在处理的当前元素的索引。 如果提供了initialValue,则起始索引号为0,否则从索引1起始。(可选)
  4. 调用reduce()的数组(可选)

initialValue 作为第一次调用 callback函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 在没有初始值的空数组上调用 reduce 将报错。(可选)

返回值

函数累计处理的结果

使用MockServe保存数据

上面的示列中我们将数据直接保存在data()中,这是很不科学的,下面将通过MockServe模拟从后台拿数据。

  1. 在当前项目目录下创建vue.config.js文件

**注意:**每次对这个文件进行了修改之后,一定要重启一下项目才能生效。

可通过在终端输入

ctrl+c : 终止批处理操作吗(Y/N)

退出,然后再重新输入npm run serve,重新运行项目

//commonjs规范
module.exports = {
    devServer: {
        //每次修改完这个文件,一定要重新启动才能生效
        //mock模拟数据
        //app相当于后端提供的一个变量对象,也可以是其他命名,
        before(app, server) {
            //第一个参数 填写自己想设计的接口
            //补全地址 http://localhost:8080/api/cartList
            //第二个参数 是一个回调函数
            app.get('/api/cartList', (req, res) => {
                //返回一个json数据格式
                res.json({
                    result: [{
                            id: 1,
                            title: "Vue实战开发",
                            price: 188,
                            active: true,
                            count: 1
                        },
                        {
                            id: 2,
                            title: "React实战开发",
                            price: 288,
                            active: true,
                            count: 1
                        }
                    ]
                })
            })
        }
    }
}
  1. 在终端当前项目目录下下载axios

在终端输入,下面这行指令即可

npm i axios -s

在main.js中导入axios,并将axios挂载到Vue的原型上

import Vue from 'vue'
import App from './App.vue'
import axios from 'axios'//导入axios

Vue.config.productionTip = false

Vue.prototype.$http = axios;//将axios挂载到vue原型上,以后在vue实例中可以直接通过this.$http调用
new Vue({
  render: h => h(App),
}).$mount('#app')

此时可以在App.vue的create()中请求数据

  async created() {
    /* this.$http
      .get("/api/cartList")
      .then((res) => {
        this.cartList = res.data.result;
      })
      .catch((err) => {
        console.log(err);
      }); */
    try {
      //发起请求获取数据
      const res = await this.$http.get("/api/cartList");
      //将获取到的数据赋值
      this.cartList = res.data.result;
    } catch (error) {
      console.log(error);
    }
  },

数据持久化

首先在main.js中创建一个中央事件总线$bus,并绑定到Vue的原型上

Vue.prototype.$bus = new Vue();

然后我们模拟将商品添加到购物车,给每一个商品li中添加一个按钮<button @click="addCart(index)">添加购物车</button>并给它绑定点击事件。在addCart()方法中通过this.$bus.$emit("addCart", good);触发在Cart.vue中中央事件总线绑定的事件。

App.vue

<template>
  <div id="app">
    <img alt="Vue logo" src="./assets/logo.png" />
    <h1>{{title}}</h1>
    <ul>
      <li v-for="(item,index) in cartList" :key="item.id">
        <h2>{{item.title}}</h2>
        <p>¥{{item.price}}</p>
        <button @click="addCart(index)">添加购物车</button>
      </li>
    </ul>
    <!--  -->
    <my-cart :title="title"></my-cart>
  </div>
</template>

<script>
//
import MyCart from "./components/Cart";
export default {
  name: "App",
  data() {
    return {
      cartList: [],
      title: "购物车",
    };
  },
  methods: {
    addCart(i) {
      const good = this.cartList[i];
      this.$bus.$emit("addCart", good);//good将作为addCard函数的参数传入
    },
  },
  async created() {
    try {
      const res = await this.$http.get("/api/cartList");
      this.cartList = res.data.result;
    } catch (error) {
      console.log(error);
    }
  },
  //
  components: {
    MyCart,
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

在Cart.vue中的created方法通过中央事件总线this.$bus绑定被触发的函数,首先通过find()查找是否该商品已经添加到购物车,如果已经添加,则count+1,如果没有添加,则添加到购物车。

模拟添加商品到购物车已经完成,下面进行数据持久化,保存到本地localStorage

通过watch对cart进行深度监听,一旦cart中的值发生改变则通过localStorage将cart保存到本地。同时cart: JSON.parse(localStorage.getItem("cart")) || [],保证每次刷新后都能立马得到值。

**注意:**localStorage只能存储字符串,首先通过JSON.stringify()将对象转成字符串后再存储,取的时候通过JSON.parse()将字符串转成对象。

Cart.vue

<template>
  <div>
    <h2>{{title}}</h2>
    <table border="1">
      <tr>
        <th>#</th>
        <th>课程</th>
        <th>单价</th>
        <th>数量</th>
        <th>总价</th>
      </tr>
      <tr v-for="(item,index) in cart" :key="item.id">
        <td>
          <!-- 通过v-model绑定active,判断是否选中 -->
          <input type="checkbox" v-model="item.active" />
        </td>
        <td>{{item.title}}</td>
        <td>{{item.price}}</td>
        <td>
          <!-- 给button绑定的函数传入index,用于区分是对哪一行数据进行修改 -->
          <button @click="handleSub(index)">-</button>
          {{item.count}}
          <button @click="handleAdd(index)">+</button>
        </td>
        <td>¥{{item.count*item.price}}</td>
      </tr>
      <tr>
        <td></td>
        <!-- activeCount是选中的项,count是购物车中的项,通过computed计算属性能够产生缓存 -->
        <td colspan="2">{{activeCount}}/{{count}}</td>
        <!-- 总价 -->
        <td colspan="2">{{total}}</td>
      </tr>
    </table>
  </div>
</template>

<script>
export default {
  name: "cart",
  //通过props接收父组件中传入的数据
  props: ["title"],
  data() {
    return {
      cart: JSON.parse(localStorage.getItem("cart")) || [],
    };
  },
  watch: {
    //对cart进行深度监听
    cart: {
      deep: true,
      handler(newV, oldV) {
        this.setLocalData(newV);
      },
    },
  },
  created() {
    this.$bus.$on("addCart", (good) => {
  	  //查找购物车内是否有当前添加的商品,如果有则当前商品数量+1,如果没有则添加到购物车
      const ret = this.cart.find((v) => v.id === good.id);
      if (!ret) {
        //购物车没有数据
        this.cart.push(good);
      } else {
        //ret返回的是当前的对象
        ret.count++;
      }
    });
  },
  methods: {
    setLocalData(newV) {
      //计算总课数量

      //将数组转换成json字符串存储到本地
      localStorage.setItem("cart", JSON.stringify(newV));
    },
    remove(i) {
      if (window.confirm("是否确定删除")) {
        this.cart.splice(i, 1);
      }
    },
    handleSub(i) {
      let count = this.cart[i].count;
      count > 1 ? this.cart[i].count-- : this.remove(i);
    },
    handleAdd(i) {
      this.cart[i].count++;
    },
  },
  computed: {
    count() {
      //返回购物车中的项
      return this.cart.length;
    },
    activeCount() {
      //通过过滤器,将购物车中选中的项筛选出来
      return this.cart.filter((v) => v.active).length;
    },
    total() {
      //通过reduce遍历,
      return this.cart.reduce((sum, c) => {
        if (c.active) {
          sum += c.count * c.price;
        }
        return sum;
      }, 0);
    },
  },
};
</script>

<style scoped>
</style>

对中央事件总线$emit()$on()有了新的认识,它们并不局限于平行组件之间的通信,感觉只要是组件之间的通信都能用上,学东西还是不能学的太死啊!!!

find

find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。

语法

arr.find(callback(element,[index],[array]))

callback在数组每一项上执行的函数,接收 3 个参数:

  1. 当前遍历到的元素。
  2. 当前遍历到的索引。(可选)
  3. 数组本身。(可选)

返回值

数组中第一个满足所提供测试函数的元素的值,否则返回 undefined。