This commit is contained in:
Professional 2025-05-26 15:52:26 +07:00
parent b8369c53c3
commit 4ef4c5f428
4 changed files with 262 additions and 2 deletions

View File

@ -113,6 +113,7 @@ const getUserDetails = async (req, res) => {
const toggleUserActive = async (req, res) => { const toggleUserActive = async (req, res) => {
try { try {
const userId = req.params.id; const userId = req.params.id;
const { reason } = req.body; // Добавляем возможность указать причину блокировки
const user = await User.findById(userId); const user = await User.findById(userId);
if (!user) { if (!user) {
@ -124,9 +125,76 @@ const toggleUserActive = async (req, res) => {
return res.status(403).json({ message: 'Невозможно заблокировать администратора' }); return res.status(403).json({ message: 'Невозможно заблокировать администратора' });
} }
// Получаем доступ к io для отправки уведомлений
const io = req.app.get('io');
// Изменяем статус активности на противоположный // Изменяем статус активности на противоположный
const wasActive = user.isActive;
user.isActive = !user.isActive; user.isActive = !user.isActive;
await user.save(); await user.save();
// Если пользователь был активным и теперь блокируется
if (wasActive && !user.isActive) {
// Находим активное соединение пользователя
const activeUsers = Array.from(io.sockets.sockets).map(socket => socket[1]);
const userSockets = activeUsers.filter(socket => {
const userData = socket.handshake.query;
return userData && userData.userId === userId;
});
// Получаем информацию о пользователе из массива activeUsers
// (этот массив хранится в server.js)
const activeUsersArray = Array.from(io.sockets.adapter.rooms).filter(room => {
return room[1].has(room[0]); // Фильтруем комнаты, где id комнаты совпадает с id сокета
}).map(room => {
const socket = io.sockets.sockets.get(room[0]);
return socket && socket.handshake.query.userId
? { userId: socket.handshake.query.userId, socketId: room[0] }
: null;
}).filter(Boolean);
// Находим сокет пользователя
const userSocketInfo = activeUsersArray.find(u => u.userId === userId);
// Если пользователь онлайн, отправляем уведомление о блокировке
if (userSocketInfo && userSocketInfo.socketId) {
io.to(userSocketInfo.socketId).emit("account_blocked", {
message: 'Ваш аккаунт был заблокирован администратором.',
reason: reason || 'Нарушение правил сервиса',
timestamp: new Date().toISOString()
});
console.log(`[AdminController] Отправлено уведомление о блокировке пользователю ${userId}`);
} else {
// Если пользователь не в сети, просто оставляем лог
console.log(`[AdminController] Пользователь ${userId} заблокирован, но не находится в сети для отправки уведомления`);
}
}
// Если пользователь был неактивным и теперь разблокируется
else if (!wasActive && user.isActive) {
// Используем тот же подход для поиска сокетов пользователя
const activeUsersArray = Array.from(io.sockets.adapter.rooms).filter(room => {
return room[1].has(room[0]);
}).map(room => {
const socket = io.sockets.sockets.get(room[0]);
return socket && socket.handshake.query.userId
? { userId: socket.handshake.query.userId, socketId: room[0] }
: null;
}).filter(Boolean);
// Находим сокет пользователя (если он вдруг залогинился с другим статусом)
const userSocketInfo = activeUsersArray.find(u => u.userId === userId);
// Если пользователь онлайн, отправляем уведомление о разблокировке
if (userSocketInfo && userSocketInfo.socketId) {
io.to(userSocketInfo.socketId).emit("account_unblocked", {
message: 'Ваш аккаунт был разблокирован администратором.',
timestamp: new Date().toISOString()
});
console.log(`[AdminController] Отправлено уведомление о разблокировке пользователю ${userId}`);
} else {
console.log(`[AdminController] Пользователь ${userId} разблокирован, но не находится в сети для отправки уведомления`);
}
}
res.json({ res.json({
message: user.isActive ? 'Пользователь разблокирован' : 'Пользователь заблокирован', message: user.isActive ? 'Пользователь разблокирован' : 'Пользователь заблокирован',

View File

@ -127,6 +127,55 @@ async function logout() {
console.log('Пользователь вышел из системы.'); console.log('Пользователь вышел из системы.');
} }
// Функция обработки события блокировки аккаунта
function handleAccountBlocked(data) {
// Создаем объект с информацией о блокировке
const blockInfo = {
blocked: true,
message: data?.message || 'Ваш аккаунт был заблокирован администратором.',
reason: data?.reason || 'Нарушение правил сервиса',
timestamp: new Date().toISOString()
};
// Сохраняем информацию о блокировке в localStorage для отображения на странице логина
localStorage.setItem('accountBlockedInfo', JSON.stringify(blockInfo));
// Выполняем выход пользователя
user.value = null;
token.value = null;
localStorage.removeItem('userToken');
// Перенаправляем на страницу входа с уведомлением о блокировке
router.push({
name: 'Login',
query: { blocked: 'true' }
});
console.log('Пользователь был разлогинен из-за блокировки аккаунта.');
}
// Функция обработки события разблокировки аккаунта
function handleAccountUnblocked(data) {
console.log('Получено уведомление о разблокировке аккаунта:', data);
// Удаляем информацию о блокировке, если она была сохранена
localStorage.removeItem('accountBlockedInfo');
// Показываем уведомление пользователю, если он сейчас в приложении
// Можно использовать какую-то систему уведомлений в приложении
// или просто логировать это событие
// Если пользователь не залогинен, но его аккаунт был разблокирован,
// можно показать соответствующее сообщение при следующем входе
localStorage.setItem('accountUnblockedInfo', JSON.stringify({
unblocked: true,
message: data?.message || 'Ваш аккаунт был разблокирован.',
timestamp: new Date().toISOString()
}));
console.log('Аккаунт пользователя разблокирован.');
}
// Экспортируем то, что понадобится в компонентах // Экспортируем то, что понадобится в компонентах
export function useAuth() { export function useAuth() {
return { return {
@ -137,5 +186,7 @@ export function useAuth() {
register, register,
logout, logout,
fetchUser, // Эту функцию нужно будет вызвать при инициализации приложения fetchUser, // Эту функцию нужно будет вызвать при инициализации приложения
handleAccountBlocked, // Добавляем функцию обработки блокировки аккаунта
handleAccountUnblocked // Добавляем функцию обработки разблокировки аккаунта
}; };
} }

View File

@ -8,7 +8,7 @@ let socket;
const SOCKET_URL = '/'; // Vite будет перехватывать /socket.io/ путь const SOCKET_URL = '/'; // Vite будет перехватывать /socket.io/ путь
export const connectSocket = () => { export const connectSocket = () => {
const { user, isAuthenticated } = useAuth(); const { user, isAuthenticated, handleAccountBlocked, handleAccountUnblocked } = useAuth();
if (isAuthenticated.value && user.value && user.value._id) { if (isAuthenticated.value && user.value && user.value._id) {
// Подключаемся только если пользователь аутентифицирован и есть ID // Подключаемся только если пользователь аутентифицирован и есть ID
@ -46,6 +46,30 @@ export const connectSocket = () => {
console.error('[SocketService] Ошибка подключения к Socket.IO:', error.message, error.data); console.error('[SocketService] Ошибка подключения к Socket.IO:', error.message, error.data);
}); });
// Добавляем обработчик для события блокировки аккаунта
socket.on('account_blocked', (data) => {
console.log('[SocketService] Получено уведомление о блокировке аккаунта:', data);
// Вызываем функцию обработки блокировки из auth.js тихо, без показа браузерного уведомления
// Отложенный вызов, чтобы избежать показа стандартного уведомления браузера
setTimeout(() => {
handleAccountBlocked(data);
}, 10);
});
// Добавляем обработчик для события разблокировки аккаунта
socket.on('account_unblocked', (data) => {
console.log('[SocketService] Получено уведомление о разблокировке аккаунта:', data);
// Аналогично блокировке, используем setTimeout для предотвращения браузерного уведомления
setTimeout(() => {
// Если есть функция handleAccountUnblocked, вызываем её
if (typeof handleAccountUnblocked === 'function') {
handleAccountUnblocked(data);
} else {
console.log('[SocketService] Аккаунт разблокирован, но обработчик не определен');
}
}, 10);
});
// Можно здесь же слушать глобальные события, если нужно // Можно здесь же слушать глобальные события, если нужно
// socket.on('getUsers', (users) => { // socket.on('getUsers', (users) => {
// console.log('[SocketService] Получен список активных пользователей:', users); // console.log('[SocketService] Получен список активных пользователей:', users);

View File

@ -16,6 +16,18 @@
<p>Рады видеть вас снова!</p> <p>Рады видеть вас снова!</p>
</div> </div>
<!-- Добавляем показ информации о блокировке аккаунта -->
<div v-if="accountBlocked" class="blocked-account-message">
<i class="bi-shield-exclamation"></i>
<div class="blocked-message-content">
<h3>Аккаунт заблокирован</h3>
<p>{{ blockedInfo.message }}</p>
<p v-if="blockedInfo.reason" class="block-reason">
<strong>Причина:</strong> {{ blockedInfo.reason }}
</p>
</div>
</div>
<form @submit.prevent="handleLogin" class="login-form"> <form @submit.prevent="handleLogin" class="login-form">
<div class="form-group"> <div class="form-group">
<label for="email"> <label for="email">
@ -80,16 +92,30 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted, onUnmounted } from 'vue'; import { ref, onMounted, onUnmounted, computed } from 'vue';
import { useAuth } from '@/auth'; import { useAuth } from '@/auth';
import { useRoute } from 'vue-router';
const route = useRoute();
const email = ref(''); const email = ref('');
const password = ref(''); const password = ref('');
const errorMessage = ref(''); const errorMessage = ref('');
const loading = ref(false); const loading = ref(false);
const blockedInfo = ref({
blocked: false,
message: 'Ваш аккаунт был заблокирован администратором.',
reason: 'Нарушение правил сервиса',
timestamp: ''
});
const { login } = useAuth(); const { login } = useAuth();
// Проверяем, был ли аккаунт заблокирован
const accountBlocked = computed(() => {
// Проверяем query параметр и информацию из localStorage
return route.query.blocked === 'true' || blockedInfo.value.blocked;
});
// Функция для блокировки прокрутки страницы // Функция для блокировки прокрутки страницы
const preventScroll = () => { const preventScroll = () => {
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
@ -108,6 +134,23 @@ const enableScroll = () => {
onMounted(() => { onMounted(() => {
preventScroll(); // Блокируем прокрутку при монтировании компонента preventScroll(); // Блокируем прокрутку при монтировании компонента
// Проверяем, есть ли информация о блокировке в localStorage
const storedBlockInfo = localStorage.getItem('accountBlockedInfo');
if (storedBlockInfo) {
try {
const parsedInfo = JSON.parse(storedBlockInfo);
blockedInfo.value = parsedInfo;
// Если форма загружается с query параметром blocked=true,
// показываем сообщение об ошибке
if (route.query.blocked === 'true') {
errorMessage.value = 'Ваш аккаунт был заблокирован администратором.';
}
} catch (e) {
console.error('Ошибка при парсинге информации о блокировке:', e);
}
}
}); });
onUnmounted(() => { onUnmounted(() => {
@ -606,4 +649,78 @@ const handleLogin = async () => {
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
} }
} }
.blocked-account-message {
background-color: rgba(220, 53, 69, 0.15);
border: 1px solid rgba(220, 53, 69, 0.3);
border-radius: 10px;
padding: 1.25rem 1rem;
margin-bottom: 1.5rem;
display: flex;
align-items: flex-start;
gap: 1rem;
text-align: left;
animation: fadeInDown 0.5s ease;
border-left: 4px solid rgba(220, 53, 69, 0.7);
}
.blocked-account-message i {
font-size: 1.5rem;
color: rgba(255, 255, 255, 0.85);
margin-top: 0.1rem;
}
.blocked-message-content {
flex: 1;
}
.blocked-message-content h3 {
margin: 0 0 0.5rem 0;
color: white;
font-weight: 600;
font-size: 1.1rem;
}
.blocked-message-content p {
margin: 0;
color: rgba(255, 255, 255, 0.9);
font-size: 0.95rem;
line-height: 1.4;
}
.block-reason {
margin-top: 0.5rem !important;
font-style: italic;
}
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Адаптивность для блока с информацией о блокировке */
@media (max-width: 576px) {
.blocked-account-message {
padding: 1rem 0.8rem;
gap: 0.75rem;
}
.blocked-account-message i {
font-size: 1.3rem;
}
.blocked-message-content h3 {
font-size: 1rem;
}
.blocked-message-content p {
font-size: 0.85rem;
}
}
</style> </style>