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

news2024/11/6 9:35:11

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.coloradmin.cn/o/2134774.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【最新华为OD机试E卷】报文响应时间(100分)多语言题解-(Python/C/JavaScript/Java/Cpp)

🍭 大家好这里是春秋招笔试突围 ,一枚热爱算法的程序员 💻 ACM金牌🏅️团队 | 大厂实习经历 | 多年算法竞赛经历 ✨ 本系列打算持续跟新华为OD-E/D卷的多语言AC题解 🧩 大部分包含 Python / C / Javascript / Java / Cpp 多语言代码 👏 感谢大家的订阅➕ 和 喜欢�…

mybatis获取参数的5种情况

Mybatis获取参数值的两种方式 mybatis获取参数值的方式有两种: ${} 和 #{} ${} 这个的本质就是字符串拼接 这个无法避免sql注入攻击 #{} 这个的本质就是占位符(尽量使用 #{} 的方式) 可以避免sql注入 mybatis获取参数值的情况 1.mapper接口方法的参数为单个字面量类型…

solidity-20-sendeth

发送ETH 这章的标题让我觉得奇怪&#xff0c;因为先前我也发送ETH&#xff0c;如上一篇提到的recieve和fallback函数。 重现了教程中的代码 // SPDX-License-Identifier: MIT pragma solidity ^0.8.21;contract sendeth{// 这个事件是为了打log,记录收到的eth和剩余的gas fee…

echarts中tooptips提示框超出了怎么解决

我们在制作echarts表格时&#xff0c;有时候会遇到提示框内容较多&#xff0c;会让提示框超出&#xff0c;展示不全数据&#xff0c;如下&#xff1a; 这种情况下需要在tooltips下增加一些属性&#xff1a; 1.confine: true&#xff1a;这个配置的作用是让提示框&#xff08;t…

Docker笔记-容器数据卷

Docker笔记-容器数据卷 docker的理念将运行的环境打包形成容器运行&#xff0c;运行可以伴随容器&#xff0c;但是我们对数据的要求是希望持久化&#xff0c;容器 之间可以共享数据&#xff0c;Docker容器产生的数据&#xff0c;如果不通过docker commit生成新的镜像&#xf…

大数据新视界 --大数据大厂之数据挖掘入门:用 R 语言开启数据宝藏的探索之旅

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

类型转换等 面试真题

题目1 请问哪个结果为NaN A. 123null B. 123‘1’ C. 123/0 D. 123undefined 在这四个表达式中&#xff0c;只有D. 123 undefined 的结果是 NaN&#xff0c;原因如下&#xff1a; A. 123 null 结果是&#xff1a;123原因&#xff1a;null 在数值运算中会被自动转换为 0&a…

mac上什么压缩软件没有广告,苹果电脑解压软件BetterZip有广告吗

mac上有很多压缩软件&#xff0c;可以帮助用户压缩或解压各种格式的文件&#xff0c;如zip、rar、7z等。但是&#xff0c;有些压缩软件会在使用过程中弹出广告&#xff0c;影响用户的体验和效率。那么&#xff0c;mac上什么压缩软件没有广告呢&#xff1f;苹果电脑解压软件Bett…

一步步教你利用大模型开发个性化AI应用,告别‘人工智障’!

为了回答这个问题&#xff0c;我用说人话的方式拿gpts创建了一个“我”&#xff0c;然后让她来回答这个问题。&#xff08;确认过眼神&#xff0c;我是懂套娃的&#xff09; 接下来我会先展示下整个定制过程&#xff1b;然后我们一起看一下她能把题答到什么程度&#xff1b;最后…

UnrealEngine 打包Android平台应用

虚幻引擎 支持将项目发布到 安卓&#xff08;Android&#xff09; 移动设备上&#xff0c;并且提供了若干功能帮你将项目发布到 谷歌游戏商店。本节包含了如何设置Android开发环境、如何使用Android功能和服务、以及如何为发布游戏做准备相关的指南。 当前SDK要求 当前UE版本…

JavaSE篇之内部类和图书系统

1.内部类(类中类) 在Java中&#xff0c;将一个类定义在另一个类内部&#xff0c;前者称为内部类&#xff0c;后者称为外部类。 注意事项&#xff1a; 1. 1.静态内部类&#xff08;被static修饰的内部类&#xff09; 1.在静态内部类的方法中不能直接引用外部类的成员变量&…

中国农业银行——轻量式云原生应用平台(轻云平台)

2021年10月&#xff0c;中国人民银行等联合发布了《关于规范金融业开源技术应用与发展的意见》&#xff08;银办发〔2021〕146 号&#xff09;&#xff0c;规范金融机构合理应用开源技术&#xff0c;提高应用水平和自主可控能力&#xff0c;促进开源技术健康可持续发展。前期&a…

幻灯片放映过程中如何调出激光笔

1、第一步先打开制作好的幻灯片 2、进行幻灯片放映 3、看到上图最下面一行&#xff0c;减号左方的小杯进入幻灯片播放 4、幻灯片下方有个放映&#x1f58a;&#xff0c;点击一下 5、选择激光笔就好啦

基于Java的建筑节能监测系统+公共建筑能耗监测系统+建筑能耗监测系统+节能监测系统+能源管理系统

建筑节能监测系统公共建筑能耗监测系统建筑能耗监测系统节能监测系统能耗监测建筑能耗监测能耗分析能耗管理能耗预测能耗监控能耗监测平台建筑能耗 介绍 建筑节能监测系统是基于计算机网络、物联网、大数据和数据可视化等多种技术融合形成的一套节能监测系统。 系统实现了对建…

el-table表格的展开行,初始化的时候展开哪一行+设置点击行可展开功能

效果&#xff1a; 表格展开行官网使用&#xff1a; 通过设置 type"expand" 和 Scoped slot 可以开启展开行功能&#xff0c;el-table-column 的模板会被渲染成为展开行的内容&#xff0c;展开行可访问的属性与使用自定义列模板时的 Scoped slot 相同。 但是这种方法…

开源 TTS 模型「Fish Speech」1.4 发布;GameGen-O :生成开放世界游戏视频模型丨 RTE 开发者日报

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。 我们的社区编辑团队会整理分享 RTE&#xff08;Real-Time Engagement&#xff09; 领域内「有话题的新闻」、「有态度的观点」、「有意思的数据」、「有思考的文章」、…

计算机网络:概述 - 性能指标

目录 一. 速率 二. 带宽 三. 吞吐量 四. 时延 五. 时延带宽积 六. 往返时间RTT 七. 利用率 八. 丢包率 此博客介绍计算机网络中的性能指标&#xff0c;性能指标从不同的角度来度量计算机网络的性能。下面介绍几个常用的性能指标&#xff1a; 一. 速率…

【TabBar嵌套Navigation案例-cell重用 Objective-C语言】

一、我们来说这个cell重用(重复使用)的问题啊 1.我们这个比分直播推送页面, 这个里边呢,现在这个cell,涉及到两个样式,上面呢,是Default的,下面呢,是Value1的,然后,我们在这个里边啊,我们每一组就一个cell啊,然后呢,我把这个组,多给它复制几份儿,现在是三个组…

OpenSSH后门从入门到应急响应与加固

目录 1. Openssh与后门介绍 1.1 Openssh介绍 1.2 Openssh后门介绍 2. 实战演练 2.1 查看版本,注意V是大写的 2.2 下载SSH配置文件 2.3 安装 2.4、修改后⻔密码和⽂件记录 2.5、修改版本号为原本的版本号(伪装openssh) 2.6、修改/etc/ssh中的key 2.7、安装所需环境与…

【黑神话】无脑过大头怪(幽魂)教程,手残也能打过关!

在《黑神话悟空》这款扣人心弦的动作角色扮演游戏中&#xff0c;玩家将面对众多考验操作与策略的Boss战。其中&#xff0c;大头幽魂作为玩家早期就会遇到的挑战之一&#xff0c;其独特的战斗机制和技能组合&#xff0c;对新手玩家而言无疑是一次不小的考验。今天&#xff0c;就…