Springboot3 + MyBatis-Plus + MySql + Uniapp 商品加入购物车功能实现(针对上一篇sku)
- 1、效果展示
- 2、后端代码
- 2.1 model
- 2.2 mapper server serverImpl 参照上一篇自动生成
- 2.3 controller
- 3、前端代码
- 3.1 index.js
- 3.2 shop-info.vue
- 3.3 ShopBottomButton.vue
- 3.4 shop-cart.vue
本文章基于上一篇文章 Springboot3 + MyBatis-Plus + MySql + Uniapp 实现商品规格选择sku(附带自设计数据库,最新保姆级教程)
1、效果展示
2、后端代码
2.1 model
package com.zhong.model.entity.shop;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.math.BigDecimal;
import java.util.Date;import com.zhong.model.entity.BaseEntity;
import lombok.Data;/**** @TableName shop_cart*/
@TableName(value ="shop_cart")
@Data
public class ShopCart extends BaseEntity {/*** 商品ID*/private Integer goodsId;/*** 附加信息 “自营”*/private String businessName;/*** 商品店铺*/private String shopName;/*** 商品主图*/private String mainImage;/*** 商品标题*/private String title;/*** 商品价格*/private BigDecimal price;/*** 商品数量*/private Integer goodsNum;/*** 购物车所属用户id*/private Integer userId;/*** 所选商品规格id*/private Integer goodsSpecsId;@TableField(exist = false)private static final long serialVersionUID = 1L;
}
2.2 mapper server serverImpl 参照上一篇自动生成
ShopCartService
package com.zhong.service;import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zhong.model.entity.shop.ShopCart;
import com.zhong.vo.shop.ShopCartVo;
import com.zhong.vo.shop.ShopSkuVo;/**
* @author zhong
* @description 针对表【shop_cart】的数据库操作Service
* @createDate 2024-09-19 10:53:15
*/
public interface ShopCartService extends IService<ShopCart> {Page<ShopCartVo> pageItem(Page<ShopCart> page);ShopSkuVo getSpecsById(Long id);}
ShopCartServiceImpl
package com.zhong.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.zhong.login.LoginUserHolder;
import com.zhong.mapper.shop.ShopCartMapper;
import com.zhong.mapper.shop.ShopSpecsMapper;
import com.zhong.model.entity.shop.ShopCart;
import com.zhong.model.entity.shop.ShopSku;
import com.zhong.model.entity.shop.ShopSpecs;
import com.zhong.service.ShopCartService;
import com.zhong.service.ShopSkuService;
import com.zhong.vo.shop.ShopCartVo;
import com.zhong.vo.shop.ShopSkuVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;/*** @author zhong* @description 针对表【shop_cart】的数据库操作Service实现* @createDate 2024-09-19 10:53:15*/
@Service
public class ShopCartServiceImpl extends ServiceImpl<ShopCartMapper, ShopCart>implements ShopCartService {@Autowiredprivate ShopCartMapper shopCartMapper;@Autowiredprivate ShopSpecsMapper shopSpecsMapper;@Autowiredprivate ShopSkuService shopSkuService;@Overridepublic Page<ShopCartVo> pageItem(Page<ShopCart> page) {Long userId = LoginUserHolder.getLoginUser().getUserId();LambdaQueryWrapper<ShopCart> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ShopCart::getIsDeleted, 0).eq(ShopCart::getUserId, userId);// 获取所有购物车列表Page<ShopCart> shopCartPage = shopCartMapper.selectPage(page, queryWrapper);// 初始化返回的 ShopCartVo 分页对象Page<ShopCartVo> shopCartVoPage = new Page<>(shopCartPage.getCurrent(), shopCartPage.getSize(), shopCartPage.getTotal());// 获取 ShopCart 列表List<ShopCart> shopCartList = shopCartPage.getRecords();List<ShopCartVo> shopCartListVo = new ArrayList<>();// 遍历 ShopCart 列表并转换为 ShopCartVo 列表for (ShopCart shopCart : shopCartList) {ShopCartVo shopCartVo = new ShopCartVo();// 使用 BeanUtils 复制属性BeanUtils.copyProperties(shopCart, shopCartVo);// 获取商品规格信息并设置到 ShopCartVo 中Integer specsId = shopCart.getGoodsSpecsId();if (specsId != null) {ShopSkuVo shopSkuVo = getSpecsById(Long.valueOf(specsId));shopCartVo.setSpecs(shopSkuVo);}// 添加到 ShopCartVo 列表shopCartListVo.add(shopCartVo);}// 将转换后的 ShopCartVo 列表设置到分页对象中shopCartVoPage.setRecords(shopCartListVo);return shopCartVoPage;}@Overridepublic ShopSkuVo getSpecsById(Long id) {ShopSkuVo shopSkuVo = new ShopSkuVo();ShopSpecs shopSpecs = shopSpecsMapper.selectById(id);// 新建一个规格详情信息 Vo 方便后续添加到 shopSkuVoList<ShopSku> skuArrayList = new ArrayList<>();if (shopSpecs.getSku1() != null) {// 新建一个 shopSku 方便添加到 List<ShopSku>ShopSku shopSku = new ShopSku();// 根据规格ID获取规格详情ShopSku sku = shopSkuService.getById(shopSpecs.getSku1());shopSkuVo.setId(shopSpecs.getId());shopSku.setAttr(sku.getAttr());// 将规格值添加到 shopSkushopSku.setAttrValue(sku.getAttrValue());skuArrayList.add(shopSku);}if (shopSpecs.getSku2() != null) {// 新建一个 shopSku 方便添加到 List<ShopSku>ShopSku shopSku = new ShopSku();// 根据规格ID获取规格详情ShopSku sku = shopSkuService.getById(shopSpecs.getSku2());shopSkuVo.setId(shopSpecs.getId());shopSku.setAttr(sku.getAttr());// 将规格值添加到 shopSkushopSku.setAttrValue(sku.getAttrValue());skuArrayList.add(shopSku);}if (shopSpecs.getSku3() != null) {// 新建一个 shopSku 方便添加到 List<ShopSku>ShopSku shopSku = new ShopSku();// 根据规格ID获取规格详情ShopSku sku = shopSkuService.getById(shopSpecs.getSku3());shopSkuVo.setId(shopSpecs.getId());shopSku.setAttr(sku.getAttr());// 将规格值添加到 shopSkushopSku.setAttrValue(sku.getAttrValue());skuArrayList.add(shopSku);}if (shopSpecs.getSku4() != null) {// 新建一个 shopSku 方便添加到 List<ShopSku>ShopSku shopSku = new ShopSku();// 根据规格ID获取规格详情ShopSku sku = shopSkuService.getById(shopSpecs.getSku4());shopSkuVo.setId(shopSpecs.getId());shopSku.setAttr(sku.getAttr());// 将规格值添加到 shopSkushopSku.setAttrValue(sku.getAttrValue());skuArrayList.add(shopSku);}shopSkuVo.setSkus(skuArrayList);shopSkuVo.setPrice(shopSpecs.getPrice());return shopSkuVo;}
}
2.3 controller
package com.zhong.controller.shop;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zhong.login.LoginUserHolder;
import com.zhong.model.entity.shop.ShopCart;
import com.zhong.result.Result;
import com.zhong.service.ShopCartService;
import com.zhong.vo.shop.ShopCartVo;
import com.zhong.vo.shop.ShopInfoVo;
import com.zhong.vo.shop.ShopSkuVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;/*** @ClassName : ShopCartController* @Description :* @Author : zhx* @Date: 2024-09-19 10:55*/
@RestController
@RequestMapping("/app/shop/cart")
@Tag(name = "购物车信息")
public class ShopCartController {@Autowiredprivate ShopCartService service;@Operation(summary = "添加到购物车")@PostMapping("add")public Result getDetailById(@RequestBody ShopCart shopCart) {// 判断是否存在LambdaQueryWrapper<ShopCart> queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(ShopCart::getIsDeleted, 0).eq(ShopCart::getGoodsSpecsId, shopCart.getGoodsSpecsId());ShopCart dbShopCart = service.getOne(queryWrapper);if(dbShopCart != null) {dbShopCart.setPrice(dbShopCart.getPrice().add(shopCart.getPrice()));dbShopCart.setGoodsNum(dbShopCart.getGoodsNum() + shopCart.getGoodsNum());service.saveOrUpdate(dbShopCart);}else {Long userId = LoginUserHolder.getLoginUser().getUserId();shopCart.setUserId(Math.toIntExact(userId));shopCart.setIsDeleted((byte) 0);service.saveOrUpdate(shopCart);}return Result.ok();}@Operation(summary = "分页获取购物车商品信息")@GetMapping("listItem")public Result<Page<ShopCartVo>> listItem(@RequestParam long current, @RequestParam long size) {Page<ShopCart> shopCartPage = new Page<>(current, size);Page<ShopCartVo> page = service.pageItem(shopCartPage);return Result.ok(page);}@Operation(summary = "根据id获取商品规格")@GetMapping("getSpecsById")public Result<ShopSkuVo> getSpecsById(@RequestParam Long id) {ShopSkuVo shopSkuVo = service.getSpecsById(id);return Result.ok(shopSkuVo);}
}
3、前端代码
3.1 index.js
import http from '@/utils/request.js';export const getAllShopApi = (params) => {return http.get(`/app/shop/listItem?current=${params.current}&size=${params.size}`)
}export const getShopByIdApi = (id) => {return http.get(`/app/shop/getDetailById?id=${id}`)
}// 添加到购物车
export const addShopToCartApi = (params) => {return http.post(`/app/shop/cart/add`, params)
}// 分页获取购物车
export const getAllShopCartApi = (params) => {return http.get(`/app/shop/cart/listItem?current=${params.current}&size=${params.size}`)
}
3.2 shop-info.vue
<template><view><template v-for="(item, index) in [data]" :key="index"><view class=""><up-swiper :list="item.shopImgSwiper" circular :autoplay="true" bgColor="#ffffff" height="360rpx"imgMode="auto"></up-swiper></view><view class="connect card card-shadow"><view class="price"><view class=""><up-text mode="price" :text="item.newPrice" color="red" size="24"></up-text></view><view class=""><text>已售{{item.saleNumber}}</text></view></view><!-- 标题 --><view class="title"><up-row customStyle="margin-bottom: 10px"><up-col span="2" v-if="item.businessName"><view class="" style="display: flex;"><up-tag :text="item.businessName" size="mini" type="error"></up-tag></view></up-col><up-col :span="item.businessName?10 :12"><text>{{item.title}}</text></up-col></up-row></view><!-- 发货 --><view class="logistics flex" style=" position: relative;"><up-icon name="car"></up-icon><view class="" style="width: 20rpx;"></view><view class="font-lite-size"><text>承诺24小时内发货,晚发必赔</text></view><view class="" style="position: absolute;right: 10rpx;"><up-icon name="arrow-right"></up-icon></view></view><!-- 破损 --><view class="pock flex" style=" position: relative;"><up-icon name="car"></up-icon><view class="" style="width: 20rpx;"></view><view class="font-lite-size"><text>破损包退 | 退货运费险 | 极速退款 | 7天无理由退换</text></view><view class="" style="position: absolute;right: 10rpx;"><up-icon name="arrow-right" size="16"></up-icon></view></view></view><!-- 评价 --><view class="card card-shadow"><ShopCommentVue></ShopCommentVue></view><!-- 店铺信息 --><view class="card card-shadow"><StoreInformationVue></StoreInformationVue></view><!-- 商品详情图片 --><view class="bb-info card card-shadow" v-if="data.shopImgInfo.length> 0"><ShopInfoImageListVue :imgList="data.shopImgInfo"></ShopInfoImageListVue></view><!-- 提示 --><view class="tips card card-shadow"><ShopTipsVue></ShopTipsVue></view><!-- 底部tabbar安全距离 --><view class="" style="height: 140rpx;"></view></template><!-- 加入购物车等操作 --><view class="bottom"><ShopBottomButtonVue :data="data"></ShopBottomButtonVue></view></view>
</template><script setup>import {reactive,ref,onMounted} from 'vue';import ShopCommentVue from '@/pages/components/Home/ShopComment.vue';import StoreInformationVue from '@/pages/components/Home/StoreInformation.vue';import ShopInfoImageListVue from '@/pages/components/Home/ShopInfoImageList.vue';import ShopTipsVue from '@/pages/components/Home/ShopTips.vue';import ShopBottomButtonVue from '@/pages/components/Home/ShopBottomButton.vue';import {onLoad} from "@dcloudio/uni-app"import {getShopByIdApi} from "@/pages/api/shop/index.js"const shopId = ref();const data = ref();onLoad((options) => {shopId.value = options.id;})onMounted(async () => {console.log(shopId.value);let res = await getShopByIdApi(shopId.value);data.value = res;console.log(res);})// 父组件中的价格数据const price = ref(null);// 处理子组件传来的价格更新const handlePriceUpdate = (newPrice) => {price.value = newPrice;};
</script><style lang="less" scoped>.card-shadow {border-radius: 20rpx;box-shadow: 10rpx 10rpx 10rpx 10rpx rgba(0.2, 0.1, 0.2, 0.2);}.card {margin: 20rpx;padding: 20rpx;background-color: #FFF;border-radius: 20rpx;}.font-lite-size {font-size: 26rpx;}.flex {display: flex;align-items: center;}.title {margin-top: 20rpx;}.pock {margin: 20rpx 0;}.price {padding-right: 20rpx;display: flex;justify-content: space-between;align-items: center;}
</style>
3.3 ShopBottomButton.vue
<template><view class="mains"><view class="connect"><view class="letf-connect"><up-icon name="gift" size="40rpx"></up-icon><text style="font-size: 26rpx;">店铺</text></view><view class="letf-connect"><up-icon name="kefu-ermai" size="40rpx"></up-icon><text style="font-size: 26rpx;">客服</text></view><view class="letf-connect" @click="toShopCart"><up-icon name="shopping-cart" size="40rpx"></up-icon><text style="font-size: 26rpx;">购物车</text></view><view class="" style="display: flex;flex: 1;padding-left: 20rpx;"><up-button text="加入购物车" type="warning" @click="addCartButtonFun"></up-button><up-button text="立即购买" type="success" @click="nowBuyFun"></up-button></view></view><!-- 弹出层选择商品规格 --><up-popup :show="show" mode="bottom" :round="10" @close="close" @open="open"><view><view class="top"><up-image :src="props.data.mainImage" width="200rpx" height="300rpx" radius="10"></up-image><view style="padding-left: 40rpx;"><text style="flex: 1;overflow: hidden;">{{props.data.title}}</text><view style="padding: 20rpx 0;" v-if="calculatedPrice"><up-text mode="price" :text="calculatedPrice" color="red" size="20"></up-text></view><view style="padding: 20rpx 0;" v-else><up-text mode="price" :text="props.data.newPrice * shopNum" color="red" size="20"></up-text></view><view style="display: flex;padding-top: 20rpx;"><up-number-box v-model="shopNum" min="1"></up-number-box></view></view></view><!-- 渲染规格 --><view class=""><template v-for="(item,index) in resSkuGroup"><view style="padding-left: 20rpx;">{{item.key}}</view><view style="display: flex;"><template v-for="(tag,i) in item.value" :key="i"><view class="" style="display: flex;padding:20rpx;"><up-tag :text="tag.info" :plain="!tag.isCheck" :color="tag.isCheck?'#FFF':'#000'":borderColor="tag.isCheck?'#FFF':'#000'" type="error"@click="changeTagIsCheckFun(tag,index)"></up-tag></view></template></view></template></view><view class="" style="padding: 20rpx;" v-if="isBuy"><up-button text="立即购买" shape="circle" type="error" @click="BuyShopFun"></up-button></view><view class="" style="padding: 20rpx;" v-else><up-button text="加入购物车" shape="circle" type="error" @click="addCartFun"></up-button></view></view></up-popup></view>
</template><script setup>import {computed,onMounted,reactive,defineEmits,watch,ref} from 'vue';import {addShopToCartApi} from "@/pages/api/shop/index.js";// 创建响应式数据const props = defineProps({data: Object});const show = ref(false);const resData = ref();const resSkuData = ref();const resSkuGroup = ref();const resDataFun = async () => {resSkuGroup.value = await props.data.skuGroup;resSkuData.value = await props.data.shopSpecs;console.log(props.data.shopSpecs);console.log(resSkuData.value);}const changeTagIsCheckFun = (item, index) => {resSkuGroup.value[index].value.map(x => {if (x.info == item.info) {x.isCheck = true;} else {x.isCheck = false;}})console.log(resSkuGroup.value);}// 通过 computed 计算选中的属性值const checkedAttributes = computed(() => {return resSkuGroup.value.map(option => ({attr: option.key,attrValue: option.value.find(item => item.isCheck)?.info || null}));});// 商品数量const shopNum = ref(1);// 根据选中的属性值匹配 SKU,返回匹配的 SKU 对象const matchingSku = computed(() => {return resSkuData.value.find(sku => {return sku.skus.every(skuAttr => {return checkedAttributes.value.some(attr =>attr.attr === skuAttr.attr && attr.attrValue === skuAttr.attrValue);});});});// 计算匹配 SKU 的价格和 IDconst calculatedPrice = computed(() => {return matchingSku.value ? matchingSku.value.price * shopNum.value : null;});const matchingSkuId = computed(() => {return matchingSku.value ? matchingSku.value.id : null;});// 区分是加入购物车还是立即购买const isBuy = ref(false);// 加入购物车const addCartButtonFun = () => {show.value = true;isBuy.value = false;}// 立即购买 const nowBuyFun = () => {show.value = true;isBuy.value = true;}// 立即购买操作 const BuyShopFun = () => {}onMounted(() => {console.log(props.data);resDataFun();})// 定义方法 const open = () => {// 打开逻辑,比如设置 show 为 true show.value = true;// console.log('open'); }const close = () => {// 关闭逻辑,设置 show 为 false show.value = false;// console.log('close'); }const toShopCart = () => {uni.navigateTo({url: "/pages/src/home/shop-cart/shop-cart"})}// 添加到购物车const addCartFun = async () => {let res = {businessName: props.data.businessName,shopName: props.data.shopName,mainImage: props.data.mainImage,title: props.data.title,price: calculatedPrice.value,goodsId: props.data.id,goodsNum: shopNum.value,goodsSpecsId: matchingSkuId.value}console.log(res);await addShopToCartApi(res);close();uni.showToast({title: "添加成功",icon: 'success'})}
</script><style lang="scss" scoped>.top {display: flex;padding: 40rpx;}.mains {position: fixed;bottom: 0;left: 0;width: 100%;/* 占据全宽 */height: 120rpx;/* Tabbar 高度 */background-color: #FFF;border-top: 2rpx solid #7d7e80;}.connect {display: flex;justify-content: space-around;padding: 20rpx;align-items: center;}.letf-connect {padding: 0 10rpx;display: flex;flex-direction: column;align-items: center;}
</style>
3.4 shop-cart.vue
<template><view class=""><view class="" v-if="state.cartItems.length == 0"><up-empty mode="car" icon="http://cdn.uviewui.com/uview/empty/car.png"></up-empty></view><view class="card" v-else><template v-for="(info, j) in state.cartItems" :key="j"><view class="cart-data card-shadow"><view class="" style="display: flex;">{{info.shopName}}<up-icon name="arrow-right"></up-icon></view><template v-for="(item, index) in info.items" :key="index"><view class="" style="display: flex;padding: 20rpx 0;align-items: center;"><view><up-checkbox :customStyle="{marginBottom: '8px'}" usedAlonev-model:checked="item.isChoose" @change="toggleItemChoose(item.shopName, item.id)"></up-checkbox></view><view class="cart-image"><up-image :src="item.mainImage" mode="widthFix" height="200rpx" width="220rpx"radius="10"></up-image></view><view><view class="cart-right"><view style="margin-bottom: 10rpx;font-size: 30rpx;">{{item.title}}</view><view class="" v-if="item.specs" style="display: flex;"><template v-for="(sku, i) in item.specs.skus"><viewstyle="margin-bottom: 20rpx;font-size: 26rpx;color: #7d7e80;margin-right: 10rpx;">{{sku.attr}}/{{sku.attrValue}}</view></template></view><view class="" style="display: flex;align-items: center;"><up-text mode="price" :text="item.price"></up-text><view class="" style="width: 10rpx;"></view><up-number-box v-model="item.goodsNum"@change="val => changeItemQuantity(item,item.id, val.value)"min="1"></up-number-box></view></view></view></view></template></view></template></view><view class="" style="height: 160rpx;"></view><view class="foot card"><view class="card-connect"><up-checkbox :customStyle="{marginBottom: '8px'}" usedAlone v-model:checked="state.allChose"@change="toggleAllChose"></up-checkbox><view class="" style="display: flex; align-items: center;"><view style="font-size: 28rpx;">全选</view><view style="padding-left: 20rpx;font-size: 24rpx;">已选{{selectedItemsCount}}件,合计</view><view class="" style="display: flex;flex: 1;"><up-text mode="price" :text="totalSelectedPrice" color="red" size="18"></up-text></view></view><view class="" style="width: 20rpx;position: relative;"></view><view class="" style="position: absolute;right: 40rpx;"><view class="" style="display: flex;"><up-button type="error" text="去结算" shape="circle" style="width: 150rpx;"@click="toSubmitOrder"></up-button></view></view><up-toast ref="uToastRef"></up-toast></view></view></view>
</template><script setup>import {ref,computed,onMounted,watch} from 'vue';import {useCartStore} from '@/pages/store/cart/cart.js'import {getAllShopCartApi} from "@/pages/api/shop/index.js"import {storeToRefs} from "pinia";// 使用 Pinia storeconst cartStore = useCartStore();// 获取状态和操作const {state,selectedItemsCount,totalSelectedPrice,selectedItems} = storeToRefs(cartStore);const {toggleItemChoose,changeItemQuantity,toggleAllChose} = cartStore;const current = ref(1);const size = ref(10);onMounted(async () => {let res = {current: current.value,size: size.value}// 恢复购物车数据const response = await getAllShopCartApi(res)console.log(response);const groupedItems = [];response.records.forEach(item => {// 查找是否已经存在相同店铺名的对象const shop = groupedItems.find(shop => shop.shopName === item.shopName);if (shop) {// 如果存在,直接将商品添加到该店铺的商品列表中shop.items.push(item);} else {// 如果不存在,创建一个新的店铺对象,并将商品添加进去groupedItems.push({shopName: item.shopName,items: [item]});}});console.log(groupedItems);cartStore.setCartItems(groupedItems);});// 创建响应式数据 const show = ref(false);// 方法const uToastRef = ref(null)const showToast = (params) => {uToastRef.value.show(params);}const toSubmitOrder = () => {if (selectedItems.value.length > 0) {uni.navigateTo({url: "/pages/src/home/submit-order/submit-order"})} else {showToast({type: 'default',title: '默认主题',message: "您还没有选择商品哦",});}}
</script><style lang="scss" scoped>.foot {position: fixed;bottom: 0;left: 0;width: 90%;/* 占据全宽 */height: 100rpx;/* Tabbar 高度 */background-color: #FFF;display: flex;align-items: center;.card-connect {display: flex;align-items: center;justify-content: space-between;}}.card {margin: 20rpx;padding: 20rpx;background-color: #FFF;border-radius: 20rpx;}.card-shadow {border-radius: 20rpx;box-shadow: 10rpx 10rpx 10rpx 10rpx rgba(0.2, 0.1, 0.2, 0.2);}.cart-data {margin-bottom: 40rpx;padding: 20rpx;display: flex;flex-wrap: wrap;align-items: center;.cart-image {flex: 1;}.cart-right {display: flex;flex-direction: column;padding-left: 20rpx;}}
</style>