feat(wip): migrate WIP trio pages from Jinja2 to Vue 3 + Vite
Migrate /wip-overview, /wip-detail, and /hold-detail (1,941 lines vanilla JS) to Vue 3 SFC architecture. Extract shared CSS/constants/components to wip-shared/. Switch Pareto charts to vue-echarts with autoresize. Replace Jinja2 template injection with frontend URL params + constant classification for Hold Detail. Add 10-min auto-refresh + AbortController to Hold Detail. Remove three Jinja2 templates, update Flask routes to send_from_directory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-09
|
||||
@@ -0,0 +1,127 @@
|
||||
## Context
|
||||
|
||||
WIP 三頁(Overview、Detail、Hold Detail)是報表類核心頁面,合計 1,941 行 vanilla JS,目前透過 Jinja2 `_base.html` 載入 `window.MesApi` 和 `window.Toast` 全域物件。QC-GATE 和 Tables 頁面已成功遷移至純 Vite 架構,建立了 `send_from_directory` + `apiGet`/`apiPost` 模式。
|
||||
|
||||
三頁存在 drill-down 導覽關係:
|
||||
- Overview → Detail:點擊 matrix workcenter,透過 URL params 傳遞 `workcenter` + 四個篩選條件
|
||||
- Overview → Hold Detail:點擊 Pareto 柱/表格連結,透過 `?reason=` 傳遞
|
||||
|
||||
所有後端 API 均為 GET,無 CSRF 依賴,後端路由和 API 不需修改。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
- 三頁完全脫離 Jinja2 模板和 `window.MesApi` 依賴
|
||||
- 保持現有功能完全一致(pixel-level 不要求,行為一致即可)
|
||||
- 建立三頁共用的 CSS 變數和基礎樣式模組
|
||||
- Hold Detail 新增自動刷新 + AbortController(與另兩頁一致)
|
||||
- 保持三頁之間的 drill-down 導覽正常運作
|
||||
|
||||
**Non-Goals:**
|
||||
- 不引入 Vue Router SPA 架構(三頁仍為獨立 HTML entry)
|
||||
- 不引入 Pinia 狀態管理(composable 足夠)
|
||||
- 不重構後端 API 或資料結構
|
||||
- 不改變 portal iframe 嵌入機制
|
||||
- 不引入 vue-echarts 套件(直接使用 echarts API,與 QC-GATE 模式一致)
|
||||
|
||||
## Decisions
|
||||
|
||||
### D1: 三頁保持獨立 entry(不合併為 SPA)
|
||||
|
||||
**選擇**:每頁獨立 `index.html` entry point,沿用 QC-GATE/Tables 模式。
|
||||
|
||||
**替代方案**:Vue Router SPA 將三頁合併為一個 entry。
|
||||
|
||||
**理由**:
|
||||
- 三頁在 portal iframe 中各自獨立載入,SPA 無法帶來路由切換加速
|
||||
- 獨立 entry 與既有遷移模式一致,降低風險
|
||||
- 各頁 bundle 獨立,不會因一頁改動影響其他頁面的快取
|
||||
|
||||
### D2: Hold Detail 的 hold_type 判斷移至前端
|
||||
|
||||
**選擇**:在前端維護 `NON_QUALITY_HOLD_REASONS` 常數集合(11 個值),從 URL `?reason=` 讀取後在前端判斷。
|
||||
|
||||
**替代方案**:新增 API endpoint 回傳 hold_type 分類。
|
||||
|
||||
**理由**:
|
||||
- 集合很小且穩定(11 個非品質原因值)
|
||||
- 避免新增 API 的維護成本
|
||||
- Summary API 已回傳足夠資訊,無需額外 round-trip
|
||||
- 若未來集合需要動態管理,可改為從 config API 載入
|
||||
|
||||
### D3: 共用 CSS 提取為 `wip-shared.css`
|
||||
|
||||
**選擇**:建立 `frontend/src/wip-shared/styles.css`,包含三頁共用的 `:root` 變數、gradient header、loading overlay、card、button、pagination、responsive breakpoints。各頁 `style.css` 只包含頁面特有樣式,透過 `@import` 引入共用樣式。
|
||||
|
||||
**替代方案**:每頁複製一份完整 CSS。
|
||||
|
||||
**理由**:
|
||||
- 三頁 CSS 基底高度重複(`:root` 變數、header、loading overlay、summary card 完全相同)
|
||||
- 減少維護成本,修改一處即可影響三頁
|
||||
- Vite 會將 `@import` 合併到各頁 bundle,不增加 HTTP 請求
|
||||
|
||||
### D4: Autocomplete 提取為共用 Vue composable
|
||||
|
||||
**選擇**:建立 `frontend/src/wip-shared/composables/useAutocomplete.js`,封裝 debounce 搜尋 + cross-filter + dropdown 狀態。Overview 和 Detail 共用。
|
||||
|
||||
**理由**:
|
||||
- 兩頁的 autocomplete 邏輯幾乎相同(4 個欄位、cross-filter、debounce 300ms)
|
||||
- 現有 `core/autocomplete.js` 提供底層函式,composable 封裝 Vue 反應式狀態
|
||||
- Hold Detail 不使用 autocomplete,不受影響
|
||||
|
||||
### D5: ECharts 直接使用(不引入 vue-echarts)
|
||||
|
||||
**選擇**:與 QC-GATE 一致,在 Vue 元件中直接使用 `echarts.init()` + `onMounted`/`onUnmounted` 管理生命週期。
|
||||
|
||||
**理由**:
|
||||
- QC-GATE 已建立此模式且運作良好
|
||||
- 避免引入額外依賴
|
||||
- Pareto 圖 click 事件需要 drill-down 到 `/hold-detail`,直接操作更靈活
|
||||
|
||||
### D6: Hold Detail Flask route 保持 server-side redirect
|
||||
|
||||
**選擇**:`/hold-detail` route 保留 server-side `reason` 參數驗證(缺少時 redirect 到 `/wip-overview`),驗證通過後 `send_from_directory` 回傳靜態 HTML。
|
||||
|
||||
**替代方案**:完全移除 server-side 驗證,在前端處理缺少 reason 的情況。
|
||||
|
||||
**理由**:
|
||||
- 保持與現有行為一致(無 reason 時不顯示空白頁面)
|
||||
- Server-side redirect 比前端 `window.location` 更快
|
||||
- Blueprint 路由只需小幅修改
|
||||
|
||||
### D7: 元件拆分策略
|
||||
|
||||
**WIP Overview(App.vue + 5 元件):**
|
||||
- `FilterPanel.vue`:4 個 autocomplete 輸入 + filter tags
|
||||
- `SummaryCards.vue`:2 個 KPI 卡片
|
||||
- `StatusCards.vue`:4 個可點擊狀態卡片
|
||||
- `MatrixTable.vue`:Workcenter × Package 交叉表
|
||||
- `ParetoSection.vue`:Pareto 圖 + 明細表(品質/非品質各一個實例)
|
||||
|
||||
**WIP Detail(App.vue + 5 元件):**
|
||||
- `FilterPanel.vue`:4 個 autocomplete 輸入(與 Overview 共用 composable)
|
||||
- `SummaryCards.vue`:5 個狀態 KPI 卡片
|
||||
- `LotTable.vue`:sticky 欄位 + spec 動態欄 + 分頁
|
||||
- `LotDetailPanel.vue`:inline 展開式 lot 明細面板
|
||||
- `Pagination.vue`:分頁控制(可與 Hold Detail 共用)
|
||||
|
||||
**Hold Detail(App.vue + 5 元件):**
|
||||
- `SummaryCards.vue`:5 個 KPI 卡片
|
||||
- `AgeDistribution.vue`:4 個可點擊 age 卡片
|
||||
- `DistributionTable.vue`:Workcenter/Package 分佈表(2 個實例)
|
||||
- `LotTable.vue`:10 欄 lot 明細表 + filter indicator
|
||||
- `Pagination.vue`:分頁控制
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
**[Hold Detail reason redirect] → 前端 fallback**
|
||||
`send_from_directory` 回傳靜態 HTML 後,前端需在 `onMounted` 中檢查 `URLSearchParams` 是否有 `reason`,若無則 `window.location.href = '/wip-overview'`。Server-side redirect 只在直接 URL 存取時作用,iframe 載入時也需要前端保護。
|
||||
|
||||
**[CSS 提取可能遺漏] → 逐頁驗證**
|
||||
三頁 CSS 雖然高度重複但並非完全相同(如 Hold Detail header 用動態顏色、Detail 有 sticky column 樣式)。提取共用部分時需逐頁視覺驗證,確保沒有遺漏特有樣式。
|
||||
|
||||
**[NON_QUALITY_HOLD_REASONS 同步] → 單一來源**
|
||||
前端維護的 11 個非品質原因值必須與後端 `sql/filters.py` 的 `NON_QUALITY_HOLD_REASONS` 保持一致。可在 `wip-shared/constants.js` 中建立,並在 code review 時交叉比對。
|
||||
|
||||
**[三頁同時遷移範圍較大] → 逐頁推進**
|
||||
1,941 行一次遷移風險較高。實作順序建議:Hold Detail(最簡單 336 行) → Overview(核心頁面 784 行) → Detail(最複雜 821 行),每頁完成後即可獨立測試。
|
||||
@@ -0,0 +1,35 @@
|
||||
## Why
|
||||
|
||||
WIP 三頁(Overview、Detail、Hold Detail)是目前使用量最高的報表頁面,仍依賴 Jinja2 模板 + vanilla JS 架構。三頁有 drill-down 導覽依賴關係,必須作為一個整體遷移至 Vue 3 + Vite 純前端架構,以統一前端技術棧並消除 `_base.html` / `window.MesApi` 依賴。QC-GATE 和 Tables 頁面已成功建立遷移模式,現在是批量套用此模式的時機。
|
||||
|
||||
## What Changes
|
||||
|
||||
- 將 `/wip-overview`(784 行 vanilla JS)重寫為 Vue 3 SFC 元件,包含 ECharts Pareto 圖、autocomplete 篩選、狀態卡片矩陣互動
|
||||
- 將 `/wip-detail`(821 行 vanilla JS)重寫為 Vue 3 SFC 元件,包含 4 sticky 欄位表格、動態 spec 欄、inline lot detail panel
|
||||
- 將 `/hold-detail`(336 行 vanilla JS)重寫為 Vue 3 SFC 元件,包含 age/workcenter/package 三維篩選
|
||||
- 三頁 Vite entry 從 `main.js` 改為 `index.html`,Flask route 從 `render_template` 改為 `send_from_directory`
|
||||
- 刪除三個 Jinja2 模板(`wip_overview.html`、`wip_detail.html`、`hold_detail.html`)
|
||||
- Hold Detail 移除 Jinja2 server-side 注入(`reason`、`hold_type`),改為前端 URL params + 常數判斷
|
||||
- Hold Detail 新增 10 分鐘自動刷新 + AbortController(與 Overview/Detail 一致)
|
||||
- 提取三頁共用 CSS 變數與基礎樣式為共用模組
|
||||
- 所有 `window.MesApi.get()` 呼叫改為 `apiGet()` from `core/api.js`
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `wip-overview-page`: WIP Overview 頁面的功能需求(summary、matrix、hold pareto、autocomplete 篩選、狀態卡片互動、drill-down 導覽)
|
||||
- `wip-detail-page`: WIP Detail 頁面的功能需求(workcenter lot 明細、sticky 欄位表格、spec 動態欄、inline lot detail panel、autocomplete 篩選、狀態卡片互動)
|
||||
- `hold-detail-page`: Hold Detail 頁面的功能需求(hold reason 分析、age/workcenter/package 三維篩選、分頁 lot 明細)
|
||||
|
||||
### Modified Capabilities
|
||||
- `vue-vite-page-architecture`: 新增三頁的 Vite entry 與 chunk splitting 規則,擴展 ECharts 共用 chunk 至 Overview 頁面
|
||||
|
||||
## Impact
|
||||
|
||||
- **前端**:`frontend/src/wip-overview/`、`frontend/src/wip-detail/`、`frontend/src/hold-detail/` 目錄結構重組為 Vue 3 SFC
|
||||
- **Vite 配置**:`vite.config.js` 三個 entry 從 `main.js` 改為 `index.html`
|
||||
- **Flask 路由**:`app.py` 中 `/wip-overview`、`/wip-detail` 改為 `send_from_directory`;`hold_routes.py` 中 `/hold-detail` 改為 `send_from_directory`(需保留 reason 驗證邏輯改為 API 層)
|
||||
- **模板刪除**:`templates/wip_overview.html`、`templates/wip_detail.html`、`templates/hold_detail.html`
|
||||
- **共用模組**:`core/wip-derive.js`、`core/autocomplete.js` 保持不變(已為 ES module);`core/table-tree.js` 的 `escapeHtml` 在 Vue 中不再需要
|
||||
- **建置腳本**:`package.json` build script 需 copy 三個新 HTML 檔案
|
||||
- **後端 API**:所有 API endpoint 不變,僅 `/hold-detail` 頁面路由變更
|
||||
@@ -0,0 +1,115 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Hold Detail page SHALL display hold reason analysis
|
||||
The page SHALL show summary statistics for a specific hold reason.
|
||||
|
||||
#### Scenario: Summary cards rendering
|
||||
- **WHEN** the page loads with `?reason={reason}` in the URL
|
||||
- **THEN** the page SHALL call `GET /api/wip/hold-detail/summary` with the reason
|
||||
- **THEN** five cards SHALL display: Total Lots, Total QTY, 平均當站滯留, 最久當站滯留, 影響站群
|
||||
- **THEN** age values SHALL display with "天" suffix
|
||||
|
||||
#### Scenario: Hold type classification
|
||||
- **WHEN** the page loads
|
||||
- **THEN** the header gradient color SHALL be red (#ef4444) for quality holds or orange (#f97316) for non-quality holds
|
||||
- **THEN** a badge SHALL display "品質異常" or "非品質異常" accordingly
|
||||
- **THEN** classification SHALL use a frontend `NON_QUALITY_HOLD_REASONS` constant set (11 values matching backend `sql/filters.py`)
|
||||
|
||||
#### Scenario: Missing reason parameter
|
||||
- **WHEN** the page loads without a `reason` URL parameter
|
||||
- **THEN** the page SHALL redirect to `/wip-overview`
|
||||
|
||||
### Requirement: Hold Detail page SHALL display age distribution
|
||||
The page SHALL show the distribution of hold lots by age at current station.
|
||||
|
||||
#### Scenario: Age distribution cards
|
||||
- **WHEN** distribution data is loaded from `GET /api/wip/hold-detail/distribution`
|
||||
- **THEN** four clickable cards SHALL display: 0-1天, 1-3天, 3-7天, 7+天
|
||||
- **THEN** each card SHALL show Lots, QTY, and percentage
|
||||
|
||||
#### Scenario: Age card click filters lots
|
||||
- **WHEN** user clicks an age card
|
||||
- **THEN** the lot table SHALL reload filtered to that age range
|
||||
- **THEN** the clicked card SHALL show a blue active border
|
||||
- **THEN** clicking the same card again SHALL remove the filter
|
||||
|
||||
### Requirement: Hold Detail page SHALL display workcenter and package distribution
|
||||
The page SHALL show distribution tables for workcenter and package breakdowns.
|
||||
|
||||
#### Scenario: Distribution tables rendering
|
||||
- **WHEN** distribution data is loaded
|
||||
- **THEN** two side-by-side tables SHALL display: By Workcenter and By Package
|
||||
- **THEN** each table SHALL show Name, Lots, QTY, and percentage columns
|
||||
- **THEN** tables SHALL be scrollable with max-height 300px
|
||||
|
||||
#### Scenario: Distribution row click filters lots
|
||||
- **WHEN** user clicks a row in the workcenter or package table
|
||||
- **THEN** the lot table SHALL reload filtered by that workcenter or package
|
||||
- **THEN** the clicked row SHALL show an active highlight
|
||||
- **THEN** clicking the same row again SHALL remove the filter
|
||||
|
||||
### Requirement: Hold Detail page SHALL display paginated lot details
|
||||
The page SHALL display detailed lot information with server-side pagination.
|
||||
|
||||
#### Scenario: Lot table rendering
|
||||
- **WHEN** lot data is loaded from `GET /api/wip/hold-detail/lots`
|
||||
- **THEN** a table SHALL display with 10 columns: LOTID, WORKORDER, QTY, Package, Workcenter, Spec, Age, Hold By, Dept, Hold Comment
|
||||
- **THEN** age values SHALL display with "天" suffix
|
||||
|
||||
#### Scenario: Filter indicator
|
||||
- **WHEN** any filter is active (workcenter, package, or age range)
|
||||
- **THEN** a blue filter indicator bar SHALL display showing active filters (e.g., "篩選: Workcenter=WC-A, Age=3-7天")
|
||||
- **THEN** clicking the "×" on the indicator SHALL clear all filters
|
||||
|
||||
#### Scenario: Pagination
|
||||
- **WHEN** total pages exceeds 1
|
||||
- **THEN** Prev/Next buttons and page info SHALL display
|
||||
- **THEN** page info SHALL show "顯示 {start} - {end} / {total}"
|
||||
|
||||
#### Scenario: Filter changes reset pagination
|
||||
- **WHEN** any filter is toggled
|
||||
- **THEN** pagination SHALL reset to page 1
|
||||
|
||||
### Requirement: Hold Detail page SHALL have back navigation to Overview
|
||||
The page SHALL provide a way to return to the WIP Overview page.
|
||||
|
||||
#### Scenario: Back button
|
||||
- **WHEN** user clicks the "← WIP Overview" button in the header
|
||||
- **THEN** the page SHALL navigate to `/wip-overview`
|
||||
|
||||
### Requirement: Hold Detail page SHALL auto-refresh and handle request cancellation
|
||||
The page SHALL automatically refresh data and prevent stale request pile-up.
|
||||
|
||||
#### Scenario: Auto-refresh interval
|
||||
- **WHEN** the page is loaded
|
||||
- **THEN** data SHALL auto-refresh every 10 minutes
|
||||
- **THEN** auto-refresh SHALL be skipped when the tab is hidden
|
||||
|
||||
#### Scenario: Visibility change refresh
|
||||
- **WHEN** the tab becomes visible after being hidden
|
||||
- **THEN** data SHALL refresh immediately
|
||||
|
||||
#### Scenario: Request cancellation
|
||||
- **WHEN** a new data load is triggered while a previous request is in-flight
|
||||
- **THEN** the previous request SHALL be cancelled via AbortController
|
||||
- **THEN** the cancelled request SHALL NOT update the UI
|
||||
|
||||
#### Scenario: Manual refresh
|
||||
- **WHEN** user clicks the "重新整理" button
|
||||
- **THEN** data SHALL reload and the auto-refresh timer SHALL reset
|
||||
|
||||
### Requirement: Hold Detail page SHALL handle loading and error states
|
||||
The page SHALL display appropriate feedback during API calls and on errors.
|
||||
|
||||
#### Scenario: Initial loading overlay
|
||||
- **WHEN** the page first loads
|
||||
- **THEN** a full-page loading overlay SHALL display until all data is loaded
|
||||
|
||||
#### Scenario: API error handling
|
||||
- **WHEN** an API call fails
|
||||
- **THEN** the affected section SHALL display an error message
|
||||
- **THEN** the page SHALL NOT crash or become unresponsive
|
||||
|
||||
#### Scenario: Empty lot result
|
||||
- **WHEN** a query returns zero lots
|
||||
- **THEN** the lot table SHALL display a "No data" placeholder
|
||||
@@ -0,0 +1,47 @@
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Vite config SHALL support Vue SFC and HTML entry points
|
||||
The Vite build configuration SHALL support Vue Single File Components alongside existing vanilla JS entries.
|
||||
|
||||
#### Scenario: Vue plugin coexistence
|
||||
- **WHEN** `vite build` is executed
|
||||
- **THEN** Vue SFC (`.vue` files) SHALL be compiled by `@vitejs/plugin-vue`
|
||||
- **THEN** existing vanilla JS entry points SHALL continue to build without modification
|
||||
|
||||
#### Scenario: HTML entry point
|
||||
- **WHEN** a page uses an HTML file as its Vite entry point
|
||||
- **THEN** Vite SHALL process the HTML and its referenced JS/CSS into `static/dist/`
|
||||
- **THEN** the output SHALL include `<page-name>.html`, `<page-name>.js`, and `<page-name>.css`
|
||||
|
||||
#### Scenario: Chunk splitting
|
||||
- **WHEN** Vite builds the project
|
||||
- **THEN** Vue runtime SHALL be split into a `vendor-vue` chunk
|
||||
- **THEN** ECharts modules SHALL be split into the existing `vendor-echarts` chunk
|
||||
- **THEN** chunk splitting SHALL NOT affect existing page bundles
|
||||
|
||||
#### Scenario: Migrated page entry replacement
|
||||
- **WHEN** a vanilla JS page is migrated to Vue 3
|
||||
- **THEN** its Vite entry SHALL change from JS file to HTML file (e.g., `src/wip-overview/main.js` → `src/wip-overview/index.html`)
|
||||
- **THEN** the original JS entry SHALL be replaced, not kept alongside
|
||||
|
||||
#### Scenario: Shared CSS import across migrated pages
|
||||
- **WHEN** multiple migrated pages import a shared CSS module (e.g., `wip-shared/styles.css`)
|
||||
- **THEN** Vite SHALL bundle the shared CSS into each page's output CSS
|
||||
- **THEN** shared CSS SHALL NOT create a separate shared chunk that requires additional HTTP requests
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Pure Vite pages with server-side route validation SHALL use send_from_directory with pre-validation
|
||||
Pages that require server-side parameter validation before serving SHALL validate parameters in the Flask route and then serve the static HTML.
|
||||
|
||||
#### Scenario: Hold Detail reason validation
|
||||
- **WHEN** user navigates to `/hold-detail` without a `reason` parameter
|
||||
- **THEN** Flask SHALL redirect to `/wip-overview`
|
||||
- **WHEN** user navigates to `/hold-detail?reason={value}`
|
||||
- **THEN** Flask SHALL serve the pre-built HTML file from `static/dist/` via `send_from_directory`
|
||||
- **THEN** the HTML SHALL NOT pass through Jinja2 template rendering
|
||||
|
||||
#### Scenario: Frontend fallback validation
|
||||
- **WHEN** the pure Vite hold-detail page loads
|
||||
- **THEN** the page SHALL read `reason` from URL parameters
|
||||
- **THEN** if `reason` is empty or missing, the page SHALL redirect to `/wip-overview`
|
||||
@@ -0,0 +1,120 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Detail page SHALL receive drill-down parameters from Overview
|
||||
The page SHALL read URL query parameters to initialize its state from the Overview page drill-down.
|
||||
|
||||
#### Scenario: URL parameter initialization
|
||||
- **WHEN** the page loads with `?workcenter={name}` in the URL
|
||||
- **THEN** the page SHALL use the specified workcenter for data loading
|
||||
- **THEN** the page title SHALL display "WIP Detail - {workcenter}"
|
||||
|
||||
#### Scenario: Filter passthrough from Overview
|
||||
- **WHEN** the URL contains additional filter parameters (workorder, lotid, package, type)
|
||||
- **THEN** filter inputs SHALL be pre-filled with those values
|
||||
- **THEN** data SHALL be loaded with those filters applied
|
||||
|
||||
#### Scenario: Missing workcenter fallback
|
||||
- **WHEN** the page loads without a `workcenter` parameter
|
||||
- **THEN** the page SHALL fetch available workcenters from `GET /api/wip/meta/workcenters`
|
||||
- **THEN** the first workcenter SHALL be used and the URL SHALL be updated via `replaceState`
|
||||
|
||||
### Requirement: Detail page SHALL display WIP summary cards
|
||||
The page SHALL display five summary cards with status counts for the current workcenter.
|
||||
|
||||
#### Scenario: Summary cards rendering
|
||||
- **WHEN** detail data is loaded
|
||||
- **THEN** five cards SHALL display: Total Lots, RUN, QUEUE, 品質異常, 非品質異常
|
||||
|
||||
#### Scenario: Status card click filters table
|
||||
- **WHEN** user clicks a status card (RUN, QUEUE, 品質異常, 非品質異常)
|
||||
- **THEN** the lot table SHALL reload filtered to that status
|
||||
- **THEN** the active card SHALL show a visual active state
|
||||
- **THEN** non-active status cards SHALL dim
|
||||
- **THEN** clicking the same card again SHALL remove the filter
|
||||
|
||||
### Requirement: Detail page SHALL display lot details table with sticky columns
|
||||
The page SHALL display a scrollable table with fixed left columns and dynamic spec columns.
|
||||
|
||||
#### Scenario: Table with sticky columns
|
||||
- **WHEN** lot data is loaded from `GET /api/wip/detail/{workcenter}`
|
||||
- **THEN** the table SHALL display with 4 sticky left columns: LOT ID, Equipment, WIP Status, Package
|
||||
- **THEN** dynamic spec columns (e.g., 1OO, 2OO, TC) SHALL render to the right
|
||||
- **THEN** the sticky columns SHALL remain visible during horizontal scroll
|
||||
|
||||
#### Scenario: LOT ID is clickable
|
||||
- **WHEN** user clicks a LOT ID in the table
|
||||
- **THEN** the lot detail panel SHALL open below the table
|
||||
- **THEN** the clicked LOT ID SHALL show an active highlight
|
||||
|
||||
#### Scenario: WIP Status display
|
||||
- **WHEN** a lot has status HOLD
|
||||
- **THEN** the status cell SHALL display "HOLD ({holdReason})" with red styling
|
||||
- **WHEN** a lot has status RUN or QUEUE
|
||||
- **THEN** the status cell SHALL display with green or yellow styling respectively
|
||||
|
||||
#### Scenario: Spec column data display
|
||||
- **WHEN** a lot's spec matches a spec column
|
||||
- **THEN** the cell SHALL display the lot QTY with green background
|
||||
- **THEN** non-matching spec cells SHALL be empty
|
||||
|
||||
### Requirement: Detail page SHALL display inline lot detail panel
|
||||
The page SHALL show expandable lot detail information when a LOT ID is clicked.
|
||||
|
||||
#### Scenario: Lot detail loading
|
||||
- **WHEN** user clicks a LOT ID
|
||||
- **THEN** the panel SHALL call `GET /api/wip/lot/{lotid}`
|
||||
- **THEN** a loading indicator SHALL display while fetching
|
||||
|
||||
#### Scenario: Lot detail sections
|
||||
- **WHEN** lot detail data is loaded
|
||||
- **THEN** the panel SHALL display sections: 基本資訊, 產品資訊, 製程資訊, 物料資訊
|
||||
- **THEN** Hold 資訊 section SHALL display only when status is HOLD or holdCount > 0
|
||||
- **THEN** NCR 資訊 section SHALL display only when ncrId exists
|
||||
|
||||
#### Scenario: Close lot detail
|
||||
- **WHEN** user clicks the Close button on the panel
|
||||
- **THEN** the panel SHALL be hidden
|
||||
- **THEN** the LOT ID highlight SHALL be removed
|
||||
|
||||
### Requirement: Detail page SHALL support autocomplete filtering
|
||||
The page SHALL provide autocomplete-enabled filter inputs identical to Overview.
|
||||
|
||||
#### Scenario: Autocomplete with cross-filtering
|
||||
- **WHEN** user types 2+ characters in a filter input
|
||||
- **THEN** the page SHALL call `GET /api/wip/meta/search` with debounce (300ms)
|
||||
- **THEN** cross-filter parameters SHALL be included
|
||||
- **THEN** suggestions SHALL appear in a dropdown
|
||||
|
||||
#### Scenario: Apply filters resets pagination
|
||||
- **WHEN** user applies filters
|
||||
- **THEN** pagination SHALL reset to page 1
|
||||
- **THEN** table data SHALL reload with the new filters
|
||||
|
||||
### Requirement: Detail page SHALL support server-side pagination
|
||||
The page SHALL paginate lot data with server-side support.
|
||||
|
||||
#### Scenario: Pagination controls
|
||||
- **WHEN** total pages exceeds 1
|
||||
- **THEN** Prev/Next buttons and page info SHALL display
|
||||
- **THEN** Prev SHALL be disabled on page 1
|
||||
- **THEN** Next SHALL be disabled on the last page
|
||||
|
||||
#### Scenario: Page navigation
|
||||
- **WHEN** user clicks Next or Prev
|
||||
- **THEN** data SHALL reload with the updated page number
|
||||
|
||||
### Requirement: Detail page SHALL have back navigation to Overview
|
||||
The page SHALL provide a way to return to the Overview page.
|
||||
|
||||
#### Scenario: Back button
|
||||
- **WHEN** user clicks the "← Overview" button in the header
|
||||
- **THEN** the page SHALL navigate to `/wip-overview`
|
||||
|
||||
### Requirement: Detail page SHALL auto-refresh and handle request cancellation
|
||||
The page SHALL auto-refresh and cancel stale requests identically to Overview.
|
||||
|
||||
#### Scenario: Auto-refresh and cancellation
|
||||
- **WHEN** the page is loaded
|
||||
- **THEN** data SHALL auto-refresh every 10 minutes, skipping when tab is hidden
|
||||
- **THEN** visibility change SHALL trigger immediate refresh
|
||||
- **THEN** new requests SHALL cancel in-flight requests via AbortController
|
||||
@@ -0,0 +1,107 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Overview page SHALL display WIP summary statistics
|
||||
The page SHALL fetch and display total lot count and total quantity as summary cards.
|
||||
|
||||
#### Scenario: Summary cards rendering
|
||||
- **WHEN** the page loads
|
||||
- **THEN** the page SHALL call `GET /api/wip/overview/summary`
|
||||
- **THEN** summary cards SHALL display Total Lots and Total QTY with zh-TW number formatting
|
||||
- **THEN** values SHALL animate with a scale transition when updated
|
||||
|
||||
#### Scenario: Data update timestamp
|
||||
- **WHEN** summary data is loaded
|
||||
- **THEN** the header SHALL display the `dataUpdateDate` from the API response
|
||||
|
||||
### Requirement: Overview page SHALL display WIP status breakdown cards
|
||||
The page SHALL display four clickable status cards (RUN, QUEUE, 品質異常, 非品質異常) with lot and quantity counts.
|
||||
|
||||
#### Scenario: Status cards rendering
|
||||
- **WHEN** summary data is loaded
|
||||
- **THEN** four status cards SHALL be displayed with color coding (green=RUN, yellow=QUEUE, red=品質異常, orange=非品質異常)
|
||||
- **THEN** each card SHALL show lot count and quantity
|
||||
|
||||
#### Scenario: Status card click filters matrix
|
||||
- **WHEN** user clicks a status card
|
||||
- **THEN** the matrix table SHALL reload with the selected status filter
|
||||
- **THEN** the clicked card SHALL show an active visual state
|
||||
- **THEN** non-active cards SHALL dim to 50% opacity
|
||||
- **THEN** clicking the same card again SHALL deactivate the filter and restore all cards
|
||||
|
||||
### Requirement: Overview page SHALL display Workcenter × Package matrix
|
||||
The page SHALL display a cross-tabulation table of workcenters vs packages.
|
||||
|
||||
#### Scenario: Matrix table rendering
|
||||
- **WHEN** matrix data is loaded from `GET /api/wip/overview/matrix`
|
||||
- **THEN** the table SHALL display workcenters as rows and packages as columns (limited to top 15)
|
||||
- **THEN** the first column (Workcenter) SHALL be sticky on horizontal scroll
|
||||
- **THEN** a Total row and Total column SHALL be displayed
|
||||
|
||||
#### Scenario: Matrix workcenter drill-down
|
||||
- **WHEN** user clicks a workcenter name in the matrix
|
||||
- **THEN** the page SHALL navigate to `/wip-detail?workcenter={name}`
|
||||
- **THEN** active filter values (workorder, lotid, package, type) SHALL be passed as URL parameters
|
||||
|
||||
### Requirement: Overview page SHALL display Hold Pareto analysis
|
||||
The page SHALL display Pareto charts and tables for quality and non-quality hold reasons.
|
||||
|
||||
#### Scenario: Pareto chart rendering
|
||||
- **WHEN** hold data is loaded from `GET /api/wip/overview/hold`
|
||||
- **THEN** hold items SHALL be split into quality and non-quality groups
|
||||
- **THEN** each group SHALL display an ECharts dual-axis Pareto chart (bar=QTY, line=cumulative %)
|
||||
- **THEN** items SHALL be sorted by QTY descending
|
||||
|
||||
#### Scenario: Pareto chart drill-down
|
||||
- **WHEN** user clicks a bar in the Pareto chart
|
||||
- **THEN** the page SHALL navigate to `/hold-detail?reason={reason}`
|
||||
|
||||
#### Scenario: Pareto table with drill-down links
|
||||
- **WHEN** Pareto data is rendered
|
||||
- **THEN** a table SHALL display below each chart with Hold Reason, Lots, QTY, and cumulative %
|
||||
- **THEN** reason names SHALL be clickable links to `/hold-detail?reason={reason}`
|
||||
|
||||
#### Scenario: Empty hold data
|
||||
- **WHEN** a hold type has no items
|
||||
- **THEN** the chart area SHALL display a "目前無資料" message
|
||||
- **THEN** the chart SHALL be cleared
|
||||
|
||||
### Requirement: Overview page SHALL support autocomplete filtering
|
||||
The page SHALL provide autocomplete-enabled filter inputs for WORKORDER, LOT ID, PACKAGE, and TYPE.
|
||||
|
||||
#### Scenario: Autocomplete search
|
||||
- **WHEN** user types 2+ characters in a filter input
|
||||
- **THEN** the page SHALL call `GET /api/wip/meta/search` with debounce (300ms)
|
||||
- **THEN** suggestions SHALL appear in a dropdown below the input
|
||||
- **THEN** cross-filter parameters SHALL be included (other active filter values)
|
||||
|
||||
#### Scenario: Apply and clear filters
|
||||
- **WHEN** user clicks "套用篩選" or presses Enter in a filter input
|
||||
- **THEN** all three API calls (summary, matrix, hold) SHALL reload with the filter values
|
||||
- **WHEN** user clicks "清除篩選"
|
||||
- **THEN** all filter inputs SHALL be cleared and data SHALL reload without filters
|
||||
|
||||
#### Scenario: Active filter display
|
||||
- **WHEN** filters are applied
|
||||
- **THEN** active filters SHALL be displayed as removable tags (e.g., "WO: {value} ×")
|
||||
- **THEN** clicking a tag's remove button SHALL clear that filter and reload data
|
||||
|
||||
### Requirement: Overview page SHALL auto-refresh and handle request cancellation
|
||||
The page SHALL automatically refresh data and prevent stale request pile-up.
|
||||
|
||||
#### Scenario: Auto-refresh interval
|
||||
- **WHEN** the page is loaded
|
||||
- **THEN** data SHALL auto-refresh every 10 minutes
|
||||
- **THEN** auto-refresh SHALL be skipped when the tab is hidden (`document.hidden`)
|
||||
|
||||
#### Scenario: Visibility change refresh
|
||||
- **WHEN** the tab becomes visible after being hidden
|
||||
- **THEN** data SHALL refresh immediately
|
||||
|
||||
#### Scenario: Request cancellation
|
||||
- **WHEN** a new data load is triggered while a previous request is in-flight
|
||||
- **THEN** the previous request SHALL be cancelled via AbortController
|
||||
- **THEN** the cancelled request SHALL NOT update the UI
|
||||
|
||||
#### Scenario: Manual refresh
|
||||
- **WHEN** user clicks the "重新整理" button
|
||||
- **THEN** data SHALL reload and the auto-refresh timer SHALL reset
|
||||
@@ -0,0 +1,61 @@
|
||||
## 1. Shared Infrastructure
|
||||
|
||||
- [x] 1.1 Create `frontend/src/wip-shared/styles.css` with shared CSS variables (`:root`), gradient header, loading overlay, summary card, button, pagination, filter indicator, and responsive breakpoints extracted from the three pages
|
||||
- [x] 1.2 Create `frontend/src/wip-shared/constants.js` with `NON_QUALITY_HOLD_REASONS` set (11 values matching backend `sql/filters.py`)
|
||||
- [x] 1.3 Create `frontend/src/wip-shared/composables/useAutoRefresh.js` composable encapsulating 10-min interval, visibility-change refresh, and AbortController cancellation
|
||||
- [x] 1.4 Create `frontend/src/wip-shared/composables/useAutocomplete.js` composable wrapping `core/autocomplete.js` with Vue reactive state, debounce 300ms, cross-filter, and dropdown management
|
||||
- [x] 1.5 Create `frontend/src/wip-shared/components/Pagination.vue` shared pagination component (Prev/Next buttons, page info display)
|
||||
|
||||
## 2. Hold Detail Page (336 lines → Vue 3)
|
||||
|
||||
- [x] 2.1 Create `frontend/src/hold-detail/index.html` Vite entry point and `main.js` Vue app bootstrap
|
||||
- [x] 2.2 Create `frontend/src/hold-detail/App.vue` with URL reason parameter reading, hold_type classification via `NON_QUALITY_HOLD_REASONS`, dynamic gradient header, missing-reason redirect, and auto-refresh setup
|
||||
- [x] 2.3 Create `frontend/src/hold-detail/components/SummaryCards.vue` (5 cards: Total Lots, Total QTY, 平均當站滯留, 最久當站滯留, 影響站群) calling `GET /api/wip/hold-detail/summary`
|
||||
- [x] 2.4 Create `frontend/src/hold-detail/components/AgeDistribution.vue` (4 clickable age cards: 0-1天, 1-3天, 3-7天, 7+天) with toggle filter
|
||||
- [x] 2.5 Create `frontend/src/hold-detail/components/DistributionTable.vue` (reusable for workcenter and package tables) with row click filter toggle, calling `GET /api/wip/hold-detail/distribution`
|
||||
- [x] 2.6 Create `frontend/src/hold-detail/components/LotTable.vue` (10 columns with filter indicator bar, "×" clear button) calling `GET /api/wip/hold-detail/lots`
|
||||
- [x] 2.7 Create `frontend/src/hold-detail/style.css` with page-specific styles (dynamic gradient, age cards, distribution tables) importing `wip-shared/styles.css`
|
||||
- [x] 2.8 Update `hold_routes.py` Flask route: keep reason validation redirect, change `render_template` to `send_from_directory` for `static/dist/hold-detail.html`
|
||||
- [x] 2.9 Delete `src/mes_dashboard/templates/hold_detail.html`
|
||||
- [x] 2.10 Verify Hold Detail page: summary cards, age/workcenter/package filtering, lot table pagination, back navigation, auto-refresh, missing-reason redirect
|
||||
|
||||
## 3. WIP Overview Page (784 lines → Vue 3)
|
||||
|
||||
- [x] 3.1 Create `frontend/src/wip-overview/index.html` Vite entry point and `main.js` Vue app bootstrap
|
||||
- [x] 3.2 Create `frontend/src/wip-overview/App.vue` with data loading orchestration (summary + matrix + hold), auto-refresh setup, and filter state management
|
||||
- [x] 3.3 Create `frontend/src/wip-overview/components/FilterPanel.vue` (4 autocomplete inputs using `useAutocomplete` composable, active filter tags with remove buttons, apply/clear buttons)
|
||||
- [x] 3.4 Create `frontend/src/wip-overview/components/SummaryCards.vue` (2 KPI cards: Total Lots, Total QTY with zh-TW number formatting and scale transition)
|
||||
- [x] 3.5 Create `frontend/src/wip-overview/components/StatusCards.vue` (4 clickable status cards: RUN, QUEUE, 品質異常, 非品質異常 with dim/active toggle)
|
||||
- [x] 3.6 Create `frontend/src/wip-overview/components/MatrixTable.vue` (workcenter × package cross-tab, sticky first column, Total row/column, workcenter click drill-down to `/wip-detail`)
|
||||
- [x] 3.7 Create `frontend/src/wip-overview/components/ParetoSection.vue` (ECharts dual-axis Pareto chart + detail table, bar click drill-down to `/hold-detail`, "目前無資料" empty state) — used as 2 instances (quality/non-quality)
|
||||
- [x] 3.8 Create `frontend/src/wip-overview/style.css` with page-specific styles importing `wip-shared/styles.css`
|
||||
- [x] 3.9 Update `app.py` Flask route for `/wip-overview`: change `render_template` to `send_from_directory`
|
||||
- [x] 3.10 Delete `src/mes_dashboard/templates/wip_overview.html`
|
||||
- [x] 3.11 Verify Overview page: summary cards, status card filtering, matrix drill-down, Pareto chart rendering and drill-down, autocomplete filtering, auto-refresh
|
||||
|
||||
## 4. WIP Detail Page (821 lines → Vue 3)
|
||||
|
||||
- [x] 4.1 Create `frontend/src/wip-detail/index.html` Vite entry point and `main.js` Vue app bootstrap
|
||||
- [x] 4.2 Create `frontend/src/wip-detail/App.vue` with URL parameter initialization (workcenter + 4 filters), workcenter fallback fetch, auto-refresh setup, and replaceState URL update
|
||||
- [x] 4.3 Create `frontend/src/wip-detail/components/FilterPanel.vue` (4 autocomplete inputs using `useAutocomplete` composable, shared with Overview pattern)
|
||||
- [x] 4.4 Create `frontend/src/wip-detail/components/SummaryCards.vue` (5 clickable status cards: Total Lots, RUN, QUEUE, 品質異常, 非品質異常 with dim/active toggle)
|
||||
- [x] 4.5 Create `frontend/src/wip-detail/components/LotTable.vue` (4 sticky left columns with cascading left positions, dynamic spec columns, LOT ID click to open detail panel, status color coding)
|
||||
- [x] 4.6 Create `frontend/src/wip-detail/components/LotDetailPanel.vue` (inline expandable panel with 4-column grid: 基本資訊, 產品資訊, 製程資訊, 物料資訊, conditional Hold/NCR sections)
|
||||
- [x] 4.7 Create `frontend/src/wip-detail/style.css` with page-specific styles (sticky columns, spec column coloring, lot detail panel) importing `wip-shared/styles.css`
|
||||
- [x] 4.8 Update `app.py` Flask route for `/wip-detail`: change `render_template` to `send_from_directory`
|
||||
- [x] 4.9 Delete `src/mes_dashboard/templates/wip_detail.html`
|
||||
- [x] 4.10 Verify Detail page: URL param initialization, workcenter fallback, summary card filtering, sticky table scrolling, lot detail panel, autocomplete filtering, pagination, auto-refresh
|
||||
|
||||
## 5. Vite Configuration & Build
|
||||
|
||||
- [x] 5.1 Update `vite.config.js`: change 3 entries from `main.js` to `index.html`, add `vendor-vue` chunk splitting rule, ensure `vendor-echarts` chunk includes Overview usage
|
||||
- [x] 5.2 Update `package.json` build script to copy the 3 new HTML files to `static/dist/`
|
||||
- [x] 5.3 Run `npm run build` and verify output includes `wip-overview.html/js/css`, `wip-detail.html/js/css`, `hold-detail.html/js/css` with correct chunk splitting
|
||||
|
||||
## 6. Integration & Cleanup
|
||||
|
||||
- [x] 6.1 Verify drill-down navigation: Overview → Detail (workcenter + filters URL params), Overview → Hold Detail (Pareto chart/table reason links)
|
||||
- [x] 6.2 Verify back navigation: Detail → Overview, Hold Detail → Overview
|
||||
- [x] 6.3 Verify portal iframe embedding works for all three pages
|
||||
- [x] 6.4 Remove unused imports of `escapeHtml`/`safeText` from `core/table-tree.js` in hold-detail (Vue handles escaping)
|
||||
- [x] 6.5 Verify all three pages render correctly at responsive breakpoints (1400px, 1000px, 768px)
|
||||
Reference in New Issue
Block a user