☕ NEW! 完成新手任務即可參加抽獎!LINE 星巴克禮券等你拿,名額有限!        🎉 推廣活動:邀請好友註冊 DevLearn,累積推薦抽 LINE 星巴克禮券! 活動詳情 →        🔥 活動期間 2026/4/1 - 5/31 |已有 0 人參加       
iot 中級

Web-Based POS 系統開發

POS 系統架構設計

💡 比喻:數位化的收銀台 傳統的收銀台有:收銀機、商品標籤、計算機、收據紙。 Web-Based POS 就是把這些全部搬到瀏覽器裡:

  • 收銀機 → Web 畫面上的購物車
  • 商品標籤 → 資料庫裡的商品資料
  • 計算機 → JavaScript 自動計算
  • 收據紙 → 透過 Print Agent 列印

系統架構圖

Web-Based POS 系統架構:

  使用者(店員)
      │
      ▼
  ┌──────────────┐
  │  Chromium     │   ← 前端(HTML/CSS/JS)
  │  Kiosk Mode   │   ← 觸控螢幕操作
  └──────┬───────┘
         │ HTTP / WebSocket
         ▼
  ┌──────────────┐
  │  ASP.NET     │   ← 後端 API
  │  Core API    │   ← 商品/訂單/庫存管理
  └──────┬───────┘
         │
    ┌────┴────┐
    ▼         ▼
 ┌──────┐ ┌──────────┐
 │ SQLite│ │ Print    │
 │ 資料庫│ │ Agent    │
 └──────┘ └──────────┘

前端:HTML/CSS/JS 收銀畫面

💡 比喻:店員面前的收銀螢幕 收銀畫面就是店員每天面對的「工作台」。 左邊是商品按鈕(快速點選),右邊是購物車(已選商品)。 設計時要注意:按鈕要夠大(觸控螢幕用手指按)、字要清楚。

POS 前端 HTML 結構

<!-- POS 收銀畫面的 HTML 結構 -->
<!-- 主要分成左右兩個區域 -->
<!DOCTYPE html>
<html lang="zh-TW"> <!-- 設定語言為繁體中文 -->
<head>
    <meta charset="UTF-8"> <!-- 字元編碼 UTF-8 -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- 響應式設計 -->
    <title>POS 收銀系統</title> <!-- 頁面標題 -->
    <style>
        /* POS 系統的基本樣式 */
        * { margin: 0; padding: 0; box-sizing: border-box; } /* 重設邊距 */
        body { font-family: 'Noto Sans TC', sans-serif; } /* 使用中文字型 */

        .pos-container { /* POS 主容器 */
            display: grid; /* 使用 Grid 排版 */
            grid-template-columns: 1fr 400px; /* 左寬右窄 */
            height: 100vh; /* 滿版高度 */
        }
        .product-grid { /* 商品區域 */
            display: grid; /* 商品用 Grid 排列 */
            grid-template-columns: repeat(4, 1fr); /* 每排 4 個 */
            gap: 10px; /* 間距 10px */
            padding: 20px; /* 內距 20px */
            overflow-y: auto; /* 超出時可捲動 */
        }
        .product-btn { /* 商品按鈕 */
            height: 100px; /* 按鈕高度 100px */
            font-size: 18px; /* 字體大小 18px */
            border: 2px solid #ddd; /* 邊框樣式 */
            border-radius: 8px; /* 圓角 */
            cursor: pointer; /* 滑鼠游標 */
            display: flex; /* Flex 排版 */
            flex-direction: column; /* 垂直排列 */
            align-items: center; /* 水平置中 */
            justify-content: center; /* 垂直置中 */
        }
        .cart-panel { /* 購物車面板 */
            background: #f5f5f5; /* 淺灰背景 */
            padding: 20px; /* 內距 */
            display: flex; /* Flex 排版 */
            flex-direction: column; /* 垂直排列 */
        }
    </style>
</head>
<body>
    <div class="pos-container"> <!-- POS 主容器開始 -->
        <div class="product-grid" id="productGrid"> <!-- 商品區域 -->
            <!-- 商品按鈕會由 JavaScript 動態產生 -->
        </div>
        <div class="cart-panel"> <!-- 購物車面板 -->
            <h2>購物車</h2> <!-- 購物車標題 -->
            <div id="cartItems" style="flex:1;overflow-y:auto;"> <!-- 購物車商品清單 -->
            </div>
            <div id="cartTotal" style="font-size:24px;font-weight:bold;padding:10px 0;"> <!-- 總計金額 -->
                總計:$0
            </div>
            <button id="checkoutBtn" style="height:60px;font-size:20px;background:#4CAF50;color:white;border:none;border-radius:8px;"> <!-- 結帳按鈕 -->
                結帳
            </button>
        </div>
    </div>
</body>
</html>

商品管理(CRUD + 條碼掃描)

商品資料模型

// 定義商品資料的類別 // POS 系統最核心的資料
public class Product // 商品類別
{
    public int Id { get; set; }           // 商品編號 // 自動遞增的主鍵
    public string Name { get; set; } = ""; // 商品名稱 // 例如「美式咖啡」
    public string Barcode { get; set; } = ""; // 條碼 // EAN-13 或自訂條碼
    public decimal Price { get; set; }    // 售價 // 含稅價格
    public int Stock { get; set; }        // 庫存數量 // 即時庫存
    public string Category { get; set; } = ""; // 商品分類 // 例如「飲料」、「餐點」
    public bool IsActive { get; set; } = true; // 是否上架 // 可暫時下架
    public string ImageUrl { get; set; } = ""; // 商品圖片網址 // 顯示在 POS 按鈕上
    public DateTime CreatedAt { get; set; } = DateTime.Now; // 建立時間 // 自動記錄
}

// 商品管理服務的類別 // 處理商品的新增、查詢、修改、刪除
public class ProductService // 商品服務類別
{
    private readonly List<Product> _products = new(); // 商品清單 // 模擬資料庫

    // 新增商品的方法 // Create 操作
    public Product AddProduct(string name, string barcode, decimal price, int stock) // 新增商品
    {
        var product = new Product // 建立商品物件
        {
            Id = _products.Count + 1, // 自動設定編號 // 簡單的遞增 ID
            Name = name,      // 設定名稱 // 商品顯示名稱
            Barcode = barcode, // 設定條碼 // 用於掃描器讀取
            Price = price,    // 設定價格 // 銷售單價
            Stock = stock     // 設定庫存 // 初始庫存數量
        }; // 商品物件建立完成

        _products.Add(product); // 加入清單 // 儲存到資料庫
        return product; // 回傳新建的商品 // 含自動產生的 ID
    }

    // 用條碼查詢商品的方法 // 掃描器掃到條碼後用這個查
    public Product? FindByBarcode(string barcode) // 條碼查詢方法
    {
        return _products.FirstOrDefault(p => p.Barcode == barcode); // 找到第一個符合的商品 // 找不到回傳 null
    }

    // 更新庫存的方法 // 賣出商品後扣庫存
    public bool UpdateStock(int productId, int quantitySold) // 更新庫存方法
    {
        var product = _products.FirstOrDefault(p => p.Id == productId); // 找到商品 // 用 ID 查詢
        if (product == null) return false; // 商品不存在 // 回傳失敗

        if (product.Stock < quantitySold) return false; // 庫存不足 // 回傳失敗

        product.Stock -= quantitySold; // 扣除庫存 // 減去賣出數量
        return true; // 回傳成功 // 庫存更新完成
    }
}

購物車邏輯(加入/移除/數量/小計/總計)

💡 比喻:超市推車 購物車就像實體的超市推車: 可以放入商品、拿出商品、改數量。 最後結帳時算總金額。

購物車 JavaScript 實作

// POS 購物車類別 // 管理所有購物車操作
class PosCart { // 購物車類別定義
    constructor() { // 建構函式 // 初始化購物車
        this.items = []; // 購物車商品陣列 // 空陣列開始
    }

    // 加入商品的方法 // 點擊商品按鈕時呼叫
    addItem(product) { // 加入商品方法
        const existing = this.items.find(i => i.productId === product.id); // 檢查商品是否已在購物車 // 用商品 ID 比對
        if (existing) { // 如果已存在
            existing.quantity += 1; // 數量加 1 // 同商品累加
            existing.subtotal = existing.quantity * existing.price; // 重新計算小計 // 數量 x 單價
        } else { // 如果是新商品
            this.items.push({ // 加入新商品到陣列
                productId: product.id, // 商品 ID // 用於識別
                name: product.name,    // 商品名稱 // 顯示用
                price: product.price,  // 單價 // 用於計算
                quantity: 1,           // 初始數量為 1 // 第一次加入
                subtotal: product.price // 小計等於單價 // 數量為 1 所以等於單價
            }); // 新商品加入完成
        }
        this.render(); // 重新渲染畫面 // 更新顯示
    }

    // 移除商品的方法 // 從購物車移除指定商品
    removeItem(productId) { // 移除商品方法
        this.items = this.items.filter(i => i.productId !== productId); // 過濾掉指定商品 // 保留其他商品
        this.render(); // 重新渲染畫面 // 更新顯示
    }

    // 修改數量的方法 // 加減按鈕用
    updateQuantity(productId, delta) { // 修改數量方法
        const item = this.items.find(i => i.productId === productId); // 找到指定商品 // 用 ID 查找
        if (!item) return; // 找不到就跳過 // 防禦性程式設計

        item.quantity += delta; // 加減數量 // delta 可以是 +1 或 -1
        if (item.quantity <= 0) { // 如果數量變成 0 或負數
            this.removeItem(productId); // 移除該商品 // 數量為 0 就不要了
            return; // 結束方法 // 已經移除了
        }
        item.subtotal = item.quantity * item.price; // 重新計算小計 // 數量 x 單價
        this.render(); // 重新渲染畫面 // 更新顯示
    }

    // 計算總金額的方法 // 所有商品小計加總
    getTotal() { // 取得總計方法
        return this.items.reduce((sum, item) => sum + item.subtotal, 0); // 加總所有小計 // reduce 累加
    }

    // 清空購物車的方法 // 結帳完成後呼叫
    clear() { // 清空方法
        this.items = []; // 清空陣列 // 重設為空
        this.render(); // 重新渲染畫面 // 更新顯示
    }

    // 渲染購物車畫面的方法 // 更新 HTML 顯示
    render() { // 渲染方法
        const cartEl = document.getElementById('cartItems'); // 取得購物車容器 // DOM 元素
        const totalEl = document.getElementById('cartTotal'); // 取得總計容器 // DOM 元素

        cartEl.innerHTML = this.items.map(item => // 產生每個商品的 HTML // 用 map 轉換
            `<div style="display:flex;justify-content:space-between;padding:8px;border-bottom:1px solid #ddd;">
                <span>${item.name}</span>
                <span>${item.quantity} x $${item.price} = $${item.subtotal}</span>
            </div>` // 商品名稱、數量、小計 // 格式化顯示
        ).join(''); // 組合所有 HTML // 串接字串

        totalEl.textContent = `總計:$${this.getTotal()}`; // 更新總計顯示 // 呼叫 getTotal()
    }
}

// 初始化購物車 // 頁面載入時建立
const cart = new PosCart(); // 建立購物車實例 // 全域變數

付款流程(現金/信用卡/行動支付)

// 定義付款方式的列舉 // POS 支援的付款方式
public enum PaymentMethod // 付款方式列舉
{
    Cash,       // 現金 // 最傳統的付款方式
    CreditCard, // 信用卡 // 刷卡付款
    LinePay,    // LINE Pay // 行動支付
    JkoPay,     // 街口支付 // 行動支付
    EasyCard    // 悠遊卡 // 電子票證
}

// 定義訂單的類別 // 一筆完整的交易記錄
public class Order // 訂單類別
{
    public int Id { get; set; }          // 訂單編號 // 自動遞增
    public DateTime OrderTime { get; set; } = DateTime.Now; // 下單時間 // 自動記錄
    public List<OrderItem> Items { get; set; } = new(); // 訂單明細 // 商品清單
    public decimal TotalAmount { get; set; } // 總金額 // 所有商品加總
    public PaymentMethod Payment { get; set; } // 付款方式 // 現金/信用卡/行動支付
    public decimal ReceivedAmount { get; set; } // 收到金額 // 現金付款時使用
    public decimal ChangeAmount { get; set; }  // 找零金額 // 收到 - 總額
    public string Status { get; set; } = "completed"; // 訂單狀態 // 完成/取消/退貨
}

// 定義訂單明細的類別 // 單一商品在訂單中的記錄
public class OrderItem // 訂單明細類別
{
    public int ProductId { get; set; }  // 商品編號 // 對應商品資料
    public string ProductName { get; set; } = ""; // 商品名稱 // 冗餘儲存避免關聯查詢
    public decimal UnitPrice { get; set; } // 單價 // 購買當下的價格
    public int Quantity { get; set; }   // 數量 // 購買數量
    public decimal Subtotal { get; set; } // 小計 // 單價 x 數量
}

// 結帳服務的類別 // 處理付款流程
public class CheckoutService // 結帳服務
{
    // 處理現金付款的方法 // 計算找零
    public Order ProcessCashPayment(List<OrderItem> items, decimal receivedAmount) // 現金結帳方法
    {
        var total = items.Sum(i => i.Subtotal); // 計算總金額 // 加總所有小計

        if (receivedAmount < total) // 檢查收到金額是否足夠
            throw new InvalidOperationException( // 金額不足丟出例外
                $"金額不足!應收 {total},實收 {receivedAmount}"); // 顯示差額

        var order = new Order // 建立訂單物件
        {
            Items = items,           // 設定訂單明細 // 所有商品
            TotalAmount = total,     // 設定總金額 // 加總結果
            Payment = PaymentMethod.Cash, // 設定付款方式為現金
            ReceivedAmount = receivedAmount, // 設定收到金額
            ChangeAmount = receivedAmount - total // 計算找零 // 收到 - 總額
        }; // 訂單建立完成

        Console.WriteLine($"交易完成!總額:{total},收到:{receivedAmount},找零:{order.ChangeAmount}"); // 顯示交易結果
        return order; // 回傳訂單 // 包含完整交易資訊
    }
}

收據格式設計

// 定義收據產生器的類別 // 產生文字格式的收據
public class ReceiptGenerator // 收據產生器
{
    private const int ReceiptWidth = 32; // 收據寬度(字元數)// 熱感應印表機通常 32 或 48 字元

    // 產生收據的方法 // 回傳格式化的收據字串
    public static string GenerateReceipt(Order order, string storeName) // 產生收據方法
    {
        var lines = new List<string>(); // 建立行清單 // 收據的每一行

        lines.Add(CenterText(storeName)); // 店名置中 // 收據最上方
        lines.Add(CenterText("統一編號:12345678")); // 統編置中 // 公司稅號
        lines.Add(new string('-', ReceiptWidth)); // 分隔線 // 用虛線分隔
        lines.Add($"日期:{order.OrderTime:yyyy/MM/dd HH:mm}"); // 交易日期時間 // 格式化日期
        lines.Add($"單號:{order.Id:D6}"); // 訂單編號 // 6 位數補零
        lines.Add(new string('-', ReceiptWidth)); // 分隔線

        foreach (var item in order.Items) // 逐一列出商品
        {
            lines.Add($"{item.ProductName}"); // 商品名稱 // 獨立一行
            lines.Add($"  {item.Quantity} x ${item.UnitPrice} = ${item.Subtotal}"); // 數量 x 單價 = 小計
        }

        lines.Add(new string('=', ReceiptWidth)); // 雙線分隔 // 用等號
        lines.Add($"總計:${order.TotalAmount}"); // 總金額 // 所有商品加總
        lines.Add($"付款方式:{order.Payment}"); // 付款方式 // 現金/信用卡等
        if (order.Payment == PaymentMethod.Cash) // 如果是現金付款
        {
            lines.Add($"收到:${order.ReceivedAmount}"); // 收到金額
            lines.Add($"找零:${order.ChangeAmount}"); // 找零金額
        }
        lines.Add(new string('-', ReceiptWidth)); // 分隔線
        lines.Add(CenterText("謝謝光臨!")); // 感謝語置中

        return string.Join("\n", lines); // 組合所有行 // 用換行符號連接
    }

    // 文字置中的輔助方法 // 在收據寬度內置中顯示
    private static string CenterText(string text) // 置中方法
    {
        var padding = (ReceiptWidth - text.Length) / 2; // 計算左邊空白數 // 讓文字置中
        return text.PadLeft(padding + text.Length); // 左邊補空白 // 實現置中效果
    }
}

Raspberry Pi Kiosk Mode(全螢幕 Web 模式)

# 設定 Pi 開機自動進入 Kiosk Mode // 自動啟動 POS 畫面
# 編輯自動啟動設定檔 // 開機後自動執行

# 步驟 1:建立自動啟動腳本 // 放在 home 目錄
cat << 'SCRIPT' > ~/start-pos.sh  # 建立啟動腳本
#!/bin/bash  # 使用 bash 執行
# 等待桌面環境準備好 // 避免太快啟動
sleep 5  # 等待 5 秒

# 停用螢幕保護程式 // POS 不需要螢幕保護
xset s off  # 關閉螢幕保護
xset -dpms  # 關閉電源管理
xset s noblank  # 不要黑屏

# 啟動 Chromium Kiosk Mode // 全螢幕開啟 POS 系統
chromium-browser \
  --kiosk \  # 全螢幕模式
  --noerrdialogs \  # 不顯示錯誤對話框
  --disable-infobars \  # 隱藏資訊列
  --disable-translate \  # 停用翻譯功能
  --no-first-run \  # 跳過首次執行提示
  --start-fullscreen \  # 全螢幕啟動
  http://localhost:5000  # POS 系統網址
SCRIPT
chmod +x ~/start-pos.sh  # 設定執行權限

# 步驟 2:設定開機自動執行 // 加入 autostart
mkdir -p ~/.config/autostart  # 建立 autostart 目錄
cat << 'DESKTOP' > ~/.config/autostart/pos-kiosk.desktop  # 建立 desktop 檔案
[Desktop Entry]  # Desktop 檔案格式
Type=Application  # 類型為應用程式
Name=POS Kiosk  # 名稱
Exec=/home/admin/start-pos.sh  # 執行腳本路徑
DESKTOP

觸控螢幕操作優化

/* 觸控螢幕優化的 CSS 樣式 */
/* 讓按鈕更適合手指操作 */

/* 防止長按選取文字 */ /* 觸控螢幕常見問題 */
* {
    -webkit-user-select: none; /* Safari/Chrome 防選取 */
    user-select: none; /* 標準屬性 防選取 */
    -webkit-touch-callout: none; /* iOS 防長按選單 */
    -webkit-tap-highlight-color: transparent; /* 移除點擊高亮 */
}

/* 按鈕最小尺寸 */ /* 手指操作至少 44px */
.touch-btn {
    min-width: 44px; /* 最小寬度 44px */
    min-height: 44px; /* 最小高度 44px */
    padding: 12px 16px; /* 內距加大 */
    font-size: 16px; /* 字體不要太小 */
    touch-action: manipulation; /* 優化觸控行為 */
}

/* 快速點擊回饋 */ /* 讓店員知道按到了 */
.touch-btn:active {
    transform: scale(0.95); /* 按下時縮小 */
    opacity: 0.8; /* 按下時半透明 */
    transition: all 0.1s; /* 過渡動畫 0.1 秒 */
}

/* 數字鍵盤樣式 */ /* 輸入金額用 */
.numpad {
    display: grid; /* Grid 排版 */
    grid-template-columns: repeat(3, 1fr); /* 3 欄 */
    gap: 8px; /* 間距 8px */
    padding: 16px; /* 內距 16px */
}
.numpad button {
    height: 64px; /* 按鈕高度 64px */
    font-size: 24px; /* 字體大小 24px */
    border-radius: 8px; /* 圓角 8px */
}

離線模式(Service Worker)

💡 比喻:停電時的備用發電機 如果網路斷了怎麼辦?Service Worker 就像備用發電機, 讓 POS 系統在沒有網路時也能繼續運作。 等網路恢復後,再把離線期間的交易資料同步回伺服器。

// Service Worker 註冊 // 在主頁面載入時執行
if ('serviceWorker' in navigator) { // 檢查瀏覽器是否支援 Service Worker
    navigator.serviceWorker.register('/sw.js') // 註冊 Service Worker 檔案
        .then(reg => console.log('SW 註冊成功', reg.scope)) // 註冊成功的回呼 // 顯示作用範圍
        .catch(err => console.log('SW 註冊失敗', err)); // 註冊失敗的回呼 // 顯示錯誤
}

// sw.js - Service Worker 檔案 // 處理離線快取
const CACHE_NAME = 'pos-cache-v1'; // 快取名稱 // 版本號用於更新
const URLS_TO_CACHE = [ // 需要快取的檔案清單
    '/',                // 首頁 // POS 主畫面
    '/css/pos.css',     // CSS 樣式 // 畫面樣式
    '/js/pos.js',       // JavaScript // 購物車邏輯
    '/js/cart.js',      // 購物車 JS // 購物車類別
    '/api/products',    // 商品資料 API // 商品清單
]; // 快取清單結束

// 安裝事件 // Service Worker 第一次安裝時執行
self.addEventListener('install', event => { // 監聽安裝事件
    event.waitUntil( // 等待快取完成
        caches.open(CACHE_NAME) // 開啟快取空間
            .then(cache => cache.addAll(URLS_TO_CACHE)) // 快取所有指定檔案
    ); // waitUntil 結束
}); // install 事件結束

// 攔截請求事件 // 決定從快取還是網路取得資料
self.addEventListener('fetch', event => { // 監聽網路請求
    event.respondWith( // 回應請求
        caches.match(event.request) // 先從快取找
            .then(response => { // 快取查詢結果
                if (response) return response; // 有快取就用快取 // 離線也能用
                return fetch(event.request); // 沒快取就從網路取 // 正常連線時
            }) // 查詢結束
    ); // respondWith 結束
}); // fetch 事件結束

離線交易暫存

// 離線交易暫存管理 // 網路斷線時暫存交易
class OfflineStore { // 離線暫存類別
    constructor() { // 建構函式
        this.dbName = 'pos-offline-db'; // IndexedDB 名稱 // 本地資料庫
    }

    // 儲存離線交易 // 存到 IndexedDB
    async saveOfflineOrder(order) { // 儲存離線訂單方法
        const orders = JSON.parse(localStorage.getItem('offlineOrders') || '[]'); // 讀取現有離線訂單 // 從 localStorage
        orders.push({ ...order, offlineAt: new Date().toISOString() }); // 加入新訂單和時間戳
        localStorage.setItem('offlineOrders', JSON.stringify(orders)); // 儲存回 localStorage
        console.log(`離線訂單已暫存,共 ${orders.length} 筆待同步`); // 顯示暫存數量
    }

    // 同步離線交易 // 網路恢復時呼叫
    async syncOfflineOrders() { // 同步方法
        const orders = JSON.parse(localStorage.getItem('offlineOrders') || '[]'); // 讀取離線訂單
        if (orders.length === 0) return; // 沒有離線訂單就跳過

        console.log(`開始同步 ${orders.length} 筆離線訂單...`); // 顯示同步開始
        for (const order of orders) { // 逐筆同步
            try { // 嘗試同步
                await fetch('/api/orders', { // 送出 API 請求
                    method: 'POST', // POST 方法
                    headers: { 'Content-Type': 'application/json' }, // JSON 格式
                    body: JSON.stringify(order) // 訂單資料
                }); // 請求結束
                console.log(`訂單同步成功:${order.id}`); // 同步成功
            } catch (err) { // 同步失敗
                console.error(`訂單同步失敗:${order.id}`, err); // 顯示錯誤
                return; // 停止同步 // 等下次再試
            }
        }
        localStorage.removeItem('offlineOrders'); // 清除已同步的離線訂單
        console.log('所有離線訂單同步完成!'); // 顯示同步完成
    }
}

🤔 我這樣寫為什麼會錯?

❌ 錯誤 1:購物車用浮點數計算金額

// ❌ 錯誤:直接用浮點數計算 // 會產生精度問題
let total = 0; // 初始化總計
total += 19.9; // 加一杯咖啡 // 19.9 元
total += 35.5; // 加一份三明治 // 35.5 元
console.log(total); // 55.400000000000006 ← 浮點數精度問題!

// ✅ 正確:用整數計算(分為單位)// 最後再除以 100
let totalCents = 0; // 用「分」為單位 // 避免浮點數問題
totalCents += 1990; // 19.9 元 = 1990 分
totalCents += 3550; // 35.5 元 = 3550 分
console.log(totalCents / 100); // 55.4 ← 正確!

// 或使用 toFixed // 簡單但不夠精確
console.log(parseFloat((19.9 + 35.5).toFixed(2))); // 55.4

❌ 錯誤 2:Service Worker 沒有處理更新

// ❌ 錯誤:永遠只用快取,不更新 // 商品價格改了也不知道
self.addEventListener('fetch', event => { // 監聽請求
    event.respondWith(caches.match(event.request)); // 只看快取 // 永遠不會更新
}); // 這樣修改商品價格後 POS 還是顯示舊價格

// ✅ 正確:Cache-first + 背景更新 // 先用快取再更新
self.addEventListener('fetch', event => { // 監聯請求
    event.respondWith( // 回應請求
        caches.match(event.request).then(cached => { // 先查快取
            const fetchPromise = fetch(event.request).then(response => { // 同時從網路取
                caches.open(CACHE_NAME).then(cache => { // 開啟快取空間
                    cache.put(event.request, response.clone()); // 更新快取 // 下次就用新的
                }); // 快取更新完成
                return response; // 回傳網路結果
            }); // 網路請求結束
            return cached || fetchPromise; // 有快取用快取,沒有就等網路 // 離線容錯
        }) // 快取查詢結束
    ); // respondWith 結束
}); // fetch 事件結束

❌ 錯誤 3:觸控按鈕太小,店員按不到

/* ❌ 錯誤:按鈕太小 */ /* 手指按不準 */
.product-btn {
    width: 30px; /* 太小了! */ /* 手指至少需要 44px */
    height: 30px; /* 太小了! */ /* 觸控螢幕操作困難 */
    font-size: 10px; /* 字太小看不清 */ /* 店員要瞇眼睛 */
}

/* ✅ 正確:按鈕至少 44x44px */ /* Apple HIG 觸控最小尺寸建議 */
.product-btn {
    min-width: 80px; /* 足夠的寬度 */ /* 手指輕鬆點擊 */
    min-height: 60px; /* 足夠的高度 */ /* 不會按錯 */
    font-size: 16px; /* 清楚的字體 */ /* 店員一目了然 */
    padding: 12px; /* 充足的內距 */ /* 點擊區域更大 */
}

💡 大家的想法 · 0

載入中...
💬 即時聊天室 🟢 0 人在線
😀 😎 🤓 💻 🎮 🎸 🔥
➕ 新問題
📋 我的工單
💬 LINE 社群
🔒
需要註冊才能使用此功能
註冊帳號即可解鎖測驗、遊戲、簽到、筆記下載等所有功能,完全免費!
免費註冊