Uniapp + Vue3 + Vite +Uview + Pinia 分商家实现购物车功能(最新附源码保姆级)

news/2025/1/3 5:47:08/

Uniapp + Vue3 + Vite +Uview + Pinia 分商家实现购物车功能(最新附源码保姆级)

  • 1、效果展示
  • 2、安装 Pinia 和 Uview
  • 3、配置 Pinia
  • 4、页面展示

1、效果展示


在这里插入图片描述

注意:这个演示图没有背景色,背景色建议在 App.vue 中新增代码实现全局背景色

<style>page {background-color: #f8f9fb;}
</style>

2、安装 Pinia 和 Uview


官网

https://pinia.vuejs.org/zh/getting-started.html

安装命令

cnpm install pinia

Uiew 的安装以及配置参照我的这篇文章

Uniapp + Vite + Vue3 + uView + Pinia 实现自定义底部 Tabbar(最新保姆级教程)

3、配置 Pinia


  • main.js
import { createPinia } from 'pinia'
const app = createSSRApp(App);
app.use(pinia);
  • cart.js
// src/pages/store/cart/cart.js
import {defineStore
} from 'pinia';
import {reactive,computed
} from 'vue';export const useCartStore = defineStore('cart', () => {// 用 reactive 管理购物车数据const state = reactive({cartItems: [], // 购物车商品列表allChose: false // 全选状态});// 设置购物车数据const setCartItems = (items) => {state.cartItems = items.map(item => ({...item,isChoose: false, // 初始化为未选中状态num: item.num || 1 // 初始化数量}));saveCartToLocalStorage(); // 每次设置后将数据持久化};// 计算已选中的商品数量const selectedItemsCount = computed(() => {return state.cartItems.reduce((count, shop) => {return count + shop.items.filter(item => item.isChoose).reduce((shopCount, item) =>shopCount + item.num, 0);}, 0);});// 计算已选中商品的总价格const totalSelectedPrice = computed(() => {return state.cartItems.reduce((total, shop) => {return total + shop.items.filter(item => item.isChoose).reduce((shopTotal, item) =>shopTotal + item.price * item.num, 0);}, 0);});// 切换商品的选中状态const toggleItemChoose = (shopName, itemId) => {const shop = state.cartItems.find(shop => shop.shopName === shopName);console.log(shop);if (shop) {const cartItem = shop.items.find(cartItem => cartItem.id === itemId);if (cartItem) {cartItem.isChoose = !cartItem.isChoose;}updateAllChoseStatus(); // 每次切换选中状态后更新全选状态saveCartToLocalStorage();}};// 修改商品数量const changeItemQuantity = (shopName, itemId, quantity) => {const shop = state.cartItems.find(shop => shop.shopName === shopName);if (shop) {const cartItem = shop.items.find(cartItem => cartItem.id === itemId);if (cartItem) {cartItem.num = quantity;}saveCartToLocalStorage();}};// 获取所有已选中的商品const selectedItems = computed(() => {return state.cartItems.reduce((selected, shop) => {const selectedShopItems = shop.items.filter(item => item.isChoose);if (selectedShopItems.length > 0) {selected.push(...selectedShopItems);}return selected;}, []);});// 切换全选状态const toggleAllChose = () => {state.allChose = !state.allChose;state.cartItems.forEach(shop => {shop.items.forEach(item => {item.isChoose = state.allChose;});});saveCartToLocalStorage();};// 更新全选状态const updateAllChoseStatus = () => {// 遍历所有店铺的所有商品,如果有一个未选中,则全选状态为 falsestate.allChose = state.cartItems.every(shop =>shop.items.every(item => item.isChoose));};// 将购物车数据保存到 localStorageconst saveCartToLocalStorage = () => {localStorage.setItem('cartItems', JSON.stringify(state.cartItems));};// 从 localStorage 中恢复购物车数据const loadCartFromLocalStorage = () => {const savedCart = localStorage.getItem('cartItems');if (savedCart) {state.cartItems = JSON.parse(savedCart);}};return {state,setCartItems, // 暴露 setCartItems 方法selectedItems,selectedItemsCount,totalSelectedPrice,toggleItemChoose,changeItemQuantity,toggleAllChose,loadCartFromLocalStorage};
});

4、页面展示


<template><view class=""><view class="card"><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.image" 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 style="margin-bottom: 20rpx;font-size: 26rpx;color: #7d7e80;">{{item.type}}</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.num"@change="val => changeItemQuantity(item,item.iid, val.value)"min="1"></up-number-box></view></view></view></view></template></view></template></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 {storeToRefs} from "pinia";// 使用 Pinia storeconst cartStore = useCartStore();// 获取状态和操作const {state,selectedItemsCount,totalSelectedPrice,selectedItems} = storeToRefs(cartStore);const {toggleItemChoose,changeItemQuantity,toggleAllChose} = cartStore;onMounted(async () => {// 恢复购物车数据// 模拟 API 请求获取购物车数据const response = await fetchCartData();const groupedItems = [];response.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: "您还没有选择商品哦",});}}// 模拟 API 请求函数async function fetchCartData() {return [{id: 1,shopName: "三只松鼠旗舰店",isChoose: false,image: "https://gw.alicdn.com/imgextra/i3/2218288872763/O1CN01rN6Cn91WHVIflhWLg_!!2218288872763.jpg",title: "散装土鸡蛋  360枚 40斤",type: "40斤 正负25g",price: 29.9,num: 1},{id: 2,isChoose: false,shopName: "三只松鼠旗舰店",image: "https://gw.alicdn.com/imgextra/i1/2218288872763/O1CN01DipCdH1WHVIqTtCQR_!!0-item_pic.jpg",title: "散装土鸡蛋  360枚 40斤",type: "40斤 正负25g",price: 30,num: 1},{id: 2,isChoose: false,shopName: "三只耗子",image: "https://gw.alicdn.com/imgextra/i1/2218288872763/O1CN01DipCdH1WHVIqTtCQR_!!0-item_pic.jpg",title: "散装土鸡蛋  360枚 40斤",type: "40斤 正负25g",price: 29.9,num: 1}];}
</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>

http://www.ppmy.cn/news/1527462.html

相关文章

Qt_多元素控件

目录 1、认识多元素控件 2、QListWidget 2.1 使用QListWidget 3、QTableWidget 3.1 使用QListWidget 4、QTreeWidget 4.1 使用QTreeWidget 5、QGroupBox 5.1 使用QGroupBox 6、QTabWidget 6.1 使用QTabWidget 结语 前言&#xff1a; 在Qt中&#xff0c;控件之间…

4.qml单例模式

这里写目录标题 js文件单例模式qml文件单例模式 js文件单例模式 直接添加一个js文件到qml中 修改内容 TestA.qml import QtQuick 2.0 import QtQuick.Controls 2.12 import "./MyWork.js" as MWItem {Row{TextField {onEditingFinished: {MW.setA(text)}}Button…

第十八节:学习统一异常处理(自学Spring boot 3.x的第五天)

这节记录下如何通过AOP方式统一处理异常拦截。 第一步&#xff1a; 新建一个exception包&#xff0c;创建一个ExcetionHandler.java&#xff08;名字随意取&#xff09; package cn.wcyf.wcai.exception;import cn.wcyf.wcai.common.Result; import org.springframework.web…

mysql实用系列:日期格式化

在MySQL中&#xff0c;你可以使用DATE_FORMAT()函数来格式化日期。DATE_FORMAT() 函数通常用于格式化 DATETIME 或 TIMESTAMP类型的字段。这个函数允许你按照指定的格式来显示日期和时间。下面是一些常见的日期格式化的例子&#xff1a; 显示年-月-日&#xff1a; SELECT DATE_…

经典sql题(六)查找用户每月累积访问次数

使用聚合开窗查找用户每月累积访问次数&#xff0c;首先介绍一下使用 GROUP BY和开窗的区别 GROUP BY 行数变化&#xff1a;使用 GROUP BY 后&#xff0c;原始数据会按指定列进行分组&#xff0c;结果中每组只保留一行&#xff0c;因此行数通常减少。作用&#xff1a;适用于需…

java-在ANTLR中BaseListner的方法和词法规则的关系0.5.0

java-在ANTLR中BaseListner的方法和词法规则的关系0.5.0 环境介绍词法规则与类方法的对应关系ClassOrInterfaceModifierContext与词法对应关系参考 环境介绍 java.g4ideawindows10 词法规则与类方法的对应关系 随便找一个词法规则&#xff0c;如ClassOrInterfaceModifier&am…

linux--防火墙

linux防火墙 ubuntu 1&#xff0c; 关于ufw 查看防火墙&#xff1a; sudo ufw status 关闭防火墙&#xff1a; sudo ufw disable 开启&#xff1a; sudo ufw enable 2&#xff0c;firewalld 执行&#xff1a; systemctl status firewalld 出现&#xff1a; Unit fi…

RTC、ADC

RTC RTC&#xff08;Real-Time Clock&#xff09;是实时时钟模块&#xff0c;用于跟踪实际时间&#xff08;年、月、日、时、分、秒&#xff09;&#xff0c;即使在系统断电或处于低功耗模式下也能保持时间的准确性。 特点 时间和日期跟踪低功耗模式支持可编程闹钟和定时器备…