485 lines
14 KiB
Vue
485 lines
14 KiB
Vue
<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>
|