优化完善套餐购买页面
This commit is contained in:
189
src/components/composites/purchase/shared/side-panel.tsx
Normal file
189
src/components/composites/purchase/shared/side-panel.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
'use client'
|
||||
|
||||
import {use, useEffect, useRef, useState} from 'react'
|
||||
import Link from 'next/link'
|
||||
import {useFormContext, useWatch} from 'react-hook-form'
|
||||
import {Card} from '@/components/ui/card'
|
||||
import {buttonVariants} from '@/components/ui/button'
|
||||
import {useProfileStore} from '@/components/stores/profile'
|
||||
import Pay from '@/components/composites/purchase/pay'
|
||||
import {FieldPayment} from './field-payment'
|
||||
import {buildPurchaseResource, PurchaseKind, PurchaseSelection} from './resource'
|
||||
import {getPrice, getPriceHome} from '@/actions/resource'
|
||||
import {ExtraResp} from '@/lib/api'
|
||||
import {formatPurchaseLiveLabel} from './sku'
|
||||
import {User} from '@/lib/models'
|
||||
import {PurchaseFormValues} from './form-values'
|
||||
|
||||
const emptyPrice: ExtraResp<typeof getPrice> = {
|
||||
price: '0.00',
|
||||
actual: '0.00',
|
||||
discounted: '0.00',
|
||||
}
|
||||
|
||||
export type PurchaseSidePanelProps = {
|
||||
kind: PurchaseKind
|
||||
}
|
||||
|
||||
export function PurchaseSidePanel(props: PurchaseSidePanelProps) {
|
||||
const {control} = useFormContext<PurchaseFormValues>()
|
||||
const method = useWatch<PurchaseFormValues>({control, name: 'pay_type'}) as PurchaseFormValues['pay_type']
|
||||
const mode = useWatch<PurchaseFormValues>({control, name: 'type'}) as PurchaseFormValues['type']
|
||||
const live = useWatch<PurchaseFormValues>({control, name: 'live'}) as PurchaseFormValues['live']
|
||||
const quota = useWatch<PurchaseFormValues>({control, name: 'quota'}) as PurchaseFormValues['quota']
|
||||
const expire = useWatch<PurchaseFormValues>({control, name: 'expire'}) as PurchaseFormValues['expire']
|
||||
const dailyLimit = useWatch<PurchaseFormValues>({control, name: 'daily_limit'}) as PurchaseFormValues['daily_limit']
|
||||
const profile = use(useProfileStore(store => store.profile))
|
||||
const selection: PurchaseSelection = {
|
||||
kind: props.kind,
|
||||
mode,
|
||||
live,
|
||||
quota,
|
||||
expire,
|
||||
dailyLimit,
|
||||
}
|
||||
const priceData = usePurchasePrice(profile, selection)
|
||||
const {price, actual: discountedPrice = '0.00'} = priceData
|
||||
const totalDiscount = getTotalDiscount(price, discountedPrice)
|
||||
const hasDiscount = Number(totalDiscount) > 0
|
||||
const liveLabel = formatPurchaseLiveLabel(live, props.kind)
|
||||
const resource = buildPurchaseResource(selection)
|
||||
|
||||
return (
|
||||
<Card className="flex-none basis-90 p-6 flex flex-col gap-6 relative">
|
||||
<h3>订单详情</h3>
|
||||
<ul className="flex flex-col gap-3">
|
||||
<li className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">套餐名称</span>
|
||||
<span className="text-sm">{mode === '2' ? '包量套餐' : '包时套餐'}</span>
|
||||
</li>
|
||||
<li className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">IP 时效</span>
|
||||
<span className="text-sm">{liveLabel}</span>
|
||||
</li>
|
||||
{mode === '2' ? (
|
||||
<>
|
||||
<li className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">购买 IP 量</span>
|
||||
<span className="text-sm">{quota} 个</span>
|
||||
</li>
|
||||
<li className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">原价</span>
|
||||
<span className="text-sm">¥{price}</span>
|
||||
</li>
|
||||
{hasDiscount && (
|
||||
<li className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">总折扣</span>
|
||||
<span className="text-sm">-¥{totalDiscount}</span>
|
||||
</li>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<li className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">套餐时长</span>
|
||||
<span className="text-sm">{expire} 天</span>
|
||||
</li>
|
||||
<li className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">每日限额</span>
|
||||
<span className="text-sm">{dailyLimit} 个</span>
|
||||
</li>
|
||||
<li className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">原价</span>
|
||||
<span className="text-sm">¥{price}</span>
|
||||
</li>
|
||||
{hasDiscount && (
|
||||
<li className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500">总折扣</span>
|
||||
<span className="text-sm">-¥{totalDiscount}</span>
|
||||
</li>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
<div className="border-b border-gray-200"></div>
|
||||
<p className="flex justify-between items-center">
|
||||
<span>实付价格</span>
|
||||
<span className="text-xl text-orange-500">¥{discountedPrice}</span>
|
||||
</p>
|
||||
{profile ? (
|
||||
<>
|
||||
<FieldPayment balance={profile.balance}/>
|
||||
<Pay
|
||||
method={method}
|
||||
balance={profile.balance}
|
||||
amount={discountedPrice}
|
||||
resource={resource}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<Link href="/login" className={buttonVariants()}>
|
||||
登录后支付
|
||||
</Link>
|
||||
)}
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function usePurchasePrice(profile: User | null, selection: PurchaseSelection) {
|
||||
const [priceData, setPriceData] = useState<ExtraResp<typeof getPrice>>(emptyPrice)
|
||||
const requestIdRef = useRef(0)
|
||||
const {kind, mode, live, quota, expire, dailyLimit} = selection
|
||||
|
||||
useEffect(() => {
|
||||
const requestId = ++requestIdRef.current
|
||||
|
||||
const loadPrice = async () => {
|
||||
try {
|
||||
const resource = buildPurchaseResource({
|
||||
kind,
|
||||
mode,
|
||||
live,
|
||||
quota,
|
||||
expire,
|
||||
dailyLimit,
|
||||
})
|
||||
const response = profile
|
||||
? await getPrice(resource)
|
||||
: await getPriceHome(resource)
|
||||
|
||||
if (requestId !== requestIdRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!response.success) {
|
||||
throw new Error(response.message || '获取价格失败')
|
||||
}
|
||||
|
||||
setPriceData({
|
||||
price: response.data.price,
|
||||
actual: response.data.actual ?? response.data.price ?? '0.00',
|
||||
discounted: response.data.discounted ?? '0.00',
|
||||
})
|
||||
}
|
||||
catch (error) {
|
||||
if (requestId !== requestIdRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
console.error('获取价格失败:', error)
|
||||
setPriceData(emptyPrice)
|
||||
}
|
||||
}
|
||||
|
||||
loadPrice()
|
||||
}, [dailyLimit, expire, kind, live, mode, profile, quota])
|
||||
|
||||
return priceData
|
||||
}
|
||||
|
||||
function getTotalDiscount(price: string, discountedPrice: string) {
|
||||
const originalPrice = Number.parseFloat(price)
|
||||
const actualPrice = Number.parseFloat(discountedPrice)
|
||||
|
||||
if (Number.isNaN(originalPrice) || Number.isNaN(actualPrice)) {
|
||||
return '0.00'
|
||||
}
|
||||
|
||||
return (originalPrice - actualPrice).toFixed(2)
|
||||
}
|
||||
Reference in New Issue
Block a user