修复提取 ip 接口json类型返回值错误问题;完善 ip 提取页响应式设计
This commit is contained in:
@@ -9,7 +9,7 @@ import {Button} from '@/components/ui/button'
|
||||
import {useForm, useFormContext} from 'react-hook-form'
|
||||
import {Alert, AlertTitle} from '@/components/ui/alert'
|
||||
import {Box, CircleAlert, CopyIcon, ExternalLinkIcon, Loader, Timer} from 'lucide-react'
|
||||
import {memo, useEffect, useRef, useState} from 'react'
|
||||
import {memo, ReactNode, useEffect, useRef, useState} from 'react'
|
||||
import {useStatus} from '@/lib/states'
|
||||
import {allResource} from '@/actions/resource'
|
||||
import {Resource} from '@/lib/models'
|
||||
@@ -18,6 +18,8 @@ import {toast} from 'sonner'
|
||||
import {merge} from '@/lib/utils'
|
||||
import {Combobox} from '@/components/ui/combobox'
|
||||
import cities from './_assets/cities.json'
|
||||
import ExtractDocs from '@/components/docs/extract.mdx'
|
||||
import Markdown from '@/components/markdown'
|
||||
|
||||
const schema = z.object({
|
||||
resource: z.number({required_error: '请选择套餐'}),
|
||||
@@ -64,25 +66,44 @@ export default function Extract(props: ExtractProps) {
|
||||
<Form
|
||||
form={form}
|
||||
className={merge(
|
||||
`bg-white flex flex-col gap-4 rounded-md`,
|
||||
props.className,
|
||||
`flex flex-col gap-6`,
|
||||
)}
|
||||
>
|
||||
<Alert variant="warn">
|
||||
<CircleAlert/>
|
||||
<AlertTitle>提取IP前需要将本机IP添加到白名单后才可使用</AlertTitle>
|
||||
</Alert>
|
||||
<CardSection>
|
||||
<Alert variant="warn" >
|
||||
<CircleAlert/>
|
||||
<AlertTitle>提取IP前需要将本机IP添加到白名单后才可使用</AlertTitle>
|
||||
</Alert>
|
||||
|
||||
<FormFields/>
|
||||
<FormFields/>
|
||||
</CardSection>
|
||||
|
||||
<ApplyLink/>
|
||||
<CardSection>
|
||||
<ApplyLink/>
|
||||
</CardSection>
|
||||
|
||||
<CardSection>
|
||||
<Markdown>
|
||||
<ExtractDocs/>
|
||||
</Markdown>
|
||||
</CardSection>
|
||||
</Form>
|
||||
)
|
||||
}
|
||||
|
||||
function CardSection(props: {
|
||||
children: ReactNode
|
||||
}) {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 p-4 md:p-6 bg-white rounded-lg">
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const FormFields = memo(() => {
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-6 items-stretch max-w-[calc(160px*4+1rem*3)]">
|
||||
{/* 选择套餐 */}
|
||||
<SelectResource/>
|
||||
|
||||
@@ -90,192 +111,177 @@ const FormFields = memo(() => {
|
||||
<SelectRegion/>
|
||||
|
||||
{/* 运营商筛选 */}
|
||||
<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>
|
||||
<FormField name="isp" label="运营商筛选" classNames={{label: 'max-md:text-sm'}}>
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="grid grid-cols-2 md:grid-cols-4 gap-4"
|
||||
>
|
||||
<FormLabel htmlFor={`${id}-v-all`} className="px-3 h-10 border rounded-md flex items-center 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 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 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 text-sm">
|
||||
<RadioGroupItem value="3" id={`${id}-v-unicom`}/>
|
||||
<span>移动</span>
|
||||
</FormLabel>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
{/* 协议类型 */}
|
||||
<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>
|
||||
<FormField name="proto" label="协议类型" classNames={{label: 'max-md:text-sm'}}>
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<FormLabel htmlFor={`${id}-v-all`} className="px-3 h-10 border rounded-md flex items-center 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 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 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 text-sm">
|
||||
<RadioGroupItem value="3" id={`${id}-v-socks5`} className="mr-2"/>
|
||||
<span>SOCKS5</span>
|
||||
</FormLabel>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
{/* 认证方式 */}
|
||||
<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>
|
||||
<FormField name="authType" className="md:max-w-[calc(160px*2+1rem)]" label="认证方式" classNames={{label: 'max-md:text-sm'}}>
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="flex gap-4">
|
||||
<FormLabel htmlFor={`${id}-v-http`} className="px-3 h-10 flex-1 border rounded-md flex items-center 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 flex-1 border rounded-md flex items-center text-sm">
|
||||
<RadioGroupItem value="2" id={`${id}-v-https`} className="mr-2"/>
|
||||
<span>密码</span>
|
||||
</FormLabel>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
{/* 去重选项 */}
|
||||
<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>
|
||||
<FormField name="distinct" className="md:max-w-[calc(160px*2+1rem)]" label="去重选项" classNames={{label: 'max-md:text-sm'}}>
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="flex gap-4">
|
||||
<FormLabel htmlFor={`${id}-v-true`} className="px-3 h-10 flex-1 border rounded-md flex items-center 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 flex-1 border rounded-md flex items-center text-sm">
|
||||
<RadioGroupItem value="0" id={`${id}-v-false`} className="mr-2"/>
|
||||
<span>不去重</span>
|
||||
</FormLabel>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
{/* 导出格式 */}
|
||||
<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>
|
||||
<FormField name="format" className="md:max-w-[calc(160px*2+1rem)]" label="导出格式" classNames={{label: 'max-md:text-sm'}}>
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="flex gap-4"
|
||||
>
|
||||
<FormLabel htmlFor={`${id}-v-txt`} className="px-3 h-10 flex-1 border rounded-md flex items-center 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 flex-1 border rounded-md flex items-center text-sm">
|
||||
<RadioGroupItem value="json" id={`${id}-v-json`} className="mr-2"/>
|
||||
<span>JSON 格式</span>
|
||||
</FormLabel>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
{/* 分隔符 */}
|
||||
<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">
|
||||
<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="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="9" id={`${id}-v-space`} className="mr-2"/>
|
||||
<span>制表符 ( \t )</span>
|
||||
</FormLabel>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
<FormField name="separator" className="md:max-w-[calc(160px*3+1rem*2)]" label="分隔符" classNames={{label: 'max-md:text-sm'}}>
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<FormLabel htmlFor={`${id}-v-comma`} className="px-3 h-10 border rounded-md flex items-center text-sm">
|
||||
<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 text-sm">
|
||||
<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 text-sm">
|
||||
<RadioGroupItem value="9" id={`${id}-v-space`} className="mr-2"/>
|
||||
<span>制表符 ( \t )</span>
|
||||
</FormLabel>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
{/* 换行符 */}
|
||||
<div className="flex items-center">
|
||||
<FormField name="breaker" label="换行符">
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
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="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="13" id={`${id}-v-newline3`} className="mr-2"/>
|
||||
<span>回车 ( \r )</span>
|
||||
</FormLabel>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
<FormField name="breaker" className="md:max-w-[calc(160px*3+1rem*2)]" label="换行符" classNames={{label: 'max-md:text-sm'}}>
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<FormLabel htmlFor={`${id}-v-newline2`} className="px-3 h-10 border rounded-md flex items-center 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 text-sm">
|
||||
<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 text-sm">
|
||||
<RadioGroupItem value="13" id={`${id}-v-newline3`} className="mr-2"/>
|
||||
<span>回车 ( \r )</span>
|
||||
</FormLabel>
|
||||
</RadioGroup>
|
||||
)}
|
||||
</FormField>
|
||||
|
||||
{/* 提取数量 */}
|
||||
<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>
|
||||
<FormField name="count" className="max-w[160px*2+1rem]" label="提取数量" classNames={{label: 'max-md:text-sm'}}>
|
||||
{({id, field}) => (
|
||||
<Input
|
||||
{...field}
|
||||
id={id}
|
||||
type="number"
|
||||
onChange={e => field.onChange(Number(e.target.value))}
|
||||
className="h-10"
|
||||
placeholder="输入提取数量"
|
||||
/>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
@@ -308,116 +314,114 @@ function SelectResource() {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<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 ? (
|
||||
<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">
|
||||
<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}/>
|
||||
<span>{name(resource)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-2 text-xs text-weak">
|
||||
<span>
|
||||
到期时间:
|
||||
{format(resource.short.expire, 'yyyy-MM-dd HH:mm')}
|
||||
</span>
|
||||
<span>{intlFormatDistance(resource.short.expire, new Date())}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{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}/>
|
||||
<span>{name(resource)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-2 text-xs text-weak">
|
||||
<span>
|
||||
提取数量:
|
||||
{resource.short.used}
|
||||
{' '}
|
||||
/
|
||||
{resource.short.quota}
|
||||
</span>
|
||||
<span>
|
||||
剩余
|
||||
{resource.short.quota - resource.short.used}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{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}/>
|
||||
<span>{name(resource)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-2 text-xs text-weak">
|
||||
<span>
|
||||
到期时间:
|
||||
{format(resource.long.expire, 'yyyy-MM-dd HH:mm')}
|
||||
</span>
|
||||
<span>{intlFormatDistance(resource.long.expire, new Date())}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{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}/>
|
||||
<span>{name(resource)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-2 text-xs text-weak">
|
||||
<span>
|
||||
提取数量:
|
||||
{resource.long.used}
|
||||
{' '}
|
||||
/
|
||||
{resource.long.quota}
|
||||
</span>
|
||||
<span>
|
||||
剩余
|
||||
{resource.long.quota - resource.long.used}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</SelectItem>
|
||||
{i < resources.length - 1 && <SelectSeparator className="m-1"/>}
|
||||
</>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</FormField>
|
||||
</div>
|
||||
<FormField name="resource" className="md:max-w-[calc(160px*2+1rem)]" label="选择套餐" classNames={{label: 'max-md:text-sm'}}>
|
||||
{({field}) => (
|
||||
<Select
|
||||
value={field.value ? String(field.value) : undefined}
|
||||
onValueChange={value => field.onChange(Number(value))}
|
||||
>
|
||||
<SelectTrigger className="min-h-10 h-auto w-full">
|
||||
<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 ? (
|
||||
<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">
|
||||
<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}/>
|
||||
<span>{name(resource)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-2 text-xs text-weak">
|
||||
<span>
|
||||
到期时间:
|
||||
{format(resource.short.expire, 'yyyy-MM-dd HH:mm')}
|
||||
</span>
|
||||
<span>{intlFormatDistance(resource.short.expire, new Date())}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{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}/>
|
||||
<span>{name(resource)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-2 text-xs text-weak">
|
||||
<span>
|
||||
提取数量:
|
||||
{resource.short.used}
|
||||
{' '}
|
||||
/
|
||||
{resource.short.quota}
|
||||
</span>
|
||||
<span>
|
||||
剩余
|
||||
{resource.short.quota - resource.short.used}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{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}/>
|
||||
<span>{name(resource)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-2 text-xs text-weak">
|
||||
<span>
|
||||
到期时间:
|
||||
{format(resource.long.expire, 'yyyy-MM-dd HH:mm')}
|
||||
</span>
|
||||
<span>{intlFormatDistance(resource.long.expire, new Date())}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{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}/>
|
||||
<span>{name(resource)}</span>
|
||||
</div>
|
||||
<div className="flex justify-between gap-2 text-xs text-weak">
|
||||
<span>
|
||||
提取数量:
|
||||
{resource.long.used}
|
||||
{' '}
|
||||
/
|
||||
{resource.long.quota}
|
||||
</span>
|
||||
<span>
|
||||
剩余
|
||||
{resource.long.quota - resource.long.used}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</SelectItem>
|
||||
{i < resources.length - 1 && <SelectSeparator className="m-1"/>}
|
||||
</>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
</FormField>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -428,8 +432,8 @@ function SelectRegion() {
|
||||
const city = form.watch('city')
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<FormField name="regionType" label="地区筛选">
|
||||
<div className="flex flex-col gap-4 md:max-w-[calc(160px*2+1rem)]">
|
||||
<FormField name="regionType" label="地区筛选" classNames={{label: 'max-md:text-sm'}}>
|
||||
{({id, field}) => (
|
||||
<RadioGroup
|
||||
onValueChange={(e) => {
|
||||
@@ -442,11 +446,11 @@ function SelectRegion() {
|
||||
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">
|
||||
<FormLabel htmlFor={`${id}-v-unlimited`} className="px-3 h-10 flex-1 border rounded-md flex items-center 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">
|
||||
<FormLabel htmlFor={`${id}-v-specific`} className="px-3 h-10 flex-1 border rounded-md flex items-center text-sm">
|
||||
<RadioGroupItem value="specific" id={`${id}-v-specific`} className="mr-2"/>
|
||||
<span>指定地区</span>
|
||||
</FormLabel>
|
||||
@@ -456,7 +460,6 @@ function SelectRegion() {
|
||||
|
||||
{regionType === 'specific' && (
|
||||
<Combobox
|
||||
className="w-84"
|
||||
placeholder="请选择地区"
|
||||
options={cities.options}
|
||||
value={[prov || '', city || '']}
|
||||
@@ -531,11 +534,13 @@ function ApplyLink() {
|
||||
|
||||
return (
|
||||
<div className={merge(
|
||||
`flex flex-col gap-4 sticky bottom-0 bg-muted p-4`,
|
||||
`flex flex-col gap-4`,
|
||||
`rounded-lg`,
|
||||
)}>
|
||||
<h4>API 链接</h4>
|
||||
|
||||
{/* 展示链接地址 */}
|
||||
<div className="bg-neutral-900 text-white p-4 rounded-md break-all">
|
||||
<div className="bg-secondary p-4 rounded-md break-all">
|
||||
{link(values)}
|
||||
</div>
|
||||
|
||||
|
||||
68
src/components/docs/extract.mdx
Normal file
68
src/components/docs/extract.mdx
Normal file
@@ -0,0 +1,68 @@
|
||||
# 提取代理接口文档
|
||||
|
||||
## 请求方式
|
||||
|
||||
`GET https://lanhuip.com/api/extract`
|
||||
|
||||
## 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必填 | 描述 |
|
||||
|--------|----------|------|----------------------------------------------------------------------------------------------------------------------------|
|
||||
| i | number | 是 | 用于提取的套餐 ID |
|
||||
| t | number | 是 | 认证类型:1 - 白名单,2 - 密码 |
|
||||
| a | string | 否 | 归属地省份。默认全局随机 |
|
||||
| b | string | 否 | 归属地城市。默认全局随机 |
|
||||
| s | string | 否 | 归属地运营商。默认全局随机 |
|
||||
| d | string | 否 | 是否去重:1 - 是,0 - 否。默认为是 |
|
||||
| rt | string | 否 | 返回类型:1 - TXT,2 - JSON。默认 TXT |
|
||||
| rs | number[] | 否 | 返回时要使用的分隔符,值为该字符的 ascii 编码,可以有多个字符,多个字符用半角逗号连接。默认为 13,10,即回车 + 换行(\r\n) |
|
||||
| rb | number[] | 否 | 返回时要使用的换行符,值为该字符的 ascii 编码,可以有多个字符,多个字符用半角逗号连接。默认为 124,即垂直线( \| ) |
|
||||
| n | number | 否 | 提取数量。默认为 1 |
|
||||
|
||||
## 响应参数
|
||||
|
||||
如果请求参数中返回类型为 TXT,则响应为纯文本格式,内容为提取的代理列表,每个代理信息占一行。
|
||||
|
||||
如果请求参数中返回类型为 JSON,则响应为 JSON 格式,内容为提取的代理列表,每个代理信息为一个对象,包含以下字段:
|
||||
|
||||
| 参数名 | 类型 | 描述 |
|
||||
|----------|--------|--------------------------------------------- |
|
||||
| host | string | 代理服务器地址 |
|
||||
| port | number | 代理服务器端口 |
|
||||
| username | string | 代理服务器用户名(仅在认证类型为密码时返回) |
|
||||
| password | string | 代理服务器密码(仅在认证类型为密码时返回) |
|
||||
|
||||
|
||||
## 示例
|
||||
|
||||
### 请求示例
|
||||
|
||||
```http
|
||||
GET https://lanhuip.com/api/extract?i=1&t=2&a=广东省&b=广州市&s=移动&d=1&rt=2&n=3
|
||||
```
|
||||
|
||||
### 响应示例
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"host": "fwd1.lanhuip.com",
|
||||
"port": 20000,
|
||||
"username": "user1",
|
||||
"password": "pass1"
|
||||
},
|
||||
{
|
||||
"host": "fwd1.lanhuip.com",
|
||||
"port": 20001,
|
||||
"username": "user2",
|
||||
"password": "pass2"
|
||||
},
|
||||
{
|
||||
"host": "fwd1.lanhuip.com",
|
||||
"port": 20002,
|
||||
"username": "user3",
|
||||
"password": "pass3"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
@@ -2,6 +2,12 @@ import {Button} from '@/components/ui/button';
|
||||
|
||||
# qweqwe
|
||||
|
||||
`dasdasd`
|
||||
|
||||
```typescript
|
||||
console.log('Hello, world!');
|
||||
```
|
||||
|
||||
## awdasdasd
|
||||
|
||||
### zxczxczxc
|
||||
|
||||
@@ -2,13 +2,13 @@ import {merge} from '@/lib/utils'
|
||||
|
||||
export default function Markdown(props: React.ComponentProps<'div'>) {
|
||||
return (
|
||||
<div
|
||||
<article
|
||||
{...props}
|
||||
className={merge(
|
||||
`prose`,
|
||||
`prose max-w-none`,
|
||||
props.className,
|
||||
)}>
|
||||
{props.children}
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ export function Combobox(props: ComboboxProps) {
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0 rounded-lg h-[var(--radix-popover-content-available-height)] flex flex-col overflow-hidden"
|
||||
className="p-0 rounded-lg w-[var(--radix-popover-trigger-width)] h-[var(--radix-popover-content-available-height)] flex flex-col overflow-hidden"
|
||||
align="start"
|
||||
collisionPadding={6}
|
||||
>
|
||||
|
||||
@@ -76,7 +76,7 @@ function FormField<
|
||||
name={props.name}
|
||||
control={form.control}
|
||||
render={({field, fieldState, formState}) => (
|
||||
<div data-slot="form-field" className={merge('grid gap-2', props.className)}>
|
||||
<div data-slot="form-field" className={merge('flex flex-col gap-2', props.className)}>
|
||||
|
||||
{/* label */}
|
||||
{!!props.label
|
||||
|
||||
28
src/components/ui/separator.tsx
Normal file
28
src/components/ui/separator.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import * as SeparatorPrimitive from '@radix-ui/react-separator'
|
||||
|
||||
import {merge} from '@/lib/utils'
|
||||
|
||||
function Separator({
|
||||
className,
|
||||
orientation = 'horizontal',
|
||||
decorative = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
||||
return (
|
||||
<SeparatorPrimitive.Root
|
||||
data-slot="separator"
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={merge(
|
||||
'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export {Separator}
|
||||
Reference in New Issue
Block a user