订单和订阅修改
All checks were successful
site-dist-deploy / build-and-deploy (push) Successful in 1m4s

This commit is contained in:
speakeloudest 2026-01-06 23:13:39 -08:00
parent 7e12363976
commit 0aee63988c
4 changed files with 117 additions and 102 deletions

View File

@ -41,7 +41,10 @@
</Button>
</div>
<div class="text-center text-xs" :class="{ 'text-[#FF00B7]': expireDateInfo.highlight }">
<div
class="text-center text-xs text-white tabular-nums"
:class="{ 'text-[#FF00B7]': expireDateInfo.highlight }"
>
{{ expireDateInfo.text }}
</div>
</div>
@ -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() {

View File

@ -8,7 +8,10 @@
<img src="../avatar.png" class="size-[60px]" alt="" />
<div class="flex h-full flex-col justify-center text-white">
<div class="text-xl font-semibold">{{ userInfo.email }}</div>
<div class="text-xs" :class="{ 'text-[#FF00B7]': expireDateInfo.highlight }">
<div
class="text-xs tabular-nums"
:class="{ 'text-[#FF00B7]': expireDateInfo.highlight }"
>
{{ expireDateInfo.text }}
</div>
</div>
@ -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() {

View File

@ -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() {

View File

@ -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,
}
}