|
|
|
|
@@ -1,6 +1,7 @@
|
|
|
|
|
// /js/app-init.js
|
|
|
|
|
// 앱 초기화 - 인증, 네비바, 사이드바를 한 번에 로드
|
|
|
|
|
// 모든 페이지에서 이 하나의 스크립트만 로드하면 됨
|
|
|
|
|
// api-base.js가 먼저 로드되어야 함 (getSSOToken, getSSOUser, clearSSOAuth 등)
|
|
|
|
|
|
|
|
|
|
(function() {
|
|
|
|
|
'use strict';
|
|
|
|
|
@@ -9,24 +10,29 @@
|
|
|
|
|
const CACHE_DURATION = 10 * 60 * 1000; // 10분
|
|
|
|
|
const COMPONENT_CACHE_PREFIX = 'component_v3_';
|
|
|
|
|
|
|
|
|
|
// ===== 인증 함수 (api-base.js의 전역 헬퍼 활용) =====
|
|
|
|
|
// ===== 인증 함수 (api-base.js의 SSO 함수 활용) =====
|
|
|
|
|
function isLoggedIn() {
|
|
|
|
|
var token = window.getSSOToken ? window.getSSOToken() : localStorage.getItem('sso_token');
|
|
|
|
|
const token = window.getSSOToken ? window.getSSOToken() : (localStorage.getItem('sso_token') || localStorage.getItem('token'));
|
|
|
|
|
return token && token !== 'undefined' && token !== 'null';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getUser() {
|
|
|
|
|
return window.getSSOUser ? window.getSSOUser() : (function() {
|
|
|
|
|
var u = localStorage.getItem('sso_user');
|
|
|
|
|
return u ? JSON.parse(u) : null;
|
|
|
|
|
})();
|
|
|
|
|
if (window.getSSOUser) return window.getSSOUser();
|
|
|
|
|
const user = localStorage.getItem('sso_user') || localStorage.getItem('user');
|
|
|
|
|
try { return user ? JSON.parse(user) : null; } catch(e) { return null; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getToken() {
|
|
|
|
|
return window.getSSOToken ? window.getSSOToken() : (localStorage.getItem('sso_token') || localStorage.getItem('token'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearAuthData() {
|
|
|
|
|
if (window.clearSSOAuth) { window.clearSSOAuth(); return; }
|
|
|
|
|
localStorage.removeItem('sso_token');
|
|
|
|
|
localStorage.removeItem('sso_user');
|
|
|
|
|
localStorage.removeItem('userPageAccess_v2');
|
|
|
|
|
localStorage.removeItem('token');
|
|
|
|
|
localStorage.removeItem('user');
|
|
|
|
|
localStorage.removeItem('userPageAccess');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== 페이지 권한 캐시 =====
|
|
|
|
|
@@ -36,7 +42,7 @@
|
|
|
|
|
if (!currentUser || !currentUser.user_id) return null;
|
|
|
|
|
|
|
|
|
|
// 캐시 확인
|
|
|
|
|
const cached = localStorage.getItem('userPageAccess_v2');
|
|
|
|
|
const cached = localStorage.getItem('userPageAccess');
|
|
|
|
|
if (cached) {
|
|
|
|
|
try {
|
|
|
|
|
const cacheData = JSON.parse(cached);
|
|
|
|
|
@@ -44,7 +50,7 @@
|
|
|
|
|
return cacheData.pages;
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
localStorage.removeItem('userPageAccess_v2');
|
|
|
|
|
localStorage.removeItem('userPageAccess');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -54,11 +60,12 @@
|
|
|
|
|
// 새로운 API 호출
|
|
|
|
|
pageAccessPromise = (async () => {
|
|
|
|
|
try {
|
|
|
|
|
const token = getToken();
|
|
|
|
|
const response = await fetch(`${window.API_BASE_URL}/users/${currentUser.user_id}/page-access`, {
|
|
|
|
|
method: 'GET',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
'Authorization': 'Bearer ' + (window.getSSOToken ? window.getSSOToken() : localStorage.getItem('sso_token'))
|
|
|
|
|
'Authorization': `Bearer ${token}`
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -67,7 +74,7 @@
|
|
|
|
|
const data = await response.json();
|
|
|
|
|
const pages = data.data.pageAccess || [];
|
|
|
|
|
|
|
|
|
|
localStorage.setItem('userPageAccess_v2', JSON.stringify({
|
|
|
|
|
localStorage.setItem('userPageAccess', JSON.stringify({
|
|
|
|
|
pages: pages,
|
|
|
|
|
timestamp: Date.now()
|
|
|
|
|
}));
|
|
|
|
|
@@ -87,15 +94,17 @@
|
|
|
|
|
async function getAccessiblePageKeys(currentUser) {
|
|
|
|
|
const pages = await getPageAccess(currentUser);
|
|
|
|
|
if (!pages) return [];
|
|
|
|
|
return pages.filter(p => p.can_access === 1).map(p => p.page_key);
|
|
|
|
|
return pages.filter(p => p.can_access == 1).map(p => p.page_key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== 현재 페이지 키 추출 =====
|
|
|
|
|
// 하위 페이지 → 부모 페이지 키 매핑 (동일 권한 공유)
|
|
|
|
|
var PAGE_KEY_ALIASES = {
|
|
|
|
|
'work.tbm-create': 'work.tbm',
|
|
|
|
|
const PAGE_KEY_ALIASES = {
|
|
|
|
|
'work.tbm-mobile': 'work.tbm',
|
|
|
|
|
'work.report-create-mobile': 'work.report-create'
|
|
|
|
|
'work.tbm-create': 'work.tbm',
|
|
|
|
|
'work.report-create-mobile': 'work.report-create',
|
|
|
|
|
'admin.equipment-detail': 'admin.equipments',
|
|
|
|
|
'safety.issue-detail': 'safety.issue-report'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function getCurrentPageKey() {
|
|
|
|
|
@@ -186,7 +195,6 @@
|
|
|
|
|
async function processSidebar(doc, currentUser, accessiblePageKeys) {
|
|
|
|
|
const userRole = (currentUser.role || '').toLowerCase();
|
|
|
|
|
const accessLevel = (currentUser.access_level || '').toLowerCase();
|
|
|
|
|
// role 또는 access_level로 관리자 확인
|
|
|
|
|
const isAdmin = userRole === 'admin' || userRole === 'system admin' || userRole === 'system' ||
|
|
|
|
|
accessLevel === 'admin' || accessLevel === 'system';
|
|
|
|
|
|
|
|
|
|
@@ -212,26 +220,6 @@
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 크로스 시스템 링크 URL 설정
|
|
|
|
|
var hostname = window.location.hostname;
|
|
|
|
|
var protocol = window.location.protocol;
|
|
|
|
|
var systemUrls = {};
|
|
|
|
|
if (hostname.includes('technicalkorea.net')) {
|
|
|
|
|
systemUrls.report = protocol + '//tkreport.technicalkorea.net';
|
|
|
|
|
systemUrls.nc = protocol + '//tkqc.technicalkorea.net';
|
|
|
|
|
} else {
|
|
|
|
|
systemUrls.report = protocol + '//' + hostname + ':30180';
|
|
|
|
|
systemUrls.nc = protocol + '//' + hostname + ':30280';
|
|
|
|
|
}
|
|
|
|
|
doc.querySelectorAll('.cross-system-link').forEach(function(link) {
|
|
|
|
|
var system = link.getAttribute('data-system');
|
|
|
|
|
var path = link.getAttribute('data-path');
|
|
|
|
|
if (systemUrls[system]) {
|
|
|
|
|
link.setAttribute('href', systemUrls[system] + path);
|
|
|
|
|
link.setAttribute('target', '_blank');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 저장된 상태 복원 (기본값: 접힌 상태)
|
|
|
|
|
const isCollapsed = localStorage.getItem('sidebarCollapsed') !== 'false';
|
|
|
|
|
const sidebar = doc.querySelector('.sidebar-nav');
|
|
|
|
|
@@ -281,7 +269,8 @@
|
|
|
|
|
logoutButton.addEventListener('click', () => {
|
|
|
|
|
if (confirm('로그아웃 하시겠습니까?')) {
|
|
|
|
|
clearAuthData();
|
|
|
|
|
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';
|
|
|
|
|
if (window.clearSSOAuth) window.clearSSOAuth();
|
|
|
|
|
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login?redirect=' + encodeURIComponent('/pages/dashboard.html');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
@@ -308,7 +297,7 @@
|
|
|
|
|
// ===== 알림 로드 =====
|
|
|
|
|
async function loadNotifications() {
|
|
|
|
|
try {
|
|
|
|
|
const token = window.getSSOToken ? window.getSSOToken() : localStorage.getItem('sso_token');
|
|
|
|
|
const token = getToken();
|
|
|
|
|
if (!token) return;
|
|
|
|
|
|
|
|
|
|
const response = await fetch(`${window.API_BASE_URL}/notifications/unread`, {
|
|
|
|
|
@@ -351,11 +340,11 @@
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const icons = { repair: '🔧', safety: '⚠️', system: '📢', equipment: '🔩', maintenance: '🛠️' };
|
|
|
|
|
const icons = { repair: '\ud83d\udd27', safety: '\u26a0\ufe0f', system: '\ud83d\udce2', equipment: '\ud83d\udea9', maintenance: '\ud83d\udee0\ufe0f' };
|
|
|
|
|
|
|
|
|
|
list.innerHTML = notifications.slice(0, 5).map(n => `
|
|
|
|
|
<div class="notification-item ${n.is_read ? '' : 'unread'}" data-id="${n.notification_id}" data-url="${n.link_url || ''}">
|
|
|
|
|
<div class="notification-item-icon ${n.type || 'repair'}">${icons[n.type] || '🔔'}</div>
|
|
|
|
|
<div class="notification-item-icon ${n.type || 'repair'}">${icons[n.type] || '\ud83d\udd14'}</div>
|
|
|
|
|
<div class="notification-item-content">
|
|
|
|
|
<div class="notification-item-title">${escapeHtml(n.title)}</div>
|
|
|
|
|
<div class="notification-item-desc">${escapeHtml(n.message || '')}</div>
|
|
|
|
|
@@ -367,7 +356,6 @@
|
|
|
|
|
list.querySelectorAll('.notification-item').forEach(item => {
|
|
|
|
|
item.addEventListener('click', () => {
|
|
|
|
|
const url = item.dataset.url;
|
|
|
|
|
// 수리 알림은 클릭해도 읽음 처리 안함 (수리 처리 페이지에서 확인 처리해야 함)
|
|
|
|
|
window.location.href = url || '/pages/admin/notifications.html';
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
@@ -388,8 +376,12 @@
|
|
|
|
|
return date.toLocaleDateString('ko-KR', { month: 'short', day: 'numeric' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// escapeHtml은 api-base.js에서 window.escapeHtml로 전역 제공
|
|
|
|
|
var escapeHtml = window.escapeHtml;
|
|
|
|
|
function escapeHtml(text) {
|
|
|
|
|
if (!text) return '';
|
|
|
|
|
const div = document.createElement('div');
|
|
|
|
|
div.textContent = text;
|
|
|
|
|
return div.innerHTML;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== 날짜/시간 업데이트 =====
|
|
|
|
|
function updateDateTime() {
|
|
|
|
|
@@ -410,12 +402,12 @@
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== 날씨 업데이트 =====
|
|
|
|
|
const WEATHER_ICONS = { clear: '☀️', rain: '🌧️', snow: '❄️', heat: '🔥', cold: '🥶', wind: '💨', fog: '🌫️', dust: '😷', cloudy: '⛅', overcast: '☁️' };
|
|
|
|
|
const WEATHER_ICONS = { clear: '\u2600\ufe0f', rain: '\ud83c\udf27\ufe0f', snow: '\u2744\ufe0f', heat: '\ud83e\udd75', cold: '\ud83e\udd76', wind: '\ud83c\udf2c\ufe0f', fog: '\ud83c\udf2b\ufe0f', dust: '\ud83d\ude37', cloudy: '\u26c5', overcast: '\u2601\ufe0f' };
|
|
|
|
|
const WEATHER_NAMES = { clear: '맑음', rain: '비', snow: '눈', heat: '폭염', cold: '한파', wind: '강풍', fog: '안개', dust: '미세먼지', cloudy: '구름많음', overcast: '흐림' };
|
|
|
|
|
|
|
|
|
|
async function updateWeather() {
|
|
|
|
|
try {
|
|
|
|
|
const token = window.getSSOToken ? window.getSSOToken() : localStorage.getItem('sso_token');
|
|
|
|
|
const token = getToken();
|
|
|
|
|
if (!token) return;
|
|
|
|
|
|
|
|
|
|
// 캐시 확인
|
|
|
|
|
@@ -446,7 +438,7 @@
|
|
|
|
|
const descEl = document.getElementById('weatherDesc');
|
|
|
|
|
if (conditions && conditions.length > 0) {
|
|
|
|
|
const primary = conditions[0];
|
|
|
|
|
if (iconEl) iconEl.textContent = WEATHER_ICONS[primary] || '🌤️';
|
|
|
|
|
if (iconEl) iconEl.textContent = WEATHER_ICONS[primary] || '\ud83c\udf24\ufe0f';
|
|
|
|
|
if (descEl) descEl.textContent = WEATHER_NAMES[primary] || '맑음';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -457,72 +449,60 @@
|
|
|
|
|
|
|
|
|
|
// ===== 메인 초기화 =====
|
|
|
|
|
async function init() {
|
|
|
|
|
console.log('🚀 app-init 시작');
|
|
|
|
|
|
|
|
|
|
// 1. 인증 확인
|
|
|
|
|
if (!isLoggedIn()) {
|
|
|
|
|
clearAuthData();
|
|
|
|
|
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';
|
|
|
|
|
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login?redirect=' + encodeURIComponent(window.location.href);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const currentUser = getUser();
|
|
|
|
|
if (!currentUser || !currentUser.username) {
|
|
|
|
|
clearAuthData();
|
|
|
|
|
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login';
|
|
|
|
|
window.location.href = window.getLoginUrl ? window.getLoginUrl() : '/login?redirect=' + encodeURIComponent(window.location.href);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('✅ 인증 확인:', currentUser.username);
|
|
|
|
|
|
|
|
|
|
const userRole = (currentUser.role || '').toLowerCase();
|
|
|
|
|
const accessLevel = (currentUser.access_level || '').toLowerCase();
|
|
|
|
|
// role 또는 access_level로 관리자 확인
|
|
|
|
|
const isAdmin = userRole === 'admin' || userRole === 'system admin' || userRole === 'system' ||
|
|
|
|
|
accessLevel === 'admin' || accessLevel === 'system';
|
|
|
|
|
|
|
|
|
|
// 2. 페이지 접근 권한 체크 (Admin은 건너뛰기)
|
|
|
|
|
// 2. 페이지 접근 권한 체크 (Admin은 건너뛰기, API 실패시 허용)
|
|
|
|
|
let accessiblePageKeys = [];
|
|
|
|
|
const pageKey = getCurrentPageKey();
|
|
|
|
|
if (!isAdmin) {
|
|
|
|
|
const pageKey = getCurrentPageKey();
|
|
|
|
|
if (pageKey && pageKey !== 'dashboard' && !pageKey.startsWith('profile.')) {
|
|
|
|
|
accessiblePageKeys = await getAccessiblePageKeys(currentUser);
|
|
|
|
|
if (!accessiblePageKeys.includes(pageKey)) {
|
|
|
|
|
alert('이 페이지에 접근할 권한이 없습니다.');
|
|
|
|
|
window.location.href = '/pages/dashboard.html';
|
|
|
|
|
return;
|
|
|
|
|
const pages = await getPageAccess(currentUser);
|
|
|
|
|
if (pages) {
|
|
|
|
|
accessiblePageKeys = pages.filter(p => p.can_access == 1).map(p => p.page_key);
|
|
|
|
|
if (pageKey && pageKey !== 'dashboard' && !pageKey.startsWith('profile.')) {
|
|
|
|
|
if (!accessiblePageKeys.includes(pageKey)) {
|
|
|
|
|
alert('이 페이지에 접근할 권한이 없습니다.');
|
|
|
|
|
window.location.href = '/pages/dashboard.html';
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 네비바 로드 (모바일이면 사이드바 스킵)
|
|
|
|
|
var isMobile = window.innerWidth <= 768;
|
|
|
|
|
|
|
|
|
|
if (!isMobile) {
|
|
|
|
|
// 데스크톱: 사이드바 컨테이너 생성 및 로드
|
|
|
|
|
let sidebarContainer = document.getElementById('sidebar-container');
|
|
|
|
|
if (!sidebarContainer) {
|
|
|
|
|
sidebarContainer = document.createElement('div');
|
|
|
|
|
sidebarContainer.id = 'sidebar-container';
|
|
|
|
|
document.body.prepend(sidebarContainer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log('📥 컴포넌트 로딩 시작 (데스크톱: 네비바+사이드바)');
|
|
|
|
|
await Promise.all([
|
|
|
|
|
loadComponent('navbar', '#navbar-container', (doc) => processNavbar(doc, currentUser, accessiblePageKeys)),
|
|
|
|
|
loadComponent('sidebar-nav', '#sidebar-container', (doc) => processSidebar(doc, currentUser, accessiblePageKeys))
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
setupNavbarEvents();
|
|
|
|
|
setupSidebarEvents();
|
|
|
|
|
document.body.classList.add('has-sidebar');
|
|
|
|
|
} else {
|
|
|
|
|
// 모바일: 네비바만 로드, 사이드바 없음
|
|
|
|
|
console.log('📥 컴포넌트 로딩 시작 (모바일: 네비바만)');
|
|
|
|
|
await loadComponent('navbar', '#navbar-container', (doc) => processNavbar(doc, currentUser, accessiblePageKeys));
|
|
|
|
|
setupNavbarEvents();
|
|
|
|
|
// 3. 사이드바 컨테이너 생성 (없으면)
|
|
|
|
|
let sidebarContainer = document.getElementById('sidebar-container');
|
|
|
|
|
if (!sidebarContainer) {
|
|
|
|
|
sidebarContainer = document.createElement('div');
|
|
|
|
|
sidebarContainer.id = 'sidebar-container';
|
|
|
|
|
document.body.prepend(sidebarContainer);
|
|
|
|
|
}
|
|
|
|
|
console.log('✅ 컴포넌트 로딩 완료');
|
|
|
|
|
|
|
|
|
|
// 4. 네비바와 사이드바 동시 로드
|
|
|
|
|
await Promise.all([
|
|
|
|
|
loadComponent('navbar', '#navbar-container', (doc) => processNavbar(doc, currentUser, accessiblePageKeys)),
|
|
|
|
|
loadComponent('sidebar-nav', '#sidebar-container', (doc) => processSidebar(doc, currentUser, accessiblePageKeys))
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
// 5. 이벤트 설정
|
|
|
|
|
setupNavbarEvents();
|
|
|
|
|
setupSidebarEvents();
|
|
|
|
|
document.body.classList.add('has-sidebar');
|
|
|
|
|
|
|
|
|
|
// 6. 페이지 전환 로딩 인디케이터 설정
|
|
|
|
|
setupPageTransitionLoader();
|
|
|
|
|
@@ -537,73 +517,10 @@
|
|
|
|
|
// 9. 알림 로드 (30초마다 갱신)
|
|
|
|
|
setTimeout(loadNotifications, 200);
|
|
|
|
|
setInterval(loadNotifications, 30000);
|
|
|
|
|
|
|
|
|
|
// 10. PWA 설정 (manifest + 서비스 워커 + iOS 메타태그)
|
|
|
|
|
setupPWA();
|
|
|
|
|
|
|
|
|
|
console.log('✅ app-init 완료');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== PWA 설정 =====
|
|
|
|
|
function setupPWA() {
|
|
|
|
|
// manifest.json 동적 추가
|
|
|
|
|
if (!document.querySelector('link[rel="manifest"]')) {
|
|
|
|
|
var manifest = document.createElement('link');
|
|
|
|
|
manifest.rel = 'manifest';
|
|
|
|
|
manifest.href = '/manifest.json';
|
|
|
|
|
document.head.appendChild(manifest);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// iOS 홈 화면 앱 메타태그
|
|
|
|
|
if (!document.querySelector('meta[name="apple-mobile-web-app-capable"]')) {
|
|
|
|
|
var metaTags = [
|
|
|
|
|
{ name: 'apple-mobile-web-app-capable', content: 'yes' },
|
|
|
|
|
{ name: 'apple-mobile-web-app-status-bar-style', content: 'default' },
|
|
|
|
|
{ name: 'apple-mobile-web-app-title', content: 'TK공장' },
|
|
|
|
|
{ name: 'theme-color', content: '#1e40af' }
|
|
|
|
|
];
|
|
|
|
|
metaTags.forEach(function(tag) {
|
|
|
|
|
var meta = document.createElement('meta');
|
|
|
|
|
meta.name = tag.name;
|
|
|
|
|
meta.content = tag.content;
|
|
|
|
|
document.head.appendChild(meta);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// iOS 아이콘
|
|
|
|
|
var appleIcon = document.createElement('link');
|
|
|
|
|
appleIcon.rel = 'apple-touch-icon';
|
|
|
|
|
appleIcon.href = '/img/icon-192x192.png';
|
|
|
|
|
document.head.appendChild(appleIcon);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 서비스 워커 등록 (킬스위치 포함)
|
|
|
|
|
if ('serviceWorker' in navigator) {
|
|
|
|
|
// 킬스위치: ?sw-kill 파라미터로 서비스 워커 해제
|
|
|
|
|
if (window.location.search.includes('sw-kill')) {
|
|
|
|
|
navigator.serviceWorker.getRegistrations().then(function(regs) {
|
|
|
|
|
regs.forEach(function(r) { r.unregister(); });
|
|
|
|
|
caches.keys().then(function(keys) {
|
|
|
|
|
keys.forEach(function(k) { caches.delete(k); });
|
|
|
|
|
});
|
|
|
|
|
console.log('SW 해제 완료');
|
|
|
|
|
window.location.replace(window.location.pathname);
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
navigator.serviceWorker.register('/sw.js')
|
|
|
|
|
.then(function(reg) {
|
|
|
|
|
console.log('SW 등록 완료');
|
|
|
|
|
})
|
|
|
|
|
.catch(function(err) {
|
|
|
|
|
console.warn('SW 등록 실패:', err);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ===== 페이지 전환 로딩 인디케이터 =====
|
|
|
|
|
function setupPageTransitionLoader() {
|
|
|
|
|
// 로딩 바 스타일 추가
|
|
|
|
|
const style = document.createElement('style');
|
|
|
|
|
style.textContent = `
|
|
|
|
|
#page-loader {
|
|
|
|
|
@@ -634,12 +551,10 @@
|
|
|
|
|
`;
|
|
|
|
|
document.head.appendChild(style);
|
|
|
|
|
|
|
|
|
|
// 로딩 바 엘리먼트 생성
|
|
|
|
|
const loader = document.createElement('div');
|
|
|
|
|
loader.id = 'page-loader';
|
|
|
|
|
document.body.appendChild(loader);
|
|
|
|
|
|
|
|
|
|
// 모든 내부 링크에 클릭 이벤트 추가
|
|
|
|
|
document.addEventListener('click', (e) => {
|
|
|
|
|
const link = e.target.closest('a');
|
|
|
|
|
if (!link) return;
|
|
|
|
|
@@ -647,19 +562,14 @@
|
|
|
|
|
const href = link.getAttribute('href');
|
|
|
|
|
if (!href) return;
|
|
|
|
|
|
|
|
|
|
// 외부 링크, 해시 링크, javascript: 링크 제외
|
|
|
|
|
if (href.startsWith('http') || href.startsWith('#') || href.startsWith('javascript:')) return;
|
|
|
|
|
|
|
|
|
|
// 새 탭 링크 제외
|
|
|
|
|
if (link.target === '_blank') return;
|
|
|
|
|
|
|
|
|
|
// 로딩 시작
|
|
|
|
|
loader.classList.remove('done');
|
|
|
|
|
loader.classList.add('loading');
|
|
|
|
|
document.body.classList.add('page-loading');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 페이지 떠날 때 완료 표시
|
|
|
|
|
window.addEventListener('beforeunload', () => {
|
|
|
|
|
const loader = document.getElementById('page-loader');
|
|
|
|
|
if (loader) {
|
|
|
|
|
@@ -677,5 +587,5 @@
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 전역 노출 (필요시)
|
|
|
|
|
window.appInit = { getUser, clearAuthData, isLoggedIn };
|
|
|
|
|
window.appInit = { getUser, getToken, clearAuthData, isLoggedIn };
|
|
|
|
|
})();
|
|
|
|
|
|