загрузка сервиса и рандом
This commit is contained in:
committed by
GitVerse
parent
01238e93d9
commit
97609b3ba7
848
public/loading-start.js
Normal file
848
public/loading-start.js
Normal file
@@ -0,0 +1,848 @@
|
||||
// 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);
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user