Files
quyun-v2/frontend/portal/src/views/order/PaymentView.vue
Rogee cf29a2bf1a feat(auth): implement OTP login flow with toast notifications
feat(content): enhance detail view with dynamic content and comments
feat(order): add polling for payment status in the payment view
feat(user): update dashboard to display wallet and recent orders
feat(user): improve orders view with dynamic order fetching and status mapping
feat(api): create API modules for auth, content, order, user, and common functionalities
refactor(request): implement a centralized request utility for API calls
2025-12-30 21:15:13 +08:00

168 lines
7.5 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.
<template>
<div class="min-h-screen bg-slate-50 py-8">
<div class="mx-auto max-w-3xl">
<!-- Header -->
<div class="flex items-center justify-between px-4 mb-6">
<div class="flex items-center gap-2">
<div class="w-8 h-8 bg-primary-600 rounded-lg flex items-center justify-center text-white font-bold">Q</div>
<span class="text-xl font-bold text-slate-900">收银台</span>
</div>
<div class="text-sm text-slate-500">订单号: {{ orderId }}</div>
</div>
<!-- Main Card -->
<div class="bg-white rounded-xl shadow-sm border border-slate-100 overflow-hidden">
<!-- Countdown Timer -->
<div class="bg-orange-50 text-orange-700 text-center py-3 text-sm font-bold border-b border-orange-100">
<i class="pi pi-clock mr-1"></i> 支付剩余时间{{ formatTime(timeLeft) }}
</div>
<div class="p-8 md:p-12">
<!-- Amount -->
<div class="text-center mb-10">
<p class="text-slate-500 mb-2">订单提交成功请尽快支付</p>
<div class="text-4xl font-bold text-slate-900">¥ {{ amount }}</div>
<div class="text-sm text-slate-500 mt-2">商品{{ productName }}</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-12">
<!-- Payment Methods -->
<div class="space-y-4">
<h3 class="font-bold text-slate-900 mb-4">选择支付方式</h3>
<label class="flex items-center gap-4 p-4 border rounded-xl cursor-pointer transition-all relative overflow-hidden"
:class="paymentMethod === 'wechat' ? 'border-green-500 bg-green-50/30 ring-1 ring-green-500' : 'border-slate-200 hover:border-slate-300'">
<input type="radio" v-model="paymentMethod" value="wechat" class="hidden">
<i class="pi pi-wechat text-3xl text-green-600"></i>
<div class="flex-1">
<div class="font-bold text-slate-900">微信支付</div>
<div class="text-xs text-slate-500">推荐使用</div>
</div>
<i v-if="paymentMethod === 'wechat'" class="pi pi-check-circle text-green-600 text-xl"></i>
</label>
<label class="flex items-center gap-4 p-4 border rounded-xl cursor-pointer transition-all"
:class="paymentMethod === 'alipay' ? 'border-blue-500 bg-blue-50/30 ring-1 ring-blue-500' : 'border-slate-200 hover:border-slate-300'">
<input type="radio" v-model="paymentMethod" value="alipay" class="hidden">
<i class="pi pi-alipay text-3xl text-blue-500"></i>
<div class="flex-1">
<div class="font-bold text-slate-900">支付宝</div>
</div>
<i v-if="paymentMethod === 'alipay'" class="pi pi-check-circle text-blue-500 text-xl"></i>
</label>
<label class="flex items-center gap-4 p-4 border rounded-xl cursor-pointer transition-all opacity-50 bg-slate-50">
<input type="radio" value="balance" disabled class="hidden">
<i class="pi pi-wallet text-3xl text-slate-400"></i>
<div class="flex-1">
<div class="font-bold text-slate-400">余额支付</div>
<div class="text-xs text-slate-400">余额不足 (¥ 0.00)</div>
</div>
</label>
</div>
<!-- QR Code Area -->
<div class="flex flex-col items-center justify-center border-t md:border-t-0 md:border-l border-slate-100 pt-8 md:pt-0 md:pl-8">
<div class="bg-white p-4 border border-slate-200 rounded-xl shadow-sm mb-4">
<!-- Mock QR -->
<div class="w-48 h-48 bg-slate-100 flex items-center justify-center relative overflow-hidden">
<i class="pi pi-qrcode text-6xl text-slate-300"></i>
<!-- Loading Overlay -->
<div v-if="isScanning" class="absolute inset-0 bg-white/90 flex flex-col items-center justify-center">
<i class="pi pi-spin pi-spinner text-3xl text-primary-600 mb-2"></i>
<span class="text-xs text-slate-500">正在获取支付结果...</span>
</div>
<!-- Success Overlay -->
<div v-if="isSuccess" class="absolute inset-0 bg-green-500 flex flex-col items-center justify-center text-white animate-in zoom-in">
<i class="pi pi-check-circle text-5xl mb-2"></i>
<span class="font-bold">支付成功</span>
</div>
</div>
</div>
<div class="text-center">
<p class="text-sm font-bold text-slate-700 mb-1">
使用 {{ paymentMethodName }} 扫一扫
</p>
<p class="text-xs text-slate-400">二维码有效时间 2小时</p>
</div>
<!-- Dev Tool -->
<button @click="simulateSuccess" class="mt-8 text-xs text-slate-300 hover:text-slate-500 underline">
[开发调试] 模拟支付成功
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { orderApi } from '../../api/order';
const route = useRoute();
const router = useRouter();
const orderId = route.params.id || '82934712';
const amount = '9.90'; // Should fetch order details first
const productName = '《霸王别姬》全本实录珍藏版';
const paymentMethod = ref('wechat');
const timeLeft = ref(900); // 15 minutes
const isScanning = ref(false);
const isSuccess = ref(false);
let timer = null;
let pollTimer = null;
const paymentMethodName = computed(() => {
return paymentMethod.value === 'wechat' ? '微信' : '支付宝';
});
const formatTime = (seconds) => {
const m = Math.floor(seconds / 60);
const s = seconds % 60;
return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
};
const simulateSuccess = () => {
isScanning.value = true;
setTimeout(() => {
isScanning.value = false;
isSuccess.value = true;
setTimeout(() => {
router.replace(`/me/orders/${orderId}`);
}, 1500);
}, 1000);
};
onMounted(() => {
timer = setInterval(() => {
if (timeLeft.value > 0) timeLeft.value--;
}, 1000);
// Poll Status
pollTimer = setInterval(async () => {
try {
const res = await orderApi.status(orderId);
if (res.status === 'paid' || res.status === 'completed') {
isScanning.value = false;
isSuccess.value = true;
clearInterval(pollTimer);
setTimeout(() => router.replace(`/me/orders/${orderId}`), 1500);
}
} catch (e) {
console.error('Poll status failed', e);
}
}, 3000);
});
onUnmounted(() => {
if (timer) clearInterval(timer);
if (pollTimer) clearInterval(pollTimer);
});
</script>