4 Commits

Author SHA1 Message Date
Eamon-meng
93110954bb 更新订单详情显示字段名称 2026-04-02 14:08:32 +08:00
Eamon-meng
8ce5f99a24 开启充值和余额支付功能 2026-03-31 16:11:47 +08:00
Eamon-meng
e27869fb4a 重新计算价格显示 2026-03-31 16:11:46 +08:00
Eamon-meng
01c4afd209 更新发布v1.2.3版本 2026-03-31 16:11:46 +08:00
15 changed files with 169 additions and 138 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "lanhu-web", "name": "lanhu-web",
"version": "1.2.2", "version": "1.2.3",
"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

@@ -17,8 +17,8 @@ export default function HelpMenu() {
icon={h01} icon={h01}
title="提取 IP" title="提取 IP"
items={[ items={[
{lead: '短效 IP 提取', href: '/collect?type=short'}, {lead: '短效/长效 IP 提取', href: '/collect?type=short'},
{lead: '长效 IP 提取', href: '/collect?type=long'}, // {lead: '长效 IP 提取', href: '/collect?type=long'},
]} ]}
/> />
<Column <Column

View File

@@ -16,7 +16,7 @@ export default function CollectPage(props: CollectPageProps) {
// </Wrap> // </Wrap>
// </main> // </main>
<HomePage path={[ <HomePage path={[
{label: '短效IP 提取', href: '/collect'}, {label: '短效/长效IP 提取', href: '/collect'},
]}> ]}>
<Wrap> <Wrap>
<Extract/> <Extract/>

View File

@@ -2,7 +2,7 @@ import {ReactNode} from 'react'
import Header from './header' import Header from './header'
import Footer from './footer' import Footer from './footer'
import Script from 'next/script' import Script from 'next/script'
import {MessageCircleMoreIcon} from 'lucide-react'
export type HomeLayoutProps = { export type HomeLayoutProps = {
children: ReactNode children: ReactNode
} }
@@ -19,7 +19,21 @@ export default function HomeLayout(props: HomeLayoutProps) {
{/* 页脚 */} {/* 页脚 */}
<Footer/> <Footer/>
<Script id="qd2852138148beb7882a4a6a3e5ff5b569436003e7dc" src="https://wp.qiye.qq.com/qidian/2852138148/beb7882a4a6a3e5ff5b569436003e7dc" async defer></Script> {/* <Script id="qd2852138148beb7882a4a6a3e5ff5b569436003e7dc" src="https://wp.qiye.qq.com/qidian/2852138148/beb7882a4a6a3e5ff5b569436003e7dc" async defer></Script> */}
<a
href="https://wpa1.qq.com/K0s0cvwf?_type=wpa&qidian=true"
target="_blank"
rel="noopener noreferrer"
className="fixed bottom-6 right-6 z-50 w-14 h-14 rounded-full bg-blue-600 hover:bg-blue-700 shadow-lg hover:shadow-xl transition-all duration-300 hover:scale-110 flex items-center justify-center group"
aria-label="在线客服"
>
<span className="text-white font-bold text-lg"></span>
<span className="text-white font-bold text-lg"></span>
<span className="absolute -top-2 -right-2 flex items-center justify-center w-6 h-6 bg-red-500 rounded-full text-white shadow-md">
<MessageCircleMoreIcon size={14}/>
</span>
</a>
</div> </div>
) )
} }

View File

@@ -59,7 +59,7 @@ export default async function UserCenter() {
</> </>
)} )}
</div> </div>
{/* <div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<h4 className="text-sm text-weak"></h4> <h4 className="text-sm text-weak"></h4>
<div className="flex justify-between items-baseline"> <div className="flex justify-between items-baseline">
<p className="text-xl text-accent"> <p className="text-xl text-accent">
@@ -68,7 +68,7 @@ export default async function UserCenter() {
</p> </p>
<RechargeModal/> <RechargeModal/>
</div> </div>
</div> */} </div>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<h4 className="text-sm text-weak"></h4> <h4 className="text-sm text-weak"></h4>
<div className="flex justify-around gap-2"> <div className="flex justify-around gap-2">

View File

@@ -165,7 +165,7 @@ export default function ResourceList({resourceType}: ResourceListProps) {
const live = resourceKey === 'long' const live = resourceKey === 'long'
? (row.original as Resource<2>).long.live ? (row.original as Resource<2>).long.live
: (row.original as Resource<1>).short.live : (row.original as Resource<1>).short.live
return <span>{isLong ? `${live}小时` : `${live / 60}分钟`}</span> return <span>{isLong ? `${live}小时` : `${live}分钟`}</span>
}, },
}, },
{ {

View File

@@ -337,6 +337,7 @@ function SelectResource() {
setStatus('load') setStatus('load')
try { try {
const resp = await allResource() const resp = await allResource()
console.log(resp, '/api/resource/all')
if (!resp.success) { if (!resp.success) {
throw new Error('获取套餐失败,请稍后再试') throw new Error('获取套餐失败,请稍后再试')
@@ -381,102 +382,103 @@ function SelectResource() {
<Loader className="animate-spin" size={20}/> <Loader className="animate-spin" size={20}/>
<span></span> <span></span>
</div> </div>
) : resources.map((resource, i) => ( ) : (
<> <>
<SelectItem {resources.map(resource => (
key={`${resource.id}`} <SelectItem
value={String(resource.id)} key={resource.id}
className="p-3"> value={String(resource.id)}
<div className="flex flex-col gap-2 w-72"> className="p-3">
{resource.type === 1 && resource.short.type === 1 && ( <div className="flex flex-col gap-2 w-72">
<> {resource.type === 1 && resource.short.type === 1 && (
<div className="flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md text-sm"> <>
<Timer size={20}/> <div className="flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md text-sm">
<span>{name(resource)}</span> <Timer size={20}/>
</div> <span>{name(resource)}</span>
<div className="flex text-xs text-weak"> </div>
<span>{resource.resource_no}</span> <div className="flex text-xs text-weak">
</div> <span>{resource.resource_no}</span>
<div className="flex justify-between gap-2 text-xs text-weak"> </div>
<span> <div className="flex justify-between gap-2 text-xs text-weak">
<span>
{format(resource.short.expire_at, 'yyyy-MM-dd HH:mm')}
</span> {format(resource.short.expire_at, 'yyyy-MM-dd HH:mm')}
<span>{intlFormatDistance(resource.short.expire_at, new Date())}</span> </span>
</div> <span>{intlFormatDistance(resource.short.expire_at, new Date())}</span>
</> </div>
)} </>
{resource.type === 1 && resource.short.type === 2 && ( )}
<> {resource.type === 1 && resource.short.type === 2 && (
<div className="flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md text-sm"> <>
<Box size={20}/> <div className="flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md text-sm">
<span>{name(resource)}</span> <Box size={20}/>
</div> <span>{name(resource)}</span>
<div className="flex text-xs text-weak"> </div>
<span>{resource.resource_no}</span> <div className="flex text-xs text-weak">
</div> <span>{resource.resource_no}</span>
<div className="flex justify-between gap-2 text-xs text-weak"> </div>
<span> <div className="flex justify-between gap-2 text-xs text-weak">
<span>
{resource.short.used}
{' '} {resource.short.used}
/ {' '}
{resource.short.quota} /
</span> {resource.short.quota}
<span> </span>
<span>
{resource.short.quota - resource.short.used}
</span> {resource.short.quota - resource.short.used}
</div> </span>
</> </div>
)} </>
{resource.type === 2 && resource.long.type === 1 && ( )}
<> {resource.type === 2 && resource.long.type === 1 && (
<div className="flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md text-sm"> <>
<Timer size={20}/> <div className="flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md text-sm">
<span>{name(resource)}</span> <Timer size={20}/>
</div> <span>{name(resource)}</span>
<div className="flex text-xs text-weak"> </div>
<span>{resource.resource_no}</span> <div className="flex text-xs text-weak">
</div> <span>{resource.resource_no}</span>
<div className="flex justify-between gap-2 text-xs text-weak"> </div>
<span> <div className="flex justify-between gap-2 text-xs text-weak">
<span>
{format(resource.long.expire_at, 'yyyy-MM-dd HH:mm')}
</span> {format(resource.long.expire_at, 'yyyy-MM-dd HH:mm')}
<span>{intlFormatDistance(resource.long.expire_at, new Date())}</span> </span>
</div> <span>{intlFormatDistance(resource.long.expire_at, new Date())}</span>
</> </div>
)} </>
{resource.type === 2 && resource.long.type === 2 && ( )}
<> {resource.type === 2 && resource.long.type === 2 && (
<div className="flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md text-sm"> <>
<Box size={20}/> <div className="flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md text-sm">
<span>{name(resource)}</span> <Box size={20}/>
</div> <span>{name(resource)}</span>
<div className="flex text-xs text-weak"> </div>
<span>{resource.resource_no}</span> <div className="flex text-xs text-weak">
</div> <span>{resource.resource_no}</span>
<div className="flex justify-between gap-2 text-xs text-weak"> </div>
<span> <div className="flex justify-between gap-2 text-xs text-weak">
<span>
{resource.long.used}
{' '} {resource.long.used}
/ {' '}
{resource.long.quota} /
</span> {resource.long.quota}
<span> </span>
<span>
{resource.long.quota - resource.long.used}
</span> {resource.long.quota - resource.long.used}
</div> </span>
</> </div>
)} </>
</div> )}
</SelectItem> </div>
{i < resources.length - 1 && <SelectSeparator className="m-1"/>} </SelectItem>
))}
</> </>
))} )}
</SelectContent> </SelectContent>
</Select> </Select>
)} )}
@@ -651,9 +653,9 @@ function name(resource: Resource) {
// 短效套餐 // 短效套餐
switch (resource.short.type) { switch (resource.short.type) {
case 1: case 1:
return `短效包时 ${resource.short.live / 60} 分钟` return `短效包时 ${resource.short.live} 分钟`
case 2: case 2:
return `短效包量 ${resource.short.live / 60} 分钟` return `短效包量 ${resource.short.live} 分钟`
} }
break break

View File

@@ -62,12 +62,12 @@ export function PaymentModal(props: PaymentModalProps) {
if (!open) handleClose() if (!open) handleClose()
}}> }}>
{/* {props.platform === TradePlatform.Mobile {props.platform === TradePlatform.Mobile
? <MobilePayment {...props} onClose={handleClose}/> ? <MobilePayment {...props} onClose={handleClose}/>
: <DesktopPayment {...props} onClose={handleClose}/> : <DesktopPayment {...props} onClose={handleClose}/>
} */} }
<UniversalDesktopPayment {...props} onClose={handleClose}/> {/* <UniversalDesktopPayment {...props} onClose={handleClose}/> */}
</Dialog> </Dialog>
) )
} }

View File

@@ -28,7 +28,7 @@ export default function LongForm() {
quota: 500, quota: 500,
expire: '30', // 天 expire: '30', // 天
daily_limit: 100, daily_limit: 100,
pay_type: 'wechat', // 余额支付 pay_type: 'balance', // 余额支付
}, },
}) })

View File

@@ -32,12 +32,13 @@ export default function Right() {
const resp = await getPrice({ const resp = await getPrice({
type: 2, type: 2,
long: { long: {
live: Number(live), live: Number(live) * 60,
mode: Number(mode), mode: Number(mode),
quota: mode === '1' ? Number(dailyLimit) : Number(quota), quota: mode === '1' ? Number(dailyLimit) : Number(quota),
expire: mode === '1' ? Number(expire) : undefined, expire: mode === '1' ? Number(expire) : undefined,
}, },
}) })
if (!resp.success) { if (!resp.success) {
throw new Error('获取价格失败') throw new Error('获取价格失败')
} }
@@ -49,6 +50,7 @@ export default function Right() {
}) })
} }
catch (error) { catch (error) {
console.error('获取价格失败:', error)
setPriceData({ setPriceData({
price: '0.00', price: '0.00',
discounted_price: '0.00', discounted_price: '0.00',
@@ -114,19 +116,19 @@ export default function Right() {
</span> </span>
</li> </li>
<li className="flex justify-between items-center"> <li className="flex justify-between items-center">
<span className="text-sm text-gray-500"></span> <span className="text-sm text-gray-500"></span>
<span className="text-sm"> <span className="text-sm">
{price} {price}
</span> </span>
</li> </li>
{discounted === 1 ? '' : ( {/* {discounted === 1 ? '' : (
<li className="flex justify-between items-center"> <li className="flex justify-between items-center">
<span className="text-sm text-gray-500">总折扣</span> <span className="text-sm text-gray-500">总折扣</span>
<span className="text-sm"> <span className="text-sm">
-¥{discounted} -¥{discounted}
</span> </span>
</li> </li>
)} )} */}
</> </>
)} )}
</ul> </ul>
@@ -156,7 +158,7 @@ function BalanceOrLogin(props: {
const profile = use(useProfileStore(store => store.profile)) const profile = use(useProfileStore(store => store.profile))
return profile ? ( return profile ? (
<> <>
{/* <FieldPayment/> */} <FieldPayment/>
<Pay <Pay
method={props.method} method={props.method}
balance={profile.balance} balance={profile.balance}
@@ -165,9 +167,9 @@ function BalanceOrLogin(props: {
type: 2, type: 2,
long: { long: {
mode: Number(props.mode), mode: Number(props.mode),
live: Number(props.live), live: Number(props.live) * 60,
expire: Number(props.expire), expire: props.mode === '1' ? Number(props.expire) : undefined,
quota: props.mode === '1' ? props.dailyLimit : props.quota, quota: props.mode === '1' ? Number(props.dailyLimit) : Number(props.quota),
}, },
}}/> }}/>
</> </>

View File

@@ -47,6 +47,7 @@ export default function Pay(props: PayProps) {
payment_method: method, payment_method: method,
payment_platform: TradePlatform.Desktop, payment_platform: TradePlatform.Desktop,
} }
console.log(req, 'req')
const resp = await prepareResource(req) const resp = await prepareResource(req)

View File

@@ -4,9 +4,17 @@ import {FormField} from '@/components/ui/form'
import Image from 'next/image' import Image from 'next/image'
import alipay from '../_assets/alipay.svg' import alipay from '../_assets/alipay.svg'
import wechat from '../_assets/wechat.svg' import wechat from '../_assets/wechat.svg'
import balance from '../_assets/balance.svg'
import RechargeModal from '@/components/composites/recharge'
import {useProfileStore} from '@/components/stores/profile'
import {use} from 'react'
import Link from 'next/link'
import {buttonVariants} from '@/components/ui/button'
export function FieldPayment() { export function FieldPayment() {
return ( const profile = use(useProfileStore(store => store.profile))
return profile ? (
<FormField name="pay_type" label="支付方式" className="flex flex-col gap-6"> <FormField name="pay_type" label="支付方式" className="flex flex-col gap-6">
{({id, field}) => ( {({id, field}) => (
<RadioGroup <RadioGroup
@@ -15,7 +23,7 @@ export function FieldPayment() {
onValueChange={field.onChange} onValueChange={field.onChange}
className="flex flex-col gap-3"> className="flex flex-col gap-3">
{/* <div className="w-full p-3 flex flex-col gap-4 bg-gray-100 rounded-md"> <div className="w-full p-3 flex flex-col gap-4 bg-gray-100 rounded-md">
<p className="flex items-center gap-3"> <p className="flex items-center gap-3">
<Image src={balance} alt="余额icon"/> <Image src={balance} alt="余额icon"/>
<span className="text-sm text-gray-500"></span> <span className="text-sm text-gray-500"></span>
@@ -24,16 +32,15 @@ export function FieldPayment() {
<span className="text-xl">{profile.balance}</span> <span className="text-xl">{profile.balance}</span>
<RechargeModal/> <RechargeModal/>
</p> </p>
</div> */} </div>
<FormOption
{/* <FormOption
id={`${id}-balance`} id={`${id}-balance`}
value="balance" value="balance"
compare={field.value} compare={field.value}
className="p-3 w-full flex-row gap-2 justify-center"> className="p-3 w-full flex-row gap-2 justify-center">
<Image src={balance} alt="余额 icon"/> <Image src={balance} alt="余额 icon"/>
<span></span> <span></span>
</FormOption> */} </FormOption>
<FormOption <FormOption
id={`${id}-wechat`} id={`${id}-wechat`}
value="wechat" value="wechat"
@@ -53,5 +60,9 @@ export function FieldPayment() {
</RadioGroup> </RadioGroup>
)} )}
</FormField> </FormField>
) : (
<Link href="/login" className={buttonVariants()}>
</Link>
) )
} }

View File

@@ -68,11 +68,11 @@ export default function Center() {
onValueChange={field.onChange} onValueChange={field.onChange}
className="grid grid-cols-[repeat(auto-fill,minmax(120px,1fr))] gap-4"> className="grid grid-cols-[repeat(auto-fill,minmax(120px,1fr))] gap-4">
<FormOption id={`${id}-3`} value="180" label="3 分钟" description="¥0.005/IP" compare={field.value}/> <FormOption id={`${id}-3`} value="3" label="3 分钟" description="¥0.005/IP" compare={field.value}/>
<FormOption id={`${id}-5`} value="300" label="5 分钟" description="¥0.01/IP" compare={field.value}/> <FormOption id={`${id}-5`} value="5" label="5 分钟" description="¥0.01/IP" compare={field.value}/>
<FormOption id={`${id}-10`} value="600" label="10 分钟" description="¥0.02/IP" compare={field.value}/> <FormOption id={`${id}-10`} value="10" label="10 分钟" description="¥0.02/IP" compare={field.value}/>
<FormOption id={`${id}-20`} value="1200" label="20 分钟" description="¥0.03/IP" compare={field.value}/> <FormOption id={`${id}-20`} value="15" label="15 分钟" description="¥0.03/IP" compare={field.value}/>
<FormOption id={`${id}-30`} value="1800" label="30 分钟" description="¥0.06/IP" compare={field.value}/> <FormOption id={`${id}-30`} value="30" label="30 分钟" description="¥0.06/IP" compare={field.value}/>
</RadioGroup> </RadioGroup>
)} )}
</FormField> </FormField>

View File

@@ -9,7 +9,7 @@ import {zodResolver} from '@hookform/resolvers/zod'
// 定义表单验证架构 // 定义表单验证架构
const schema = z.object({ const schema = z.object({
type: z.enum(['1', '2']).default('2'), type: z.enum(['1', '2']).default('2'),
live: z.enum(['180', '300', '600', '1200', '1800']), live: z.enum(['3', '5', '10', '15', '30']),
quota: z.number().min(10000, '购买数量不能少于10000个'), quota: z.number().min(10000, '购买数量不能少于10000个'),
expire: z.enum(['7', '15', '30', '90', '180', '365']), expire: z.enum(['7', '15', '30', '90', '180', '365']),
daily_limit: z.number().min(2000, '每日限额不能少于2000个'), daily_limit: z.number().min(2000, '每日限额不能少于2000个'),
@@ -24,11 +24,11 @@ export default function ShortForm() {
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: { defaultValues: {
type: '2', // 默认为包量套餐 type: '2', // 默认为包量套餐
live: '180', // 分钟 live: '3', // 分钟
quota: 10_000, // >= 10000 quota: 10_000, // >= 10000
expire: '30', // 天 expire: '30', // 天
daily_limit: 2_000, // >= 2000 daily_limit: 2_000, // >= 2000
pay_type: 'wechat', // 余额支付 pay_type: 'balance', // 余额支付
}, },
}) })

View File

@@ -38,6 +38,7 @@ export default function Right() {
expire: mode === '1' ? Number(expire) : undefined, expire: mode === '1' ? Number(expire) : undefined,
}, },
}) })
if (!priceResponse.success) { if (!priceResponse.success) {
throw new Error('获取价格失败') throw new Error('获取价格失败')
} }
@@ -78,7 +79,7 @@ export default function Right() {
<li className="flex justify-between items-center"> <li className="flex justify-between items-center">
<span className="text-sm text-gray-500">IP </span> <span className="text-sm text-gray-500">IP </span>
<span className="text-sm"> <span className="text-sm">
{Number(live) / 60} {live}
{' '} {' '}
</span> </span>
@@ -116,19 +117,19 @@ export default function Right() {
</span> </span>
</li> </li>
<li className="flex justify-between items-center"> <li className="flex justify-between items-center">
<span className="text-sm text-gray-500"></span> <span className="text-sm text-gray-500"></span>
<span className="text-sm"> <span className="text-sm">
{price} {price}
</span> </span>
</li> </li>
{discounted === 1 ? '' : ( {/* {discounted === 1 ? '' : (
<li className="flex justify-between items-center"> <li className="flex justify-between items-center">
<span className="text-sm text-gray-500">总折扣</span> <span className="text-sm text-gray-500">总折扣</span>
<span className="text-sm"> <span className="text-sm">
-¥{discounted === 1 ? '' : discounted} -¥{discounted === 1 ? '' : discounted}
</span> </span>
</li> </li>
)} )} */}
</> </>
)} )}
</ul> </ul>
@@ -158,7 +159,7 @@ function BalanceOrLogin(props: {
const profile = use(useProfileStore(store => store.profile)) const profile = use(useProfileStore(store => store.profile))
return profile ? ( return profile ? (
<> <>
{/* <FieldPayment/> */} <FieldPayment/>
<Pay <Pay
method={props.method} method={props.method}
balance={profile.balance} balance={profile.balance}
@@ -168,8 +169,8 @@ function BalanceOrLogin(props: {
short: { short: {
mode: Number(props.mode), mode: Number(props.mode),
live: Number(props.live), live: Number(props.live),
expire: Number(props.expire), expire: props.mode === '1' ? Number(props.expire) : undefined,
quota: props.mode === '1' ? props.dailyLimit : props.quota, quota: props.mode === '1' ? Number(props.dailyLimit) : Number(props.quota),
}, },
}}/> }}/>
</> </>