iT邦幫忙

0

Day 15 - 22 :登入系統實作規劃

  • 分享至 

  • xImage
  •  
在前面的天數中,我們成功打造了具備 SPA 頁面切換、即時記帳、動態月曆、Chart.js 統計圖表與 CSV 匯出的完整記帳網頁。然而,當一個網站開始記錄使用者的財務數據(開銷、餘額、消費習慣)時,一個致命的問題就浮現了——「隱私與資安」。如果筆記型電腦借給朋友、或是與室友共用電腦,任何人打開網頁都能對你的每一筆花費一覽無遺,所以,在後半段的部分我將新增前端會員登入與密碼保護系統。

Day 15:金庫加鎖,設計全螢幕密碼登入與解鎖遮罩 UI

在網頁最外層加入一個全螢幕的「登入解鎖遮罩(Lock Screen UI)」。透過高權重的 z-index,在使用者輸入正確密碼前,完整保護底下的記帳數據與圖表。

<!-- Day 15:全螢幕解鎖登入遮罩 -->
<div id="lock-screen" style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(240, 244, 248, 0.95); z-index: 9999; display: flex; justify-content: center; align-items: center; flex-direction: column;">
    <div style="background: white; padding: 30px 40px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); text-align: center; max-width: 350px; width: 100%;">
        <h2 id="lock-title" style="margin-top: 0; color: #333;">金庫已上鎖</h2>
        <p id="lock-desc" style="color: #666; font-size: 14px; margin-bottom: 20px;">請輸入您的密碼以解鎖您的專屬記帳本</p>
        <input type="password" id="lock-password-input" placeholder="請輸入密碼" style="width: 100%; padding: 10px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 6px; box-sizing: border-box; font-size: 16px;">
        <button id="lock-submit-btn" style="width: 100%; padding: 10px; background-color: #1967d2; color: white; border: none; border-radius: 6px; font-weight: bold; cursor: pointer; font-size: 16px;">解鎖 / 設定密碼</button>
    </div>
</div>

▲ 說明:利用 position: fixed 與 vw/vh 讓遮罩完美覆蓋整個畫面,搭配 z-index: 9999 確保它永遠浮在最上層,阻擋所有未授權的操作。

Day 16:密碼註冊與驗證邏輯:結合 LocalStorage 與 SessionStorage

const lockScreen = document.getElementById('lock-screen');
const lockTitle = document.getElementById('lock-title');
const lockDesc = document.getElementById('lock-desc');
const lockPasswordInput = document.getElementById('lock-password-input');
const lockSubmitBtn = document.getElementById('lock-submit-btn');

// 初始化檢查
function initAuthSystem() {
    const savedPassword = localStorage.getItem('my_vault_password');
    const isUnlocked = sessionStorage.getItem('vault_unlocked');

    if (!savedPassword) {
        lockTitle.textContent = " 初始化您的金庫密碼";
        lockDesc.textContent = "這是您第一次使用,請設定一組解鎖密碼:";
        lockSubmitBtn.textContent = "確認並建立密碼";
    } else if (isUnlocked === 'true') {
        lockScreen.style.display = 'none';
    }
}

// 點擊解鎖或設定密碼
lockSubmitBtn.addEventListener('click', function() {
    const inputVal = lockPasswordInput.value.trim();
    const savedPassword = localStorage.getItem('my_vault_password');

    if (!inputVal) { alert("密碼不能為空白!"); return; }

    if (!savedPassword) {
        localStorage.setItem('my_vault_password', inputVal);
        sessionStorage.setItem('vault_unlocked', 'true');
        lockScreen.style.display = 'none';
        alert("密碼設定成功!");
    } else {
        if (inputVal === savedPassword) {
            sessionStorage.setItem('vault_unlocked', 'true');
            lockScreen.style.display = 'none';
        } else {
            alert("密碼錯誤,請重新輸入!");
            lockPasswordInput.value = '';
        }
    }
});
window.addEventListener('DOMContentLoaded', initAuthSystem);

▲ 說明:透過 localStorage 長期儲存密碼(重開瀏覽器還在),並用 sessionStorage 記錄「當前分頁是否已解鎖」,讓使用者在同一個瀏覽行程中不用重複輸入密碼。

Day 17:支援 Enter 鍵快速解鎖與防呆提示

// 支援按下 Enter 鍵快速解鎖
lockPasswordInput.addEventListener('keypress', function(e) {
    if (e.key === 'Enter') {
        lockSubmitBtn.click();
    }
});

▲ 說明:監聽密碼輸入框的 keypress 事件,當偵測到鍵盤按鍵為 Enter 時,自動觸發按鈕的 .click() 行為,大幅提升表單操作的流暢度。

Day 18:實作側邊欄「一鍵上鎖」按鈕

HTML(加在側邊欄下方):

<a id="lock-system-btn" style="margin-top: auto; background-color: #e74c3c; color: white; text-align: center; cursor: pointer; border-radius: 6px; padding: 10px;">🔒 鎖定金庫</a>

JS:

const lockSystemBtn = document.getElementById('lock-system-btn');
if (lockSystemBtn) {
    lockSystemBtn.addEventListener('click', function() {
        sessionStorage.removeItem('vault_unlocked');
        lockPasswordInput.value = '';
        lockTitle.textContent = "金庫已上鎖";
        lockDesc.textContent = "請輸入您的密碼以解鎖您的專屬記帳本";
        lockSubmitBtn.textContent = "解鎖";
        lockScreen.style.display = 'flex';
    });
}

▲ 說明:點擊後透過 sessionStorage.removeItem() 清除解鎖狀態,並將遮罩重新顯示,達到隨時隨地手動上鎖的效果。

Day 19:程式碼架構與模組化:登入系統的初始化與生命週期管理

  • 今日要做的事:
    將登入檢核與初始化邏輯進行模組化封裝,確保網頁載入時執行順序流暢,不會發生畫面閃爍或權限漏洞。

  • 程式碼說明與省思:
    將所有驗證邏輯收斂至 initAuthSystem() 與事件監聽函式中。利用 DOM 載入生命週期確保畫面元素就緒後才進行身分判斷,提升程式碼的可讀性與日後維護性。

Day 20:移除舊按鈕並新增「設定頁面(Settings)」分頁路由

<!-- 側邊欄選單新增「設定」 -->
<div class="sidebar">
    <a class="menu-btn" data-target="settings-page">設定</a> <!-- 【新增】設定頁面按鈕 -->
</div>

<!-- 主畫面中新增設定頁面容器 -->
<div id="settings-page" class="page-view">
    <div class="section-box">
        <h3>帳號與資安設定</h3>
        <p style="color: #666; margin-bottom: 20px;">在此管理您的金庫登入狀態與密碼保護:</p>
        <div id="settings-content" style="display: flex; flex-direction: column; gap: 15px; max-width: 400px;">
        </div>
    </div>
</div>

▲ 說明:配合前面建立的 SPA 單頁式架構,我們在側邊欄加上 data-target="settings-page",並在右側主畫面建立對應的 ,點擊時就能平滑切換。

Day 21:登入與登出控制中心:在設定頁面實作狀態切換

function renderSettingsPage() {
    const settingsContainer = document.getElementById('settings-content');
    if (!settingsContainer) return;

    const isUnlocked = sessionStorage.getItem('vault_unlocked');
    const savedPassword = localStorage.getItem('my_vault_password');

    settingsContainer.innerHTML = `
        <div style="padding: 15px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e9ecef;">
            <h4 style="margin: 0 0 10px 0; color: #333;">目前金庫狀態</h4>
            <p style="margin: 0; font-size: 14px; color: ${isUnlocked === 'true' ? '#27ae60' : '#e74c3c'}; font-weight: bold;">
                ${isUnlocked === 'true' ? ' 目前狀態:已解鎖' : ' 目前狀態:已上鎖'}
            </p>
        </div>

        <button id="settings-logout-btn" style="background-color: #e74c3c; color: white; padding: 10px; border: none; border-radius: 6px; font-weight: bold; cursor: pointer;">
             立即登出 / 鎖定金庫
        </button>
    `;

    // 綁定設定頁面的登出按鈕事件
    document.getElementById('settings-logout-btn').addEventListener('click', function() {
        sessionStorage.removeItem('vault_unlocked');
        alert("已成功登出金庫!");
        location.reload(); // 重新整理並跳出全螢幕解鎖畫面
    });
}

// 當切換到設定頁面時觸發渲染
// (假設你的切換按鈕邏輯會呼叫頁面渲染,可在切換分頁時執行 renderSettingsPage())

▲ 說明:將原本陽春的側邊欄按鈕移除,改由「設定頁面」統一管理帳號狀態,點擊登出後會清除 sessionStorage 並重新載入畫面,強制返回全螢幕登入遮罩。

Day 22:實作修改密碼功能

// 在 renderSettingsPage 的 HTML 內加入修改密碼區塊
/*

<div style="margin-top: 20px; border-top: 1px solid #eee; padding-top: 20px;">
    <h4 style="margin: 0 0 10px 0; color: #333;">變更解鎖密碼</h4>
    <input type="password" id="old-password" placeholder="請輸入舊密碼" style="padding: 8px; border: 1px solid #ddd; border-radius: 4px;">
    <input type="password" id="new-password" placeholder="請輸入新密碼" style="padding: 8px; border: 1px solid #ddd; border-radius: 4px; margin-top: 8px;">
    <button id="change-pwd-btn" style="margin-top: 10px; background-color: #1967d2; color: white; padding: 8px; border: none; border-radius: 4px; cursor: pointer; font-weight: bold;">確認修改密碼</button>
</div>
*/

// 綁定修改密碼事件
document.addEventListener('click', function(e) {
    if (e.target && e.target.id === 'change-pwd-btn') {
        const oldPwd = document.getElementById('old-password').value.trim();
        const newPwd = document.getElementById('new-password').value.trim();
        const savedPassword = localStorage.getItem('my_vault_password');

        if (!oldPwd || !newPwd) {
            alert("舊密碼與新密碼皆不能為空白!");
            return;
        }

        if (oldPwd !== savedPassword) {
            alert("舊密碼輸入錯誤,無法修改!");
            return;
        }

        localStorage.setItem('my_vault_password', newPwd);
        alert("密碼修改成功!下次登入請使用新密碼。");
        document.getElementById('old-password').value = '';
        document.getElementById('new-password').value = '';
    }
});

https://ithelp.ithome.com.tw/upload/images/20261015/20184373vMqe6p1lX7.png
https://ithelp.ithome.com.tw/upload/images/20261015/20184373g7n7iUukmj.png


*提醒邦友,使用第三方服務/API 時,請務必評估資安風險與隱私保護
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言