修复用户未/授权购买套餐价格计算 & 发布v1.6.0版本

This commit is contained in:
Eamon-meng
2026-04-16 17:46:11 +08:00
parent 5607217625
commit 8b65a1745c
7 changed files with 97 additions and 47 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "lanhu-web", "name": "lanhu-web",
"version": "1.5.0", "version": "1.6.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -H 0.0.0.0 --turbopack", "dev": "next dev -H 0.0.0.0 --turbopack",

View File

@@ -89,6 +89,14 @@ export async function payClose(props: {
} }
export async function getPrice(props: CreateResourceReq) { export async function getPrice(props: CreateResourceReq) {
return callByUser<{
price: string
actual?: string
discounted?: string
}>('/api/resource/price', props)
}
export async function getPriceHome(props: CreateResourceReq) {
return callByDevice<{ return callByDevice<{
price: string price: string
actual?: string actual?: string

View File

@@ -1,6 +1,6 @@
'use client' 'use client'
import {useContext, useState} from 'react' import {useContext, useEffect, useState} from 'react'
import {useRouter} from 'next/navigation' import {useRouter} from 'next/navigation'
import {X} from 'lucide-react' import {X} from 'lucide-react'
import {HeaderContext} from './common' import {HeaderContext} from './common'
@@ -21,6 +21,8 @@ import h03 from '@/assets/header/help/03.svg'
import {merge} from '@/lib/utils' import {merge} from '@/lib/utils'
import Link from 'next/link' import Link from 'next/link'
import logo from '@/assets/logo.webp' import logo from '@/assets/logo.webp'
import {Product} from '@/lib/models/product'
import {listProductHome} from '@/actions/product'
export type MobileMenuProps = {} export type MobileMenuProps = {}
@@ -37,7 +39,18 @@ export default function MobileMenu(props: MobileMenuProps) {
ctx.setMenu(false) ctx.setMenu(false)
router.push(href) router.push(href)
} }
const [productList, setProductList] = useState<Product[]>([])
useEffect(() => {
const fetchProducts = async () => {
const res = await listProductHome({})
if (res.success) {
setProductList(res.data)
}
}
fetchProducts()
}, [])
const shortProduct = productList.find(p => p.name?.includes('短效') || p.code === 'short')
const longProduct = productList.find(p => p.name?.includes('长效') || p.code === 'long')
return ( return (
<div className="h-full flex flex-col bg-white"> <div className="h-full flex flex-col bg-white">
<div className="flex items-center justify-between px-4 h-16 border-b border-gray-100"> <div className="flex items-center justify-between px-4 h-16 border-b border-gray-100">
@@ -87,20 +100,24 @@ export default function MobileMenu(props: MobileMenuProps) {
{productTab === 'domestic' && ( {productTab === 'domestic' && (
<div className="space-y-2"> <div className="space-y-2">
<ProductItem {shortProduct && (
icon={prod} <ProductItem
label="短效动态IP" icon={prod}
badge="最低4.5折" label="短效动态IP"
href="/product?type=short" badge="最低4.5折"
onNavigate={navigate} href={`/product?type=${shortProduct.code}`}
/> onNavigate={navigate}
<ProductItem />
icon={prod} )}
label="长效静态IP" {longProduct && (
badge="最低4.5折" <ProductItem
href="/product?type=long" icon={prod}
onNavigate={navigate} label="长效静态IP"
/> badge="最低4.5折"
href={`/product?type=${longProduct.code}`}
onNavigate={navigate}
/>
)}
<ProductItem <ProductItem
icon={custom} icon={custom}
label="优质/企业/精选IP" label="优质/企业/精选IP"

View File

@@ -1,12 +1,13 @@
'use client' 'use client'
import {ReactNode, useEffect, useState} from 'react' import {ReactNode, use, useEffect, useState} from 'react'
import {merge} from '@/lib/utils' import {merge} from '@/lib/utils'
import {Tabs, TabsContent, TabsList, TabsTrigger} from '@/components/ui/tabs' import {Tabs, TabsContent, TabsList, TabsTrigger} from '@/components/ui/tabs'
import LongForm from '@/components/composites/purchase/long/form' import LongForm from '@/components/composites/purchase/long/form'
import ShortForm from '@/components/composites/purchase/short/form' import ShortForm from '@/components/composites/purchase/short/form'
import {usePathname, useRouter, useSearchParams} from 'next/navigation' import {usePathname, useRouter, useSearchParams} from 'next/navigation'
import SelfDesc from '@/components/features/self-desc' import SelfDesc from '@/components/features/self-desc'
import {listProduct, ProductItem} from '@/actions/product' import {listProduct, listProductHome, ProductItem} from '@/actions/product'
import {useProfileStore} from '@/components/stores/profile'
export type TabType = 'short' | 'long' | 'fixed' | 'custom' export type TabType = 'short' | 'long' | 'fixed' | 'custom'
export default function Purchase() { export default function Purchase() {
@@ -22,19 +23,21 @@ export default function Purchase() {
newParams.set('type', tab) newParams.set('type', tab)
router.push(`${path}?${newParams.toString()}`) router.push(`${path}?${newParams.toString()}`)
} }
const profile = use(useProfileStore(store => store.profile))
useEffect(() => { useEffect(() => {
const fetchProducts = async () => { const fetchProducts = async () => {
const res = await listProduct({}) const res = profile
? await listProduct({})
: await listProductHome({})
if (res.success) { if (res.success) {
setProductList(res.data) setProductList(res.data)
} }
} }
fetchProducts() fetchProducts()
}, []) }, [profile])
const currentProduct = productList.find(item => item.code === tab)
const currentSkuList = currentProduct?.skus || []
const componentMap: Record<string, React.FC<{skuList: ProductItem['skus']}>> = { const componentMap: Record<string, React.FC<{skuList: ProductItem['skus']}>> = {
short: ShortForm, short: ShortForm,
long: LongForm, long: LongForm,

View File

@@ -8,7 +8,7 @@ import {merge} from '@/lib/utils'
import {useFormContext, useWatch} from 'react-hook-form' import {useFormContext, useWatch} from 'react-hook-form'
import {Schema} from '@/components/composites/purchase/long/form' import {Schema} from '@/components/composites/purchase/long/form'
import {Card} from '@/components/ui/card' import {Card} from '@/components/ui/card'
import {getPrice} from '@/actions/resource' import {getPrice, getPriceHome} from '@/actions/resource'
import {ExtraResp} from '@/lib/api' import {ExtraResp} from '@/lib/api'
import {FieldPayment} from '../shared/field-payment' import {FieldPayment} from '../shared/field-payment'
@@ -25,19 +25,29 @@ export default function Right() {
actual: '0.00', actual: '0.00',
discounted: '0.00', discounted: '0.00',
}) })
const profile = use(useProfileStore(store => store.profile))
useEffect(() => { useEffect(() => {
const price = async () => { const price = async () => {
try { try {
const resp = await getPrice({ const resp = profile
type: 2, ? await getPrice({
long: { type: 2,
live: Number(live), long: {
mode: Number(mode), live: Number(live),
quota: mode === '1' ? Number(dailyLimit) : Number(quota), mode: Number(mode),
expire: mode === '1' ? Number(expire) : undefined, quota: mode === '1' ? Number(dailyLimit) : Number(quota),
}, expire: mode === '1' ? Number(expire) : undefined,
}) },
}) : await getPriceHome({
type: 1,
short: {
live: Number(live),
mode: Number(mode),
quota: mode === '1' ? Number(dailyLimit) : Number(quota),
expire: mode === '1' ? Number(expire) : undefined,
},
})
if (!resp.success) { if (!resp.success) {
throw new Error('获取价格失败') throw new Error('获取价格失败')
} }
@@ -57,7 +67,7 @@ export default function Right() {
} }
} }
price() price()
}, [dailyLimit, expire, live, quota, mode]) }, [dailyLimit, expire, live, quota, mode, profile])
const {price, actual: discountedPrice = ''} = priceData const {price, actual: discountedPrice = ''} = priceData
// 计算总折扣价(原价 - 实付价格) // 计算总折扣价(原价 - 实付价格)

View File

@@ -59,7 +59,7 @@ export default function ShortForm({skuList}: {skuList: ProductItem['skus']}) {
return ( return (
<Form form={form} className="flex flex-col lg:flex-row gap-4"> <Form form={form} className="flex flex-col lg:flex-row gap-4">
<Center {...{priceMap, liveList, expireList}}/> <Center {...{priceMap, liveList, expireList}}/>
<Right {...{skuList, priceMap}}/> <Right/>
</Form> </Form>
) )
} }

View File

@@ -8,12 +8,12 @@ import {merge} from '@/lib/utils'
import Pay from '@/components/composites/purchase/pay' import Pay from '@/components/composites/purchase/pay'
import {useFormContext, useWatch} from 'react-hook-form' import {useFormContext, useWatch} from 'react-hook-form'
import {Card} from '@/components/ui/card' import {Card} from '@/components/ui/card'
import {getPrice} from '@/actions/resource' import {getPrice, getPriceHome} from '@/actions/resource'
import {ExtraResp} from '@/lib/api' import {ExtraResp} from '@/lib/api'
import {FieldPayment} from '../shared/field-payment' import {FieldPayment} from '../shared/field-payment'
import {ProductItem} from '@/actions/product' import {ProductItem} from '@/actions/product'
export default function Right({skuList}: {skuList: ProductItem['skus']}) { export default function Right() {
const {control} = useFormContext<Schema>() const {control} = useFormContext<Schema>()
const method = useWatch({control, name: 'pay_type'}) const method = useWatch({control, name: 'pay_type'})
const live = useWatch({control, name: 'live'}) const live = useWatch({control, name: 'live'})
@@ -26,19 +26,31 @@ export default function Right({skuList}: {skuList: ProductItem['skus']}) {
actual: '0.00', actual: '0.00',
discounted: '0.00', discounted: '0.00',
}) })
const profile = use(useProfileStore(store => store.profile))
useEffect(() => { useEffect(() => {
const price = async () => { const price = async () => {
try { try {
const priceResponse = await getPrice({ const priceResponse = profile
type: 1, ? await getPrice({
short: { type: 1,
live: Number(live), short: {
mode: Number(mode), live: Number(live),
quota: mode === '1' ? Number(dailyLimit) : Number(quota), mode: Number(mode),
expire: mode === '1' ? Number(expire) : undefined, quota: mode === '1' ? Number(dailyLimit) : Number(quota),
}, expire: mode === '1' ? Number(expire) : undefined,
}) },
})
: await getPriceHome({
type: 1,
short: {
live: Number(live),
mode: Number(mode),
quota: mode === '1' ? Number(dailyLimit) : Number(quota),
expire: mode === '1' ? Number(expire) : undefined,
},
})
if (!priceResponse.success) { if (!priceResponse.success) {
throw new Error('获取价格失败') throw new Error('获取价格失败')
} }
@@ -60,7 +72,7 @@ export default function Right({skuList}: {skuList: ProductItem['skus']}) {
} }
} }
price() price()
}, [expire, live, quota, mode, dailyLimit]) }, [expire, live, quota, mode, dailyLimit, profile])
const {price, actual: discountedPrice = ''} = priceData const {price, actual: discountedPrice = ''} = priceData