로그인 후 관제 연결 단계 추가
This commit is contained in:
@@ -18,6 +18,7 @@ const state = {
|
||||
vehicleSearch: "",
|
||||
vehicleStatusFilter: "all",
|
||||
eventSource: null,
|
||||
launchTimer: 0,
|
||||
activeView: "locations",
|
||||
adminToken: new URLSearchParams(window.location.search).get("adminToken") ||
|
||||
window.localStorage.getItem("cargoRadarAdminToken") ||
|
||||
@@ -26,12 +27,35 @@ const state = {
|
||||
|
||||
const LOGIN_SESSION_KEY = "cargoRadarLoginReady";
|
||||
const LOGIN_NAME_KEY = "cargoRadarLoginName";
|
||||
const LAUNCH_STEPS = [
|
||||
{
|
||||
title: "관제 데이터 연결 중",
|
||||
message: "운행 차량의 최신 위치를 불러오고 있습니다."
|
||||
},
|
||||
{
|
||||
title: "차량 위치 불러오는 중",
|
||||
message: "수신 중인 차량과 화물 정보를 정리합니다."
|
||||
},
|
||||
{
|
||||
title: "경로 데이터 확인 중",
|
||||
message: "목적지 경로와 도착 시간을 확인합니다."
|
||||
},
|
||||
{
|
||||
title: "관제 화면 준비 완료",
|
||||
message: "실시간 관제 화면으로 이동합니다."
|
||||
}
|
||||
];
|
||||
|
||||
const elements = {
|
||||
loginScreen: document.getElementById("loginScreen"),
|
||||
loginForm: document.getElementById("loginForm"),
|
||||
loginNameInput: document.getElementById("loginNameInput"),
|
||||
loginCodeInput: document.getElementById("loginCodeInput"),
|
||||
launchScreen: document.getElementById("launchScreen"),
|
||||
launchTitle: document.getElementById("launchTitle"),
|
||||
launchMessage: document.getElementById("launchMessage"),
|
||||
launchProgress: document.getElementById("launchProgress"),
|
||||
launchSteps: [...document.querySelectorAll("[data-launch-step]")],
|
||||
connectionStatus: document.getElementById("connectionStatus"),
|
||||
systemClock: document.getElementById("systemClock"),
|
||||
refreshButton: document.getElementById("refreshButton"),
|
||||
@@ -320,11 +344,12 @@ function submitLoginForm(event) {
|
||||
window.localStorage.setItem(LOGIN_NAME_KEY, name || "관제 담당자");
|
||||
window.localStorage.setItem("cargoRadarAdminToken", state.adminToken);
|
||||
elements.loginCodeInput.value = "";
|
||||
hideLoginScreen();
|
||||
beginLaunchSequence();
|
||||
startDataServices();
|
||||
}
|
||||
|
||||
function showLoginScreen(options = {}) {
|
||||
clearLaunchSequence();
|
||||
window.localStorage.removeItem(LOGIN_SESSION_KEY);
|
||||
if (state.eventSource) {
|
||||
state.eventSource.close();
|
||||
@@ -352,6 +377,56 @@ function hideLoginScreen(options = {}) {
|
||||
}, 620);
|
||||
}
|
||||
|
||||
function beginLaunchSequence() {
|
||||
clearLaunchSequence();
|
||||
document.body.classList.add("is-launch-active");
|
||||
setLaunchStep(0);
|
||||
hideLoginScreen();
|
||||
|
||||
let stepIndex = 0;
|
||||
const advance = () => {
|
||||
stepIndex += 1;
|
||||
if (stepIndex >= LAUNCH_STEPS.length) {
|
||||
finishLaunchSequence();
|
||||
return;
|
||||
}
|
||||
|
||||
setLaunchStep(stepIndex);
|
||||
state.launchTimer = window.setTimeout(advance, stepIndex === LAUNCH_STEPS.length - 1 ? 460 : 620);
|
||||
};
|
||||
|
||||
state.launchTimer = window.setTimeout(advance, 620);
|
||||
}
|
||||
|
||||
function setLaunchStep(index) {
|
||||
const step = LAUNCH_STEPS[index] || LAUNCH_STEPS[0];
|
||||
elements.launchTitle.textContent = step.title;
|
||||
elements.launchMessage.textContent = step.message;
|
||||
elements.launchProgress.style.width = `${Math.min(100, ((index + 1) / LAUNCH_STEPS.length) * 100)}%`;
|
||||
|
||||
for (const item of elements.launchSteps) {
|
||||
const itemIndex = Number(item.dataset.launchStep);
|
||||
item.classList.toggle("is-active", itemIndex === index);
|
||||
item.classList.toggle("is-complete", itemIndex < index);
|
||||
}
|
||||
}
|
||||
|
||||
function finishLaunchSequence() {
|
||||
document.body.classList.add("is-launch-closing");
|
||||
state.launchTimer = window.setTimeout(() => {
|
||||
document.body.classList.remove("is-launch-active", "is-launch-closing");
|
||||
state.launchTimer = 0;
|
||||
}, 360);
|
||||
}
|
||||
|
||||
function clearLaunchSequence() {
|
||||
if (state.launchTimer) {
|
||||
window.clearTimeout(state.launchTimer);
|
||||
state.launchTimer = 0;
|
||||
}
|
||||
document.body.classList.remove("is-launch-active", "is-launch-closing");
|
||||
}
|
||||
|
||||
function startSystemClock() {
|
||||
const updateClock = () => {
|
||||
const now = new Date();
|
||||
|
||||
@@ -43,6 +43,22 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="launchScreen" class="launch-screen" aria-label="관제 연결 상태" aria-live="polite">
|
||||
<div class="launch-panel">
|
||||
<span class="section-eyebrow">연결 확인</span>
|
||||
<strong id="launchTitle">관제 데이터 연결 중</strong>
|
||||
<p id="launchMessage">운행 차량의 최신 위치를 불러오고 있습니다.</p>
|
||||
<div class="launch-progress" aria-hidden="true">
|
||||
<span id="launchProgress"></span>
|
||||
</div>
|
||||
<ol class="launch-steps">
|
||||
<li data-launch-step="0" class="is-active">관제 데이터</li>
|
||||
<li data-launch-step="1">차량 위치</li>
|
||||
<li data-launch-step="2">경로 정보</li>
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="app-shell">
|
||||
<header class="topbar">
|
||||
<div class="brand-lockup">
|
||||
|
||||
@@ -70,6 +70,12 @@ body.is-login-active .app-shell {
|
||||
transform: scale(0.985);
|
||||
}
|
||||
|
||||
body.is-launch-active .app-shell {
|
||||
filter: blur(3px) saturate(0.92);
|
||||
pointer-events: none;
|
||||
transform: scale(0.995);
|
||||
}
|
||||
|
||||
.login-screen {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@@ -90,6 +96,126 @@ body.is-login-closing .login-screen {
|
||||
animation: login-screen-out 0.62s ease forwards;
|
||||
}
|
||||
|
||||
.launch-screen {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1900;
|
||||
display: none;
|
||||
place-items: center;
|
||||
padding: 28px;
|
||||
background: rgba(12, 20, 30, 0.28);
|
||||
color: var(--text);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
body.is-launch-active .launch-screen {
|
||||
display: grid;
|
||||
animation: launch-screen-in 0.34s ease both;
|
||||
}
|
||||
|
||||
body.is-launch-closing .launch-screen {
|
||||
animation: launch-screen-out 0.36s ease forwards;
|
||||
}
|
||||
|
||||
.launch-panel {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
width: min(420px, calc(100% - 24px));
|
||||
padding: 22px;
|
||||
border: 1px solid rgba(203, 213, 223, 0.92);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: 0 18px 48px rgba(5, 13, 22, 0.22);
|
||||
animation: launch-panel-in 0.44s cubic-bezier(0.2, 0.72, 0.2, 1) both;
|
||||
}
|
||||
|
||||
.launch-panel > strong {
|
||||
color: var(--navy);
|
||||
font-size: 22px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.launch-panel p {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
word-break: keep-all;
|
||||
}
|
||||
|
||||
.launch-progress {
|
||||
display: block;
|
||||
height: 4px;
|
||||
margin-top: 4px;
|
||||
border-radius: 999px;
|
||||
background: #e2e8ef;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.launch-progress span {
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
background: #174ea6;
|
||||
transition: width 0.46s ease;
|
||||
}
|
||||
|
||||
.launch-steps {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 6px;
|
||||
margin: 4px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.launch-steps li {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
padding: 8px 8px 8px 24px;
|
||||
border: 1px solid #e1e6eb;
|
||||
border-radius: 5px;
|
||||
background: #f8fafc;
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.launch-steps li::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 8px;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border: 2px solid #cbd5df;
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.launch-steps li.is-active {
|
||||
border-color: rgba(23, 78, 166, 0.28);
|
||||
background: #eef5ff;
|
||||
color: #174ea6;
|
||||
}
|
||||
|
||||
.launch-steps li.is-active::before {
|
||||
border-color: #174ea6;
|
||||
background: #174ea6;
|
||||
box-shadow: 0 0 0 4px rgba(23, 78, 166, 0.12);
|
||||
}
|
||||
|
||||
.launch-steps li.is-complete {
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.launch-steps li.is-complete::before {
|
||||
border-color: var(--green);
|
||||
background: var(--green);
|
||||
}
|
||||
|
||||
.login-visual,
|
||||
.login-shade {
|
||||
position: absolute;
|
||||
@@ -336,15 +462,47 @@ body.is-login-closing .login-screen {
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes launch-screen-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes launch-panel-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(16px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes launch-screen-out {
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.login-visual,
|
||||
.login-route-line,
|
||||
.login-copy,
|
||||
.login-card,
|
||||
body.is-login-closing .login-screen {
|
||||
body.is-login-closing .login-screen,
|
||||
body.is-launch-active .launch-screen,
|
||||
body.is-launch-closing .launch-screen,
|
||||
.launch-panel {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.launch-progress span,
|
||||
.app-shell {
|
||||
transition: none;
|
||||
}
|
||||
@@ -1803,6 +1961,15 @@ body.is-login-closing .login-screen {
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.launch-panel {
|
||||
width: min(390px, calc(100% - 20px));
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.launch-steps {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.login-content {
|
||||
align-content: center;
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
Reference in New Issue
Block a user