实现用户注销和删除系统
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AdminController } from './admin.controller';
|
||||
import { AdminUserController } from './controller/admin-user.controller';
|
||||
import { AdminUserService } from './service/admin-user.service';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { User } from 'src/user/entities/user.entity';
|
||||
import { UserModule } from 'src/user/user.module';
|
||||
@@ -35,8 +34,5 @@ import { BlogModule } from 'src/blog/blog.module';
|
||||
AdminWebResourceController,
|
||||
AdminWebBlogController,
|
||||
],
|
||||
providers: [
|
||||
AdminUserService,
|
||||
]
|
||||
})
|
||||
export class AdminModule { }
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CreateDto } from "../dto/admin-user/create.dto";
|
||||
import { UserService } from "src/user/user.service";
|
||||
import { UpdateDto } from "../dto/admin-user/update.dto";
|
||||
import { UpdatePasswordDto } from "../dto/admin-user/update-password.dto";
|
||||
import { RemoveUserDto } from "../dto/admin-user/remove.dto";
|
||||
|
||||
@Controller('admin/user')
|
||||
export class AdminUserController {
|
||||
@@ -53,8 +54,9 @@ export class AdminUserController {
|
||||
@Delete(':userId')
|
||||
async delete(
|
||||
@Param('userId', new ParseUUIDPipe({ version: '4' })) userId: string,
|
||||
@Query() dto: RemoveUserDto,
|
||||
) {
|
||||
return this.userService.delete(userId);
|
||||
return this.userService.delete(userId, dto.soft);
|
||||
}
|
||||
|
||||
@Post(':userId/password')
|
||||
|
||||
8
tone-page-server/src/admin/dto/admin-user/remove.dto.ts
Normal file
8
tone-page-server/src/admin/dto/admin-user/remove.dto.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { Transform } from "class-transformer";
|
||||
import { IsBoolean } from "class-validator";
|
||||
|
||||
export class RemoveUserDto {
|
||||
@Transform(({ value }) => value === 'true')
|
||||
@IsBoolean({ message: '需指定删除类型' })
|
||||
soft: boolean;
|
||||
}
|
||||
@@ -51,12 +51,23 @@ export class UserService {
|
||||
}
|
||||
}
|
||||
|
||||
async delete(userId: string): Promise<void> {
|
||||
const existingUser = await this.userRepository.findOne({ where: { userId } });
|
||||
async delete(userId: string, soft: boolean) {
|
||||
const existingUser = await this.userRepository.findOne({ where: { userId }, withDeleted: true });
|
||||
if (!existingUser) {
|
||||
throw new BadRequestException('User not found');
|
||||
throw new BadRequestException('用户不存在');
|
||||
}
|
||||
await this.userRepository.softDelete(existingUser.userId);
|
||||
|
||||
if (existingUser.deletedAt && soft) {
|
||||
throw new BadRequestException('账户已注销,不得重复操作')
|
||||
}
|
||||
|
||||
if (!existingUser.deletedAt && !soft) {
|
||||
throw new BadRequestException('账号未注销,请先注销再执行删除操作')
|
||||
}
|
||||
|
||||
return soft
|
||||
? await this.userRepository.softDelete(existingUser.userId)
|
||||
: await this.userRepository.delete(existingUser.userId)
|
||||
}
|
||||
|
||||
hashPassword(password: string, salt: string): string {
|
||||
|
||||
@@ -75,7 +75,7 @@ export function CreateUserEditor({ children, onRefresh }: CreateUserEditorProps)
|
||||
<Label htmlFor="password">密码</Label>
|
||||
<Input id="password" name="password" />
|
||||
</div>
|
||||
<Button type="submit">保存</Button>
|
||||
<Button type="submit">创建</Button>
|
||||
</form>
|
||||
|
||||
<DrawerFooter className="pt-2">
|
||||
|
||||
@@ -27,17 +27,17 @@ import {
|
||||
} 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";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
|
||||
export function UserInfoEditor({
|
||||
onClose,
|
||||
onUserUpdate,
|
||||
onUserDelete,
|
||||
onUserSoftDelete,
|
||||
userId,
|
||||
}: {
|
||||
onClose: () => void,
|
||||
onUserUpdate: (user: User) => void,
|
||||
onUserDelete: (userId: string) => void,
|
||||
onUserSoftDelete: (userId: string) => void,
|
||||
userId: string
|
||||
}) {
|
||||
const { user, isLoading, error } = useUser(userId);
|
||||
@@ -65,12 +65,12 @@ export function UserInfoEditor({
|
||||
const handleRemove = async (userId: string) => {
|
||||
try {
|
||||
setRemoveLoading(true);
|
||||
await AdminApi.user.remove(userId);
|
||||
toast.success("删除成功");
|
||||
onUserDelete(userId);
|
||||
await AdminApi.user.remove(userId, true);
|
||||
toast.success("注销成功");
|
||||
onUserSoftDelete(userId);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
toast.error((error as Error).message || "删除失败");
|
||||
toast.error((error as Error).message || "注销失败");
|
||||
} finally {
|
||||
setRemoveLoading(false);
|
||||
}
|
||||
@@ -208,18 +208,18 @@ function ProfileForm({ className, user, onSetPassword, onRemove, passwordDialogO
|
||||
|
||||
<AlertDialog>
|
||||
<AlertDialogTrigger asChild>
|
||||
<Button type="button" variant="destructive" className="flex-1">删除用户</Button>
|
||||
<Button type="button" variant="destructive" className="flex-1">注销</Button>
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>是否要删除该用户?</AlertDialogTitle>
|
||||
<AlertDialogTitle>是否要注销该账号?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
该操作无法撤销,这会永久删除该用户的所有数据
|
||||
该操作无法撤销,稍后可通过删除来彻底清理该用户信息
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => onRemove(user.userId)}>删除</AlertDialogAction>
|
||||
<AlertDialogAction onClick={() => onRemove(user.userId)}>注销</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
@@ -8,13 +8,17 @@ import { useState } from "react";
|
||||
import { UserInfoEditor } from "./components/user-info-editor";
|
||||
import { User } from "@/lib/types/user";
|
||||
import { CreateUserEditor } from "./components/create-user-editor";
|
||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger } from "@/components/ui/alert-dialog";
|
||||
import { AdminApi } from "@/lib/api";
|
||||
import { toast } from "sonner";
|
||||
import { ApiError } from "next/dist/server/api-utils";
|
||||
|
||||
|
||||
export default function Page() {
|
||||
const { users, isLoading, error, total, page, pageSize, mutate, refresh } = useUserList();
|
||||
const [editorUserId, setEditorUserId] = useState("");
|
||||
|
||||
const handleUserUpdate = async (newUser: User) => {
|
||||
const handleUserUpdateLocal = async (newUser: User) => {
|
||||
await mutate(
|
||||
(data) => {
|
||||
if (!data) return data;
|
||||
@@ -31,11 +35,14 @@ export default function Page() {
|
||||
)
|
||||
}
|
||||
|
||||
const handleUserDelete = async (userId: string) => {
|
||||
const handleUserDeleteLocal = async (userId: string, soft: boolean) => {
|
||||
await mutate(
|
||||
(data) => {
|
||||
if (!data) return data;
|
||||
return {
|
||||
return soft ? {
|
||||
...data,
|
||||
items: data.items.map(u => u.userId === userId ? { ...u, deletedAt: new Date().toLocaleString() } : u)
|
||||
} : {
|
||||
...data,
|
||||
items: data.items.filter((user) => user.userId !== userId),
|
||||
};
|
||||
@@ -46,6 +53,18 @@ export default function Page() {
|
||||
)
|
||||
}
|
||||
|
||||
const [deletedUserId, setDeletedUserId] = useState('');
|
||||
const handleUserDelete = async (userId: string) => {
|
||||
try {
|
||||
await AdminApi.user.remove(userId, false);
|
||||
toast.success('删除成功');
|
||||
handleUserDeleteLocal(userId, false);
|
||||
setDeletedUserId('');
|
||||
} catch (error) {
|
||||
toast.error((error as ApiError).message || '删除失败');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
@@ -86,7 +105,12 @@ export default function Page() {
|
||||
<TableCell>{user.email}</TableCell>
|
||||
<TableCell>{user.phone}</TableCell>
|
||||
<TableCell>
|
||||
<Button className="cursor-pointer" variant='outline' size='sm' onClick={() => setEditorUserId(user.userId)}>编辑</Button>
|
||||
{user.deletedAt
|
||||
? <Button className="cursor-pointer" variant='destructive' size='sm'
|
||||
onClick={() => setDeletedUserId(user.userId)}>删除</Button>
|
||||
: <Button className="cursor-pointer" variant='outline' size='sm'
|
||||
onClick={() => setEditorUserId(user.userId)}>编辑</Button>
|
||||
}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
@@ -104,14 +128,29 @@ export default function Page() {
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Table >
|
||||
|
||||
<UserInfoEditor
|
||||
onClose={() => setEditorUserId('')}
|
||||
userId={editorUserId}
|
||||
onUserUpdate={handleUserUpdate}
|
||||
onUserDelete={handleUserDelete}
|
||||
onUserUpdate={handleUserUpdateLocal}
|
||||
onUserSoftDelete={userId => handleUserDeleteLocal(userId, true)}
|
||||
/>
|
||||
|
||||
<AlertDialog open={!!deletedUserId} onOpenChange={o => !o && setDeletedUserId('')}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>是否要彻底删除账号?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
该操作无法撤销,会彻底删除该账号相关连的所有数据
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => handleUserDelete(deletedUserId)}>删除</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import fetcher from "../../fetcher";
|
||||
|
||||
export async function remove(userId: string) {
|
||||
return fetcher(`/api/admin/user/${userId}`, {
|
||||
export async function remove(userId: string, soft: boolean) {
|
||||
return fetcher(`/api/admin/user/${userId}?soft=${soft}`, {
|
||||
method: 'DELETE',
|
||||
})
|
||||
}
|
||||
@@ -7,4 +7,5 @@ export interface User {
|
||||
avatar?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
}
|
||||
Reference in New Issue
Block a user