diff --git a/src/pages/UserCenter/DesktopLayout/index.vue b/src/pages/UserCenter/DesktopLayout/index.vue index bb1d8e6..783afcd 100644 --- a/src/pages/UserCenter/DesktopLayout/index.vue +++ b/src/pages/UserCenter/DesktopLayout/index.vue @@ -41,7 +41,10 @@ -
+
{{ expireDateInfo.text }}
@@ -91,6 +94,7 @@ import DeviceList from '@/components/user-center/DeviceList.vue' import PaymentMethod from '@/components/user-center/PaymentMethod.vue' import UserCenterSkeleton from '@/components/user-center/UserCenterSkeleton.vue' import { Button } from '@/components/ui/button' +import { formatExpireDate } from '../subscription' import { toast } from 'vue-sonner' import { useRouter } from 'vue-router' const router = useRouter() @@ -120,38 +124,7 @@ const currentPlanIndex = computed(() => { const expireDateInfo = computed(() => { const first = props.alreadySubscribed[0] - let text = '' - let highlight = false - - if (!first || !first.expireDate) { - text = '尚未购买套餐' - highlight = true - } else { - // 尝试解析日期,兼容多种格式 - const dateStr = first.expireDate.replace(/ /g, 'T') - const expireDateTime = new Date(dateStr) - - if (isNaN(expireDateTime.getTime())) { - text = '套餐信息无效' - } else if (expireDateTime < new Date()) { - const year = expireDateTime.getFullYear() - const month = String(expireDateTime.getMonth() + 1).padStart(2, '0') - const day = String(expireDateTime.getDate()).padStart(2, '0') - text = `已于 ${year}/${month}/${day} 到期` - highlight = true - } else { - const year = expireDateTime.getFullYear() - const month = String(expireDateTime.getMonth() + 1).padStart(2, '0') - const day = String(expireDateTime.getDate()).padStart(2, '0') - const hour = String(expireDateTime.getHours()).padStart(2, '0') - const minute = String(expireDateTime.getMinutes()).padStart(2, '0') - const second = String(expireDateTime.getSeconds()).padStart(2, '0') - text = `到期时间:${year}/${month}/${day} ${hour}:${minute}:${second}` - highlight = false - } - } - - return { text, highlight } + return formatExpireDate(first) }) function logout() { diff --git a/src/pages/UserCenter/MobileLayout/index.vue b/src/pages/UserCenter/MobileLayout/index.vue index fb33ce1..6fb9aba 100644 --- a/src/pages/UserCenter/MobileLayout/index.vue +++ b/src/pages/UserCenter/MobileLayout/index.vue @@ -8,7 +8,10 @@
{{ userInfo.email }}
-
+
{{ expireDateInfo.text }}
@@ -78,6 +81,7 @@ import DeviceList from '@/components/user-center/DeviceList.vue' import PaymentMethod from '@/components/user-center/PaymentMethod.vue' import UserCenterSkeleton from '@/components/user-center/UserCenterSkeleton.vue' import { Button } from '@/components/ui/button' +import { formatExpireDate } from '../subscription' import { toast } from 'vue-sonner' import { useRouter } from 'vue-router' const router = useRouter() @@ -107,38 +111,7 @@ const currentPlanIndex = computed(() => { const expireDateInfo = computed(() => { const first = props.alreadySubscribed[0] - let text = '' - let highlight = false - - if (!first || !first.expireDate) { - text = '尚未购买套餐' - highlight = true - } else { - // 尝试解析日期,兼容多种格式 - const dateStr = first.expireDate.replace(/ /g, 'T') - const expireDateTime = new Date(dateStr) - - if (isNaN(expireDateTime.getTime())) { - text = '套餐信息无效' - } else if (expireDateTime < new Date()) { - const year = expireDateTime.getFullYear() - const month = String(expireDateTime.getMonth() + 1).padStart(2, '0') - const day = String(expireDateTime.getDate()).padStart(2, '0') - text = `已于 ${year}/${month}/${day} 到期` - highlight = true - } else { - const year = expireDateTime.getFullYear() - const month = String(expireDateTime.getMonth() + 1).padStart(2, '0') - const day = String(expireDateTime.getDate()).padStart(2, '0') - const hour = String(expireDateTime.getHours()).padStart(2, '0') - const minute = String(expireDateTime.getMinutes()).padStart(2, '0') - const second = String(expireDateTime.getSeconds()).padStart(2, '0') - text = `到期时间:${year}/${month}/${day} ${hour}:${minute}:${second}` - highlight = false - } - } - - return { text, highlight } + return formatExpireDate(first) }) function logout() { diff --git a/src/pages/UserCenter/index.vue b/src/pages/UserCenter/index.vue index 64ffc36..a5f9a3e 100644 --- a/src/pages/UserCenter/index.vue +++ b/src/pages/UserCenter/index.vue @@ -74,6 +74,7 @@ import UserCenterSkeleton from '@/components/user-center/UserCenterSkeleton.vue' import Logo from '@/pages/Home/logo.svg?component' import MobileLogo from '@/pages/Home/mobile-logo.svg?component' import request from '@/utils/request' +import { toast } from 'vue-sonner' const route = useRoute() const router = useRouter() @@ -144,48 +145,57 @@ const handlePlanSelect = (id: string) => { selectedPlanId.value = id } -const handlePay = (methodId: number | string) => { +const handlePay = async (methodId: number | string) => { if (isPaying.value) return + + // 1. 查找套餐并校验 const plan = plans.value.find((p) => p.id === selectedPlanId.value) - if (!plan) return - - 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 params: any = { - subscribe_id: plan.planId, - quantity: plan.quantity, - payment: methodId, + if (!plan) { + toast.error('请选择有效的订阅套餐') + return } - if (isRenewal) { - params.user_subscribe_id = already.id - } isPaying.value = true - request - .post(api, params) - .then((res: any) => { - request - .post('/api/v1/public/portal/order/checkout', { - orderNo: res.order_no, - returnUrl: `${window.location.origin}/user-center?order_no=${res.order_no}`, - }) - .then((checkoutRes: any) => { - if (checkoutRes.type === 'url' && 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 - }) - } - }) - .finally(() => { - isPaying.value = false - }) - }) - .catch(() => { - isPaying.value = false + + try { + // 2. 检查订阅状态以决定是“购买”还是“续费” + const { list } = await request.get('/api/v1/public/user/subscribe', { includeExpired: 'all' }) + const existingSub = list.find((s: any) => s.subscribe_id === plan.planId) + + const isRenewal = !!existingSub + const api = isRenewal ? '/api/v1/public/order/renewal' : '/api/v1/public/order/purchase' + + const orderParams = { + subscribe_id: plan.planId, + quantity: plan.quantity, + payment: methodId, + ...(isRenewal && { user_subscribe_id: existingSub.id }), // 仅续费时添加此字段 + } + + // 3. 创建订单 + const orderRes: any = await request.post(api, orderParams) + if (!orderRes?.order_no) throw new Error('订单创建失败') + + // 4. 获取支付收银台链接 + const checkoutRes: any = await request.post('/api/v1/public/portal/order/checkout', { + orderNo: orderRes.order_no, + returnUrl: `${window.location.origin}/user-center?order_no=${orderRes.order_no}`, }) + + // 5. 执行跳转 + if (checkoutRes.type === 'url' && checkoutRes.checkout_url) { + localStorage.setItem('pending_order_no', orderRes.order_no) + window.location.href = checkoutRes.checkout_url + } else { + throw new Error('支付网关配置异常') + } + } catch (error: any) { + console.error('支付流程异常:', error) + // 使用你之前调整过字号的 Toaster 提示错误 + toast.error(error.message || '发起支付失败,请稍后重试') + } finally { + isPaying.value = false + } } async function init() { diff --git a/src/pages/UserCenter/subscription.ts b/src/pages/UserCenter/subscription.ts new file mode 100644 index 0000000..f2465df --- /dev/null +++ b/src/pages/UserCenter/subscription.ts @@ -0,0 +1,59 @@ +/** + * 订阅对象的接口定义(根据你的后端数据结构调整) + */ +interface SubscriptionItem { + expire_time?: string | number | null +} + +export interface ExpireInfo { + text: string + highlight: boolean +} + +/** + * 格式化订阅到期信息 + * @param sub 整个订阅对象 item + */ +export function formatExpireDate(sub?: SubscriptionItem | null): ExpireInfo { + // 1. 处理对象不存在的情况 + if (!sub || !sub.expire_time) { + return { text: '尚未购买套餐', highlight: true } + } + + // 2. 解析日期(兼容 ISO 字符串和时间戳) + const timestamp = sub.expire_time + const isMs = typeof timestamp === 'number' && timestamp > 10000000000 + const expireDate = new Date( + isMs ? timestamp : typeof timestamp === 'number' ? timestamp * 1000 : timestamp, + ) + + if (isNaN(expireDate.getTime())) { + return { text: '套餐信息无效', highlight: false } + } + + // 3. 提取日期组件 + const now = new Date() + const isExpired = expireDate < now + + const y = expireDate.getFullYear() + const m = String(expireDate.getMonth() + 1).padStart(2, '0') + const d = String(expireDate.getDate()).padStart(2, '0') + const dateStr = `${y}/${m}/${d}` + + // 4. 根据是否过期返回不同格式 + if (isExpired) { + return { + text: `已于 ${dateStr} 到期`, + highlight: true, + } + } + + const hh = String(expireDate.getHours()).padStart(2, '0') + const mm = String(expireDate.getMinutes()).padStart(2, '0') + const ss = String(expireDate.getSeconds()).padStart(2, '0') + + return { + text: `到期时间:${dateStr} ${hh}:${mm}:${ss}`, + highlight: false, + } +}