Files
egg bf7285fb51 feat(qc-gate): add QC-GATE real-time LOT status report as first pure Vue 3 + Vite page
Introduce QC-GATE station monitoring with stacked bar chart and filterable LOT table,
using Vue 3 SFC + ECharts via npm. Establishes the pure Vite page architecture pattern
(no Jinja2) for future page migration. Also removes stale design files and README.mdj.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 13:06:59 +08:00

7.3 KiB
Raw Permalink Blame History

Context

目前系統有 11 個頁面,全部使用 Jinja2 shell + Vite JS 混合模式。前端無 UI 框架(純 vanilla JSECharts 以靜態 minified 檔案引入(非 npm。WIP 資料透過 Redis 快取,每 10 分鐘從 Oracle DWH.DW_MES_LOT_V 同步。

QC-GATE 頁面將是第一個採用 Vue 3 + Vite 純前端架構的頁面,建立後續遷移模式。

Goals / Non-Goals

Goals:

  • 提供 QC-GATE 站點即時 LOT 狀態報表(條圖 + 篩選清單)
  • 建立純 Vue 3 + Vite 頁面架構模式(不依賴 Jinja2
  • 與現有 portal iframe 機制無縫整合
  • 複用現有 WIP Redis 快取,不增加 Oracle 查詢負擔

Non-Goals:

  • 不遷移現有頁面到 Vue 3本次僅建立模式
  • 不引入 Vue Router 或 Pinia單頁報表不需要
  • 不修改現有 CSP 或安全策略
  • 不改動 WIP 快取更新機制

Decisions

D1: UI 框架選型 — Vue 3

選擇: Vue 3 (Composition API + SFC) 替代方案: React (JSX 轉換成本較高), Svelte (生態系較小) 理由: Vite 原生支援、template 語法接近 Jinja 降低遷移門檻、vue-echarts 整合成熟、支持漸進式頁面遷移

D2: 頁面服務方式 — Vite MPA HTML entry + Flask 靜態服務

選擇: Vite build 產出完整 HTML (frontend/src/qc-gate/index.html)Flask 以 send_from_directory 服務 替代方案: 最小 Jinja shell仍有 Jinja 依賴) 理由:

  • 完全脫離 Jinja2建立乾淨的遷移模式
  • 此頁面為唯讀報表,只有 GET 請求,不需要 CSRF token 注入
  • Toast 功能由 Vue component 自行實作(不依賴 _base.html 的全域 toast
  • Vite config 已有 manualChunks 設定,只需新增 HTML entry

D3: ECharts 引入方式 — npm + tree-shaking

選擇: npm install echarts vue-echarts,使用 tree-shaking 只引入 bar chart 相關模組 替代方案: 繼續使用靜態 minified 檔案(無法 tree-shake~1MB 理由: 既有 Vite config 已有 vendor-echarts chunk split 邏輯npm 引入後可 tree-shake 到只需 bar chart 模組(~200KB gzipped

D4: API 設計 — 新增 /api/qc-gate/summary 端點

選擇: 新建 qc_gate_routes.py blueprint + qc_gate_service.py 服務 替代方案: 擴展現有 wip_routes不符合 SRP 理由:

  • 從 WIP Redis 快取中讀取,篩選 SPECNAME LIKE '%QC%GATE%'
  • 使用 DW_MES_SPEC_WORKCENTER_V(已在 filter_cache.py 中快取)取得站點排序
  • 在後端完成 6HR 分級計算,前端只負責渲染
  • 回傳結構包含 summary條圖資料和 lots清單資料

D5: QC-GATE 站點識別與排序

選擇: 從 WIP 快取篩選 SPECNAME 包含 "QC" 和 "GATE" 的 LOT站點排序從 DW_MES_SPEC_WORKCENTER_VSPEC 欄位匹配取得 SPEC_ORDER 理由: SPECNAME 是 LOT 層級的製程步驟名稱DW_MES_SPEC_WORKCENTER_V 是維度主表提供排序資訊

D6: 等待時間分級

選擇: 四級分組

  • < 6hr — 正常(綠色)
  • 6-12hr — 注意(黃色)
  • 12-24hr — 警告(橙色)
  • > 24hr — 超時(紅色)

計算: wait_hours = (SYS_DATE - MOVEINTIMESTAMP) 以小時為單位,在後端計算

D7: 自動刷新 — 複用 wip-overview 模式

選擇: 10 分鐘 setInterval + visibilitychange 即時刷新 理由: 與 WIP 快取同步週期一致避免無效請求tab 隱藏時跳過刷新

Architecture

┌─ Vite Build ──────────────────────────────┐
│ frontend/src/qc-gate/                     │
│   index.html       ← HTML entry (no Jinja)│
│   main.js          ← createApp, mount     │
│   App.vue          ← root layout          │
│   components/                             │
│     QcGateChart.vue  ← ECharts stacked bar│
│     LotTable.vue     ← filterable table   │
│   composables/                            │
│     useQcGateData.js ← fetch + transform  │
│     useAutoRefresh.js← 10min refresh logic│
│   style.css        ← page styles          │
│                                           │
│ Build output → static/dist/qc-gate.html   │
│                static/dist/qc-gate.js     │
│                static/dist/qc-gate.css    │
└───────────────────────────────────────────┘

┌─ Flask Backend ───────────────────────────┐
│ routes/qc_gate_routes.py                  │
│   GET /api/qc-gate/summary               │
│                                           │
│ services/qc_gate_service.py               │
│   get_qc_gate_summary()                   │
│     ├─ get_cached_wip_data()  (Redis)     │
│     ├─ filter SPECNAME %QC%GATE%          │
│     ├─ compute wait_hours per LOT         │
│     └─ group by SPECNAME + bucket         │
│                                           │
│ app.py                                    │
│   GET /qc-gate → send_from_directory()    │
└───────────────────────────────────────────┘

API Response Shape

{
  "cache_time": "2026-02-09T14:30:00",
  "stations": [
    {
      "specname": "QC-GATE-DB",
      "spec_order": "030",
      "buckets": {
        "lt_6h": 12,
        "6h_12h": 5,
        "12h_24h": 3,
        "gt_24h": 1
      },
      "total": 21,
      "lots": [
        {
          "lot_id": "L001",
          "container_id": "C001",
          "product": "PKG-A",
          "qty": 5000,
          "step": "QC-GATE-DB",
          "workorder": "WO123",
          "move_in_time": "2026-02-09T08:30:00",
          "wait_hours": 6.0,
          "bucket": "6h_12h",
          "status": "QUEUE",
          "equipment": null
        }
      ]
    }
  ]
}

Vite Config Changes

// vite.config.js additions
import vue from '@vitejs/plugin-vue';

export default defineConfig({
  plugins: [vue()],
  build: {
    rollupOptions: {
      input: {
        // ... existing entries ...
        'qc-gate': resolve(__dirname, 'src/qc-gate/index.html')  // HTML entry
      },
      output: {
        manualChunks(id) {
          if (!id.includes('node_modules')) return;
          if (id.includes('echarts')) return 'vendor-echarts';
          if (id.includes('vue')) return 'vendor-vue';  // 新增 Vue chunk
          return 'vendor';
        }
      }
    }
  }
});

Risks / Trade-offs

  • [Vue plugin 影響既有 build]@vitejs/plugin-vue 只處理 .vue 檔案,不影響現有 .js entry points。已驗證 Vite plugin 系統為 additive。
  • [ECharts npm vs 靜態檔案共存] → 新頁面用 npm echarts舊頁面繼續用靜態檔案。vendor-echarts chunk 只被 qc-gate 引用,不影響舊頁面 bundle size。
  • [SPECNAME pattern 可能變動] → 篩選邏輯集中在 qc_gate_service.py 單一位置,易於調整。
  • [純靜態 HTML 無法使用 Flask template context] → 此頁面為唯讀報表,不需要 CSRF、不需要 session 資料。認證由 portal iframe 外層處理。