登录对接

This commit is contained in:
speakeloudest 2025-12-31 06:39:11 -08:00
parent 033dda7744
commit 2b0741fdb9
3 changed files with 222 additions and 6 deletions

View File

@ -1,4 +1,5 @@
<template>
<div v-if="!devices?.length" class="w-full text-center">暂无绑定设备</div>
<div class="grid min-h-[76px] grid-cols-2 gap-3">
<div
v-for="device in devices"

View File

@ -0,0 +1,191 @@
<template>
<Dialog :open="open" @update:open="handleClose">
<DialogContent
:show-close-button="false"
@pointer-down-outside="(event) => event.preventDefault()"
@focus-outside="(event) => event.preventDefault()"
class="max-w-[345px] rounded-[32px] border-none bg-[#E5E5E5] p-6"
>
<div class="flex flex-col items-center py-6">
<h2 class="mb-1 text-xl font-semibold text-black">订单状态</h2>
<div class="mb-10 text-[24px] font-bold text-black">
{{ statusInfo.title }}
</div>
<div
v-if="statusInfo.description"
class="mb-8 text-center text-sm whitespace-pre-line text-gray-600"
>
{{ statusInfo.description }}
</div>
<div class="mt-4 flex w-full gap-3">
<Button
variant="secondary"
class="h-[50px] flex-1 rounded-[25px] bg-[#D1D1D1] text-lg font-bold text-gray-600 hover:bg-gray-300"
@click="handleClose"
>
关闭
</Button>
<Button
class="h-[50px] flex-1 rounded-[25px] bg-[#ADFF5B] text-lg font-bold text-black hover:bg-[#9EE64F]"
@click="handleClose"
>
返回首页
</Button>
</div>
</div>
</DialogContent>
</Dialog>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { Dialog, DialogContent } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import request from '@/utils/request'
const emit = defineEmits(['close', 'refresh'])
const open = ref(false)
const orderNo = ref<string | null>(null)
const status = ref<number>(1) // 1: Pending, 2: Paid, 3: Finished, 4: Close, 5: Failed, -1: Error
const countdown = ref('')
let timer: any = null
let countdownTimer: any = null
let createdAt: Date | null = null
const statusInfo = computed(() => {
switch (status.value) {
case -1:
return {
title: '检查失败',
description: '检查支付状态失败,请稍后刷新页面或联系客服',
}
case 1:
return {
title: '待支付',
description: `订单正在处理中\n剩余时间: ${countdown.value}`,
}
case 2:
return {
title: '支付确认中',
description: '订单已支付,系统正在确认,请稍候...',
}
case 3:
return {
title: '支付成功',
description: '您的订单已支付完成',
}
case 4:
return {
title: '订单已关闭',
description: '该订单已超时或被手动关闭',
}
case 5:
return {
title: '支付失败',
description: '订单支付失败,请检查支付信息并重试',
}
default:
return {
title: '待支付',
description: `订单正在处理中\n剩余时间: ${countdown.value}`,
}
}
})
function handleClose() {
stopPolling()
open.value = false
emit('close')
}
async function checkStatus() {
if (!orderNo.value) return
try {
const res: any = await request.get('/api/v1/public/order/detail', { order_no: orderNo.value })
// 1.
if (!createdAt && res.created_at) {
const timestamp = res.created_at
const isMilliseconds = timestamp > 10000000000
createdAt = new Date(isMilliseconds ? timestamp : timestamp * 1000)
startCountdown()
}
status.value = res.status
// 2.
if (status.value === 3) {
// Finished
stopPolling()
emit('refresh')
} else if (status.value === 4 || status.value === 5) {
// Closed or Failed
stopPolling()
}
} catch (error) {
console.error('❌ 查询失败:', error)
status.value = -1
stopPolling()
}
}
function startPolling() {
stopPolling()
checkStatus()
timer = setInterval(checkStatus, 3000)
}
function stopPolling() {
if (timer) {
clearInterval(timer)
timer = null
}
if (countdownTimer) {
clearInterval(countdownTimer)
countdownTimer = null
}
}
function startCountdown() {
if (!createdAt) return
const updateCountdown = () => {
const now = new Date()
// 15 (900 )
const expiryTime = new Date(createdAt!.getTime() + 900 * 1000)
const diff = expiryTime.getTime() - now.getTime()
console.log('diff', diff)
if (diff <= 0) {
countdown.value = '00:00'
clearInterval(countdownTimer)
return
}
const minutes = Math.floor(diff / 1000 / 60)
const seconds = Math.floor((diff / 1000) % 60)
countdown.value = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
}
updateCountdown()
if (countdownTimer) clearInterval(countdownTimer)
countdownTimer = setInterval(updateCountdown, 1000)
}
function show(no: string) {
orderNo.value = no
status.value = 1
createdAt = null
countdown.value = '15:00'
open.value = true
startPolling()
}
defineExpose({ show })
onUnmounted(() => {
stopPolling()
})
</script>

View File

@ -51,20 +51,27 @@
@refresh="init"
/>
</div>
<OrderStatusDialog ref="orderStatusDialogRef" @close="handleStatusClose" @refresh="init" />
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import MobileLayout from './MobileLayout/index.vue'
import DesktopLayout from './DesktopLayout/index.vue'
import OrderStatusDialog from '@/components/user-center/OrderStatusDialog.vue'
import Logo from '@/pages/Home/logo.svg?component'
import MobileLogo from '@/pages/Home/mobile-logo.svg?component'
import request from '@/utils/request'
const route = useRoute()
const router = useRouter()
// --- State ---
const selectedPlanId = ref('p2')
const selectedPayment = ref('alipay') // This variable is no longer directly used for payment selection in the UI, but kept for potential future use or if other parts of the app rely on it.
const orderStatusDialogRef = ref<InstanceType<typeof OrderStatusDialog> | null>(null)
const selectedPayment = ref('alipay')
const devices = ref<any[]>([])
const plans = ref<any[]>([])
@ -130,7 +137,7 @@ const handlePay = (methodId: number | string) => {
const already = alreadySubscribed.value.find((s: any) => s.subscribe_id === plan.planId)
const isRenewal = !!already
const api = isRenewal ? '/api/v1//public/order/renewal' : '/api/v1/public/order/purchase'
const api = isRenewal ? '/api/v1/public/order/renewal' : '/api/v1/public/order/purchase'
const params: any = {
subscribe_id: plan.planId,
quantity: plan.quantity,
@ -145,11 +152,15 @@ const handlePay = (methodId: number | string) => {
request
.post('/api/v1/public/portal/order/checkout', {
orderNo: res.order_no,
returnUrl: window.location.origin,
returnUrl: `${window.location.origin}/user-center?order_no=${res.order_no}`,
})
.then((checkoutRes: any) => {
if (checkoutRes.type === 'url' && checkoutRes.checkout_url) {
window.location.href = checkoutRes.checkout_url
localStorage.setItem('pending_order_no', res.order_no)
console.log('pending_order_no', res.order_no)
setTimeout(() => {
window.location.href = checkoutRes.checkout_url
})
}
})
})
@ -184,7 +195,20 @@ function init() {
payments.value = res.list?.filter((p: any) => p.platform !== 'apple_iap') || []
})
}
init()
onMounted(() => {
init()
const orderNo = (route.query.order_no as string) || localStorage.getItem('pending_order_no')
if (orderNo) {
orderStatusDialogRef.value?.show(orderNo)
}
})
function handleStatusClose() {
localStorage.removeItem('pending_order_no')
// URL order_no
router.replace({ query: { ...route.query, order_no: undefined } })
}
</script>
<style scoped>