diff --git a/tone-page-server/src/admin/controller/admin-user.controller.ts b/tone-page-server/src/admin/controller/admin-user.controller.ts index c33533d..13ff6f7 100644 --- a/tone-page-server/src/admin/controller/admin-user.controller.ts +++ b/tone-page-server/src/admin/controller/admin-user.controller.ts @@ -32,7 +32,16 @@ export class AdminUserController { async create( @Body() createDto: CreateDto ) { - return this.userService.create(createDto); + return this.userService.create({ + ...createDto, + ...createDto.password && (() => { + const salt = this.userService.generateSalt(); + return { + salt, + password_hash: this.userService.hashPassword(createDto.password, salt), + } + })(), + }); } @Put(':userId') diff --git a/tone-page-server/src/admin/dto/admin-user/create.dto.ts b/tone-page-server/src/admin/dto/admin-user/create.dto.ts index 30f0e87..2c998c6 100644 --- a/tone-page-server/src/admin/dto/admin-user/create.dto.ts +++ b/tone-page-server/src/admin/dto/admin-user/create.dto.ts @@ -1,4 +1,4 @@ -import { IsString, Length, ValidateIf } from "class-validator"; +import { IsString, Length, Matches, ValidateIf } from "class-validator"; export class CreateDto { @ValidateIf(o => o.username !== null) @@ -6,18 +6,24 @@ export class CreateDto { @Length(6, 32, { message: '用户名长度只能为6~32' }) username: string | null; - @ValidateIf(o => o.username !== null) + @ValidateIf(o => o.nickname !== null) @IsString({ message: '昵称不得为空' }) @Length(6, 30, { message: '昵称长度只能为6~30' }) nickname: string | null; - @ValidateIf(o => o.username !== null) + @ValidateIf(o => o.email !== null) @IsString({ message: '邮箱不得为空' }) @Length(6, 254, { message: '邮箱长度只能为6~254' }) email: string | null; - @ValidateIf(o => o.username !== null) + @ValidateIf(o => o.phone !== null) @IsString({ message: '手机号不得为空' }) @Length(11, 11, { message: '手机号长度只能为11' }) phone: string | null; + + @ValidateIf(o => o.password !== null) + @IsString({ message: '密码不得为空' }) + @Length(6, 32) + @Matches(/^(?=.*[a-zA-Z])(?=.*\d)[a-zA-Z\d!@#$%^&*()_+\-=\[\]{};:'",.<>/?]{6,32}$/) + password: string | null; } \ No newline at end of file diff --git a/tone-page-server/src/user/user.service.ts b/tone-page-server/src/user/user.service.ts index 1844f18..af6a7bc 100644 --- a/tone-page-server/src/user/user.service.ts +++ b/tone-page-server/src/user/user.service.ts @@ -22,8 +22,15 @@ export class UserService { } async create(user: Partial): Promise { - const newUser = this.userRepository.create(user); - return this.userRepository.save(newUser); + try { + const newUser = this.userRepository.create(user); + return this.userRepository.save(newUser); + } catch (error) { + if (error instanceof QueryFailedError) { + throw new ConflictException(this.getDuplicateErrorMessage(error)); + } + throw new BadRequestException('创建用户失败'); + } } async update(userId: string, user: Partial): Promise { diff --git a/tone-page-web/app/console/(with-menu)/user/components/create-user-editor.tsx b/tone-page-web/app/console/(with-menu)/user/components/create-user-editor.tsx new file mode 100644 index 0000000..b7533a6 --- /dev/null +++ b/tone-page-web/app/console/(with-menu)/user/components/create-user-editor.tsx @@ -0,0 +1,88 @@ +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { + Drawer, + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer" +import { useState } from "react"; +import { AdminApi } from "@/lib/api"; +import { toast } from "sonner"; +import { ApiError } from "next/dist/server/api-utils"; + +interface CreateUserEditorProps { + children: React.ReactNode +} + +export function CreateUserEditor({ children }: CreateUserEditorProps) { + const [open, setOpen] = useState(false); + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + const formData = new FormData(event.currentTarget); + try { + await AdminApi.user.create({ + username: formData.get("username")?.toString() || null, + nickname: formData.get("nickname")?.toString() || null, + email: formData.get("email")?.toString() || null, + phone: formData.get("phone")?.toString() || null, + password: formData.get("password")?.toString() || null, + }); + setOpen(false); + toast.success('创建成功') + } catch (error) { + toast.error((error as ApiError).message || '创建失败') + } + } + + return ( + <> +
setOpen(true)} className="cursor-pointer"> + {children} +
+ setOpen(false)}> + + + 新增用户 + 确保你在保存之前检查所有更改 + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + + + + + +
+
+ + ) +} \ No newline at end of file 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 4450475..73abcb3 100644 --- a/tone-page-web/app/console/(with-menu)/user/page.tsx +++ b/tone-page-web/app/console/(with-menu)/user/page.tsx @@ -7,6 +7,7 @@ import { useUserList } from "@/hooks/admin/user/use-user-list"; import { useState } from "react"; import { UserInfoEditor } from "./components/user-info-editor"; import { User } from "@/lib/types/user"; +import { CreateUserEditor } from "./components/create-user-editor"; export default function Page() { @@ -47,6 +48,11 @@ export default function Page() { return ( <> +
+ + + +
{error && {error.message}} diff --git a/tone-page-web/lib/api/admin/user/create.ts b/tone-page-web/lib/api/admin/user/create.ts index 07e5411..4bc3e5b 100644 --- a/tone-page-web/lib/api/admin/user/create.ts +++ b/tone-page-web/lib/api/admin/user/create.ts @@ -1,3 +1,20 @@ -export function create() { +import { User } from "@/lib/types/user"; +import fetcher from "../../fetcher"; +interface createUserParams { + username: string | null; + nickname: string | null; + email: string | null; + phone: string | null; + password: string | null; +} + +export async function create(data: createUserParams) { + return fetcher("/api/admin/user", { + method: "POST", + body: JSON.stringify(data), + headers: { + "Content-Type": "application/json", + }, + }); } \ No newline at end of file