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

485 lines
14 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';
import { useProductStore } from "@/pinia/stores/product";
import { useRentingCabinetStore } from "@/pinia/stores/rentingCabinet"; // <<<<<< 新增:引入 rentingCabinet store
// 导入支付方式映射
import { paymentMethodOptions, modeToPaymentMethodMap } from "@/common/utils/maps/payment";
const router = useRouter();
const cartStore = useCartStore();
const { cartItems, totalPrice } = storeToRefs(cartStore);
// <<<<<< 新增:引入 rentingCabinet store 数据
const rentingCabinetStore = useRentingCabinetStore();
const { rentingCartItems, rentingCartTotalPrice } = storeToRefs(rentingCabinetStore);
const wxStore = useWxStore();
const { openid, balance, corpidLogin, userid: qyUserid, name: qyName } = storeToRefs(wxStore);
const ab98UserStore = useAb98UserStore();
const { tel, userid: ab98Userid, name } = storeToRefs(ab98UserStore);
const productStore = useProductStore();
// <<<<<< 新增:判断当前是否为租用模式 (mode 为 3)
const isRentingMode = computed(() => productStore.selectedShop?.mode === 3);
const currentTotalPrice = computed(() => {
return isRentingMode.value ? rentingCartTotalPrice.value : totalPrice.value;
});
// 根据店铺模式计算支持的支付方式
const supportedPayments = computed(() => {
const shopMode = productStore.selectedShop?.mode || 0;
const allowedValues = modeToPaymentMethodMap[shopMode] || [];
// 筛选条件:
// 1. 支付方式值必须在店铺模式允许的列表中
// 2. 微信支付(值为0)仅当非企业微信登录时才显示
return paymentMethodOptions.filter(option => allowedValues.includes(option.value)
&& (option.value !== 0 || !corpidLogin.value));
});
// 初始化选中的支付方式为第一个支持的选项
const selectedPayment = ref<string>(supportedPayments.value[0]?.value.toString() || '0');
const applyRemark = ref("");
const submitting = ref(false);
// <<<<<< 修改isApproval 根据 currentItems 来判断
const isApproval = computed(() => {
// 对于普通商品,判断是否需要审批 (belongType == 1)
// 对于租用机柜,通常没有审批概念,这里默认不认为是审批模式
if (isRentingMode.value) {
return false; // 租用模式下通常不走审批流程
} else {
return cartItems.value.some(item => item.product.belongType == 1);
}
});
// 支付方式value到类型的映射
const paymentValueToType: Record<string, 'wechat' | 'balance' | 'approval'> = {
'0': 'wechat',
'1': 'balance',
'2': 'approval',
'3': 'balance'
};
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 (isRentingMode.value) {
if (!rentingCartItems.value.length) {
return showConfirmDialog({
title: "提示",
message: `请先选择租用商品后再结算`
});
}
} else {
if (!cartItems.value.length) {
return showConfirmDialog({
title: "提示",
message: `请先选择商品后再结算`
});
}
}
// 检查是否有可用支付方式
if (supportedPayments.value.length === 0) {
return showConfirmDialog({
title: "提示",
message: "没有支持的支付方法,请从微信进入智借还"
});
}
if (!openid.value && !wxStore.isFakeQyLogin) {
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)
// <<<<<< 修改:根据模式组织 goodsList
let goodsListToSend;
if (isRentingMode.value) {
goodsListToSend = rentingCartItems.value.map(item => ({
quantity: item.quantity,
cellId: item.cabinetCell.cellId, // 租用模式下cellId
mode: productStore.selectedShop?.mode || 0, // 默认为 0
// 注意:后端可能需要额外的字段来区分是商品订单还是租用订单,
// 或者根据 goodsId/cellId 的性质来判断。
// 如果后端接收的是 price可能还需要加上price: item.cabinetCell.cellPrice,
// 这里暂时不加price依赖后端从 goodsId 查找价格。
}));
} else {
goodsListToSend = cartItems.value.map(item => ({
goodsId: item.product.id,
quantity: item.quantity,
cellId: item.product.cellId,
mode: productStore.selectedShop?.mode || 0, // 默认为 0
}));
}
const requestData: SubmitOrderRequestData = {
openid: openid.value,
userid: wxStore.userid,
ab98UserId: wxStore.ab98User?.ab98UserId || 0,
corpid: wxStore.corpid,
goodsList: goodsListToSend, // <<<<<< 使用动态生成的 goodsListToSend
// 根据选中的支付方式value获取对应的类型
paymentType: paymentValueToType[selectedPayment.value] || 'wechat',
mobile: tel.value,
name: isInternal === 2 ? qyName.value : name.value,
applyRemark: applyRemark.value,
qyUserid: wxStore.userid,
mode: productStore.selectedShop?.mode || 0, // 默认为 0
isInternal: isInternal
}
const { code, data } = await submitOrderApi(requestData);
if (code !== 0) {
throw new Error("订单提交失败")
}
if (selectedPayment.value == '0') { // 微信支付
if (data.paymentInfo) {
await callWxJsApi(data.paymentInfo);
}
} else if (selectedPayment.value == '1' || selectedPayment.value == '3') { // 借呗支付 或 余额支付 (通常两者逻辑相同)
wxStore.setBalance(data.newBalance || 0);
try {
await showConfirmDialog({
title: "支付成功",
message: `${selectedPayment.value === '1' ? '借呗' : '余额'}支付成功,剩余余额:¥${data.newBalance?.toFixed(2)}`
})
} catch (error) { }
} else if (selectedPayment.value == '2') { // 要呗支付 (审批模式)
try {
await showConfirmDialog({
title: "提交领用申请成功",
message: `请等待管理员审批`
})
} catch (error) { }
} else { // 新增支付方式
// 处理逻辑
}
router.push({
path: '/order-success',
query: { orderId: data.orderId }
});
// <<<<<< 修改:根据模式清空对应的购物车
if (isRentingMode.value) {
rentingCabinetStore.clearRentingCart();
} else {
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">
<!-- 根据 isRentingMode 动态渲染列表 -->
<van-cell-group class="product-list">
<!-- <<<<<< 修改:根据 isRentingMode 渲染不同商品列表 -->
<template v-if="isRentingMode">
<van-cell v-for="item in rentingCartItems" :key="item.cabinetCell.cellId" class="product-item">
<template #icon>
<!-- 租用机柜可以放一个默认图片或特定图标 -->
<van-image src="/img/product-image.svg" width="60" height="60" class="product-image" />
</template>
<div class="product-info">
<div class="product-name van-ellipsis">
{{ `格口号 ${item.cabinetCell.cellNo}` }}
</div>
<div class="price-row">
<span class="product-price">
¥{{ item.cabinetCell.cellPrice?.toFixed(2) }}
</span>
<span class="quantity">
×{{ item.quantity }}
</span>
</div>
</div>
</van-cell>
</template>
<template v-else>
<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>
</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> -->
<!-- 支付方式 -->
<!-- <<<<<< 修改isApproval -->
<van-cell-group v-if="!isApproval" class="contact-form">
<van-cell v-for="method in supportedPayments" :key="method.value"
:class="['payment-option', { selected: selectedPayment === method.value.toString(), disabled: method.value === 1 && (balance < currentTotalPrice) }]"
@click="selectedPayment = method.value.toString()">
<van-icon
:name="method.value === 0 ? 'wechat' : method.value === 1 ? 'balance-o' : method.value === 2 ? 'credit-card' : 'wallet'"
class="method-icon" />
<span class="method-label">
{{ method.label }}
<!-- <<<<<< 修改balance < currentTotalPrice -->
<span v-if="method.value === 1" class="balance-amount">(当前:¥{{ balance.toFixed(2) }}</span>
</span>
<div class="empty"></div>
<van-icon v-if="selectedPayment === method.value.toString()" name="success" class="check-icon" />
</van-cell>
</van-cell-group>
<!-- 提交订单栏 -->
<div class="submit-bar">
<div class="total-price">
合计:¥{{ currentTotalPrice.toFixed(2) }} <!-- <<<<<< 修改:使用 currentTotalPrice -->
</div>
<van-button type="primary" size="large" :loading="submitting" loading-text="提交中..." @click="handleSubmit">
提交订单
</van-button>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
// 样式无需修改,因为已经在 cart.vue 中做了通用处理,这里只是替换了数据源
.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>