Добавлены индексы для оптимизации запросов и улучшена обработка заметок с изображениями

- Реализована функция создания индексов для таблиц в базе данных, что улучшает производительность запросов
- Обновлены API для получения и поиска заметок, теперь они возвращают изображения, связанные с заметками
- Добавлен кэш для заметок на клиенте с возможностью принудительной перезагрузки
- Внедрен индикатор загрузки при загрузке заметок для улучшения пользовательского опыта
This commit is contained in:
Fovway 2025-10-20 07:24:31 +07:00
parent f8692177f9
commit 092c01dff4
2 changed files with 171 additions and 17 deletions

View File

@ -33,6 +33,8 @@ let selectedDateFilter = null;
let selectedTagFilter = null; let selectedTagFilter = null;
let searchQuery = ""; let searchQuery = "";
let searchResults = []; let searchResults = [];
let notesCache = null; // Кэш для заметок
let lastLoadTime = 0; // Время последней загрузки
// Функция для получения текущей даты и времени // Функция для получения текущей даты и времени
function getFormattedDateTime() { 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 { try {
const response = await fetch("/api/notes"); const response = await fetch("/api/notes");
if (!response.ok) { if (!response.ok) {
@ -481,6 +500,8 @@ async function loadNotes() {
} }
const notes = await response.json(); const notes = await response.json();
allNotes = notes; // Сохраняем все заметки в глобальную переменную allNotes = notes; // Сохраняем все заметки в глобальную переменную
notesCache = notes; // Сохраняем в кэш
lastLoadTime = now;
await renderNotes(notes); await renderNotes(notes);
renderCalendar(); // Обновляем календарь после загрузки заметок renderCalendar(); // Обновляем календарь после загрузки заметок
renderTags(); // Обновляем теги после загрузки заметок renderTags(); // Обновляем теги после загрузки заметок
@ -489,6 +510,59 @@ async function loadNotes() {
} catch (error) { } catch (error) {
console.error("Ошибка:", error); console.error("Ошибка:", error);
notesList.innerHTML = "<p>Ошибка загрузки заметок</p>"; notesList.innerHTML = "<p>Ошибка загрузки заметок</p>";
} finally {
// Скрываем индикатор загрузки
hideLoadingIndicator();
}
}
// Функция для показа индикатора загрузки
function showLoadingIndicator() {
if (!document.getElementById("loading-indicator")) {
const loadingDiv = document.createElement("div");
loadingDiv.id = "loading-indicator";
loadingDiv.innerHTML = `
<div style="
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 20px;
border-radius: 10px;
z-index: 10000;
font-size: 16px;
">
<div style="text-align: center;">
<div style="
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #007bff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
"></div>
Загрузка заметок...
</div>
</div>
<style>
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
`;
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 parsedContent = marked.parse(contentWithClickableTags);
// Получаем изображения заметки // Используем изображения, которые уже пришли с заметкой
const noteImages = await getNoteImages(note.id); const noteImages = Array.isArray(note.images) ? note.images : [];
let imagesHtml = ""; let imagesHtml = "";
if (noteImages.length > 0) { if (noteImages.length > 0) {
@ -703,7 +777,7 @@ function addNoteEventListeners() {
} }
// Перезагружаем заметки // Перезагружаем заметки
await loadNotes(); await loadNotes(true);
} catch (error) { } catch (error) {
console.error("Ошибка:", error); console.error("Ошибка:", error);
alert("Ошибка удаления заметки"); alert("Ошибка удаления заметки");
@ -802,7 +876,7 @@ function addNoteEventListeners() {
} }
// Перезагружаем заметки // Перезагружаем заметки
await loadNotes(); await loadNotes(true);
} catch (error) { } catch (error) {
console.error("Ошибка:", error); console.error("Ошибка:", error);
alert("Ошибка сохранения заметки"); alert("Ошибка сохранения заметки");
@ -909,7 +983,7 @@ function addImageEventListeners() {
if (noteId && imageId && confirm("Вы уверены, что хотите удалить это изображение?")) { if (noteId && imageId && confirm("Вы уверены, что хотите удалить это изображение?")) {
const success = await deleteNoteImage(noteId, imageId); const success = await deleteNoteImage(noteId, imageId);
if (success) { if (success) {
await loadNotes(); // Перезагружаем заметки await loadNotes(true); // Перезагружаем заметки
} else { } else {
alert("Ошибка удаления изображения"); alert("Ошибка удаления изображения");
} }
@ -957,7 +1031,7 @@ async function saveNote() {
selectedImages = []; selectedImages = [];
updateImagePreview(); updateImagePreview();
imageInput.value = ""; imageInput.value = "";
await loadNotes(); await loadNotes(true);
} catch (error) { } catch (error) {
console.error("Ошибка:", error); console.error("Ошибка:", error);
alert("Ошибка сохранения заметки"); alert("Ошибка сохранения заметки");

100
server.js
View File

@ -224,10 +224,32 @@ function createTables() {
console.error("Ошибка создания таблицы пользователей:", err.message); console.error("Ошибка создания таблицы пользователей:", err.message);
} else { } else {
console.log("Таблица пользователей готова"); 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 для аутентификации // Middleware для аутентификации
function requireAuth(req, res, next) { function requireAuth(req, res, next) {
if (req.session.authenticated) { if (req.session.authenticated) {
@ -397,52 +419,110 @@ app.get("/notes", requireAuth, (req, res) => {
res.sendFile(path.join(__dirname, "public", "notes.html")); res.sendFile(path.join(__dirname, "public", "notes.html"));
}); });
// API для поиска заметок (должен быть ПЕРЕД /api/notes/:id) // API для поиска заметок с изображениями (должен быть ПЕРЕД /api/notes/:id)
app.get("/api/notes/search", requireAuth, (req, res) => { app.get("/api/notes/search", requireAuth, (req, res) => {
const { q, tag, date } = req.query; 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]; let params = [req.session.userId];
// Поиск по тексту // Поиск по тексту
if (q && q.trim()) { if (q && q.trim()) {
sql += " AND content LIKE ?"; whereClause += " AND n.content LIKE ?";
params.push(`%${q.trim()}%`); params.push(`%${q.trim()}%`);
} }
// Поиск по тегу // Поиск по тегу
if (tag && tag.trim()) { if (tag && tag.trim()) {
sql += " AND content LIKE ?"; whereClause += " AND n.content LIKE ?";
params.push(`%#${tag.trim()}%`); params.push(`%#${tag.trim()}%`);
} }
// Поиск по дате // Поиск по дате
if (date && date.trim()) { if (date && date.trim()) {
sql += " AND date = ?"; whereClause += " AND n.date = ?";
params.push(date.trim()); 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) => { db.all(sql, params, (err, rows) => {
if (err) { if (err) {
console.error("Ошибка поиска заметок:", err.message); console.error("Ошибка поиска заметок:", err.message);
return res.status(500).json({ error: "Ошибка сервера" }); 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) => { 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) => { db.all(sql, [req.session.userId], (err, rows) => {
if (err) { if (err) {
console.error("Ошибка получения заметок:", err.message); console.error("Ошибка получения заметок:", err.message);
return res.status(500).json({ error: "Ошибка сервера" }); 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);
}); });
}); });