로그인 후 관제 연결 단계 추가
This commit is contained in:
@@ -18,6 +18,7 @@ const state = {
|
|||||||
vehicleSearch: "",
|
vehicleSearch: "",
|
||||||
vehicleStatusFilter: "all",
|
vehicleStatusFilter: "all",
|
||||||
eventSource: null,
|
eventSource: null,
|
||||||
|
launchTimer: 0,
|
||||||
activeView: "locations",
|
activeView: "locations",
|
||||||
adminToken: new URLSearchParams(window.location.search).get("adminToken") ||
|
adminToken: new URLSearchParams(window.location.search).get("adminToken") ||
|
||||||
window.localStorage.getItem("cargoRadarAdminToken") ||
|
window.localStorage.getItem("cargoRadarAdminToken") ||
|
||||||
@@ -26,12 +27,35 @@ const state = {
|
|||||||
|
|
||||||
const LOGIN_SESSION_KEY = "cargoRadarLoginReady";
|
const LOGIN_SESSION_KEY = "cargoRadarLoginReady";
|
||||||
const LOGIN_NAME_KEY = "cargoRadarLoginName";
|
const LOGIN_NAME_KEY = "cargoRadarLoginName";
|
||||||
|
const LAUNCH_STEPS = [
|
||||||
|
{
|
||||||
|
title: "관제 데이터 연결 중",
|
||||||
|
message: "운행 차량의 최신 위치를 불러오고 있습니다."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "차량 위치 불러오는 중",
|
||||||
|
message: "수신 중인 차량과 화물 정보를 정리합니다."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "경로 데이터 확인 중",
|
||||||
|
message: "목적지 경로와 도착 시간을 확인합니다."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "관제 화면 준비 완료",
|
||||||
|
message: "실시간 관제 화면으로 이동합니다."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const elements = {
|
const elements = {
|
||||||
loginScreen: document.getElementById("loginScreen"),
|
loginScreen: document.getElementById("loginScreen"),
|
||||||
loginForm: document.getElementById("loginForm"),
|
loginForm: document.getElementById("loginForm"),
|
||||||
loginNameInput: document.getElementById("loginNameInput"),
|
loginNameInput: document.getElementById("loginNameInput"),
|
||||||
loginCodeInput: document.getElementById("loginCodeInput"),
|
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"),
|
connectionStatus: document.getElementById("connectionStatus"),
|
||||||
systemClock: document.getElementById("systemClock"),
|
systemClock: document.getElementById("systemClock"),
|
||||||
refreshButton: document.getElementById("refreshButton"),
|
refreshButton: document.getElementById("refreshButton"),
|
||||||
@@ -320,11 +344,12 @@ function submitLoginForm(event) {
|
|||||||
window.localStorage.setItem(LOGIN_NAME_KEY, name || "관제 담당자");
|
window.localStorage.setItem(LOGIN_NAME_KEY, name || "관제 담당자");
|
||||||
window.localStorage.setItem("cargoRadarAdminToken", state.adminToken);
|
window.localStorage.setItem("cargoRadarAdminToken", state.adminToken);
|
||||||
elements.loginCodeInput.value = "";
|
elements.loginCodeInput.value = "";
|
||||||
hideLoginScreen();
|
beginLaunchSequence();
|
||||||
startDataServices();
|
startDataServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showLoginScreen(options = {}) {
|
function showLoginScreen(options = {}) {
|
||||||
|
clearLaunchSequence();
|
||||||
window.localStorage.removeItem(LOGIN_SESSION_KEY);
|
window.localStorage.removeItem(LOGIN_SESSION_KEY);
|
||||||
if (state.eventSource) {
|
if (state.eventSource) {
|
||||||
state.eventSource.close();
|
state.eventSource.close();
|
||||||
@@ -352,6 +377,56 @@ function hideLoginScreen(options = {}) {
|
|||||||
}, 620);
|
}, 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() {
|
function startSystemClock() {
|
||||||
const updateClock = () => {
|
const updateClock = () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|||||||
@@ -43,6 +43,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</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">
|
<div class="app-shell">
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<div class="brand-lockup">
|
<div class="brand-lockup">
|
||||||
|
|||||||
@@ -70,6 +70,12 @@ body.is-login-active .app-shell {
|
|||||||
transform: scale(0.985);
|
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 {
|
.login-screen {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -90,6 +96,126 @@ body.is-login-closing .login-screen {
|
|||||||
animation: login-screen-out 0.62s ease forwards;
|
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-visual,
|
||||||
.login-shade {
|
.login-shade {
|
||||||
position: absolute;
|
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) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.login-visual,
|
.login-visual,
|
||||||
.login-route-line,
|
.login-route-line,
|
||||||
.login-copy,
|
.login-copy,
|
||||||
.login-card,
|
.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;
|
animation: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.launch-progress span,
|
||||||
.app-shell {
|
.app-shell {
|
||||||
transition: none;
|
transition: none;
|
||||||
}
|
}
|
||||||
@@ -1803,6 +1961,15 @@ body.is-login-closing .login-screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 860px) {
|
@media (max-width: 860px) {
|
||||||
|
.launch-panel {
|
||||||
|
width: min(390px, calc(100% - 20px));
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.launch-steps {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.login-content {
|
.login-content {
|
||||||
align-content: center;
|
align-content: center;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
Reference in New Issue
Block a user