/
DriveVision
/
Smart-Column-S3
Обзор
Документация
Войти
/
DriveVision
/
Smart-Column-S3
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
data/manual.html
385 строк
14 KB
Claude
Исправить запуск процессов и добавить страницу ручного управления
13 дек 2025, 20:43
13 дек 2025, 20:43
fcf2d1a
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Ручное управление - Smart Column S3</title> <link rel="stylesheet" href="style.css"> <style> .manual-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-top: 20px; } .control-panel { background: var(--card-bg); border-radius: 12px; padding: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } .slider-container { margin: 15px 0; } .slider { width: 100%; height: 8px; border-radius: 4px; background: var(--primary-color); outline: none; opacity: 0.7; transition: opacity 0.2s; } .slider:hover { opacity: 1; } .value-display { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; font-size: 18px; font-weight: 600; } .temp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; margin-top: 15px; } .temp-card { background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); padding: 15px; border-radius: 8px; color: white; text-align: center; } .temp-value { font-size: 28px; font-weight: bold; margin: 5px 0; } .temp-label { font-size: 14px; opacity: 0.9; } .valve-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 15px; } .valve-btn { padding: 15px; border-radius: 8px; border: 2px solid var(--primary-color); background: white; cursor: pointer; transition: all 0.3s; font-size: 16px; } .valve-btn.active { background: var(--primary-color); color: white; } .status-bar { background: var(--card-bg); padding: 15px; border-radius: 8px; margin-bottom: 20px; display: flex; justify-content: space-between; align-items: center; } .status-indicator { display: flex; align-items: center; gap: 10px; } .status-dot { width: 12px; height: 12px; border-radius: 50%; background: #ccc; } .status-dot.running { background: #4CAF50; animation: pulse 2s infinite; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } } </style> </head> <body> <div class="container"> <!-- Шапка --> <div class="header"> <h1>🎛️ Ручное управление</h1> <a href="index.html" class="btn btn-secondary">← Назад</a> </div> <!-- Статус бар --> <div class="status-bar"> <div class="status-indicator"> <div class="status-dot" id="process-status"></div> <span id="status-text">Процесс остановлен</span> </div> <div> <button class="btn btn-success" id="btn-start" onclick="startManualProcess()">▶️ Запустить</button> <button class="btn btn-danger" id="btn-stop" onclick="stopProcess()" style="display:none;">⏹️ Остановить</button> </div> </div> <!-- Панели управления --> <div class="manual-grid"> <!-- Температуры --> <div class="control-panel"> <h2>🌡️ Температуры</h2> <div class="temp-grid"> <div class="temp-card"> <div class="temp-label">Куб</div> <div class="temp-value" id="temp-cube">--</div> <div class="temp-label">°C</div> </div> <div class="temp-card"> <div class="temp-label">Колонна</div> <div class="temp-value" id="temp-column">--</div> <div class="temp-label">°C</div> </div> <div class="temp-card"> <div class="temp-label">Дефлегматор</div> <div class="temp-value" id="temp-defleg">--</div> <div class="temp-label">°C</div> </div> <div class="temp-card"> <div class="temp-label">Продукт</div> <div class="temp-value" id="temp-product">--</div> <div class="temp-label">°C</div> </div> </div> </div> <!-- Нагреватель --> <div class="control-panel"> <h2>🔥 Нагреватель</h2> <div class="slider-container"> <div class="value-display"> <span>Мощность:</span> <span id="heater-display">0%</span> </div> <input type="range" min="0" max="100" value="0" class="slider" id="heater-slider" oninput="updateHeaterManual(this.value)"> </div> <div style="margin-top: 15px;"> <div style="display: flex; justify-content: space-between; font-size: 14px;"> <span>Текущая: <strong id="heater-current">0 Вт</strong></span> <span>Макс: <strong>3000 Вт</strong></span> </div> </div> </div> <!-- Насос --> <div class="control-panel"> <h2>💧 Насос отбора</h2> <div class="slider-container"> <div class="value-display"> <span>Скорость:</span> <span id="pump-display">0 мл/ч</span> </div> <input type="range" min="0" max="300" value="0" class="slider" id="pump-slider" oninput="updatePumpManual(this.value)"> </div> <div style="margin-top: 15px;"> <div style="font-size: 14px;"> Отобрано: <strong id="collected-volume">0 мл</strong> </div> </div> </div> <!-- Клапаны --> <div class="control-panel"> <h2>🔀 Клапаны</h2> <div class="valve-grid"> <button class="valve-btn" id="valve-heads" onclick="toggleValveManual('heads')"> 🥃 Головы </button> <button class="valve-btn" id="valve-body" onclick="toggleValveManual('body')"> 🍷 Тело </button> <button class="valve-btn" id="valve-tails" onclick="toggleValveManual('tails')"> 🧪 Хвосты </button> <button class="valve-btn" id="valve-waste" onclick="toggleValveManual('waste')"> ♻️ Отходы </button> </div> </div> </div> <!-- Лог --> <div class="control-panel" style="margin-top: 20px;"> <h2>📋 Журнал</h2> <div id="log-container" style="max-height: 200px; overflow-y: auto; font-family: monospace; font-size: 12px;"> </div> </div> </div> <script> let processRunning = false; let updateInterval = null; // Инициализация при загрузке window.addEventListener('load', () => { loadStatus(); startUpdates(); addLog('✅ Страница ручного управления загружена'); }); // Запуск процесса async function startManualProcess() { try { const response = await fetch('/api/process/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'manual_rect' }) }); if (response.ok) { processRunning = true; updateUI(); addLog('✅ Ручной режим запущен', 'success'); } else { const error = await response.text(); addLog('❌ Ошибка запуска: ' + error, 'error'); } } catch (e) { addLog('❌ Ошибка: ' + e.message, 'error'); } } // Остановка процесса async function stopProcess() { if (!confirm('Остановить процесс?')) return; try { const response = await fetch('/api/process/stop', { method: 'POST' }); if (response.ok) { processRunning = false; updateUI(); addLog('✅ Процесс остановлен', 'warning'); } else { addLog('❌ Ошибка остановки', 'error'); } } catch (e) { addLog('❌ Ошибка: ' + e.message, 'error'); } } // Обновление мощности нагревателя function updateHeaterManual(value) { document.getElementById('heater-display').textContent = value + '%'; // TODO: отправить команду на сервер addLog(`🔥 Мощность нагревателя: ${value}%`); } // Обновление скорости насоса function updatePumpManual(value) { document.getElementById('pump-display').textContent = value + ' мл/ч'; // TODO: отправить команду на сервер addLog(`💧 Скорость насоса: ${value} мл/ч`); } // Переключение клапана function toggleValveManual(valve) { const btn = document.getElementById('valve-' + valve); const isActive = btn.classList.contains('active'); // Деактивировать все клапаны document.querySelectorAll('.valve-btn').forEach(b => b.classList.remove('active')); // Активировать выбранный if (!isActive) { btn.classList.add('active'); addLog(`🔀 Клапан "${valve}" открыт`); } else { addLog(`🔀 Все клапаны закрыты`); } } // Обновление UI function updateUI() { const statusDot = document.getElementById('process-status'); const statusText = document.getElementById('status-text'); const btnStart = document.getElementById('btn-start'); const btnStop = document.getElementById('btn-stop'); if (processRunning) { statusDot.classList.add('running'); statusText.textContent = 'Процесс запущен (ручной режим)'; btnStart.style.display = 'none'; btnStop.style.display = 'inline-block'; } else { statusDot.classList.remove('running'); statusText.textContent = 'Процесс остановлен'; btnStart.style.display = 'inline-block'; btnStop.style.display = 'none'; } } // Загрузка статуса async function loadStatus() { try { const response = await fetch('/api/status'); const data = await response.json(); // Обновить температуры document.getElementById('temp-cube').textContent = data.temps?.cube?.toFixed(1) || '--'; document.getElementById('temp-column').textContent = data.temps?.column?.toFixed(1) || '--'; document.getElementById('temp-defleg').textContent = data.temps?.defleg?.toFixed(1) || '--'; document.getElementById('temp-product').textContent = data.temps?.product?.toFixed(1) || '--'; // Обновить состояние процесса processRunning = data.mode !== 'idle'; updateUI(); } catch (e) { console.error('Ошибка загрузки статуса:', e); } } // Периодическое обновление function startUpdates() { updateInterval = setInterval(loadStatus, 2000); } // Добавить запись в лог function addLog(message, level = 'info') { const container = document.getElementById('log-container'); const time = new Date().toLocaleTimeString(); const colors = { info: '#2196F3', success: '#4CAF50', warning: '#FF9800', error: '#F44336' }; const color = colors[level] || colors.info; const entry = document.createElement('div'); entry.style.color = color; entry.textContent = `[${time}] ${message}`; container.appendChild(entry); container.scrollTop = container.scrollHeight; // Ограничить количество записей while (container.children.length > 50) { container.removeChild(container.firstChild); } } </script> </body> </html>