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:
egg
2026-02-09 16:39:20 +08:00
parent dcbf6dcf1f
commit a2653b8139
53 changed files with 5397 additions and 6646 deletions

View File

@@ -0,0 +1,119 @@
## Purpose
Define stable requirements for hold-detail-page.
## 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

View File

@@ -38,9 +38,14 @@ The Vite build configuration SHALL support Vue Single File Components alongside
#### 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/tables/main.js``src/tables/index.html`)
- **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
### Requirement: Pure Vite pages SHALL handle API calls without legacy MesApi
Pure Vite pages SHALL use the existing `frontend/src/core/api.js` module for API communication without depending on the global `window.MesApi` object from `_base.html`.
@@ -62,3 +67,18 @@ Pure Vite pages SHALL use the `apiPost` function from `core/api.js` for POST req
- **WHEN** a pure Vite page calls `apiPost`
- **THEN** `apiPost` SHALL attempt to read CSRF token from `<meta name="csrf-token">`
- **THEN** if no meta tag exists, the request SHALL still proceed (non-admin APIs do not enforce CSRF)
### 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`

View File

@@ -0,0 +1,124 @@
## Purpose
Define stable requirements for wip-detail-page.
## 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

View File

@@ -0,0 +1,111 @@
## Purpose
Define stable requirements for wip-overview-page.
## 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