Uniapp + Vue3 + Vite +Uview + Pinia 实现提交订单以及支付功能(最新附源码保姆级)
- 1 效果展示
- 2 提交订单
- 2.1 cart.js
- 2.2 submit-order.vue
- 3、支付页面
-
1 效果展示
2 提交订单
2.1 cart.js
import {
defineStore
} from 'pinia';
import {
reactive,
computed
} from 'vue';
export const useCartStore = defineStore('cart', () => {
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);
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 = () => {
state.allChose = state.cartItems.every(shop =>
shop.items.every(item => item.isChoose)
);
};
const saveCartToLocalStorage = () => {
localStorage.setItem('cartItems', JSON.stringify(state.cartItems));
};
const loadCartFromLocalStorage = () => {
const savedCart = localStorage.getItem('cartItems');
if (savedCart) {
state.cartItems = JSON.parse(savedCart);
}
};
return {
state,
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";
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.value = false;
}
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;
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 {
}
.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";
const cartStore = useCartStore();
const {
totalSelectedPrice,
selectedItems
} = storeToRefs(cartStore);
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>