From c17108e09409cc2404fa7df1307fd3e52fb57510 Mon Sep 17 00:00:00 2001 From: tone <3341154833@qq.com> Date: Mon, 12 May 2025 12:47:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E4=BF=AE=E6=94=B9=E5=AF=86?= =?UTF-8?q?=E7=A0=81=E5=88=A0=E9=99=A4=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/components/user-info-editor.tsx | 98 +++++++++-- .../app/console/(with-menu)/user/page.tsx | 16 ++ tone-page-web/components/ui/alert-dialog.tsx | 157 ++++++++++++++++++ tone-page-web/hooks/admin/user/use-user.ts | 1 - tone-page-web/lib/api/admin/user/index.ts | 3 +- tone-page-web/lib/api/admin/user/remove.ts | 2 +- .../lib/api/admin/user/set-password.ts | 2 +- tone-page-web/package.json | 1 + tone-page-web/pnpm-lock.yaml | 65 ++++++++ 9 files changed, 331 insertions(+), 14 deletions(-) create mode 100644 tone-page-web/components/ui/alert-dialog.tsx diff --git a/tone-page-web/app/console/(with-menu)/user/components/user-info-editor.tsx b/tone-page-web/app/console/(with-menu)/user/components/user-info-editor.tsx index f924de4..c30dddf 100644 --- a/tone-page-web/app/console/(with-menu)/user/components/user-info-editor.tsx +++ b/tone-page-web/app/console/(with-menu)/user/components/user-info-editor.tsx @@ -26,38 +26,66 @@ import { AlertTitle, } from "@/components/ui/alert" import { AlertCircle } from "lucide-react"; +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog"; +import { Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; export function UserInfoEditor({ onClose, onUserUpdate, + onUserDelete, userId, }: { onClose: () => void, onUserUpdate: (user: User) => void, + onUserDelete: (userId: string) => void, userId: string }) { - const { user, isLoading, error } = userId ? useUser(userId) : {}; + const { user, isLoading, error } = useUser(userId); + + const [saveLoading, setSaveLoading] = React.useState(false); const handleSave = async (user: updateUser) => { try { + setSaveLoading(true); const res = await AdminApi.user.update(userId, user); if (res) { toast.success("保存成功"); onUserUpdate(res); - onClose(); } else { throw new Error(); } } catch (error) { toast.error((error as Error).message || "保存失败"); + } finally { + setSaveLoading(false); } } - const handleRemove = async () => { - + const [removeLoading, setRemoveLoading] = React.useState(false); + const handleRemove = async (userId: string) => { + try { + setRemoveLoading(true); + await AdminApi.user.remove(userId); + toast.success("删除成功"); + onUserDelete(userId); + onClose(); + } catch (error) { + toast.error((error as Error).message || "删除失败"); + } finally { + setRemoveLoading(false); + } } - const handleSetPassword = async () => { - + const [setPasswordLoading, setSetPasswordLoading] = React.useState(false); + const handleSetPassword = async (userId: string, password: string) => { + try { + setSetPasswordLoading(true); + await AdminApi.user.setPassword(userId, password); + toast.success("密码修改成功"); + } catch (error) { + toast.error((error as Error).message || "密码修改失败"); + } finally { + setSetPasswordLoading(false); + } } return ( @@ -112,9 +140,11 @@ export function UserInfoEditor({ function ProfileForm({ className, user, onSetPassword, onRemove, ...props }: React.ComponentProps<"form"> & { user: User, - onSetPassword: () => Promise, - onRemove: () => Promise, + onSetPassword: (userId: string, password: string) => Promise, + onRemove: (userId: string) => Promise, }) { + const [newPassword, setNewPassword] = React.useState(""); + return (
@@ -138,8 +168,56 @@ function ProfileForm({ className, user, onSetPassword, onRemove, ...props }:
- - + + + + + + + 修改密码 + + 新密码长度在6-32位之间,且至少包含一个字母和一个数字,可以包含特殊字符 + + +
+
+ + setNewPassword(e.target.value)} + className="col-span-3" + /> +
+
+ + + + + +
+
+ + + + + + + + 是否要删除该用户? + + 该操作无法撤销,这会永久删除该用户的所有数据 + + + + 取消 + onRemove(user.userId)}>删除 + + +
diff --git a/tone-page-web/app/console/(with-menu)/user/page.tsx b/tone-page-web/app/console/(with-menu)/user/page.tsx index 78745e1..4450475 100644 --- a/tone-page-web/app/console/(with-menu)/user/page.tsx +++ b/tone-page-web/app/console/(with-menu)/user/page.tsx @@ -30,6 +30,21 @@ export default function Page() { ) } + const handleUserDelete = async (userId: string) => { + await mutate( + (data) => { + if (!data) return data; + return { + ...data, + items: data.items.filter((user) => user.userId !== userId), + }; + }, + { + revalidate: false, + } + ) + } + return ( <> @@ -89,6 +104,7 @@ export default function Page() { onClose={() => setEditorUserId('')} userId={editorUserId} onUserUpdate={handleUserUpdate} + onUserDelete={handleUserDelete} /> ) diff --git a/tone-page-web/components/ui/alert-dialog.tsx b/tone-page-web/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..0863e40 --- /dev/null +++ b/tone-page-web/components/ui/alert-dialog.tsx @@ -0,0 +1,157 @@ +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +function AlertDialog({ + ...props +}: React.ComponentProps) { + return +} + +function AlertDialogTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogPortal({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + ) +} + +function AlertDialogHeader({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogFooter({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function AlertDialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogAction({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AlertDialogCancel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/tone-page-web/hooks/admin/user/use-user.ts b/tone-page-web/hooks/admin/user/use-user.ts index c5c4a4c..73718a4 100644 --- a/tone-page-web/hooks/admin/user/use-user.ts +++ b/tone-page-web/hooks/admin/user/use-user.ts @@ -7,7 +7,6 @@ export function useUser(userId: string) { ['/api/admin/user', userId], () => AdminApi.user.get(userId), { - revalidateOnFocus: false, revalidateOnReconnect: false, revalidateIfStale: false, dedupingInterval: 0, diff --git a/tone-page-web/lib/api/admin/user/index.ts b/tone-page-web/lib/api/admin/user/index.ts index 630f508..965ef50 100644 --- a/tone-page-web/lib/api/admin/user/index.ts +++ b/tone-page-web/lib/api/admin/user/index.ts @@ -2,4 +2,5 @@ export * from './list'; export * from './get'; export * from './create'; export * from './update'; -export * from './set-password'; \ No newline at end of file +export * from './set-password'; +export * from './remove'; \ No newline at end of file diff --git a/tone-page-web/lib/api/admin/user/remove.ts b/tone-page-web/lib/api/admin/user/remove.ts index 47e0f36..6f1bf54 100644 --- a/tone-page-web/lib/api/admin/user/remove.ts +++ b/tone-page-web/lib/api/admin/user/remove.ts @@ -1,7 +1,7 @@ import fetcher from "../../fetcher"; export async function remove(userId: string) { - return fetcher(`/admin/user/${userId}`, { + return fetcher(`/api/admin/user/${userId}`, { method: 'DELETE', }) } \ No newline at end of file diff --git a/tone-page-web/lib/api/admin/user/set-password.ts b/tone-page-web/lib/api/admin/user/set-password.ts index 55d44e9..ac7acb1 100644 --- a/tone-page-web/lib/api/admin/user/set-password.ts +++ b/tone-page-web/lib/api/admin/user/set-password.ts @@ -1,7 +1,7 @@ import fetcher from "../../fetcher"; export async function setPassword(userId: string, password: string) { - return fetcher(`/admin/user/${userId}/password`, { + return fetcher(`/api/admin/user/${userId}/password`, { method: 'POST', body: JSON.stringify({ password, diff --git a/tone-page-web/package.json b/tone-page-web/package.json index 0526e2a..d341ecb 100644 --- a/tone-page-web/package.json +++ b/tone-page-web/package.json @@ -9,6 +9,7 @@ "lint": "next lint" }, "dependencies": { + "@radix-ui/react-alert-dialog": "^1.1.13", "@radix-ui/react-avatar": "^1.1.7", "@radix-ui/react-collapsible": "^1.1.10", "@radix-ui/react-dialog": "^1.1.11", diff --git a/tone-page-web/pnpm-lock.yaml b/tone-page-web/pnpm-lock.yaml index 81c72f0..8e575bd 100644 --- a/tone-page-web/pnpm-lock.yaml +++ b/tone-page-web/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@radix-ui/react-alert-dialog': + specifier: ^1.1.13 + version: 1.1.13(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-avatar': specifier: ^1.1.7 version: 1.1.7(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -415,6 +418,19 @@ packages: '@radix-ui/primitive@1.1.2': resolution: {integrity: sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==} + '@radix-ui/react-alert-dialog@1.1.13': + resolution: {integrity: sha512-/uPs78OwxGxslYOG5TKeUsv9fZC0vo376cXSADdKirTmsLJU2au6L3n34c3p6W26rFDDDze/hwy4fYeNd0qdGA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-arrow@1.1.4': resolution: {integrity: sha512-qz+fxrqgNxG0dYew5l7qR3c7wdgRu1XVUHGnGYX7rg5HM4p9SWaRmJwfgR3J0SgyUKayLmzQIun+N6rWRgiRKw==} peerDependencies: @@ -524,6 +540,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dialog@1.1.13': + resolution: {integrity: sha512-ARFmqUyhIVS3+riWzwGTe7JLjqwqgnODBUZdqpWar/z1WFs9z76fuOs/2BOWCR+YboRn4/WN9aoaGVwqNRr8VA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-direction@1.1.1': resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} peerDependencies: @@ -2759,6 +2788,20 @@ snapshots: '@radix-ui/primitive@1.1.2': {} + '@radix-ui/react-alert-dialog@1.1.13(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-dialog': 1.1.13(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.2(@types/react@19.1.2)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.2 + '@types/react-dom': 19.1.2(@types/react@19.1.2) + '@radix-ui/react-arrow@1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/react-primitive': 2.1.0(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -2864,6 +2907,28 @@ snapshots: '@types/react': 19.1.2 '@types/react-dom': 19.1.2(@types/react@19.1.2) + '@radix-ui/react-dialog@1.1.13(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/primitive': 1.1.2 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-dismissable-layer': 1.1.9(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-focus-guards': 1.1.2(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-focus-scope': 1.1.6(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-portal': 1.1.8(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-presence': 1.1.4(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-primitive': 2.1.2(@types/react-dom@19.1.2(@types/react@19.1.2))(@types/react@19.1.2)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': 1.2.2(@types/react@19.1.2)(react@19.1.0) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.2)(react@19.1.0) + aria-hidden: 1.2.4 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-remove-scroll: 2.6.3(@types/react@19.1.2)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.2 + '@types/react-dom': 19.1.2(@types/react@19.1.2) + '@radix-ui/react-direction@1.1.1(@types/react@19.1.2)(react@19.1.0)': dependencies: react: 19.1.0