This commit is contained in:
Professional 2025-05-26 16:41:09 +07:00
parent 4ef4c5f428
commit 71b48b5d25
5 changed files with 209 additions and 6 deletions

View File

@ -63,6 +63,18 @@
</router-link> </router-link>
</template> </template>
</nav> </nav>
<!-- Контейнер для уведомлений -->
<div class="toast-list">
<Toast
v-for="toast in toasts"
:key="toast.id"
:message="toast.message"
:type="toast.type"
:duration="toast.duration"
@close="removeToast(toast.id)"
/>
</div>
</div> </div>
</template> </template>
@ -72,10 +84,16 @@ import { ref, watch, onMounted, computed, onUnmounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import api from '@/services/api'; import api from '@/services/api';
import { getSocket, connectSocket } from '@/services/socketService'; import { getSocket, connectSocket } from '@/services/socketService';
import Toast from '@/components/Toast.vue';
import toastService from '@/services/toastService';
const { user, isAuthenticated, logout, fetchUser } = useAuth(); const { user, isAuthenticated, logout, fetchUser } = useAuth();
const route = useRoute(); const route = useRoute();
// Импортируем уведомления из сервиса
const toasts = toastService.toasts;
const removeToast = toastService.remove;
const userData = ref(null); const userData = ref(null);
const authState = ref(false); const authState = ref(false);
const conversations = ref([]); const conversations = ref([]);
@ -270,6 +288,22 @@ body {
will-change: transform; /* Ускорение для анимаций */ will-change: transform; /* Ускорение для анимаций */
} }
/* Контейнер для уведомлений */
.toast-list {
position: fixed;
top: 20px;
right: 20px;
z-index: 2000;
display: flex;
flex-direction: column;
gap: 10px;
pointer-events: none;
}
.toast-list > * {
pointer-events: auto;
}
#app-container { #app-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

117
src/components/Toast.vue Normal file
View File

@ -0,0 +1,117 @@
<template>
<transition name="toast-fade">
<div v-if="show" class="toast-container" :class="type">
<div class="toast-content">
<i v-if="type === 'success'" class="bi-check-circle-fill"></i>
<i v-else-if="type === 'error'" class="bi-exclamation-circle-fill"></i>
<i v-else-if="type === 'warning'" class="bi-exclamation-triangle-fill"></i>
<i v-else class="bi-info-circle-fill"></i>
<span>{{ message }}</span>
</div>
</div>
</transition>
</template>
<script>
import { ref, onMounted, onBeforeUnmount } from 'vue';
export default {
name: 'Toast',
props: {
message: {
type: String,
required: true
},
duration: {
type: Number,
default: 3000
},
type: {
type: String,
default: 'info',
validator(value) {
return ['info', 'success', 'warning', 'error'].includes(value);
}
}
},
emits: ['close'],
setup(props, { emit }) {
const show = ref(false);
let timer = null;
const startTimer = () => {
timer = setTimeout(() => {
show.value = false;
setTimeout(() => emit('close'), 300); // Даем время для завершения анимации
}, props.duration);
};
onMounted(() => {
show.value = true;
startTimer();
});
onBeforeUnmount(() => {
clearTimeout(timer);
});
return {
show
};
}
};
</script>
<style scoped>
.toast-container {
position: fixed;
bottom: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 8px;
color: white;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
z-index: 1000;
display: flex;
align-items: center;
max-width: 350px;
}
.toast-content {
display: flex;
align-items: center;
gap: 12px;
}
.toast-content i {
font-size: 1.2rem;
}
.success {
background-color: #4caf50;
}
.info {
background-color: #2196f3;
}
.warning {
background-color: #ff9800;
}
.error {
background-color: #f44336;
}
/* Анимация появления и исчезновения */
.toast-fade-enter-active,
.toast-fade-leave-active {
transition: opacity 0.3s, transform 0.3s;
}
.toast-fade-enter-from,
.toast-fade-leave-to {
opacity: 0;
transform: translateY(20px);
}
</style>

View File

@ -0,0 +1,48 @@
// Простой сервис для отображения всплывающих уведомлений
import { ref, reactive } from 'vue';
const toasts = reactive([]);
let toastId = 0;
// Добавление нового уведомления
const add = (message, type = 'info', duration = 3000) => {
const id = toastId++;
toasts.push({ id, message, type, duration });
// Автоматически убираем уведомление через duration + 300ms анимации
setTimeout(() => {
remove(id);
}, duration + 300);
return id;
};
// Удаление уведомления по ID
const remove = (id) => {
const index = toasts.findIndex(toast => toast.id === id);
if (index > -1) {
toasts.splice(index, 1);
}
};
// Показ информационного уведомления
const info = (message, duration) => add(message, 'info', duration);
// Показ уведомления об успехе
const success = (message, duration) => add(message, 'success', duration);
// Показ предупреждения
const warning = (message, duration) => add(message, 'warning', duration);
// Показ уведомления об ошибке
const error = (message, duration) => add(message, 'error', duration);
export default {
toasts,
add,
remove,
info,
success,
warning,
error
};

View File

@ -142,6 +142,7 @@
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
import toastService from '@/services/toastService';
export default { export default {
name: 'AdminUserDetail', name: 'AdminUserDetail',
@ -214,11 +215,12 @@ export default {
// Обновляем статус пользователя // Обновляем статус пользователя
user.value.isActive = response.data.isActive; user.value.isActive = response.data.isActive;
// Показываем уведомление (можно добавить компонент уведомлений) // Заменяем alert на уведомление через сервис
alert(response.data.message); const actionType = user.value.isActive ? 'success' : 'warning';
toastService.add(response.data.message, actionType, 3000);
} catch (err) { } catch (err) {
console.error('Ошибка при изменении статуса пользователя:', err); console.error('Ошибка при изменении статуса пользователя:', err);
alert('Ошибка при изменении статуса пользователя'); toastService.error('Ошибка при изменении статуса пользователя');
} finally { } finally {
loading.value = false; loading.value = false;
} }

View File

@ -167,6 +167,7 @@
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import axios from 'axios'; import axios from 'axios';
import toastService from '@/services/toastService';
export default { export default {
name: 'AdminUsers', name: 'AdminUsers',
@ -270,11 +271,12 @@ export default {
users.value[userIndex].isActive = response.data.isActive; users.value[userIndex].isActive = response.data.isActive;
} }
// Показываем уведомление (можно добавить компонент уведомлений) // Заменяем стандартный alert на наш сервис уведомлений
alert(response.data.message); const type = response.data.isActive ? 'success' : 'warning';
toastService.add(response.data.message, type, 3000);
} catch (err) { } catch (err) {
console.error('Ошибка при изменении статуса пользователя:', err); console.error('Ошибка при изменении статуса пользователя:', err);
alert('Ошибка при изменении статуса пользователя'); toastService.error('Ошибка при изменении статуса пользователя');
} finally { } finally {
loading.value = false; loading.value = false;
} }