feat: v0.12.5 admin 保护 + 管理员角色切换 + 团队详情加宽
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m37s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m37s
①admin 账号不可被禁用(包括自己,防误操作) ②admin 密码不可被其他超管重置(admin 自己可以) ③超管可在团队详情点击角色切换成员/管理员 ④团队详情弹窗宽度 1080→1280px Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
969283690f
commit
a026c04310
@ -22,6 +22,7 @@ urlpatterns = [
|
||||
path('admin/teams/<int:team_id>/topup', views.admin_team_topup_view, name='admin_team_topup'),
|
||||
path('admin/teams/<int:team_id>/set-pool', views.admin_team_set_pool_view, name='admin_team_set_pool'),
|
||||
path('admin/teams/<int:team_id>/admin', views.admin_team_create_admin_view, name='admin_team_create_admin'),
|
||||
path('admin/teams/<int:team_id>/members/<int:member_id>/role', views.admin_team_member_role_view, name='admin_team_member_role'),
|
||||
|
||||
# ── Super Admin: User management ──
|
||||
path('admin/users', views.admin_users_list_view, name='admin_users_list'),
|
||||
|
||||
@ -1019,6 +1019,36 @@ def admin_team_detail_view(request, team_id):
|
||||
})
|
||||
|
||||
|
||||
@api_view(['PATCH'])
|
||||
@permission_classes([IsSuperAdmin])
|
||||
def admin_team_member_role_view(request, team_id, member_id):
|
||||
"""PATCH /api/v1/admin/teams/<id>/members/<id>/role — Toggle team admin role."""
|
||||
try:
|
||||
team = Team.objects.get(id=team_id)
|
||||
except Team.DoesNotExist:
|
||||
return Response({'error': '团队不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||
try:
|
||||
member = team.members.get(id=member_id)
|
||||
except User.DoesNotExist:
|
||||
return Response({'error': '成员不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
is_admin = request.data.get('is_team_admin')
|
||||
if is_admin is None:
|
||||
return Response({'error': '请提供 is_team_admin 参数'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
before = {'is_team_admin': member.is_team_admin}
|
||||
member.is_team_admin = bool(is_admin)
|
||||
member.save(update_fields=['is_team_admin'])
|
||||
log_admin_action(request, 'team_update', 'user', target_id=member.id, target_name=member.username,
|
||||
before=before, after={'is_team_admin': member.is_team_admin})
|
||||
|
||||
return Response({
|
||||
'user_id': member.id,
|
||||
'username': member.username,
|
||||
'is_team_admin': member.is_team_admin,
|
||||
})
|
||||
|
||||
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsSuperAdmin])
|
||||
def admin_team_topup_view(request, team_id):
|
||||
@ -1419,6 +1449,10 @@ def admin_user_status_view(request, user_id):
|
||||
except User.DoesNotExist:
|
||||
return Response({'error': '用户不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 保护 admin 账号不被任何人禁用(包括自己,防误操作)
|
||||
if user.username == 'admin':
|
||||
return Response({'error': '不能禁用超级管理员账号'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
serializer = UserStatusSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
|
||||
@ -1451,6 +1485,10 @@ def admin_reset_password_view(request, user_id):
|
||||
except User.DoesNotExist:
|
||||
return Response({'error': '用户不存在'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 保护 admin 账号密码不被其他超管重置
|
||||
if user.username == 'admin' and request.user.username != 'admin':
|
||||
return Response({'error': '不能重置超级管理员的密码'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
new_password = request.data.get('new_password', '')
|
||||
if len(new_password) < 8:
|
||||
return Response({'error': '密码至少8位'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@ -190,6 +190,9 @@ export const adminApi = {
|
||||
createTeamAdmin: (teamId: number, data: { username: string; email: string; password: string }) =>
|
||||
api.post(`/admin/teams/${teamId}/admin`, data),
|
||||
|
||||
setMemberRole: (teamId: number, memberId: number, isTeamAdmin: boolean) =>
|
||||
api.patch(`/admin/teams/${teamId}/members/${memberId}/role`, { is_team_admin: isTeamAdmin }),
|
||||
|
||||
// User management
|
||||
createUser: (data: {
|
||||
username: string;
|
||||
|
||||
@ -90,7 +90,7 @@
|
||||
-webkit-backdrop-filter: blur(24px) saturate(180%);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 16px;
|
||||
width: 1080px;
|
||||
width: 1280px;
|
||||
max-width: 96vw;
|
||||
min-height: 70vh;
|
||||
max-height: 90vh;
|
||||
|
||||
@ -823,8 +823,22 @@ export function TeamsPage() {
|
||||
<td>{m.email}</td>
|
||||
<td>
|
||||
{m.is_team_admin ? (
|
||||
<span className={styles.adminBadge}>管理员</span>
|
||||
) : '成员'}
|
||||
<span className={styles.adminBadge} style={{ cursor: 'pointer' }} title="点击取消管理员" onClick={async () => {
|
||||
try {
|
||||
await adminApi.setMemberRole(detailTeam!.id, m.id, false);
|
||||
showToast('已取消管理员');
|
||||
const { data: refreshed } = await adminApi.getTeamDetail(detailTeam!.id); setDetailTeam(refreshed);
|
||||
} catch { showToast('操作失败'); }
|
||||
}}>管理员</span>
|
||||
) : (
|
||||
<span style={{ cursor: 'pointer', color: 'var(--color-text-secondary)' }} title="点击设为管理员" onClick={async () => {
|
||||
try {
|
||||
await adminApi.setMemberRole(detailTeam!.id, m.id, true);
|
||||
showToast('已设为管理员');
|
||||
const { data: refreshed } = await adminApi.getTeamDetail(detailTeam!.id); setDetailTeam(refreshed);
|
||||
} catch { showToast('操作失败'); }
|
||||
}}>成员</span>
|
||||
)}
|
||||
</td>
|
||||
<td>
|
||||
<span className={`${styles.statusBadge} ${m.is_active ? styles.active : styles.disabled}`}>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user