NoteJS/public/test-mobile-upload.html
Fovway efc3c4c777 Улучшена функциональность загрузки изображений и мобильный интерфейс
- Добавлены обработчики для предотвращения дублирования изображений и проверки размера файлов при загрузке (максимум 10MB).
- Реализованы уведомления о добавленных изображениях и улучшен интерфейс для мобильных устройств с индикаторами загрузки и сохранения.
- Оптимизированы стили для мобильных устройств, включая улучшения для кнопок и элементов управления.
2025-10-20 09:50:26 +07:00

577 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Тест загрузки изображений на мобильных - NoteJS</title>
<!-- PWA Meta Tags -->
<meta name="description" content="Тестирование загрузки изображений на мобильных устройствах" />
<meta name="theme-color" content="#007bff" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="NoteJS" />
<meta name="apple-touch-fullscreen" content="yes" />
<!-- Icons -->
<link rel="icon" type="image/svg+xml" href="/icon.svg" />
<link rel="icon" type="image/png" sizes="32x32" href="/icons/icon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/icons/icon-16x16.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
<!-- Styles -->
<link rel="stylesheet" href="/style.css" />
<style>
.test-container {
max-width: 600px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.test-section {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.upload-area {
border: 2px dashed #007bff;
border-radius: 8px;
padding: 40px 20px;
text-align: center;
background: #f8f9fa;
margin: 20px 0;
cursor: pointer;
transition: all 0.3s ease;
}
.upload-area:hover {
background: #e7f3ff;
border-color: #0056b3;
}
.upload-area.dragover {
background: #d4edda;
border-color: #28a745;
}
.upload-icon {
font-size: 48px;
color: #007bff;
margin-bottom: 10px;
}
.upload-text {
font-size: 16px;
color: #333;
margin-bottom: 10px;
}
.upload-hint {
font-size: 14px;
color: #6c757d;
}
.file-input {
display: none;
}
.preview-container {
margin: 20px 0;
}
.preview-item {
position: relative;
display: inline-block;
margin: 10px;
border: 1px solid #dee2e6;
border-radius: 6px;
overflow: hidden;
background: white;
}
.preview-item img {
width: 150px;
height: 150px;
object-fit: cover;
display: block;
}
.preview-item .remove-btn {
position: absolute;
top: 5px;
right: 5px;
background: rgba(220, 53, 69, 0.8);
color: white;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
}
.preview-item .file-info {
padding: 5px;
font-size: 12px;
color: #6c757d;
background: #f8f9fa;
word-break: break-all;
}
.test-button {
background: #007bff;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
cursor: pointer;
width: 100%;
margin: 10px 0;
}
.test-button:hover {
background: #0056b3;
}
.test-button:disabled {
background: #6c757d;
cursor: not-allowed;
}
.status {
padding: 10px;
border-radius: 4px;
margin: 10px 0;
font-weight: bold;
}
.status.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.status.info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.debug-info {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 4px;
padding: 10px;
font-family: monospace;
font-size: 12px;
white-space: pre-wrap;
max-height: 200px;
overflow-y: auto;
margin: 10px 0;
}
.device-info {
background: #e7f3ff;
border: 1px solid #b3d9ff;
border-radius: 6px;
padding: 15px;
margin: 15px 0;
font-size: 14px;
}
</style>
</head>
<body>
<div class="test-container">
<h1>📱 Тест загрузки изображений на мобильных</h1>
<div class="device-info" id="device-info">
Определение устройства...
</div>
<div class="test-section">
<h3>Тест загрузки файлов</h3>
<div class="upload-area" id="upload-area">
<div class="upload-icon">📷</div>
<div class="upload-text">Нажмите для выбора изображений</div>
<div class="upload-hint">или перетащите файлы сюда</div>
<input type="file" id="file-input" class="file-input" accept="image/*" multiple>
</div>
<div class="preview-container" id="preview-container"></div>
<button class="test-button" id="upload-btn" disabled>Загрузить на сервер</button>
<button class="test-button" id="clear-btn">Очистить все</button>
</div>
<div class="test-section">
<h3>Результаты тестов</h3>
<div id="test-results"></div>
</div>
<div class="test-section">
<h3>Отладочная информация</h3>
<button class="test-button" onclick="showDebugInfo()">Показать отладочную информацию</button>
<div id="debug-info" class="debug-info" style="display: none;"></div>
</div>
</div>
<script>
let selectedFiles = [];
// Определение типа устройства
function detectDevice() {
const userAgent = navigator.userAgent;
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent) ||
(navigator.maxTouchPoints && navigator.maxTouchPoints > 2) ||
window.matchMedia('(max-width: 768px)').matches;
const isIOS = /iPad|iPhone|iPod/.test(userAgent);
const isAndroid = /Android/.test(userAgent);
const deviceInfo = document.getElementById('device-info');
let deviceText = '';
if (isMobile) {
deviceText = '📱 <strong>Мобильное устройство</strong><br>';
if (isIOS) {
deviceText += '🍎 iOS устройство<br>';
} else if (isAndroid) {
deviceText += '🤖 Android устройство<br>';
} else {
deviceText += '📱 Другое мобильное устройство<br>';
}
} else {
deviceText = '💻 <strong>ПК/Десктоп</strong><br>';
}
deviceText += `User Agent: ${userAgent}<br>`;
deviceText += `Touch Points: ${navigator.maxTouchPoints || 0}<br>`;
deviceText += `Screen: ${screen.width}x${screen.height}<br>`;
deviceText += `Viewport: ${window.innerWidth}x${window.innerHeight}`;
deviceInfo.innerHTML = deviceText;
return { isMobile, isIOS, isAndroid };
}
// Инициализация
document.addEventListener('DOMContentLoaded', function() {
const device = detectDevice();
setupFileUpload();
runTests(device);
});
// Настройка загрузки файлов
function setupFileUpload() {
const uploadArea = document.getElementById('upload-area');
const fileInput = document.getElementById('file-input');
const uploadBtn = document.getElementById('upload-btn');
const clearBtn = document.getElementById('clear-btn');
// Клик по области загрузки
uploadArea.addEventListener('click', function() {
fileInput.click();
});
// Выбор файлов
fileInput.addEventListener('change', function(e) {
handleFiles(e.target.files);
});
// Drag and drop
uploadArea.addEventListener('dragover', function(e) {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', function(e) {
e.preventDefault();
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', function(e) {
e.preventDefault();
uploadArea.classList.remove('dragover');
handleFiles(e.dataTransfer.files);
});
// Кнопка загрузки
uploadBtn.addEventListener('click', function() {
uploadFiles();
});
// Кнопка очистки
clearBtn.addEventListener('click', function() {
clearAll();
});
}
// Обработка выбранных файлов
function handleFiles(files) {
const fileArray = Array.from(files);
const imageFiles = fileArray.filter(file => file.type.startsWith('image/'));
if (imageFiles.length === 0) {
showStatus('error', 'Пожалуйста, выберите только изображения');
return;
}
selectedFiles = [...selectedFiles, ...imageFiles];
updatePreview();
updateUploadButton();
showStatus('success', `Добавлено ${imageFiles.length} изображений. Всего: ${selectedFiles.length}`);
}
// Обновление превью
function updatePreview() {
const container = document.getElementById('preview-container');
container.innerHTML = '';
selectedFiles.forEach((file, index) => {
const reader = new FileReader();
reader.onload = function(e) {
const previewItem = document.createElement('div');
previewItem.className = 'preview-item';
previewItem.innerHTML = `
<img src="${e.target.result}" alt="Preview">
<button class="remove-btn" onclick="removeFile(${index})">×</button>
<div class="file-info">
${file.name}<br>
${(file.size / 1024 / 1024).toFixed(2)} MB<br>
${file.type}
</div>
`;
container.appendChild(previewItem);
};
reader.readAsDataURL(file);
});
}
// Удаление файла
function removeFile(index) {
selectedFiles.splice(index, 1);
updatePreview();
updateUploadButton();
}
// Обновление кнопки загрузки
function updateUploadButton() {
const uploadBtn = document.getElementById('upload-btn');
uploadBtn.disabled = selectedFiles.length === 0;
}
// Очистка всех файлов
function clearAll() {
selectedFiles = [];
updatePreview();
updateUploadButton();
document.getElementById('file-input').value = '';
showStatus('info', 'Все файлы очищены');
}
// Загрузка файлов на сервер
async function uploadFiles() {
if (selectedFiles.length === 0) {
showStatus('error', 'Нет файлов для загрузки');
return;
}
const uploadBtn = document.getElementById('upload-btn');
uploadBtn.disabled = true;
uploadBtn.textContent = 'Загрузка...';
try {
const formData = new FormData();
selectedFiles.forEach(file => {
formData.append('images', file);
});
// Создаем тестовую заметку
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' })
}),
});
if (!noteResponse.ok) {
throw new Error('Ошибка создания заметки');
}
const noteData = await noteResponse.json();
const noteId = noteData.id;
// Загружаем изображения
const uploadResponse = await fetch(`/api/notes/${noteId}/images`, {
method: 'POST',
body: formData,
});
if (!uploadResponse.ok) {
throw new Error('Ошибка загрузки изображений');
}
const uploadData = await uploadResponse.json();
showStatus('success', `Успешно загружено ${uploadData.images.length} изображений!`);
// Очищаем после успешной загрузки
clearAll();
} catch (error) {
console.error('Ошибка загрузки:', error);
showStatus('error', `Ошибка загрузки: ${error.message}`);
} finally {
uploadBtn.disabled = false;
uploadBtn.textContent = 'Загрузить на сервер';
}
}
// Показ статуса
function showStatus(type, message) {
const results = document.getElementById('test-results');
const statusDiv = document.createElement('div');
statusDiv.className = `status ${type}`;
statusDiv.textContent = `${new Date().toLocaleTimeString()}: ${message}`;
results.appendChild(statusDiv);
results.scrollTop = results.scrollHeight;
}
// Запуск тестов
function runTests(device) {
const tests = [
{
name: 'Поддержка File API',
test: () => typeof File !== 'undefined' && typeof FileReader !== 'undefined'
},
{
name: 'Поддержка FormData',
test: () => typeof FormData !== 'undefined'
},
{
name: 'Поддержка fetch API',
test: () => typeof fetch !== 'undefined'
},
{
name: 'Поддержка input[type="file"]',
test: () => {
const input = document.createElement('input');
input.type = 'file';
return input.type === 'file';
}
},
{
name: 'Поддержка multiple атрибута',
test: () => {
const input = document.createElement('input');
input.type = 'file';
input.multiple = true;
return input.multiple === true;
}
},
{
name: 'Поддержка accept атрибута',
test: () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
return input.accept === 'image/*';
}
},
{
name: 'Поддержка Drag and Drop',
test: () => 'draggable' in document.createElement('div')
},
{
name: 'Поддержка Touch Events',
test: () => 'ontouchstart' in window
}
];
tests.forEach(test => {
try {
const result = test.test();
showStatus(result ? 'success' : 'error', `${test.name}: ${result ? '✅ Поддерживается' : '❌ Не поддерживается'}`);
} catch (error) {
showStatus('error', `${test.name}: ❌ Ошибка - ${error.message}`);
}
});
// Дополнительные тесты для мобильных устройств
if (device.isMobile) {
showStatus('info', '📱 Мобильные тесты:');
// Тест размера экрана
const screenSize = screen.width * screen.height;
showStatus('info', `Размер экрана: ${screen.width}x${screen.height} (${screenSize} пикселей)`);
// Тест viewport
const viewportSize = window.innerWidth * window.innerHeight;
showStatus('info', `Размер viewport: ${window.innerWidth}x${window.innerHeight} (${viewportSize} пикселей)`);
// Тест ориентации
const orientation = screen.orientation ? screen.orientation.type : 'unknown';
showStatus('info', `Ориентация: ${orientation}`);
}
}
// Показать отладочную информацию
function showDebugInfo() {
const debugInfo = document.getElementById('debug-info');
const info = {
userAgent: navigator.userAgent,
platform: navigator.platform,
language: navigator.language,
onLine: navigator.onLine,
cookieEnabled: navigator.cookieEnabled,
maxTouchPoints: navigator.maxTouchPoints,
screen: {
width: screen.width,
height: screen.height,
availWidth: screen.availWidth,
availHeight: screen.availHeight
},
window: {
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
outerWidth: window.outerWidth,
outerHeight: window.outerHeight
},
devicePixelRatio: window.devicePixelRatio,
selectedFiles: selectedFiles.map(f => ({
name: f.name,
size: f.size,
type: f.type,
lastModified: f.lastModified
}))
};
debugInfo.textContent = JSON.stringify(info, null, 2);
debugInfo.style.display = debugInfo.style.display === 'none' ? 'block' : 'none';
}
</script>
</body>
</html>