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

401 lines
10 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, name: qyName } = storeToRefs(wxStore)
const ab98UserStore = useAb98UserStore()
const { tel, userid: ab98Userid, name } = storeToRefs(ab98UserStore)
const selectedPayment = ref<'wechat' | 'balance' | 'approval'>('wechat');
const contact = ref("");
const applyRemark = ref("");
const submitting = ref(false);
const isApproval = computed(() => {
return cartItems.value.some(item => item.product.belongType == 1);
});
watch(corpidLogin, (newValue) => {
if (isApproval.value) {
selectedPayment.value = 'approval';
} else {
if (newValue) {
selectedPayment.value = 'balance';
} else {
selectedPayment.value = 'wechat';
}
}
}, { immediate: true });
watch(isApproval, (newValue) => {
if (newValue) {
selectedPayment.value = 'approval';
} else {
if (corpidLogin.value) {
selectedPayment.value = 'balance';
} else {
selectedPayment.value = 'wechat';
}
}
})
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,
cellId: item.product.cellId,
})),
paymentType: selectedPayment.value,
mobile: tel.value,
name: isInternal === 2 ? qyName.value : name.value,
applyRemark: applyRemark.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 if (selectedPayment.value === 'balance') {
// 余额支付成功处理
wxStore.setBalance(data.newBalance || 0);
try {
await showConfirmDialog({
title: "支付成功",
message: `余额支付成功,剩余余额:¥${data.newBalance?.toFixed(2)}`
})
} catch (error) {
}
} else if (selectedPayment.value === 'approval') {
// 审批支付成功处理
try {
await showConfirmDialog({
title: "提交领用申请成功",
message: `请等待管理员审批`
})
} 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">
<template v-for="item in cartItems" :key="item.product.id">
<van-cell 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 v-if="item.product.usageInstruction" class="usage-instruction">
<div class="instruction-content">
使用说明:{{ item.product.usageInstruction }}
</div>
</van-cell>
</template>
<van-cell>
<van-field v-model="tel" label="手机号" placeholder="请输入联系电话" required class="tel-input" />
</van-cell>
</van-cell-group>
<!-- <van-cell-group v-if="isApproval">
<van-field v-model="applyRemark" label="领用说明" type="textarea" rows="2" autosize />
</van-cell-group> -->
<van-cell-group v-if="!isApproval" 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 v-if="balance && balance > 0"
: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>