feat: 키오스크 마일리지 적립 시스템 추가
- 키오스크 전체화면 웹 UI (/kiosk) - QR 표시 + 전화번호 숫자패드 입력 - 키오스크 API 4개 (trigger, current, claim, kiosk 페이지) - POS GUI에 "키오스크 적립" 버튼 추가 (Flask 서버로 HTTP 트리거) - NHN Cloud 알림톡 발송 모듈 (적립 완료 시 자동 발송) - Qt 플랫폼 플러그인 경로 자동 설정 (no Qt platform plugin 에러 해결) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
638
backend/templates/kiosk.html
Normal file
638
backend/templates/kiosk.html
Normal file
@@ -0,0 +1,638 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>키오스크 적립 - 청춘약국</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;500;700;900&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans KR', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
background: #f5f7fa;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* ── 헤더 ── */
|
||||
.header {
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
padding: 20px 32px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.header-logo {
|
||||
color: #fff;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
.header-time {
|
||||
color: rgba(255,255,255,0.8);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* ── 메인 컨텐츠 ── */
|
||||
.main {
|
||||
height: calc(100vh - 70px);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* ── 화면 상태 ── */
|
||||
.screen { display: none; width: 100%; max-width: 900px; }
|
||||
.screen.active { display: flex; }
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
대기 화면
|
||||
══════════════════════════════════════ */
|
||||
.idle-screen {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
gap: 24px;
|
||||
}
|
||||
.idle-icon {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%);
|
||||
border-radius: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 56px;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); opacity: 0.9; }
|
||||
50% { transform: scale(1.05); opacity: 1; }
|
||||
}
|
||||
.idle-title {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
color: #1e1b4b;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
.idle-subtitle {
|
||||
font-size: 18px;
|
||||
color: #6b7280;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
적립 화면
|
||||
══════════════════════════════════════ */
|
||||
.claim-screen {
|
||||
flex-direction: row;
|
||||
gap: 48px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* 왼쪽: QR + 안내 */
|
||||
.claim-left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 24px;
|
||||
}
|
||||
.claim-info-card {
|
||||
background: #fff;
|
||||
border-radius: 20px;
|
||||
padding: 28px 36px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.06);
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
.claim-amount-label {
|
||||
font-size: 15px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.claim-amount {
|
||||
font-size: 36px;
|
||||
font-weight: 900;
|
||||
color: #1e1b4b;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
.claim-points {
|
||||
font-size: 20px;
|
||||
color: #6366f1;
|
||||
font-weight: 700;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.qr-container {
|
||||
background: #fff;
|
||||
border-radius: 20px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,0.06);
|
||||
}
|
||||
.qr-container img {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
}
|
||||
.qr-hint {
|
||||
font-size: 15px;
|
||||
color: #6b7280;
|
||||
text-align: center;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
/* 구분선 */
|
||||
.divider {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.divider-line {
|
||||
width: 2px;
|
||||
height: 80px;
|
||||
background: #e5e7eb;
|
||||
}
|
||||
.divider-text {
|
||||
font-size: 16px;
|
||||
color: #9ca3af;
|
||||
font-weight: 500;
|
||||
background: #f5f7fa;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
/* 오른쪽: 전화번호 입력 */
|
||||
.claim-right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
}
|
||||
.phone-section-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #1e1b4b;
|
||||
}
|
||||
.phone-display {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
background: #fff;
|
||||
border: 3px solid #e5e7eb;
|
||||
border-radius: 16px;
|
||||
padding: 16px 24px;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
color: #1e1b4b;
|
||||
letter-spacing: 2px;
|
||||
min-height: 68px;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
.phone-display.focus {
|
||||
border-color: #6366f1;
|
||||
}
|
||||
.phone-display.error {
|
||||
border-color: #ef4444;
|
||||
animation: shake 0.3s;
|
||||
}
|
||||
@keyframes shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
25% { transform: translateX(-8px); }
|
||||
75% { transform: translateX(8px); }
|
||||
}
|
||||
|
||||
/* 숫자 패드 */
|
||||
.numpad {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
}
|
||||
.numpad-btn {
|
||||
background: #fff;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 14px;
|
||||
padding: 18px;
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
color: #1e1b4b;
|
||||
cursor: pointer;
|
||||
transition: all 0.1s;
|
||||
font-family: inherit;
|
||||
}
|
||||
.numpad-btn:active {
|
||||
background: #6366f1;
|
||||
color: #fff;
|
||||
border-color: #6366f1;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
.numpad-btn.delete {
|
||||
background: #fef2f2;
|
||||
border-color: #fecaca;
|
||||
color: #ef4444;
|
||||
font-size: 20px;
|
||||
}
|
||||
.numpad-btn.delete:active {
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
}
|
||||
.numpad-btn.clear {
|
||||
background: #f5f5f5;
|
||||
border-color: #d4d4d4;
|
||||
color: #737373;
|
||||
font-size: 16px;
|
||||
}
|
||||
.numpad-btn.clear:active {
|
||||
background: #737373;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* 적립 버튼 */
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
max-width: 360px;
|
||||
padding: 18px;
|
||||
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 14px;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
transition: all 0.2s;
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
.submit-btn:active {
|
||||
transform: scale(0.97);
|
||||
}
|
||||
.submit-btn:disabled {
|
||||
background: #d1d5db;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════
|
||||
성공 화면
|
||||
══════════════════════════════════════ */
|
||||
.success-screen {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
gap: 20px;
|
||||
}
|
||||
.success-icon {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background: linear-gradient(135deg, #dcfce7 0%, #bbf7d0 100%);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 60px;
|
||||
animation: pop 0.4s ease-out;
|
||||
}
|
||||
@keyframes pop {
|
||||
0% { transform: scale(0); }
|
||||
80% { transform: scale(1.1); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
.success-title {
|
||||
font-size: 36px;
|
||||
font-weight: 900;
|
||||
color: #16a34a;
|
||||
}
|
||||
.success-points {
|
||||
font-size: 48px;
|
||||
font-weight: 900;
|
||||
color: #1e1b4b;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
.success-balance {
|
||||
font-size: 20px;
|
||||
color: #6b7280;
|
||||
}
|
||||
.success-balance strong {
|
||||
color: #6366f1;
|
||||
font-weight: 700;
|
||||
}
|
||||
.success-countdown {
|
||||
font-size: 15px;
|
||||
color: #9ca3af;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* ── 에러 메시지 ── */
|
||||
.error-msg {
|
||||
color: #ef4444;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
min-height: 22px;
|
||||
}
|
||||
|
||||
/* ── 로딩 ── */
|
||||
.loading-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.3);
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
}
|
||||
.loading-overlay.active { display: flex; }
|
||||
.loading-spinner {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border: 6px solid #e5e7eb;
|
||||
border-top-color: #6366f1;
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
|
||||
/* ── 반응형 (세로 모드) ── */
|
||||
@media (max-width: 700px) {
|
||||
.claim-screen { flex-direction: column; gap: 24px; }
|
||||
.divider { flex-direction: row; }
|
||||
.divider-line { width: 80px; height: 2px; }
|
||||
.claim-amount { font-size: 28px; }
|
||||
.qr-container img { width: 150px; height: 150px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- 헤더 -->
|
||||
<div class="header">
|
||||
<div class="header-logo">청춘약국 마일리지</div>
|
||||
<div class="header-time" id="headerTime"></div>
|
||||
</div>
|
||||
|
||||
<!-- 메인 -->
|
||||
<div class="main">
|
||||
|
||||
<!-- 1. 대기 화면 -->
|
||||
<div class="screen idle-screen active" id="idleScreen">
|
||||
<div class="idle-icon">💊</div>
|
||||
<div class="idle-title">마일리지 적립</div>
|
||||
<div class="idle-subtitle">
|
||||
약사님이 적립을 시작하면<br>
|
||||
이 화면에서 바로 적립할 수 있습니다
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2. 적립 화면 -->
|
||||
<div class="screen claim-screen" id="claimScreen">
|
||||
<!-- 왼쪽: QR + 금액 -->
|
||||
<div class="claim-left">
|
||||
<div class="claim-info-card">
|
||||
<div class="claim-amount-label">결제 금액</div>
|
||||
<div class="claim-amount" id="claimAmount">0원</div>
|
||||
<div class="claim-points">적립 <span id="claimPoints">0</span>P</div>
|
||||
</div>
|
||||
<div class="qr-container" id="qrContainer" style="display:none;">
|
||||
<img id="qrImage" src="" alt="QR Code">
|
||||
<div class="qr-hint">휴대폰으로 QR을 스캔하여<br>적립할 수도 있습니다</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 구분선 -->
|
||||
<div class="divider" id="dividerEl" style="display:none;">
|
||||
<div class="divider-line"></div>
|
||||
<div class="divider-text">또는</div>
|
||||
<div class="divider-line"></div>
|
||||
</div>
|
||||
|
||||
<!-- 오른쪽: 전화번호 입력 -->
|
||||
<div class="claim-right">
|
||||
<div class="phone-section-title">전화번호로 적립하기</div>
|
||||
<div class="phone-display" id="phoneDisplay">-</div>
|
||||
<div class="error-msg" id="errorMsg"></div>
|
||||
<div class="numpad">
|
||||
<button class="numpad-btn" onclick="numPress('1')">1</button>
|
||||
<button class="numpad-btn" onclick="numPress('2')">2</button>
|
||||
<button class="numpad-btn" onclick="numPress('3')">3</button>
|
||||
<button class="numpad-btn" onclick="numPress('4')">4</button>
|
||||
<button class="numpad-btn" onclick="numPress('5')">5</button>
|
||||
<button class="numpad-btn" onclick="numPress('6')">6</button>
|
||||
<button class="numpad-btn" onclick="numPress('7')">7</button>
|
||||
<button class="numpad-btn" onclick="numPress('8')">8</button>
|
||||
<button class="numpad-btn" onclick="numPress('9')">9</button>
|
||||
<button class="numpad-btn clear" onclick="numClear()">전체삭제</button>
|
||||
<button class="numpad-btn" onclick="numPress('0')">0</button>
|
||||
<button class="numpad-btn delete" onclick="numDelete()">← 삭제</button>
|
||||
</div>
|
||||
<button class="submit-btn" id="submitBtn" onclick="submitClaim()" disabled>적립하기</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 3. 성공 화면 -->
|
||||
<div class="screen success-screen" id="successScreen">
|
||||
<div class="success-icon">✓</div>
|
||||
<div class="success-title">적립 완료!</div>
|
||||
<div class="success-points" id="successPoints">0P</div>
|
||||
<div class="success-balance">총 잔액: <strong id="successBalance">0P</strong></div>
|
||||
<div class="success-countdown" id="successCountdown"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 로딩 오버레이 -->
|
||||
<div class="loading-overlay" id="loadingOverlay">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// ── 상태 관리 ──
|
||||
let phoneNumber = '';
|
||||
let currentSession = null;
|
||||
let pollingInterval = null;
|
||||
let successTimeout = null;
|
||||
|
||||
// ── 화면 전환 ──
|
||||
function showScreen(name) {
|
||||
document.querySelectorAll('.screen').forEach(s => s.classList.remove('active'));
|
||||
document.getElementById(name + 'Screen').classList.add('active');
|
||||
}
|
||||
|
||||
// ── 시계 ──
|
||||
function updateClock() {
|
||||
const now = new Date();
|
||||
const h = String(now.getHours()).padStart(2, '0');
|
||||
const m = String(now.getMinutes()).padStart(2, '0');
|
||||
document.getElementById('headerTime').textContent = h + ':' + m;
|
||||
}
|
||||
updateClock();
|
||||
setInterval(updateClock, 30000);
|
||||
|
||||
// ── 전화번호 포맷 (010-1234-5678) ──
|
||||
function formatPhone(num) {
|
||||
if (num.length <= 3) return num;
|
||||
if (num.length <= 7) return num.slice(0, 3) + '-' + num.slice(3);
|
||||
return num.slice(0, 3) + '-' + num.slice(3, 7) + '-' + num.slice(7);
|
||||
}
|
||||
|
||||
function updatePhoneDisplay() {
|
||||
const display = document.getElementById('phoneDisplay');
|
||||
const btn = document.getElementById('submitBtn');
|
||||
|
||||
if (phoneNumber.length === 0) {
|
||||
display.textContent = '-';
|
||||
display.classList.remove('focus');
|
||||
btn.disabled = true;
|
||||
} else {
|
||||
display.textContent = formatPhone(phoneNumber);
|
||||
display.classList.add('focus');
|
||||
btn.disabled = phoneNumber.length < 10;
|
||||
}
|
||||
|
||||
display.classList.remove('error');
|
||||
document.getElementById('errorMsg').textContent = '';
|
||||
}
|
||||
|
||||
// ── 숫자 패드 ──
|
||||
function numPress(digit) {
|
||||
if (phoneNumber.length >= 11) return;
|
||||
phoneNumber += digit;
|
||||
updatePhoneDisplay();
|
||||
}
|
||||
|
||||
function numDelete() {
|
||||
phoneNumber = phoneNumber.slice(0, -1);
|
||||
updatePhoneDisplay();
|
||||
}
|
||||
|
||||
function numClear() {
|
||||
phoneNumber = '';
|
||||
updatePhoneDisplay();
|
||||
}
|
||||
|
||||
// ── 적립 제출 ──
|
||||
async function submitClaim() {
|
||||
if (phoneNumber.length < 10) {
|
||||
document.getElementById('phoneDisplay').classList.add('error');
|
||||
document.getElementById('errorMsg').textContent = '전화번호를 정확히 입력해주세요';
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('loadingOverlay').classList.add('active');
|
||||
document.getElementById('submitBtn').disabled = true;
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/kiosk/claim', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phone: phoneNumber })
|
||||
});
|
||||
const data = await resp.json();
|
||||
|
||||
document.getElementById('loadingOverlay').classList.remove('active');
|
||||
|
||||
if (data.success) {
|
||||
showSuccess(data.points, data.balance);
|
||||
} else {
|
||||
document.getElementById('phoneDisplay').classList.add('error');
|
||||
document.getElementById('errorMsg').textContent = data.message || '적립 실패';
|
||||
document.getElementById('submitBtn').disabled = false;
|
||||
}
|
||||
} catch (err) {
|
||||
document.getElementById('loadingOverlay').classList.remove('active');
|
||||
document.getElementById('errorMsg').textContent = '서버 연결 실패';
|
||||
document.getElementById('submitBtn').disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 성공 화면 ──
|
||||
function showSuccess(points, balance) {
|
||||
document.getElementById('successPoints').textContent = points.toLocaleString() + 'P';
|
||||
document.getElementById('successBalance').textContent = balance.toLocaleString() + 'P';
|
||||
showScreen('success');
|
||||
|
||||
// 5초 카운트다운 후 대기 화면
|
||||
let countdown = 5;
|
||||
const el = document.getElementById('successCountdown');
|
||||
el.textContent = countdown + '초 후 처음 화면으로 돌아갑니다';
|
||||
|
||||
if (successTimeout) clearInterval(successTimeout);
|
||||
successTimeout = setInterval(() => {
|
||||
countdown--;
|
||||
if (countdown <= 0) {
|
||||
clearInterval(successTimeout);
|
||||
resetToIdle();
|
||||
} else {
|
||||
el.textContent = countdown + '초 후 처음 화면으로 돌아갑니다';
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// ── 대기 화면 복귀 ──
|
||||
function resetToIdle() {
|
||||
phoneNumber = '';
|
||||
currentSession = null;
|
||||
updatePhoneDisplay();
|
||||
showScreen('idle');
|
||||
}
|
||||
|
||||
// ── 폴링: 키오스크 세션 확인 (2초) ──
|
||||
async function pollKioskSession() {
|
||||
try {
|
||||
const resp = await fetch('/api/kiosk/current');
|
||||
const data = await resp.json();
|
||||
|
||||
if (data.active && !currentSession) {
|
||||
// 새 세션 감지 → 적립 화면 전환
|
||||
currentSession = data;
|
||||
phoneNumber = '';
|
||||
updatePhoneDisplay();
|
||||
|
||||
// 금액, 포인트 표시
|
||||
document.getElementById('claimAmount').textContent =
|
||||
data.amount.toLocaleString() + '원';
|
||||
document.getElementById('claimPoints').textContent =
|
||||
data.points.toLocaleString();
|
||||
|
||||
// QR 코드 (있으면 표시)
|
||||
if (data.qr_url) {
|
||||
document.getElementById('qrImage').src =
|
||||
'https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=' +
|
||||
encodeURIComponent(data.qr_url);
|
||||
document.getElementById('qrContainer').style.display = '';
|
||||
document.getElementById('dividerEl').style.display = '';
|
||||
} else {
|
||||
document.getElementById('qrContainer').style.display = 'none';
|
||||
document.getElementById('dividerEl').style.display = 'none';
|
||||
}
|
||||
|
||||
showScreen('claim');
|
||||
} else if (!data.active && currentSession) {
|
||||
// 세션 종료 (다른 곳에서 적립 완료 등)
|
||||
resetToIdle();
|
||||
}
|
||||
} catch (err) {
|
||||
// 네트워크 오류 시 무시 (다음 폴링에서 재시도)
|
||||
}
|
||||
}
|
||||
|
||||
// ── 폴링 시작 ──
|
||||
pollingInterval = setInterval(pollKioskSession, 1000);
|
||||
pollKioskSession(); // 즉시 1회 실행
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user