Files
ctms/backend/app/api/v1/permission_templates.py
T
Cheng Zhou f8a959b801 权限管理界面重构:角色概览、生效管理与编辑权限整合
【界面优化】
- 移除权限模板列表和角色概览卡片上的"系统预设"标签,视觉上减少噪音
- 将 PermissionTemplateSelector 改为纯展示组件,重命名为"角色权限概览"
- 移除"应用此模板"按钮及预览面板,卡片仅展示各角色权限统计
- 角色描述改为鼠标悬浮 tooltip 显示,节省卡片空间
- 未生效角色以虚线边框 + 降低透明度区分,悬浮提示状态说明

【生效管理】
- Study 模型新增 active_roles JSON 字段,记录项目已生效角色列表
- 新增 /studies/{study_id}/active-roles GET/PUT 接口
- 数据库迁移 20260518_01:studies 表添加 active_roles 列
- 成员管理的项目角色下拉列表仅显示已生效角色,未生效角色不可分配

【模板管理抽屉重构】
- 抽屉改为三标签页:模板列表 / 生效管理 / 编辑权限
- 生效管理:开关控制各角色生效状态,保存后同步后端
- 编辑权限:左右分栏设计,左侧角色列表点击选中,右侧实时展示权限勾选
- 原独立的角色权限编辑抽屉合并至此,角色概览卡片"编辑权限"直接跳转对应标签页

【操作类型标签统一】
- 系统级权限和编辑权限界面的操作类型标签统一使用细分类型
- 操作类型从粗粒度 read/write 细化为 read/create/update/delete/export
- 颜色规范:读取灰色、创建绿色、更新黄色、删除红色、导出无色
- 权限模板管理接口限制为 ADMIN 角色,修复原先权限过宽的问题

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-19 08:48:07 +08:00

128 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""权限模板管理API"""
from __future__ import annotations
import uuid
from typing import Annotated, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import Response
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_current_user, get_db_session, require_roles, require_study_roles
from app.models.permission_template import TemplateType
from app.models.user import UserRole
from app.schemas.permission_template import (
ApplyTemplateRequest,
ApplyTemplateResponse,
PermissionTemplateCreate,
PermissionTemplateRead,
PermissionTemplateUpdate,
)
from app.services.permission_template_service import PermissionTemplateService
# 模板管理路由(不需要 study_id)
router = APIRouter(prefix="/permission-templates", tags=["permission-templates"])
# 项目级模板操作路由(需要 study_id,挂载到 /studies/{study_id}
study_router = APIRouter(prefix="/permission-templates", tags=["permission-templates"])
@router.get("", response_model=list[PermissionTemplateRead])
async def list_templates(
template_type: Optional[str] = None,
category: Optional[str] = None,
skip: int = 0,
limit: int = 100,
db: Annotated[AsyncSession, Depends(get_db_session)] = None,
) -> list[PermissionTemplateRead]:
"""列表权限模板"""
t_type = TemplateType(template_type) if template_type else None
templates = await PermissionTemplateService.list_templates(
db, template_type=t_type, category=category, skip=skip, limit=limit
)
return [PermissionTemplateRead.model_validate(t) for t in templates]
@router.post(
"",
response_model=PermissionTemplateRead,
status_code=status.HTTP_201_CREATED,
)
async def create_template(
payload: PermissionTemplateCreate,
db: Annotated[AsyncSession, Depends(get_db_session)] = None,
current_user=Depends(require_roles([UserRole.ADMIN.value])),
) -> PermissionTemplateRead:
"""创建权限模板"""
try:
template = await PermissionTemplateService.create_template(db, payload, current_user.id)
return PermissionTemplateRead.model_validate(template)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
@router.get("/{template_id}", response_model=PermissionTemplateRead)
async def get_template(
template_id: uuid.UUID,
db: Annotated[AsyncSession, Depends(get_db_session)] = None,
) -> PermissionTemplateRead:
"""获取权限模板"""
template = await PermissionTemplateService.get_template(db, template_id)
if not template:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="模板不存在")
return PermissionTemplateRead.model_validate(template)
@router.put("/{template_id}", response_model=PermissionTemplateRead)
async def update_template(
template_id: uuid.UUID,
payload: PermissionTemplateUpdate,
db: Annotated[AsyncSession, Depends(get_db_session)] = None,
current_user=Depends(require_roles([UserRole.ADMIN.value])),
) -> PermissionTemplateRead:
"""更新权限模板"""
try:
template = await PermissionTemplateService.update_template(db, template_id, payload)
return PermissionTemplateRead.model_validate(template)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
@router.delete("/{template_id}", status_code=status.HTTP_204_NO_CONTENT, response_class=Response)
async def delete_template(
template_id: uuid.UUID,
db: Annotated[AsyncSession, Depends(get_db_session)] = None,
current_user=Depends(require_roles([UserRole.ADMIN.value])),
):
"""删除权限模板"""
try:
await PermissionTemplateService.delete_template(db, template_id)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
@study_router.post(
"/{template_id}/apply",
response_model=ApplyTemplateResponse,
)
async def apply_template(
study_id: uuid.UUID,
template_id: uuid.UUID,
payload: ApplyTemplateRequest,
db: Annotated[AsyncSession, Depends(get_db_session)] = None,
_=Depends(require_study_roles(["PM"])),
) -> ApplyTemplateResponse:
"""应用权限模板到项目"""
try:
result = await PermissionTemplateService.apply_template(
db,
study_id,
payload.template_id,
roles=payload.roles,
override=payload.override,
)
return ApplyTemplateResponse(**result)
except ValueError as e:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))