From be225714212ef401b3557d53a915e8f20cea6d2c Mon Sep 17 00:00:00 2001 From: egg Date: Wed, 11 Feb 2026 07:22:48 +0800 Subject: [PATCH] feat(wip): preserve filters between Overview and Detail with thundering-herd fix URL is now single source of truth for filter state (workorder, lotid, package, type, status) across WIP Overview and Detail pages. Drill-down carries all filters + status; back button dynamically reflects Detail changes. Backend Detail API now supports pj_type filter parameter. Harden concurrency: add pagehide abort for MPA navigation, double-check locking on Redis JSON parse and snapshot build to prevent thread pool saturation during rapid page switching. Fix watchdog setsid and PID discovery. Fix test_realtime_equipment_cache RUNCARDLOTID field mismatch. Co-Authored-By: Claude Opus 4.6 --- frontend/src/wip-detail/App.vue | 29 ++- frontend/src/wip-overview/App.vue | 48 +++- .../wip-shared/composables/useAutoRefresh.js | 11 + .../.openspec.yaml | 2 + .../design.md | 56 +++++ .../proposal.md | 30 +++ .../specs/wip-detail-page/spec.md | 63 ++++++ .../specs/wip-overview-page/spec.md | 79 +++++++ .../tasks.md | 28 +++ openspec/specs/wip-detail-page/spec.md | 29 ++- openspec/specs/wip-overview-page/spec.md | 29 ++- scripts/start_server.sh | 17 +- src/mes_dashboard/core/cache.py | 46 ++-- src/mes_dashboard/routes/wip_routes.py | 3 + src/mes_dashboard/services/wip_service.py | 26 ++- tests/e2e/test_wip_hold_pages_e2e.py | 168 ++++++++++++++ tests/stress/test_api_load.py | 207 +++++++++++++++-- tests/stress/test_frontend_stress.py | 128 ++++++++++- tests/test_cache.py | 68 +++++- tests/test_hold_routes.py | 114 +++++++--- tests/test_realtime_equipment_cache.py | 166 +++++++------- tests/test_wip_hold_pages_integration.py | 152 +++++++++++++ tests/test_wip_routes.py | 208 ++++++++++++------ tests/test_wip_service.py | 81 ++++++- 24 files changed, 1522 insertions(+), 266 deletions(-) create mode 100644 openspec/changes/archive/2026-02-10-wip-filter-persistence/.openspec.yaml create mode 100644 openspec/changes/archive/2026-02-10-wip-filter-persistence/design.md create mode 100644 openspec/changes/archive/2026-02-10-wip-filter-persistence/proposal.md create mode 100644 openspec/changes/archive/2026-02-10-wip-filter-persistence/specs/wip-detail-page/spec.md create mode 100644 openspec/changes/archive/2026-02-10-wip-filter-persistence/specs/wip-overview-page/spec.md create mode 100644 openspec/changes/archive/2026-02-10-wip-filter-persistence/tasks.md create mode 100644 tests/e2e/test_wip_hold_pages_e2e.py create mode 100644 tests/test_wip_hold_pages_integration.py diff --git a/frontend/src/wip-detail/App.vue b/frontend/src/wip-detail/App.vue index dfbc901..776d558 100644 --- a/frontend/src/wip-detail/App.vue +++ b/frontend/src/wip-detail/App.vue @@ -69,6 +69,9 @@ function updateUrlState() { if (filters.type) { params.set('type', filters.type); } + if (activeStatusFilter.value) { + params.set('status', activeStatusFilter.value); + } window.history.replaceState({}, '', `/wip-detail?${params.toString()}`); } @@ -183,6 +186,28 @@ const tableData = computed(() => ({ specs: detailData.value?.specs || [], pagination: detailData.value?.pagination || { page: 1, page_size: PAGE_SIZE, total_count: 0, total_pages: 1 }, })); +const backUrl = computed(() => { + const params = new URLSearchParams(); + + if (filters.workorder) { + params.set('workorder', filters.workorder); + } + if (filters.lotid) { + params.set('lotid', filters.lotid); + } + if (filters.package) { + params.set('package', filters.package); + } + if (filters.type) { + params.set('type', filters.type); + } + if (activeStatusFilter.value) { + params.set('status', activeStatusFilter.value); + } + + const query = params.toString(); + return query ? `/wip-overview?${query}` : '/wip-overview'; +}); function updateFilters(nextFilters) { filters.workorder = nextFilters.workorder || ''; @@ -210,6 +235,7 @@ function toggleStatusFilter(status) { activeStatusFilter.value = activeStatusFilter.value === status ? null : status; page.value = 1; selectedLotId.value = ''; + updateUrlState(); void loadTableOnly(); } @@ -251,6 +277,7 @@ async function initializePage() { filters.lotid = getUrlParam('lotid'); filters.package = getUrlParam('package'); filters.type = getUrlParam('type'); + activeStatusFilter.value = getUrlParam('status') || null; if (!workcenter.value) { const signal = createAbortSignal('wip-detail-init'); @@ -284,7 +311,7 @@ void initializePage();
- ← Overview + ← Overview

{{ pageTitle }}

diff --git a/frontend/src/wip-overview/App.vue b/frontend/src/wip-overview/App.vue index 57de296..d80104b 100644 --- a/frontend/src/wip-overview/App.vue +++ b/frontend/src/wip-overview/App.vue @@ -46,6 +46,10 @@ function unwrapApiResult(result, fallbackMessage) { return result; } +function getUrlParam(name) { + return new URLSearchParams(window.location.search).get(name)?.trim() || ''; +} + function buildFilters(status = null) { return buildWipOverviewQueryParams(filters, status); } @@ -165,6 +169,7 @@ async function loadMatrixOnly() { function toggleStatusFilter(status) { activeStatusFilter.value = activeStatusFilter.value === status ? null : status; + updateUrlState(); void loadMatrixOnly(); } @@ -175,18 +180,46 @@ function updateFilters(nextFilters) { filters.type = nextFilters.type || ''; } +function updateUrlState() { + const params = new URLSearchParams(); + + if (filters.workorder) { + params.set('workorder', filters.workorder); + } + if (filters.lotid) { + params.set('lotid', filters.lotid); + } + if (filters.package) { + params.set('package', filters.package); + } + if (filters.type) { + params.set('type', filters.type); + } + if (activeStatusFilter.value) { + params.set('status', activeStatusFilter.value); + } + + const query = params.toString(); + const nextUrl = query ? `/wip-overview?${query}` : '/wip-overview'; + window.history.replaceState({}, '', nextUrl); +} + function applyFilters(nextFilters) { updateFilters(nextFilters); + updateUrlState(); void loadAllData(false); } function clearFilters() { updateFilters({ workorder: '', lotid: '', package: '', type: '' }); + activeStatusFilter.value = null; + updateUrlState(); void loadAllData(false); } function removeFilter(field) { filters[field] = ''; + updateUrlState(); void loadAllData(false); } @@ -206,6 +239,9 @@ function navigateToDetail(workcenter) { if (filters.type) { params.append('type', filters.type); } + if (activeStatusFilter.value) { + params.append('status', activeStatusFilter.value); + } window.location.href = `/wip-detail?${params.toString()}`; } @@ -221,7 +257,17 @@ async function manualRefresh() { await triggerRefresh({ resetTimer: true, force: true }); } -void loadAllData(true); +async function initializePage() { + filters.workorder = getUrlParam('workorder'); + filters.lotid = getUrlParam('lotid'); + filters.package = getUrlParam('package'); + filters.type = getUrlParam('type'); + activeStatusFilter.value = getUrlParam('status') || null; + + await loadAllData(true); +} + +void initializePage();