diff --git a/backend/app/api/v1/endpoints/users.py b/backend/app/api/v1/endpoints/users.py
index 96f6126..32e1d48 100644
--- a/backend/app/api/v1/endpoints/users.py
+++ b/backend/app/api/v1/endpoints/users.py
@@ -1,54 +1,96 @@
-from typing import Any, List
-from fastapi import APIRouter, Body, Depends, HTTPException
+from typing import Any, List, Optional
+from fastapi import APIRouter, Body, Depends, HTTPException, Query
from fastapi.encoders import jsonable_encoder
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.future import select
+from sqlalchemy import func, or_
from app.api import deps
from app.core import security
from app.db.session import get_db
from app.models.user import User
-from app.schemas.user import User as UserSchema, UserCreate, UserUpdate
+from app.schemas.user import User as UserSchema, UserCreate, UserUpdate, UserListResponse
router = APIRouter()
-@router.get("/", response_model=List[UserSchema])
+@router.get("/", response_model=UserListResponse)
async def read_users(
db: AsyncSession = Depends(get_db),
- skip: int = 0,
- limit: int = 100,
+ skip: int = Query(0, ge=0),
+ limit: int = Query(20, ge=1, le=100),
+ search: Optional[str] = Query(None, description="搜索关键词"),
+ role: Optional[str] = Query(None, description="角色筛选"),
+ is_active: Optional[bool] = Query(None, description="状态筛选"),
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""
- Retrieve users.
+ 获取用户列表(支持分页、搜索、筛选)
"""
- result = await db.execute(select(User).offset(skip).limit(limit))
+ query = select(User)
+ count_query = select(func.count(User.id))
+
+ # 搜索条件
+ if search:
+ search_filter = or_(
+ User.email.ilike(f"%{search}%"),
+ User.full_name.ilike(f"%{search}%"),
+ User.phone.ilike(f"%{search}%")
+ )
+ query = query.where(search_filter)
+ count_query = count_query.where(search_filter)
+
+ # 角色筛选
+ if role:
+ query = query.where(User.role == role)
+ count_query = count_query.where(User.role == role)
+
+ # 状态筛选
+ if is_active is not None:
+ query = query.where(User.is_active == is_active)
+ count_query = count_query.where(User.is_active == is_active)
+
+ # 获取总数
+ total_result = await db.execute(count_query)
+ total = total_result.scalar()
+
+ # 分页查询
+ query = query.order_by(User.created_at.desc()).offset(skip).limit(limit)
+ result = await db.execute(query)
users = result.scalars().all()
- return users
+
+ return {
+ "users": users,
+ "total": total,
+ "skip": skip,
+ "limit": limit
+ }
@router.post("/", response_model=UserSchema)
async def create_user(
*,
db: AsyncSession = Depends(get_db),
user_in: UserCreate,
+ current_user: User = Depends(deps.get_current_active_superuser),
) -> Any:
"""
- Create new user.
+ 创建新用户(仅管理员)
"""
result = await db.execute(select(User).where(User.email == user_in.email))
user = result.scalars().first()
if user:
raise HTTPException(
status_code=400,
- detail="The user with this username already exists in the system.",
+ detail="该邮箱已被注册",
)
db_user = User(
email=user_in.email,
hashed_password=security.get_password_hash(user_in.password),
full_name=user_in.full_name,
- is_active=user_in.is_active,
- is_superuser=user_in.is_superuser,
+ phone=user_in.phone,
+ role=user_in.role,
+ is_active=user_in.is_active if user_in.is_active is not None else True,
+ is_superuser=user_in.is_superuser if user_in.is_superuser is not None else False,
)
db.add(db_user)
await db.commit()
@@ -60,8 +102,125 @@ async def read_user_me(
current_user: User = Depends(deps.get_current_user),
) -> Any:
"""
- Get current user.
+ 获取当前用户信息
"""
return current_user
+@router.put("/me", response_model=UserSchema)
+async def update_user_me(
+ *,
+ db: AsyncSession = Depends(get_db),
+ user_in: UserUpdate,
+ current_user: User = Depends(deps.get_current_user),
+) -> Any:
+ """
+ 更新当前用户信息
+ """
+ update_data = user_in.model_dump(exclude_unset=True)
+
+ # 普通用户不能修改自己的角色和超级管理员状态
+ update_data.pop('role', None)
+ update_data.pop('is_superuser', None)
+ update_data.pop('is_active', None)
+
+ # 如果更新密码
+ if 'password' in update_data and update_data['password']:
+ update_data['hashed_password'] = security.get_password_hash(update_data['password'])
+ update_data.pop('password', None)
+
+ for field, value in update_data.items():
+ setattr(current_user, field, value)
+
+ await db.commit()
+ await db.refresh(current_user)
+ return current_user
+
+@router.get("/{user_id}", response_model=UserSchema)
+async def read_user(
+ user_id: str,
+ db: AsyncSession = Depends(get_db),
+ current_user: User = Depends(deps.get_current_user),
+) -> Any:
+ """
+ 获取指定用户信息
+ """
+ result = await db.execute(select(User).where(User.id == user_id))
+ user = result.scalars().first()
+ if not user:
+ raise HTTPException(status_code=404, detail="用户不存在")
+ return user
+
+@router.put("/{user_id}", response_model=UserSchema)
+async def update_user(
+ user_id: str,
+ *,
+ db: AsyncSession = Depends(get_db),
+ user_in: UserUpdate,
+ current_user: User = Depends(deps.get_current_active_superuser),
+) -> Any:
+ """
+ 更新用户信息(仅管理员)
+ """
+ result = await db.execute(select(User).where(User.id == user_id))
+ user = result.scalars().first()
+ if not user:
+ raise HTTPException(status_code=404, detail="用户不存在")
+
+ update_data = user_in.model_dump(exclude_unset=True)
+
+ # 如果更新密码
+ if 'password' in update_data and update_data['password']:
+ update_data['hashed_password'] = security.get_password_hash(update_data['password'])
+ update_data.pop('password', None)
+
+ for field, value in update_data.items():
+ setattr(user, field, value)
+
+ await db.commit()
+ await db.refresh(user)
+ return user
+
+@router.delete("/{user_id}")
+async def delete_user(
+ user_id: str,
+ db: AsyncSession = Depends(get_db),
+ current_user: User = Depends(deps.get_current_active_superuser),
+) -> Any:
+ """
+ 删除用户(仅管理员)
+ """
+ if user_id == current_user.id:
+ raise HTTPException(status_code=400, detail="不能删除自己的账户")
+
+ result = await db.execute(select(User).where(User.id == user_id))
+ user = result.scalars().first()
+ if not user:
+ raise HTTPException(status_code=404, detail="用户不存在")
+
+ await db.delete(user)
+ await db.commit()
+ return {"message": "用户已删除"}
+
+@router.post("/{user_id}/toggle-status", response_model=UserSchema)
+async def toggle_user_status(
+ user_id: str,
+ db: AsyncSession = Depends(get_db),
+ current_user: User = Depends(deps.get_current_active_superuser),
+) -> Any:
+ """
+ 切换用户状态(启用/禁用)
+ """
+ if user_id == current_user.id:
+ raise HTTPException(status_code=400, detail="不能禁用自己的账户")
+
+ result = await db.execute(select(User).where(User.id == user_id))
+ user = result.scalars().first()
+ if not user:
+ raise HTTPException(status_code=404, detail="用户不存在")
+
+ user.is_active = not user.is_active
+ await db.commit()
+ await db.refresh(user)
+ return user
+
diff --git a/backend/app/schemas/user.py b/backend/app/schemas/user.py
index adc33a6..584c5c4 100644
--- a/backend/app/schemas/user.py
+++ b/backend/app/schemas/user.py
@@ -1,4 +1,4 @@
-from typing import Optional
+from typing import Optional, List
from pydantic import BaseModel, EmailStr
class UserBase(BaseModel):
@@ -33,4 +33,10 @@ class UserInDBBase(UserBase):
class User(UserInDBBase):
pass
+class UserListResponse(BaseModel):
+ users: List[User]
+ total: int
+ skip: int
+ limit: int
+
diff --git a/frontend/src/app/routes.tsx b/frontend/src/app/routes.tsx
index e982f67..207fba8 100644
--- a/frontend/src/app/routes.tsx
+++ b/frontend/src/app/routes.tsx
@@ -7,6 +7,7 @@ import AuditTasks from "@/pages/AuditTasks";
import TaskDetail from "@/pages/TaskDetail";
import AdminDashboard from "@/pages/AdminDashboard";
import LogsPage from "@/pages/LogsPage";
+import Account from "@/pages/Account";
import type { ReactNode } from 'react';
export interface RouteConfig {
@@ -71,6 +72,12 @@ const routes: RouteConfig[] = [
element: