2025-04-12 11:10:51 +08:00
|
|
|
|
'use client'
|
|
|
|
|
|
import {z} from 'zod'
|
|
|
|
|
|
import {zodResolver} from '@hookform/resolvers/zod'
|
|
|
|
|
|
import {Form, FormField, FormLabel} from '@/components/ui/form'
|
|
|
|
|
|
import {RadioGroup, RadioGroupItem} from '@/components/ui/radio-group'
|
|
|
|
|
|
import {Input} from '@/components/ui/input'
|
|
|
|
|
|
import {Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue} from '@/components/ui/select'
|
|
|
|
|
|
import {Button} from '@/components/ui/button'
|
|
|
|
|
|
import {useForm} from 'react-hook-form'
|
|
|
|
|
|
import {Alert, AlertTitle} from '@/components/ui/alert'
|
2025-04-14 16:00:46 +08:00
|
|
|
|
import {Box, CircleAlert, CopyIcon, ExternalLinkIcon, Loader, Timer} from 'lucide-react'
|
|
|
|
|
|
import {useEffect, useMemo, useState} from 'react'
|
2025-04-12 11:10:51 +08:00
|
|
|
|
import {useStatus} from '@/lib/states'
|
|
|
|
|
|
import {allResource} from '@/actions/resource'
|
|
|
|
|
|
import {Resource, name} from '@/lib/models'
|
|
|
|
|
|
import {format, intlFormatDistance} from 'date-fns'
|
2025-04-14 16:00:46 +08:00
|
|
|
|
import {usePathname} from 'next/navigation'
|
|
|
|
|
|
import {toast} from 'sonner'
|
|
|
|
|
|
import {merge} from '@/lib/utils'
|
2025-04-12 11:10:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
2025-04-14 16:00:46 +08:00
|
|
|
|
type ExtractProps = {
|
|
|
|
|
|
className?: string
|
|
|
|
|
|
}
|
2025-04-12 11:10:51 +08:00
|
|
|
|
|
|
|
|
|
|
export default function Extract(props: ExtractProps) {
|
|
|
|
|
|
const [resources, setResources] = useState<Resource[]>([])
|
|
|
|
|
|
const [status, setStatus] = useStatus()
|
|
|
|
|
|
|
|
|
|
|
|
const schema = z.object({
|
2025-04-14 16:00:46 +08:00
|
|
|
|
resource: z.number({required_error: '请选择套餐'}),
|
2025-04-12 11:10:51 +08:00
|
|
|
|
prov: z.string().optional(),
|
|
|
|
|
|
city: z.string().optional(),
|
|
|
|
|
|
regionType: z.enum(['unlimited', 'specific']).default('unlimited'),
|
2025-04-14 16:00:46 +08:00
|
|
|
|
isp: z.enum(['all', '1', '2', '3'], {required_error: '请选择运营商'}),
|
|
|
|
|
|
proto: z.enum(['all', '1', '2', '3'], {required_error: '请选择协议'}),
|
|
|
|
|
|
authType: z.enum([ '1', '2'], {required_error: '请选择认证方式'}),
|
|
|
|
|
|
distinct: z.enum(['1', '0'], {required_error: '请选择去重选项'}),
|
|
|
|
|
|
format: z.enum(['text', 'json'], {required_error: '请选择导出格式'}),
|
|
|
|
|
|
separator: z.string({required_error: '请选择分隔符'}),
|
|
|
|
|
|
breaker: z.string({required_error: '请选择换行符'}),
|
|
|
|
|
|
count: z.number({required_error: '请输入有效的数量'}).min(1),
|
2025-04-12 11:10:51 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
type Schema = z.infer<typeof schema>
|
|
|
|
|
|
|
|
|
|
|
|
const form = useForm<Schema>({
|
|
|
|
|
|
resolver: zodResolver(schema),
|
|
|
|
|
|
defaultValues: {
|
|
|
|
|
|
regionType: 'unlimited',
|
|
|
|
|
|
isp: 'all',
|
|
|
|
|
|
proto: 'all',
|
2025-04-14 16:00:46 +08:00
|
|
|
|
authType: '1',
|
2025-04-12 11:10:51 +08:00
|
|
|
|
count: 1,
|
|
|
|
|
|
distinct: '1',
|
|
|
|
|
|
format: 'text',
|
2025-04-14 16:00:46 +08:00
|
|
|
|
breaker: '13,10',
|
|
|
|
|
|
separator: '124',
|
2025-04-12 11:10:51 +08:00
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const regionType = form.watch('regionType')
|
|
|
|
|
|
|
2025-04-14 16:00:46 +08:00
|
|
|
|
const resource = form.watch('resource')
|
|
|
|
|
|
const prov = form.watch('prov')
|
|
|
|
|
|
const city = form.watch('city')
|
|
|
|
|
|
const isp = form.watch('isp')
|
|
|
|
|
|
const proto = form.watch('proto')
|
|
|
|
|
|
const authType = form.watch('authType')
|
|
|
|
|
|
const distinct = form.watch('distinct')
|
|
|
|
|
|
const formatType = form.watch('format')
|
|
|
|
|
|
const separator = form.watch('separator')
|
|
|
|
|
|
const breaker = form.watch('breaker')
|
|
|
|
|
|
const count = form.watch('count')
|
|
|
|
|
|
|
|
|
|
|
|
const params = useMemo(() => {
|
|
|
|
|
|
const sp = new URLSearchParams()
|
|
|
|
|
|
if (resource) sp.set('i', String(resource))
|
|
|
|
|
|
if (authType) sp.set('t', authType)
|
|
|
|
|
|
if (proto != 'all') sp.set('x', proto)
|
|
|
|
|
|
if (prov) sp.set('a', prov)
|
|
|
|
|
|
if (city) sp.set('b', city)
|
|
|
|
|
|
if (isp != 'all') sp.set('s', isp)
|
|
|
|
|
|
sp.set('d', distinct)
|
|
|
|
|
|
sp.set('rt', formatType)
|
|
|
|
|
|
sp.set('rs', separator)
|
|
|
|
|
|
sp.set('rb', breaker)
|
|
|
|
|
|
sp.set('n', String(count))
|
|
|
|
|
|
|
|
|
|
|
|
return `/proxies?${sp.toString()}`
|
|
|
|
|
|
}, [resource, prov, city, isp, proto, distinct, formatType, separator, breaker, count])
|
|
|
|
|
|
|
2025-04-12 11:10:51 +08:00
|
|
|
|
const onSubmit = (values: z.infer<typeof schema>) => {
|
|
|
|
|
|
console.log(values)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const getResources = async () => {
|
|
|
|
|
|
setStatus('load')
|
|
|
|
|
|
try {
|
|
|
|
|
|
const resp = await allResource()
|
|
|
|
|
|
if (!resp.success) {
|
|
|
|
|
|
throw new Error('Unable to fetch packages.')
|
|
|
|
|
|
}
|
|
|
|
|
|
setResources(resp.data)
|
|
|
|
|
|
setStatus('done')
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (error) {
|
|
|
|
|
|
console.error('Error fetching packages:', error)
|
|
|
|
|
|
setStatus('fail')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
getResources().then()
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Form
|
|
|
|
|
|
form={form}
|
|
|
|
|
|
onSubmit={onSubmit}
|
2025-04-14 16:00:46 +08:00
|
|
|
|
onError={errors => {
|
|
|
|
|
|
const desc: (string | undefined)[] = []
|
|
|
|
|
|
Object.entries(errors).forEach(([field, error]) => {
|
|
|
|
|
|
if (error.message) {
|
|
|
|
|
|
desc.push(error.message)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
toast.error('请完成填写:', {
|
|
|
|
|
|
description: desc.map((msg, i) => (
|
|
|
|
|
|
<span key={i}>- {msg}</span>
|
|
|
|
|
|
)),
|
|
|
|
|
|
})
|
|
|
|
|
|
}}
|
|
|
|
|
|
className={merge(
|
|
|
|
|
|
`bg-white flex flex-col gap-4 rounded-md`,
|
|
|
|
|
|
props.className,
|
|
|
|
|
|
)}
|
2025-04-12 11:10:51 +08:00
|
|
|
|
>
|
|
|
|
|
|
<Alert variant={`warn`}>
|
|
|
|
|
|
<CircleAlert/>
|
|
|
|
|
|
<AlertTitle>提取IP前需要将本机IP添加到白名单后才可使用</AlertTitle>
|
|
|
|
|
|
</Alert>
|
|
|
|
|
|
|
2025-04-14 16:00:46 +08:00
|
|
|
|
<div className={`flex flex-col gap-4`}>
|
2025-04-12 11:10:51 +08:00
|
|
|
|
{/* 选择套餐 */}
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<FormField name="resource" label={`选择套餐`}>
|
|
|
|
|
|
{({field}) => (
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={field.value ? String(field.value) : undefined}
|
|
|
|
|
|
onValueChange={value => field.onChange(Number(value))}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className={`min-h-10 h-auto w-84`}>
|
|
|
|
|
|
<SelectValue placeholder={`选择套餐`}/>
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{status === 'load' ? (
|
|
|
|
|
|
<div className={`p-4 flex gap-1 items-center`}>
|
|
|
|
|
|
<Loader className={`animate-spin`} size={20}/>
|
|
|
|
|
|
<span>加载中...</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : resources.length === 0 ? (
|
2025-04-14 16:00:46 +08:00
|
|
|
|
<div className={`p-4 flex gap-1 items-center`}>
|
|
|
|
|
|
<Loader className={`animate-spin`} size={20}/>
|
|
|
|
|
|
<span>暂无可用套餐</span>
|
|
|
|
|
|
</div>
|
2025-04-12 11:10:51 +08:00
|
|
|
|
) : resources.map((resource, i) => (<>
|
|
|
|
|
|
<SelectItem
|
|
|
|
|
|
key={`${resource.id}`} value={String(resource.id)} className={`p-3`}>
|
|
|
|
|
|
<div className={`flex flex-col gap-2 w-72`}>
|
|
|
|
|
|
{resource.type === 1 && resource.pss.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}/>
|
|
|
|
|
|
<span>{name(resource)}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className={`flex justify-between gap-2 text-xs text-weak`}>
|
|
|
|
|
|
<span>到期时间:{format(resource.pss.expire, 'yyyy-MM-dd HH:mm')}</span>
|
|
|
|
|
|
<span>{intlFormatDistance(resource.pss.expire, new Date())}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>)}
|
|
|
|
|
|
{resource.type === 1 && resource.pss.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}/>
|
|
|
|
|
|
<span>{name(resource)}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className={`flex justify-between gap-2 text-xs text-weak`}>
|
|
|
|
|
|
<span>提取数量:{resource.pss.used} / {resource.pss.quota}</span>
|
|
|
|
|
|
<span>剩余 {resource.pss.quota - resource.pss.used}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SelectItem>
|
|
|
|
|
|
{i < resources.length - 1 && <SelectSeparator className={`m-2`}/>}
|
|
|
|
|
|
</>))
|
|
|
|
|
|
}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 地区筛选 */}
|
|
|
|
|
|
<div className="flex flex-col gap-4">
|
|
|
|
|
|
<FormField name="regionType" label={`地区筛选`}>
|
|
|
|
|
|
{({id, field}) => (
|
|
|
|
|
|
<RadioGroup
|
|
|
|
|
|
onValueChange={field.onChange}
|
|
|
|
|
|
defaultValue={field.value}
|
|
|
|
|
|
className="flex gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-unlimited`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="unlimited" id={`${id}-v-unlimited`} className="mr-2"/>
|
|
|
|
|
|
<span>不限地区</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-specific`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="specific" id={`${id}-v-specific`} className="mr-2"/>
|
|
|
|
|
|
<span>指定地区</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
</RadioGroup>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
|
|
|
|
|
|
{regionType === 'specific' && (
|
|
|
|
|
|
<div className="flex gap-4">
|
|
|
|
|
|
<FormField name="prov" label="">
|
|
|
|
|
|
{({field}) => (
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={field.value}
|
|
|
|
|
|
onValueChange={field.onChange}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="w-40">
|
|
|
|
|
|
<SelectValue placeholder="选择省份"/>
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
<SelectItem value="bj">北京</SelectItem>
|
|
|
|
|
|
<SelectItem value="sh">上海</SelectItem>
|
|
|
|
|
|
<SelectItem value="gd">广东</SelectItem>
|
|
|
|
|
|
{/* 更多省份... */}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
|
|
|
|
|
|
<FormField name="city" label="">
|
|
|
|
|
|
{({field}) => (
|
|
|
|
|
|
<Select
|
|
|
|
|
|
value={field.value}
|
|
|
|
|
|
onValueChange={field.onChange}
|
|
|
|
|
|
disabled={!form.watch('prov')}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SelectTrigger className="w-40">
|
|
|
|
|
|
<SelectValue placeholder="选择城市"/>
|
|
|
|
|
|
</SelectTrigger>
|
|
|
|
|
|
<SelectContent>
|
|
|
|
|
|
{form.watch('prov') === 'bj' && <SelectItem value="bj01">北京市</SelectItem>}
|
|
|
|
|
|
{form.watch('prov') === 'sh' && <SelectItem value="sh01">上海市</SelectItem>}
|
|
|
|
|
|
{form.watch('prov') === 'gd' && (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<SelectItem value="gz">广州</SelectItem>
|
|
|
|
|
|
<SelectItem value="sz">深圳</SelectItem>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{/* 更多城市... */}
|
|
|
|
|
|
</SelectContent>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 运营商筛选 */}
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<FormField name="isp" label={`运营商筛选`}>
|
|
|
|
|
|
{({id, field}) => (
|
|
|
|
|
|
<RadioGroup
|
|
|
|
|
|
onValueChange={field.onChange}
|
|
|
|
|
|
defaultValue={field.value}
|
|
|
|
|
|
className="flex gap-4">
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-all`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="all" id={`${id}-v-all`}/>
|
|
|
|
|
|
<span>不限</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-telecom`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="1" id={`${id}-v-telecom`}/>
|
|
|
|
|
|
<span>电信</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-mobile`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="2" id={`${id}-v-mobile`}/>
|
|
|
|
|
|
<span>移动</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-unicom`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="3" id={`${id}-v-unicom`}/>
|
|
|
|
|
|
<span>联通</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
</RadioGroup>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 协议类型 */}
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<FormField name="proto" label={`协议类型`}>
|
|
|
|
|
|
{({id, field}) => (
|
|
|
|
|
|
<RadioGroup
|
|
|
|
|
|
onValueChange={field.onChange}
|
|
|
|
|
|
defaultValue={field.value}
|
|
|
|
|
|
className="flex gap-4">
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-all`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="all" id={`${id}-v-all`} className="mr-2"/>
|
|
|
|
|
|
<span>不限</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-http`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="1" id={`${id}-v-http`} className="mr-2"/>
|
|
|
|
|
|
<span>HTTP</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-https`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="2" id={`${id}-v-https`} className="mr-2"/>
|
|
|
|
|
|
<span>HTTPS</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-socks5`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="3" id={`${id}-v-socks5`} className="mr-2"/>
|
|
|
|
|
|
<span>SOCKS5</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
</RadioGroup>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-04-14 16:00:46 +08:00
|
|
|
|
{/* 认证方式 */}
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<FormField name="authType" label={`协议类型`}>
|
|
|
|
|
|
{({id, field}) => (
|
|
|
|
|
|
<RadioGroup
|
|
|
|
|
|
onValueChange={field.onChange}
|
|
|
|
|
|
defaultValue={field.value}
|
|
|
|
|
|
className="flex gap-4">
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-http`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="1" id={`${id}-v-http`} className="mr-2"/>
|
|
|
|
|
|
<span>白名单</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-https`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="2" id={`${id}-v-https`} className="mr-2"/>
|
|
|
|
|
|
<span>密码</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
</RadioGroup>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-04-12 11:10:51 +08:00
|
|
|
|
{/* 去重选项 */}
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<FormField name="distinct" label={`去重选项`}>
|
|
|
|
|
|
{({id, field}) => (
|
|
|
|
|
|
<RadioGroup
|
|
|
|
|
|
onValueChange={field.onChange}
|
|
|
|
|
|
defaultValue={field.value}
|
|
|
|
|
|
className="flex gap-4">
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-true`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="1" id={`${id}-v-true`} className="mr-2"/>
|
|
|
|
|
|
<span>去重</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-false`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="0" id={`${id}-v-false`} className="mr-2"/>
|
|
|
|
|
|
<span>不去重</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
</RadioGroup>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 导出格式 */}
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<FormField name="format" label={`导出格式`}>
|
|
|
|
|
|
{({id, field}) => (
|
|
|
|
|
|
<RadioGroup
|
|
|
|
|
|
onValueChange={field.onChange}
|
|
|
|
|
|
defaultValue={field.value}
|
|
|
|
|
|
className="flex gap-4"
|
|
|
|
|
|
>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-txt`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="text" id={`${id}-v-txt`} className="mr-2"/>
|
|
|
|
|
|
<span>TXT 格式</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-json`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="json" id={`${id}-v-json`} className="mr-2"/>
|
|
|
|
|
|
<span>JSON 格式</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
</RadioGroup>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 分隔符 */}
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<FormField name="separator" label={`分隔符`}>
|
|
|
|
|
|
{({id, field}) => (
|
|
|
|
|
|
<RadioGroup
|
|
|
|
|
|
onValueChange={field.onChange}
|
|
|
|
|
|
defaultValue={field.value}
|
|
|
|
|
|
className="flex gap-4">
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-comma`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
2025-04-14 16:00:46 +08:00
|
|
|
|
<RadioGroupItem value="124" id={`${id}-v-comma`} className="mr-2"/>
|
2025-04-12 11:10:51 +08:00
|
|
|
|
<span>竖线 ( | )</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-semicolon`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
2025-04-14 16:00:46 +08:00
|
|
|
|
<RadioGroupItem value="58" id={`${id}-v-semicolon`} className="mr-2"/>
|
2025-04-12 11:10:51 +08:00
|
|
|
|
<span>冒号 ( : )</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-space`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
2025-04-14 16:00:46 +08:00
|
|
|
|
<RadioGroupItem value="9" id={`${id}-v-space`} className="mr-2"/>
|
2025-04-12 11:10:51 +08:00
|
|
|
|
<span>制表符 ( \t )</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
</RadioGroup>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 换行符 */}
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<FormField name="breaker" label={`换行符`}>
|
|
|
|
|
|
{({id, field}) => (
|
|
|
|
|
|
<RadioGroup
|
|
|
|
|
|
onValueChange={field.onChange}
|
|
|
|
|
|
defaultValue={field.value}
|
|
|
|
|
|
className="flex gap-4">
|
2025-04-14 16:00:46 +08:00
|
|
|
|
<FormLabel htmlFor={`${id}-v-newline2`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
|
|
|
|
|
<RadioGroupItem value="13,10" id={`${id}-v-newline2`} className="mr-2"/>
|
|
|
|
|
|
<span>回车换行 ( \r\n )</span>
|
|
|
|
|
|
</FormLabel>
|
2025-04-12 11:10:51 +08:00
|
|
|
|
<FormLabel htmlFor={`${id}-v-newline`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
2025-04-14 16:00:46 +08:00
|
|
|
|
<RadioGroupItem value="10" id={`${id}-v-newline`} className="mr-2"/>
|
2025-04-12 11:10:51 +08:00
|
|
|
|
<span>换行 ( \n )</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
<FormLabel htmlFor={`${id}-v-newline3`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
|
2025-04-14 16:00:46 +08:00
|
|
|
|
<RadioGroupItem value="13" id={`${id}-v-newline3`} className="mr-2"/>
|
2025-04-12 11:10:51 +08:00
|
|
|
|
<span>回车 ( \r )</span>
|
|
|
|
|
|
</FormLabel>
|
|
|
|
|
|
</RadioGroup>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 提取数量 */}
|
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
|
<FormField name="count" label={`提取数量`}>
|
|
|
|
|
|
{({id, field}) => (
|
|
|
|
|
|
<Input
|
|
|
|
|
|
{...field}
|
|
|
|
|
|
id={id}
|
|
|
|
|
|
type="number"
|
|
|
|
|
|
onChange={e => field.onChange(Number(e.target.value))}
|
|
|
|
|
|
className="h-10 w-84"
|
|
|
|
|
|
placeholder="输入提取数量"
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</FormField>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2025-04-14 16:00:46 +08:00
|
|
|
|
<div className={merge(
|
|
|
|
|
|
`flex flex-col gap-4 sticky bottom-0 bg-white py-4`,
|
|
|
|
|
|
`border-t`,
|
|
|
|
|
|
)}>
|
|
|
|
|
|
{/* 展示链接地址 */}
|
|
|
|
|
|
<div className={`bg-card text-card-foreground p-4 rounded-md`}>
|
|
|
|
|
|
{params}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 操作 */}
|
|
|
|
|
|
<div className="flex gap-4">
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="submit"
|
|
|
|
|
|
onClick={async () => {
|
|
|
|
|
|
const url = new URL(window.location.href).origin
|
|
|
|
|
|
await navigator.clipboard.writeText(`${url}${params}`)
|
|
|
|
|
|
toast.success('链接已复制到剪贴板')
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<CopyIcon/>
|
|
|
|
|
|
<span>复制链接</span>
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="submit"
|
|
|
|
|
|
onClick={async () => {
|
|
|
|
|
|
window.open(params, '_blank')
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<ExternalLinkIcon/>
|
|
|
|
|
|
<span>打开链接</span>
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</div>
|
2025-04-12 11:10:51 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|