shop-web/src/pages/product/components/checkout.vue

361 lines
8.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup lang="ts">
import { useCartStore } from "@/pinia/stores/cart"
import { useWxStore } from "@/pinia/stores/wx"
import { useAb98UserStore } from '@/pinia/stores/ab98-user'
import { storeToRefs } from "pinia"
import { showConfirmDialog } from "vant"
import { submitOrderApi } from "@/common/apis/shop"
import type { SubmitOrderRequestData, WxJsApiPreCreateResponse } from "@/common/apis/shop/type"
import { useRouter } from 'vue-router'
const router = useRouter()
const cartStore = useCartStore()
const { cartItems, totalPrice } = storeToRefs(cartStore)
const wxStore = useWxStore()
const { openid, balance, corpidLogin, userid: qyUserid } = storeToRefs(wxStore)
const ab98UserStore = useAb98UserStore()
const { tel, userid: ab98Userid } = storeToRefs(ab98UserStore)
const selectedPayment = ref<'wechat' | 'balance'>('wechat')
const contact = ref("")
const remark = ref("")
const submitting = ref(false)
function callWxJsApi(paymentInfo: WxJsApiPreCreateResponse) {
return new Promise((resolve, reject) => {
function onBridgeReady() {
(window as any).WeixinJSBridge.invoke(
'getBrandWCPayRequest',
{
appId: paymentInfo.appId,
timeStamp: paymentInfo.timeStamp,
nonceStr: paymentInfo.nonceStr,
package: paymentInfo.package.startsWith('prepay_id=')
? paymentInfo.package
: `prepay_id=${paymentInfo.package}`,
signType: paymentInfo.signType,
paySign: paymentInfo.paySign
},
(res: { err_msg: string }) => {
if (res.err_msg === "get_brand_wcpay_request:ok") {
resolve(true);
} else {
reject(new Error('支付未完成'));
}
}
);
}
if (typeof (window as any).WeixinJSBridge === 'undefined') {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else {
onBridgeReady();
}
});
}
async function handleSubmit() {
if (!cartItems.value.length) {
return showConfirmDialog({
title: "提示",
message: "请先选择商品后再结算"
})
}
if (!openid.value) {
return showConfirmDialog({
title: "登录提示",
message: "请从微信中打开"
})
}
// 在handleSubmit函数开头添加验证
if (!/^1[3-9]\d{9}$/.test(tel.value)) {
return showConfirmDialog({
title: "格式错误",
message: "请输入有效的手机号码"
});
}
submitting.value = true
try {
// 判断用户类型:
// 2 - 企业微信用户
// 1 - 汇邦云用户
// 0 - 外部用户
const isInternal = corpidLogin.value ? 2 : ab98Userid.value ? 1 : 0;
console.log('corpidLogin', corpidLogin.value)
console.log('qyUserid', qyUserid.value)
console.log("isInternal", isInternal)
const requestData: SubmitOrderRequestData = {
openid: openid.value,
userid: wxStore.userid,
corpid: wxStore.corpid,
goodsList: cartItems.value.map(item => ({
goodsId: item.product.id,
quantity: item.quantity
})),
paymentType: selectedPayment.value,
mobile: tel.value,
qyUserid: isInternal === 2 ? qyUserid.value : ab98Userid.value,
isInternal: isInternal
}
const { code, data } = await submitOrderApi(requestData)
if (code !== 0) {
throw new Error("订单提交失败")
}
if (selectedPayment.value === 'wechat') {
if (data.paymentInfo) {
await callWxJsApi(data.paymentInfo);
}
} else {
// 余额支付成功处理
wxStore.setBalance(data.newBalance || 0);
try {
await showConfirmDialog({
title: "支付成功",
message: `余额支付成功,剩余余额:¥${data.newBalance?.toFixed(2)}`
})
} catch (error) {
}
}
router.push({
path: '/order-success',
query: { orderId: data.orderId }
});
cartStore.clearCart()
} catch (error) {
if (error !== 'user_cancel') {
showConfirmDialog({
title: "支付失败",
message: error instanceof Error ? error.message : "支付流程中断"
});
}
} finally {
submitting.value = false
}
}
</script>
<template>
<div class="checkout-container">
<van-nav-bar title="结算页面" left-text="返回" left-arrow fixed @click-left="() => $router.go(-1)" />
<div class="content-wrapper">
<!-- 原有商品列表等代码保持不动 -->
<van-cell-group class="product-list">
<van-cell v-for="item in cartItems" :key="item.product.id" class="product-item">
<template #icon>
<van-image :src="item.product.image" width="60" height="60" class="product-image" />
</template>
<div class="product-info">
<div class="product-name van-ellipsis">
{{ item.product.name }}
</div>
<div class="price-row">
<span class="product-price">
¥{{ item.product.price.toFixed(2) }}
</span>
<span class="quantity">
×{{ item.quantity }}
</span>
</div>
</div>
</van-cell>
<van-cell>
<van-field
v-model="tel"
label="手机号"
placeholder="请输入联系电话"
required
class="tel-input"
/>
</van-cell>
</van-cell-group>
<van-cell-group class="contact-form">
<van-cell v-if="!corpidLogin"
:class="['payment-option', { selected: selectedPayment === 'wechat' }]"
@click="selectedPayment = 'wechat'"
>
<van-icon name="wechat" class="method-icon" />
<span class="method-label">微信支付</span>
<div class="empty"></div>
<van-icon v-if="selectedPayment === 'wechat'" name="success" class="check-icon" />
</van-cell>
<van-cell
:class="['payment-option', { selected: selectedPayment === 'balance', disabled: balance < totalPrice }]"
@click="selectedPayment = 'balance'"
>
<van-icon name="balance-o" class="method-icon" />
<span class="method-label">
余额支付
<span class="balance-amount">(当前:¥{{ balance.toFixed(2) }}</span>
</span>
<div class="empty"></div>
<van-icon v-if="selectedPayment === 'balance'" name="success" class="check-icon" />
</van-cell>
</van-cell-group>
<!-- 提交订单栏 -->
<div class="submit-bar">
<div class="total-price">
合计:¥{{ totalPrice.toFixed(2) }}
</div>
<van-button type="primary" size="large" :loading="submitting" loading-text="提交中..." @click="handleSubmit">
提交订单
</van-button>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.van-nav-bar {
position: fixed;
top: 0;
width: 100%;
z-index: 999;
background: #fff;
border-bottom: 1px solid #ebedf0;
}
.content-wrapper {
padding-top: 46px;
/* 导航栏高度 */
}
.checkout-container {
padding: 12px 16px 80px;
}
.product-list {
margin-bottom: 20px;
}
.product-item {
align-items: flex-start;
}
.product-image {
margin-right: 12px;
border-radius: 4px;
}
.product-info {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 60px;
}
.price-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.product-price {
color: #e95d5d;
font-weight: bold;
font-size: 14px;
}
.quantity {
color: #666;
font-size: 13px;
}
.submit-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 10px 16px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
}
.total-price {
font-size: 16px;
color: #e95d5d;
font-weight: bold;
}
.payment-option {
display: flex;
align-items: center;
justify-content: flex-start;
margin: 8px 0;
border: 1px solid #ebedf0;
border-radius: 8px;
transition: all 0.2s;
:deep(.van-cell__value) {
display: flex;
}
}
.check-icon {
font-size: 18px;
}
.payment-option.selected {
border-color: #07c160;
background-color: #f6ffed;
}
.payment-option.disabled {
opacity: 0.6;
filter: grayscale(1);
pointer-events: none;
}
.method-icon {
vertical-align: middle;
margin-right: 12px;
font-size: 24px;
}
.method-label {
vertical-align: middle;
font-size: 15px;
}
.empty {
flex-grow: 1;
margin-right: 20px;
}
.balance-amount {
font-size: 13px;
color: #666;
}
.tel-input {
background-color: #f5f5f5;
border-radius: 4px;
padding: 8px 12px;
}
.check-icon {
margin-left: auto;
color: #07c160;
font-size: 18px;
}
</style>