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>
This commit is contained in:
egg
2026-02-09 13:06:59 +08:00
parent 9b1d2edc52
commit bf7285fb51
29 changed files with 2366 additions and 3020 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-09

View File

@@ -0,0 +1,180 @@
## 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_V``SPEC` 欄位匹配取得 `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
```json
{
"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
```js
// 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 外層處理。

View File

@@ -0,0 +1,32 @@
## Why
目前系統缺乏 QC-GATE 站點的即時 LOT 狀態監控。QC-GATE 是製程中的品質關卡LOT 在此站點的等待時間直接影響生產效率。需要一個視覺化報表即時呈現各 QC-GATE 站點的 LOT 分佈與等待時間,讓管理者快速識別瓶頸。
此頁面同時作為前端架構遷移的起點 — 第一個完全脫離 Jinja2 的純 Vue 3 + Vite 頁面,為後續頁面遷移建立模式。
## What Changes
- 新增 QC-GATE 即時狀態報表頁面(`/qc-gate`),使用 Vue 3 + ECharts 實作
- 新增後端 API endpoint`/api/qc-gate/summary`),從 WIP Redis cache 篩選 QC-GATE 相關 LOT
- 前端引入 Vue 3 和 ECharts npm 套件,建立純 Vite 頁面架構模式
- 頁面以 Vite HTML entry 方式建置,完全不使用 Jinja2 template
- 頁面註冊至「報表類」drawer狀態為 released
- 使用 `DW_MES_SPEC_WORKCENTER_V` 取得 QC-GATE 站點清單與排序
- 等待時間以 6 小時為基準分為四級:<6hr, 6-12hr, 12-24hr, >24hr
- 支援 10 分鐘自動刷新與 visibilitychange 即時刷新
## Capabilities
### New Capabilities
- `qc-gate-status-report`: QC-GATE 站點即時 LOT 狀態報表 — 包含 API 端點、Vue 3 前端頁面、圖表互動、清單篩選
- `vue-vite-page-architecture`: 純 Vue 3 + Vite 頁面架構模式 — 脫離 Jinja2 的前端建置模式、CSRF/auth 處理、與 portal iframe 整合
### Modified Capabilities
- `page-drawer-assignment`: 新增 qc-gate 頁面至報表類 drawer 的 page_status.json 配置
## Impact
- **前端**: 引入 `vue``echarts``vue-echarts` npm 依賴;修改 `vite.config.js` 加入 Vue plugin 和新 entry point
- **後端**: 新增 `qc_gate_routes.py` blueprint 和 `qc_gate_service.py` 服務;新增 Flask route serving 純靜態 HTML
- **配置**: `page_status.json` 新增 qc-gate 頁面定義
- **建置**: Vite config 需加入 `@vitejs/plugin-vue` 和 HTML entry

View File

@@ -0,0 +1,80 @@
## ADDED Requirements
### Requirement: System SHALL provide QC-GATE LOT status API
The system SHALL provide an API endpoint that returns real-time LOT status for all QC-GATE stations, with wait time classification.
#### Scenario: Retrieve QC-GATE summary
- **WHEN** user sends GET `/api/qc-gate/summary`
- **THEN** the system SHALL return all LOTs whose `SPECNAME` contains both "QC" and "GATE" (case-insensitive)
- **THEN** each LOT SHALL include `wait_hours` calculated as `(SYS_DATE - MOVEINTIMESTAMP)` in hours
- **THEN** each LOT SHALL be classified into a time bucket: `lt_6h`, `6h_12h`, `12h_24h`, or `gt_24h`
- **THEN** the response SHALL include per-station bucket counts and the full lot list
#### Scenario: QC-GATE data sourced from WIP cache
- **WHEN** the API is called
- **THEN** the system SHALL read from the existing WIP Redis cache (not direct Oracle query)
- **THEN** the response SHALL include `cache_time` indicating the WIP snapshot timestamp
#### Scenario: No QC-GATE lots in cache
- **WHEN** no LOTs match the QC-GATE SPECNAME pattern
- **THEN** the system SHALL return an empty `stations` array with `cache_time`
### Requirement: QC-GATE stations SHALL be ordered by spec sequence
The system SHALL order QC-GATE stations according to the manufacturing flow sequence from `DW_MES_SPEC_WORKCENTER_V`.
#### Scenario: Station ordering
- **WHEN** the API returns multiple QC-GATE stations
- **THEN** the stations SHALL be sorted by `SPEC_ORDER` from `DW_MES_SPEC_WORKCENTER_V` where `SPEC` matches the SPECNAME
#### Scenario: Station not found in spec dimension table
- **WHEN** a QC-GATE SPECNAME is not found in `DW_MES_SPEC_WORKCENTER_V`
- **THEN** the station SHALL appear at the end of the list with a high default sort order
### Requirement: QC-GATE report page SHALL display stacked bar chart
The page SHALL display a stacked bar chart showing LOT counts per QC-GATE station, grouped by wait time bucket.
#### Scenario: Bar chart rendering
- **WHEN** the page loads and data is available
- **THEN** the X-axis SHALL show QC-GATE station names
- **THEN** the Y-axis SHALL show LOT counts
- **THEN** each bar SHALL be stacked with four color-coded segments: <6hr (green), 6-12hr (yellow), 12-24hr (orange), >24hr (red)
#### Scenario: Empty state
- **WHEN** no QC-GATE LOTs exist
- **THEN** the chart area SHALL display a "目前無 QC-GATE LOT" message
### Requirement: QC-GATE report page SHALL display filterable LOT table
The page SHALL display a table listing individual LOTs, with click-to-filter interaction from the bar chart.
#### Scenario: Default table display
- **WHEN** the page loads
- **THEN** the table SHALL show all QC-GATE LOTs sorted by wait time descending
#### Scenario: Click bar chart to filter
- **WHEN** user clicks a specific segment of a bar (e.g., QC-GATE-DB's 6-12hr segment)
- **THEN** the table SHALL filter to show only LOTs matching that station AND time bucket
- **THEN** a filter indicator SHALL be visible showing the active filter
#### Scenario: Clear filter
- **WHEN** user clicks the active filter indicator or clicks the same bar segment again
- **THEN** the table SHALL return to showing all QC-GATE LOTs
### Requirement: QC-GATE report page SHALL auto-refresh
The page SHALL automatically refresh data at the same interval as the WIP cache update cycle.
#### Scenario: Auto-refresh while visible
- **WHEN** the page is visible and 10 minutes have elapsed since last refresh
- **THEN** the page SHALL fetch new data from the API without showing a full loading overlay
- **THEN** the chart and table SHALL update with new data
#### Scenario: Auto-refresh while hidden
- **WHEN** the page tab/iframe is hidden (document.hidden === true)
- **THEN** the auto-refresh SHALL be skipped
#### Scenario: Page becomes visible after being hidden
- **WHEN** the page becomes visible after being hidden
- **THEN** the page SHALL immediately refresh data
#### Scenario: Manual refresh
- **WHEN** user clicks the refresh button
- **THEN** the page SHALL fetch new data and reset the auto-refresh timer

View File

@@ -0,0 +1,41 @@
## ADDED Requirements
### Requirement: Pure Vite pages SHALL be served as static HTML
The system SHALL support serving Vite-built HTML pages directly via Flask without Jinja2 rendering.
#### Scenario: Serve pure Vite page
- **WHEN** user navigates to a pure Vite page route (e.g., `/qc-gate`)
- **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: Page works in portal iframe
- **WHEN** the pure Vite page is loaded inside the portal iframe
- **THEN** the page SHALL render correctly within the iframe context
- **THEN** CSP `frame-ancestors 'self'` SHALL allow the embedding
### 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
### 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`.
#### Scenario: API GET request from pure Vite page
- **WHEN** a pure Vite page makes a GET API call
- **THEN** the call SHALL use the `apiGet` function from `core/api.js`
- **THEN** the call SHALL work without `window.MesApi` being present

View File

@@ -0,0 +1,31 @@
## 1. Frontend Toolchain Setup
- [x] 1.1 Install npm dependencies: `vue`, `@vitejs/plugin-vue`, `echarts`, `vue-echarts`
- [x] 1.2 Update `vite.config.js`: add Vue plugin, add `qc-gate` HTML entry point, add `vendor-vue` manual chunk
## 2. Backend API
- [x] 2.1 Create `services/qc_gate_service.py`: read WIP cache, filter SPECNAME by QC/GATE pattern, compute wait_hours and bucket classification, sort stations by SPEC_ORDER from filter_cache
- [x] 2.2 Create `routes/qc_gate_routes.py`: blueprint with `GET /api/qc-gate/summary` endpoint
- [x] 2.3 Register blueprint in `routes/__init__.py` and add Flask route `GET /qc-gate` serving static HTML via `send_from_directory`
## 3. Vue Frontend Page
- [x] 3.1 Create `frontend/src/qc-gate/index.html`: standalone HTML entry with Vue app mount point
- [x] 3.2 Create `frontend/src/qc-gate/main.js`: Vue app bootstrap with createApp and mount
- [x] 3.3 Create `frontend/src/qc-gate/App.vue`: root layout with header (title, cache time, refresh button), chart area, and table area
- [x] 3.4 Create `frontend/src/qc-gate/composables/useQcGateData.js`: data fetching, 10min auto-refresh with visibilitychange, reactive state management
- [x] 3.5 Create `frontend/src/qc-gate/components/QcGateChart.vue`: ECharts stacked bar chart (x=station, y=count, stacked by 4 time buckets with color coding)
- [x] 3.6 Create `frontend/src/qc-gate/components/LotTable.vue`: sortable lot table with click-to-filter from chart, filter indicator, clear filter
- [x] 3.7 Create `frontend/src/qc-gate/style.css`: page styling consistent with existing dashboard aesthetic
## 4. Page Registration & Integration
- [x] 4.1 Register qc-gate page in `page_status.json`: route `/qc-gate`, name `QC-GATE 狀態`, drawer_id `reports`, status `released`
- [x] 4.2 Build frontend (`npm run build`) and verify output files exist in `static/dist/`
## 5. Verification
- [x] 5.1 Verify API endpoint returns correct data structure with QC-GATE filtered lots, wait time buckets, and station ordering
- [x] 5.2 Verify page renders in portal iframe: chart displays, table populates, click-to-filter works, auto-refresh fires
- [x] 5.3 Verify existing pages still build and function correctly (Vue plugin does not break vanilla JS entries)

View File

@@ -0,0 +1,84 @@
## Purpose
Define stable requirements for qc-gate-status-report.
## Requirements
### Requirement: System SHALL provide QC-GATE LOT status API
The system SHALL provide an API endpoint that returns real-time LOT status for all QC-GATE stations, with wait time classification.
#### Scenario: Retrieve QC-GATE summary
- **WHEN** user sends GET `/api/qc-gate/summary`
- **THEN** the system SHALL return all LOTs whose `SPECNAME` contains both "QC" and "GATE" (case-insensitive)
- **THEN** each LOT SHALL include `wait_hours` calculated as `(SYS_DATE - MOVEINTIMESTAMP)` in hours
- **THEN** each LOT SHALL be classified into a time bucket: `lt_6h`, `6h_12h`, `12h_24h`, or `gt_24h`
- **THEN** the response SHALL include per-station bucket counts and the full lot list
#### Scenario: QC-GATE data sourced from WIP cache
- **WHEN** the API is called
- **THEN** the system SHALL read from the existing WIP Redis cache (not direct Oracle query)
- **THEN** the response SHALL include `cache_time` indicating the WIP snapshot timestamp
#### Scenario: No QC-GATE lots in cache
- **WHEN** no LOTs match the QC-GATE SPECNAME pattern
- **THEN** the system SHALL return an empty `stations` array with `cache_time`
### Requirement: QC-GATE stations SHALL be ordered by spec sequence
The system SHALL order QC-GATE stations according to the manufacturing flow sequence from `DW_MES_SPEC_WORKCENTER_V`.
#### Scenario: Station ordering
- **WHEN** the API returns multiple QC-GATE stations
- **THEN** the stations SHALL be sorted by `SPEC_ORDER` from `DW_MES_SPEC_WORKCENTER_V` where `SPEC` matches the SPECNAME
#### Scenario: Station not found in spec dimension table
- **WHEN** a QC-GATE SPECNAME is not found in `DW_MES_SPEC_WORKCENTER_V`
- **THEN** the station SHALL appear at the end of the list with a high default sort order
### Requirement: QC-GATE report page SHALL display stacked bar chart
The page SHALL display a stacked bar chart showing LOT counts per QC-GATE station, grouped by wait time bucket.
#### Scenario: Bar chart rendering
- **WHEN** the page loads and data is available
- **THEN** the X-axis SHALL show QC-GATE station names
- **THEN** the Y-axis SHALL show LOT counts
- **THEN** each bar SHALL be stacked with four color-coded segments: <6hr (green), 6-12hr (yellow), 12-24hr (orange), >24hr (red)
#### Scenario: Empty state
- **WHEN** no QC-GATE LOTs exist
- **THEN** the chart area SHALL display a "目前無 QC-GATE LOT" message
### Requirement: QC-GATE report page SHALL display filterable LOT table
The page SHALL display a table listing individual LOTs, with click-to-filter interaction from the bar chart.
#### Scenario: Default table display
- **WHEN** the page loads
- **THEN** the table SHALL show all QC-GATE LOTs sorted by wait time descending
#### Scenario: Click bar chart to filter
- **WHEN** user clicks a specific segment of a bar (e.g., QC-GATE-DB's 6-12hr segment)
- **THEN** the table SHALL filter to show only LOTs matching that station AND time bucket
- **THEN** a filter indicator SHALL be visible showing the active filter
#### Scenario: Clear filter
- **WHEN** user clicks the active filter indicator or clicks the same bar segment again
- **THEN** the table SHALL return to showing all QC-GATE LOTs
### Requirement: QC-GATE report page SHALL auto-refresh
The page SHALL automatically refresh data at the same interval as the WIP cache update cycle.
#### Scenario: Auto-refresh while visible
- **WHEN** the page is visible and 10 minutes have elapsed since last refresh
- **THEN** the page SHALL fetch new data from the API without showing a full loading overlay
- **THEN** the chart and table SHALL update with new data
#### Scenario: Auto-refresh while hidden
- **WHEN** the page tab/iframe is hidden (document.hidden === true)
- **THEN** the auto-refresh SHALL be skipped
#### Scenario: Page becomes visible after being hidden
- **WHEN** the page becomes visible after being hidden
- **THEN** the page SHALL immediately refresh data
#### Scenario: Manual refresh
- **WHEN** user clicks the refresh button
- **THEN** the page SHALL fetch new data and reset the auto-refresh timer

View File

@@ -0,0 +1,45 @@
## Purpose
Define stable requirements for vue-vite-page-architecture.
## Requirements
### Requirement: Pure Vite pages SHALL be served as static HTML
The system SHALL support serving Vite-built HTML pages directly via Flask without Jinja2 rendering.
#### Scenario: Serve pure Vite page
- **WHEN** user navigates to a pure Vite page route (e.g., `/qc-gate`)
- **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: Page works in portal iframe
- **WHEN** the pure Vite page is loaded inside the portal iframe
- **THEN** the page SHALL render correctly within the iframe context
- **THEN** CSP `frame-ancestors 'self'` SHALL allow the embedding
### 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
### 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`.
#### Scenario: API GET request from pure Vite page
- **WHEN** a pure Vite page makes a GET API call
- **THEN** the call SHALL use the `apiGet` function from `core/api.js`
- **THEN** the call SHALL work without `window.MesApi` being present