当前位置: 首页 > article >正文

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

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

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

1、效果展示


在这里插入图片描述

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.filter(item => item.isChoose).reduce((count, item) => count + item.num, 0);
  });

  // 计算已选中商品的总价格
  const totalSelectedPrice = computed(() => {
    return state.cartItems.filter(item => item.isChoose).reduce((total, item) => total + item.price * item.num, 0);
  });

  // 切换商品的选中状态
  const toggleItemChoose = (item) => {
    const cartItem = state.cartItems.find(cartItem => cartItem.id === item.id);
    if (cartItem) {
      cartItem.isChoose = !cartItem.isChoose;
    }
    updateAllChoseStatus(); // 每次切换选中状态后更新全选状态
    saveCartToLocalStorage();
  };

  // 修改商品数量
  const changeItemQuantity = (item, quantity) => {
    const cartItem = state.cartItems.find(cartItem => cartItem.id === item.id);
    if (cartItem) {
      cartItem.num = quantity;
    }
    saveCartToLocalStorage();
  };

  // 切换全选状态
  const toggleAllChose = () => {
    state.allChose = !state.allChose;
    state.cartItems.forEach(item => {
      item.isChoose = state.allChose;
    });
    saveCartToLocalStorage();
  };

  // 更新全选状态
  const updateAllChoseStatus = () => {
    state.allChose = state.cartItems.every(item => item.isChoose);
  };

  // 将购物车数据保存到 localStorage
  const 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 方法
    selectedItemsCount,
    totalSelectedPrice,
    toggleItemChoose,
    changeItemQuantity,
    toggleAllChose,
    loadCartFromLocalStorage
  };
});

4、页面展示


<template>
	<view class="">
		<view class="card">
			<template v-for="(item, index) in state.cartItems" :key="index">
				<view class="cart-data card-shadow">
					<view>
						<up-checkbox :customStyle="{marginBottom: '8px'}" usedAlone v-model:checked="item.isChoose"
							@change="toggleItemChoose(item)">
						</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;">{{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, val.value)"
									min="1"></up-number-box>
							</view>
						</view>
					</view>
				</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;"></up-button>
					</view>
				</view>

			</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 store
	const cartStore = useCartStore();
	// 获取状态和操作
	const {
		state,
		selectedItemsCount,
		totalSelectedPrice,
	} = storeToRefs(cartStore);

	const {
		toggleItemChoose,
		changeItemQuantity,
		toggleAllChose
	} = cartStore;

	onMounted(async () => {
		// 模拟 API 请求获取购物车数据
		const response = await fetchCartData();
		cartStore.setCartItems(response);

	});

	// 模拟 API 请求函数
	async function fetchCartData() {
		return [{
				id: 1,
				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,
				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;
		align-items: center;

		.cart-image {
			flex: 1;
		}

		.cart-right {
			display: flex;
			flex-direction: column;
		}
	}
</style>

http://www.kler.cn/a/300289.html

相关文章:

  • Java自定义多队列线程池
  • STM32 FreeROTS Tickless低功耗模式
  • Python绘制数据地图-MovingPandas
  • 阿里云 Serverless 助力盟主直播:高并发下的稳定性和成本优化
  • 2024年第十五届蓝桥杯青少组国赛(c++)真题—快速分解质因数
  • vue2 - Day05 - VueX
  • Self Refine技术测评:利用Self Refine提高LLM的生成质量
  • 手机如何执行Python
  • 比较stl库的ostringstream与Qt的QString::arg(),QString::number()
  • Transformer理论阶段
  • Unet改进31:添加Star_Block(2024最新改进方法)|紧凑的网络结构和高效的运算
  • 全国各地身份证号开头6位数字及地区对照表
  • 代码随想录Day39|322. 零钱兑换、279.完全平方数、139.单词拆分
  • Kubectl:Kubernetes 的强大命令行工具
  • C++的智能指针
  • 通过ASCII码打印HelloWorld(花式打印HelloWorld)
  • 应用宝自动下载安装
  • 如何下载和安装 Notepad++
  • 【数据库】MySQL表的Updata(更新)和Delete(删除)操作
  • Spring 框架——@Retryable 注解与 @Recover 注解
  • 【delphi】判断多显示器下,程序在那个显示器中
  • C++day7
  • python 实现gaussian高斯算法
  • Vuex快速入门
  • mysql等相关面试题
  • Sentinel实时监控不展示问题