|
|
@@ -3,7 +3,7 @@ import { useQuery } from '@tanstack/react-query';
|
|
|
import { format } from 'date-fns';
|
|
|
import { Plus, Search, Edit, Trash2 } from 'lucide-react';
|
|
|
import { userClient } from '@/client/api';
|
|
|
-import type { InferRequestType } from 'hono/client';
|
|
|
+import type { InferRequestType, InferResponseType } from 'hono/client';
|
|
|
import { Button } from '@/client/components/ui/button';
|
|
|
import { Input } from '@/client/components/ui/input';
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
|
|
|
@@ -18,21 +18,35 @@ import { toast } from 'sonner';
|
|
|
import { Skeleton } from '@/client/components/ui/skeleton';
|
|
|
import { Switch } from '@/client/components/ui/switch';
|
|
|
|
|
|
+// 使用RPC方式提取类型
|
|
|
type CreateUserRequest = InferRequestType<typeof userClient.$post>['json'];
|
|
|
type UpdateUserRequest = InferRequestType<typeof userClient[':id']['$put']>['json'];
|
|
|
+type UserResponse = InferResponseType<typeof userClient.$get, 200>['data'][0];
|
|
|
|
|
|
-// 表单验证Schema
|
|
|
-const userFormSchema = z.object({
|
|
|
+// 创建用户表单Schema - 与后端CreateUserSchema保持一致
|
|
|
+const createUserFormSchema = z.object({
|
|
|
username: z.string().min(3, '用户名至少3个字符'),
|
|
|
nickname: z.string().optional(),
|
|
|
- email: z.string().email('请输入有效的邮箱地址').nullable().optional().transform(val => val === '' ? null : val),
|
|
|
- phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入有效的手机号').nullable().optional().transform(val => val === '' ? null : val),
|
|
|
- name: z.string().nullable().optional().transform(val => val === '' ? null : val),
|
|
|
- password: z.string().min(6, '密码至少6个字符').optional(),
|
|
|
+ email: z.string().email('请输入有效的邮箱地址').optional().transform(val => val === '' ? null : val),
|
|
|
+ phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入有效的手机号').optional().transform(val => val === '' ? null : val),
|
|
|
+ name: z.string().optional().transform(val => val === '' ? null : val),
|
|
|
+ password: z.string().min(6, '密码至少6个字符'),
|
|
|
isDisabled: z.boolean().default(false),
|
|
|
});
|
|
|
|
|
|
-type UserFormData = z.infer<typeof userFormSchema>;
|
|
|
+// 更新用户表单Schema - 与后端UpdateUserSchema保持一致
|
|
|
+const updateUserFormSchema = z.object({
|
|
|
+ username: z.string().min(3, '用户名至少3个字符').optional(),
|
|
|
+ nickname: z.string().optional(),
|
|
|
+ email: z.string().email('请输入有效的邮箱地址').optional().transform(val => val === '' ? null : val),
|
|
|
+ phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入有效的手机号').optional().transform(val => val === '' ? null : val),
|
|
|
+ name: z.string().optional().transform(val => val === '' ? null : val),
|
|
|
+ password: z.string().min(6, '密码至少6个字符').optional(),
|
|
|
+ isDisabled: z.boolean().optional(),
|
|
|
+});
|
|
|
+
|
|
|
+type CreateUserFormData = z.infer<typeof createUserFormSchema>;
|
|
|
+type UpdateUserFormData = z.infer<typeof updateUserFormSchema>;
|
|
|
|
|
|
export const UsersPage = () => {
|
|
|
const [searchParams, setSearchParams] = useState({
|
|
|
@@ -45,8 +59,23 @@ export const UsersPage = () => {
|
|
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
|
const [userToDelete, setUserToDelete] = useState<number | null>(null);
|
|
|
|
|
|
- const form = useForm<UserFormData>({
|
|
|
- resolver: zodResolver(userFormSchema),
|
|
|
+ const [isCreateForm, setIsCreateForm] = useState(true);
|
|
|
+
|
|
|
+ const createForm = useForm<CreateUserFormData>({
|
|
|
+ resolver: zodResolver(createUserFormSchema),
|
|
|
+ defaultValues: {
|
|
|
+ username: '',
|
|
|
+ nickname: '',
|
|
|
+ email: '',
|
|
|
+ phone: '',
|
|
|
+ name: '',
|
|
|
+ password: '',
|
|
|
+ isDisabled: false,
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ const updateForm = useForm<UpdateUserFormData>({
|
|
|
+ resolver: zodResolver(updateUserFormSchema),
|
|
|
defaultValues: {
|
|
|
username: '',
|
|
|
nickname: '',
|
|
|
@@ -92,7 +121,8 @@ export const UsersPage = () => {
|
|
|
// 打开创建用户对话框
|
|
|
const handleCreateUser = () => {
|
|
|
setEditingUser(null);
|
|
|
- form.reset({
|
|
|
+ setIsCreateForm(true);
|
|
|
+ createForm.reset({
|
|
|
username: '',
|
|
|
nickname: '',
|
|
|
email: '',
|
|
|
@@ -105,9 +135,10 @@ export const UsersPage = () => {
|
|
|
};
|
|
|
|
|
|
// 打开编辑用户对话框
|
|
|
- const handleEditUser = (user: any) => {
|
|
|
+ const handleEditUser = (user: UserResponse) => {
|
|
|
setEditingUser(user);
|
|
|
- form.reset({
|
|
|
+ setIsCreateForm(false);
|
|
|
+ updateForm.reset({
|
|
|
username: user.username,
|
|
|
nickname: user.nickname || '',
|
|
|
email: user.email || '',
|
|
|
@@ -118,40 +149,52 @@ export const UsersPage = () => {
|
|
|
setIsModalOpen(true);
|
|
|
};
|
|
|
|
|
|
- // 处理表单提交
|
|
|
- const handleSubmit = async (data: UserFormData) => {
|
|
|
+ // 处理创建表单提交
|
|
|
+ const handleCreateSubmit = async (data: CreateUserFormData) => {
|
|
|
try {
|
|
|
- const submitData = {
|
|
|
+ const submitData: CreateUserRequest = {
|
|
|
...data,
|
|
|
isDisabled: data.isDisabled ? 1 : 0,
|
|
|
};
|
|
|
|
|
|
- if (editingUser) {
|
|
|
- // 编辑用户
|
|
|
- const res = await userClient[':id']['$put']({
|
|
|
- param: { id: editingUser.id },
|
|
|
- json: submitData as UpdateUserRequest
|
|
|
- });
|
|
|
- if (res.status !== 200) {
|
|
|
- throw new Error('更新用户失败');
|
|
|
- }
|
|
|
- toast.success('用户更新成功');
|
|
|
- } else {
|
|
|
- // 创建用户
|
|
|
- const res = await userClient.$post({
|
|
|
- json: submitData as CreateUserRequest
|
|
|
- });
|
|
|
- if (res.status !== 201) {
|
|
|
- throw new Error('创建用户失败');
|
|
|
- }
|
|
|
- toast.success('用户创建成功');
|
|
|
+ const res = await userClient.$post({
|
|
|
+ json: submitData
|
|
|
+ });
|
|
|
+ if (res.status !== 201) {
|
|
|
+ throw new Error('创建用户失败');
|
|
|
+ }
|
|
|
+ toast.success('用户创建成功');
|
|
|
+ setIsModalOpen(false);
|
|
|
+ refetch();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('创建用户失败:', error);
|
|
|
+ toast.error('创建失败,请重试');
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 处理更新表单提交
|
|
|
+ const handleUpdateSubmit = async (data: UpdateUserFormData) => {
|
|
|
+ if (!editingUser) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ const submitData: UpdateUserRequest = {
|
|
|
+ ...data,
|
|
|
+ isDisabled: data.isDisabled !== undefined ? (data.isDisabled ? 1 : 0) : undefined,
|
|
|
+ };
|
|
|
+
|
|
|
+ const res = await userClient[':id']['$put']({
|
|
|
+ param: { id: editingUser.id },
|
|
|
+ json: submitData
|
|
|
+ });
|
|
|
+ if (res.status !== 200) {
|
|
|
+ throw new Error('更新用户失败');
|
|
|
}
|
|
|
-
|
|
|
+ toast.success('用户更新成功');
|
|
|
setIsModalOpen(false);
|
|
|
refetch();
|
|
|
} catch (error) {
|
|
|
- console.error('操作失败:', error);
|
|
|
- toast.error('操作失败,请重试');
|
|
|
+ console.error('更新用户失败:', error);
|
|
|
+ toast.error('更新失败,请重试');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -268,14 +311,14 @@ export const UsersPage = () => {
|
|
|
<TableCell>{user.name || '-'}</TableCell>
|
|
|
<TableCell>
|
|
|
<Badge
|
|
|
- variant={user.roles?.some(role => role.name === 'admin') ? 'destructive' : 'default'}
|
|
|
+ variant={user.roles?.some((role: any) => role.name === 'admin') ? 'destructive' : 'default'}
|
|
|
className="capitalize"
|
|
|
>
|
|
|
- {user.roles?.some(role => role.name === 'admin') ? '管理员' : '普通用户'}
|
|
|
+ {user.roles?.some((role: any) => role.name === 'admin') ? '管理员' : '普通用户'}
|
|
|
</Badge>
|
|
|
</TableCell>
|
|
|
<TableCell>
|
|
|
- <Badge
|
|
|
+ <Badge
|
|
|
variant={user.isDisabled === 1 ? 'secondary' : 'default'}
|
|
|
>
|
|
|
{user.isDisabled === 1 ? '禁用' : '启用'}
|
|
|
@@ -346,81 +389,81 @@ export const UsersPage = () => {
|
|
|
</DialogDescription>
|
|
|
</DialogHeader>
|
|
|
|
|
|
- <Form {...form}>
|
|
|
- <form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
|
|
|
- <FormField
|
|
|
- control={form.control}
|
|
|
- name="username"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem>
|
|
|
- <FormLabel>用户名</FormLabel>
|
|
|
- <FormControl>
|
|
|
- <Input placeholder="请输入用户名" {...field} />
|
|
|
- </FormControl>
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
-
|
|
|
- <FormField
|
|
|
- control={form.control}
|
|
|
- name="nickname"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem>
|
|
|
- <FormLabel>昵称</FormLabel>
|
|
|
- <FormControl>
|
|
|
- <Input placeholder="请输入昵称" {...field} />
|
|
|
- </FormControl>
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
-
|
|
|
- <FormField
|
|
|
- control={form.control}
|
|
|
- name="email"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem>
|
|
|
- <FormLabel>邮箱</FormLabel>
|
|
|
- <FormControl>
|
|
|
- <Input type="email" placeholder="请输入邮箱" {...field} />
|
|
|
- </FormControl>
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
-
|
|
|
- <FormField
|
|
|
- control={form.control}
|
|
|
- name="phone"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem>
|
|
|
- <FormLabel>手机号</FormLabel>
|
|
|
- <FormControl>
|
|
|
- <Input placeholder="请输入手机号" {...field} />
|
|
|
- </FormControl>
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
-
|
|
|
- <FormField
|
|
|
- control={form.control}
|
|
|
- name="name"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem>
|
|
|
- <FormLabel>真实姓名</FormLabel>
|
|
|
- <FormControl>
|
|
|
- <Input placeholder="请输入真实姓名" {...field} />
|
|
|
- </FormControl>
|
|
|
- <FormMessage />
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
-
|
|
|
- {!editingUser && (
|
|
|
+ {isCreateForm ? (
|
|
|
+ <Form {...createForm}>
|
|
|
+ <form onSubmit={createForm.handleSubmit(handleCreateSubmit)} className="space-y-4">
|
|
|
<FormField
|
|
|
- control={form.control}
|
|
|
+ control={createForm.control}
|
|
|
+ name="username"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>用户名</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input placeholder="请输入用户名" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="nickname"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>昵称</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input placeholder="请输入昵称" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="email"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>邮箱</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input type="email" placeholder="请输入邮箱" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="phone"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>手机号</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input placeholder="请输入手机号" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="name"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>真实姓名</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input placeholder="请输入真实姓名" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
name="password"
|
|
|
render={({ field }) => (
|
|
|
<FormItem>
|
|
|
@@ -432,39 +475,157 @@ export const UsersPage = () => {
|
|
|
</FormItem>
|
|
|
)}
|
|
|
/>
|
|
|
- )}
|
|
|
-
|
|
|
- <FormField
|
|
|
- control={form.control}
|
|
|
- name="isDisabled"
|
|
|
- render={({ field }) => (
|
|
|
- <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
|
|
- <div className="space-y-0.5">
|
|
|
- <FormLabel className="text-base">用户状态</FormLabel>
|
|
|
- <FormDescription>
|
|
|
- 禁用后用户将无法登录系统
|
|
|
- </FormDescription>
|
|
|
- </div>
|
|
|
- <FormControl>
|
|
|
- <Switch
|
|
|
- checked={field.value}
|
|
|
- onCheckedChange={field.onChange}
|
|
|
- />
|
|
|
- </FormControl>
|
|
|
- </FormItem>
|
|
|
- )}
|
|
|
- />
|
|
|
-
|
|
|
- <DialogFooter>
|
|
|
- <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
|
|
|
- 取消
|
|
|
- </Button>
|
|
|
- <Button type="submit">
|
|
|
- {editingUser ? '更新用户' : '创建用户'}
|
|
|
- </Button>
|
|
|
- </DialogFooter>
|
|
|
- </form>
|
|
|
- </Form>
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={createForm.control}
|
|
|
+ name="isDisabled"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
|
|
+ <div className="space-y-0.5">
|
|
|
+ <FormLabel className="text-base">用户状态</FormLabel>
|
|
|
+ <FormDescription>
|
|
|
+ 禁用后用户将无法登录系统
|
|
|
+ </FormDescription>
|
|
|
+ </div>
|
|
|
+ <FormControl>
|
|
|
+ <Switch
|
|
|
+ checked={field.value}
|
|
|
+ onCheckedChange={field.onChange}
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <DialogFooter>
|
|
|
+ <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
|
|
|
+ 取消
|
|
|
+ </Button>
|
|
|
+ <Button type="submit">
|
|
|
+ 创建用户
|
|
|
+ </Button>
|
|
|
+ </DialogFooter>
|
|
|
+ </form>
|
|
|
+ </Form>
|
|
|
+ ) : (
|
|
|
+ <Form {...updateForm}>
|
|
|
+ <form onSubmit={updateForm.handleSubmit(handleUpdateSubmit)} className="space-y-4">
|
|
|
+ <FormField
|
|
|
+ control={updateForm.control}
|
|
|
+ name="username"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>用户名</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input placeholder="请输入用户名" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={updateForm.control}
|
|
|
+ name="nickname"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>昵称</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input placeholder="请输入昵称" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={updateForm.control}
|
|
|
+ name="email"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>邮箱</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input type="email" placeholder="请输入邮箱" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={updateForm.control}
|
|
|
+ name="phone"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>手机号</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input placeholder="请输入手机号" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={updateForm.control}
|
|
|
+ name="name"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>真实姓名</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input placeholder="请输入真实姓名" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={updateForm.control}
|
|
|
+ name="password"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem>
|
|
|
+ <FormLabel>新密码</FormLabel>
|
|
|
+ <FormControl>
|
|
|
+ <Input type="password" placeholder="留空则不修改密码" {...field} />
|
|
|
+ </FormControl>
|
|
|
+ <FormMessage />
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <FormField
|
|
|
+ control={updateForm.control}
|
|
|
+ name="isDisabled"
|
|
|
+ render={({ field }) => (
|
|
|
+ <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
|
|
|
+ <div className="space-y-0.5">
|
|
|
+ <FormLabel className="text-base">用户状态</FormLabel>
|
|
|
+ <FormDescription>
|
|
|
+ 禁用后用户将无法登录系统
|
|
|
+ </FormDescription>
|
|
|
+ </div>
|
|
|
+ <FormControl>
|
|
|
+ <Switch
|
|
|
+ checked={field.value}
|
|
|
+ onCheckedChange={field.onChange}
|
|
|
+ />
|
|
|
+ </FormControl>
|
|
|
+ </FormItem>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+
|
|
|
+ <DialogFooter>
|
|
|
+ <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
|
|
|
+ 取消
|
|
|
+ </Button>
|
|
|
+ <Button type="submit">
|
|
|
+ 更新用户
|
|
|
+ </Button>
|
|
|
+ </DialogFooter>
|
|
|
+ </form>
|
|
|
+ </Form>
|
|
|
+ )}
|
|
|
</DialogContent>
|
|
|
</Dialog>
|
|
|
|