/
liatana
/
Risk
Обзор
Документация
Войти
/
liatana
/
Risk
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
risk_bot.html
505 строк
25 KB
liatana
Create: risk_bot.html
15 июн 2026, 11:47
Верифицирован
15 июн 2026, 11:47
26b97f9
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Чат-бот: Карта профессиональных рисков</title> <style> * { box-sizing: border-box; } body { font-family: 'Segoe UI', Arial, sans-serif; background: #f0f4f8; margin: 0; padding: 20px; height: 100vh; display: flex; justify-content: center; align-items: center; } .chat-container { max-width: 1200px; width: 100%; height: 90vh; background: white; border-radius: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); display: flex; flex-direction: column; overflow: hidden; } .chat-header { background: linear-gradient(135deg, #2c7da0, #1f5068); color: white; padding: 15px 20px; text-align: center; } .chat-header h2 { margin: 0; font-size: 1.3rem; } .chat-header p { margin: 5px 0 0; font-size: 0.8rem; opacity: 0.9; } .chat-messages { flex: 1; overflow-y: auto; padding: 20px; background: #f9fafb; } .message { margin-bottom: 15px; display: flex; flex-direction: column; } .bot-message { align-items: flex-start; } .user-message { align-items: flex-end; } .message-bubble { max-width: 70%; padding: 10px 15px; border-radius: 18px; font-size: 14px; line-height: 1.4; } .bot-message .message-bubble { background: #e9f0f5; color: #1f2d3d; border-bottom-left-radius: 5px; } .user-message .message-bubble { background: #2c7da0; color: white; border-bottom-right-radius: 5px; } .options { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; } .option-btn { background: #61a5c2; color: white; border: none; border-radius: 20px; padding: 8px 16px; cursor: pointer; font-size: 13px; transition: all 0.2s; } .option-btn:hover { background: #2c7da0; transform: scale(1.02); } .chat-input-area { padding: 15px; background: white; border-top: 1px solid #ddd; display: flex; gap: 10px; } .chat-input { flex: 1; padding: 10px; border: 1px solid #ccc; border-radius: 25px; font-size: 14px; outline: none; } .send-btn { background: #2c7da0; color: white; border: none; border-radius: 25px; padding: 10px 20px; cursor: pointer; } .send-btn:hover { background: #1f5068; } .card-preview { background: #fef9e6; border-left: 4px solid #f4a261; padding: 15px; margin-top: 15px; border-radius: 10px; max-height: 400px; overflow-y: auto; font-size: 12px; } .download-btn { background: #4a6fa5; margin-top: 10px; padding: 8px 16px; border: none; border-radius: 20px; color: white; cursor: pointer; } table { width: 100%; border-collapse: collapse; font-size: 11px; } th, td { border: 1px solid #aaa; padding: 5px; text-align: left; vertical-align: top; } th { background: #e9f0f5; } .risk-high { background: #ffe0e0; } .risk-medium { background: #fff3e0; } .risk-low { background: #e0ffe0; } </style> <script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js"></script> </head> <body> <div class="chat-container"> <div class="chat-header"> <h2>🤖 Помощник по оценке профессиональных рисков</h2> <p>Задайте параметры, и я сформирую карту рисков по Приказу №926</p> </div> <div class="chat-messages" id="chatMessages"> <div class="message bot-message"> <div class="message-bubble"> Здравствуйте! Я помогу вам разработать карту профессиональных рисков.<br> Давайте начнём. Выберите методику оценки: </div> <div class="options" id="methodOptions"></div> </div> </div> <div class="chat-input-area"> <input type="text" class="chat-input" id="chatInput" placeholder="Введите ответ..." disabled> <button class="send-btn" id="sendBtn" disabled>➤ Отправить</button> </div> </div> <script src="https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js"></script> <script> // ==================== БАЗА РИСКОВ ==================== const riskDatabase = { categories: { transport: { keywords: ['транспортировщик', 'водитель', 'шофер', 'погрузчик', 'крановщик', 'машинист'], hazards: [ { name: 'Наезд движущихся механизмов', recommendation: 'Установить звуковую сигнализацию, ограничить скорость, провести инструктаж', level: 'Высокий' }, { name: 'Повышенная подвижность воздуха (сквозняки)', recommendation: 'Защитные экраны, тёплая спецодежда', level: 'Средний' }, { name: 'Недостаточная освещенность', recommendation: 'Дополнительное освещение, замена ламп', level: 'Средний' }, { name: 'Физические перегрузки', recommendation: 'Регламентированные перерывы, эргономичное сиденье', level: 'Средний' }, { name: 'Повышенный шум и вибрация', recommendation: 'СИЗ (наушники, виброперчатки), медосмотры', level: 'Высокий' }, { name: 'Риск ДТП', recommendation: 'Предрейсовый медосмотр, тахографы, контроль отдыха', level: 'Высокий' } ] }, loading: { keywords: ['грузчик', 'такелажник', 'склад', 'комплектовщик', 'кладовщик'], hazards: [ { name: 'Падение предметов с высоты', recommendation: 'Правильная укладка штабелей, страховочные пояса', level: 'Высокий' }, { name: 'Острые кромки, заусенцы', recommendation: 'СИЗ рук (краги), контроль тары', level: 'Средний' }, { name: 'Превышение допустимой тяжести', recommendation: 'Механизация погрузки, нормирование веса (15/30 кг)', level: 'Высокий' }, { name: 'Недостаточная освещенность склада', recommendation: 'Установка светильников, очистка окон', level: 'Средний' }, { name: 'Травмы при работе с гидротележкой', recommendation: 'Инструктаж, ограничение скорости', level: 'Средний' } ] }, production: { keywords: ['сварщик', 'станочник', 'фрезеровщик', 'токарь', 'электромонтер', 'слесарь'], hazards: [ { name: 'Химические вредные вещества', recommendation: 'Вытяжная вентиляция, СИЗ органов дыхания', level: 'Высокий' }, { name: 'Повышенный шум и вибрация', recommendation: 'Антивибрационные рукоятки, наушники', level: 'Высокий' }, { name: 'Риск поражения электрическим током', recommendation: 'Заземление, изоляция, II группа допуска', level: 'Высокий' }, { name: 'Термические ожоги', recommendation: 'Экраны, теплоизоляция, спецодежда', level: 'Средний' }, { name: 'Травмы движущимися частями', recommendation: 'Ограждения, блокировки, инструктаж', level: 'Высокий' } ] }, office: { keywords: ['бухгалтер', 'экономист', 'менеджер', 'секретарь', 'специалист', 'юрист', 'кадровик'], hazards: [ { name: 'Длительное статическое напряжение', recommendation: 'Эргономичное кресло, гимнастика каждые 45 мин', level: 'Средний' }, { name: 'Перенапряжение зрения (ПК)', recommendation: 'Регулируемая яркость, перерывы 5 мин/час', level: 'Средний' }, { name: 'Психоэмоциональные перегрузки', recommendation: 'Комната разгрузки, нормирование задач', level: 'Средний' }, { name: 'Несоответствие микроклимата', recommendation: 'Кондиционирование, увлажнители', level: 'Низкий' }, { name: 'Ненормированный день', recommendation: 'Чёткий график, компенсация переработок', level: 'Средний' } ] }, manager: { keywords: ['директор', 'начальник', 'руководитель', 'управляющий', 'заместитель'], hazards: [ { name: 'Нервно-психическое напряжение', recommendation: 'Делегирование, психологическая поддержка', level: 'Высокий' }, { name: 'Ненормированный день, переработки', recommendation: 'Контроль рабочего времени, компенсация', level: 'Высокий' }, { name: 'Коррупционные риски', recommendation: 'Антикоррупционная политика', level: 'Средний' }, { name: 'Статические нагрузки', recommendation: 'Перерывы, физкультминутки', level: 'Средний' }, { name: 'Профессиональное выгорание', recommendation: 'Ротация, наставничество, психолог', level: 'Высокий' } ] } }, getCategory: function(positionName) { const name = positionName.toLowerCase(); for (const [cat, data] of Object.entries(this.categories)) { for (const kw of data.keywords) { if (name.includes(kw)) return cat; } } return 'office'; }, getHazards: function(positionName) { const category = this.getCategory(positionName); return this.categories[category].hazards; } }; // Состояние диалога let state = { step: 'method', // method, company, chairmanName, chairmanPost, member1Name, member1Post, member2Name, member2Post, positions data: { method: '', company: '', chairmanName: '', chairmanPost: '', member1Name: '', member1Post: '', member2Name: '', member2Post: '', positions: [] }, tempPosition: '' }; const methods = [ "Метод анализа опасностей и операций (HAZOP)", "Метод «Что будет, если?» (What-If)", "Метод проверочного листа", "Метод матрицы оценки рисков", "Метод FMEA", "Метод дерева событий", "Метод дерева неисправностей", "Метод экспертных оценок", "Метод статистических данных" ]; const chatMessages = document.getElementById('chatMessages'); const chatInput = document.getElementById('chatInput'); const sendBtn = document.getElementById('sendBtn'); // Функции отображения function addBotMessage(text, withOptions = null) { const messageDiv = document.createElement('div'); messageDiv.className = 'message bot-message'; const bubble = document.createElement('div'); bubble.className = 'message-bubble'; bubble.innerHTML = text; messageDiv.appendChild(bubble); if (withOptions) { const optionsDiv = document.createElement('div'); optionsDiv.className = 'options'; withOptions.forEach(opt => { const btn = document.createElement('button'); btn.className = 'option-btn'; btn.textContent = opt; btn.onclick = () => { handleUserInput(opt); optionsDiv.remove(); }; optionsDiv.appendChild(btn); }); messageDiv.appendChild(optionsDiv); } chatMessages.appendChild(messageDiv); chatMessages.scrollTop = chatMessages.scrollHeight; } function addUserMessage(text) { const messageDiv = document.createElement('div'); messageDiv.className = 'message user-message'; const bubble = document.createElement('div'); bubble.className = 'message-bubble'; bubble.textContent = text; messageDiv.appendChild(bubble); chatMessages.appendChild(messageDiv); chatMessages.scrollTop = chatMessages.scrollHeight; } function addCardPreview(cardHtml) { const messageDiv = document.createElement('div'); messageDiv.className = 'message bot-message'; const bubble = document.createElement('div'); bubble.className = 'message-bubble'; bubble.innerHTML = '<strong>📋 Сформированная карта профессиональных рисков:</strong><div class="card-preview">' + cardHtml + '</div><button class="download-btn" id="inlineDownloadBtn">📄 Скачать DOC (Word, альбомная ориентация)</button>'; messageDiv.appendChild(bubble); chatMessages.appendChild(messageDiv); chatMessages.scrollTop = chatMessages.scrollHeight; document.getElementById('inlineDownloadBtn').onclick = () => downloadDocx(cardHtml); } function handleUserInput(input) { addUserMessage(input); switch(state.step) { case 'method': state.data.method = input; state.step = 'company'; addBotMessage('Отлично! Укажите полное название организации (например, ООО «Ромашка»):'); break; case 'company': state.data.company = input; state.step = 'chairmanName'; addBotMessage('Укажите ФИО председателя комиссии:'); break; case 'chairmanName': state.data.chairmanName = input; state.step = 'chairmanPost'; addBotMessage('Укажите должность председателя комиссии:'); break; case 'chairmanPost': state.data.chairmanPost = input; state.step = 'member1Name'; addBotMessage('Укажите ФИО первого члена комиссии:'); break; case 'member1Name': state.data.member1Name = input; state.step = 'member1Post'; addBotMessage('Укажите должность первого члена комиссии:'); break; case 'member1Post': state.data.member1Post = input; state.step = 'member2Name'; addBotMessage('Укажите ФИО второго члена комиссии:'); break; case 'member2Name': state.data.member2Name = input; state.step = 'member2Post'; addBotMessage('Укажите должность второго члена комиссии:'); break; case 'member2Post': state.data.member2Post = input; state.step = 'position'; addBotMessage('Теперь добавим должности для оценки. Введите название должности (например: Транспортировщик, Бухгалтер, Сварщик). После каждого ввода я спрошу, добавить ещё одну или закончить.'); break; case 'position': if (input.toLowerCase() === 'готово' || input.toLowerCase() === 'закончить' || input === '0') { if (state.data.positions.length === 0) { addBotMessage('Добавьте хотя бы одну должность. Введите название должности:'); return; } generateCard(); } else { state.data.positions.push(input); addBotMessage(`✅ Должность "${input}" добавлена. Введите следующую должность или напишите "Готово" для завершения:`); } break; } } function generateCard() { const data = state.data; const date = new Date().toLocaleDateString('ru-RU', { year: 'numeric', month: 'long', day: 'numeric' }); let allRows = ''; data.positions.forEach(pos => { const hazards = riskDatabase.getHazards(pos); allRows += `<tr><td colspan="3" style="background:#d9e8f5; font-weight:bold;">📌 Должность: ${escapeHtml(pos)}</td></tr>`; hazards.forEach(h => { let levelClass = h.level === 'Высокий' ? 'risk-high' : (h.level === 'Средний' ? 'risk-medium' : 'risk-low'); allRows += ` <tr> <td style="width:30%">${escapeHtml(h.name)}</td> <td style="width:50%">${escapeHtml(h.recommendation)}</td> <td style="width:20%; text-align:center;" class="${levelClass}">${escapeHtml(h.level)}</td> </tr> `; }); }); const cardHtml = ` <div style="font-family: 'Times New Roman', serif; font-size: 12pt;"> <h3 style="text-align:center;">КАРТА ОЦЕНКИ ПРОФЕССИОНАЛЬНЫХ РИСКОВ</h3> <p style="text-align:center;">Приказ Минтруда №926 от 28.12.2021</p> <hr> <p><strong>Организация:</strong> ${escapeHtml(data.company)}</p> <p><strong>Дата:</strong> ${date}</p> <p><strong>Методика:</strong> ${escapeHtml(data.method)}</p> <p><strong>Комиссия:</strong><br>Председатель комиссии: ${escapeHtml(data.chairmanName)} (${escapeHtml(data.chairmanPost)})<br>Член комиссии: ${escapeHtml(data.member1Name)} (${escapeHtml(data.member1Post)})<br>Член комиссии: ${escapeHtml(data.member2Name)} (${escapeHtml(data.member2Post)})</p> <hr> <h4>📊 Выявленные опасности и уровни рисков</h4> <table border="1" cellpadding="6" style="border-collapse:collapse; width:100%;"> <thead><tr style="background:#e9f0f5;"><th>Опасность</th><th>Рекомендации</th><th>Уровень риска</th></tr></thead> <tbody>${allRows}</tbody> </table> <div style="margin-top:20px; background:#f5f0e0; padding:12px;"><strong>Заключение:</strong> Для каждой опасности определены меры управления. Рекомендуется реализовать в установленном порядке.</div> <p style="margin-top:30px;"><strong>Подписи:</strong><br>_________________ / ${escapeHtml(data.chairmanName)} /<br>_________________ / ${escapeHtml(data.member1Name)} /<br>_________________ / ${escapeHtml(data.member2Name)} /</p> </div> `; addCardPreview(cardHtml); // Завершаем чат chatInput.disabled = true; sendBtn.disabled = true; } function downloadDocx(cardHtml) { const style = ` <style> @page { size: A4 landscape; margin: 1.5cm; } body { font-family: 'Times New Roman', Arial, sans-serif; font-size: 12pt; margin: 0; padding: 0; } table { border-collapse: collapse; width: 100%; margin: 15px 0; } th, td { border: 1px solid #000; padding: 6px; vertical-align: top; } th { background: #e9f0f5; } .risk-high { background: #ffe0e0; } .risk-medium { background: #fff3e0; } .risk-low { background: #e0ffe0; } </style> `; const fullDoc = `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Карта_проф_рисков</title>${style}</head><body>${cardHtml}</body></html>`; const blob = new Blob([fullDoc], { type: 'application/msword' }); saveAs(blob, `Карта_проф_рисков_${state.data.method.substring(0,20)}.doc`); } function escapeHtml(str) { if (!str) return ''; return str.replace(/[&<>]/g, m => m === '&' ? '&' : (m === '<' ? '<' : '>')); } // Инициализация: показываем кнопки выбора методики function init() { const optionsDiv = document.getElementById('methodOptions'); methods.forEach(method => { const btn = document.createElement('button'); btn.className = 'option-btn'; btn.textContent = method; btn.onclick = () => { handleUserInput(method); optionsDiv.innerHTML = ''; }; optionsDiv.appendChild(btn); }); // Включаем ввод после выбора методики (но выбор через кнопки, ввод пока не нужен) // Ввод активируется после того, как выбран метод - в handleUserInput переключаем step // Просто слушаем sendBtn sendBtn.onclick = () => { const input = chatInput.value.trim(); if (!input) return; chatInput.value = ''; if (state.step === 'method') { addUserMessage(input); addBotMessage('Пожалуйста, выберите методику из предложенных кнопок выше.'); return; } handleUserInput(input); }; chatInput.onkeypress = (e) => { if (e.key === 'Enter') sendBtn.onclick(); }; } init(); </script> </body> </html>