iT邦幫忙

2026 iThome 鐵人賽

DAY 13
0
Build on Google AI

液態玻璃美學:結合 Gemini 與 Antigravity IDE,打造 120FPS、多視窗比例與網格順延推擠佈局的強大商務運算終端系列 第 13

Day 13:【高階商務計算機】跨步驟計算不再靠死記!五鍵記憶體系統與螢幕發光 [M] 指示燈

  • 分享至 

  • xImage
  •  

第一段:Day 12 完成了商務折扣、正負號切換與雙零/三零快速輸入,使用者在大額交易中已能流暢地完成單步計算。然而,真實的商務場景往往需要跨步驟保存中間結果——計算小計後切換幣別、確認稅額後再加總——若每次都重新輸入,不僅效率低落,更容易累積手誤。

第二段:今日聚焦於五鍵完整記憶體系統(MC、MR、M+、M‑、MS)的實作,以及螢幕左上角的 [M] 發光指示燈。指示燈在記憶值非零時點亮,並以 CSS 過渡動畫呈現柔和的光暈浮現效果;點擊指示燈即可直接喚醒 MR,讓記憶體操作從「找按鍵」升級為「看一眼即能回應」的直觀體驗。


專案正式上線體驗與開源倉庫

線上體驗 Live Demo:https://410355-collab.github.io/Premium-Business-Calculator/
(強烈建議使用 iPhone Safari 或 Android 手機開啟體驗!)
GitHub 開源倉庫:https://github.com/410355-collab/Premium-Business-Calculator
歡迎提出建議與指導
(開發環境:Google Antgravity IDE + Google Gemini 協同開發)


一、技術痛點

記憶體操作的缺口

傳統計算機的記憶體功能雖然存在,但多數實作僅停在單一 M 變數,且沒有明確的視覺狀態。使用者在快節奏的商務計算中無法確認「我剛才有存過嗎?」,導致誤按 MR 後得到「MEMORY EMPTY」的回應,打斷思路。

無視覺回饋的盲操作

當記憶值存在時,若 UI 沒有任何提示,使用者必須記住自己的操作步驟,增加認知負荷。在多步驟財務試算中,這種無狀態的設計是記憶體功能利用率低落的根本原因。

二、解決方案

全域記憶體變數

// script.js 第 134 行
let memoryValue = 0;

memoryValue 以 JS number 初始化為 0,但在 M+ 與 M‑ 的累加/扣除運算中,立即轉換為 math.bignumber 以確保大數精度,避免 JS 原生浮點誤差在高額財務計算中擴大。

五鍵記憶體定義

// script.js – 記憶體鍵位物件
'mem_clear':  { label: 'MC', action: 'memoryClear',  type: 'accent-mem small-text', category: 'mem', name: 'MC' },
'mem_recall': { label: 'MR', action: 'memoryRecall', type: 'accent-mem small-text', category: 'mem', name: 'MR' },
'mem_add':    { label: 'M+', action: 'memoryAdd',    type: 'accent-mem small-text', category: 'mem', name: 'M+' },
'mem_sub':    { label: 'M-', action: 'memorySub',    type: 'accent-mem small-text', category: 'mem', name: 'M-' },
'mem_store':  { label: 'MS', action: 'memoryStore',  type: 'accent-mem small-text', category: 'mem', name: 'MS' },

五鍵均隸屬 category: 'mem',在 renderMainKeyboard 時統一排入「記憶體區」,視覺上以 accent-mem 配色與數字鍵明確區隔。

各功能核心邏輯

// script.js 第 1186 ~ 1230 行

// MC – 清零並更新指示燈
} else if (funcName === 'memoryClear') {
    memoryValue = 0;
    updateMemoryIndicator();
    showToastMsg(I18N[currentLang].toastMemCleared);

// MR – 讀取並插入當前運算式
} else if (funcName === 'memoryRecall') {
    if (memoryValue === 0) {
        showToastMsg(I18N[currentLang].toastNoMem);
    } else {
        const mStr = formatNumber(memoryValue).replace(/,/g, '');
        if (isEvaluated || currentInput === "0") {
            currentInput = mStr;
            isEvaluated = false;
        } else {
            currentInput += mStr;
        }
        cursorPos = null;
        animType = 'append';
    }

// M+ – 累加(bignumber 精確運算)
} else if (funcName === 'memoryAdd') {
    try {
        let val = safeEvaluate(autoCloseParentheses(currentInput));
        if (math.isBigNumber(val) && val.isFinite()) {
            memoryValue = math.add(math.bignumber(memoryValue || 0), val);
            updateMemoryIndicator();
            showToastMsg(I18N[currentLang].toastMemStored);
        }
    } catch(e) {}

// M- – 扣除(bignumber 精確運算)
} else if (funcName === 'memorySub') {
    try {
        let val = safeEvaluate(autoCloseParentheses(currentInput));
        if (math.isBigNumber(val) && val.isFinite()) {
            memoryValue = math.subtract(math.bignumber(memoryValue || 0), val);
            updateMemoryIndicator();
            showToastMsg(I18N[currentLang].toastMemStored);
        }
    } catch(e) {}

// MS – 直接覆寫記憶值
} else if (funcName === 'memoryStore') {
    try {
        let val = safeEvaluate(autoCloseParentheses(currentInput));
        if (math.isBigNumber(val) && val.isFinite()) {
            memoryValue = val;
            updateMemoryIndicator();
            showToastMsg(I18N[currentLang].toastMemStored);
        }
    } catch(e) {}

關鍵設計:autoCloseParentheses 前置處理

M+、M‑、MS 在求值前均先呼叫 autoCloseParentheses(currentInput),確保使用者若未關閉括號仍能正確計算當前值,防止因括號未閉合而靜默失敗。

[M] 指示燈 UI

HTML(index.html 第 168 行)

<div class="mem-indicator" id="mem-indicator" title="Memory (M)">M</div>

純文字 M 搭配 title 提示,不使用圖示,保持輕量與可讀性。

CSS 狀態切換(style.css 第 569 ~ 607 行)

.mem-indicator {
    font-size: 11px;
    font-weight: 900;
    letter-spacing: 0.5px;
    padding: 2px 7px;
    border-radius: 8px;
    color: var(--text-muted);
    background: rgba(255, 255, 255, 0.04);
    border: 0.5px solid rgba(255, 255, 255, 0.12);
    opacity: 0;
    transform: scale(0.85);
    transition: opacity 0.22s ease, transform 0.22s var(--ios-spring-snappy),
                color 0.2s, background 0.2s, border-color 0.2s, box-shadow 0.2s;
    cursor: pointer;
    pointer-events: none;
}
.mem-indicator.active {
    opacity: 1;
    transform: scale(1);
    color: var(--accent-main);
    background: color-mix(in srgb, var(--accent-main) 18%, transparent);
    border-color: color-mix(in srgb, var(--accent-main) 45%, transparent);
    box-shadow: 0 0 12px color-mix(in srgb, var(--accent-main) 35%, transparent);
    pointer-events: auto;
}
.mem-indicator.active:active {
    transform: scale(0.92);
}

亮色模式下同步切換為 #0284c7 藍系配色,與深色模式的 --accent-main 保持語意一致。

JavaScript 狀態更新(script.js 第 518 ~ 523 行)

function updateMemoryIndicator() {
    const memEl = document.getElementById('mem-indicator');
    if (!memEl) return;
    const hasMem = (memoryValue !== 0 && memoryValue !== '0'
                    && memoryValue !== null && memoryValue !== undefined);
    memEl.classList.toggle('active', !!hasMem);
}

四重非零條件防止 bignumber(0)、字串 '0'nullundefined 造成指示燈誤亮。

點擊觸發 MR(script.js 第 4442 ~ 4447 行)

const memBadge = document.getElementById('mem-indicator');
if (memBadge) {
    attachSmartTap(memBadge, () => {
        instantInput(null, 'memoryRecall');
    });
}

使用 attachSmartTap 而非原生 addEventListener('click'),可自動繼承專案的 haptic 回饋與防雙觸邏輯,確保在手機快速操作時不會觸發兩次 MR。

三、核心代碼精華:script.js

  • 全域 memoryValue(第 134 行)
  • updateMemoryIndicator(第 518 ~ 523 行):四重非零檢測,單行 classList.toggle
  • 五大記憶體操作分支(第 1186 ~ 1230 行):MC / MR / M+ / M‑ / MS
  • attachSmartTap 點擊綁定(第 4442 ~ 4447 行)

四、深度調校剖析

為何使用 color-mix 而非 hardcode 色碼

color-mix(in srgb, var(--accent-main) 18%, transparent) 讓指示燈的背景色完全隨主題的 --accent-main 動態調整,使用者切換主題色時指示燈自動跟隨,無需另外為每個主題寫覆蓋規則。

為何 pointer-events: none 在非 active 狀態

指示燈在記憶值為 0 時 opacity: 0pointer-events: none,確保使用者不會誤觸到一個視覺上「不存在」的元素。只有在 active 時才切換為 pointer-events: auto

math.bignumber 的必要性

M+ 累加若使用原生 JS 加法,在數值超過 2⁵³ 時會喪失精度。在商務場景中,億元以上的資產合計若出現浮點偏差,是無法被接受的錯誤,因此強制使用 mathjsbignumber 運算。

autoCloseParentheses 的保護意義

使用者在輸入 12*(5+3 時若直接按 M+ 而未閉合括號,safeEvaluate 會拋出例外。前置 autoCloseParentheses 自動補全為 12*(5+3),讓記憶體操作能正確捕獲當前值,而非靜默失敗。

五、總結

Day 13 完成了五鍵記憶體系統與 [M] 指示燈的完整閉環。使用者現在可以在多步驟商務計算中安全地暫存、累加、扣除與讀取中間結果;[M] 指示燈以最低的視覺干擾、最直觀的互動方式告知記憶狀態,點擊即觸發 MR,讓記憶體操作真正融入快節奏的計算流程。

明日預告:科學計算擴充矩陣與智慧括號平衡

Day 14 將引入 sin、cos、tan、ln、log、xʸ、x!、|x|、1/x、π、e 等完整科學函數模組,每個函數均透過 mathjs 求值並支援角度/弧度雙模式。同時,我們將推出智慧括號平衡演算法,能精確區分「未閉合括號」、「除以零」與「語法錯誤」三種錯誤類型並給予對應提示,讓科學計算的錯誤診斷體驗大幅升級。


上一篇
Day 12:【高階商務計算機】商務折扣 (-%)、正負號即時反轉 (±) 與雙零/三零快速輸入
下一篇
Day 14:【高階商務計算機】手機計算機也能跑三角函數!科學計算擴充矩陣與智慧括號平衡
系列文
液態玻璃美學:結合 Gemini 與 Antigravity IDE,打造 120FPS、多視窗比例與網格順延推擠佈局的強大商務運算終端15
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言