修复表单响应性问题 & 更新套餐管理接口字段

This commit is contained in:
2025-12-16 17:10:30 +08:00
parent 427502489e
commit 1f9b6bb044
11 changed files with 77 additions and 98 deletions

View File

@@ -1,5 +1,9 @@
## TODO
### 禁止直接依赖 form
`\[(.*,)?form(,.*)?\]`
业务场景页面优化实现
业务定制页面优化实现

View File

@@ -1,8 +1,23 @@
'use server'
import {callByUser} from '@/actions/base'
import {callByDevice, callByUser} from '@/actions/base'
import {Resource} from '@/lib/models'
import {ExtraReq, PageRecord} from '@/lib/api'
import {PageRecord} from '@/lib/api'
export type CreateResourceReq = {
type: number
short?: {
live: number
mode: number
quota: number
expire?: number
}
long?: {
live: number
mode: number
quota: number
expire?: number
}
}
export async function listResourceShort(props: {
page: number
@@ -34,45 +49,14 @@ export async function allResource() {
return callByUser<Resource<1 | 2>[]>('/api/resource/all')
}
export async function createResource(props: {
type: number
short?: {
live: number
mode: number
quota: number
expire_at: number
daily_limit: number
}
long?: {
live: number
mode: number
quota: number
expire_at: number
daily_limit: number
}
}) {
export async function createResource(props: CreateResourceReq) {
return await callByUser('/api/resource/create', props)
}
export async function prepareResource(props: {
type: number
short?: {
live: number
mode: number
quota: number
expire_at: number
daily_limit: number
}
long?: {
live: number
mode: number
quota: number
expire_at: number
daily_limit: number
}
payment_method: number
payment_platform: number
}) {
} & CreateResourceReq) {
return await callByUser<{
trade_no: string
pay_url: string
@@ -101,3 +85,7 @@ export async function payClose(props: {
}) {
return callByUser('/api/trade/cancel', props)
}
export async function getPrice(props: CreateResourceReq) {
return callByDevice<{price: string}>('/api/resource/price', props)
}

View File

@@ -207,7 +207,7 @@ function Tab(props: {
}
function SendMsgByUsername() {
const form = useFormContext<LoginSchema>()
const phone = form.watch('username')
const {control} = useFormContext<LoginSchema>()
const phone = useWatch({control, name: 'username'})
return <SendMsg phone={phone}/>
}

View File

@@ -4,7 +4,7 @@ import {Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTr
import {Button} from '@/components/ui/button'
import {Form, FormField} from '@/components/ui/form'
import {Input} from '@/components/ui/input'
import {useForm, useFormContext} from 'react-hook-form'
import {useForm, useFormContext, useWatch} from 'react-hook-form'
import {zodResolver} from '@hookform/resolvers/zod'
import * as z from 'zod'
import {toast} from 'sonner'
@@ -154,7 +154,7 @@ export function ChangePasswordDialog({
}
function SendMsgByPhone() {
const form = useFormContext<Schema>()
const phone = form.watch('phone')
const {control} = useFormContext<Schema>()
const phone = useWatch({control, name: 'phone'})
return <SendMsg phone={phone}/>
}

View File

@@ -6,7 +6,7 @@ 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, useFormContext} from 'react-hook-form'
import {useForm, useFormContext, useWatch} from 'react-hook-form'
import {Alert, AlertTitle} from '@/components/ui/alert'
import {ArrowRight, Box, CircleAlert, CopyIcon, ExternalLinkIcon, LinkIcon, Loader, Plus, Timer} from 'lucide-react'
import {memo, ReactNode, useEffect, useRef, useState} from 'react'
@@ -487,10 +487,10 @@ function SelectResource() {
}
function SelectRegion() {
const form = useFormContext<Schema>()
const regionType = form.watch('regionType')
const prov = form.watch('prov')
const city = form.watch('city')
const {control, setValue} = useFormContext<Schema>()
const regionType = useWatch({control, name: 'regionType'})
const prov = useWatch({control, name: 'prov'})
const city = useWatch({control, name: 'city'})
return (
<div className="flex flex-col gap-4 md:max-w-[calc(160px*2+1rem)]">
@@ -500,8 +500,8 @@ function SelectRegion() {
onValueChange={(e) => {
field.onChange(e)
if (e === 'unlimited') {
form.setValue('prov', '')
form.setValue('city', '')
setValue('prov', '')
setValue('city', '')
}
}}
defaultValue={field.value}
@@ -525,8 +525,8 @@ function SelectRegion() {
options={cities.options}
value={[prov || '', city || '']}
onChange={(value) => {
form.setValue('prov', value[0])
form.setValue('city', value[1])
setValue('prov', value[0])
setValue('city', value[1])
}}
/>
)}
@@ -536,7 +536,7 @@ function SelectRegion() {
function ApplyLink() {
const form = useFormContext<Schema>()
const values = form.watch()
useWatch()
// let type: 'open' | 'copy' = 'open'
const type = useRef<'open' | 'copy'>('open')
@@ -613,7 +613,7 @@ function ApplyLink() {
{/* 展示链接地址 */}
<div className="bg-secondary p-4 rounded-md break-all">
{link(values)}
{link(form.getValues())}
</div>
{/* 操作 */}

View File

@@ -8,12 +8,12 @@ import FormOption from '@/components/composites/purchase/option'
import Image from 'next/image'
import check from '../_assets/check.svg'
import {Schema} from '@/components/composites/purchase/long/form'
import {useFormContext} from 'react-hook-form'
import {useFormContext, useWatch} from 'react-hook-form'
import {Card} from '@/components/ui/card'
export default function Center() {
const form = useFormContext<Schema>()
const type = form.watch('type')
const type = useWatch({name: 'type'})
return (
<Card className="flex-auto p-6 flex flex-col gap-6 relative">

View File

@@ -14,20 +14,18 @@ import Pay from '@/components/composites/purchase/pay'
import {buttonVariants} from '@/components/ui/button'
import Link from 'next/link'
import {merge} from '@/lib/utils'
import {useFormContext} from 'react-hook-form'
import {useFormContext, useWatch} from 'react-hook-form'
import {Schema} from '@/components/composites/purchase/long/form'
import {Card} from '@/components/ui/card'
export default function Right() {
const profile = useProfileStore(store => store.profile)
const form = useFormContext<Schema>()
const method = form.watch('pay_type')
const mode = form.watch('type')
const live = form.watch('live')
const quota = form.watch('quota')
const expire = form.watch('expire')
const dailyLimit = form.watch('daily_limit')
const {control} = useFormContext<Schema>()
const method = useWatch({control, name: 'pay_type'})
const mode = useWatch({control, name: 'type'})
const live = useWatch({control, name: 'live'})
const quota = useWatch({control, name: 'quota'})
const expire = useWatch({control, name: 'expire'})
const dailyLimit = useWatch({control, name: 'daily_limit'})
const price = useMemo(() => {
const base = {
@@ -173,9 +171,8 @@ function BalanceOrLogin(props: {
long: {
mode: Number(props.mode),
live: Number(props.live),
daily_limit: props.dailyLimit,
expire_at: Number(props.expire),
quota: props.quota,
expire: Number(props.expire),
quota: props.mode === '1' ? props.dailyLimit : props.quota,
},
}}/>
</>

View File

@@ -8,7 +8,7 @@ import {useProfileStore} from '@/components/stores/profile'
import {Alert, AlertTitle} from '@/components/ui/alert'
import {toast} from 'sonner'
import {useRouter} from 'next/navigation'
import {completeResource, createResource, prepareResource} from '@/actions/resource'
import {completeResource, createResource, CreateResourceReq, prepareResource} from '@/actions/resource'
import {
TradeMethod,
TradeMethodDecoration,
@@ -19,7 +19,7 @@ import {usePlatformType} from '@/lib/hooks'
export type PayProps = {
amount: string
resource: Parameters<typeof createResource>[0]
resource: CreateResourceReq
} & ({
method: 'alipay' | 'wechat'
} | {

View File

@@ -7,13 +7,13 @@ import {Minus, Plus} from 'lucide-react'
import FormOption from '@/components/composites/purchase/option'
import Image from 'next/image'
import check from '../_assets/check.svg'
import {useFormContext} from 'react-hook-form'
import {useFormContext, useWatch} from 'react-hook-form'
import {Schema} from '@/components/composites/purchase/short/form'
import {Card} from '@/components/ui/card'
export default function Center() {
const form = useFormContext<Schema>()
const type = form.watch('type')
const type = useWatch({name: 'type'})
return (
<Card className="flex-auto p-6 flex flex-col gap-6 relative">
@@ -82,8 +82,8 @@ export default function Center() {
theme="outline"
type="button"
className="h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg"
onClick={() => form.setValue('quota', Math.max(10_000, Number(field.value) - 5_000))}
disabled={Number(field.value) === 10_000}>
onClick={() => form.setValue('quota', Math.max(10000, Number(field.value) - 5000))}
disabled={Number(field.value) === 10000}>
<Minus/>
</Button>
<Input
@@ -91,14 +91,13 @@ export default function Center() {
id={id}
type="number"
className="w-40 h-10 border border-gray-200 rounded-sm text-center"
min={10_000}
step={5_000}
/>
min={10000}
step={5000}/>
<Button
theme="outline"
type="button"
className="h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg"
onClick={() => form.setValue('quota', Number(field.value) + 5_000)}>
onClick={() => form.setValue('quota', Number(field.value) + 5000)}>
<Plus/>
</Button>
</div>

View File

@@ -14,18 +14,17 @@ import {buttonVariants} from '@/components/ui/button'
import Link from 'next/link'
import {merge} from '@/lib/utils'
import Pay from '@/components/composites/purchase/pay'
import {useFormContext} from 'react-hook-form'
import {useFormContext, useWatch} from 'react-hook-form'
import {Card} from '@/components/ui/card'
export default function Right() {
const form = useFormContext<Schema>()
const method = form.watch('pay_type')
const live = form.watch('live')
const mode = form.watch('type')
const dailyLimit = form.watch('daily_limit')
const expire = form.watch('expire')
const quota = form.watch('quota')
const {control} = useFormContext<Schema>()
const method = useWatch({control, name: 'pay_type'})
const live = useWatch({control, name: 'live'})
const mode = useWatch({control, name: 'type'})
const dailyLimit = useWatch({control, name: 'daily_limit'})
const expire = useWatch({control, name: 'expire'})
const quota = useWatch({control, name: 'quota'})
const price = useMemo(() => {
const base = live === '180' ? 150 : Number(live)
@@ -165,9 +164,8 @@ function BalanceOrLogin(props: {
short: {
mode: Number(props.mode),
live: Number(props.live),
quota: props.quota,
expire_at: Number(props.expire),
daily_limit: props.dailyLimit,
expire: Number(props.expire),
quota: props.mode === '1' ? props.dailyLimit : props.quota,
},
}}/>
</>

View File

@@ -17,20 +17,13 @@ import React, {ComponentProps, createContext, ReactNode, useContext, useId} from
type FormProps<T extends FieldValues> = {
form: UseFormReturn<T>
onSubmit?: SubmitHandler<T>
onError?: SubmitErrorHandler<T>
handler?: (e?: React.BaseSyntheticEvent) => Promise<void>
} & Omit<ComponentProps<'form'>, 'onSubmit' | 'onError'>
function Form<T extends FieldValues>(rawProps: FormProps<T>) {
const {children, onSubmit, onError, handler, ...props} = rawProps
const {children, handler, ...props} = rawProps
const form = props.form
const handle = handler || form.handleSubmit(
onSubmit || ((_) => {}),
onError,
)
return (
<FormProvider {...form}>
<form
@@ -38,7 +31,7 @@ function Form<T extends FieldValues>(rawProps: FormProps<T>) {
onSubmit={async (event) => {
event.preventDefault()
event.stopPropagation()
await handle(event)
await handler?.(event)
}}>
{children}
</form>