init
This commit is contained in:
+7
-1
@@ -69,7 +69,13 @@ class Settings:
|
|||||||
# JWT配置
|
# JWT配置
|
||||||
self.SECRET_KEY = os.getenv('SECRET_KEY', 'your-secret-key-change-in-production')
|
self.SECRET_KEY = os.getenv('SECRET_KEY', 'your-secret-key-change-in-production')
|
||||||
self.ALGORITHM = os.getenv('ALGORITHM', 'HS256')
|
self.ALGORITHM = os.getenv('ALGORITHM', 'HS256')
|
||||||
self.ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv('ACCESS_TOKEN_EXPIRE_MINUTES', '1440')) # 24小时
|
self.ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv('ACCESS_TOKEN_EXPIRE_MINUTES', '1440'))
|
||||||
|
|
||||||
|
# 管理员账户配置
|
||||||
|
self.ADMIN_USERNAME = os.getenv('ADMIN_USERNAME', 'admin')
|
||||||
|
self.ADMIN_PASSWORD = os.getenv('ADMIN_PASSWORD', 'admin123')
|
||||||
|
self.ADMIN_EMAIL = os.getenv('ADMIN_EMAIL', 'admin@gemold.com')
|
||||||
|
self.ADMIN_FULL_NAME = os.getenv('ADMIN_FULL_NAME', '系统管理员')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def DATABASE_URL(self) -> str:
|
def DATABASE_URL(self) -> str:
|
||||||
|
|||||||
+426
-69
@@ -1,40 +1,33 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, status
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
from fastapi.security import OAuth2PasswordRequestForm
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from pydantic import BaseModel, EmailStr
|
from pydantic import BaseModel
|
||||||
from typing import Optional
|
from typing import Optional, List
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from sqlalchemy import select
|
||||||
|
|
||||||
from database.database import get_db_session
|
from database.database import get_db_session
|
||||||
from services.auth_service import (
|
from services.auth_service import (
|
||||||
authenticate_user,
|
authenticate_user,
|
||||||
create_access_token,
|
create_access_token,
|
||||||
create_user,
|
|
||||||
get_user_by_username,
|
|
||||||
get_user_by_email,
|
|
||||||
get_current_active_user,
|
get_current_active_user,
|
||||||
get_current_admin_user
|
get_password_hash
|
||||||
)
|
)
|
||||||
from models.database import User
|
from models.database import User, Role, Permission, UserRole, RolePermission
|
||||||
from config.settings import settings
|
from config.settings import settings
|
||||||
|
from utils.logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
router = APIRouter(prefix="/api/auth", tags=["认证"])
|
router = APIRouter(prefix="/api/auth", tags=["认证"])
|
||||||
|
|
||||||
|
|
||||||
class UserCreate(BaseModel):
|
|
||||||
username: str
|
|
||||||
email: EmailStr
|
|
||||||
password: str
|
|
||||||
full_name: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class UserResponse(BaseModel):
|
class UserResponse(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
username: str
|
username: str
|
||||||
email: str
|
email: str
|
||||||
full_name: Optional[str]
|
full_name: Optional[str]
|
||||||
is_active: bool
|
is_active: bool
|
||||||
is_superuser: bool
|
roles: List[str]
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
@@ -51,6 +44,63 @@ class LoginRequest(BaseModel):
|
|||||||
password: str
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
class RoleCreate(BaseModel):
|
||||||
|
code: str
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class RoleResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
code: str
|
||||||
|
name: str
|
||||||
|
description: Optional[str]
|
||||||
|
is_system: bool
|
||||||
|
permissions: List[str]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionCreate(BaseModel):
|
||||||
|
code: str
|
||||||
|
name: str
|
||||||
|
module: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
code: str
|
||||||
|
name: str
|
||||||
|
module: Optional[str]
|
||||||
|
description: Optional[str]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreate(BaseModel):
|
||||||
|
username: str
|
||||||
|
email: str
|
||||||
|
password: str
|
||||||
|
full_name: Optional[str] = None
|
||||||
|
role_ids: List[int] = []
|
||||||
|
|
||||||
|
|
||||||
|
class UserUpdate(BaseModel):
|
||||||
|
email: Optional[str] = None
|
||||||
|
full_name: Optional[str] = None
|
||||||
|
is_active: Optional[bool] = None
|
||||||
|
role_ids: Optional[List[int]] = None
|
||||||
|
|
||||||
|
|
||||||
|
def check_admin(user: User) -> bool:
|
||||||
|
if not user.is_superuser:
|
||||||
|
raise HTTPException(status_code=403, detail="需要管理员权限")
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@router.post("/login", response_model=Token)
|
@router.post("/login", response_model=Token)
|
||||||
async def login(
|
async def login(
|
||||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||||
@@ -72,7 +122,14 @@ async def login(
|
|||||||
return Token(
|
return Token(
|
||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
token_type="bearer",
|
token_type="bearer",
|
||||||
user=UserResponse.from_orm(user)
|
user=UserResponse(
|
||||||
|
id=user.id,
|
||||||
|
username=user.username,
|
||||||
|
email=user.email,
|
||||||
|
full_name=user.full_name,
|
||||||
|
is_active=user.is_active,
|
||||||
|
roles=[r.code for r in user.roles]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -96,39 +153,29 @@ async def login_json(
|
|||||||
return Token(
|
return Token(
|
||||||
access_token=access_token,
|
access_token=access_token,
|
||||||
token_type="bearer",
|
token_type="bearer",
|
||||||
user=UserResponse.from_orm(user)
|
user=UserResponse(
|
||||||
|
id=user.id,
|
||||||
|
username=user.username,
|
||||||
|
email=user.email,
|
||||||
|
full_name=user.full_name,
|
||||||
|
is_active=user.is_active,
|
||||||
|
roles=[r.code for r in user.roles]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/register", response_model=UserResponse, status_code=201)
|
|
||||||
async def register(
|
|
||||||
user_data: UserCreate,
|
|
||||||
db_session: AsyncSession = Depends(get_db_session)
|
|
||||||
):
|
|
||||||
existing_user = await get_user_by_username(db_session, user_data.username)
|
|
||||||
if existing_user:
|
|
||||||
raise HTTPException(status_code=400, detail="用户名已存在")
|
|
||||||
|
|
||||||
existing_email = await get_user_by_email(db_session, user_data.email)
|
|
||||||
if existing_email:
|
|
||||||
raise HTTPException(status_code=400, detail="邮箱已被注册")
|
|
||||||
|
|
||||||
user = await create_user(
|
|
||||||
db_session=db_session,
|
|
||||||
username=user_data.username,
|
|
||||||
email=user_data.email,
|
|
||||||
password=user_data.password,
|
|
||||||
full_name=user_data.full_name
|
|
||||||
)
|
|
||||||
|
|
||||||
return UserResponse.from_orm(user)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/me", response_model=UserResponse)
|
@router.get("/me", response_model=UserResponse)
|
||||||
async def get_current_user_info(
|
async def get_current_user_info(
|
||||||
current_user: User = Depends(get_current_active_user)
|
current_user: User = Depends(get_current_active_user)
|
||||||
):
|
):
|
||||||
return UserResponse.from_orm(current_user)
|
return UserResponse(
|
||||||
|
id=current_user.id,
|
||||||
|
username=current_user.username,
|
||||||
|
email=current_user.email,
|
||||||
|
full_name=current_user.full_name,
|
||||||
|
is_active=current_user.is_active,
|
||||||
|
roles=[r.code for r in current_user.roles]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/logout")
|
@router.post("/logout")
|
||||||
@@ -136,58 +183,368 @@ async def logout():
|
|||||||
return {"message": "已登出"}
|
return {"message": "已登出"}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/users", response_model=list[UserResponse])
|
@router.get("/users", response_model=List[UserResponse])
|
||||||
async def list_users(
|
async def list_users(
|
||||||
db_session: AsyncSession = Depends(get_db_session),
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
admin_user: User = Depends(get_current_admin_user)
|
current_user: User = Depends(get_current_active_user)
|
||||||
):
|
):
|
||||||
from sqlalchemy import select
|
check_admin(current_user)
|
||||||
result = await db_session.execute(select(User))
|
result = await db_session.execute(
|
||||||
|
select(User).options(
|
||||||
|
select(User).options()
|
||||||
|
)
|
||||||
|
)
|
||||||
users = result.scalars().all()
|
users = result.scalars().all()
|
||||||
return [UserResponse.from_orm(u) for u in users]
|
return [
|
||||||
|
UserResponse(
|
||||||
|
id=u.id,
|
||||||
|
username=u.username,
|
||||||
|
email=u.email,
|
||||||
|
full_name=u.full_name,
|
||||||
|
is_active=u.is_active,
|
||||||
|
roles=[r.code for r in u.roles]
|
||||||
|
) for u in users
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
@router.put("/users/{user_id}/toggle-active", response_model=UserResponse)
|
@router.post("/users", response_model=UserResponse, status_code=201)
|
||||||
async def toggle_user_active(
|
async def create_user(
|
||||||
user_id: int,
|
user_data: UserCreate,
|
||||||
db_session: AsyncSession = Depends(get_db_session),
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
admin_user: User = Depends(get_current_admin_user)
|
current_user: User = Depends(get_current_active_user)
|
||||||
):
|
):
|
||||||
from sqlalchemy import select
|
check_admin(current_user)
|
||||||
result = await db_session.execute(select(User).where(User.id == user_id))
|
|
||||||
user = result.scalar_one_or_none()
|
|
||||||
|
|
||||||
if not user:
|
existing = await db_session.execute(
|
||||||
raise HTTPException(status_code=404, detail="用户不存在")
|
select(User).where(User.username == user_data.username)
|
||||||
|
)
|
||||||
|
if existing.scalar_one_or_none():
|
||||||
|
raise HTTPException(status_code=400, detail="用户名已存在")
|
||||||
|
|
||||||
if user.id == admin_user.id:
|
existing_email = await db_session.execute(
|
||||||
raise HTTPException(status_code=400, detail="不能禁用自己的账户")
|
select(User).where(User.email == user_data.email)
|
||||||
|
)
|
||||||
|
if existing_email.scalar_one_or_none():
|
||||||
|
raise HTTPException(status_code=400, detail="邮箱已存在")
|
||||||
|
|
||||||
|
user = User(
|
||||||
|
username=user_data.username,
|
||||||
|
email=user_data.email,
|
||||||
|
hashed_password=get_password_hash(user_data.password),
|
||||||
|
full_name=user_data.full_name,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
db_session.add(user)
|
||||||
|
await db_session.flush()
|
||||||
|
|
||||||
|
for role_id in user_data.role_ids:
|
||||||
|
user_role = UserRole(user_id=user.id, role_id=role_id)
|
||||||
|
db_session.add(user_role)
|
||||||
|
|
||||||
user.is_active = not user.is_active
|
|
||||||
await db_session.commit()
|
await db_session.commit()
|
||||||
await db_session.refresh(user)
|
await db_session.refresh(user)
|
||||||
|
|
||||||
return UserResponse.from_orm(user)
|
logger.info(f"管理员 {current_user.username} 创建了用户 {user.username}")
|
||||||
|
|
||||||
|
return UserResponse(
|
||||||
|
id=user.id,
|
||||||
|
username=user.username,
|
||||||
|
email=user.email,
|
||||||
|
full_name=user.full_name,
|
||||||
|
is_active=user.is_active,
|
||||||
|
roles=[r.code for r in user.roles]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/users/{user_id}/toggle-admin", response_model=UserResponse)
|
@router.put("/users/{user_id}", response_model=UserResponse)
|
||||||
async def toggle_user_admin(
|
async def update_user(
|
||||||
user_id: int,
|
user_id: int,
|
||||||
|
user_data: UserUpdate,
|
||||||
db_session: AsyncSession = Depends(get_db_session),
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
admin_user: User = Depends(get_current_admin_user)
|
current_user: User = Depends(get_current_active_user)
|
||||||
):
|
):
|
||||||
from sqlalchemy import select
|
check_admin(current_user)
|
||||||
|
|
||||||
result = await db_session.execute(select(User).where(User.id == user_id))
|
result = await db_session.execute(select(User).where(User.id == user_id))
|
||||||
user = result.scalar_one_or_none()
|
user = result.scalar_one_or_none()
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
raise HTTPException(status_code=404, detail="用户不存在")
|
raise HTTPException(status_code=404, detail="用户不存在")
|
||||||
|
|
||||||
if user.id == admin_user.id:
|
if user_data.email is not None:
|
||||||
raise HTTPException(status_code=400, detail="不能修改自己的管理员权限")
|
user.email = user_data.email
|
||||||
|
if user_data.full_name is not None:
|
||||||
|
user.full_name = user_data.full_name
|
||||||
|
if user_data.is_active is not None:
|
||||||
|
user.is_active = user_data.is_active
|
||||||
|
|
||||||
|
if user_data.role_ids is not None:
|
||||||
|
await db_session.execute(
|
||||||
|
select(UserRole).where(UserRole.user_id == user_id)
|
||||||
|
)
|
||||||
|
for ur in (await db_session.execute(select(UserRole).where(UserRole.user_id == user_id))).scalars().all():
|
||||||
|
await db_session.delete(ur)
|
||||||
|
|
||||||
|
for role_id in user_data.role_ids:
|
||||||
|
user_role = UserRole(user_id=user.id, role_id=role_id)
|
||||||
|
db_session.add(user_role)
|
||||||
|
|
||||||
user.is_superuser = not user.is_superuser
|
|
||||||
await db_session.commit()
|
await db_session.commit()
|
||||||
await db_session.refresh(user)
|
await db_session.refresh(user)
|
||||||
|
|
||||||
return UserResponse.from_orm(user)
|
logger.info(f"管理员 {current_user.username} 更新了用户 {user.username}")
|
||||||
|
|
||||||
|
return UserResponse(
|
||||||
|
id=user.id,
|
||||||
|
username=user.username,
|
||||||
|
email=user.email,
|
||||||
|
full_name=user.full_name,
|
||||||
|
is_active=user.is_active,
|
||||||
|
roles=[r.code for r in user.roles]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/users/{user_id}")
|
||||||
|
async def delete_user(
|
||||||
|
user_id: int,
|
||||||
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
):
|
||||||
|
check_admin(current_user)
|
||||||
|
|
||||||
|
result = await db_session.execute(select(User).where(User.id == user_id))
|
||||||
|
user = result.scalar_one_or_none()
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=404, detail="用户不存在")
|
||||||
|
|
||||||
|
if user.id == current_user.id:
|
||||||
|
raise HTTPException(status_code=400, detail="不能删除自己的账户")
|
||||||
|
|
||||||
|
username = user.username
|
||||||
|
await db_session.delete(user)
|
||||||
|
await db_session.commit()
|
||||||
|
|
||||||
|
logger.info(f"管理员 {current_user.username} 删除了用户 {username}")
|
||||||
|
|
||||||
|
return {"message": "用户已删除"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/users/{user_id}/reset-password")
|
||||||
|
async def reset_user_password(
|
||||||
|
user_id: int,
|
||||||
|
new_password: str,
|
||||||
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
):
|
||||||
|
check_admin(current_user)
|
||||||
|
|
||||||
|
result = await db_session.execute(select(User).where(User.id == user_id))
|
||||||
|
user = result.scalar_one_or_none()
|
||||||
|
if not user:
|
||||||
|
raise HTTPException(status_code=404, detail="用户不存在")
|
||||||
|
|
||||||
|
user.hashed_password = get_password_hash(new_password)
|
||||||
|
await db_session.commit()
|
||||||
|
|
||||||
|
logger.info(f"管理员 {current_user.username} 重置了用户 {user.username} 的密码")
|
||||||
|
|
||||||
|
return {"message": "密码已重置"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/roles", response_model=List[RoleResponse])
|
||||||
|
async def list_roles(
|
||||||
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
):
|
||||||
|
check_admin(current_user)
|
||||||
|
result = await db_session.execute(select(Role))
|
||||||
|
roles = result.scalars().all()
|
||||||
|
return [
|
||||||
|
RoleResponse(
|
||||||
|
id=r.id,
|
||||||
|
code=r.code,
|
||||||
|
name=r.name,
|
||||||
|
description=r.description,
|
||||||
|
is_system=r.is_system,
|
||||||
|
permissions=[p.code for p in r.permissions]
|
||||||
|
) for r in roles
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/roles", response_model=RoleResponse, status_code=201)
|
||||||
|
async def create_role(
|
||||||
|
role_data: RoleCreate,
|
||||||
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
):
|
||||||
|
check_admin(current_user)
|
||||||
|
|
||||||
|
existing = await db_session.execute(
|
||||||
|
select(Role).where(Role.code == role_data.code)
|
||||||
|
)
|
||||||
|
if existing.scalar_one_or_none():
|
||||||
|
raise HTTPException(status_code=400, detail="角色编码已存在")
|
||||||
|
|
||||||
|
role = Role(
|
||||||
|
code=role_data.code,
|
||||||
|
name=role_data.name,
|
||||||
|
description=role_data.description
|
||||||
|
)
|
||||||
|
db_session.add(role)
|
||||||
|
await db_session.commit()
|
||||||
|
await db_session.refresh(role)
|
||||||
|
|
||||||
|
logger.info(f"管理员 {current_user.username} 创建了角色 {role.code}")
|
||||||
|
|
||||||
|
return RoleResponse(
|
||||||
|
id=role.id,
|
||||||
|
code=role.code,
|
||||||
|
name=role.name,
|
||||||
|
description=role.description,
|
||||||
|
is_system=role.is_system,
|
||||||
|
permissions=[]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/roles/{role_id}", response_model=RoleResponse)
|
||||||
|
async def update_role(
|
||||||
|
role_id: int,
|
||||||
|
role_data: RoleCreate,
|
||||||
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
):
|
||||||
|
check_admin(current_user)
|
||||||
|
|
||||||
|
result = await db_session.execute(select(Role).where(Role.id == role_id))
|
||||||
|
role = result.scalar_one_or_none()
|
||||||
|
if not role:
|
||||||
|
raise HTTPException(status_code=404, detail="角色不存在")
|
||||||
|
|
||||||
|
if role.is_system:
|
||||||
|
raise HTTPException(status_code=400, detail="系统角色不能修改")
|
||||||
|
|
||||||
|
role.name = role_data.name
|
||||||
|
role.description = role_data.description
|
||||||
|
await db_session.commit()
|
||||||
|
await db_session.refresh(role)
|
||||||
|
|
||||||
|
return RoleResponse(
|
||||||
|
id=role.id,
|
||||||
|
code=role.code,
|
||||||
|
name=role.name,
|
||||||
|
description=role.description,
|
||||||
|
is_system=role.is_system,
|
||||||
|
permissions=[p.code for p in role.permissions]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/roles/{role_id}")
|
||||||
|
async def delete_role(
|
||||||
|
role_id: int,
|
||||||
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
):
|
||||||
|
check_admin(current_user)
|
||||||
|
|
||||||
|
result = await db_session.execute(select(Role).where(Role.id == role_id))
|
||||||
|
role = result.scalar_one_or_none()
|
||||||
|
if not role:
|
||||||
|
raise HTTPException(status_code=404, detail="角色不存在")
|
||||||
|
|
||||||
|
if role.is_system:
|
||||||
|
raise HTTPException(status_code=400, detail="系统角色不能删除")
|
||||||
|
|
||||||
|
await db_session.delete(role)
|
||||||
|
await db_session.commit()
|
||||||
|
|
||||||
|
logger.info(f"管理员 {current_user.username} 删除了角色 {role.code}")
|
||||||
|
|
||||||
|
return {"message": "角色已删除"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/roles/{role_id}/permissions")
|
||||||
|
async def set_role_permissions(
|
||||||
|
role_id: int,
|
||||||
|
permission_ids: List[int],
|
||||||
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
):
|
||||||
|
check_admin(current_user)
|
||||||
|
|
||||||
|
result = await db_session.execute(select(Role).where(Role.id == role_id))
|
||||||
|
role = result.scalar_one_or_none()
|
||||||
|
if not role:
|
||||||
|
raise HTTPException(status_code=404, detail="角色不存在")
|
||||||
|
|
||||||
|
for rp in (await db_session.execute(select(RolePermission).where(RolePermission.role_id == role_id))).scalars().all():
|
||||||
|
await db_session.delete(rp)
|
||||||
|
|
||||||
|
for perm_id in permission_ids:
|
||||||
|
rp = RolePermission(role_id=role_id, permission_id=perm_id)
|
||||||
|
db_session.add(rp)
|
||||||
|
|
||||||
|
await db_session.commit()
|
||||||
|
|
||||||
|
logger.info(f"管理员 {current_user.username} 更新了角色 {role.code} 的权限")
|
||||||
|
|
||||||
|
return {"message": "权限已更新"}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/permissions", response_model=List[PermissionResponse])
|
||||||
|
async def list_permissions(
|
||||||
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
):
|
||||||
|
check_admin(current_user)
|
||||||
|
result = await db_session.execute(select(Permission))
|
||||||
|
permissions = result.scalars().all()
|
||||||
|
return [PermissionResponse.from_orm(p) for p in permissions]
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/permissions", response_model=PermissionResponse, status_code=201)
|
||||||
|
async def create_permission(
|
||||||
|
perm_data: PermissionCreate,
|
||||||
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
):
|
||||||
|
check_admin(current_user)
|
||||||
|
|
||||||
|
existing = await db_session.execute(
|
||||||
|
select(Permission).where(Permission.code == perm_data.code)
|
||||||
|
)
|
||||||
|
if existing.scalar_one_or_none():
|
||||||
|
raise HTTPException(status_code=400, detail="权限编码已存在")
|
||||||
|
|
||||||
|
permission = Permission(
|
||||||
|
code=perm_data.code,
|
||||||
|
name=perm_data.name,
|
||||||
|
module=perm_data.module,
|
||||||
|
description=perm_data.description
|
||||||
|
)
|
||||||
|
db_session.add(permission)
|
||||||
|
await db_session.commit()
|
||||||
|
await db_session.refresh(permission)
|
||||||
|
|
||||||
|
logger.info(f"管理员 {current_user.username} 创建了权限 {permission.code}")
|
||||||
|
|
||||||
|
return PermissionResponse.from_orm(permission)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/permissions/{permission_id}")
|
||||||
|
async def delete_permission(
|
||||||
|
permission_id: int,
|
||||||
|
db_session: AsyncSession = Depends(get_db_session),
|
||||||
|
current_user: User = Depends(get_current_active_user)
|
||||||
|
):
|
||||||
|
check_admin(current_user)
|
||||||
|
|
||||||
|
result = await db_session.execute(select(Permission).where(Permission.id == permission_id))
|
||||||
|
permission = result.scalar_one_or_none()
|
||||||
|
if not permission:
|
||||||
|
raise HTTPException(status_code=404, detail="权限不存在")
|
||||||
|
|
||||||
|
await db_session.delete(permission)
|
||||||
|
await db_session.commit()
|
||||||
|
|
||||||
|
logger.info(f"管理员 {current_user.username} 删除了权限 {permission.code}")
|
||||||
|
|
||||||
|
return {"message": "权限已删除"}
|
||||||
|
|||||||
+123
-34
@@ -1,42 +1,108 @@
|
|||||||
# database/init_db.py
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from database.database import db_manager
|
|
||||||
from utils.logger import get_logger
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from models.database import User
|
from database.database import db_manager
|
||||||
|
from models.database import User, Role, Permission, UserRole, RolePermission
|
||||||
from services.auth_service import get_password_hash
|
from services.auth_service import get_password_hash
|
||||||
|
from config.settings import settings
|
||||||
|
from utils.logger import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
DEFAULT_PERMISSIONS = [
|
||||||
|
{"code": "view_dashboard", "name": "查看仪表盘", "module": "dashboard"},
|
||||||
|
{"code": "view_moldinsight", "name": "使用模具分析", "module": "moldinsight"},
|
||||||
|
{"code": "upload_file", "name": "上传文件", "module": "moldinsight"},
|
||||||
|
{"code": "view_history", "name": "查看历史记录", "module": "moldinsight"},
|
||||||
|
{"code": "view_inventory", "name": "查看库存", "module": "inventory"},
|
||||||
|
{"code": "manage_inventory", "name": "管理库存", "module": "inventory"},
|
||||||
|
{"code": "view_products", "name": "查看产品", "module": "inventory"},
|
||||||
|
{"code": "manage_products", "name": "管理产品", "module": "inventory"},
|
||||||
|
{"code": "view_suppliers", "name": "查看供应商", "module": "inventory"},
|
||||||
|
{"code": "manage_suppliers", "name": "管理供应商", "module": "inventory"},
|
||||||
|
{"code": "view_customers", "name": "查看客户", "module": "inventory"},
|
||||||
|
{"code": "manage_customers", "name": "管理客户", "module": "inventory"},
|
||||||
|
{"code": "view_users", "name": "查看用户", "module": "admin"},
|
||||||
|
{"code": "manage_users", "name": "管理用户", "module": "admin"},
|
||||||
|
{"code": "manage_roles", "name": "管理角色", "module": "admin"},
|
||||||
|
]
|
||||||
|
|
||||||
async def create_default_admin():
|
DEFAULT_ROLES = [
|
||||||
"""创建默认管理员账户"""
|
{"code": "admin", "name": "管理员", "description": "系统管理员,拥有所有权限", "is_system": True, "permissions": ["view_dashboard", "view_moldinsight", "upload_file", "view_history", "view_inventory", "manage_inventory", "view_products", "manage_products", "view_suppliers", "manage_suppliers", "view_customers", "manage_customers", "view_users", "manage_users", "manage_roles"]},
|
||||||
try:
|
{"code": "user", "name": "普通用户", "description": "普通用户,可使用模具分析和查看库存", "is_system": False, "permissions": ["view_dashboard", "view_moldinsight", "upload_file", "view_history", "view_inventory", "view_products", "view_suppliers", "view_customers"]},
|
||||||
async with db_manager.session() as session:
|
{"code": "viewer", "name": "只读用户", "description": "只读用户,只能查看数据", "is_system": False, "permissions": ["view_dashboard", "view_moldinsight", "view_history", "view_inventory", "view_products", "view_suppliers", "view_customers"]},
|
||||||
result = await session.execute(
|
]
|
||||||
select(User).where(User.username == "admin")
|
|
||||||
)
|
|
||||||
existing_admin = result.scalar_one_or_none()
|
async def init_permissions(session):
|
||||||
|
"""初始化权限"""
|
||||||
if existing_admin:
|
result = await session.execute(select(Permission))
|
||||||
logger.info("管理员账户已存在")
|
existing_perms = result.scalars().all()
|
||||||
return
|
|
||||||
|
if existing_perms:
|
||||||
admin = User(
|
logger.info("权限已初始化")
|
||||||
username="admin",
|
return
|
||||||
email="admin@gemold.com",
|
|
||||||
hashed_password=get_password_hash("admin123"),
|
perm_map = {}
|
||||||
full_name="系统管理员",
|
for perm_data in DEFAULT_PERMISSIONS:
|
||||||
is_active=True,
|
perm = Permission(**perm_data)
|
||||||
is_superuser=True
|
session.add(perm)
|
||||||
)
|
await session.flush()
|
||||||
|
perm_map[perm.code] = perm.id
|
||||||
session.add(admin)
|
|
||||||
await session.commit()
|
logger.info(f"创建了 {len(DEFAULT_PERMISSIONS)} 个权限")
|
||||||
logger.info("默认管理员账户创建成功 (用户名: admin, 密码: admin123)")
|
return perm_map
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"创建默认管理员失败: {e}")
|
async def init_roles(session, perm_map):
|
||||||
|
"""初始化角色"""
|
||||||
|
result = await session.execute(select(Role))
|
||||||
|
existing_roles = result.scalars().all()
|
||||||
|
|
||||||
|
if existing_roles:
|
||||||
|
logger.info("角色已初始化")
|
||||||
|
return
|
||||||
|
|
||||||
|
for role_data in DEFAULT_ROLES:
|
||||||
|
perm_ids = [perm_map[code] for code in role_data.pop("permissions")]
|
||||||
|
role = Role(**role_data)
|
||||||
|
session.add(role)
|
||||||
|
await session.flush()
|
||||||
|
|
||||||
|
for perm_id in perm_ids:
|
||||||
|
rp = RolePermission(role_id=role.id, permission_id=perm_id)
|
||||||
|
session.add(rp)
|
||||||
|
|
||||||
|
logger.info(f"创建了 {len(DEFAULT_ROLES)} 个角色")
|
||||||
|
|
||||||
|
|
||||||
|
async def create_admin_user(session):
|
||||||
|
"""创建默认管理员"""
|
||||||
|
result = await session.execute(select(User).where(User.username == settings.ADMIN_USERNAME))
|
||||||
|
existing_admin = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if existing_admin:
|
||||||
|
logger.info("管理员账户已存在")
|
||||||
|
return
|
||||||
|
|
||||||
|
admin = User(
|
||||||
|
username=settings.ADMIN_USERNAME,
|
||||||
|
email=settings.ADMIN_EMAIL,
|
||||||
|
hashed_password=get_password_hash(settings.ADMIN_PASSWORD),
|
||||||
|
full_name=settings.ADMIN_FULL_NAME,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
session.add(admin)
|
||||||
|
await session.flush()
|
||||||
|
|
||||||
|
result = await session.execute(select(Role).where(Role.code == "admin"))
|
||||||
|
admin_role = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if admin_role:
|
||||||
|
user_role = UserRole(user_id=admin.id, role_id=admin_role.id)
|
||||||
|
session.add(user_role)
|
||||||
|
|
||||||
|
await session.commit()
|
||||||
|
logger.info(f"创建了管理员账户: {settings.ADMIN_USERNAME}")
|
||||||
|
|
||||||
|
|
||||||
async def init_database():
|
async def init_database():
|
||||||
@@ -44,13 +110,36 @@ async def init_database():
|
|||||||
try:
|
try:
|
||||||
await db_manager.connect()
|
await db_manager.connect()
|
||||||
await db_manager.create_tables()
|
await db_manager.create_tables()
|
||||||
await create_default_admin()
|
|
||||||
|
async with db_manager.session() as session:
|
||||||
|
perm_map = await init_permissions(session)
|
||||||
|
await init_roles(session, perm_map)
|
||||||
|
await create_admin_user(session)
|
||||||
|
|
||||||
logger.info("数据库初始化完成")
|
logger.info("数据库初始化完成")
|
||||||
|
print("=" * 60)
|
||||||
|
print("数据库初始化成功!")
|
||||||
|
print("=" * 60)
|
||||||
|
print(f"管理员用户名: {settings.ADMIN_USERNAME}")
|
||||||
|
print(f"管理员密码: {settings.ADMIN_PASSWORD}")
|
||||||
|
print(f"管理员邮箱: {settings.ADMIN_EMAIL}")
|
||||||
|
print("=" * 60)
|
||||||
|
print("可以在 .env 文件中修改管理员配置:")
|
||||||
|
print(" ADMIN_USERNAME")
|
||||||
|
print(" ADMIN_PASSWORD")
|
||||||
|
print(" ADMIN_EMAIL")
|
||||||
|
print(" ADMIN_FULL_NAME")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"数据库初始化失败: {e}")
|
logger.error(f"数据库初始化失败: {e}")
|
||||||
|
print(f"数据库初始化失败: {e}")
|
||||||
return False
|
return False
|
||||||
|
finally:
|
||||||
|
await db_manager.disconnect()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(init_database())
|
asyncio.run(init_database())
|
||||||
|
|||||||
+90
-3
@@ -17,15 +17,102 @@ class User(Base):
|
|||||||
hashed_password = Column(String(255), nullable=False)
|
hashed_password = Column(String(255), nullable=False)
|
||||||
full_name = Column(String(100))
|
full_name = Column(String(100))
|
||||||
is_active = Column(Boolean, default=True)
|
is_active = Column(Boolean, default=True)
|
||||||
is_superuser = Column(Boolean, default=False)
|
|
||||||
created_at = Column(DateTime, default=func.now())
|
created_at = Column(DateTime, default=func.now())
|
||||||
last_login = Column(DateTime, nullable=True)
|
last_login = Column(DateTime, nullable=True)
|
||||||
|
|
||||||
# 关联关系
|
|
||||||
stp_files = relationship("STPFile", back_populates="user")
|
stp_files = relationship("STPFile", back_populates="user")
|
||||||
|
user_roles = relationship("UserRole", back_populates="user", cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def roles(self):
|
||||||
|
return [ur.role for ur in self.user_roles]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_superuser(self):
|
||||||
|
return any(r.code == 'admin' for r in self.roles)
|
||||||
|
|
||||||
|
def has_permission(self, permission_code: str) -> bool:
|
||||||
|
if self.is_superuser:
|
||||||
|
return True
|
||||||
|
for role in self.roles:
|
||||||
|
for perm in role.permissions:
|
||||||
|
if perm.code == permission_code:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"<User(id={self.id}, username='{self.username}', email='{self.email}')>"
|
return f"<User(id={self.id}, username='{self.username}')>"
|
||||||
|
|
||||||
|
|
||||||
|
class Role(Base):
|
||||||
|
"""角色表"""
|
||||||
|
__tablename__ = "roles"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
code = Column(String(50), unique=True, index=True, nullable=False)
|
||||||
|
name = Column(String(100), nullable=False)
|
||||||
|
description = Column(Text, nullable=True)
|
||||||
|
is_system = Column(Boolean, default=False)
|
||||||
|
created_at = Column(DateTime, default=func.now())
|
||||||
|
|
||||||
|
user_roles = relationship("UserRole", back_populates="role", cascade="all, delete-orphan")
|
||||||
|
role_permissions = relationship("RolePermission", back_populates="role", cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def permissions(self):
|
||||||
|
return [rp.permission for rp in self.role_permissions]
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Role(code='{self.code}', name='{self.name}')>"
|
||||||
|
|
||||||
|
|
||||||
|
class Permission(Base):
|
||||||
|
"""权限表"""
|
||||||
|
__tablename__ = "permissions"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
code = Column(String(100), unique=True, index=True, nullable=False)
|
||||||
|
name = Column(String(100), nullable=False)
|
||||||
|
module = Column(String(50), nullable=True)
|
||||||
|
description = Column(Text, nullable=True)
|
||||||
|
created_at = Column(DateTime, default=func.now())
|
||||||
|
|
||||||
|
role_permissions = relationship("RolePermission", back_populates="permission", cascade="all, delete-orphan")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<Permission(code='{self.code}', name='{self.name}')>"
|
||||||
|
|
||||||
|
|
||||||
|
class UserRole(Base):
|
||||||
|
"""用户角色关联表"""
|
||||||
|
__tablename__ = "user_roles"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||||
|
role_id = Column(Integer, ForeignKey("roles.id"), nullable=False, index=True)
|
||||||
|
created_at = Column(DateTime, default=func.now())
|
||||||
|
|
||||||
|
user = relationship("User", back_populates="user_roles")
|
||||||
|
role = relationship("Role", back_populates="user_roles")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<UserRole(user_id={self.user_id}, role_id={self.role_id})>"
|
||||||
|
|
||||||
|
|
||||||
|
class RolePermission(Base):
|
||||||
|
"""角色权限关联表"""
|
||||||
|
__tablename__ = "role_permissions"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
role_id = Column(Integer, ForeignKey("roles.id"), nullable=False, index=True)
|
||||||
|
permission_id = Column(Integer, ForeignKey("permissions.id"), nullable=False, index=True)
|
||||||
|
created_at = Column(DateTime, default=func.now())
|
||||||
|
|
||||||
|
role = relationship("Role", back_populates="role_permissions")
|
||||||
|
permission = relationship("Permission", back_populates="role_permissions")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<RolePermission(role_id={self.role_id}, permission_id={self.permission_id})>"
|
||||||
|
|
||||||
class STPFile(Base):
|
class STPFile(Base):
|
||||||
"""STP源文件元数据表"""
|
"""STP源文件元数据表"""
|
||||||
|
|||||||
@@ -870,6 +870,141 @@ a:hover {
|
|||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ================================
|
||||||
|
模态框
|
||||||
|
================================ */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 1000;
|
||||||
|
animation: fadeIn 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
width: 90%;
|
||||||
|
max-width: 500px;
|
||||||
|
max-height: 90vh;
|
||||||
|
overflow: auto;
|
||||||
|
animation: slideUp 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--space-6);
|
||||||
|
border-bottom: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h2 {
|
||||||
|
font-size: var(--text-xl);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close:hover {
|
||||||
|
background: var(--gray-100);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: var(--space-6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--space-3);
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: var(--space-6);
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================
|
||||||
|
复选框组
|
||||||
|
================================ */
|
||||||
|
.checkbox-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-2);
|
||||||
|
padding: var(--space-2) var(--space-3);
|
||||||
|
background: var(--gray-100);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label:hover {
|
||||||
|
background: var(--gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label input[type="checkbox"] {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================
|
||||||
|
按钮变体
|
||||||
|
================================ */
|
||||||
|
.btn-error {
|
||||||
|
background: var(--error);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: var(--space-2) var(--space-4);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-error:hover {
|
||||||
|
background: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: var(--gray-200);
|
||||||
|
color: var(--text-primary);
|
||||||
|
border: none;
|
||||||
|
padding: var(--space-2) var(--space-4);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background: var(--gray-300);
|
||||||
|
}
|
||||||
|
|
||||||
/* ================================
|
/* ================================
|
||||||
进度条
|
进度条
|
||||||
================================ */
|
================================ */
|
||||||
@@ -1489,6 +1624,11 @@ a:hover {
|
|||||||
color: var(--primary-700);
|
color: var(--primary-700);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.auth-tip {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
}
|
||||||
|
|
||||||
.page-container {
|
.page-container {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|||||||
+174
-107
@@ -271,11 +271,8 @@ const LoginView = {
|
|||||||
setup() {
|
setup() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
isLogin: true,
|
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
email: '',
|
|
||||||
full_name: '',
|
|
||||||
loading: false,
|
loading: false,
|
||||||
error: ''
|
error: ''
|
||||||
});
|
});
|
||||||
@@ -296,51 +293,24 @@ const LoginView = {
|
|||||||
state.error = '';
|
state.error = '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (state.isLogin) {
|
const res = await fetch('/api/auth/login/json', {
|
||||||
const res = await fetch('/api/auth/login/json', {
|
method: 'POST',
|
||||||
method: 'POST',
|
headers: { 'Content-Type': 'application/json' },
|
||||||
headers: { 'Content-Type': 'application/json' },
|
body: JSON.stringify({
|
||||||
body: JSON.stringify({
|
username: state.username,
|
||||||
username: state.username,
|
password: state.password
|
||||||
password: state.password
|
})
|
||||||
})
|
});
|
||||||
});
|
|
||||||
|
if (!res.ok) {
|
||||||
if (!res.ok) {
|
const error = await res.json();
|
||||||
const error = await res.json();
|
throw new Error(error.detail || '登录失败');
|
||||||
throw new Error(error.detail || '登录失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await res.json();
|
|
||||||
saveAuth(data.access_token, data.user);
|
|
||||||
addNotification('登录成功', 'success');
|
|
||||||
router.push('/');
|
|
||||||
} else {
|
|
||||||
if (!state.email) {
|
|
||||||
state.error = '请填写邮箱';
|
|
||||||
state.loading = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const res = await fetch('/api/auth/register', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
username: state.username,
|
|
||||||
password: state.password,
|
|
||||||
email: state.email,
|
|
||||||
full_name: state.full_name || null
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!res.ok) {
|
|
||||||
const error = await res.json();
|
|
||||||
throw new Error(error.detail || '注册失败');
|
|
||||||
}
|
|
||||||
|
|
||||||
addNotification('注册成功,请登录', 'success');
|
|
||||||
state.isLogin = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
saveAuth(data.access_token, data.user);
|
||||||
|
addNotification('登录成功', 'success');
|
||||||
|
router.push('/');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
state.error = e.message;
|
state.error = e.message;
|
||||||
addNotification(e.message, 'error');
|
addNotification(e.message, 'error');
|
||||||
@@ -356,8 +326,8 @@ const LoginView = {
|
|||||||
<div class="auth-card">
|
<div class="auth-card">
|
||||||
<div class="auth-header">
|
<div class="auth-header">
|
||||||
<div class="auth-logo">◆</div>
|
<div class="auth-logo">◆</div>
|
||||||
<h1>{{ state.isLogin ? '登录' : '注册' }}</h1>
|
<h1>登录</h1>
|
||||||
<p>{{ state.isLogin ? '登录到 Gemold 系统' : '创建新账户' }}</p>
|
<p>登录到 Gemold 系统</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form @submit.prevent="handleSubmit" class="auth-form">
|
<form @submit.prevent="handleSubmit" class="auth-form">
|
||||||
@@ -371,47 +341,25 @@ const LoginView = {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group" v-if="!state.isLogin">
|
|
||||||
<label>邮箱</label>
|
|
||||||
<input
|
|
||||||
v-model="state.email"
|
|
||||||
type="email"
|
|
||||||
placeholder="请输入邮箱"
|
|
||||||
autocomplete="email"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group" v-if="!state.isLogin">
|
|
||||||
<label>姓名</label>
|
|
||||||
<input
|
|
||||||
v-model="state.full_name"
|
|
||||||
type="text"
|
|
||||||
placeholder="请输入姓名(可选)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>密码</label>
|
<label>密码</label>
|
||||||
<input
|
<input
|
||||||
v-model="state.password"
|
v-model="state.password"
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="请输入密码"
|
placeholder="请输入密码"
|
||||||
:autocomplete="state.isLogin ? 'current-password' : 'new-password'"
|
autocomplete="current-password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="state.error" class="error-message">{{ state.error }}</div>
|
<div v-if="state.error" class="error-message">{{ state.error }}</div>
|
||||||
|
|
||||||
<button type="submit" class="btn-primary btn-full" :disabled="state.loading">
|
<button type="submit" class="btn-primary btn-full" :disabled="state.loading">
|
||||||
{{ state.loading ? '处理中...' : (state.isLogin ? '登录' : '注册') }}
|
{{ state.loading ? '登录中...' : '登录' }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="auth-footer">
|
<div class="auth-footer">
|
||||||
<span>{{ state.isLogin ? '没有账户?' : '已有账户?' }}</span>
|
<p class="auth-tip">如需开通账号,请联系管理员</p>
|
||||||
<a @click="state.isLogin = !state.isLogin; state.error = ''">
|
|
||||||
{{ state.isLogin ? '立即注册' : '立即登录' }}
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -566,7 +514,17 @@ const UsersView = {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
users: [],
|
users: [],
|
||||||
loading: true
|
roles: [],
|
||||||
|
loading: true,
|
||||||
|
showUserModal: false,
|
||||||
|
editingUser: null,
|
||||||
|
userForm: {
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
full_name: '',
|
||||||
|
role_ids: []
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const loadUsers = async () => {
|
const loadUsers = async () => {
|
||||||
@@ -579,43 +537,123 @@ const UsersView = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleActive = async (user) => {
|
const loadRoles = async () => {
|
||||||
try {
|
try {
|
||||||
const updated = await apiRequest(`/api/auth/users/${user.id}/toggle-active`, { method: 'PUT' });
|
state.roles = await apiRequest('/api/auth/roles');
|
||||||
const index = state.users.findIndex(u => u.id === user.id);
|
|
||||||
if (index > -1) state.users[index] = updated;
|
|
||||||
addNotification(`用户 ${user.username} 已${updated.is_active ? '启用' : '禁用'}`, 'success');
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleApiError(e, '切换用户状态');
|
handleApiError(e, '加载角色列表');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleAdmin = async (user) => {
|
const openUserModal = (user = null) => {
|
||||||
|
state.editingUser = user;
|
||||||
|
if (user) {
|
||||||
|
state.userForm = {
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
password: '',
|
||||||
|
full_name: user.full_name || '',
|
||||||
|
role_ids: user.roles.map(r => {
|
||||||
|
const role = state.roles.find(role => role.code === r);
|
||||||
|
return role ? role.id : null;
|
||||||
|
}).filter(id => id !== null)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
state.userForm = {
|
||||||
|
username: '',
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
full_name: '',
|
||||||
|
role_ids: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
state.showUserModal = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveUser = async () => {
|
||||||
|
if (!state.userForm.username || !state.userForm.email) {
|
||||||
|
addNotification('请填写用户名和邮箱', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!state.editingUser && !state.userForm.password) {
|
||||||
|
addNotification('请填写密码', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const updated = await apiRequest(`/api/auth/users/${user.id}/toggle-admin`, { method: 'PUT' });
|
if (state.editingUser) {
|
||||||
const index = state.users.findIndex(u => u.id === user.id);
|
await apiRequest(`/api/auth/users/${state.editingUser.id}`, {
|
||||||
if (index > -1) state.users[index] = updated;
|
method: 'PUT',
|
||||||
addNotification(`用户 ${user.username} ${updated.is_superuser ? '已设为管理员' : '已取消管理员'}`, 'success');
|
body: JSON.stringify({
|
||||||
|
email: state.userForm.email,
|
||||||
|
full_name: state.userForm.full_name || null,
|
||||||
|
role_ids: state.userForm.role_ids
|
||||||
|
})
|
||||||
|
});
|
||||||
|
addNotification('用户更新成功', 'success');
|
||||||
|
} else {
|
||||||
|
await apiRequest('/api/auth/users', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(state.userForm)
|
||||||
|
});
|
||||||
|
addNotification('用户创建成功', 'success');
|
||||||
|
}
|
||||||
|
state.showUserModal = false;
|
||||||
|
loadUsers();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleApiError(e, '切换管理员权限');
|
handleApiError(e, '保存用户');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
const deleteUser = async (user) => {
|
||||||
|
if (!confirm(`确定要删除用户 ${user.username} 吗?`)) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apiRequest(`/api/auth/users/${user.id}`, { method: 'DELETE' });
|
||||||
|
addNotification('用户已删除', 'success');
|
||||||
|
loadUsers();
|
||||||
|
} catch (e) {
|
||||||
|
handleApiError(e, '删除用户');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetPassword = async (user) => {
|
||||||
|
const newPassword = prompt(`请输入 ${user.username} 的新密码:`);
|
||||||
|
if (!newPassword || newPassword.length < 6) {
|
||||||
|
addNotification('密码长度至少6位', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await apiRequest(`/api/auth/users/${user.id}/reset-password`, {
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify(newPassword)
|
||||||
|
});
|
||||||
|
addNotification('密码已重置', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
handleApiError(e, '重置密码');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
if (!appState.user?.is_superuser) {
|
if (!appState.user?.is_superuser) {
|
||||||
router.push('/');
|
router.push('/');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
await loadRoles();
|
||||||
loadUsers();
|
loadUsers();
|
||||||
});
|
});
|
||||||
|
|
||||||
return { state, appState, toggleActive, toggleAdmin, formatDateTime };
|
return { state, appState, openUserModal, saveUser, deleteUser, resetPassword, formatDateTime };
|
||||||
},
|
},
|
||||||
template: `
|
template: `
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<div class="page-header">
|
<div class="page-header">
|
||||||
<h1>用户管理</h1>
|
<div>
|
||||||
<p>管理系统用户和权限</p>
|
<h1>用户管理</h1>
|
||||||
|
<p>管理系统用户和权限</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn-primary" @click="openUserModal()">+ 添加用户</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="state.loading" class="loading-state">
|
<div v-if="state.loading" class="loading-state">
|
||||||
@@ -647,33 +685,62 @@ const UsersView = {
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span :class="['badge', user.is_superuser ? 'badge-primary' : 'badge-neutral']">
|
<span v-for="role in user.roles" :key="role" class="badge badge-info" style="margin-right: 4px;">
|
||||||
{{ user.is_superuser ? '管理员' : '普通用户' }}
|
{{ role }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ formatDateTime(user.created_at) }}</td>
|
<td>{{ formatDateTime(user.created_at) }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button
|
<button class="btn-sm btn-primary" @click="openUserModal(user)">编辑</button>
|
||||||
v-if="user.id !== appState.user?.id"
|
<button class="btn-sm btn-warning" @click="resetPassword(user)">重置密码</button>
|
||||||
:class="['btn-sm', user.is_active ? 'btn-warning' : 'btn-success']"
|
<button v-if="user.id !== appState.user?.id" class="btn-sm btn-error" @click="deleteUser(user)">删除</button>
|
||||||
@click="toggleActive(user)"
|
|
||||||
>
|
|
||||||
{{ user.is_active ? '禁用' : '启用' }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="user.id !== appState.user?.id"
|
|
||||||
:class="['btn-sm', user.is_superuser ? 'btn-warning' : 'btn-primary']"
|
|
||||||
@click="toggleAdmin(user)"
|
|
||||||
>
|
|
||||||
{{ user.is_superuser ? '取消管理员' : '设为管理员' }}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="state.showUserModal" class="modal-overlay" @click.self="state.showUserModal = false">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>{{ state.editingUser ? '编辑用户' : '添加用户' }}</h2>
|
||||||
|
<button class="modal-close" @click="state.showUserModal = false">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>用户名</label>
|
||||||
|
<input v-model="state.userForm.username" type="text" :disabled="!!state.editingUser" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>邮箱</label>
|
||||||
|
<input v-model="state.userForm.email" type="email" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group" v-if="!state.editingUser">
|
||||||
|
<label>密码</label>
|
||||||
|
<input v-model="state.userForm.password" type="password" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>姓名</label>
|
||||||
|
<input v-model="state.userForm.full_name" type="text" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>角色</label>
|
||||||
|
<div class="checkbox-group">
|
||||||
|
<label v-for="role in state.roles" :key="role.id" class="checkbox-label">
|
||||||
|
<input type="checkbox" :value="role.id" v-model="state.userForm.role_ids" />
|
||||||
|
{{ role.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn-secondary" @click="state.showUserModal = false">取消</button>
|
||||||
|
<button class="btn-primary" @click="saveUser">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user