/* ================================================ login.js — 登录层逻辑(WebView2 消息通信版) 所有与后端的通信通过 Bridge(bridge.js)完成 不再使用 HTTP fetch,改用 postMessage / on ================================================ */ /* 动态背景粒子 */ (function () { const canvas = document.getElementById('bg-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const COUNT = 35, DIST = 100; let W, H, particles, animId, running = true; let lastTime = 0; const FPS = 30, INTERVAL = 1000 / FPS; function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; } function mkP() { return { x: Math.random()*W, y: Math.random()*H, vx: (Math.random()-.5)*.4, vy: (Math.random()-.5)*.4, r: Math.random()*2+1 }; } function init() { resize(); particles = Array.from({length:COUNT}, mkP); } function draw(ts) { if (!running) return; animId = requestAnimationFrame(draw); if (ts - lastTime < INTERVAL) return; lastTime = ts; ctx.clearRect(0,0,W,H); const g = ctx.createLinearGradient(0,0,W,H); const dark = document.documentElement.getAttribute('data-theme') === 'dark'; if (dark) { g.addColorStop(0,'#0f1117'); g.addColorStop(1,'#1a1d27'); } else { g.addColorStop(0,'#dfe6f5'); g.addColorStop(1,'#e4eaf5'); } ctx.fillStyle = g; ctx.fillRect(0,0,W,H); ctx.lineWidth = 1; for (let i=0;iW) p.vx*=-1; if(p.y<0||p.y>H) p.vy*=-1; } } window.addEventListener('resize', resize); init(); animId = requestAnimationFrame(draw); window._stopParticles = function() { running = false; if (animId) cancelAnimationFrame(animId); }; })(); /* 面板切换 */ function showLoginPanel(id) { document.querySelectorAll('.card').forEach(c => c.classList.remove('active')); const el = document.getElementById(id); if (el) el.classList.add('active'); } /* 进入主界面 */ function enterMainApp(user, expireTime) { window.APP = window.APP || {}; window.APP.authAccount = user; window.APP.authExpire = expireTime || '--'; localStorage.setItem('auth_user', user); localStorage.setItem('auth_expire', expireTime || '--'); localStorage.setItem('auth_ok', '1'); _switchToMain(); } function _switchToMain() { const loginEl = document.getElementById('login-layer'); const mainEl = document.getElementById('main-layer'); if (loginEl) loginEl.style.display = 'none'; if (mainEl) { mainEl.style.display = 'flex'; mainEl.style.width = '100%'; mainEl.style.height = '100%'; } if (typeof window._stopParticles === 'function') window._stopParticles(); const saved = localStorage.getItem('theme') || 'light'; const btn = document.getElementById('themeToggleBtn'); if (btn) btn.textContent = saved === 'dark' ? '🌙' : '☀️'; if (typeof initApp === 'function') initApp(); } /* 登录页主题切换 */ function toggleLoginTheme() { const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; const t = isDark ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', t); localStorage.setItem('theme', t); const btn = document.getElementById('loginThemeBtn'); if (btn) btn.textContent = t === 'dark' ? '🌙' : '☀️'; } /* 使用主界面的 showToast */ function loginToast(msg, type) { if (typeof showToast === 'function') { showToast(msg, type === 'error' ? 'err' : ''); } } /* 按钮 loading 状态 */ function setBtn(id, loading, label) { const b = document.getElementById(id); if (!b) return; b.disabled = loading; b.textContent = loading ? '请 稍 候 ...' : label; } /* ── 监听后端推送"自动填卡"命令 ─────────────────── 后端在 WebView2 收到"请求自动填卡"后,读取 ini 再通过 sendWebMessage 回发 {"命令":"自动填卡","账号":"...","密码":"..."} ──────────────────────────────────────────────── */ Bridge.on('自动填卡', function (data) { const user = data['账号'] || ''; const pass = data['密码'] || ''; if (user) { const uEl = document.getElementById('login-user'); const pEl = document.getElementById('login-pass'); if (uEl) uEl.value = user; if (pEl) pEl.value = pass; } }); /* 页面就绪后请求自动填卡(替代原来的 HTTP /api/getConfig) */ (function autoFill() { Bridge.Auth.requestAutoFill(); })(); /* ── 刷新时自动重新验证登录状态 ─────────────────── */ if (localStorage.getItem('auth_ok') === '1') { const savedUser = localStorage.getItem('auth_user') || ''; // 通过 WebView2 发送"账号登录"命令重新验证 // (后端若 pd.ini 中存有密码则可自动续验,此处仅做静默检测) // 若无法连接,清除登录态避免永久停留主界面 const _autoVerifyTimeout = setTimeout(function () { localStorage.removeItem('auth_ok'); localStorage.removeItem('auth_user'); localStorage.removeItem('auth_expire'); }, 3000); Bridge.once('登录结果', function (res) { clearTimeout(_autoVerifyTimeout); if (res['成功'] === true) { window.APP = window.APP || {}; window.APP.authAccount = savedUser; window.APP.authExpire = res['到期时间'] || localStorage.getItem('auth_expire') || '--'; localStorage.setItem('auth_expire', window.APP.authExpire); _switchToMain(); } else { localStorage.removeItem('auth_ok'); localStorage.removeItem('auth_user'); localStorage.removeItem('auth_expire'); loginToast('账号已到期或被禁用,请重新登录', 'error'); } }); // 从 ini 中取到密码后再二次验证,此处仅触发自动填卡 Bridge.Auth.requestAutoFill(); } /* ── 登录 ────────────────────────────────────── */ document.getElementById('btn-login').addEventListener('click', async function () { const user = document.getElementById('login-user').value.trim(); const pass = document.getElementById('login-pass').value.trim(); const agree = document.getElementById('login-agree').checked; if (!user) return loginToast('请输入账号', 'error'); if (!pass) return loginToast('请输入密码', 'error'); if (!agree) return loginToast('请先同意用户使用协议', 'error'); setBtn('btn-login', true, '登 录'); try { const res = await Bridge.Auth.login(user, pass); if (res['成功'] === true) { const expireText = res['到期时间'] || res['消息'] || '--'; enterMainApp(user, expireText); } else { loginToast(res['消息'] || '账号或密码错误', 'error'); } } catch (e) { loginToast(e.message || '请求超时,请重试', 'error'); } finally { setBtn('btn-login', false, '登 录'); } }); /* ── 注册 ────────────────────────────────────── */ document.getElementById('btn-register').addEventListener('click', async function () { const user = document.getElementById('reg-user').value.trim(); const sec = document.getElementById('reg-sec').value.trim(); const pass = document.getElementById('reg-pass').value.trim(); const card = document.getElementById('reg-card').value.trim(); if (!user) return loginToast('请输入注册账号', 'error'); if (!sec) return loginToast('请输入密保', 'error'); if (!pass) return loginToast('请输入密码', 'error'); if (!card) return loginToast('请输入卡密', 'error'); setBtn('btn-register', true, '注 册'); try { const res = await Bridge.Auth.register(user, pass, sec, card); if (res['成功'] === true) { loginToast('注册成功,请登录'); showLoginPanel('panel-login'); } else { loginToast(res['消息'] || '注册失败', 'error'); } } catch (e) { loginToast(e.message || '请求超时,请重试', 'error'); } finally { setBtn('btn-register', false, '注 册'); } }); /* ── 续费 ────────────────────────────────────── */ document.getElementById('btn-renew').addEventListener('click', async function () { const user = document.getElementById('renew-user').value.trim(); const card = document.getElementById('renew-card').value.trim(); if (!user) return loginToast('请输入续费账号', 'error'); if (!card) return loginToast('请输入卡密', 'error'); setBtn('btn-renew', true, '续 费'); try { const res = await Bridge.Auth.renew(user, card); if (res['成功'] === true) { loginToast('续费成功'); } else { loginToast(res['消息'] || '卡密无效', 'error'); } } catch (e) { loginToast(e.message || '请求超时,请重试', 'error'); } finally { setBtn('btn-renew', false, '续 费'); } }); /* ── 修改密码 ─────────────────────────────────── */ document.getElementById('btn-chpwd').addEventListener('click', async function () { const user = document.getElementById('chpwd-user').value.trim(); const sec = document.getElementById('chpwd-sec').value.trim(); const np = document.getElementById('chpwd-new').value.trim(); if (!user) return loginToast('请输入账号', 'error'); if (!sec) return loginToast('请输入密保', 'error'); if (!np) return loginToast('请输入新密码', 'error'); if (np.length > 33) return loginToast('新密码长度不能超过 33 位', 'error'); setBtn('btn-chpwd', true, '更 改 密 码'); try { const res = await Bridge.Auth.changePwd(user, sec, np); if (res['成功'] === true) { loginToast('密码修改成功,请重新登录'); showLoginPanel('panel-login'); } else { loginToast(res['消息'] || '修改失败', 'error'); } } catch (e) { loginToast(e.message || '请求超时,请重试', 'error'); } finally { setBtn('btn-chpwd', false, '更 改 密 码'); } }); showLoginPanel('panel-login'); /* 协议弹窗 */ function openModal() { const overlay = document.getElementById('modal-agreement'); if (!overlay) return; overlay.classList.remove('hiding'); overlay.classList.add('show'); const body = document.getElementById('modal-body'); if (body) body.scrollTop = 0; } function closeModal() { const overlay = document.getElementById('modal-agreement'); if (!overlay) return; overlay.classList.remove('show'); overlay.classList.add('hiding'); setTimeout(() => overlay.classList.remove('hiding'), 300); } document.getElementById('link-agreement')?.addEventListener('click', function (e) { e.preventDefault(); openModal(); }); document.getElementById('modal-close-btn')?.addEventListener('click', closeModal); document.getElementById('btn-agree')?.addEventListener('click', function () { document.getElementById('login-agree').checked = true; closeModal(); loginToast('已同意用户使用协议'); }); document.getElementById('modal-agreement')?.addEventListener('click', function (e) { if (e.target === e.currentTarget) closeModal(); }); /* 登录页主题图标初始化 */ (function () { const saved = localStorage.getItem('theme') || 'light'; const btn = document.getElementById('loginThemeBtn'); if (btn) btn.textContent = saved === 'dark' ? '🌙' : '☀️'; })();