/
Lekant
/
Anketa2
Обзор
Документация
Войти
/
Lekant
/
Anketa2
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
admin.html
255 строк
15 KB
Lekant
create: admin.html
26 май 2026, 11:27
Верифицирован
26 май 2026, 11:27
9544275
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Админ-панель - Конструктор анкет</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: #f0f2f5; padding: 20px; } .tabs { display: flex; gap: 10px; background: white; padding: 0 20px; border-bottom: 1px solid #ddd; } .tab-btn { padding: 15px 30px; background: none; border: none; font-size: 16px; cursor: pointer; } .tab-btn.active { color: #667eea; border-bottom: 3px solid #667eea; } .tab-content { display: none; padding: 30px; max-width: 900px; margin: 0 auto; } .tab-content.active { display: block; } .question-card { background: white; border-radius: 12px; padding: 20px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); cursor: move; } .question-header { display: flex; justify-content: space-between; margin-bottom: 15px; } .question-text { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 8px; margin-bottom: 10px; } .question-type { padding: 8px; border-radius: 6px; margin-bottom: 10px; width: 200px; } .options-container { margin-left: 20px; margin-bottom: 10px; } .option-item { display: flex; gap: 10px; margin-bottom: 5px; align-items: center; } .option-item input { flex: 1; padding: 8px; border: 1px solid #ddd; border-radius: 6px; } .add-option-btn { background: #48bb78; color: white; border: none; padding: 5px 12px; border-radius: 6px; cursor: pointer; } .required-checkbox { margin-top: 10px; display: flex; gap: 8px; align-items: center; } .add-question-btn { background: #667eea; color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; margin-top: 20px; } .icon-btn { background: none; border: none; cursor: pointer; color: #999; font-size: 18px; } .icon-btn:hover { color: #e53e3e; } table { width: 100%; border-collapse: collapse; background: white; border-radius: 12px; overflow: hidden; } th, td { padding: 12px; text-align: left; border-bottom: 1px solid #ddd; } th { background: #f7fafc; } .export-btn { background: #48bb78; color: white; border: none; padding: 10px 20px; border-radius: 8px; cursor: pointer; margin-bottom: 20px; } .settings-group { background: white; padding: 20px; border-radius: 12px; margin-bottom: 20px; } .settings-group input, .settings-group textarea { width: 100%; padding: 10px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 8px; } .save-btn { background: #667eea; color: white; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; } .copy-link-btn { background: #4299e1; margin-left: 10px; } .auto-refresh { font-size: 12px; color: #48bb78; margin-left: 15px; } </style> </head> <body> <div class="tabs"> <button class="tab-btn active" data-tab="questions">📝 Вопросы</button> <button class="tab-btn" data-tab="responses">📊 Ответы (<span id="responseCount">0</span>)</button> <button class="tab-btn" data-tab="settings">⚙️ Настройки</button> </div> <div id="tab-questions" class="tab-content active"> <div id="questionsList"></div> <button class="add-question-btn" onclick="addQuestion()">+ Добавить вопрос</button> <button class="add-question-btn copy-link-btn" onclick="copySurveyLink()" style="background:#4299e1;">🔗 Скопировать ссылку для респондентов</button> </div> <div id="tab-responses" class="tab-content"> <button class="export-btn" onclick="exportCSV()">📥 Экспорт в Excel (CSV)</button> <span class="auto-refresh">🔄 Обновляется автоматически</span> <div id="responsesTable"></div> </div> <div id="tab-settings" class="tab-content"> <div class="settings-group"> <label>Заголовок анкеты</label> <input type="text" id="settingTitle" placeholder="Анкета выпускника"> <label>Описание</label> <textarea id="settingDesc" rows="3"></textarea> <label>Основной цвет</label> <input type="color" id="settingColor" value="#667eea"> <label>Сообщение после отправки</label> <input type="text" id="settingMessage" value="Спасибо! Анкета отправлена."> </div> <button class="save-btn" onclick="saveSettings()">💾 Сохранить настройки</button> </div> <script> let survey = { title: "Анкета выпускника", description: "Пожалуйста, заполните форму", themeColor: "#667eea", submitMessage: "Спасибо! Анкета отправлена.", questions: [], responses: [] }; function loadData() { const saved = localStorage.getItem('survey_constructor'); if (saved) { try { const data = JSON.parse(saved); survey = { ...survey, ...data }; } catch(e) {} } if (!survey.questions || survey.questions.length === 0) { survey.questions = [ { id: 'q1', text: 'Как вас зовут?', type: 'text', required: true, placeholder: 'Иванов Иван' }, { id: 'q2', text: 'Сфера интересов?', type: 'radio', required: true, options: ['IT', 'Маркетинг', 'Дизайн'] } ]; } if (!survey.responses) survey.responses = []; saveToLocal(); renderQuestions(); updateResponseCount(); } function saveToLocal() { localStorage.setItem('survey_constructor', JSON.stringify(survey)); localStorage.setItem('survey_for_respondents', JSON.stringify({ title: survey.title, description: survey.description, themeColor: survey.themeColor, submitMessage: survey.submitMessage, questions: survey.questions })); } function renderQuestions() { const container = document.getElementById('questionsList'); if (!survey.questions) survey.questions = []; container.innerHTML = survey.questions.map((q, idx) => ` <div class="question-card" draggable="true" data-idx="${idx}"> <div class="question-header"> <strong>Вопрос ${idx+1}</strong> <button class="icon-btn" onclick="deleteQuestion(${idx})">🗑</button> </div> <input class="question-text" value="${escapeHtml(q.text)}" onchange="updateQuestion(${idx}, 'text', this.value)"> <select class="question-type" onchange="updateQuestion(${idx}, 'type', this.value)"> <option value="text" ${q.type==='text'?'selected':''}>📝 Строка</option> <option value="textarea" ${q.type==='textarea'?'selected':''}>📄 Абзац</option> <option value="radio" ${q.type==='radio'?'selected':''}>🔘 Один ответ</option> <option value="checkbox" ${q.type==='checkbox'?'selected':''}>☑️ Несколько ответов</option> <option value="email" ${q.type==='email'?'selected':''}>📧 Email</option> <option value="tel" ${q.type==='tel'?'selected':''}>📞 Телефон</option> <option value="number" ${q.type==='number'?'selected':''}>🔢 Число</option> </select> ${(q.type==='radio' || q.type==='checkbox') ? ` <div class="options-container"> ${(q.options || ['Вариант 1']).map((opt, optIdx) => ` <div class="option-item"> <input value="${escapeHtml(opt)}" onchange="updateOption(${idx}, ${optIdx}, this.value)"> <button class="icon-btn" onclick="deleteOption(${idx}, ${optIdx})">✗</button> </div> `).join('')} <button class="add-option-btn" onclick="addOption(${idx})">+ Добавить вариант</button> </div> ` : (q.type==='text'||q.type==='textarea'||q.type==='number'||q.type==='email'||q.type==='tel') ? ` <input type="text" placeholder="Плейсхолдер" value="${escapeHtml(q.placeholder||'')}" onchange="updateQuestion(${idx}, 'placeholder', this.value)" style="margin-top:10px;"> ` : ''} <div class="required-checkbox"> <input type="checkbox" ${q.required?'checked':''} onchange="updateQuestion(${idx}, 'required', this.checked)"> <label>Обязательный вопрос</label> </div> </div> `).join(''); addDragDrop(); } function escapeHtml(str) { if(!str) return ''; return str.replace(/[&<>]/g, function(m){return m==='&'?'&':m==='<'?'<':'>';}); } function updateQuestion(idx, field, val) { survey.questions[idx][field] = val; saveToLocal(); renderQuestions(); } function updateOption(qIdx, optIdx, val) { survey.questions[qIdx].options[optIdx] = val; saveToLocal(); renderQuestions(); } function addOption(qIdx) { if(!survey.questions[qIdx].options) survey.questions[qIdx].options = []; survey.questions[qIdx].options.push('Новый вариант'); saveToLocal(); renderQuestions(); } function deleteOption(qIdx, optIdx) { survey.questions[qIdx].options.splice(optIdx,1); saveToLocal(); renderQuestions(); } function deleteQuestion(idx) { survey.questions.splice(idx,1); saveToLocal(); renderQuestions(); } function addQuestion() { survey.questions.push({ id:'q'+Date.now(), text:'Новый вопрос', type:'text', required:false }); saveToLocal(); renderQuestions(); } function addDragDrop() { const cards = document.querySelectorAll('.question-card'); let dragSrc = null; cards.forEach(card => { card.addEventListener('dragstart', (e) => { dragSrc = parseInt(card.dataset.idx); card.style.opacity='0.5'; }); card.addEventListener('dragend', (e) => { card.style.opacity='1'; }); card.addEventListener('dragover', (e) => e.preventDefault()); card.addEventListener('drop', (e) => { e.preventDefault(); const targetIdx = parseInt(card.dataset.idx); if(dragSrc !== null && dragSrc !== targetIdx) { const [moved] = survey.questions.splice(dragSrc,1); survey.questions.splice(targetIdx,0,moved); saveToLocal(); renderQuestions(); } }); }); } function renderResponses() { if(survey.responses.length === 0) { document.getElementById('responsesTable').innerHTML = '<p style="padding:40px;text-align:center;">Нет ответов</p>'; return; } const allKeys = ['timestamp', ...survey.questions.map(q=>q.id)]; let html = '<table><thead><tr>'; allKeys.forEach(k => html += `<th>${k}</th>`); html += '</tr></thead><tbody>'; survey.responses.forEach(resp => { html += '<tr>'; allKeys.forEach(k => html += `<td>${escapeHtml(String(resp[k]||''))}</td>`); html += '</tr>'; }); html += '</tbody></table>'; document.getElementById('responsesTable').innerHTML = html; } function updateResponseCount() { document.getElementById('responseCount').innerText = survey.responses.length; if(document.querySelector('.tab-btn.active')?.dataset.tab === 'responses') renderResponses(); } function exportCSV() { if(survey.responses.length===0) { alert('Нет данных'); return; } const headers = ['timestamp', ...survey.questions.map(q=>q.id)]; const rows = [headers.join(',')]; survey.responses.forEach(r => { const vals = headers.map(h => `"${(r[h]||'').toString().replace(/"/g,'""')}"`); rows.push(vals.join(',')); }); const blob = new Blob(['\uFEFF'+rows.join('\n')], {type:'text/csv'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `responses_${new Date().toISOString().slice(0,19)}.csv`; a.click(); } function loadSettingsToForm() { document.getElementById('settingTitle').value = survey.title || ''; document.getElementById('settingDesc').value = survey.description || ''; document.getElementById('settingColor').value = survey.themeColor || '#667eea'; document.getElementById('settingMessage').value = survey.submitMessage || ''; } function saveSettings() { survey.title = document.getElementById('settingTitle').value; survey.description = document.getElementById('settingDesc').value; survey.themeColor = document.getElementById('settingColor').value; survey.submitMessage = document.getElementById('settingMessage').value; saveToLocal(); alert('Настройки сохранены!'); } function copySurveyLink() { const url = window.location.origin + window.location.pathname.replace('admin.html', 'survey.html'); navigator.clipboard.writeText(url); alert('Ссылка для респондентов скопирована!\n' + url); } document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(c=>c.classList.remove('active')); btn.classList.add('active'); document.getElementById(`tab-${btn.dataset.tab}`).classList.add('active'); if(btn.dataset.tab === 'responses') renderResponses(); }); }); loadData(); loadSettingsToForm(); setInterval(() => { updateResponseCount(); if(document.querySelector('.tab-btn.active')?.dataset.tab === 'responses') renderResponses(); }, 5000); </script> </body> </html>