Sửa lỗi đăng nhập vào admin mà không reload được page do lỗi tạo scene trước đó, sử dụng lệnh resetDB.js để khởi tạo lại, xóa các scene trước và ảnh đã upload

This commit is contained in:
2026-06-08 10:48:54 +07:00
parent 81de520071
commit c495efad36
25 changed files with 290 additions and 47 deletions
+110 -28
View File
@@ -1,4 +1,4 @@
const API_BASE_URL = 'http://localhost:5000/api';
const API_BASE_URL = '/api'; // Sử dụng đường dẫn tương đối để tránh lỗi CORS/Hostname
let map;
let tempMarker = null;
@@ -8,10 +8,27 @@ let previousSceneId = null;
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', () => {
initMap();
checkAuthStatus();
loadScenes();
restoreActiveScene();
try {
console.log("--- Bắt đầu khởi tạo Frontend ---");
if (document.getElementById('map')) {
console.log("1. Đang khởi tạo bản đồ Leaflet...");
initMap();
}
// Chạy tuần tự để tránh xung đột luồng xử lý
checkAuthStatus(); // 2. Kiểm tra đăng nhập
// Đảm bảo map đã sẵn sàng trước khi nạp data
if (map) {
loadScenes().then(() => {
console.log("4. Đang chuẩn bị khôi phục Scene cũ (nếu có)...");
// Chỉ khôi phục khi bản đồ đã nạp xong các marker
setTimeout(restoreActiveScene, 500);
});
}
} catch (error) {
console.error("Ứng dụng không thể khởi tạo:", error);
}
});
/**
@@ -23,12 +40,16 @@ function initMap() {
const savedLng = localStorage.getItem('map-lng');
const savedZoom = localStorage.getItem('map-zoom');
// Nếu có dữ liệu cũ thì dùng, không thì mặc định là Hà Nội
const startLat = savedLat ? parseFloat(savedLat) : 21.0285;
const startLng = savedLng ? parseFloat(savedLng) : 105.8542;
const startZoom = savedZoom ? parseInt(savedZoom) : 13;
// Đảm bảo tọa độ khởi tạo luôn hợp lệ
let startLat = parseFloat(savedLat);
let startLng = parseFloat(savedLng);
let startZoom = parseInt(savedZoom);
map = L.map('map').setView([startLat, startLng], startZoom);
if (isNaN(startLat)) startLat = 21.0285;
if (isNaN(startLng)) startLng = 105.8542;
if (isNaN(startZoom)) startZoom = 13;
map = L.map('map', { zoomControl: true }).setView([startLat, startLng], startZoom);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
@@ -44,8 +65,13 @@ function initMap() {
showCoverageOnHover: false,
iconCreateFunction: function(cluster) {
const childMarkers = cluster.getAllChildMarkers();
// Lấy icon của Scene đầu tiên trong nhóm để làm đại diện cho cả cụm
return childMarkers[0].options.icon;
try {
// Thay vì tạo object mới phức tạp, lấy HTML của marker đầu tiên
const firstIcon = childMarkers[0].options.icon;
if (firstIcon) return firstIcon;
} catch (e) {}
// Fallback an toàn nếu có lỗi
return L.divIcon({ className: 'cluster-fallback', html: '<div style="background:#007bff;width:10px;height:10px;border-radius:50%;"></div>' });
}
});
@@ -174,6 +200,12 @@ function handleLogout() {
localStorage.removeItem('activeScenePrivacy');
localStorage.removeItem('activeSceneToken');
localStorage.removeItem('userId');
// Đảm bảo đóng viewer nếu đang mở
if (typeof closeViewer === 'function') {
closeViewer();
}
checkAuthStatus();
loadScenes(); // Reload scenes to filter out private ones
alert('Logged out successfully');
@@ -193,10 +225,10 @@ function openCreateSceneModal(lat, lng) {
if (tempMarker) map.removeLayer(tempMarker);
tempMarker = L.marker([lat, lng]).addTo(map);
document.getElementById('create-scene-modal').style.display = 'flex';
document.getElementById('modal-scene-id').value = '';
document.getElementById('modal-lat').value = lat.toFixed(6);
document.getElementById('modal-lng').value = lng.toFixed(6);
document.getElementById('create-scene-modal').style.display = 'flex';
}
/**
@@ -255,30 +287,34 @@ function uploadWithProgress(url, method, formData, token, prefix, callback) {
const percentText = document.getElementById(`${prefix}-progress-percent`);
const statusText = document.getElementById(`${prefix}-progress-status`);
container.style.display = 'block';
statusText.innerText = "Đang tải ảnh lên...";
if (container) container.style.display = 'block';
if (statusText) statusText.innerText = "Đang tải ảnh lên...";
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) * 100);
bar.style.width = percent + '%';
percentText.innerText = percent + '%';
if (percent === 100) statusText.innerText = "Tải lên xong! Đang khởi tạo trên server...";
if (bar) bar.style.width = percent + '%';
if (percentText) percentText.innerText = percent + '%';
if (percent === 100 && statusText) statusText.innerText = "Tải lên xong! Đang khởi tạo trên server...";
}
});
xhr.addEventListener('load', () => {
container.style.display = 'none';
if (container) container.style.display = 'none';
if (xhr.status >= 200 && xhr.status < 300) {
callback(JSON.parse(xhr.responseText));
} else {
const err = JSON.parse(xhr.responseText);
alert('Lỗi: ' + (err.message || 'Không thể tải lên'));
let errorMsg = 'Không thể tải lên';
try {
const err = JSON.parse(xhr.responseText);
errorMsg = err.message || errorMsg;
} catch (e) {}
alert('Lỗi: ' + errorMsg);
}
});
xhr.addEventListener('error', () => {
container.style.display = 'none';
if (container) container.style.display = 'none';
alert('Lỗi kết nối mạng.');
});
@@ -298,27 +334,45 @@ async function loadScenes() {
headers['Authorization'] = `Bearer ${token}`;
}
const response = await fetch(`${API_BASE_URL}/scenes`, {
// Thêm timestamp để tránh lỗi 304 hang do cache trình duyệt
const timestamp = new Date().getTime();
console.log(`3.1 Đang gửi yêu cầu lấy danh sách Scene (ts: ${timestamp})...`);
const response = await fetch(`${API_BASE_URL}/scenes?_=${timestamp}`, {
method: 'GET',
headers
});
console.log(`[API Response] /scenes status: ${response.status}`);
if (!response.ok) throw new Error('Failed to load scenes');
const scenes = await response.json();
console.log(`[Data] Nhận được ${scenes.length} scenes từ server`);
if (!Array.isArray(scenes)) return;
// Xóa sạch các layers cũ và chuẩn bị lọc tọa độ
// Xóa sạch các layers cũ trước khi nạp mới
markerClusterGroup.clearLayers();
const markersToAdd = [];
const activeSceneId = localStorage.getItem('activeSceneId');
const seenCoordinates = new Set();
const seenCoordinates = new Set(); // Dùng để lọc "Ảnh mẹ" (1 marker per location)
// Chỉ lặp qua danh sách Scene mẹ, lọc bỏ các hotspots trùng tọa độ
scenes.forEach((scene) => {
// Tạo khóa tọa độ để đảm bảo mỗi vị trí địa lý chỉ có 1 Marker duy nhất
const coordKey = `${scene.lat.toFixed(6)},${scene.lng.toFixed(6)}`;
const latNum = parseFloat(scene.lat);
const lngNum = parseFloat(scene.lng);
if (isNaN(latNum) || isNaN(lngNum)) return;
// Logic lọc Ảnh mẹ: Mỗi tọa độ GPS chỉ tạo duy nhất 1 Marker đại diện
const coordKey = `${latNum.toFixed(6)},${lngNum.toFixed(6)}`;
if (seenCoordinates.has(coordKey)) return; // Bỏ qua nếu tọa độ này đã có Marker
seenCoordinates.add(coordKey);
// Kiểm tra an toàn dữ liệu từ MongoDB trước khi truy cập
if (!scene.assetId || !scene.assetId._id) {
console.warn(`Scene "${scene.title}" thiếu dữ liệu ảnh (AssetId), bỏ qua.`);
return;
}
let thumbUrl = `${API_BASE_URL}/assets/view/${scene.assetId._id}`;
if (token) thumbUrl += `?token=${token}`;
else if (scene.privacy === 'shared' && scene.shareToken) thumbUrl += `?token=${scene.shareToken}`;
@@ -335,7 +389,7 @@ async function loadScenes() {
iconAnchor: [32, 76] // Căn giữa ngang, đáy mũi tên tại tọa độ lat/lng
});
const marker = L.marker([scene.lat, scene.lng], {
const marker = L.marker([latNum, lngNum], {
icon: calloutIcon,
title: scene.title // Tooltip khi di chuột qua
});
@@ -472,6 +526,7 @@ async function openScene(sceneId, privacy, shareToken, force = false) {
headers['Authorization'] = `Bearer ${token}`;
}
console.log(`[Viewer] Đang mở scene: ${sceneId}`);
let url = `${API_BASE_URL}/scenes/${sceneId}`;
if (privacy === 'shared' && shareToken) {
url += `?token=${shareToken}`;
@@ -703,3 +758,30 @@ async function saveHotspotToDB(pitch, yaw, text, description, targetSceneId, hot
alert(error.message);
}
}
/**
* Công cụ dọn dẹp toàn bộ dữ liệu (Chỉ dùng cho nhà phát triển)
* Gọi lệnh: systemReset() từ trình duyệt
*/
window.systemReset = async function() {
if (!confirm("CẢNH BÁO: Thao tác này sẽ xóa sạch TOÀN BỘ scene và ảnh trên server. Bạn có chắc chắn?")) return;
const token = localStorage.getItem('jwt');
try {
const response = await fetch(`${API_BASE_URL}/maintenance/reset-all`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});
const data = await response.json();
if (response.ok) {
localStorage.clear(); // Xóa sạch token, vị trí map, active scene
alert(data.message);
location.reload();
} else {
throw new Error(data.message);
}
} catch (e) {
alert("Lỗi reset: " + e.message);
}
};
+7 -5
View File
@@ -1,5 +1,6 @@
let activeViewer = null;
let currentHotspots = [];
let securityApplied = false;
/**
* Initializes and shows the Pannellum 360° panorama viewer with security overlays.
@@ -72,6 +73,9 @@ function closeViewer() {
* Appends event listeners to block right-clicks and common image saving shortcuts.
*/
function applyViewerSecurity() {
if (securityApplied) return; // Chỉ gán sự kiện một lần duy nhất
securityApplied = true;
// Target the actual viewer element where Pannellum renders
const container = document.getElementById('viewer-container');
const panoramaViewer = document.getElementById('panorama-viewer');
@@ -114,18 +118,16 @@ function applyViewerSecurity() {
});
}
// Global safety shortcut listeners (F12, Ctrl+S, Ctrl+U, Ctrl+Shift+I)
// Global safety shortcut listeners (Ctrl+S, Ctrl+U) - Tạm thời cho phép F12 và Ctrl+Shift+I để debug
document.addEventListener('keydown', (e) => {
// Only enforce when viewer is active
if (document.getElementById('viewer-container').style.display === 'block') {
const isCtrlS = e.ctrlKey && (e.key === 's' || e.key === 'S');
const isCtrlU = e.ctrlKey && (e.key === 'u' || e.key === 'U');
const isF12 = e.key === 'F12';
const isCtrlShiftI = e.ctrlKey && e.shiftKey && (e.key === 'i' || e.key === 'I');
if (isCtrlS || isCtrlU || isF12 || isCtrlShiftI) {
if (isCtrlS || isCtrlU) {
e.preventDefault();
alert('Security Alert: Inspection and saving functions are restricted on this viewer.');
console.warn('Security Alert: Inspection and saving functions are restricted.');
return false;
}
}