diff --git a/backend/app/routers/friends.py b/backend/app/routers/friends.py index 2f834a7..351f27a 100644 --- a/backend/app/routers/friends.py +++ b/backend/app/routers/friends.py @@ -56,21 +56,6 @@ async def send_friend_request( raise HTTPException(status_code=400, detail=str(e)) -@router.post("/add-direct") -async def add_friend_direct( - req: FriendRequestCreate, - user: User = Depends(get_current_user), - db: AsyncSession = Depends(get_db), -): - """直接添加好友(跳过验证)""" - service = FriendService(db) - try: - await service.add_direct(user.id, req.to_user_id) - return {"success": True, "message": "已添加好友"} - except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) - - @router.put("/{friend_user_id}/remark") async def update_friend_remark( friend_user_id: str, diff --git a/backend/app/routers/users.py b/backend/app/routers/users.py index d775303..b19b1bb 100644 --- a/backend/app/routers/users.py +++ b/backend/app/routers/users.py @@ -25,6 +25,10 @@ class StatusUpdate(BaseModel): expires_hours: int | None = None +class DeleteAccountRequest(BaseModel): + password: str + + @router.get("/me", response_model=UserRead) async def get_me(user: User = Depends(get_current_user)): """获取当前用户信息""" @@ -58,6 +62,21 @@ async def change_password( raise HTTPException(status_code=400, detail=str(e)) +@router.delete("/me") +async def delete_account( + req: DeleteAccountRequest, + user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + """注销账号(需验证密码,级联清理数据)""" + service = UserService(db) + try: + await service.delete_account(user.id, req.password) + return {"success": True, "message": "账号已注销"} + except ValueError as e: + raise HTTPException(status_code=400, detail=str(e)) + + @router.put("/me/email") async def change_email( req: EmailChange, diff --git a/backend/app/services/user_service.py b/backend/app/services/user_service.py index 5c7db63..a2962fe 100644 --- a/backend/app/services/user_service.py +++ b/backend/app/services/user_service.py @@ -61,6 +61,16 @@ class UserService: user.password_hash = hash_password(new_password) user.updated_at = datetime.utcnow() + async def delete_account(self, user_id: str, password: str): + """注销账号:验证密码后删除用户(FK CASCADE 级联清理相关数据)""" + from sqlalchemy import delete + user = await self.get_by_id(user_id) + if not user: + raise ValueError("用户不存在") + if not verify_password(password, user.password_hash): + raise ValueError("密码错误,无法注销") + await self.db.execute(delete(User).where(User.id == user_id)) + async def change_email(self, user_id: str, new_email: str, password: str): """更换绑定邮箱""" user = await self.get_by_id(user_id) diff --git a/frontend/src/api/friends.ts b/frontend/src/api/friends.ts index 1b85549..ea7c91a 100644 --- a/frontend/src/api/friends.ts +++ b/frontend/src/api/friends.ts @@ -8,9 +8,6 @@ export const friendsApi = { sendRequest: (toUserId: string, message?: string) => api.post('/friends/request', { to_user_id: toUserId, message }), - addDirect: (toUserId: string) => - api.post('/friends/add-direct', { to_user_id: toUserId }), - acceptRequest: (requestId: string) => api.put(`/friends/request/${requestId}/accept`), diff --git a/frontend/src/views/contacts/SearchView.vue b/frontend/src/views/contacts/SearchView.vue index 607adae..e3648d9 100644 --- a/frontend/src/views/contacts/SearchView.vue +++ b/frontend/src/views/contacts/SearchView.vue @@ -13,29 +13,64 @@ {{ user.bio || '这个人很懒,什么都没写' }}
- 添加 - 发请求 + + {{ user.is_friend ? '已是好友' : user.request_sent ? '已发送' : '加为好友' }} +

没有找到匹配的用户

+ + + @@ -80,4 +119,25 @@ async function sendRequest(userId: string) { .result-bio { font-size: 13px; color: var(--color-text-hint); display: block; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .result-actions { display: flex; gap: 6px; flex-shrink: 0; } .empty { text-align: center; padding: 40px; } + +/* 请求弹窗 */ +.modal-overlay { + position: fixed; inset: 0; background: rgba(0,0,0,0.4); + display: flex; align-items: center; justify-content: center; z-index: 1000; +} +.request-modal { + width: 400px; max-width: 92vw; background: var(--color-surface); border-radius: 16px; + box-shadow: 0 12px 40px rgba(0,0,0,0.15); overflow: hidden; +} +.modal-header { + display: flex; justify-content: space-between; align-items: center; + padding: 18px 24px; border-bottom: 1px solid var(--color-border); +} +.modal-header h3 { margin: 0; font-size: 17px; } +.close-btn { cursor: pointer; font-size: 18px; color: var(--color-text-hint); } +.modal-body { padding: 20px 24px; display: flex; flex-direction: column; gap: 14px; } +.target-preview { display: flex; align-items: center; gap: 12px; } +.target-name { font-weight: 600; font-size: 15px; } +.target-hint { font-size: 12px; color: var(--color-text-hint); margin-top: 2px; } +.modal-footer { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 24px; border-top: 1px solid var(--color-border); } diff --git a/frontend/src/views/settings/AccountSettingsView.vue b/frontend/src/views/settings/AccountSettingsView.vue index 09e20ef..bb6f4ca 100644 --- a/frontend/src/views/settings/AccountSettingsView.vue +++ b/frontend/src/views/settings/AccountSettingsView.vue @@ -45,6 +45,47 @@

账号操作

退出登录 + + +
+

🗑️ 注销账号

+

注销后账号将永久删除,所有聊天记录、好友、朋友圈、花园数据都会被清除,且不可恢复

+ 申请注销账号 +
+ + + + @@ -62,6 +103,10 @@ const message = useMessage() const changingPwd = ref(false) const changingEmail = ref(false) +const showDeleteModal = ref(false) +const deleteConfirmText = ref('') +const deletePassword = ref('') +const deleting = ref(false) const pwdForm = reactive({ old_password: '', new_password: '', confirm: '' }) const emailForm = reactive({ new_email: '', password: '' }) @@ -121,6 +166,20 @@ function handleLogout() { auth.logout() router.push('/login') } + +async function confirmDelete() { + deleting.value = true + try { + await api.delete('/users/me', { data: { password: deletePassword.value } }) + message.success('账号已注销,感谢你曾来过青叶 🌿') + auth.logout() + router.push('/login') + } catch (e: any) { + message.error(e.response?.data?.detail || '注销失败,请检查密码') + } finally { + deleting.value = false + } +}