- Реализована возможность загрузки изображений к заметкам с использованием multer - Добавлены API для загрузки, получения и удаления изображений заметок - Обновлен интерфейс для отображения загруженных изображений и их предварительного просмотра - Добавлены стили для управления изображениями и модального окна просмотра
293 lines
12 KiB
HTML
293 lines
12 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Тест загрузки изображений</title>
|
||
<style>
|
||
body {
|
||
font-family: Arial, sans-serif;
|
||
max-width: 800px;
|
||
margin: 0 auto;
|
||
padding: 20px;
|
||
}
|
||
.test-section {
|
||
margin: 20px 0;
|
||
padding: 20px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
}
|
||
.success {
|
||
color: green;
|
||
}
|
||
.error {
|
||
color: red;
|
||
}
|
||
.info {
|
||
color: blue;
|
||
}
|
||
button {
|
||
background: #007bff;
|
||
color: white;
|
||
border: none;
|
||
padding: 10px 20px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
margin: 5px;
|
||
}
|
||
button:hover {
|
||
background: #0056b3;
|
||
}
|
||
input[type="file"] {
|
||
margin: 10px 0;
|
||
}
|
||
.image-preview {
|
||
max-width: 200px;
|
||
max-height: 200px;
|
||
margin: 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<h1>Тест функциональности загрузки изображений</h1>
|
||
|
||
<div class="test-section">
|
||
<h2>1. Регистрация тестового пользователя</h2>
|
||
<button onclick="registerTestUser()">Зарегистрировать тестового пользователя</button>
|
||
<div id="registerResult"></div>
|
||
</div>
|
||
|
||
<div class="test-section">
|
||
<h2>2. Вход в систему</h2>
|
||
<button onclick="loginTestUser()">Войти как тестовый пользователь</button>
|
||
<div id="loginResult"></div>
|
||
</div>
|
||
|
||
<div class="test-section">
|
||
<h2>3. Создание заметки с изображениями</h2>
|
||
<input type="file" id="imageInput" accept="image/*" multiple>
|
||
<button onclick="createNoteWithImages()">Создать заметку с изображениями</button>
|
||
<div id="noteResult"></div>
|
||
<div id="imagePreviews"></div>
|
||
</div>
|
||
|
||
<div class="test-section">
|
||
<h2>4. Получение изображений заметки</h2>
|
||
<input type="number" id="noteIdInput" placeholder="ID заметки">
|
||
<button onclick="getNoteImages()">Получить изображения</button>
|
||
<div id="getImagesResult"></div>
|
||
</div>
|
||
|
||
<div class="test-section">
|
||
<h2>5. Удаление изображения</h2>
|
||
<input type="number" id="deleteNoteIdInput" placeholder="ID заметки">
|
||
<input type="number" id="deleteImageIdInput" placeholder="ID изображения">
|
||
<button onclick="deleteImage()">Удалить изображение</button>
|
||
<div id="deleteResult"></div>
|
||
</div>
|
||
|
||
<script>
|
||
let authToken = null;
|
||
let selectedFiles = [];
|
||
|
||
// Обработчик выбора файлов
|
||
document.getElementById('imageInput').addEventListener('change', function(e) {
|
||
selectedFiles = Array.from(e.target.files);
|
||
displayImagePreviews();
|
||
});
|
||
|
||
function displayImagePreviews() {
|
||
const container = document.getElementById('imagePreviews');
|
||
container.innerHTML = '';
|
||
|
||
selectedFiles.forEach((file, index) => {
|
||
const reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
const img = document.createElement('img');
|
||
img.src = e.target.result;
|
||
img.className = 'image-preview';
|
||
img.title = file.name;
|
||
container.appendChild(img);
|
||
};
|
||
reader.readAsDataURL(file);
|
||
});
|
||
}
|
||
|
||
async function registerTestUser() {
|
||
try {
|
||
const response = await fetch('/api/register', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
username: 'testuser',
|
||
password: 'testpass123',
|
||
confirmPassword: 'testpass123'
|
||
})
|
||
});
|
||
|
||
const result = await response.json();
|
||
const resultDiv = document.getElementById('registerResult');
|
||
|
||
if (response.ok) {
|
||
resultDiv.innerHTML = '<div class="success">✓ Пользователь успешно зарегистрирован</div>';
|
||
} else {
|
||
resultDiv.innerHTML = `<div class="error">✗ Ошибка: ${result.error}</div>`;
|
||
}
|
||
} catch (error) {
|
||
document.getElementById('registerResult').innerHTML = `<div class="error">✗ Ошибка: ${error.message}</div>`;
|
||
}
|
||
}
|
||
|
||
async function loginTestUser() {
|
||
try {
|
||
const response = await fetch('/api/login', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
username: 'testuser',
|
||
password: 'testpass123'
|
||
})
|
||
});
|
||
|
||
const result = await response.json();
|
||
const resultDiv = document.getElementById('loginResult');
|
||
|
||
if (response.ok) {
|
||
resultDiv.innerHTML = '<div class="success">✓ Успешный вход в систему</div>';
|
||
authToken = 'authenticated'; // В реальном приложении здесь был бы JWT токен
|
||
} else {
|
||
resultDiv.innerHTML = `<div class="error">✗ Ошибка: ${result.error}</div>`;
|
||
}
|
||
} catch (error) {
|
||
document.getElementById('loginResult').innerHTML = `<div class="error">✗ Ошибка: ${error.message}</div>`;
|
||
}
|
||
}
|
||
|
||
async function createNoteWithImages() {
|
||
if (!authToken) {
|
||
document.getElementById('noteResult').innerHTML = '<div class="error">✗ Сначала войдите в систему</div>';
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// Сначала создаем заметку
|
||
const noteResponse = await fetch('/api/notes', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
content: 'Тестовая заметка с изображениями',
|
||
date: new Date().toLocaleDateString('ru-RU'),
|
||
time: new Date().toLocaleTimeString('ru-RU', {hour: '2-digit', minute: '2-digit'})
|
||
})
|
||
});
|
||
|
||
const noteResult = await noteResponse.json();
|
||
|
||
if (!noteResponse.ok) {
|
||
throw new Error(noteResult.error);
|
||
}
|
||
|
||
const noteId = noteResult.id;
|
||
document.getElementById('noteResult').innerHTML = `<div class="success">✓ Заметка создана с ID: ${noteId}</div>`;
|
||
|
||
// Теперь загружаем изображения
|
||
if (selectedFiles.length > 0) {
|
||
const formData = new FormData();
|
||
selectedFiles.forEach(file => {
|
||
formData.append('images', file);
|
||
});
|
||
|
||
const imageResponse = await fetch(`/api/notes/${noteId}/images`, {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
const imageResult = await imageResponse.json();
|
||
|
||
if (imageResponse.ok) {
|
||
document.getElementById('noteResult').innerHTML += `<div class="success">✓ Загружено ${imageResult.images.length} изображений</div>`;
|
||
document.getElementById('noteIdInput').value = noteId;
|
||
} else {
|
||
document.getElementById('noteResult').innerHTML += `<div class="error">✗ Ошибка загрузки изображений: ${imageResult.error}</div>`;
|
||
}
|
||
} else {
|
||
document.getElementById('noteResult').innerHTML += '<div class="info">ℹ Изображения не выбраны</div>';
|
||
}
|
||
} catch (error) {
|
||
document.getElementById('noteResult').innerHTML = `<div class="error">✗ Ошибка: ${error.message}</div>`;
|
||
}
|
||
}
|
||
|
||
async function getNoteImages() {
|
||
const noteId = document.getElementById('noteIdInput').value;
|
||
if (!noteId) {
|
||
document.getElementById('getImagesResult').innerHTML = '<div class="error">✗ Введите ID заметки</div>';
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(`/api/notes/${noteId}/images`);
|
||
const result = await response.json();
|
||
|
||
if (response.ok) {
|
||
const resultDiv = document.getElementById('getImagesResult');
|
||
resultDiv.innerHTML = `<div class="success">✓ Найдено ${result.length} изображений:</div>`;
|
||
|
||
result.forEach(image => {
|
||
resultDiv.innerHTML += `
|
||
<div style="margin: 10px 0; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">
|
||
<img src="${image.file_path}" style="max-width: 100px; max-height: 100px; margin-right: 10px;">
|
||
<div>
|
||
<strong>ID:</strong> ${image.id}<br>
|
||
<strong>Имя файла:</strong> ${image.original_name}<br>
|
||
<strong>Размер:</strong> ${(image.file_size / 1024).toFixed(2)} KB<br>
|
||
<strong>Тип:</strong> ${image.mime_type}
|
||
</div>
|
||
</div>
|
||
`;
|
||
});
|
||
} else {
|
||
document.getElementById('getImagesResult').innerHTML = `<div class="error">✗ Ошибка: ${result.error}</div>`;
|
||
}
|
||
} catch (error) {
|
||
document.getElementById('getImagesResult').innerHTML = `<div class="error">✗ Ошибка: ${error.message}</div>`;
|
||
}
|
||
}
|
||
|
||
async function deleteImage() {
|
||
const noteId = document.getElementById('deleteNoteIdInput').value;
|
||
const imageId = document.getElementById('deleteImageIdInput').value;
|
||
|
||
if (!noteId || !imageId) {
|
||
document.getElementById('deleteResult').innerHTML = '<div class="error">✗ Введите ID заметки и ID изображения</div>';
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(`/api/notes/${noteId}/images/${imageId}`, {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
const result = await response.json();
|
||
|
||
if (response.ok) {
|
||
document.getElementById('deleteResult').innerHTML = '<div class="success">✓ Изображение успешно удалено</div>';
|
||
} else {
|
||
document.getElementById('deleteResult').innerHTML = `<div class="error">✗ Ошибка: ${result.error}</div>`;
|
||
}
|
||
} catch (error) {
|
||
document.getElementById('deleteResult').innerHTML = `<div class="error">✗ Ошибка: ${error.message}</div>`;
|
||
}
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|