Taro跨端开发探索17——商城小程序购物车页面开发

1,485 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第23天,点击查看活动详情

前言

昨天,我们已经完成了商城小程序商品的最后一环:商品详情页。在商品详情页面点击【加入购物车】按钮时,传入后端需要的参数,我即可将商品加入购物车列表中。
今天我们来实现购物车的逻辑

需求分析

读过我之前文章的读者可能了解我之前在介绍父子组件传参时已经实现了购物车列表加载。但是之前只是作为传值得演示代码。
今天我们先分析一下京喜的小程序,然后完成购物车需要的所有代码\

01274279b827d5f09facb7c53ca8806.jpg

如上图所示,我们可以看到,在原有的购物车列表展示的功能之外,京喜的购物车还有以下功能

  • 按照店铺分组
  • 选中店铺时自动选中店铺下的所有商品
  • 多选的时候编辑、删除、计算价格的功能。我们在这里先实现计算价格功能。
  • 单个购物车移入收藏、删除功能
    这篇文章我们来在原有的功能的基础上实现上边的几个功能

代码开发

我们基于代码来讲一下功能实现的逻辑
shop-cart/index.scss

@import "~taro-ui/dist/style/components/divider.scss";
@import "~taro-ui/dist/style/components/checkbox.scss";
@import "~taro-ui/dist/style/components/swipe-action.scss";
.shop-cart {
  .shop-title {
    color: red;
  }
}

这里就是引入taro ui的scss
shop-cart/index.tsx

import { View, Text } from "@tarojs/components";
import { useEffect, useState } from "react";
import ShopCartItem from "./shop-cart-item";
import ShopCartEdit from "./shop-cart-edit";
import { AtButton, AtDivider } from "taro-ui";
import { AtSwipeAction } from "taro-ui";
import { AtCheckbox } from "taro-ui";
import "./index.scss";
export default function ShopCart() {
  const [shopCartList, setShopCartList] = useState([] as any);
  useEffect(() => {
    let cartList = [
      {
        shopId: 1,
        shopName: "店铺A",
        isChecked: false,
        shopList: [
          {
            id: "1",
            isChecked: false,
            name: "第1个商品",
            price: 100,
            num: 1,
          },
          {
            id: "2",
            isChecked: false,
            name: "第2个商品",
            price: 200,
            num: 2,
          },
        ],
      },
      {
        shopId: 2,
        shopName: "店铺B",
        isChecked: false,
        shopList: [
          {
            id: "3",
            isChecked: false,
            name: "第3个商品",
            price: 300,
            num: 3,
          },
          {
            id: "4",
            isChecked: false,
            name: "第4个商品",
            price: 400,
            num: 4,
          },
        ],
      },
    ];
    setShopCartList(cartList);
  }, []);
  const addItemNum = (id: string, num: number) => {
    const newShopCartList = [] as any;
    shopCartList.map((item) => {
      let curItem = item["shopList"].map((cart) => {
        if (cart.id === id) {
          cart.num += num;
        }
        return cart;
      });
      newShopCartList.push({
        isChecked: item.isChecked,
        shopId: item["shopId"],
        shopName: item["shopName"],
        shopList: curItem,
      });
    });
    setShopCartList(newShopCartList);
  };
  const changeShopChecked = (shopId) => {
    const newShopCartList = [] as any;
    shopCartList.map((item) => {
      if (item.shopId === shopId) {
        item.isChecked = !item.isChecked;
      }
      item["shopList"].map((cart) => {
        cart.isChecked = item.isChecked;
      });
      newShopCartList.push({
        isChecked: item.isChecked,
        shopId: item["shopId"],
        shopName: item["shopName"],
        shopList: item["shopList"],
      });
    });
    setShopCartList(newShopCartList);
    calcuate();
  };
  const changeCartChecked = (id) => {
    const newShopCartList = [] as any;
    shopCartList.map((item) => {
      item["shopList"].map((cart) => {
        if (cart.id === id) {
          cart.isChecked = !cart.isChecked;
        }
      });
      let shopCaheckedFlag =
        item["shopList"].filter((item) => !item.isChecked).length === 0;
      console.log(shopCaheckedFlag);
      newShopCartList.push({
        isChecked: shopCaheckedFlag,
        shopId: item["shopId"],
        shopName: item["shopName"],
        shopList: item["shopList"],
      });
    });
    setShopCartList(newShopCartList);
    calcuate();
  };
  const calcuate = () => {
    let totalPrice = 0;
    shopCartList.map((item) => {
      item["shopList"].map((cart) => {
        if (cart.isChecked) {
          totalPrice += cart.price * cart.num;
        }
      });
    });
    console.log(totalPrice);
  };
  return (
    <View className="shop-cart">
      {shopCartList.map((shopItem) => (
        <View>
          <Text className="shop-title">{shopItem["shopName"]}</Text>
          <AtCheckbox
            options={[{ value: shopItem["id"], label: "" }]}
            selectedList={shopItem["isChecked"] ? [shopItem["id"]] : []}
            onChange={() => {
              changeShopChecked(shopItem["shopId"]);
            }}
          />
          {shopItem["shopList"].map((item) => (
            <View>
              <AtCheckbox
                options={[{ value: item["id"] + "_item", label: "" }]}
                selectedList={item["isChecked"] ? [item["id"] + "_item"] : []}
                onChange={() => {
                  changeCartChecked(item["id"]);
                }}
              />
              <AtSwipeAction maxDistance={150} areaWidth={350}>
                <ShopCartItem item={item}></ShopCartItem>
                <ShopCartEdit
                  item={item}
                  addIndexNum={addItemNum}
                ></ShopCartEdit>
                <AtButton type="primary">点我删除</AtButton>
                <AtButton type="primary">移入收藏夹</AtButton>
              </AtSwipeAction>
            </View>
          ))}
          <AtDivider content="" />
        </View>
      ))}
    </View>
  );
}

简单解释一下我的实现逻辑

  • 修改请求返回体格式 因为我们要先显示店铺再显示购物车明细,所以我们应该将在后端返回的接口数据中添加一层,用来封装购物车信息

1650706616(1).png

  • 修改计算的逻辑。因为我们多嵌套了一层,所以组件传参和计算是否选中和计算金额的方法都要根据定义好的层级来计算

1650706748(1).png

我截取出来的这两个function从上到下实现的逻辑为:

  1. 子组件更新数量的时候,遍历店铺和店铺下的购物车明细,找到匹配的购物车明细重新设值
  2. 在点击店铺的单选时,将店铺下的所有购物车明细设置为和店铺一样的点击状态

1650707426(1).png

这两个function从上到下实现的逻辑为:

  1. 在点击购物车明细的单选时,先设置当前明细的单选状态,然后将店铺下的所有明细filter一下,判断是否含有未选中的,如果没有店铺的选中就是true
  2. 获取所有状态为checked的购物车明细信息,进行计算。注意我这里只是作为验证是否开发成功的打印和计算,实际情况下的场景应该是前端只传输购物车唯一标识,后端根据id数组查询库里的数量和金额计算!!!
  • 最后关于渲染区域需要注意的事项

1650707668(1).png

可以看到我在遍历购物车明细的时候,加了一个_item的后缀,这是因为后端接口中的店铺和购物车明细的唯一标识可能会相同,所以我们加上这个防止重复

结语

今天我们已经完成了购物车页面的开发,现在商场小程只剩下订单和个人中心相关的功能了。我们明天计划探索一下个人中心的功能,进行相关代码开发,欢迎各位多多点赞关注!