/
Cooolprogrammers
/
web-nodejs-labs
Обзор
Документация
Войти
/
Cooolprogrammers
/
web-nodejs-labs
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
public/lab10/task2/manage.html
506 строк
16 KB
darkvoid6@mail.ru
ЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫЫ
18 дек 2025, 07:59
18 дек 2025, 07:59
f2c4f06
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <title>Справочники системы</title> <script src="https://kit.fontawesome.com/a2d9d5a64b.js" crossorigin="anonymous"></script> <style> body { font-family: 'JetBrains Mono', 'Courier New', monospace; padding: 20px; background-color: #111827; color: #10b981; background-image: radial-gradient(circle at 10% 20%, rgba(16, 185, 129, 0.05) 0%, transparent 20%), radial-gradient(circle at 90% 80%, rgba(16, 185, 129, 0.05) 0%, transparent 20%); } h2 { color: #10b981; margin-bottom: 20px; font-size: 1.8rem; border-bottom: 1px solid #374151; padding-bottom: 10px; } /* Стили кнопок */ .btn { padding: 10px 20px; border-radius: 8px; cursor: pointer; border: 1px solid transparent; margin-right: 5px; font-size: 14px; transition: 0.3s; font-family: 'JetBrains Mono', monospace; font-weight: 600; background: transparent; color: #10b981; } .btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3); } .btn-primary { background: transparent; color: #10b981; text-decoration: none; display: inline-block; border-color: #10b981; } .btn-primary:hover { background: rgba(16, 185, 129, 0.1); } .btn-success { background: transparent; color: #10b981; border-color: #10b981; } .btn-success:hover { background: rgba(16, 185, 129, 0.1); } .btn-warning { background: transparent; color: #fbbf24; border-color: #fbbf24; } .btn-warning:hover { background: rgba(251, 191, 36, 0.1); } .btn-danger { background: transparent; color: #ef4444; border-color: #ef4444; } .btn-danger:hover { background: rgba(239, 68, 68, 0.1); } /* Контейнеры */ .ref-selector { margin: 20px 0; background: #1f2937; padding: 20px; border-radius: 12px; box-shadow: 0 8px 32px rgba(16, 185, 129, 0.1); border: 1px solid #374151; backdrop-filter: blur(10px); } .form-container { margin-top: 20px; padding: 25px; border-radius: 12px; background: #1f2937; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2); border-left: 5px solid #10b981; border: 1px solid #374151; } /* Элементы формы */ .form-group { margin-bottom: 20px; } label { display: block; margin-bottom: 8px; font-weight: bold; color: #34d399; text-transform: uppercase; letter-spacing: 0.5px; font-size: 0.85rem; } input, select { width: 100%; padding: 12px; border: 1px solid #374151; border-radius: 8px; box-sizing: border-box; background: #0a0e14; color: #a7f3d0; font-family: 'JetBrains Mono', monospace; transition: all 0.3s; } input:focus, select:focus { outline: none; border-color: #10b981; box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.2); } /* Таблица */ table { width: 100%; border-collapse: collapse; margin-top: 20px; background: #1f2937; border: 1px solid #374151; border-radius: 12px; overflow: hidden; } th, td { border: 1px solid #374151; padding: 12px 15px; text-align: left; } th { background-color: rgba(16, 185, 129, 0.08); color: #34d399; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; } tr:nth-child(even) { background-color: rgba(255, 255, 255, 0.02); } tr:hover { background-color: rgba(16, 185, 129, 0.05); } /* Стили для иконок */ .fa-database, .fa-list { color: #10b981; } select#refPicker { background: #0a0e14; color: #a7f3d0; border: 1px solid #374151; padding: 10px; border-radius: 8px; margin-right: 15px; } #refList { margin-top: 20px; } .ref-list { animation: slideUp 0.5s ease; } @keyframes slideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } </style> </head> <body> <h2><i class="fas fa-database"></i> Управление данными</h2> <a href="index.html" class="btn btn-primary">← Назад к расписанию</a> <div class="ref-selector"> <h3><i class="fas fa-list"></i> Выберите справочник</h3> <select id="refPicker" style="width: auto; display: inline-block; padding: 10px;"> <option value="persons">1. Люди (Сотрудники)</option> <option value="departments">2. Отделы (Кафедры)</option> <option value="positions">3. Должности</option> <option value="teachers">4. Преподаватели</option> <option value="groups">5. Группы</option> <option value="buildings">6. Корпуса</option> <option value="classrooms">7. Аудитории</option> <option value="subjects">8. Дисциплины</option> <option value="lesson-types">9. Типы занятий</option> </select> <button class="btn btn-success" id="newRecordBtn"><i class="fas fa-plus"></i> Новая запись</button> </div> <!-- Место для отображения таблицы --> <div id="refList" class="ref-list"></div> <!-- Форма добавления/редактирования --> <div id="refFormContainer" class="form-container" style="display:none;"> <h3 id="formTitle">Добавить запись</h3> <form id="refForm"></form> <div style="margin-top: 20px;"> <button class="btn btn-success" id="saveRecordBtn">💾 Сохранить</button> <button class="btn btn-warning" id="cancelBtn">Отмена</button> </div> </div> <script> // Настройка API (замените если путь отличается) const API_BASE = "/api/lab10/task2"; const refPicker = document.getElementById("refPicker"); const refList = document.getElementById("refList"); const refFormContainer = document.getElementById("refFormContainer"); const refForm = document.getElementById("refForm"); const formTitle = document.getElementById("formTitle"); let currentRef = "persons"; // По умолчанию открываем людей let currentData = []; let editingId = null; // ============================================================ // КОНФИГУРАЦИЯ ПОЛЕЙ // type: 'select' включает выпадающий список // ref: указывает API-ресурс, откуда брать данные для списка // ============================================================ function getRefFields(ref) { switch (ref) { case "persons": return [ { name: "last_name", label: "Фамилия" }, { name: "first_name", label: "Имя" }, { name: "middle_name", label: "Отчество" } ]; case "departments": return [ { name: "name", label: "Название отдела" } ]; case "positions": return [ { name: "title", label: "Название должности" } ]; case "buildings": return [ { name: "name", label: "Название корпуса" }, { name: "address", label: "Адрес" } ]; case "lesson-types": return [ { name: "name", label: "Тип занятия" } ]; // --- СВЯЗНЫЕ ТАБЛИЦЫ (С ВЫПАДАЮЩИМИ СПИСКАМИ) --- case "teachers": return [ { name: "person_id", label: "Сотрудник", type: "select", ref: "persons" }, { name: "department_id", label: "Отдел", type: "select", ref: "departments" }, { name: "position_id", label: "Должность", type: "select", ref: "positions" } ]; case "groups": return [ { name: "name", label: "Название группы" }, { name: "course", label: "Курс" }, { name: "department_id", label: "Отдел", type: "select", ref: "departments" } ]; case "subjects": return [ { name: "name", label: "Название дисциплины" }, { name: "short_name", label: "Краткое название" }, { name: "department_id", label: "Отдел", type: "select", ref: "departments" } ]; case "classrooms": return [ { name: "room_number", label: "Номер аудитории" }, { name: "building_id", label: "Корпус", type: "select", ref: "buildings" } ]; default: return []; } } // Загрузка основной таблицы данных async function loadReference() { try { refList.innerHTML = "<p>Загрузка...</p>"; const res = await fetch(`${API_BASE}/${currentRef}`); if (!res.ok) throw new Error("Ошибка HTTP: " + res.status); const data = await res.json(); // Удаляем дубликаты (если API глючит и возвращает дубли) const seen = new Set(); currentData = data.filter(item => { if (seen.has(item.id)) return false; seen.add(item.id); return true; }); renderTable(); } catch (e) { refList.innerHTML = `<p style="color:#ef4444">Ошибка: ${e.message}</p>`; } } // Отрисовка HTML таблицы function renderTable() { const fields = getRefFields(currentRef); if (currentData.length === 0) { refList.innerHTML = "<p>Нет записей.</p>"; return; } let html = ` <table> <thead> <tr> <th style="width: 50px;">ID</th> ${fields.map(f => `<th>${f.label}</th>`).join("")} <th style="width: 100px;">Действия</th> </tr> </thead> <tbody> `; currentData.forEach(item => { html += `<tr><td>${item.id}</td>`; fields.forEach(f => { let val = item[f.name]; html += `<td>${val ?? ""}</td>`; }); html += ` <td> <button class="btn btn-primary" onclick="editItem(${item.id})"><i class="fas fa-edit"></i></button> <button class="btn btn-danger" onclick="deleteItem(${item.id})"><i class="fas fa-trash"></i></button> </td> </tr>`; }); html += `</tbody></table>`; refList.innerHTML = html; } // Хелпер: Получить список опций для Select с сервера async function fetchOptions(refName) { try { const res = await fetch(`${API_BASE}/${refName}`); if(!res.ok) return []; return await res.json(); } catch(e) { console.error("Ошибка загрузки справочника " + refName, e); return []; } } // Хелпер: Определить, что показывать в тексте опции function getOptionLabel(item) { if (item.last_name || item.first_name) { return `${item.last_name || ""} ${item.first_name || ""} ${item.middle_name || ""}`.trim(); } if (item.title) return item.title; if (item.name) return item.name; return `ID: ${item.id}`; } // Открытие формы (теперь АСИНХРОННОЕ, так как грузим списки) async function openForm(edit = false) { refFormContainer.style.display = "block"; refForm.innerHTML = "<p>Подготовка формы...</p>"; const fields = getRefFields(currentRef); let values = editingId ? currentData.find(item => item.id == editingId) : {}; let formHtml = ""; for (const f of fields) { let inputHtml = ""; if (f.type === "select") { const options = await fetchOptions(f.ref); let optionsHtml = `<option value="">-- Выберите --</option>`; options.forEach(opt => { const isSelected = (values[f.name] == opt.id) ? "selected" : ""; optionsHtml += `<option value="${opt.id}" ${isSelected}> ${getOptionLabel(opt)} </option>`; }); inputHtml = `<select name="${f.name}" required>${optionsHtml}</select>`; } else { inputHtml = `<input type="text" name="${f.name}" value="${values[f.name] ?? ""}" required>`; } formHtml += ` <div class="form-group"> <label>${f.label}</label> ${inputHtml} </div> `; } refForm.innerHTML = formHtml; } // --- Обработчики событий --- document.getElementById("newRecordBtn").onclick = () => { editingId = null; formTitle.textContent = "Добавить запись в: " + refPicker.options[refPicker.selectedIndex].text; openForm(); }; document.getElementById("cancelBtn").onclick = () => { refFormContainer.style.display = "none"; }; window.editItem = function(id) { editingId = id; formTitle.textContent = "Редактировать запись #" + id; openForm(true); }; window.deleteItem = async function(id) { if (!confirm("Удалить запись?")) return; try { await fetch(`${API_BASE}/${currentRef}/${id}`, { method: "DELETE" }); loadReference(); } catch(e) { alert("Ошибка удаления"); } }; document.getElementById("saveRecordBtn").onclick = async () => { const formData = new FormData(refForm); const data = Object.fromEntries(formData.entries()); for (let key in data) { if (data[key] === "") data[key] = null; } const url = editingId ? `${API_BASE}/${currentRef}/${editingId}` : `${API_BASE}/${currentRef}`; const method = editingId ? "PUT" : "POST"; try { const res = await fetch(url, { method: method, headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) }); if (!res.ok) { const err = await res.json(); alert("Ошибка сохранения: " + (err.error || JSON.stringify(err))); return; } refFormContainer.style.display = "none"; loadReference(); } catch (e) { alert("Ошибка сети: " + e.message); } }; refPicker.onchange = () => { currentRef = refPicker.value; refFormContainer.style.display = "none"; loadReference(); }; // Старт loadReference(); </script> </body> </html>