From d3f0eea0ec82d18516ad3c823e5713822dd72595 Mon Sep 17 00:00:00 2001 From: Zhenyu Zheng Date: Tue, 10 Feb 2026 20:15:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=92=8C=E5=88=A0=E9=99=A4=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E4=BF=AE=E5=A4=8D=E7=99=BB=E5=BD=95=E9=A1=B5logo?= =?UTF-8?q?=E9=BB=91=E8=89=B2=E8=83=8C=E6=99=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端新增 PATCH/DELETE /auth/users/{user_id} 端点,支持编辑和删除用户 - 添加安全保护:不能删除自己、不能删除/降级最后一个超级管理员 - 前端用户管理页面新增操作列(编辑、删除按钮)和编辑对话框 - 修复登录页logo图片透明背景渲染为黑色的问题 - 清理旧的 logo.svg 文件 --- backend/app/api/auth.py | 98 ++++++++++++++++++++++- backend/app/schemas/user.py | 1 + frontend/public/logo.svg | 45 ----------- frontend/src/api/auth.ts | 14 ++++ frontend/src/views/Login.vue | 3 + frontend/src/views/UserManage.vue | 128 +++++++++++++++++++++++++++++- logo.svg | 45 ----------- 7 files changed, 241 insertions(+), 93 deletions(-) delete mode 100644 frontend/public/logo.svg delete mode 100644 logo.svg diff --git a/backend/app/api/auth.py b/backend/app/api/auth.py index 34d2d62..32bfd2c 100644 --- a/backend/app/api/auth.py +++ b/backend/app/api/auth.py @@ -11,7 +11,7 @@ from app.models import User from app.models.password_reset import PasswordResetToken from app.schemas import ( - LoginRequest, Token, UserCreate, UserOut, UserInfoResponse, + LoginRequest, Token, UserCreate, UserUpdate, UserOut, UserInfoResponse, InitialSetupRequest, PasswordResetRequest, PasswordResetConfirm, SystemStatusResponse, ) @@ -245,6 +245,102 @@ def list_all_users( return users +@router.patch("/users/{user_id}", response_model=UserOut) +def update_user( + user_id: int, + user_update: UserUpdate, + current_user: User = Depends(get_current_active_superuser), + db: Session = Depends(get_db), +): + """ + Update a user's information. Only superusers can update users. + """ + target_user = db.query(User).filter(User.id == user_id).first() + if not target_user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="用户不存在", + ) + + # Prevent demoting yourself + if user_update.is_superuser is False and target_user.id == current_user.id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="不能取消自己的超级管理员权限", + ) + + # Prevent demoting the last superuser + if user_update.is_superuser is False and target_user.is_superuser: + superuser_count = db.query(User).filter( + User.is_superuser == True, User.is_default_admin == False # noqa: E712 + ).count() + if superuser_count <= 1: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="不能取消最后一个超级管理员的权限", + ) + + # Check email uniqueness if updating email + if user_update.email is not None: + existing = db.query(User).filter( + User.email == user_update.email, User.id != user_id + ).first() + if existing: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="该邮箱已被其他用户使用", + ) + + # Apply updates + update_data = user_update.model_dump(exclude_unset=True) + if "password" in update_data: + target_user.hashed_password = get_password_hash(update_data.pop("password")) + for field, value in update_data.items(): + setattr(target_user, field, value) + + db.commit() + db.refresh(target_user) + return target_user + + +@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT) +def delete_user( + user_id: int, + current_user: User = Depends(get_current_active_superuser), + db: Session = Depends(get_db), +): + """ + Delete a user. Only superusers can delete users. + """ + target_user = db.query(User).filter(User.id == user_id).first() + if not target_user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="用户不存在", + ) + + # Cannot delete yourself + if target_user.id == current_user.id: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="不能删除自己的账号", + ) + + # Cannot delete the last superuser + if target_user.is_superuser: + superuser_count = db.query(User).filter( + User.is_superuser == True, User.is_default_admin == False # noqa: E712 + ).count() + if superuser_count <= 1: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="不能删除最后一个超级管理员", + ) + + db.delete(target_user) + db.commit() + + @router.post("/password-reset/request", status_code=status.HTTP_200_OK) def request_password_reset( reset_request: PasswordResetRequest, diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py index d021db7..08e7dc7 100644 --- a/backend/app/schemas/user.py +++ b/backend/app/schemas/user.py @@ -20,6 +20,7 @@ class UserUpdate(BaseModel): full_name: Optional[str] = None password: Optional[str] = Field(None, min_length=6, max_length=100) is_active: Optional[bool] = None + is_superuser: Optional[bool] = None class UserOut(UserBase): diff --git a/frontend/public/logo.svg b/frontend/public/logo.svg deleted file mode 100644 index a899ad7..0000000 --- a/frontend/public/logo.svg +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index e185372..03cf913 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -83,3 +83,17 @@ export async function listAllUsers(): Promise { const { data } = await apiClient.get('/auth/users') return data } + +export async function updateUser(userId: number, userData: { + email?: string + full_name?: string + is_superuser?: boolean + is_active?: boolean +}): Promise { + const { data } = await apiClient.patch(`/auth/users/${userId}`, userData) + return data +} + +export async function deleteUser(userId: number): Promise { + await apiClient.delete(`/auth/users/${userId}`) +} diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index c72250c..c63dc6e 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -164,6 +164,9 @@ const handleLogin = async () => { height: auto; margin: 0 auto 16px; display: block; + background-color: #fff; + border-radius: 8px; + padding: 8px; } h2 { diff --git a/frontend/src/views/UserManage.vue b/frontend/src/views/UserManage.vue index fed0d09..af9d029 100644 --- a/frontend/src/views/UserManage.vue +++ b/frontend/src/views/UserManage.vue @@ -29,6 +29,17 @@ + + + @@ -71,21 +82,64 @@ 注册 + + + + + + + + + + + + + + + + + + + 超级管理员 + + + + +