/
delphin
/
CatRW
Обзор
Документация
Войти
/
delphin
/
CatRW
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
js/admin.js
430 строк
15 KB
delphin
перед глобальным изменением
04 мар 2026, 09:56
Верифицирован
04 мар 2026, 09:56
c6cfdf3
Код
Авторство
О чём код?
// admin.js - общая логика админки + система уведомлений // ===== СИСТЕМА УВЕДОМЛЕНИЙ ===== class Notifications { constructor() { this.containerId = 'admin-notifications'; this.init(); } // Инициализация init() { this.initContainer(); this.initStyles(); } // Создаём контейнер для уведомлений initContainer() { let container = document.getElementById(this.containerId); if (!container) { container = document.createElement('div'); container.id = this.containerId; container.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 10000; display: flex; flex-direction: column; gap: 10px; max-width: 400px; pointer-events: none; `; document.body.appendChild(container); } this.container = container; } // Добавляем CSS стили initStyles() { if (!document.querySelector('style[data-admin-notifications]')) { const style = document.createElement('style'); style.setAttribute('data-admin-notifications', 'true'); style.textContent = ` .admin-notification { background: white; border-radius: 8px; padding: 15px 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.15); border-left: 4px solid #3498db; animation: adminNotificationSlideIn 0.3s ease-out; display: flex; align-items: center; gap: 12px; pointer-events: auto; cursor: pointer; transition: transform 0.2s, opacity 0.2s; max-width: 400px; } .admin-notification:hover { transform: translateX(-5px); } .admin-notification-success { border-left-color: #2ecc71; background: #d4edda; color: #155724; } .admin-notification-error { border-left-color: #e74c3c; background: #f8d7da; color: #721c24; } .admin-notification-warning { border-left-color: #f39c12; background: #fff3cd; color: #856404; } .admin-notification-info { border-left-color: #3498db; background: #d1ecf1; color: #0c5460; } .admin-notification-icon { font-size: 18px; flex-shrink: 0; } .admin-notification-content { flex: 1; font-size: 14px; line-height: 1.4; } .admin-notification-close { background: none; border: none; color: inherit; cursor: pointer; font-size: 16px; opacity: 0.7; flex-shrink: 0; padding: 0 5px; transition: opacity 0.2s; } .admin-notification-close:hover { opacity: 1; } @keyframes adminNotificationSlideIn { from { opacity: 0; transform: translateX(100px); } to { opacity: 1; transform: translateX(0); } } @keyframes adminNotificationSlideOut { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(100px); } } `; document.head.appendChild(style); } } // Показать уведомление show(message, type = 'info', duration = 4000) { const icons = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }; const icon = icons[type] || icons.info; const notificationId = 'admin-notif-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9); const notification = document.createElement('div'); notification.id = notificationId; notification.className = `admin-notification admin-notification-${type}`; notification.innerHTML = ` <span class="admin-notification-icon">${icon}</span> <span class="admin-notification-content">${this.escapeHtml(message)}</span> <button class="admin-notification-close" onclick="window.Notifications.hide('${notificationId}')">✕</button> `; // Добавляем в начало контейнера this.container.insertBefore(notification, this.container.firstChild); // Ограничиваем количество (максимум 3) const allNotifications = this.container.querySelectorAll('.admin-notification'); if (allNotifications.length > 3) { for (let i = 3; i < allNotifications.length; i++) { this.hide(allNotifications[i].id); } } // Клик на всё уведомление закрывает его notification.addEventListener('click', (e) => { if (!e.target.classList.contains('admin-notification-close')) { this.hide(notificationId); } }); // Эффект при наведении notification.addEventListener('mouseenter', () => { notification.style.transform = 'translateX(-5px)'; const closeBtn = notification.querySelector('.admin-notification-close'); if (closeBtn) closeBtn.style.opacity = '1'; }); notification.addEventListener('mouseleave', () => { notification.style.transform = 'translateX(0)'; const closeBtn = notification.querySelector('.admin-notification-close'); if (closeBtn) closeBtn.style.opacity = '0.7'; }); // Автоскрытие (кроме ошибок) if (type !== 'error' && duration > 0) { setTimeout(() => { this.hide(notificationId); }, duration); } return notificationId; } // Скрыть уведомление hide(notificationId) { const notification = document.getElementById(notificationId); if (notification && notification.parentElement) { notification.style.animation = 'adminNotificationSlideOut 0.3s ease-out forwards'; setTimeout(() => { if (notification.parentElement) { notification.remove(); } }, 300); } } // Быстрые методы success(message, duration = 4000) { return this.show(message, 'success', duration); } error(message, duration = 0) { return this.show(message, 'error', duration); } warning(message, duration = 4000) { return this.show(message, 'warning', duration); } info(message, duration = 4000) { return this.show(message, 'info', duration); } // Экранирование HTML escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } } // ===== ОБЩИЕ ФУНКЦИИ АДМИНКИ ===== // Переключение навигации function toggleNav() { const nav = document.getElementById('adminNav'); if (!nav) return; const isCollapsed = nav.classList.contains('collapsed'); if (isCollapsed) { nav.classList.remove('collapsed'); localStorage.setItem('adminNavCollapsed', 'false'); } else { nav.classList.add('collapsed'); localStorage.setItem('adminNavCollapsed', 'true'); } } // Обновление текущего времени function updateCurrentTime() { const timeElement = document.getElementById('currentTime'); if (timeElement) { const now = new Date(); timeElement.textContent = now.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); } } // Инициализация табов (если есть) function initTabs() { document.querySelectorAll('.tab-btn').forEach(button => { button.addEventListener('click', function() { const tabId = this.getAttribute('onclick')?.match(/'([^']+)'/)?.[1]; if (!tabId) return; // Скрываем все табы document.querySelectorAll('.tab-content').forEach(tab => { tab.classList.remove('active'); }); // Убираем active у всех кнопок document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.remove('active'); }); // Показываем выбранный таб const tab = document.getElementById('tab-' + tabId); if (tab) { tab.classList.add('active'); this.classList.add('active'); } }); }); } // Инициализация модальных окон function initModals() { // Закрытие по клику на фон document.querySelectorAll('.modal').forEach(modal => { modal.addEventListener('click', function(e) { if (e.target === this) { this.style.display = 'none'; document.body.style.overflow = ''; } }); }); // Закрытие по ESC document.addEventListener('keydown', function(e) { if (e.key === 'Escape') { document.querySelectorAll('.modal.show, .modal[style*="display: block"]').forEach(modal => { modal.style.display = 'none'; document.body.style.overflow = ''; }); } }); } // Показать модальное окно function showModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.style.display = 'block'; document.body.style.overflow = 'hidden'; } } // Скрыть модальное окно function hideModal(modalId) { const modal = document.getElementById(modalId); if (modal) { modal.style.display = 'none'; document.body.style.overflow = ''; } } // Подтверждение действия function confirmAction(message, callback) { if (window.confirm(message)) { if (typeof callback === 'function') { callback(); } return true; } return false; } // Форматирование даты function formatDate(dateString) { if (!dateString) return '-'; const date = new Date(dateString); if (isNaN(date.getTime())) return dateString; return date.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); } // ===== ИНИЦИАЛИЗАЦИЯ ===== // Создаём глобальный экземпляр уведомлений window.Notifications = new Notifications(); // Алиас для обратной совместимости (если где-то используется showStatusAlert) window.showStatusAlert = function(message, type = 'info') { return Notifications[type] ? Notifications[type](message) : Notifications.info(message); }; // Инициализация при загрузке страницы document.addEventListener('DOMContentLoaded', function() { console.log('✅ Admin.js инициализирован'); // 1. Восстанавливаем состояние навигации const savedNavState = localStorage.getItem('adminNavCollapsed'); const nav = document.getElementById('adminNav'); if (nav && savedNavState === 'true') { nav.classList.add('collapsed'); } // 2. Запускаем обновление времени updateCurrentTime(); setInterval(updateCurrentTime, 1000); // 3. Инициализируем табы initTabs(); // 4. Инициализируем модальные окна initModals(); // 5. Добавляем CSS для спиннера если нет if (!document.querySelector('style[data-admin-spinner]')) { const style = document.createElement('style'); style.setAttribute('data-admin-spinner', 'true'); style.textContent = ` @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .admin-loader { display: inline-block; animation: spin 1s linear infinite; margin-right: 10px; } `; document.head.appendChild(style); } }); // Экспортируем функции в глобальную область видимости window.toggleNav = toggleNav; window.showModal = showModal; window.hideModal = hideModal; window.confirmAction = confirmAction; window.formatDate = formatDate; console.log('✅ Admin.js загружен');