/
Rayven2035
/
Web
Обзор
Документация
Войти
/
Rayven2035
/
Web
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
profile.html
400 строк
23 KB
Xusat
registration.html
25 май 2026, 11:20
25 май 2026, 11:20
7786b17
Код
Авторство
О чём код?
<!DOCTYPE html> <html lang="ru"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Логисмарт — Профиль</title> <script src="https://cdn.tailwindcss.com"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"> <link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;400;500;600;700&display=swap" rel="stylesheet"> <style> * { font-family: 'Inter', sans-serif; } body { background: linear-gradient(135deg, #0f2b3d 0%, #1b4a6e 100%); min-height: 100vh; } .card { background: rgba(255,255,255,0.98); backdrop-filter: blur(10px); } .btn-primary { background: linear-gradient(95deg, #2563eb, #1e40af); transition: all 0.2s; } .btn-primary:hover { transform: translateY(-2px); box-shadow: 0 10px 20px -5px rgba(37,99,235,0.4); } .btn-danger { background: linear-gradient(95deg, #dc2626, #b91c1c); } .btn-danger:hover { transform: translateY(-1px); box-shadow: 0 5px 15px -3px rgba(220,38,38,0.4); } .btn-success { background: linear-gradient(95deg, #16a34a, #15803d); } .input-field { border: 1px solid #e2e8f0; transition: all 0.2s; } .input-field:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59,130,246,0.1); outline: none; } .vehicle-card { transition: all 0.3s; } .vehicle-card:hover { transform: translateY(-4px); box-shadow: 0 20px 40px -15px rgba(0,0,0,0.2); } .modal { transition: opacity 0.2s, visibility 0.2s; } .modal.hidden { opacity: 0; visibility: hidden; } .modal:not(.hidden) { opacity: 1; visibility: visible; } </style> </head> <body class="p-4 md:p-8"> <div class="max-w-6xl mx-auto"> <div class="flex items-center justify-between mb-8"> <div class="flex items-center gap-4"> <button onclick="goBack()" class="text-white hover:text-gray-200 transition"> <i class="fas fa-arrow-left text-xl"></i> </button> <div class="text-white"> <h1 class="text-2xl font-bold">Профиль</h1> <p class="text-white/70" id="userEmail"></p> </div> </div> <button onclick="logout()" class="text-white hover:text-red-300 transition"> <i class="fas fa-sign-out-alt text-xl"></i> <span class="ml-2 hidden md:inline">Выйти</span> </button> </div> <div class="grid md:grid-cols-3 gap-6"> <div class="md:col-span-1"> <div class="card rounded-2xl shadow-xl p-6 mb-6"> <div class="text-center mb-6"> <div class="w-24 h-24 bg-gradient-to-br from-blue-500 to-blue-600 rounded-full flex items-center justify-center mx-auto mb-4"> <i class="fas fa-user text-white text-4xl"></i> </div> <h2 class="text-xl font-bold text-gray-800" id="userName"></h2> <p class="text-gray-500 text-sm" id="userCreated"></p> </div> <div class="space-y-3"> <div class="flex items-center gap-3 text-gray-600"> <i class="fas fa-truck"></i> <span id="vehicleCount">0</span> транспортных средств </div> </div> </div> </div> <div class="md:col-span-2"> <div class="card rounded-2xl shadow-xl p-6"> <div class="flex items-center justify-between mb-6"> <h2 class="text-xl font-bold text-gray-800"> <i class="fas fa-truck-moving text-blue-500 mr-2"></i>Автопарк </h2> <button onclick="openAddModal()" class="btn-primary text-white px-4 py-2 rounded-xl font-medium flex items-center gap-2"> <i class="fas fa-plus"></i> <span>Добавить</span> </button> </div> <div id="vehiclesList" class="space-y-4"> <div class="text-center py-12 text-gray-400"> <i class="fas fa-truck text-6xl mb-4 opacity-30"></i> <p>У вас пока нет транспортных средств</p> <p class="text-sm">Добавьте первый автомобиль</p> </div> </div> </div> </div> </div> </div> <div id="vehicleModal" class="modal fixed inset-0 bg-black/50 flex items-center justify-center p-4 hidden z-50"> <div class="bg-white rounded-2xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-y-auto"> <div class="p-6 border-b border-gray-100"> <div class="flex items-center justify-between"> <h3 class="text-xl font-bold text-gray-800" id="modalTitle">Добавить транспорт</h3> <button onclick="closeModal()" class="text-gray-400 hover:text-gray-600"> <i class="fas fa-times text-xl"></i> </button> </div> </div> <form id="vehicleForm" class="p-6 space-y-4"> <input type="hidden" id="vehicleId"> <div class="grid md:grid-cols-2 gap-4"> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Марка *</label> <input type="text" id="brand" required class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Модель *</label> <input type="text" id="model" required class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white"> </div> </div> <div class="grid md:grid-cols-2 gap-4"> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Объем бака (л)</label> <input type="number" step="0.01" id="tank_volume" class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Расход топлива (л/100км)</label> <input type="number" step="0.01" id="fuel_consumption" class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white"> </div> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-2">Габариты (Д × Ш × В, м)</label> <div class="grid grid-cols-3 gap-4"> <div> <input type="number" step="0.01" id="length" placeholder="Длина" class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white"> </div> <div> <input type="number" step="0.01" id="width" placeholder="Ширина" class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white"> </div> <div> <input type="number" step="0.01" id="height" placeholder="Высота" class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white"> </div> </div> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Объем перевозимого груза (м³)</label> <input type="number" step="0.01" id="cargo_volume" class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white"> </div> <div class="grid md:grid-cols-2 gap-4"> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Макс. масса (кг)</label> <input type="number" step="0.01" id="max_weight" placeholder="20000" class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Макс. осевая нагрузка (кг)</label> <input type="number" step="0.01" id="max_axle_load" placeholder="10000" class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white"> </div> </div> <div class="grid md:grid-cols-2 gap-4"> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Гос. номер</label> <input type="text" id="license_plate" placeholder="А123АА" class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white uppercase"> </div> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Категория оплаты (N1/N2/N3)</label> <select id="toll_category" class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white"> <option value="">Не указана</option> <option value="N1">N1 (до 3.5т)</option> <option value="N2">N2 (3.5-12т)</option> <option value="N3">N3 (более 12т)</option> </select> </div> </div> <div class="grid md:grid-cols-2 gap-4"> <div> <label class="block text-sm font-medium text-gray-700 mb-1">Экологический класс</label> <select id="euro_class" class="input-field w-full px-4 py-3 rounded-xl bg-gray-50 focus:bg-white"> <option value="">Не указан</option> <option value="Euro 1">Euro 1</option> <option value="Euro 2">Euro 2</option> <option value="Euro 3">Euro 3</option> <option value="Euro 4">Euro 4</option> <option value="Euro 5">Euro 5</option> <option value="Euro 6">Euro 6</option> </select> </div> <div class="flex items-center gap-3 pt-8"> <input type="checkbox" id="has_trailer" class="w-5 h-5 text-blue-500 rounded"> <label for="has_trailer" class="text-sm font-medium text-gray-700">Наличие прицепа/полуприцепа</label> </div> </div> <div class="flex gap-3 pt-4"> <button type="button" onclick="closeModal()" class="flex-1 py-3 rounded-xl border border-gray-200 text-gray-600 font-medium hover:bg-gray-50 transition">Отмена</button> <button type="submit" class="btn-primary flex-1 py-3 rounded-xl text-white font-medium">Сохранить</button> </div> </form> </div> </div> <script> const API_BASE = ''; let token = localStorage.getItem('logismart_token'); function getAuthHeaders() { return { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }; } function checkToken() { if (!token) { logout(); return false; } try { const payload = JSON.parse(atob(token.split('.')[1])); if (payload.exp * 1000 < Date.now()) { logout(); return false; } } catch(e) { logout(); return false; } return true; } async function loadProfile() { if (!checkToken()) return; try { const res = await fetch(`${API_BASE}/users/me`, { headers: getAuthHeaders() }); if (!res.ok) throw new Error(); const user = await res.json(); document.getElementById('userName').textContent = user.name; document.getElementById('userEmail').textContent = user.email; document.getElementById('userCreated').textContent = `С нами с ${new Date(user.created_at).toLocaleDateString('ru-RU')}`; } catch (e) { logout(); } } async function loadVehicles() { if (!checkToken()) return; try { const res = await fetch(`${API_BASE}/vehicles`, { headers: getAuthHeaders() }); if (!res.ok) throw new Error(); const vehicles = await res.json(); document.getElementById('vehicleCount').textContent = vehicles.length; renderVehicles(vehicles); } catch (e) { console.error('Failed to load vehicles'); } } function renderVehicles(vehicles) { const container = document.getElementById('vehiclesList'); if (vehicles.length === 0) { container.innerHTML = ` <div class="text-center py-12 text-gray-400"> <i class="fas fa-truck text-6xl mb-4 opacity-30"></i> <p>У вас пока нет транспортных средств</p> <p class="text-sm">Добавьте первый автомобиль</p> </div>`; return; } container.innerHTML = vehicles.map(v => ` <div class="vehicle-card bg-gray-50 rounded-xl p-5 border border-gray-100"> <div class="flex items-start justify-between"> <div class="flex items-start gap-4"> <div class="w-14 h-14 bg-blue-100 rounded-xl flex items-center justify-center"> <i class="fas fa-truck text-blue-600 text-2xl"></i> </div> <div> <h4 class="font-bold text-lg text-gray-800">${v.brand} ${v.model}</h4> ${v.license_plate ? `<span class="text-sm font-mono bg-gray-200 px-2 py-0.5 rounded">${v.license_plate}</span>` : ''} <div class="flex flex-wrap gap-3 mt-2 text-sm text-gray-500"> ${v.tank_volume ? `<span><i class="fas fa-gas-pump mr-1"></i>${v.tank_volume} л</span>` : ''} ${v.fuel_consumption ? `<span><i class="fas fa-percentage mr-1"></i>${v.fuel_consumption} л/100км</span>` : ''} ${v.cargo_volume ? `<span><i class="fas fa-box mr-1"></i>${v.cargo_volume} м³</span>` : ''} ${(v.length && v.width && v.height) ? `<span><i class="fas fa-expand mr-1"></i>${v.length}×${v.width}×${v.height} м</span>` : ''} </div> <div class="flex flex-wrap gap-2 mt-2"> ${v.max_weight ? `<span class="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded"><i class="fas fa-weight mr-1"></i>${v.max_weight} кг</span>` : ''} ${v.toll_category ? `<span class="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded">${v.toll_category}</span>` : ''} ${v.euro_class ? `<span class="text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded">${v.euro_class}</span>` : ''} ${v.has_trailer ? `<span class="text-xs bg-amber-100 text-amber-700 px-2 py-0.5 rounded"><i class="fas fa-trailer mr-1"></i>Прицеп</span>` : ''} </div> </div> </div> <div class="flex gap-2"> <button onclick="editVehicle(${v.id})" class="p-2 text-blue-500 hover:bg-blue-50 rounded-lg transition" title="Редактировать"> <i class="fas fa-edit"></i> </button> <button onclick="deleteVehicle(${v.id})" class="p-2 text-red-500 hover:bg-red-50 rounded-lg transition" title="Удалить"> <i class="fas fa-trash"></i> </button> </div> </div> </div>`).join(''); } function openAddModal() { document.getElementById('modalTitle').textContent = 'Добавить транспорт'; document.getElementById('vehicleForm').reset(); document.getElementById('vehicleId').value = ''; document.getElementById('vehicleModal').classList.remove('hidden'); } async function editVehicle(id) { try { const res = await fetch(`${API_BASE}/vehicles/${id}`, { headers: getAuthHeaders() }); if (!res.ok) throw new Error(); const v = await res.json(); document.getElementById('modalTitle').textContent = 'Редактировать транспорт'; document.getElementById('vehicleId').value = v.id; document.getElementById('brand').value = v.brand; document.getElementById('model').value = v.model; document.getElementById('license_plate').value = v.license_plate || ''; document.getElementById('tank_volume').value = v.tank_volume || ''; document.getElementById('fuel_consumption').value = v.fuel_consumption || ''; document.getElementById('length').value = v.length || ''; document.getElementById('width').value = v.width || ''; document.getElementById('height').value = v.height || ''; document.getElementById('cargo_volume').value = v.cargo_volume || ''; document.getElementById('max_weight').value = v.max_weight || ''; document.getElementById('max_axle_load').value = v.max_axle_load || ''; document.getElementById('toll_category').value = v.toll_category || ''; document.getElementById('euro_class').value = v.euro_class || ''; document.getElementById('has_trailer').checked = v.has_trailer || false; document.getElementById('vehicleModal').classList.remove('hidden'); } catch (e) { alert('Ошибка загрузки данных'); } } function closeModal() { document.getElementById('vehicleModal').classList.add('hidden'); } document.getElementById('vehicleForm').addEventListener('submit', async (e) => { e.preventDefault(); const id = document.getElementById('vehicleId').value; const data = { brand: document.getElementById('brand').value, model: document.getElementById('model').value, license_plate: document.getElementById('license_plate').value || null, tank_volume: parseFloat(document.getElementById('tank_volume').value) || null, fuel_consumption: parseFloat(document.getElementById('fuel_consumption').value) || null, length: parseFloat(document.getElementById('length').value) || null, width: parseFloat(document.getElementById('width').value) || null, height: parseFloat(document.getElementById('height').value) || null, cargo_volume: parseFloat(document.getElementById('cargo_volume').value) || null, max_weight: parseFloat(document.getElementById('max_weight').value) || null, max_axle_load: parseFloat(document.getElementById('max_axle_load').value) || null, toll_category: document.getElementById('toll_category').value || null, euro_class: document.getElementById('euro_class').value || null, has_trailer: document.getElementById('has_trailer').checked }; try { if (!checkToken()) { alert('Сессия истекла, войдите снова'); return; } const url = id ? `${API_BASE}/vehicles/${id}` : `${API_BASE}/vehicles`; const method = id ? 'PUT' : 'POST'; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 15000); const res = await fetch(url, { method, headers: getAuthHeaders(), body: JSON.stringify(data), signal: controller.signal }); clearTimeout(timeout); if (!res.ok) { const err = await res.json(); throw new Error(err.detail || 'Save error'); } closeModal(); loadVehicles(); } catch (e) { console.error('Vehicle save error:', e); if (e.name === 'AbortError') { alert('Ошибка сохранения: таймаут соединения. Проверьте, запущен ли сервер.'); } else { alert('Ошибка сохранения: ' + e.message); } } }); async function deleteVehicle(id) { if (!confirm('Удалить это транспортное средство?')) return; try { const res = await fetch(`${API_BASE}/vehicles/${id}`, { method: 'DELETE', headers: getAuthHeaders() }); if (!res.ok) throw new Error(); loadVehicles(); } catch (e) { alert('Ошибка удаления'); } } function logout() { localStorage.removeItem('logismart_token'); localStorage.removeItem('logismart_user'); localStorage.removeItem('logismart_auth'); window.location.href = 'registration.html'; } function goBack() { window.location.href = 'Web-приложение.html'; } if (!token) { logout(); } else { loadProfile(); loadVehicles(); } </script> </body> </html>