|
@@ -9,7 +9,8 @@
|
|
|
<!-- 左侧:大图区域 -->
|
|
<!-- 左侧:大图区域 -->
|
|
|
<div class="goods-cover">
|
|
<div class="goods-cover">
|
|
|
<div class="cover-image">
|
|
<div class="cover-image">
|
|
|
- <img :src="goodsInfo.cover" alt="商品封面">
|
|
|
|
|
|
|
+ <img v-if="goodsInfo.product?.cover" :src="goodsInfo.product.cover" alt="商品封面">
|
|
|
|
|
+ <div v-else class="no-image">暂无图片</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -21,9 +22,9 @@
|
|
|
{{ goodsInfo.name }}
|
|
{{ goodsInfo.name }}
|
|
|
</h2>
|
|
</h2>
|
|
|
<div class="goods-tags">
|
|
<div class="goods-tags">
|
|
|
- <el-tag type="success" size="small">{{ goodsInfo.deliveryType }}</el-tag>
|
|
|
|
|
- <el-tag size="small">已售 {{ goodsInfo.sold }}</el-tag>
|
|
|
|
|
- <el-tag type="warning" size="small">库存 {{ goodsInfo.stockText }}</el-tag>
|
|
|
|
|
|
|
+ <el-tag type="success" size="small">{{ goodsInfo.type_text }}</el-tag>
|
|
|
|
|
+ <el-tag size="small">已售 {{ goodsInfo.sold_count }}</el-tag>
|
|
|
|
|
+ <el-tag type="warning" size="small">库存 {{ goodsInfo.stock_text }}</el-tag>
|
|
|
<el-tag type="info" size="small">{{ cardTypeText }}</el-tag>
|
|
<el-tag type="info" size="small">{{ cardTypeText }}</el-tag>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -39,32 +40,22 @@
|
|
|
</div>
|
|
</div>
|
|
|
<div class="price-desc">
|
|
<div class="price-desc">
|
|
|
<span v-if="isSeatBasedEnterprise">
|
|
<span v-if="isSeatBasedEnterprise">
|
|
|
- {{ goodsInfo.comboPeopleNum }} 人席位,总价将按 {{ goodsInfo.comboPrice }} 元/席位计算
|
|
|
|
|
|
|
+ 企业套餐,按席位计算(单价:{{ goodsInfo.price_text }})
|
|
|
</span>
|
|
</span>
|
|
|
<span v-else>
|
|
<span v-else>
|
|
|
- 已选择 {{ buyCount }} 件,自动根据数量合计
|
|
|
|
|
|
|
+ 已选择 {{ buyCount }} 件,自动根据数量合计(单价:{{ goodsInfo.price_text }})
|
|
|
</span>
|
|
</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 购买数量 -->
|
|
<!-- 购买数量 -->
|
|
|
- <div
|
|
|
|
|
- v-if="!isSeatBasedEnterprise"
|
|
|
|
|
- class="field-row quantity-row"
|
|
|
|
|
- >
|
|
|
|
|
|
|
+ <div v-if="!isSeatBasedEnterprise" class="field-row quantity-row">
|
|
|
<div class="field-label">
|
|
<div class="field-label">
|
|
|
<span>购买数量</span>
|
|
<span>购买数量</span>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="field-content quantity-control">
|
|
<div class="field-content quantity-control">
|
|
|
- <el-input-number
|
|
|
|
|
- v-model="buyCount"
|
|
|
|
|
- :min="1"
|
|
|
|
|
- :max="goodsInfo.stock"
|
|
|
|
|
- :step="1"
|
|
|
|
|
- size="small"
|
|
|
|
|
- controls-position="right"
|
|
|
|
|
- class="qty-input-number"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <el-input-number v-model="buyCount" :min="1" :max="goodsInfo.stock" :step="1" size="small"
|
|
|
|
|
+ controls-position="right" class="qty-input-number" />
|
|
|
<span class="stock-hint">
|
|
<span class="stock-hint">
|
|
|
库存剩余:{{ goodsInfo.stock }} 件
|
|
库存剩余:{{ goodsInfo.stock }} 件
|
|
|
</span>
|
|
</span>
|
|
@@ -72,24 +63,15 @@
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 企业套餐:席位人数 -->
|
|
<!-- 企业套餐:席位人数 -->
|
|
|
- <div
|
|
|
|
|
- v-else
|
|
|
|
|
- class="field-row quantity-row"
|
|
|
|
|
- >
|
|
|
|
|
|
|
+ <div v-else class="field-row quantity-row">
|
|
|
<div class="field-label">
|
|
<div class="field-label">
|
|
|
<span>购买席位</span>
|
|
<span>购买席位</span>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="field-content quantity-control">
|
|
<div class="field-content quantity-control">
|
|
|
- <el-input-number
|
|
|
|
|
- v-model="goodsInfo.comboPeopleNum"
|
|
|
|
|
- :min="2"
|
|
|
|
|
- :step="1"
|
|
|
|
|
- size="small"
|
|
|
|
|
- controls-position="right"
|
|
|
|
|
- class="qty-input-number"
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <el-input-number v-model="seatCount" :min="2" :step="1" size="small"
|
|
|
|
|
+ controls-position="right" class="qty-input-number" />
|
|
|
<span class="stock-hint">
|
|
<span class="stock-hint">
|
|
|
- 最少2个席位,当前默认 {{ goodsInfo.comboPeopleNum }} 个
|
|
|
|
|
|
|
+ 最少2个席位,当前默认 {{ seatCount }} 个
|
|
|
</span>
|
|
</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -100,27 +82,17 @@
|
|
|
<span>付款</span>
|
|
<span>付款</span>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="field-content pay-methods">
|
|
<div class="field-content pay-methods">
|
|
|
- <el-button
|
|
|
|
|
- size="small"
|
|
|
|
|
- :type="selectedPayMethod === 'alipay' ? 'primary' : 'default'"
|
|
|
|
|
- :plain="selectedPayMethod !== 'alipay'"
|
|
|
|
|
- class="pay-btn"
|
|
|
|
|
- :class="{ active: selectedPayMethod === 'alipay' }"
|
|
|
|
|
- @click="selectedPayMethod = 'alipay'"
|
|
|
|
|
- >
|
|
|
|
|
-<el-icon class="pay-icon alipay">
|
|
|
|
|
- <Coin />
|
|
|
|
|
|
|
+ <!-- <el-button size="small" :type="selectedPayMethod === 'alipay' ? 'primary' : 'default'"
|
|
|
|
|
+ :plain="selectedPayMethod !== 'alipay'" class="pay-btn"
|
|
|
|
|
+ :class="{ active: selectedPayMethod === 'alipay' }" @click="selectedPayMethod = 'alipay'">
|
|
|
|
|
+ <el-icon class="pay-icon alipay">
|
|
|
|
|
+ <Coin />
|
|
|
</el-icon>
|
|
</el-icon>
|
|
|
支付宝
|
|
支付宝
|
|
|
- </el-button>
|
|
|
|
|
- <el-button
|
|
|
|
|
- size="small"
|
|
|
|
|
- :type="selectedPayMethod === 'wechat' ? 'success' : 'default'"
|
|
|
|
|
- :plain="selectedPayMethod !== 'wechat'"
|
|
|
|
|
- class="pay-btn"
|
|
|
|
|
- :class="{ active: selectedPayMethod === 'wechat' }"
|
|
|
|
|
- @click="selectedPayMethod = 'wechat'"
|
|
|
|
|
- >
|
|
|
|
|
|
|
+ </el-button> -->
|
|
|
|
|
+ <el-button size="small" :type="selectedPayMethod === 'wechat' ? 'success' : 'default'"
|
|
|
|
|
+ :plain="selectedPayMethod !== 'wechat'" class="pay-btn"
|
|
|
|
|
+ :class="{ active: selectedPayMethod === 'wechat' }" @click="selectedPayMethod = 'wechat'">
|
|
|
<el-icon class="pay-icon wechat">
|
|
<el-icon class="pay-icon wechat">
|
|
|
<ChatDotRound />
|
|
<ChatDotRound />
|
|
|
</el-icon>
|
|
</el-icon>
|
|
@@ -130,12 +102,7 @@
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="submit-row">
|
|
<div class="submit-row">
|
|
|
- <el-button
|
|
|
|
|
- type="primary"
|
|
|
|
|
- size="large"
|
|
|
|
|
- class="submit-btn"
|
|
|
|
|
- @click="handleSubmit"
|
|
|
|
|
- >
|
|
|
|
|
|
|
+ <el-button type="primary" size="large" class="submit-btn" @click="handleSubmit">
|
|
|
立即下单
|
|
立即下单
|
|
|
</el-button>
|
|
</el-button>
|
|
|
</div>
|
|
</div>
|
|
@@ -144,9 +111,18 @@
|
|
|
</el-card>
|
|
</el-card>
|
|
|
|
|
|
|
|
<!-- 创建企业弹框组件 -->
|
|
<!-- 创建企业弹框组件 -->
|
|
|
- <CreateEnterpriseDialog
|
|
|
|
|
- v-model:visible="createEnterpriseVisible"
|
|
|
|
|
- @success="handleEnterpriseCreated"
|
|
|
|
|
|
|
+ <CreateEnterpriseDialog v-model:visible="createEnterpriseVisible" @success="handleEnterpriseCreated" />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 支付二维码弹窗 -->
|
|
|
|
|
+ <PayQrCodeDialog
|
|
|
|
|
+ v-model:visible="payDialogVisible"
|
|
|
|
|
+ :order-info="currentOrderInfo"
|
|
|
|
|
+ :qr-code-url="qrCodeUrl"
|
|
|
|
|
+ :pay-method="selectedPayMethod"
|
|
|
|
|
+ :loading="qrCodeLoading"
|
|
|
|
|
+ :pay-status="payStatus"
|
|
|
|
|
+ @close="handlePayDialogClose"
|
|
|
|
|
+ @confirm="handlePaySuccess"
|
|
|
/>
|
|
/>
|
|
|
|
|
|
|
|
<!-- 下半部分:宝贝详情 -->
|
|
<!-- 下半部分:宝贝详情 -->
|
|
@@ -158,9 +134,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<div class="detail-content">
|
|
<div class="detail-content">
|
|
|
- <p v-for="(line, index) in goodsInfo.detailLines" :key="index">
|
|
|
|
|
- {{ line }}
|
|
|
|
|
- </p>
|
|
|
|
|
|
|
+ <p>{{ goodsInfo.description || '暂无详情' }}</p>
|
|
|
</div>
|
|
</div>
|
|
|
</el-card>
|
|
</el-card>
|
|
|
</div>
|
|
</div>
|
|
@@ -168,88 +142,263 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
|
-import { computed, onMounted, reactive, ref } from 'vue'
|
|
|
|
|
|
|
+import { computed, onMounted, onBeforeUnmount, ref } from 'vue'
|
|
|
import { useRoute } from 'vue-router'
|
|
import { useRoute } from 'vue-router'
|
|
|
import { ElMessage } from 'element-plus'
|
|
import { ElMessage } from 'element-plus'
|
|
|
-import { ChatDotRound, Coin } from '@element-plus/icons-vue'
|
|
|
|
|
|
|
+import { ChatDotRound ,Coin} from '@element-plus/icons-vue'
|
|
|
import Header from './components/header.vue'
|
|
import Header from './components/header.vue'
|
|
|
import CreateEnterpriseDialog from './components/CreateEnterpriseDialog.vue'
|
|
import CreateEnterpriseDialog from './components/CreateEnterpriseDialog.vue'
|
|
|
|
|
+import PayQrCodeDialog from './components/PayQrCodeDialog.vue'
|
|
|
|
|
+import { detail } from '/@/api/frontend/home'
|
|
|
|
|
+import { createOrder, createQrCode, checkOrderStatus } from '/@/api/frontend/order'
|
|
|
|
|
|
|
|
defineOptions({ name: 'ClientOrder' })
|
|
defineOptions({ name: 'ClientOrder' })
|
|
|
|
|
|
|
|
const route = useRoute()
|
|
const route = useRoute()
|
|
|
|
|
|
|
|
const goodsId = ref(null)
|
|
const goodsId = ref(null)
|
|
|
-const goodsInfo = reactive({
|
|
|
|
|
- name: '惊鸿·日卡',
|
|
|
|
|
- price: '25',
|
|
|
|
|
- sold: 318,
|
|
|
|
|
- stockText: '即将售罄',
|
|
|
|
|
- stock: 5,
|
|
|
|
|
- deliveryType: '自动激活',
|
|
|
|
|
- cover: 'https://picsum.photos/900/400',
|
|
|
|
|
- type: 2, // 1: 个人 2: 企业
|
|
|
|
|
- cardType: 4, // 1: 日卡 2: 周卡 3: 月卡 4: 季卡 5: 年卡 , 6: 按次数 , 7: 按积分
|
|
|
|
|
- comboPeopleNum: 5, // 默认席位
|
|
|
|
|
- comboPrice: 5, // 每席位单价
|
|
|
|
|
- detailLines: [
|
|
|
|
|
- '该卡可续杯 2 次',
|
|
|
|
|
- '该卡可绑定一台设备',
|
|
|
|
|
- '未激活前永久有效',
|
|
|
|
|
- '激活后自下单起 7 日内有效',
|
|
|
|
|
- '下单激活后不予退款,请确认好需要再下单',
|
|
|
|
|
- '在 Iron 工具上使用视为激活',
|
|
|
|
|
- '使用教程:请参考商家提供的使用说明链接'
|
|
|
|
|
- ]
|
|
|
|
|
-})
|
|
|
|
|
|
|
+const goodsInfo = ref({})
|
|
|
|
|
|
|
|
const buyCount = ref(1)
|
|
const buyCount = ref(1)
|
|
|
|
|
+const seatCount = ref(2)
|
|
|
const hasEnterprise = ref(false)
|
|
const hasEnterprise = ref(false)
|
|
|
const createEnterpriseVisible = ref(false)
|
|
const createEnterpriseVisible = ref(false)
|
|
|
-const selectedPayMethod = ref('alipay')
|
|
|
|
|
-
|
|
|
|
|
-const isEnterprise = computed(() => Number(goodsInfo.type) === 2)
|
|
|
|
|
|
|
+const selectedPayMethod = ref('wechat')
|
|
|
|
|
+
|
|
|
|
|
+// 支付相关状态
|
|
|
|
|
+const payDialogVisible = ref(false)
|
|
|
|
|
+const qrCodeLoading = ref(false)
|
|
|
|
|
+const qrCodeUrl = ref('')
|
|
|
|
|
+const payStatus = ref('') // '', 'success', 'failed'
|
|
|
|
|
+const currentOrderInfo = ref({})
|
|
|
|
|
+const payCheckTimer = ref(null)
|
|
|
|
|
+const payCheckCount = ref(0)
|
|
|
|
|
+const MAX_PAY_CHECK_COUNT = 100 // 最大轮询次数(约5分钟)
|
|
|
|
|
+
|
|
|
|
|
+const isEnterprise = computed(() => Number(goodsInfo.value?.type) === 2)
|
|
|
const isSeatBasedEnterprise = computed(() => {
|
|
const isSeatBasedEnterprise = computed(() => {
|
|
|
if (!isEnterprise.value) return false
|
|
if (!isEnterprise.value) return false
|
|
|
- const cardType = Number(goodsInfo.cardType)
|
|
|
|
|
|
|
+ const cardType = Number(goodsInfo.value?.card_type)
|
|
|
|
|
+ // 月卡(6)和年卡(7)不是按席位计算
|
|
|
return ![6, 7].includes(cardType)
|
|
return ![6, 7].includes(cardType)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+// 获取卡类型文本
|
|
|
const cardTypeText = computed(() => {
|
|
const cardTypeText = computed(() => {
|
|
|
|
|
+ if (goodsInfo.value?.card_info?.name) {
|
|
|
|
|
+ const cardName = goodsInfo.value.card_info.name
|
|
|
|
|
+ const showValue = goodsInfo.value.card_info.show_value
|
|
|
|
|
+ return showValue ? `${cardName}(${showValue})` : cardName
|
|
|
|
|
+ }
|
|
|
|
|
+ // 兼容处理:如果没有card_info,使用映射
|
|
|
const map = {
|
|
const map = {
|
|
|
- 1: '日卡',
|
|
|
|
|
- 2: '周卡',
|
|
|
|
|
- 3: '月卡',
|
|
|
|
|
- 4: '季卡',
|
|
|
|
|
- 5: '年卡',
|
|
|
|
|
- 6: '按次数',
|
|
|
|
|
- 7: '按积分'
|
|
|
|
|
|
|
+ 1: '次卡',
|
|
|
|
|
+ 2: '按积分',
|
|
|
|
|
+ 3: '日卡',
|
|
|
|
|
+ 4: '周卡',
|
|
|
|
|
+ 5: '季卡',
|
|
|
|
|
+ 6: '月卡',
|
|
|
|
|
+ 7: '年卡'
|
|
|
}
|
|
}
|
|
|
- const label = map[Number(goodsInfo.cardType)] || '未知类型'
|
|
|
|
|
- return isEnterprise.value ? `企业·${label}` : `个人·${label}`
|
|
|
|
|
|
|
+ const label = map[Number(goodsInfo.value?.card_type)] || '未知类型'
|
|
|
|
|
+ return label
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
const displayPrice = computed(() => {
|
|
const displayPrice = computed(() => {
|
|
|
if (isSeatBasedEnterprise.value) {
|
|
if (isSeatBasedEnterprise.value) {
|
|
|
- return (Number(goodsInfo.comboPrice) * Number(goodsInfo.comboPeopleNum)).toFixed(2)
|
|
|
|
|
|
|
+ return (Number(goodsInfo.value?.price || 0) * seatCount.value).toFixed(2)
|
|
|
}
|
|
}
|
|
|
- return (Number(goodsInfo.price) * buyCount.value).toFixed(2)
|
|
|
|
|
|
|
+ return (Number(goodsInfo.value?.price || 0) * buyCount.value).toFixed(2)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+// 获取商品详情
|
|
|
const getGoodsInfo = () => {
|
|
const getGoodsInfo = () => {
|
|
|
goodsId.value = route.params?.goodsId
|
|
goodsId.value = route.params?.goodsId
|
|
|
|
|
+ detail({id:goodsId.value}).then(res =>{
|
|
|
|
|
+ goodsInfo.value = res.data
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 创建订单并生成支付二维码
|
|
|
|
|
+const createOrderAndQrCode = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 创建订单
|
|
|
|
|
+ const quantity = isSeatBasedEnterprise.value ? seatCount.value : buyCount.value
|
|
|
|
|
+ const orderData = {
|
|
|
|
|
+ package_id: goodsId.value,
|
|
|
|
|
+ quantity: quantity
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ qrCodeLoading.value = true
|
|
|
|
|
+ const orderRes = await createOrder(orderData)
|
|
|
|
|
+
|
|
|
|
|
+ // 验证订单创建结果
|
|
|
|
|
+ if (!orderRes || !orderRes.data) {
|
|
|
|
|
+ ElMessage.error(orderRes?.msg || '创建订单失败,请重试')
|
|
|
|
|
+ qrCodeLoading.value = false
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!orderRes.data.order_id || !orderRes.data.order_no) {
|
|
|
|
|
+ ElMessage.error(orderRes.data.msg || '订单信息不完整,请重试')
|
|
|
|
|
+ qrCodeLoading.value = false
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const orderId = orderRes.data.order_id
|
|
|
|
|
+ // 根据实际返回的数据结构构建订单信息
|
|
|
|
|
+ currentOrderInfo.value = {
|
|
|
|
|
+ id: orderId,
|
|
|
|
|
+ order_id: orderId,
|
|
|
|
|
+ order_no: orderRes.data.order_no,
|
|
|
|
|
+ title: orderRes.data.package_name || goodsInfo.value.name || '',
|
|
|
|
|
+ product_name: orderRes.data.package_name || goodsInfo.value.name || '',
|
|
|
|
|
+ package_name: orderRes.data.package_name || '',
|
|
|
|
|
+ amount: orderRes.data.total_amount || displayPrice.value,
|
|
|
|
|
+ total_price: orderRes.data.total_amount || displayPrice.value,
|
|
|
|
|
+ total_amount: orderRes.data.total_amount || 0,
|
|
|
|
|
+ quantity: orderRes.data.quantity || (isSeatBasedEnterprise.value ? seatCount.value : buyCount.value),
|
|
|
|
|
+ ...orderRes.data
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 生成支付二维码
|
|
|
|
|
+ const qrCodeData = {
|
|
|
|
|
+ order_id: orderId,
|
|
|
|
|
+ pay_method: selectedPayMethod.value
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const qrCodeRes = await createQrCode(qrCodeData)
|
|
|
|
|
+
|
|
|
|
|
+ // 验证二维码生成结果
|
|
|
|
|
+ if (!qrCodeRes || !qrCodeRes.data) {
|
|
|
|
|
+ ElMessage.error(qrCodeRes?.msg || '生成支付二维码失败,请重试')
|
|
|
|
|
+ qrCodeLoading.value = false
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!qrCodeRes.data.code_url) {
|
|
|
|
|
+ ElMessage.error(qrCodeRes.data.msg || '二维码生成失败,请重试')
|
|
|
|
|
+ qrCodeLoading.value = false
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ qrCodeUrl.value = qrCodeRes.data.code_url
|
|
|
|
|
+ qrCodeLoading.value = false
|
|
|
|
|
+ payStatus.value = ''
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 显示支付弹窗
|
|
|
|
|
+ payDialogVisible.value = true
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 开始轮询支付状态
|
|
|
|
|
+ startPayStatusPolling(orderId, orderRes.data.order_no)
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('下单失败:', error)
|
|
|
|
|
+ ElMessage.error(error?.message || '下单失败,请重试')
|
|
|
|
|
+ qrCodeLoading.value = false
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const placePersonalOrder = () => {
|
|
|
|
|
- ElMessage.success(`已下单 ${buyCount.value} 件个人套餐`)
|
|
|
|
|
|
|
+// 开始轮询支付状态
|
|
|
|
|
+const startPayStatusPolling = (orderId, orderNo) => {
|
|
|
|
|
+ // 清除之前的定时器
|
|
|
|
|
+ if (payCheckTimer.value) {
|
|
|
|
|
+ clearInterval(payCheckTimer.value)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 重置轮询计数
|
|
|
|
|
+ payCheckCount.value = 0
|
|
|
|
|
+
|
|
|
|
|
+ // 每3秒查询一次支付状态
|
|
|
|
|
+ payCheckTimer.value = setInterval(async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ payCheckCount.value++
|
|
|
|
|
+
|
|
|
|
|
+ // 超过最大轮询次数,停止轮询
|
|
|
|
|
+ if (payCheckCount.value > MAX_PAY_CHECK_COUNT) {
|
|
|
|
|
+ stopPayStatusPolling()
|
|
|
|
|
+ ElMessage.warning('支付超时,请稍后手动查询订单状态')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 根据接口注释,同时传递 order_id 和 order_no
|
|
|
|
|
+ const checkData = {
|
|
|
|
|
+ order_id: orderId,
|
|
|
|
|
+ order_no: orderNo
|
|
|
|
|
+ }
|
|
|
|
|
+ const res = await checkOrderStatus(checkData)
|
|
|
|
|
+
|
|
|
|
|
+ // 支付状态判断:1-已支付,0-待支付,2-支付失败,3-已取消
|
|
|
|
|
+ // 兼容不同的数据结构(可能返回 pay_status 或 status 字段)
|
|
|
|
|
+ const status = res.data?.pay_status ?? res.data?.status ?? res.data?.order?.pay_status
|
|
|
|
|
+
|
|
|
|
|
+ if (status == '1') {
|
|
|
|
|
+ // 支付成功
|
|
|
|
|
+ payStatus.value = 'success'
|
|
|
|
|
+ stopPayStatusPolling()
|
|
|
|
|
+
|
|
|
|
|
+ // 更新订单信息(可能包含最新的支付信息)
|
|
|
|
|
+ if (res.data) {
|
|
|
|
|
+ currentOrderInfo.value = {
|
|
|
|
|
+ ...currentOrderInfo.value,
|
|
|
|
|
+ ...res.data,
|
|
|
|
|
+ pay_status: status
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 3秒后自动关闭弹窗
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ handlePaySuccess()
|
|
|
|
|
+ }, 3000)
|
|
|
|
|
+ } else if (status === 2 || status === 3 || status === 'failed' || status === 'cancelled' || status === '2' || status === '3') {
|
|
|
|
|
+ // 支付失败或已取消
|
|
|
|
|
+ payStatus.value = 'failed'
|
|
|
|
|
+ stopPayStatusPolling()
|
|
|
|
|
+ ElMessage.warning('订单已取消或支付失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ // status === 0 或其他值表示待支付,继续轮询
|
|
|
|
|
+ // status === 0 或其他值表示待支付,继续轮询
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('查询支付状态失败:', error)
|
|
|
|
|
+ // 查询失败不中断轮询,继续尝试(除非超过最大次数)
|
|
|
|
|
+ if (payCheckCount.value > MAX_PAY_CHECK_COUNT) {
|
|
|
|
|
+ stopPayStatusPolling()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 3000)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const placeEnterpriseOrder = () => {
|
|
|
|
|
- if (isSeatBasedEnterprise.value) {
|
|
|
|
|
- ElMessage.success(`已为企业下单 ${goodsInfo.comboPeopleNum} 个席位`)
|
|
|
|
|
- } else {
|
|
|
|
|
- ElMessage.success(`已为企业下单 ${buyCount.value} 件套餐`)
|
|
|
|
|
|
|
+// 停止支付状态轮询
|
|
|
|
|
+const stopPayStatusPolling = () => {
|
|
|
|
|
+ if (payCheckTimer.value) {
|
|
|
|
|
+ clearInterval(payCheckTimer.value)
|
|
|
|
|
+ payCheckTimer.value = null
|
|
|
}
|
|
}
|
|
|
|
|
+ payCheckCount.value = 0
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 支付弹窗关闭处理
|
|
|
|
|
+const handlePayDialogClose = () => {
|
|
|
|
|
+ stopPayStatusPolling()
|
|
|
|
|
+ payDialogVisible.value = false
|
|
|
|
|
+ qrCodeUrl.value = ''
|
|
|
|
|
+ payStatus.value = ''
|
|
|
|
|
+ currentOrderInfo.value = {}
|
|
|
|
|
+ qrCodeLoading.value = false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 支付成功处理
|
|
|
|
|
+const handlePaySuccess = () => {
|
|
|
|
|
+ stopPayStatusPolling()
|
|
|
|
|
+ payDialogVisible.value = false
|
|
|
|
|
+ ElMessage.success('支付成功!')
|
|
|
|
|
+
|
|
|
|
|
+ // 可以在这里跳转到订单详情页或订单列表页
|
|
|
|
|
+ // router.push({ name: 'OrderDetail', params: { id: currentOrderInfo.value.id } })
|
|
|
|
|
+
|
|
|
|
|
+ // 重置状态
|
|
|
|
|
+ qrCodeUrl.value = ''
|
|
|
|
|
+ payStatus.value = ''
|
|
|
|
|
+ currentOrderInfo.value = {}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const openCreateEnterpriseDialog = () => {
|
|
const openCreateEnterpriseDialog = () => {
|
|
@@ -261,21 +410,39 @@ const handleEnterpriseCreated = () => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const handleSubmit = () => {
|
|
const handleSubmit = () => {
|
|
|
|
|
+ // 验证商品信息
|
|
|
|
|
+ if (!goodsId.value || !goodsInfo.value.id) {
|
|
|
|
|
+ ElMessage.warning('商品信息加载中,请稍候...')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 验证库存
|
|
|
|
|
+ if (!isSeatBasedEnterprise.value && buyCount.value > goodsInfo.value.stock) {
|
|
|
|
|
+ ElMessage.warning('库存不足,请减少购买数量')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 企业订单需要先创建企业
|
|
|
if (isEnterprise.value) {
|
|
if (isEnterprise.value) {
|
|
|
if (!hasEnterprise.value) {
|
|
if (!hasEnterprise.value) {
|
|
|
ElMessage.warning('请先创建企业后再下单')
|
|
ElMessage.warning('请先创建企业后再下单')
|
|
|
openCreateEnterpriseDialog()
|
|
openCreateEnterpriseDialog()
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- placeEnterpriseOrder()
|
|
|
|
|
- } else {
|
|
|
|
|
- placePersonalOrder()
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // 创建订单并生成支付二维码
|
|
|
|
|
+ createOrderAndQrCode()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
getGoodsInfo()
|
|
getGoodsInfo()
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
|
|
+// 组件卸载前清除定时器
|
|
|
|
|
+onBeforeUnmount(() => {
|
|
|
|
|
+ stopPayStatusPolling()
|
|
|
|
|
+})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped lang="scss">
|
|
<style scoped lang="scss">
|
|
@@ -310,6 +477,10 @@ onMounted(() => {
|
|
|
border-radius: 14px;
|
|
border-radius: 14px;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
background: #f2f6fc;
|
|
background: #f2f6fc;
|
|
|
|
|
+ min-height: 300px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
|
|
|
img {
|
|
img {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
@@ -317,6 +488,11 @@ onMounted(() => {
|
|
|
object-fit: cover;
|
|
object-fit: cover;
|
|
|
display: block;
|
|
display: block;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ .no-image {
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|