完善通道创建功能,添加参数验证与格式化输出

This commit is contained in:
2025-04-14 16:00:46 +08:00
parent e928b5a270
commit 4315c8eba9
5 changed files with 233 additions and 36 deletions

View File

@@ -8,32 +8,38 @@ import {Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, Selec
import {Button} from '@/components/ui/button'
import {useForm} from 'react-hook-form'
import {Alert, AlertTitle} from '@/components/ui/alert'
import {Box, CircleAlert, Loader, Timer} from 'lucide-react'
import {useEffect, useState} from 'react'
import {Box, CircleAlert, CopyIcon, ExternalLinkIcon, Loader, Timer} from 'lucide-react'
import {useEffect, useMemo, useState} from 'react'
import {useStatus} from '@/lib/states'
import {allResource} from '@/actions/resource'
import {Resource, name} from '@/lib/models'
import {format, intlFormatDistance} from 'date-fns'
import {usePathname} from 'next/navigation'
import {toast} from 'sonner'
import {merge} from '@/lib/utils'
type ExtractProps = {}
type ExtractProps = {
className?: string
}
export default function Extract(props: ExtractProps) {
const [resources, setResources] = useState<Resource[]>([])
const [status, setStatus] = useStatus()
const schema = z.object({
resource: z.number().optional(),
resource: z.number({required_error: '请选择套餐'}),
prov: z.string().optional(),
city: z.string().optional(),
regionType: z.enum(['unlimited', 'specific']).default('unlimited'),
isp: z.enum(['all', '1', '2', '3']),
proto: z.enum(['all', '1', '2', '3']),
distinct: z.enum(['1', '0']),
format: z.enum(['text', 'json']),
separator: z.string(),
breaker: z.string(),
count: z.number().min(1),
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),
})
type Schema = z.infer<typeof schema>
@@ -44,16 +50,46 @@ export default function Extract(props: ExtractProps) {
regionType: 'unlimited',
isp: 'all',
proto: 'all',
authType: '1',
count: 1,
distinct: '1',
format: 'text',
breaker: '\\n',
separator: '|',
breaker: '13,10',
separator: '124',
},
})
const regionType = form.watch('regionType')
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])
const onSubmit = (values: z.infer<typeof schema>) => {
console.log(values)
}
@@ -82,14 +118,30 @@ export default function Extract(props: ExtractProps) {
<Form
form={form}
onSubmit={onSubmit}
className={`p-4 bg-white flex flex-col gap-4 rounded-md`}
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,
)}
>
<Alert variant={`warn`}>
<CircleAlert/>
<AlertTitle>IP前需要将本机IP添加到白名单后才可使用</AlertTitle>
</Alert>
<div className={`flex flex-col gap-y-6`}>
<div className={`flex flex-col gap-4`}>
{/* 选择套餐 */}
<div className="flex items-center">
<FormField name="resource" label={`选择套餐`}>
@@ -108,9 +160,10 @@ export default function Extract(props: ExtractProps) {
<span>...</span>
</div>
) : resources.length === 0 ? (
<SelectItem value="0">
</SelectItem>
<div className={`p-4 flex gap-1 items-center`}>
<Loader className={`animate-spin`} size={20}/>
<span></span>
</div>
) : resources.map((resource, i) => (<>
<SelectItem
key={`${resource.id}`} value={String(resource.id)} className={`p-3`}>
@@ -274,6 +327,27 @@ export default function Extract(props: ExtractProps) {
</FormField>
</div>
{/* 认证方式 */}
<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>
{/* 去重选项 */}
<div className="flex items-center">
<FormField name="distinct" label={`去重选项`}>
@@ -326,15 +400,15 @@ export default function Extract(props: ExtractProps) {
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`}>
<RadioGroupItem value="|" id={`${id}-v-comma`} className="mr-2"/>
<RadioGroupItem value="124" id={`${id}-v-comma`} className="mr-2"/>
<span>线 ( | )</span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-semicolon`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value=":" id={`${id}-v-semicolon`} className="mr-2"/>
<RadioGroupItem value="58" id={`${id}-v-semicolon`} className="mr-2"/>
<span> ( : )</span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-space`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="\t" id={`${id}-v-space`} className="mr-2"/>
<RadioGroupItem value="9" id={`${id}-v-space`} className="mr-2"/>
<span> ( \t )</span>
</FormLabel>
</RadioGroup>
@@ -350,18 +424,18 @@ export default function Extract(props: ExtractProps) {
onValueChange={field.onChange}
defaultValue={field.value}
className="flex gap-4">
<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>
<FormLabel htmlFor={`${id}-v-newline`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="\n" id={`${id}-v-newline`} className="mr-2"/>
<RadioGroupItem value="10" id={`${id}-v-newline`} className="mr-2"/>
<span> ( \n )</span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-newline3`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="\r" id={`${id}-v-newline3`} className="mr-2"/>
<RadioGroupItem value="13" id={`${id}-v-newline3`} className="mr-2"/>
<span> ( \r )</span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-newline2`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="\r\n" id={`${id}-v-newline2`} className="mr-2"/>
<span> ( \r\n )</span>
</FormLabel>
</RadioGroup>
)}
</FormField>
@@ -384,8 +458,38 @@ export default function Extract(props: ExtractProps) {
</div>
</div>
<div className="flex mt-6 justify-center">
<Button type="submit" className="w-40 h-10 bg-blue-500 text-white rounded-md"></Button>
<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>
</div>
</Form>
)