From e16ef8e5098d4325cc91810d309123dff0c759c9 Mon Sep 17 00:00:00 2001 From: luorijun Date: Mon, 24 Mar 2025 12:29:52 +0800 Subject: [PATCH] =?UTF-8?q?=E7=9B=AE=E5=BD=95=E7=BB=93=E6=9E=84=E4=B8=8E?= =?UTF-8?q?=E8=A1=A8=E5=8D=95=E7=BB=84=E4=BB=B6=E7=BB=93=E6=9E=84=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(auth)/login/page.tsx | 145 ++++---- src/app/(root)/@header/page.tsx | 1 - .../(root)/collect/_client/form-section.tsx | 244 ++++++++++++++ src/app/(root)/collect/form-section.tsx | 311 ------------------ src/app/(root)/collect/page.tsx | 3 +- .../(root)/product/{ => _client}/combo.tsx | 0 src/app/(root)/product/page.tsx | 164 ++++----- src/components/ui/form.tsx | 215 ++++++------ src/components/ui/select.tsx | 62 ++-- 9 files changed, 530 insertions(+), 615 deletions(-) create mode 100644 src/app/(root)/collect/_client/form-section.tsx delete mode 100644 src/app/(root)/collect/form-section.tsx rename src/app/(root)/product/{ => _client}/combo.tsx (100%) diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 541d3c2..1dd5fde 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -14,11 +14,7 @@ import { } from '@/components/ui/card' import { Form, - FormControl, FormField, - FormItem, - FormLabel, - FormMessage, } from '@/components/ui/form' import {zodResolver} from '@hookform/resolvers/zod' import {useForm} from 'react-hook-form' @@ -29,6 +25,7 @@ import {login} from '@/actions/auth/login' import {useRouter} from 'next/navigation' import {toast} from 'sonner' import {ApiResponse} from '@/lib/api' +import {Label} from '@/components/ui/label' export type LoginPageProps = {} @@ -38,6 +35,7 @@ const formSchema = zod.object({ password: zod.string().min(1, '请输入验证码'), remember: zod.boolean().default(false), }) + type FormValues = zod.infer export default function LoginPage(props: LoginPageProps) { @@ -202,89 +200,70 @@ export default function LoginPage(props: LoginPageProps) { -
- - ( - - 手机号码 - - - - - - )} - /> + className="space-y-6" onSubmit={onSubmit} form={form}> + + {({id, field}) => ( + + )} + - ( - - 验证码 -
- - - - -
- -
- )} - /> + + {({id, field}) => ( +
+ + +
+ )} +
- ( - - - - -
- 保持登录 -
-
- )} - /> + + {({id, field}) => ( +
+ +
+ +
+
+ )} +
-
- +
+ -

- 登录即表示您同意《用户协议》《隐私政策》 -

-
- +

+ 登录即表示您同意《用户协议》《隐私政策》 +

+
diff --git a/src/app/(root)/@header/page.tsx b/src/app/(root)/@header/page.tsx index 71f299a..1d29d5f 100644 --- a/src/app/(root)/@header/page.tsx +++ b/src/app/(root)/@header/page.tsx @@ -48,7 +48,6 @@ export default function Header(props: HeaderProps) { , ], []) - // ====================== // 渲染组件 // ====================== diff --git a/src/app/(root)/collect/_client/form-section.tsx b/src/app/(root)/collect/_client/form-section.tsx new file mode 100644 index 0000000..6053f89 --- /dev/null +++ b/src/app/(root)/collect/_client/form-section.tsx @@ -0,0 +1,244 @@ +'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, SelectTrigger, SelectValue} from '@/components/ui/select' +import {Button} from '@/components/ui/button' +import {useForm} from 'react-hook-form' + +const formSchema = z.object({ + type: z.enum([`num`, `time`]), + order: z.number(), + region: z.string(), + provider: z.string(), + proto: z.string(), + distinct: z.string(), + format: z.enum([`txt`, `json`]), + separator: z.string(), + count: z.number(), +}) +type FormValues = z.infer + +type FormSectionProps = {} + +export default function FormSection(props: FormSectionProps) { + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + type: `num`, + order: 0, + region: ``, + provider: ``, + proto: ``, + distinct: ``, + format: `txt`, + separator: `,`, + count: 0, + }, + }) + + const onSubmit = (values: z.infer) => { + console.log(values) + // 在这里处理表单提交 + } + + return ( +
+
    +
  • + +
  • +
  • + +
  • +
+

+ {`warn`} + 提取IP前需要将本机IP添加到白名单后才可使用 +

+ +
+ {/* 套餐类型 */} +
+ name="type" label={`套餐类型`}> + {({id, field}) => ( + +
+ + +
+
+ + +
+
+ )} + +
+ + {/* 已购套餐 */} +
+ + {({field}) => ( + + )} + +
+ + {/* 地区筛选 */} +
+ + {({field}) => ( + + )} + +
+ + {/* 运营商筛选 */} +
+ + {({field}) => ( + + )} + +
+ + {/* 协议类型 */} +
+ + {({field}) => ( + + )} + +
+ + {/* 去重选项 */} +
+ + {({field}) => ( + + )} + +
+ + {/* 导出格式 */} +
+ + {({id, field}) => ( + +
+ + TXT格式 +
+
+ + JSON格式 +
+
+ )} +
+
+ + {/* 分隔符 */} +
+ + {({id, field}) => ( + + )} + +
+ + {/* 提取数量 */} +
+ + {({id, field}) => ( + field.onChange(Number(e.target.value))} + className="h-10" + placeholder="输入提取数量" + /> + )} + +
+
+ +
+ +
+
+ ) +} diff --git a/src/app/(root)/collect/form-section.tsx b/src/app/(root)/collect/form-section.tsx deleted file mode 100644 index 8df781f..0000000 --- a/src/app/(root)/collect/form-section.tsx +++ /dev/null @@ -1,311 +0,0 @@ -'use client' -import { z } from 'zod' -import { useForm } from 'react-hook-form' -import { zodResolver } from '@hookform/resolvers/zod' -import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form' -import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group' -import { Input } from '@/components/ui/input' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Button } from '@/components/ui/button' - -const schema = z.object({ - type: z.enum([`num`, `time`]), - order: z.number(), - region: z.string(), - provider: z.string(), - proto: z.string(), - distinct: z.string(), - format: z.enum([`txt`, `json`]), - separator: z.string(), - count: z.number(), -}) - -type FormSectionProps = {} - -export default function FormSection(props: FormSectionProps) { - - const form = useForm>({ - resolver: zodResolver(schema), - defaultValues: { - type: `num`, - order: 0, - region: ``, - provider: ``, - proto: ``, - distinct: ``, - format: `txt`, - separator: `,`, - count: 0, - }, - }) - - const onSubmit = (values: z.infer) => { - console.log(values) - // 在这里处理表单提交 - } - - return ( -
- -
    -
  • - -
  • -
  • - -
  • -
-

- {`warn`} - 提取IP前需要将本机IP添加到白名单后才可使用 -

- -
- {/* 套餐类型 */} -
- 套餐类型 - ( - - - -
- - -
-
- - -
-
-
- -
- )} - /> -
- - {/* 已购套餐 */} -
- 已购套餐 - ( - - - - - - - )} - /> -
- - {/* 地区筛选 */} -
- 地区筛选 - ( - - - - - - - )} - /> -
- - {/* 运营商筛选 */} -
- 运营商筛选 - ( - - - - - - - )} - /> -
- - {/* 协议类型 */} -
- 协议类型 - ( - - - - - - - )} - /> -
- - {/* 去重选项 */} -
- 去重选项 - ( - - - - - - - )} - /> -
- - {/* 导出格式 */} -
- 导出格式 - ( - - - -
- - -
-
- - -
-
-
- -
- )} - /> -
- - {/* 分隔符 */} -
- 分隔符 - ( - - - - - - - )} - /> -
- - {/* 提取数量 */} -
- 提取数量 - ( - - - field.onChange(Number(e.target.value))} - className="h-10" - placeholder="输入提取数量" - /> - - - - )} - /> -
-
- -
- -
-
- - ) -} \ No newline at end of file diff --git a/src/app/(root)/collect/page.tsx b/src/app/(root)/collect/page.tsx index bc44e1f..174f8f5 100644 --- a/src/app/(root)/collect/page.tsx +++ b/src/app/(root)/collect/page.tsx @@ -1,6 +1,6 @@ import BreadCrumb from '@/components/bread-crumb' import Wrap from '@/components/wrap' -import FormSection from '@/app/(root)/collect/form-section' +import FormSection from '@/app/(root)/collect/_client/form-section' export type CollectPageProps = {} @@ -11,7 +11,6 @@ export default function CollectPage(props: CollectPageProps) { -

提取 IP

diff --git a/src/app/(root)/product/combo.tsx b/src/app/(root)/product/_client/combo.tsx similarity index 100% rename from src/app/(root)/product/combo.tsx rename to src/app/(root)/product/_client/combo.tsx diff --git a/src/app/(root)/product/page.tsx b/src/app/(root)/product/page.tsx index fea1767..77e1e21 100644 --- a/src/app/(root)/product/page.tsx +++ b/src/app/(root)/product/page.tsx @@ -1,6 +1,6 @@ import BreadCrumb from '@/components/bread-crumb' import Wrap from '@/components/wrap' -import {Combo} from '@/app/(root)/product/combo' +import {Combo} from '@/app/(root)/product/_client/combo' export type ProductPageProps = {} @@ -13,8 +13,6 @@ export default function ProductPage(props: ProductPageProps) { {label: '产品中心', href: '/product'}, ]}/> -

多种套餐选择

-
  • @@ -117,91 +115,99 @@ function Left() { function Center() { return ( -
    +
    -

    计费方式

    -
    - - +
    +

    计费方式

    +
    + + +
    -

    IP 时效

    -
    - - - - - +
    +

    IP 时效

    +
    + + + + + +
    {/* 赠送 IP 数 */} -

    赠送IP总数

    -
    - - - +
    +

    赠送IP总数

    +
    + + + +
    {/* 产品特性 */} -

    产品特性

    -
    -

    - {`check`} - 支持高并发提取 -

    -

    - {`check`} - 指定省份、城市或混播 -

    -

    - {`check`} - 账密+白名单验证 -

    -

    - {`check`} - 完备的API接口 -

    -

    - {`check`} - IP时效3-30分钟(可定制) -

    -

    - {`check`} - IP资源定期筛选 -

    -

    - {`check`} - 完备的API接口 -

    -

    - {`check`} - 包量/包时计费方式 -

    -

    - {`check`} - 每日去重量:500万 -

    +
    +

    产品特性

    +
    +

    + {`check`} + 支持高并发提取 +

    +

    + {`check`} + 指定省份、城市或混播 +

    +

    + {`check`} + 账密+白名单验证 +

    +

    + {`check`} + 完备的API接口 +

    +

    + {`check`} + IP时效3-30分钟(可定制) +

    +

    + {`check`} + IP资源定期筛选 +

    +

    + {`check`} + 完备的API接口 +

    +

    + {`check`} + 包量/包时计费方式 +

    +

    + {`check`} + 每日去重量:500万 +

    +
    {/* 左右的边框 */} diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx index 121f2aa..3b78b66 100644 --- a/src/components/ui/form.tsx +++ b/src/components/ui/form.tsx @@ -1,143 +1,145 @@ -"use client" +'use client' -import * as React from "react" -import * as LabelPrimitive from "@radix-ui/react-label" -import { Slot } from "@radix-ui/react-slot" +import * as LabelPrimitive from '@radix-ui/react-label' +import {Slot} from '@radix-ui/react-slot' import { Controller, FormProvider, - useFormContext, - useFormState, - type ControllerProps, - type FieldPath, - type FieldValues, -} from "react-hook-form" + ControllerProps, + SubmitHandler, + FieldValues, useFormContext, FieldPath, UseFormReturn, ControllerRenderProps, + ControllerFieldState, UseFormStateReturn, FieldError, +} from 'react-hook-form' -import { merge } from "@/lib/utils" -import { Label } from "@/components/ui/label" +import {merge} from '@/lib/utils' +import {Label} from '@/components/ui/label' -const Form = FormProvider +import {ComponentProps, createContext, ReactNode, useContext, useId} from 'react' -type FormFieldContextValue< - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, -> = { - name: TName +type FormProps = { + form: UseFormReturn + onSubmit: SubmitHandler +} & Omit, 'onSubmit'> + +function Form(rawProps: FormProps) { + + const {children, onSubmit, ...props} = rawProps + const form = props.form + + return ( + +
    + {children} +
    +
    + ) } -const FormFieldContext = React.createContext( - {} as FormFieldContextValue -) +type FormFieldProps< + V extends FieldValues = FieldValues, + N extends FieldPath = FieldPath, +> = { + label?: ReactNode + className?: string + children: (props: { + id: string + field: ControllerRenderProps + fieldState: ControllerFieldState + formState: UseFormStateReturn + }) => ReactNode +} & Omit, 'control' | 'render'> -const FormField = < - TFieldValues extends FieldValues = FieldValues, - TName extends FieldPath = FieldPath, ->({ - ...props -}: ControllerProps) => { +type FormFieldContext = { + id: string + error?: FieldError +} + +const FormFieldContext = createContext(null) + +function FormField< + V extends FieldValues = FieldValues, + N extends FieldPath = FieldPath, +>(props: FormFieldProps) { + const form = useFormContext() + const id = useId() return ( - - - + name={props.name} control={form.control} render={({field, fieldState, formState}) => ( +
    + + {!!props.label && + + } + + + {props.children({id, field, fieldState, formState})} + + + {!fieldState.error ? null : ( +

    + {fieldState.error?.message} +

    + )} +
    +
    + )} + /> ) } const useFormField = () => { - const fieldContext = React.useContext(FormFieldContext) - const itemContext = React.useContext(FormItemContext) - const { getFieldState } = useFormContext() - const formState = useFormState({ name: fieldContext.name }) - const fieldState = getFieldState(fieldContext.name, formState) - - if (!fieldContext) { - throw new Error("useFormField should be used within ") - } - - const { id } = itemContext - - return { - id, - name: fieldContext.name, - formItemId: `${id}-form-item`, - formDescriptionId: `${id}-form-item-description`, - formMessageId: `${id}-form-item-message`, - ...fieldState, + const context = useContext(FormFieldContext) + if (!context) { + throw new Error('FormField components must be used within a FormField component') } + return context } -type FormItemContextValue = { - id: string -} -const FormItemContext = React.createContext( - {} as FormItemContextValue -) - -function FormItem({ className, ...props }: React.ComponentProps<"div">) { - const id = React.useId() - - return ( - -
    - - ) -} - -function FormLabel({ - className, - ...props -}: React.ComponentProps) { - const { error, formItemId } = useFormField() +function FormLabel({className, ...props}: ComponentProps) { + const {id, error} = useFormField() return (