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

Uniapp + Vue3 + Vite +Uview + Pinia 实现提交订单以及支付功能(最新附源码保姆级)

Uniapp + Vue3 + Vite +Uview + Pinia 实现提交订单以及支付功能(最新附源码保姆级)

  • 1 效果展示
  • 2 提交订单
    • 2.1 cart.js
    • 2.2 submit-order.vue
  • 3、支付页面
    • order-pay.vue

1 效果展示


在这里插入图片描述

2 提交订单

2.1 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(() => {
		const groupedSelectedItems = [];
		state.cartItems.forEach(shop => {
			const selectedShopItems = shop.items.filter(item => item.isChoose);
			if (selectedShopItems.length > 0) {
				// 查找是否已经存在相同店铺名的对象
				const groupedShop = groupedSelectedItems.find(group => group.shopName === shop
					.shopName);
				if (groupedShop) {
					// 如果存在,直接将选中的商品添加到该店铺的商品列表中
					groupedShop.items.push(...selectedShopItems);
					// 检查是否已经有 notes 字段,如果没有则添加
					if (!groupedShop.hasOwnProperty('notes')) {
						groupedShop.notes = "";
					}
				} else {
					// 如果不存在,创建一个新的店铺对象,并将选中的商品添加进去
					groupedSelectedItems.push({
						shopName: shop.shopName,
						items: selectedShopItems,
						notes: ""
					});
				}
			}
		});
		return groupedSelectedItems;
	});

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

	// 更新全选状态
	const updateAllChoseStatus = () => {
		// 遍历所有店铺的所有商品,如果有一个未选中,则全选状态为 false
		state.allChose = state.cartItems.every(shop =>
			shop.items.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 方法
		selectedItems,
		selectedItemsCount,
		totalSelectedPrice,
		toggleItemChoose,
		changeItemQuantity,
		toggleAllChose,
		loadCartFromLocalStorage
	};
});

2.2 submit-order.vue

<template>
	<view class="">
		<AddressVue></AddressVue>
		<view class="card">
			<template v-for="(info, j) in selectedItems" :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;width: 100%;justify-content: space-around;">
							<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 class="notes" @click="writeNoteFun(j)">
						<view style="flex: 1;">订单备注</view>
						<view style="display: flex;color: #7d7e80;width: 400rpx;justify-content: end;" >
							<up-text :text="info.notes.length==0?'无备注':info.notes" :lines="1"></up-text>
							<up-icon name="arrow-right"></up-icon>
						</view>
					</view>
					<!-- 弹出层输入备注 -->
					<up-popup :show="show" mode="bottom" @close="close" zIndex="9999999" round="20rpx">
						<view class="" style="text-align: center;height: 60rpx;line-height: 60rpx;margin-top: 20rpx;">
							订单备注
						</view>
						<view  style="padding: 20rpx 40rpx;">
							<up-textarea v-model="selectedItems[noteIndex].notes"  placeholder="请输入内容" count focus
								maxlength="200" height="240rpx"></up-textarea>
						</view>
						<view class="" style="display: flex;padding: 20rpx 40rpx;margin-top: 100rpx;">
							<up-button text="确定" type="warning" shape="circle" @click="enterNoteInputFun()"></up-button>
						</view>
					</up-popup>
				</view>
			</template>
		</view>
		<view class="" style="height: 150rpx;">

		</view>
		<view class="foot card">
			<view class="card-connect">
				<view class="" style="display: flex; align-items: center;">
					<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="toPayFun"></up-button>
					</view>
				</view>
				<up-toast ref="uToastRef"></up-toast>
			</view>
		</view>
	</view>
</template>

<script setup>
	import {
		ref,
		onMounted
	} from 'vue';
	import AddressVue from '@/pages/components/User/Address.vue';
	import {
		useCartStore
	} from '@/pages/store/cart/cart.js'

	import {
		storeToRefs
	} from "pinia";
	// 使用 Pinia store
	const cartStore = useCartStore();
	// 获取状态和操作
	// 获取状态和操作
	const {
		state,
		selectedItemsCount,
		totalSelectedPrice,
		selectedItems
	} = storeToRefs(cartStore);
	const {
		toggleItemChoose,
		changeItemQuantity,
		toggleAllChose
	} = cartStore;
	// 创建响应式数据  
	const show = ref(false);
	const noteIndex = ref(0);
	const writeNoteFun = (index) => {
		// 记录备注数据的下标
		noteIndex.value = index;
		show.value = true;
	}
	const close = () => {
		// 关闭逻辑,设置 show 为 false  
		show.value = false;
		// console.log('close');  
	}
	const enterNoteInputFun = () => {
		show.value = false;
	}
	// 支付调起
	const toPayFun = () => {
		uni.navigateTo({
			url: "/pages/src/home/order-pay/order-pay"
		})
	}
	onMounted(() => {

	});
</script>

<style lang="scss" scoped>
	.notes {
		padding-top: 20rpx;
		display: flex;
		width: 100%;
		justify-content: space-between;
	}

	.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>

3、支付页面

order-pay.vue


<template>
	<view>
		<view class="" style="display: flex;">
			<up-steps current="1" style="display: flex;">
				<up-steps-item title="提交订单成功" :desc="nowDate"></up-steps-item>
				<up-steps-item title="选择支付方式" desc=""></up-steps-item>
				<up-steps-item title="卖家确认发货" desc="24小时内"></up-steps-item>
			</up-steps>
		</view>
		<view class="card">
			<view class="" style="text-align: center;padding-top: 40rpx;">
				订单金额
			</view>
			<view class="" style="display: flex;justify-content: center;padding: 60rpx 0 20rpx 0;">
				<up-text mode="price" :text="totalSelectedPrice" color="red" size="40"></up-text>
			</view>
			<view class="" style="text-align: center;padding-top: 20rpx;">
				<text>订单提交成功,请在10分钟内完成支付</text>
			</view>
			<view class="" style="height: 100rpx;">

			</view>
			<up-divider text="请您选择付款方式"></up-divider>

			<view class="">
				<radio-group @change="radioChange">
					<view class="" style="width: 100%;">
						<view class=""
							style="display: flex;align-items: center;width: 100%;justify-content: space-between;">
							<up-icon name="weixin-circle-fill" size="40" color="green"></up-icon>
							<text style="padding-left: 20rpx;flex: 1;">微信支付</text>
							<radio :checked="true" value="1"></radio>
						</view>
						<view class=""
							style="display: flex;align-items: center;width: 100%;justify-content: space-between;margin-top: 20rpx;">
							<up-icon name="zhifubao-circle-fill" size="40" color="blue"></up-icon>
							<text style="padding-left: 20rpx;flex: 1;">支付宝支付</text>
							<radio style="right: 0;" value="2"></radio>
						</view>
					</view>
				</radio-group>
			</view>
		</view>
		<view class="" style="display: flex;margin-top: 40rpx;padding: 0 20rpx;">
			<up-button type="error" text="确认支付" shape="circle" @click="toPayFun"></up-button>
		</view>
	</view>
</template>

<script setup>
	import {
		timeFormat
	} from 'uview-plus';
	import {
		reactive,
		ref
	} from 'vue';
	const nowDate = timeFormat(new Date().getTime(), 'hh:MM:ss');
	import {
		useCartStore
	} from '@/pages/store/cart/cart.js'

	import {
		storeToRefs
	} from "pinia";
	// 使用 Pinia store
	const cartStore = useCartStore();

	// 获取状态和操作
	const {
		totalSelectedPrice,
		selectedItems
	} = storeToRefs(cartStore);

	// up-radio-group的v-model绑定的值如果设置为某个radio的name,就会被默认选中
	const radiovalue = ref();
	const radioChange = (e) => {
		radiovalue.value = e.detail.value;
	};
	
	const toPayFun = () =>{
		// 调起支付
	}
</script>

<style lang="less" scoped>
	.card {
		margin: 20rpx;
		padding: 20rpx;
		background-color: #FFF;
		border-radius: 20rpx;
	}
</style>

http://www.kler.cn/news/305278.html

相关文章:

  • MATLAB中who的用法
  • flink增量检查点启动恢复的时间是很久的,业务上不能接受,怎么处理
  • MySQL索引-聚簇索引和非聚簇索引
  • 【Python机器学习】循环神经网络(RNN)——传递数据并训练
  • flask中安全策略简要说明
  • 景联文科技:专业扫地机器人数据采集标注服务
  • C/C++动态库函数导出 windows
  • Python数据分析 Pandas库-初步认识
  • Spring Boot-版本兼容性问题
  • 用 SQL 写的俄罗斯方块游戏「GitHub 热点速览」
  • Nginx:高性能的Web服务器与反向代理
  • 矩阵直播换IP:如何使用代理IP提升直播效果
  • java enum code-label模式的使用方法
  • MATLAB算法实战应用案例精讲-【人工智能】数据血缘分析(概念篇)
  • 计算机视觉学习路线(纯纯小白)
  • idea开发Java程序的步骤及设置
  • Typescript 的类型断言
  • 召回02 Swing 召回通道
  • C#命令行参数解析库System.CommandLine介绍
  • 分享一些智慧农业数据集
  • springboot医院预约挂号系统 ---附源码73444
  • 【计算机网络 - 基础问题】每日 3 题(六)
  • leetcode01——27. 移除元素(双指针)、977. 有序数组的平方(双指针)、209. 长度最小的子数组(双指针/滑动窗口)
  • 准备好启程了:Nuro将授权其自动驾驶系统
  • 北斗盒子:海上安全的智能守护者——落水报警应用案例
  • GO Govaluate
  • 38. 如何在Spring Boot项目中集成MyBatis-Plus?
  • 信通院发布首个《大模型媒体生产与处理》标准,阿里云智能媒体服务作为业界首家“卓越级”通过
  • redis 中缓存 百万级别表的查询数据 出错:Query execution was interrupted
  • 添加选择登录ssh终端