848 lines
38 KiB
JavaScript
848 lines
38 KiB
JavaScript
// loading-start.js - Загружается первым в <head>
|
||
(function() {
|
||
// Конфигурация
|
||
const CONFIG = {
|
||
maxRetries: 3,
|
||
retryDelay: 2000,
|
||
timeout: 10000,
|
||
showWarnings: true,
|
||
maxLoadingTime: 15000, // Максимум 15 секунд
|
||
minLoadingTime: 5000 // Минимум 5 секунд
|
||
};
|
||
|
||
// Генерация случайных цветов для градиента
|
||
function getRandomGradient() {
|
||
// Базовая палитра цветов для школы/образования
|
||
const colorPalettes = [
|
||
// Сине-фиолетовая гамма
|
||
{ start: '#667eea', end: '#764ba2' },
|
||
{ start: '#6B73FF', end: '#000DFF' },
|
||
{ start: '#5f72bd', end: '#9b23ea' },
|
||
|
||
// Зелено-голубая гамма
|
||
{ start: '#11998e', end: '#38ef7d' },
|
||
{ start: '#0BA360', end: '#3CBBB2' },
|
||
{ start: '#1D976C', end: '#93F9B9' },
|
||
|
||
// Оранжево-розовая гамма
|
||
{ start: '#f46b45', end: '#eea849' },
|
||
{ start: '#FF512F', end: '#DD2476' },
|
||
{ start: '#F09819', end: '#EDDE5D' },
|
||
|
||
// Красная гамма
|
||
{ start: '#cb2d3e', end: '#ef473a' },
|
||
{ start: '#FF416C', end: '#FF4B2B' },
|
||
{ start: '#DC2424', end: '#4A569D' },
|
||
|
||
// Фиолетовая гамма
|
||
{ start: '#8E2DE2', end: '#4A00E0' },
|
||
{ start: '#A770EF', end: '#CF8BF3' },
|
||
{ start: '#7F00FF', end: '#E100FF' },
|
||
|
||
// Морская гамма
|
||
{ start: '#00B4DB', end: '#0083B0' },
|
||
{ start: '#00C9FF', end: '#92FE9D' },
|
||
{ start: '#1FA2FF', end: '#12D8FA' },
|
||
|
||
// Осенняя гамма
|
||
{ start: '#FC4A1A', end: '#F7B733' },
|
||
{ start: '#ED213A', end: '#93291E' },
|
||
{ start: '#F12711', end: '#F5AF19' },
|
||
|
||
// Ночная гамма
|
||
{ start: '#141E30', end: '#243B55' },
|
||
{ start: '#0F2027', end: '#203A43' },
|
||
{ start: '#1c1c1c', end: '#3a3a3a' },
|
||
|
||
// Пастельная гамма
|
||
{ start: '#a8c0ff', end: '#3f2b96' },
|
||
{ start: '#c84e89', end: '#F15F79' },
|
||
{ start: '#b993d0', end: '#8ca6db' },
|
||
|
||
// Школьная гамма
|
||
{ start: '#56ab2f', end: '#a8e063' },
|
||
{ start: '#1D976C', end: '#93F9B9' },
|
||
{ start: '#4CA1AF', end: '#2C3E50' }
|
||
];
|
||
|
||
// Случайная палитра
|
||
const palette = colorPalettes[Math.floor(Math.random() * colorPalettes.length)];
|
||
|
||
// Случайное смещение градиента
|
||
const directions = [
|
||
'135deg', '90deg', '45deg', '180deg', '225deg', '270deg', '315deg'
|
||
];
|
||
const direction = directions[Math.floor(Math.random() * directions.length)];
|
||
|
||
return {
|
||
background: `linear-gradient(${direction}, ${palette.start}, ${palette.end})`,
|
||
start: palette.start,
|
||
end: palette.end,
|
||
direction: direction
|
||
};
|
||
}
|
||
|
||
// Генерация случайного цвета для прогресс-бара
|
||
function getRandomProgressColor() {
|
||
const colors = [
|
||
'#fff', '#ffd700', '#00ff87', '#00ffff', '#ff69b4', '#ffa500',
|
||
'#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#ffeaa7', '#dfe6e9'
|
||
];
|
||
return colors[Math.floor(Math.random() * colors.length)];
|
||
}
|
||
|
||
// Генерация случайного цвета для текста
|
||
function getRandomTextColor() {
|
||
const colors = [
|
||
'white', '#f0f0f0', '#ffeaa7', '#dfe6e9', '#b2bec3', '#ffffff'
|
||
];
|
||
return colors[Math.floor(Math.random() * colors.length)];
|
||
}
|
||
|
||
// Получаем случайные цвета для этой загрузки
|
||
const gradientColors = getRandomGradient();
|
||
const progressColor = getRandomProgressColor();
|
||
const textColor = getRandomTextColor();
|
||
const accentColor = getRandomProgressColor();
|
||
|
||
// Статусы для динамического отображения
|
||
const LOADING_STATUSES = [
|
||
{ emoji: '📦', text: 'Загрузка компонентов...', weight: 10 },
|
||
{ emoji: '🔧', text: 'Настройка модулей...', weight: 15 },
|
||
{ emoji: '⚙️', text: 'Инициализация системы...', weight: 20 },
|
||
{ emoji: '🔌', text: 'Подключение к серверу...', weight: 25 },
|
||
{ emoji: '📊', text: 'Загрузка данных...', weight: 30 },
|
||
{ emoji: '🧩', text: 'Сборка интерфейса...', weight: 35 },
|
||
{ emoji: '🎨', text: 'Отрисовка элементов...', weight: 40 },
|
||
{ emoji: '🔐', text: 'Проверка безопасности...', weight: 45 },
|
||
{ emoji: '📁', text: 'Загрузка файлов...', weight: 50 },
|
||
{ emoji: '🔄', text: 'Синхронизация...', weight: 55 },
|
||
{ emoji: '🧪', text: 'Тестирование модулей...', weight: 60 },
|
||
{ emoji: '📝', text: 'Подготовка задач...', weight: 65 },
|
||
{ emoji: '👥', text: 'Загрузка пользователей...', weight: 70 },
|
||
{ emoji: '📅', text: 'Обновление календаря...', weight: 75 },
|
||
{ emoji: '🔔', text: 'Настройка уведомлений...', weight: 80 },
|
||
{ emoji: '💾', text: 'Сохранение кэша...', weight: 85 },
|
||
{ emoji: '✨', text: 'Финальная обработка...', weight: 90 },
|
||
{ emoji: '🚀', text: 'Запуск приложения...', weight: 95 },
|
||
{ emoji: '✅', text: 'Почти готово...', weight: 98 }
|
||
];
|
||
|
||
// Дополнительные случайные статусы
|
||
const RANDOM_STATUSES = [
|
||
{ emoji: '☕', text: 'Наливаем кофе...' },
|
||
{ emoji: '🧹', text: 'Подметаем баги...' },
|
||
{ emoji: '🎮', text: 'Играем в косынку...' },
|
||
{ emoji: '📚', text: 'Читаем документацию...' },
|
||
{ emoji: '🎵', text: 'Включаем музыку...' },
|
||
{ emoji: '🧘', text: 'Медитируем...' },
|
||
{ emoji: '🏋️', text: 'Качаем бицепс...' },
|
||
{ emoji: '🍕', text: 'Заказываем пиццу...' },
|
||
{ emoji: '🌴', text: 'Улетаем в отпуск...' },
|
||
{ emoji: '🎪', text: 'Устраиваем цирк...' },
|
||
{ emoji: '🎭', text: 'Играем спектакль...' },
|
||
{ emoji: '🎯', text: 'Целимся в баги...' },
|
||
{ emoji: '🎲', text: 'Бросаем кубик...' },
|
||
{ emoji: '🎰', text: 'Крутим барабан...' },
|
||
{ emoji: '🦄', text: 'Ловим единорога...' },
|
||
{ emoji: '🌈', text: 'Красим радугу...' },
|
||
{ emoji: '🪄', text: 'Колдуем...' },
|
||
{ emoji: '🎩', text: 'Достаем кролика...' },
|
||
{ emoji: '🕯️', text: 'Зажигаем свечи...' },
|
||
{ emoji: '🔮', text: 'Гадаем на баги...' }
|
||
];
|
||
|
||
// Функция для инициализации после загрузки DOM
|
||
function initializeLoadingOverlay() {
|
||
// Проверяем, существует ли уже оверлей
|
||
if (document.getElementById('loading-overlay')) {
|
||
return;
|
||
}
|
||
|
||
// Создаем контейнер для анимации загрузки
|
||
const loadingOverlay = document.createElement('div');
|
||
loadingOverlay.id = 'loading-overlay';
|
||
loadingOverlay.innerHTML = `
|
||
<div class="loading-container">
|
||
<div class="loading-spinner">
|
||
<div class="spinner" id="loading-spinner"></div>
|
||
</div>
|
||
<div class="loading-text">
|
||
<h2 id="loading-title">School CRM</h2>
|
||
<p>Загрузка сервиса управления задачами...</p>
|
||
<div class="loading-progress">
|
||
<div class="progress-bar" id="loading-progress-bar"></div>
|
||
</div>
|
||
<div class="loading-status" id="loading-status">
|
||
<span class="status-icon">📦</span> Инициализация...
|
||
</div>
|
||
<div class="loading-percent" id="loading-percent">0%</div>
|
||
<div class="loading-details" id="loading-details"></div>
|
||
</div>
|
||
<div class="loading-footer">
|
||
<p>МАОУ - СОШ № 25 | Версия 0.9</p>
|
||
<p class="loading-tip" id="loading-tip"></p>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// Добавляем стили для анимации со случайными цветами
|
||
const styles = document.createElement('style');
|
||
styles.textContent = `
|
||
#loading-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background: ${gradientColors.background};
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
z-index: 9999;
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||
transition: opacity 0.5s ease-out;
|
||
}
|
||
|
||
#loading-overlay.fade-out {
|
||
opacity: 0;
|
||
}
|
||
|
||
.loading-container {
|
||
text-align: center;
|
||
color: ${textColor};
|
||
max-width: 500px;
|
||
width: 90%;
|
||
padding: 40px;
|
||
background: rgba(255, 255, 255, 0.15);
|
||
border-radius: 20px;
|
||
backdrop-filter: blur(10px);
|
||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||
animation: slideUp 0.8s ease-out;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
@keyframes slideUp {
|
||
from {
|
||
transform: translateY(50px);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateY(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
.loading-spinner {
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.spinner {
|
||
width: 80px;
|
||
height: 80px;
|
||
margin: 0 auto;
|
||
border: 5px solid rgba(255, 255, 255, 0.2);
|
||
border-radius: 50%;
|
||
border-top-color: ${progressColor};
|
||
border-left-color: ${accentColor};
|
||
border-right-color: ${accentColor};
|
||
animation: spin 1s ease-in-out infinite;
|
||
box-shadow: 0 0 20px ${progressColor}40;
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
.loading-text h2 {
|
||
font-size: 32px;
|
||
margin: 0 0 15px;
|
||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
|
||
letter-spacing: 2px;
|
||
background: linear-gradient(135deg, ${textColor}, ${progressColor});
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
|
||
.loading-text p {
|
||
font-size: 18px;
|
||
margin: 0 0 30px;
|
||
opacity: 0.9;
|
||
color: ${textColor};
|
||
}
|
||
|
||
.loading-progress {
|
||
width: 100%;
|
||
height: 12px;
|
||
background: rgba(255, 255, 255, 0.15);
|
||
border-radius: 6px;
|
||
margin: 20px 0;
|
||
overflow: hidden;
|
||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.progress-bar {
|
||
width: 0%;
|
||
height: 100%;
|
||
background: linear-gradient(90deg, ${progressColor}, ${accentColor}, ${progressColor});
|
||
border-radius: 6px;
|
||
transition: width 0.3s ease;
|
||
animation: progressPulse 2s ease-in-out infinite;
|
||
background-size: 200% 100%;
|
||
}
|
||
|
||
@keyframes progressPulse {
|
||
0%, 100% { opacity: 1; background-position: 0% 50%; }
|
||
50% { opacity: 0.8; background-position: 100% 50%; }
|
||
}
|
||
|
||
.loading-status {
|
||
font-size: 18px;
|
||
margin: 15px 0;
|
||
min-height: 28px;
|
||
font-weight: 500;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
color: ${textColor};
|
||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.loading-percent {
|
||
font-size: 42px;
|
||
font-weight: bold;
|
||
margin: 10px 0;
|
||
text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3);
|
||
background: linear-gradient(135deg, ${textColor}, ${progressColor});
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
animation: percentPulse 1.5s ease-in-out infinite;
|
||
}
|
||
|
||
@keyframes percentPulse {
|
||
0%, 100% { transform: scale(1); }
|
||
50% { transform: scale(1.05); }
|
||
}
|
||
|
||
.loading-details {
|
||
font-size: 13px;
|
||
margin-top: 15px;
|
||
opacity: 0.8;
|
||
min-height: 20px;
|
||
color: ${textColor};
|
||
background: rgba(255, 255, 255, 0.1);
|
||
padding: 8px 15px;
|
||
border-radius: 20px;
|
||
display: inline-block;
|
||
}
|
||
|
||
.loading-footer {
|
||
margin-top: 30px;
|
||
font-size: 14px;
|
||
opacity: 0.8;
|
||
color: ${textColor};
|
||
}
|
||
|
||
.loading-footer p {
|
||
margin: 5px 0;
|
||
color: ${textColor};
|
||
}
|
||
|
||
.loading-tip {
|
||
font-size: 13px;
|
||
font-style: italic;
|
||
color: ${textColor};
|
||
animation: fadeInOut 3s ease-in-out infinite;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
padding: 5px 15px;
|
||
border-radius: 20px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
@keyframes fadeInOut {
|
||
0%, 100% { opacity: 0.7; transform: translateY(0); }
|
||
50% { opacity: 1; transform: translateY(-2px); }
|
||
}
|
||
|
||
.status-icon {
|
||
display: inline-block;
|
||
animation: bounce 1s ease infinite;
|
||
filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
|
||
}
|
||
|
||
@keyframes bounce {
|
||
0%, 100% { transform: translateY(0); }
|
||
50% { transform: translateY(-5px); }
|
||
}
|
||
|
||
.retry-button {
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border: 2px solid ${textColor};
|
||
color: ${textColor};
|
||
padding: 12px 30px;
|
||
border-radius: 30px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
margin-top: 20px;
|
||
transition: all 0.3s ease;
|
||
font-weight: bold;
|
||
text-transform: uppercase;
|
||
letter-spacing: 1px;
|
||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||
}
|
||
|
||
.retry-button:hover {
|
||
background: ${progressColor};
|
||
color: ${gradientColors.start};
|
||
transform: scale(1.05);
|
||
border-color: ${progressColor};
|
||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.warning {
|
||
color: #ffd700;
|
||
text-shadow: 0 0 10px #ffd700;
|
||
}
|
||
|
||
.error {
|
||
color: #ff6b6b;
|
||
text-shadow: 0 0 10px #ff6b6b;
|
||
}
|
||
|
||
.progress-dots {
|
||
display: inline-block;
|
||
min-width: 30px;
|
||
}
|
||
|
||
.progress-dots:after {
|
||
content: '...';
|
||
animation: dots 1.5s steps(4, end) infinite;
|
||
font-weight: bold;
|
||
letter-spacing: 2px;
|
||
}
|
||
|
||
@keyframes dots {
|
||
0%, 20% { content: ''; }
|
||
40% { content: '.'; }
|
||
60% { content: '..'; }
|
||
80%, 100% { content: '...'; }
|
||
}
|
||
|
||
/* Добавляем случайные частицы для эффекта */
|
||
.loading-container::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: -50%;
|
||
left: -50%;
|
||
width: 200%;
|
||
height: 200%;
|
||
background: radial-gradient(circle, ${progressColor}20 1px, transparent 1px);
|
||
background-size: 50px 50px;
|
||
animation: particles 20s linear infinite;
|
||
opacity: 0.1;
|
||
pointer-events: none;
|
||
}
|
||
|
||
@keyframes particles {
|
||
from { transform: rotate(0deg); }
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
`;
|
||
|
||
// Добавляем элементы в DOM с проверкой
|
||
if (document.head) {
|
||
document.head.appendChild(styles);
|
||
} else {
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.head.appendChild(styles);
|
||
});
|
||
}
|
||
|
||
if (document.body) {
|
||
document.body.appendChild(loadingOverlay);
|
||
} else {
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.body.appendChild(loadingOverlay);
|
||
});
|
||
}
|
||
|
||
// Логируем использованные цвета
|
||
console.log('🎨 Случайные цвета загрузки:', {
|
||
gradient: gradientColors,
|
||
progress: progressColor,
|
||
accent: accentColor,
|
||
text: textColor
|
||
});
|
||
}
|
||
|
||
// Инициализируем оверлей после загрузки DOM
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', initializeLoadingOverlay);
|
||
} else {
|
||
initializeLoadingOverlay();
|
||
}
|
||
|
||
// Массив полезных советов
|
||
const TIPS = [
|
||
'💡 Совет: Используйте фильтры для быстрого поиска задач',
|
||
'💡 Совет: Можно прикреплять до 15 файлов к задаче',
|
||
'💡 Совет: Настраивайте уведомления в личном кабинете',
|
||
'💡 Совет: Используйте Канбан-доску для визуализации задач',
|
||
'💡 Совет: Отмечайте важные задачи цветными метками',
|
||
'💡 Совет: Следите за просроченными задачами в отдельной вкладке',
|
||
'💡 Совет: Создавайте шаблоны для частых типов задач',
|
||
'💡 Совет: Используйте поиск для быстрого доступа к задачам',
|
||
'💡 Совет: Комментируйте задачи для уточнения деталей',
|
||
'💡 Совет: Прикрепляйте скриншоты к задачам в ИТ-отдел',
|
||
'💡 Совет: Проверяйте уведомления на email ежедневно',
|
||
'💡 Совет: Архивируйте выполненные задачи для чистоты списка',
|
||
'🌟 Факт: Сегодняшний градиент - один из 30 возможных',
|
||
'🎨 Факт: Цвета загрузки уникальны для каждого обновления',
|
||
'🌈 Факт: У вас сегодня особенная цветовая схема'
|
||
];
|
||
|
||
// Счетчик загруженных скриптов
|
||
window.loadingScripts = {
|
||
total: 0,
|
||
loaded: 0,
|
||
errors: [],
|
||
retryCounts: {},
|
||
startTime: Date.now(),
|
||
scripts: [],
|
||
animationInterval: null,
|
||
tipInterval: null,
|
||
loadingComplete: false,
|
||
minTimeElapsed: false,
|
||
|
||
updateProgress: function() {
|
||
const percent = Math.min(Math.round((this.loaded / this.total) * 100), 100);
|
||
const progressBar = document.getElementById('loading-progress-bar');
|
||
const percentDisplay = document.getElementById('loading-percent');
|
||
const statusDisplay = document.getElementById('loading-status');
|
||
const detailsDisplay = document.getElementById('loading-details');
|
||
|
||
if (progressBar) progressBar.style.width = percent + '%';
|
||
if (percentDisplay) percentDisplay.textContent = percent + '%';
|
||
|
||
// Обновляем статус в зависимости от процента загрузки
|
||
if (statusDisplay) {
|
||
if (this.errors.length > 0) {
|
||
statusDisplay.innerHTML = '<span class="status-icon warning">⚠️</span> Проблемы с загрузкой. Пытаемся восстановить...';
|
||
} else {
|
||
// Находим подходящий статус по проценту загрузки
|
||
const currentStatus = LOADING_STATUSES.find(s => s.weight >= percent) || LOADING_STATUSES[LOADING_STATUSES.length - 1];
|
||
statusDisplay.innerHTML = `<span class="status-icon">${currentStatus.emoji}</span> ${currentStatus.text} <span class="progress-dots"></span>`;
|
||
}
|
||
}
|
||
|
||
// Обновляем детальную информацию
|
||
if (detailsDisplay) {
|
||
const loadedScripts = this.loaded;
|
||
const totalScripts = this.total;
|
||
const errors = this.errors.length;
|
||
const timeElapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
|
||
|
||
let detailsHtml = `📊 ${loadedScripts}/${totalScripts} модулей • ⏱️ ${timeElapsed}с`;
|
||
|
||
if (errors > 0) {
|
||
detailsHtml += `<br><span class="warning">⚠️ Ошибок: ${errors}</span>`;
|
||
}
|
||
|
||
detailsDisplay.innerHTML = detailsHtml;
|
||
}
|
||
|
||
// Проверяем, все ли скрипты загружены
|
||
if (this.loaded >= this.total && this.total > 0 && !this.loadingComplete) {
|
||
this.loadingComplete = true;
|
||
this.checkAndFinishLoading();
|
||
}
|
||
},
|
||
|
||
checkAndFinishLoading: function() {
|
||
const elapsed = Date.now() - this.startTime;
|
||
|
||
// Если прошло минимум 5 секунд, завершаем загрузку
|
||
if (elapsed >= CONFIG.minLoadingTime) {
|
||
this.finishLoading();
|
||
} else {
|
||
// Иначе ждем оставшееся время
|
||
const remainingTime = CONFIG.minLoadingTime - elapsed;
|
||
|
||
// Показываем сообщение о финальной подготовке
|
||
const statusDisplay = document.getElementById('loading-status');
|
||
if (statusDisplay) {
|
||
statusDisplay.innerHTML = '<span class="status-icon">✨</span> Финальная подготовка интерфейса... <span class="progress-dots"></span>';
|
||
}
|
||
|
||
setTimeout(() => {
|
||
this.finishLoading();
|
||
}, remainingTime);
|
||
}
|
||
},
|
||
|
||
startDynamicLoading: function() {
|
||
// Запускаем анимацию прогресса
|
||
let fakePercent = 0;
|
||
const startTime = Date.now();
|
||
|
||
this.animationInterval = setInterval(() => {
|
||
const elapsed = Date.now() - startTime;
|
||
const realPercent = Math.min(Math.round((this.loaded / this.total) * 100), 100);
|
||
|
||
// Рассчитываем фейковый процент на основе реального и времени
|
||
if (elapsed < CONFIG.maxLoadingTime && !this.loadingComplete) {
|
||
// Фейковый процент растет медленнее к концу
|
||
const timePercent = (elapsed / CONFIG.maxLoadingTime) * 100;
|
||
fakePercent = Math.min(
|
||
Math.max(realPercent, Math.floor(timePercent * 0.7 + Math.random() * 10)),
|
||
99 // Никогда не достигаем 100% фейком
|
||
);
|
||
} else {
|
||
// Если загрузка завершена или прошло 15 секунд, показываем реальный процент
|
||
fakePercent = realPercent;
|
||
}
|
||
|
||
// Обновляем прогресс бар с фейковым процентом
|
||
const progressBar = document.getElementById('loading-progress-bar');
|
||
const percentDisplay = document.getElementById('loading-percent');
|
||
|
||
if (progressBar) progressBar.style.width = fakePercent + '%';
|
||
if (percentDisplay) percentDisplay.textContent = fakePercent + '%';
|
||
|
||
// Случайно меняем статус для динамики
|
||
if (Math.random() > 0.7 && !this.loadingComplete) {
|
||
const statusDisplay = document.getElementById('loading-status');
|
||
if (statusDisplay && this.errors.length === 0) {
|
||
const randomStatus = RANDOM_STATUSES[Math.floor(Math.random() * RANDOM_STATUSES.length)];
|
||
statusDisplay.innerHTML = `<span class="status-icon">${randomStatus.emoji}</span> ${randomStatus.text} <span class="progress-dots"></span>`;
|
||
|
||
// Возвращаем нормальный статус через 2 секунды
|
||
setTimeout(() => {
|
||
const currentStatus = LOADING_STATUSES.find(s => s.weight >= fakePercent) || LOADING_STATUSES[LOADING_STATUSES.length - 1];
|
||
if (statusDisplay && !this.loadingComplete) {
|
||
statusDisplay.innerHTML = `<span class="status-icon">${currentStatus.emoji}</span> ${currentStatus.text} <span class="progress-dots"></span>`;
|
||
}
|
||
}, 2000);
|
||
}
|
||
}
|
||
|
||
// Проверяем, не пора ли закончить по времени
|
||
if (elapsed >= CONFIG.maxLoadingTime && !this.loadingComplete) {
|
||
this.loadingComplete = true;
|
||
this.finishDynamicLoading();
|
||
}
|
||
}, 200);
|
||
|
||
// Запускаем смену советов
|
||
this.tipInterval = setInterval(() => {
|
||
const tipElement = document.getElementById('loading-tip');
|
||
if (tipElement) {
|
||
const randomTip = TIPS[Math.floor(Math.random() * TIPS.length)];
|
||
tipElement.textContent = randomTip;
|
||
}
|
||
}, 3000);
|
||
},
|
||
|
||
finishDynamicLoading: function() {
|
||
if (this.animationInterval) {
|
||
clearInterval(this.animationInterval);
|
||
this.animationInterval = null;
|
||
}
|
||
|
||
// Показываем реальный прогресс
|
||
const realPercent = Math.min(Math.round((this.loaded / this.total) * 100), 100);
|
||
const progressBar = document.getElementById('loading-progress-bar');
|
||
const percentDisplay = document.getElementById('loading-percent');
|
||
|
||
if (progressBar) progressBar.style.width = realPercent + '%';
|
||
if (percentDisplay) percentDisplay.textContent = realPercent + '%';
|
||
|
||
// Завершаем загрузку
|
||
this.finishLoading();
|
||
},
|
||
|
||
finishLoading: function() {
|
||
// Останавливаем интервалы
|
||
if (this.animationInterval) {
|
||
clearInterval(this.animationInterval);
|
||
this.animationInterval = null;
|
||
}
|
||
|
||
if (this.tipInterval) {
|
||
clearInterval(this.tipInterval);
|
||
this.tipInterval = null;
|
||
}
|
||
|
||
const loadingTime = ((Date.now() - this.startTime) / 1000).toFixed(1);
|
||
const statusDisplay = document.getElementById('loading-status');
|
||
const percentDisplay = document.getElementById('loading-percent');
|
||
|
||
if (this.errors.length > 0) {
|
||
if (statusDisplay) {
|
||
statusDisplay.innerHTML = `<span class="status-icon warning">⚠️</span> Загружено с ошибками (${this.errors.length}) за ${loadingTime}с`;
|
||
}
|
||
|
||
if (percentDisplay) {
|
||
percentDisplay.innerHTML = '⚠️';
|
||
}
|
||
|
||
// Показываем кнопку перезагрузки
|
||
const detailsDisplay = document.getElementById('loading-details');
|
||
if (detailsDisplay) {
|
||
const retryButton = document.createElement('button');
|
||
retryButton.className = 'retry-button';
|
||
retryButton.innerHTML = '🔄 Перезагрузить страницу';
|
||
retryButton.onclick = () => window.location.reload();
|
||
detailsDisplay.appendChild(retryButton);
|
||
}
|
||
|
||
// Автоматическая перезагрузка через 5 секунд
|
||
setTimeout(() => {
|
||
if (confirm('Некоторые компоненты не загрузились. Перезагрузить страницу?')) {
|
||
window.location.reload();
|
||
}
|
||
}, 5000);
|
||
} else {
|
||
if (statusDisplay) {
|
||
statusDisplay.innerHTML = `<span class="status-icon">✅</span> Загрузка завершена за ${loadingTime}с`;
|
||
}
|
||
|
||
if (percentDisplay) {
|
||
percentDisplay.innerHTML = '100%';
|
||
}
|
||
|
||
// Плавно скрываем оверлей
|
||
setTimeout(() => {
|
||
const overlay = document.getElementById('loading-overlay');
|
||
if (overlay) {
|
||
overlay.classList.add('fade-out');
|
||
setTimeout(() => {
|
||
if (overlay && overlay.parentNode) {
|
||
overlay.parentNode.removeChild(overlay);
|
||
}
|
||
}, 500);
|
||
}
|
||
}, 800);
|
||
}
|
||
},
|
||
|
||
retryScript: function(scriptSrc) {
|
||
if (!this.retryCounts[scriptSrc]) {
|
||
this.retryCounts[scriptSrc] = 0;
|
||
}
|
||
|
||
if (this.retryCounts[scriptSrc] >= CONFIG.maxRetries) {
|
||
console.error(`Не удалось загрузить скрипт после ${CONFIG.maxRetries} попыток: ${scriptSrc}`);
|
||
return false;
|
||
}
|
||
|
||
this.retryCounts[scriptSrc]++;
|
||
this.loaded--; // Уменьшаем счетчик загруженных
|
||
|
||
const statusDisplay = document.getElementById('loading-status');
|
||
if (statusDisplay) {
|
||
statusDisplay.innerHTML = `<span class="status-icon">🔄</span> Перезагрузка: ${scriptSrc.split('/').pop()} (попытка ${this.retryCounts[scriptSrc]})`;
|
||
}
|
||
|
||
// Создаем новый script элемент
|
||
const script = document.createElement('script');
|
||
script.src = scriptSrc;
|
||
script.async = false;
|
||
|
||
script.onload = () => {
|
||
this.loaded++;
|
||
this.errors = this.errors.filter(e => e !== scriptSrc);
|
||
this.updateProgress();
|
||
};
|
||
|
||
script.onerror = () => {
|
||
setTimeout(() => this.retryScript(scriptSrc), CONFIG.retryDelay);
|
||
};
|
||
|
||
document.head.appendChild(script);
|
||
return true;
|
||
}
|
||
};
|
||
|
||
// Перехватываем загрузку скриптов
|
||
const originalAppendChild = document.head ? document.head.appendChild : null;
|
||
|
||
if (originalAppendChild) {
|
||
document.head.appendChild = function(node) {
|
||
if (node.tagName === 'SCRIPT' && node.src) {
|
||
window.loadingScripts.total++;
|
||
window.loadingScripts.scripts.push(node.src);
|
||
|
||
// Устанавливаем таймаут для скрипта
|
||
const timeoutId = setTimeout(() => {
|
||
console.warn(`Таймаут загрузки скрипта: ${node.src}`);
|
||
if (window.loadingScripts.retryScript(node.src)) {
|
||
// Удаляем старый скрипт
|
||
if (node.parentNode) {
|
||
node.parentNode.removeChild(node);
|
||
}
|
||
}
|
||
}, CONFIG.timeout);
|
||
|
||
// Обновляем обработчики событий
|
||
const originalOnLoad = node.onload;
|
||
node.onload = function() {
|
||
clearTimeout(timeoutId);
|
||
window.loadingScripts.loaded++;
|
||
window.loadingScripts.updateProgress();
|
||
if (originalOnLoad) originalOnLoad.apply(this, arguments);
|
||
};
|
||
|
||
const originalOnError = node.onerror;
|
||
node.onerror = function() {
|
||
clearTimeout(timeoutId);
|
||
window.loadingScripts.errors.push(node.src);
|
||
window.loadingScripts.updateProgress();
|
||
|
||
// Пытаемся перезагрузить скрипт
|
||
if (window.loadingScripts.retryScript(node.src)) {
|
||
// Удаляем старый скрипт
|
||
if (node.parentNode) {
|
||
node.parentNode.removeChild(node);
|
||
}
|
||
}
|
||
|
||
if (originalOnError) originalOnError.apply(this, arguments);
|
||
};
|
||
}
|
||
return originalAppendChild.call(this, node);
|
||
};
|
||
}
|
||
|
||
// Добавляем обработчик для DOMContentLoaded
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
// Запускаем динамическую загрузку
|
||
window.loadingScripts.startDynamicLoading();
|
||
|
||
// Устанавливаем максимальное время загрузки 15 секунд
|
||
setTimeout(() => {
|
||
if (!window.loadingScripts.loadingComplete && window.loadingScripts.loaded < window.loadingScripts.total) {
|
||
window.loadingScripts.loadingComplete = true;
|
||
window.loadingScripts.finishDynamicLoading();
|
||
}
|
||
}, CONFIG.maxLoadingTime);
|
||
|
||
// Проверяем, не зависла ли загрузка
|
||
setTimeout(() => {
|
||
if (window.loadingScripts.loaded < window.loadingScripts.total) {
|
||
const unloadedScripts = window.loadingScripts.scripts.filter(src =>
|
||
!window.loadingScripts.retryCounts[src] ||
|
||
window.loadingScripts.retryCounts[src] < CONFIG.maxRetries
|
||
);
|
||
|
||
unloadedScripts.forEach(src => {
|
||
window.loadingScripts.retryScript(src);
|
||
});
|
||
}
|
||
}, 5000);
|
||
});
|
||
})(); |