From 092c01dff4f5d9f10b1940073b188cf7554634b2 Mon Sep 17 00:00:00 2001 From: Fovway Date: Mon, 20 Oct 2025 07:24:31 +0700 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=B8=D0=BD=D0=B4=D0=B5=D0=BA=D1=81?= =?UTF-8?q?=D1=8B=20=D0=B4=D0=BB=D1=8F=20=D0=BE=D0=BF=D1=82=D0=B8=D0=BC?= =?UTF-8?q?=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B7=D0=B0=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=81=D0=BE=D0=B2=20=D0=B8=20=D1=83=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D1=88=D0=B5=D0=BD=D0=B0=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=82=D0=BE=D0=BA?= =?UTF-8?q?=20=D1=81=20=D0=B8=D0=B7=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Реализована функция создания индексов для таблиц в базе данных, что улучшает производительность запросов - Обновлены API для получения и поиска заметок, теперь они возвращают изображения, связанные с заметками - Добавлен кэш для заметок на клиенте с возможностью принудительной перезагрузки - Внедрен индикатор загрузки при загрузке заметок для улучшения пользовательского опыта --- public/app.js | 88 ++++++++++++++++++++++++++++++++++++++++---- server.js | 100 +++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 171 insertions(+), 17 deletions(-) diff --git a/public/app.js b/public/app.js index af87151..ec8b8e1 100644 --- a/public/app.js +++ b/public/app.js @@ -33,6 +33,8 @@ let selectedDateFilter = null; let selectedTagFilter = null; let searchQuery = ""; let searchResults = []; +let notesCache = null; // Кэш для заметок +let lastLoadTime = 0; // Время последней загрузки // Функция для получения текущей даты и времени function getFormattedDateTime() { @@ -473,7 +475,24 @@ async function deleteNoteImage(noteId, imageId) { } // Функция для загрузки заметок с сервера -async function loadNotes() { +async function loadNotes(forceReload = false) { + const now = Date.now(); + const CACHE_DURATION = 30000; // 30 секунд кэширования + + // Используем кэш, если он не устарел и не требуется принудительная перезагрузка + if (!forceReload && notesCache && (now - lastLoadTime) < CACHE_DURATION) { + allNotes = notesCache; + await renderNotes(notesCache); + renderCalendar(); + renderTags(); + renderCalendarMobile(); + renderTagsMobile(); + return; + } + + // Показываем индикатор загрузки + showLoadingIndicator(); + try { const response = await fetch("/api/notes"); if (!response.ok) { @@ -481,6 +500,8 @@ async function loadNotes() { } const notes = await response.json(); allNotes = notes; // Сохраняем все заметки в глобальную переменную + notesCache = notes; // Сохраняем в кэш + lastLoadTime = now; await renderNotes(notes); renderCalendar(); // Обновляем календарь после загрузки заметок renderTags(); // Обновляем теги после загрузки заметок @@ -489,6 +510,59 @@ async function loadNotes() { } catch (error) { console.error("Ошибка:", error); notesList.innerHTML = "

Ошибка загрузки заметок

"; + } finally { + // Скрываем индикатор загрузки + hideLoadingIndicator(); + } +} + +// Функция для показа индикатора загрузки +function showLoadingIndicator() { + if (!document.getElementById("loading-indicator")) { + const loadingDiv = document.createElement("div"); + loadingDiv.id = "loading-indicator"; + loadingDiv.innerHTML = ` +
+
+
+ Загрузка заметок... +
+
+ + `; + document.body.appendChild(loadingDiv); + } +} + +// Функция для скрытия индикатора загрузки +function hideLoadingIndicator() { + const loadingIndicator = document.getElementById("loading-indicator"); + if (loadingIndicator) { + loadingIndicator.remove(); } } @@ -591,8 +665,8 @@ async function renderNotes(notes) { const parsedContent = marked.parse(contentWithClickableTags); - // Получаем изображения заметки - const noteImages = await getNoteImages(note.id); + // Используем изображения, которые уже пришли с заметкой + const noteImages = Array.isArray(note.images) ? note.images : []; let imagesHtml = ""; if (noteImages.length > 0) { @@ -703,7 +777,7 @@ function addNoteEventListeners() { } // Перезагружаем заметки - await loadNotes(); + await loadNotes(true); } catch (error) { console.error("Ошибка:", error); alert("Ошибка удаления заметки"); @@ -802,7 +876,7 @@ function addNoteEventListeners() { } // Перезагружаем заметки - await loadNotes(); + await loadNotes(true); } catch (error) { console.error("Ошибка:", error); alert("Ошибка сохранения заметки"); @@ -909,7 +983,7 @@ function addImageEventListeners() { if (noteId && imageId && confirm("Вы уверены, что хотите удалить это изображение?")) { const success = await deleteNoteImage(noteId, imageId); if (success) { - await loadNotes(); // Перезагружаем заметки + await loadNotes(true); // Перезагружаем заметки } else { alert("Ошибка удаления изображения"); } @@ -957,7 +1031,7 @@ async function saveNote() { selectedImages = []; updateImagePreview(); imageInput.value = ""; - await loadNotes(); + await loadNotes(true); } catch (error) { console.error("Ошибка:", error); alert("Ошибка сохранения заметки"); diff --git a/server.js b/server.js index 8e52ebe..0367f5f 100644 --- a/server.js +++ b/server.js @@ -224,10 +224,32 @@ function createTables() { console.error("Ошибка создания таблицы пользователей:", err.message); } else { console.log("Таблица пользователей готова"); + createIndexes(); } }); } +// Создание индексов для оптимизации запросов +function createIndexes() { + const indexes = [ + "CREATE INDEX IF NOT EXISTS idx_notes_user_id ON notes(user_id)", + "CREATE INDEX IF NOT EXISTS idx_notes_created_at ON notes(created_at)", + "CREATE INDEX IF NOT EXISTS idx_notes_date ON notes(date)", + "CREATE INDEX IF NOT EXISTS idx_note_images_note_id ON note_images(note_id)", + "CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)" + ]; + + indexes.forEach((indexSql, i) => { + db.run(indexSql, (err) => { + if (err) { + console.error(`Ошибка создания индекса ${i + 1}:`, err.message); + } else { + console.log(`Индекс ${i + 1} создан успешно`); + } + }); + }); +} + // Middleware для аутентификации function requireAuth(req, res, next) { if (req.session.authenticated) { @@ -397,52 +419,110 @@ app.get("/notes", requireAuth, (req, res) => { res.sendFile(path.join(__dirname, "public", "notes.html")); }); -// API для поиска заметок (должен быть ПЕРЕД /api/notes/:id) +// API для поиска заметок с изображениями (должен быть ПЕРЕД /api/notes/:id) app.get("/api/notes/search", requireAuth, (req, res) => { const { q, tag, date } = req.query; - let sql = "SELECT * FROM notes WHERE user_id = ?"; + let whereClause = "WHERE n.user_id = ?"; let params = [req.session.userId]; // Поиск по тексту if (q && q.trim()) { - sql += " AND content LIKE ?"; + whereClause += " AND n.content LIKE ?"; params.push(`%${q.trim()}%`); } // Поиск по тегу if (tag && tag.trim()) { - sql += " AND content LIKE ?"; + whereClause += " AND n.content LIKE ?"; params.push(`%#${tag.trim()}%`); } // Поиск по дате if (date && date.trim()) { - sql += " AND date = ?"; + whereClause += " AND n.date = ?"; params.push(date.trim()); } - sql += " ORDER BY created_at DESC"; + const sql = ` + SELECT + n.*, + CASE + WHEN COUNT(ni.id) = 0 THEN '[]' + ELSE json_group_array( + json_object( + 'id', ni.id, + 'filename', ni.filename, + 'original_name', ni.original_name, + 'file_path', ni.file_path, + 'file_size', ni.file_size, + 'mime_type', ni.mime_type, + 'created_at', ni.created_at + ) + ) + END as images + FROM notes n + LEFT JOIN note_images ni ON n.id = ni.note_id + ${whereClause} + GROUP BY n.id + ORDER BY n.created_at DESC + `; db.all(sql, params, (err, rows) => { if (err) { console.error("Ошибка поиска заметок:", err.message); return res.status(500).json({ error: "Ошибка сервера" }); } - res.json(rows); + + // Парсим JSON строки изображений + const notesWithImages = rows.map(row => ({ + ...row, + images: row.images === '[]' ? [] : JSON.parse(row.images) + })); + + res.json(notesWithImages); }); }); -// API для получения всех заметок +// API для получения всех заметок с изображениями app.get("/api/notes", requireAuth, (req, res) => { - const sql = "SELECT * FROM notes WHERE user_id = ? ORDER BY created_at ASC"; + const sql = ` + SELECT + n.*, + CASE + WHEN COUNT(ni.id) = 0 THEN '[]' + ELSE json_group_array( + json_object( + 'id', ni.id, + 'filename', ni.filename, + 'original_name', ni.original_name, + 'file_path', ni.file_path, + 'file_size', ni.file_size, + 'mime_type', ni.mime_type, + 'created_at', ni.created_at + ) + ) + END as images + FROM notes n + LEFT JOIN note_images ni ON n.id = ni.note_id + WHERE n.user_id = ? + GROUP BY n.id + ORDER BY n.created_at ASC + `; db.all(sql, [req.session.userId], (err, rows) => { if (err) { console.error("Ошибка получения заметок:", err.message); return res.status(500).json({ error: "Ошибка сервера" }); } - res.json(rows); + + // Парсим JSON строки изображений + const notesWithImages = rows.map(row => ({ + ...row, + images: row.images === '[]' ? [] : JSON.parse(row.images) + })); + + res.json(notesWithImages); }); });