Skip to content

Commit 01e9c99

Browse files
committed
公共校验表单弹窗开发
1 parent 9d9c413 commit 01e9c99

11 files changed

Lines changed: 1469 additions & 2676 deletions

File tree

package-lock.json

Lines changed: 1137 additions & 2666 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"@radix-ui/react-avatar": "^1.1.9",
2424
"@radix-ui/react-checkbox": "^1.3.1",
2525
"@radix-ui/react-collapsible": "^1.1.11",
26-
"@radix-ui/react-dialog": "^1.1.13",
26+
"@radix-ui/react-dialog": "^1.1.14",
2727
"@radix-ui/react-dropdown-menu": "^2.1.15",
2828
"@radix-ui/react-label": "^2.1.6",
2929
"@radix-ui/react-popover": "^1.1.14",
@@ -85,5 +85,6 @@
8585
"workerDirectory": [
8686
"public"
8787
]
88-
}
88+
},
89+
"packageManager": "pnpm@10.12.4+sha512.5ea8b0deed94ed68691c9bad4c955492705c5eeb8a87ef86bc62c74a26b037b08ff9570f108b2e4dbd1dd1a9186fea925e527f141c648e85af45631074680184"
8990
}

public/mockServiceWorker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* - Please do NOT modify this file.
88
*/
99

10-
const PACKAGE_VERSION = '2.10.2'
10+
const PACKAGE_VERSION = '2.10.4'
1111
const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af'
1212
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
1313
const activeClientIds = new Set()

src/components/dialog-form.tsx

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { Button } from "@/components/ui/button";
2+
import {
3+
Dialog,
4+
DialogClose,
5+
DialogContent,
6+
DialogDescription,
7+
DialogFooter,
8+
DialogHeader,
9+
DialogTitle,
10+
DialogTrigger
11+
} from "@/components/ui/dialog";
12+
import {
13+
Form,
14+
FormControl,
15+
FormField,
16+
FormItem,
17+
FormLabel,
18+
FormMessage
19+
} from "@/components/ui/form";
20+
import { Input } from "@/components/ui/input";
21+
import { zodResolver } from "@hookform/resolvers/zod";
22+
import { useForm } from "react-hook-form";
23+
24+
import { useIntl } from "react-intl";
25+
import { z } from "zod";
26+
type FormField = {
27+
name: string;
28+
label: string;
29+
validate?: z.ZodTypeAny;
30+
defaultValue?: string;
31+
};
32+
33+
export default function Index({title,description,fields,onSubmit}: {title: string,description?: string,fields: FormField[], onSubmit: (values: Record<string, unknown>) => void}) {
34+
35+
const schemaShape = fields.reduce((acc, field) => {
36+
acc[field.name] = field.validate || z.string().optional();
37+
return acc;
38+
}, {} as Record<string, z.ZodTypeAny>);
39+
const formSchema = z.object(schemaShape);
40+
const intl = useIntl();
41+
const form = useForm<z.infer<typeof formSchema>>({
42+
resolver: zodResolver(formSchema),
43+
defaultValues: Object.fromEntries(fields.map(item => [item.name, item.defaultValue || ""])),
44+
})
45+
46+
return (
47+
<Dialog>
48+
<DialogTrigger asChild>
49+
<Button>{intl.formatMessage({ id: title })}</Button>
50+
</DialogTrigger>
51+
<DialogContent className="sm:max-w-[425px]">
52+
<Form {...form}>
53+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
54+
<DialogHeader>
55+
<DialogTitle>{title}</DialogTitle>
56+
<DialogDescription>
57+
{description}
58+
</DialogDescription>
59+
</DialogHeader>
60+
61+
{fields.map((f) => (
62+
<FormField
63+
key={f.name}
64+
control={form.control}
65+
name={f.name}
66+
render={({ field }) => (
67+
<FormItem>
68+
<FormLabel>{intl.formatMessage({ id: f.label })}</FormLabel>
69+
<FormControl>
70+
<Input placeholder="" {...field} />
71+
</FormControl>
72+
<FormMessage />
73+
</FormItem>
74+
)}
75+
/>
76+
))}
77+
<DialogFooter>
78+
<DialogClose asChild>
79+
<Button variant="outline">{intl.formatMessage({ id: 'button.cancel' })}</Button>
80+
</DialogClose>
81+
<Button type="submit">{intl.formatMessage({ id: 'button.save' })}</Button>
82+
</DialogFooter>
83+
</form>
84+
</Form>
85+
</DialogContent>
86+
</Dialog >
87+
)
88+
}
89+
export type { FormField };
90+

src/components/ui/dialog.tsx

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import * as React from "react"
2+
import * as DialogPrimitive from "@radix-ui/react-dialog"
3+
import { XIcon } from "lucide-react"
4+
5+
import { cn } from "@/lib/utils"
6+
7+
function Dialog({
8+
...props
9+
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
10+
return <DialogPrimitive.Root data-slot="dialog" {...props} />
11+
}
12+
13+
function DialogTrigger({
14+
...props
15+
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
16+
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
17+
}
18+
19+
function DialogPortal({
20+
...props
21+
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
22+
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
23+
}
24+
25+
function DialogClose({
26+
...props
27+
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
28+
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
29+
}
30+
31+
function DialogOverlay({
32+
className,
33+
...props
34+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
35+
return (
36+
<DialogPrimitive.Overlay
37+
data-slot="dialog-overlay"
38+
className={cn(
39+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
40+
className
41+
)}
42+
{...props}
43+
/>
44+
)
45+
}
46+
47+
function DialogContent({
48+
className,
49+
children,
50+
showCloseButton = true,
51+
...props
52+
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
53+
showCloseButton?: boolean
54+
}) {
55+
return (
56+
<DialogPortal data-slot="dialog-portal">
57+
<DialogOverlay />
58+
<DialogPrimitive.Content
59+
data-slot="dialog-content"
60+
className={cn(
61+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg",
62+
className
63+
)}
64+
{...props}
65+
>
66+
{children}
67+
{showCloseButton && (
68+
<DialogPrimitive.Close
69+
data-slot="dialog-close"
70+
className="ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
71+
>
72+
<XIcon />
73+
<span className="sr-only">Close</span>
74+
</DialogPrimitive.Close>
75+
)}
76+
</DialogPrimitive.Content>
77+
</DialogPortal>
78+
)
79+
}
80+
81+
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
82+
return (
83+
<div
84+
data-slot="dialog-header"
85+
className={cn("flex flex-col gap-2 text-center sm:text-left", className)}
86+
{...props}
87+
/>
88+
)
89+
}
90+
91+
function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
92+
return (
93+
<div
94+
data-slot="dialog-footer"
95+
className={cn(
96+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
97+
className
98+
)}
99+
{...props}
100+
/>
101+
)
102+
}
103+
104+
function DialogTitle({
105+
className,
106+
...props
107+
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
108+
return (
109+
<DialogPrimitive.Title
110+
data-slot="dialog-title"
111+
className={cn("text-lg leading-none font-semibold", className)}
112+
{...props}
113+
/>
114+
)
115+
}
116+
117+
function DialogDescription({
118+
className,
119+
...props
120+
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
121+
return (
122+
<DialogPrimitive.Description
123+
data-slot="dialog-description"
124+
className={cn("text-muted-foreground text-sm", className)}
125+
{...props}
126+
/>
127+
)
128+
}
129+
130+
export {
131+
Dialog,
132+
DialogClose,
133+
DialogContent,
134+
DialogDescription,
135+
DialogFooter,
136+
DialogHeader,
137+
DialogOverlay,
138+
DialogPortal,
139+
DialogTitle,
140+
DialogTrigger,
141+
}

src/locale/en-US.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,8 @@ export default {
5959
'table.previous':'Previous',
6060
'table.columns':'Columns',
6161
'table.filterField':'Filter Field...',
62+
'validate.user': 'User must be at least 2 characters.',
63+
'validate.username': 'Username must be at least 4 characters."',
64+
'validate.email': 'Invalid email address.',
65+
'validate.phone': 'Invalid phone number.'
6266
};

src/locale/zh-CN.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,8 @@ export default {
5959
'table.previous':'上一页',
6060
'table.columns':'列',
6161
'table.filterField':'筛选字段...',
62+
'validate.user': '用户姓名至少2个字符。',
63+
'validate.username': '用户名至少4个字符。',
64+
'validate.email': '无效的邮箱地址。',
65+
'validate.phone': '无效的手机号。'
6266
};

src/mock/system/user.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,10 @@ const handlers = [
8080
}}
8181
)
8282
}),
83-
http.delete<{ id: string },never>('/api/system/users/:id', async ({ request,params}) => {
83+
http.delete<{ id: string },never>('/api/system/users', async ({ request,params}) => {
8484
const locale = request.headers.get("locale") || "zh";
85-
const id = params.id;
86-
console.log(id);
85+
const ids = await request.clone().json();
86+
console.log(ids);
8787
return HttpResponse.json({
8888
code:200,
8989
message:localeMap[locale]['success']

src/pages/login/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ export default function Login({
104104
<FormItem>
105105
<FormLabel>{intl.formatMessage({ id: 'page.login.password' })}</FormLabel>
106106
<FormControl>
107-
<Input placeholder="super" {...field} />
107+
<Input placeholder="super" type="password" {...field} />
108108
</FormControl>
109109
<FormMessage />
110110
</FormItem>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import DialogForm, { FormField } from "@/components/dialog-form";
2+
import { useIntl } from "react-intl";
3+
import { z } from "zod";
4+
5+
export default function Index() {
6+
const intl = useIntl();
7+
const fields:FormField[] = [
8+
{
9+
name: "user",
10+
label: "page.system.user.header.user",
11+
defaultValue: "",
12+
validate: z.string().min(2, {
13+
message: intl.formatMessage({ id: 'validate.user' }),
14+
})
15+
},
16+
{
17+
name: "username",
18+
label: "page.system.user.header.userName",
19+
defaultValue: "",
20+
validate: z.string().min(4, {
21+
message: intl.formatMessage({ id: 'validate.username' }),
22+
})
23+
},
24+
{
25+
name: "email",
26+
label: "page.system.user.header.email",
27+
defaultValue: "",
28+
validate: z.string().email({
29+
message: intl.formatMessage({ id: 'validate.email' }),
30+
})
31+
},
32+
{
33+
name: "phone",
34+
label: "page.system.user.header.phone",
35+
defaultValue: "",
36+
validate: z.string().regex(/^1[3-9]\d{9}$/, {
37+
message: intl.formatMessage({ id: 'validate.phone' }),
38+
})
39+
},
40+
{
41+
name: "group",
42+
label: "page.system.user.header.groupName",
43+
defaultValue: "",
44+
validate: z.string(),
45+
},
46+
{
47+
name: "defaultRole",
48+
label: "page.system.user.header.defaultRole",
49+
defaultValue: "",
50+
validate: z.string()
51+
},
52+
]
53+
const schemaShape = fields.reduce((acc, field) => {
54+
acc[field.name] = field.validate || z.string().optional();
55+
return acc;
56+
}, {} as Record<string, z.ZodTypeAny>);
57+
const formSchema = z.object(schemaShape);
58+
// 2. Define a submit handler.
59+
function onSubmit(values: z.infer<typeof formSchema>) {
60+
// Do something with the form values.
61+
// ✅ This will be type-safe and validated.
62+
console.log(values)
63+
}
64+
return (
65+
<DialogForm
66+
title={intl.formatMessage({ id: 'button.add' })}
67+
fields={fields}
68+
onSubmit={onSubmit}>
69+
</DialogForm>
70+
)
71+
}

0 commit comments

Comments
 (0)