一、前言

环境:VScode+Chrome
技术栈:HTML+CSS+Vue(组件之前的知识运用—Vue基础语法)
将购物车实现分成三部分分别实现:
第一部分:简单购物车功能实现;
第二部分:将商品序号改为勾选框;
第三部分:添加商店对商品进行分类。

二、第一部分

页面效果图:在这里插入图片描述
思路:
分为两部分,上半部分用于添加商品到购物车;下半部分用于展示购物车详情:
1、先把界面的框架(ui效果),大致画出来—这里使用table标签
2、先完成下半部分的逻辑
①循环数组:书籍名单是从服务器传来的,所以用for循环遍历获得;
②格式化价格:实现价格¥xx.00的表示方法(有两种);
③完成 + 和 – 按钮:购买数量的±按钮功能通过点击事件写js实现(注意传入index值知道是哪一行书籍)并且使用按钮的disabled功能使<=1时不能再点(解决会为负数的小bug);
④加移除:移除功能使用splice函数,且用ifelse判断当购物车为空时显示购物车为空;
⑤算总价:书籍总价格通过计算属性实时计算获得,最后返回一个值,通过第二步的过滤器实现最后的样子;

2、完成上半部分逻辑
①v-model绑定每个表单:在点击添加按钮时,要判断每个表单是否为空,有一个表单为空则提示请输入xx内容,然后return false;
②添加至购物车的逻辑:点击添加按钮,商品添加至购物车,并清空此时上半部分的input内容

三、第二部分

页面效果图:
在这里插入图片描述
1、把购物车的序列号,改为勾选框,当勾选起来的时候,才会计算总价;默认不勾选;列头有全选勾选框,可以实现全选功能。
2、添加商品的时候,增加判断,如果新增商品ID已经在购物车中,那么这个时候应该是把对应商品的数量增加;否则在购物车中新增商品行。

四、第三部分

页面效果图:
在这里插入图片描述

1、每个商品都有对应的商店:在data中用二维数组存放商品数据
(1)“添加要购买商品” 新增2个输入框,商店后台编号和商店名称。
(2)“购物车”按商店先划分,再按商品划分。

(3) 同样需要勾选框。这里的勾选框有3种:
a.全选勾选框,b.某个商店全选勾选框,c.某个商品的勾选框。勾选上的才算总价。默认不勾选。
大概属性定义思路:checkbox的属性是一个布尔属性,用v-model双向绑定一个布尔属性,即可在方法中用来实现各种功能;
这里分成三种复选框,一是isAllChecked(代表全选复选框,决定商店商品所有复选框的状态);二是isStChecked(代表商店复选框,决定某商店下所有商品的复选框状态);三是isChecked(代表商品复选框,决定该商品复选框的状态)

大概功能实现思路:
①先实现全选复选框(商店复选框)让下面所有商品复选框选中和取消选中
思路:让下面所有复选框的checked属性(选中状态) 跟随 全选复选框(商店复选框)即可

②下面商品复选框需要全部选中,全选复选框(商店复选框)才会选中做法:每次点击,都要循环查看下面所有的复选框是否有没选中的,如果有一个没选中的, 上面就不选中。

(4) 同样添加商品的时候要加上判断。分3种情况:
a.新增商品已经在购物车中有了,则该商品数量增加;
b.新增商品不在购物车中,但是其对应的商店在购物车中,则在商店后面新增该商品;
c.新增商品不在购物车中,且对应商店也不在购物车中,新增商店和商品。
(可看问题总结5)

2、页面结构:
页面结构实现使用二维数组来实现商店商品,因为这里使用的是table来做,我的思路是之前的thead不变,对tbody进行更改:
在tbody中再嵌入一个table-外层for循环商店,在table中再分为thead和tbody,tbody的tr再内层for循环商店下的商品即可实现上面效果图。
注意:在table标签中进行v-if判断该商店是否有商品,没有则不显示该商店,另外,这里不显示商品并没有删除存进data中的商店的数据,要在每次对商品进行移除时判断该商店商品是否为空,为空则用splice对该商店数据进行删除。

五、问题总结

在这里插入图片描述
这里报错的原因是:
在这里插入图片描述

细节问题,Price的input框为text,导致数据类型是字符串,所以报错;将数据类型转换成数字类型的就不会报这个错了;
解决方法:
①在JS中将return ‘¥’ + price.toFixed(2); 改成 return '¥ ’ + parseFloat(val).toFixed(2);
②在html中利用v-model的number修饰符,即v-model.number = “good.price”
2.注意不能直接进行this.goods.push(this.good);.这个方法会把good的地址也push进去,而不是拷贝;要构建一个新对象,再push进去
// (parse必须是json格式,所以先用stringify转换成json字符串类型再用parse转换成对象)
let goodJson = JSON.stringify(this.good);
let addGood = JSON.parse(goodJson);
this.goods.push(addGood);

3.按钮的全选和取消全选
大概属性定义思路:checkbox的属性是一个布尔属性,用v-model双向绑定一个布尔属性,即可在方法中用来实现各种功能;
这里分成三种复选框,一是isAllChecked(代表全选复选框,决定商店商品所有复选框的状态);二是isStChecked(代表商店复选框,决定某商店下所有商品的复选框状态);三是isChecked(代表商品复选框,决定该商品复选框的状态)

大概功能实现思路:
①先实现全选复选框(商店复选框)让下面所有商品复选框选中和取消选中
思路:让下面所有复选框的checked属性(选中状态) 跟随 全选复选框(商店复选框)即可

②下面商品复选框需要全部选中,全选复选框(商店复选框)才会选中做法:每次点击,都要循环查看下面所有的复选框是否有没选中的,如果有一个没选中的, 上面就不选中。

4.在实现当添加的商品编号已经在购物车存在,进行增加购物车商品数量的功能时:
1)我在对购物车商品编号遍历对比的时候用‘ ===’ 等同符时,两边值类型不相等,但是我分别打印后得到的都是number类型,不知道什么原因;所以改为使用等值符,只对值进行比较。
在这里插入图片描述
在这里插入图片描述

2)/对购物车的商品进行遍历,如果新加入的商品编号已存在就直接进行商品数量增加,否则直接进行push ,有两种实现方法:
①使用flag判断是否进行了数量增加的操作,当进行了购物车商品数量增加时,flag=false,就不会执行push操作;(不推荐此方法,在加入商店对商品进行分类后,此方法不好写)
在这里插入图片描述

②直接return,当进行了购物车商品数量增加时,return;此操作return接下来的所有代码都不会执行(推荐此方法,方便又快捷)
在这里插入图片描述

1)当商店存在,但是商品不存在,在该商店下新增商品时,新增商品addGood的类型与goods类型不同,所以需要再次构造新增对象addG,使addG的类型与goods相同,然后再在该商店下进行push
在这里插入图片描述

2)当商店商品不存在购物车时,要先创建商店,然后再在商店下创建商品,一样类型要一致
在这里插入图片描述

六、核心代码

html:

 <div id="app">
    <div class="add">
      <h3>添加要购买的商品:</h3>
      <table>
        <tr>
          <td>商店后台编号:</td>
          <td><input type="text" placeholder="请填写商店编号" v-model="good.storeId"></td>
        </tr>
        <tr>
          <td>商店名称:</td>
          <td><input type="text" placeholder="请填写商店名称" v-model="good.storeName"></td>
        </tr>
        <tr>
          <td>商品后台编号:</td>
          <td><input type="text" placeholder="请填写商品编号" v-model="good.id"></td>
        </tr>
        <tr>
          <td>商品名称:</td>
          <td><input type="text" placeholder="请填写商品名称" v-model="good.name"></td>
        </tr>
        <tr>
          <td>商品价格:</td>
          <td><input type="text" placeholder="请填写商品价格" v-model="good.price"></td>
        </tr>
        <tr>
          <td>商品数量:</td>
          <td><input type="text" placeholder="请填写商品数量" v-model.number="good.count"></td>
        </tr>
        <tr>
          <td colspan="2"><button @click="addToCar()">添加</button></td>
        </tr>
      </table>
    </div>
    <!-- 当购物车没有商店时,意味着没有商品,那么就显示购物车为空 -->
    <div v-if="infos.length">
      <h3 >购物车:</h3>
      <table>
        <thead>
          <tr>
            <th class="weightSmall"><input type="checkbox" v-model="isAllChecked" @click="clickAllChecked"></th>
            <th class="weightBig">书籍名称</th>
            <th class="weightMid">价格</th>
            <th class="weightMid">购买数量</th>
            <th class="weightMid">操作</th>
          </tr>
        </thead>
        <tbody>
          <!-- 外层for循环商店,如果该商店无商品,则不显示 -->
          <table v-for="(items, indexs) in infos" v-if="infos[indexs].goods.length">
            <thead>
              <th colspan="5"><input type="checkbox" v-model="items.isStChecked" @click="clickStChecked(indexs)">{{items.storeName}}</th>
            </thead>
            <tbody>
              <!-- 内层for循环商店下的商品 -->
              <tr v-for="(item, index) in items.goods">
                <td class="weightSmall"><input type="checkbox" v-model="item.isChecked" @click="clickChecked(indexs,index)"></td>
                <td class="weightBig">{{item.name}}</td>
                <td class="weightMid">{{item.price | getFinalPrice}}</td>
                <td class="weightMid">
                  <button @click="decrement(indexs,index)" :disabled="item.count <= 1">-</button>
                  {{item.count}}
                  <button @click="increment(indexs,index)">+</button>
                </td>
                <td class="weightMid">
                  <!-- 每次移除商品时判断该商店商品是否为空,为空就删除该商店 -->
                  <button @click="removeHandle(indexs,index)" >移除</button>
                </td>
              </tr>
            </tbody>
          </table>
          <table>
            <tr>
            <td class="final">总价格:{{totalPrice | getFinalPrice}}</td>
            </tr>
          </table>
        </tbody>
      </table>
      
    </div>
    <h2 v-else>购物车为空</h2>
  </div>

JS:

const app = new Vue({
  el: '#app',
  data: {
    // 创建一个对象用于存放要添加的商品信息
    good: {
      storeId: '',
      storeName: '',
      id: '',
      name: '',
      price: '',
      count: '',
    },
    //checkbox用布尔值表示是否选中,在data里绑定该属性,然后每次点击对该属性进行操作
    isAllChecked: false,
    infos: [
      {
        storeId: 1,
        storeName: '华为手机店',
        isStChecked: false,
        goods: [
          {
            id: 1,
            name: '华为手机',
            price: 2000,
            count: 1,
            isChecked: false,
          },
          {
            id: 2,
            name: '华为Mate30手机',
            price: 5000,
            count: 1,
            isChecked: false,
          },
        ]
      },
      {
        storeId: 2,
        storeName: '体育用品店',
        isStChecked: false,
        goods: [
          {
            id: 3,
            name: '篮球',
            price: 200,
            count: 1,
            isChecked: false,
          },
        ]
      },
      {
        storeId: 3,
        storeName: '手机用品店',
        isStChecked: false,
        goods: [
          {
            id: 4,
            name: '手机膜',
            price: 30,
            count: 1,
            isChecked: false,
          },
        ]
      }
    ],
  },
  methods: {
    // 方法①调用时用getFinalPrice(item.price)
    // getFinalPrice(price) {
    //   return '¥' + price.toFixed(2);
    // },
    decrement(indexs, index) {
      this.infos[indexs].goods[index].count--;
    },
    increment(indexs, index) {
      this.infos[indexs].goods[index].count++;
    },
    removeHandle(indexs, index) {
      this.infos[indexs].goods.splice(index, 1);
      if (this.infos[indexs].goods.length === 0) {
        this.infos.splice(indexs, 1);
      }
    },
    // 二、添加到购物车的功能
    addToCar() {
      //如果有一个表单元素输入空,就返回空值,不会在进行在购物车添加的操作
      if (!this.judgeGood()) {
        return;
      }
      else {
        //对购物车的商品进行遍历,有三种情况:
        for (let j = 0; j < this.infos.length; j++) {
          if (this.good.storeId == this.infos[j].storeId) {
            for (let i = 0; i < this.infos[j].goods.length; i++) {
              //a  商店存在且新增商品在购物车中存在,商品数量增加;
              if (this.good.id == this.infos[j].goods[i].id) {
                this.infos[j].goods[i].count += this.good.count;
                this.clearGood();
                return;
              }
            }
            //b  商店存在但新增商品不在购物车中,则在该商店下push新增商品;
            //先构造新增的对象
            let goodJson = JSON.stringify(this.good);
            let addGood = JSON.parse(goodJson);
            let addG = {
              id: addGood.id,
              name: addGood.name,
              price: addGood.price,
              count: addGood.count,
              isChecked: false,
            };
            this.infos[j].goods.push(addG);
            this.clearGood();
          }
        }
        /*1)这个方法会把good的地址也push进去,而不是拷贝
        this.goods.push(this.good);
        2)构建一个新对象,再push进去
        (parse必须是json格式,所以先用stringify转换成json字符串类型再用parse转换成对象)*/

        //c   商店不存在且新增商品不在购物车中,则新增商店商品
        //先构造新增的对象
        let goodJson = JSON.stringify(this.good);
        let addGood = JSON.parse(goodJson);
        let addS = {
          storeId: addGood.storeId,
          storeName: addGood.storeName,
          isStChecked: false,
          goods: [],
        };
        let addG = {
          id: addGood.id,
          name: addGood.name,
          price: addGood.price,
          count: addGood.count,
          isChecked: false,
        };
        this.infos.push(addS);
        addS.goods.push(addG);
        this.clearGood();
      }

    },
    //判断每个输入框的内容是否输入,让表单每个元素必填
    judgeGood() {
      if (this.good.storeId == '' || this.good.storeId == undefined || this.good.storeId == null) {
        alert('请输入商店后台编号');
        return false;
      };
      if (this.good.storeName == '' || this.good.storeName == undefined || this.good.storeName == null) {
        alert('请输入商店名称');
        return false;
      };
      if (this.good.id == '' || this.good.id == undefined || this.good.id == null) {
        alert('请输入商品编号');
        return false;
      };
      if (this.good.name == '' || this.good.name == undefined || this.good.name == null) {
        alert('请输入商品名称');
        return false;
      };
      if (this.good.price == '' || this.good.price == undefined || this.good.price == null) {
        alert('请输入商品价格');
        return false;
      };
      if (this.good.count == '' || this.good.count == undefined || this.good.count == null) {
        alert('请输入商品数量');
        return false;
      };
      return true;
    },
    //当点击添加时,输入框内的内容同时为空
    clearGood() {
      this.good.storeId = '';
      this.good.storeName = '';
      this.good.id = '';
      this.good.name = '';
      this.good.price = '';
      this.good.count = '';
    },
    // 1. 全选复选框选中,让所有商店所有商品被选中
    clickAllChecked() {
      let afterClickChecked = !this.isAllChecked;
      for (let i = 0; i < this.infos.length; i++) {
        this.infos[i].isStChecked = afterClickChecked;
        for (let j = 0; j < this.infos[i].goods.length; j++) {
          this.infos[i].goods[j].isChecked = afterClickChecked;
        }
      }
    },

    // 2.点击商店复选框,该商店下面所有商品的复选框被选中
    clickStChecked(indexs) {
      this.infos[indexs].isStChecked = !this.infos[indexs].isStChecked;//没有这句,4运行不起作用,因为商店isStChecked没有被更改
      let afterClickChecked = this.infos[indexs].isStChecked;
      for (let i = 0; i < this.infos[indexs].goods.length; i++) {
        this.infos[indexs].goods[i].isChecked = afterClickChecked;
      }
      //4.解决3c中的小bug,如果点击了商店复选框,就调用此方法对商店进行循环判断
      let flagSt = true;
      for (let j = 0; j < this.infos.length; j++) {
        if (!this.infos[j].isStChecked) {
          flagSt = false;
          break;
        }
      }
      this.isAllChecked = flagSt;
    },

    //3.
    //a.每次点击商品复选框,都循环查看该商店下所有商品复选框是否全部选中,只要有一个商品没选中,商店就不会被选中;
    //b.同时,也循环查看商店的复选框是否全部选中,商店复选框全选中,则全选复选框选中
    /*c.另外有个小bug,如果所有商店的复选框已全部选中,但最后一次点击的复选框不是商品复选框,全选复选框不会被选中
    因为如果最后一次的点击后,所有商店商品被选中,但点击的不是商品复选框,则不会调用此方法(判断商店是否全部选中的循环没有执行)*/
    clickChecked(indexs, index) {
      //决定商店复选框是否选中
      let flag = true;
      this.infos[indexs].goods[index].isChecked = !this.infos[indexs].goods[index].isChecked;
      for (let i = 0; i < this.infos[indexs].goods.length; i++) {
        if (!this.infos[indexs].goods[i].isChecked) {
          flag = false;
          break;
        }
      }
      this.infos[indexs].isStChecked = flag;
      //决定全选复选框是否选中
      let flagSt = true;
      for (let j = 0; j < this.infos.length; j++) {
        if (!this.infos[j].isStChecked) {
          flagSt = false;
          break;
        }
      }
      this.isAllChecked = flagSt;
    },

  },
  computed: {
    totalPrice() {
      let totalPrice = 0;
      for (let i = 0; i < this.infos.length; i++) {
        for (let j = 0; j < this.infos[i].goods.length; j++) {
          //被选中的商品才会把其价格加入总价格中
          if (this.infos[i].goods[j].isChecked) {
            totalPrice += this.infos[i].goods[j].price * this.infos[i].goods[j].count;
          }
        }
      }
      return totalPrice;
    }
  },
  filters: {
    // 方法②调用时用item.price | getFinalPrice
    getFinalPrice(price) {
      return '¥' + parseFloat(price).toFixed(2);
    },
  }
});

以上是我学习Vue的阶段性练习案例,如有不足,欢迎提出指正以及互相学习交流。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐