"use client" import {useEffect} from "react" import {useMutation, useQueryClient} from "@tanstack/react-query" import {zodResolver} from "@hookform/resolvers/zod" import {useForm} from "react-hook-form" import {Loader2} from "lucide-react" import {toast} from "sonner" import {z} from "zod" import {Button} from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import {Input} from "@/components/ui/input" import {Label} from "@/components/ui/label" import {Textarea} from "@/components/ui/textarea" import {type OriginItem, type OriginMutationPayload, OriginService} from "@/lib/services/openflare" const originSchema = z.object({ name: z.string().max(255), address: z .string() .trim() .min(1, "请输入源站地址") .refine( (value) => !/[/?#]/.test(value) && !value.includes("://"), "源站地址格式不合法", ), remark: z.string().max(255), }) type OriginFormValues = z.infer const originsQueryKey = ["openflare", "origins"] as const function toFormValues(origin?: OriginItem | null): OriginFormValues { if (!origin) return { name: "", address: "", remark: "" } return { name: origin.name, address: origin.address, remark: origin.remark || "", } } function toPayload(values: OriginFormValues): OriginMutationPayload { return { name: values.name.trim(), address: values.address.trim(), remark: values.remark.trim(), } } interface OriginEditorDialogProps { open: boolean onOpenChange: (open: boolean) => void origin?: OriginItem | null onSaved?: () => void } export function OriginEditorDialog({ open, onOpenChange, origin, onSaved, }: OriginEditorDialogProps) { const queryClient = useQueryClient() const form = useForm({ resolver: zodResolver(originSchema), defaultValues: toFormValues(origin), }) useEffect(() => { if (open) form.reset(toFormValues(origin)) }, [form, origin, open]) const mutation = useMutation({ mutationFn: async (values: OriginFormValues) => { const payload = toPayload(values) return origin ? OriginService.update(origin.id, payload) : OriginService.create(payload) }, onSuccess: async () => { toast.success(origin ? "源站已更新" : "源站已创建") await queryClient.invalidateQueries({ queryKey: originsQueryKey }) if (origin) { await queryClient.invalidateQueries({ queryKey: ["openflare", "origins", String(origin.id)], }) } onSaved?.() onOpenChange(false) }, onError: (error) => { toast.error(error instanceof Error ? error.message : "保存失败") }, }) return ( {origin ? "编辑源站" : "新增源站"} 源站作为规则里的可复用地址目录,协议和端口仍由规则决定。
mutation.mutate(values))} >
{form.formState.errors.address ? (

{form.formState.errors.address.message}

) : null}