从零开始,搭建一个简单的购物平台(十七)前端商城部分

本文最后更新于:9 个月前

从零开始,搭建一个简单的购物平台(十六)前端商城部分:
https://blog.csdn.net/time_____/article/details/108769229
项目源码(持续更新):https://gitee.com/DieHunter/myCode/tree/master/shopping

上篇文章讲到了商品详情页面实现,最后一个功能是加入购物车,也就是通过列表对比商品是否存在,若存在,则对现有商品数据进行修改,若不存在,则初始化商品,并添加至购物车列表中,这篇文章将介绍对购物车数据进行操作功能

购物车功能包括
修改商品数量 删除单个商品 全选/反选 批量删除

这个页面,将其分解成四个组件,分别是顶部的Title,菜单栏Tabbar,单件商品shopCarItem和下面的批量操作shopCarOrder

修改商品数量在上篇文章中已介绍,通过countShopItem方法进行修改

删除单个商品

delShopItem(_id) {
    let _shopCar = this.state//获取现有购物车列表
    _shopCar.splice(_id, 1)//数组删除第_id项
    this.state = _shopCar//刷新购物车列表
    this.$events.emitEvent(EventName.CountShop);//刷新界面
  }

全选/反选,这里有两个注意的地方,全选/反选的实现有两种状态,一是每个商品单选被全选或反选后全选按钮也会跟随状态,二是全选按钮全选或反选后所有单选按钮也会跟随状态

我们首先在store的action加一个方法,用于总计商品的全选状态,商品总数,及总价格。每当商品被选中,就触发这个方法,刷新数据

filterSelect() {//修改商品全选,单个商品驱动全选按钮,刷新数据
  let shopConfig = {//所有商品总计初始值
    _count: 1,//是否全选
    _selCount: 0,//商品总数
    _sum: 0//商品总价
  }
  this.state.forEach(item => {
    shopConfig._selCount += item.isSelect ? 1 : 0;
    shopConfig._count *= item.isSelect;//判断是否全选,初始值0,若全为false,相乘等于0,若全为true,相乘为1,即等于1是全选,等于0是未全选
    shopConfig._sum += item.isSelect ? item.sum : 0
  });
  this.$events.emitEvent(EventName.SelectParent, shopConfig);
}

此外,我们需要再写个方法,用于全选按钮修改各个商品的选中状态

selAllChild(_sel) {//修改商品全选,全选按钮驱动单个商品,刷新数据
  this.state = this.state.map(item => {
    item.isSelect = _sel;//当全选按钮选中,修改所有商品状态
    return item;
  });
  this.$events.emitEvent(EventName.SelectAllChild);
}

于是在单件商品shopCarItem组件中,我们进行调用action中的方法,修改全局state值

<template>
  <div>
    <ul v-if="shopList.length" class="shopCar">
      <li v-for="(item,index) in shopList" :key="index">
        <span class="mint-checkbox" @click="selectHandler(index)">
          <input class="mint-checkbox-input" type="checkbox" :checked="item.isSelect" />
          <span class="mint-checkbox-core"></span>
        </span>
        <img :src="imgPath+item.shopPic" alt />
        <div class="shopInfo">
          <div>
            <span>{{item.shopName}} {{item.shopScale}}克</span>
            <span>¥{{item.shopPrice}}</span>
          </div>
          <div>
            <mt-button class="minus" type="default" @click="minusShopHandler(item)">-</mt-button>
            <span>{{item.shopCount}}</span>
            <mt-button class="add" type="default" @click="addShopHandler(item)">+</mt-button>
            <mt-button class="del" type="default" @click="delShopHandler(index)">×</mt-button>
          </div>
        </div>
      </li>
    </ul>
    <div v-else class="noShop">
      <div class="icon-jiarugouwuche iconfont"></div>
      <span>购物车为空</span>
    </div>
  </div>
</template>

<script>
import Config from "../../config/config";
import { Toast } from "mint-ui";
const { EventName } = Config;
export default {
  name: "shopCarItem",
  data() {
    return {
      shopCar: null,//初始化购物车
      shopList: [],//购物车列表state
      imgPath: Config.RequestPath,//静态文件根目录
      selectAll: false,//全选
    };
  },
  created() {
    this.shopCar = new this.$store.ShopCar();
    this.shopList = this.shopCar.state;
    this.$events.onEvent(EventName.CountShop, this.countHandler);//商品数量监听
    this.$events.onEvent(EventName.SelectAllChild, this.selAllHandler);//商品全选监听
  },
  mounted() {
    this.shopCar.filterSelect();//初始化全选,商品数量,商品总价
  },
  destroyed() {
    this.$events.offEvent(EventName.CountShop, this.countHandler);
    this.$events.offEvent(EventName.SelectAllChild, this.selAllHandler);
  },
  methods: {
    countHandler(res) {//修改商品数量,刷新数据
      this.shopList = this.shopCar.state;
      this.shopCar.filterSelect();
    },
    selectHandler(_id) {//修改商品全选,单个商品驱动全选按钮,刷新数据
      this.shopList[_id].isSelect = !this.shopList[_id].isSelect;
      this.shopCar.state = this.shopList;
      this.shopCar.filterSelect();
    },
    selAllHandler() {//修改商品全选,全选按钮驱动单个商品,刷新数据
      this.shopList = this.shopCar.state;
      this.shopCar.filterSelect();
    },
    addShopHandler(_data) {//添加商品,刷新数据
      _data.shopCount = 1;
      this.shopCar.countShopItem({
        ..._data,
      });
    },
    minusShopHandler(_data) {//减少商品,刷新数据
      _data.shopCount = -1;
      this.shopCar.countShopItem({
        ..._data,
      });
    },
    delShopHandler(_id) {//删除单个商品,刷新数据
      this.shopCar.delShopItem(_id);
    },
  },
};
</script>

<style lang="less" scoped>
@import "../../style/init.less";
.noShop {
  width: 100%;
  height: 100%;
  text-align: center;
  span {
    .f_s(36);
  }
  div {
    .w(200);
    .h(200);
    border-radius: 100%;
    background: @mainColor;
    .titleFont();
    box-shadow: 5px 5px 8px #777;
    .l_h(200);
    text-align: center;
    margin: 100px auto 20px;
    .f_s(100);
  }
}
.shopCar {
  .padbottom(130);
  width: 100%;
  li {
    border-bottom: 1px solid #d3d3d3;
    padding-left: unit(35 / @pxtorem, rem);
    .h(320);
    .mint-checkbox {
      .h(320);
      .l_h(320);
      display: inline-block;
      vertical-align: middle;
    }
    .mint-checkbox-input:checked + .mint-checkbox-core {
      background: #ea3e3c;
      border-color: #ea3e3c;
    }
    img {
      .h(265);
      .w(265);
      margin-left: unit(20 / @pxtorem, rem);
      display: inline-block;
      vertical-align: middle;
      background: #f5f6f5;
    }
    .shopInfo {
      .h(235);
      width: 50%;
      .padtop(30);
      padding-left: unit(10 / @pxtorem, rem);
      display: inline-block;
      vertical-align: middle;
      div:nth-child(1) {
        overflow: hidden;
        span {
          float: left;
        }
        span:nth-child(2) {
          float: right;
        }
      }
      div:nth-child(2) {
        margin-top: unit(85 / @pxtorem, rem);
        span {
          display: inline-block;
          vertical-align: middle;
          padding: 0 unit(50 / @pxtorem, rem);
        }
        .add,
        .minus,
        .del {
          display: inline-block;
          vertical-align: middle;
          background: white;
          box-shadow: none;
          .f_s(50);
        }
        .del {
          float: right;
        }
      }
    }
  }
}
</style>

在批量操作shopCarOrder组件中同理

<template>
  <div class="shopOrder">
    <span class="mint-checkbox" @click="selectHandler">
      <input class="mint-checkbox-input" type="checkbox" :checked="isSelAll" />
      <span class="mint-checkbox-core"></span>
    </span>
    <span>全选({{selCount}})</span>
    <span @click="delSelShop">删除({{selCount}})</span>
    <span>
      <span>¥{{sum}}</span>
      <span class="icon-qianjin iconfont" @click="sendOrder"></span>
    </span>
  </div>
</template>

<script>
import Config from "../../config/config";
import ShopCarOrderBussiness from "./bussiness";
import { Toast } from "mint-ui";
const { EventName } = Config;
export default {
  name: "shopCarOrder",
  data() {
    return {
      shopCar: null,
      isSelAll: false,//全选
      selCount: 0,//商品数量
      sum: 0,//商品总价
      orderList: null,//提交订单请求参数
      shopCarOrderBussiness: null,
    };
  },
  created() {
    this.shopCar = new this.$store.ShopCar();
    this.shopCarOrderBussiness = new ShopCarOrderBussiness(this);
    this.$events.onEvent(EventName.SelectParent, this.selAllHandler);//全选按钮监听,通过监听所有商品都选中或未全选,修改状态
  },
  destroyed() {
    this.$events.offEvent(EventName.SelectParent, this.selAllHandler);
  },
  methods: {
    selectHandler() {//驱动修改所有商品选中状态
      this.isSelAll = !this.isSelAll;
      this.shopCar.selAllChild(this.isSelAll);
    },
    selAllHandler({ _count, _selCount, _sum }) {
      this.isSelAll = _count;
      this.selCount = _selCount;
      this.sum = _sum;
    },
    delSelShop() {//删除选中商品
      this.shopCar.delSelShop();
    },
    sendOrder() {//提交订单
      this.shopCarOrderBussiness.sendOrderList();
    },
  },
};
</script>

<style lang="less" scoped>
@import "../../style/init.less";
.shopOrder {
  .f_s(34);
  color: #fff;
  position: fixed;
  .mcolor();
  bottom: unit(130 / @pxtorem, rem);
  width: 100%;
  .h(130);
  .l_h(130);
  > span:nth-child(1),
  > span:nth-child(2) {
    padding-left: unit(35 / @pxtorem, rem);
  }
  > span:nth-child(3) {
    padding-left: unit(200 / @pxtorem, rem);
  }

  > span:nth-child(4) {
    float: right;
    margin-right: unit(50 / @pxtorem, rem);
    > span:nth-child(1) {
      padding-left: unit(20 / @pxtorem, rem);
      border-left: 1px dashed #fff;
    }
    > span:nth-child(2) {
      padding-left: unit(50 / @pxtorem, rem);
    }
  }
  > span {
    display: inline-block;
  }
  .mint-checkbox-input + .mint-checkbox-core {
    background: transparent;
    border-color: #fff;
  }
  .mint-checkbox-input:checked + .mint-checkbox-core {
    .mcolor();
  }
}
</style>

在action中,我们还需要写一个函数,用于删除选中商品

delSelShop() {//直接通过遍历商品选中状态值进行删除,并刷新数据
    let _list = []
    this.state.map(item => {
      if (!item.isSelect) {
        _list.push(item)
      }
    });
    this.state = _list
    this.$events.emitEvent(EventName.CountShop);
  }

最后,在shopCar.vue界面中引入这四个组件,页面实现完成

<template>
  <div>
    <Top title="购物车"></Top>
    <div class="content">
      <ShopCarItem></ShopCarItem>
      <ShopCarOrder></ShopCarOrder>
    </div>
    <TabBar></TabBar>
  </div>
</template>

<script>
import Top from "../../components/top/top";
import ShopCarItem from "../../components/shopCarItem/shopCarItem";
import ShopCarOrder from "../../components/shopCarOrder/shopCarOrder";
import TabBar from "../../components/tabBar/tabBar";
export default {
  name: "shopCar",
  data() {
    return {};
  },
  components: {
    Top,
    ShopCarItem,
    ShopCarOrder,
    TabBar
  },
  created() {}
};
</script>
<style lang="less" scoped>
@import "../../style/init.less";
</style>

本篇文章主要对商品购物车操作进行了介绍,其中运用了数据刷新视图的基本操作,将数据,逻辑,视图三者分开,结合vue的数据绑定渲染页面。下篇文章将实现用户的注册,登录功能(简单的用户名密码注册,登录,和邮箱验证模块)