fix(resource-status): sort machine names, fix LOT click, support multi-select matrix filter

- Sort level-2 resource nodes alphabetically in status matrix hierarchy
- Fix LOT_COUNT using raw row count when no valid RUNCARDLOTID exists,
  causing LOT badge to render but click to silently fail
- Change matrix cell filter from single-select to multi-select (OR logic)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
egg
2026-02-10 18:25:20 +08:00
parent 9a4e08810b
commit d033ffeb26
3 changed files with 573 additions and 581 deletions

View File

@@ -40,7 +40,7 @@ const filterState = reactive({
isMonitor: false, isMonitor: false,
}); });
const matrixFilter = ref(null); const matrixFilter = ref([]);
const summaryStatusFilter = ref(null); const summaryStatusFilter = ref(null);
const hierarchyState = reactive({}); const hierarchyState = reactive({});
@@ -146,7 +146,7 @@ async function loadEquipment() {
const data = unwrapApiResult(result, '載入設備資料失敗'); const data = unwrapApiResult(result, '載入設備資料失敗');
allEquipment.value = Array.isArray(data) ? data : []; allEquipment.value = Array.isArray(data) ? data : [];
matrixFilter.value = null; matrixFilter.value = [];
summaryStatusFilter.value = null; summaryStatusFilter.value = null;
resetHierarchyState(); resetHierarchyState();
} }
@@ -185,11 +185,7 @@ async function checkCacheStatus() {
} }
} }
function buildMatrixFilterLabel(filter) { function buildSingleFilterLabel(filter) {
if (!filter) {
return '';
}
const parts = [filter.workcenter_group]; const parts = [filter.workcenter_group];
if (filter.family) { if (filter.family) {
parts.push(filter.family); parts.push(filter.family);
@@ -198,28 +194,15 @@ function buildMatrixFilterLabel(filter) {
const resource = allEquipment.value.find((item) => item.RESOURCEID === filter.resource); const resource = allEquipment.value.find((item) => item.RESOURCEID === filter.resource);
parts.push(resource?.RESOURCENAME || filter.resource); parts.push(resource?.RESOURCENAME || filter.resource);
} }
parts.push(STATUS_DISPLAY_MAP[filter.status] || filter.status); parts.push(STATUS_DISPLAY_MAP[filter.status] || filter.status);
return `矩陣篩選: ${parts.join(' / ')}`; return parts.join(' / ');
} }
function isSameMatrixFilter(left, right) { function filterKey(f) {
if (!left || !right) { return `${f.workcenter_group}|${f.status}|${f.family || ''}|${f.resource || ''}`;
return false;
}
return (
(left.workcenter_group || null) === (right.workcenter_group || null) &&
(left.status || null) === (right.status || null) &&
(left.family || null) === (right.family || null) &&
(left.resource || null) === (right.resource || null)
);
} }
function matchMatrixFilter(eq, filter) { function matchSingleFilter(eq, filter) {
if (!filter) {
return true;
}
if ((eq.WORKCENTER_GROUP || 'UNKNOWN') !== filter.workcenter_group) { if ((eq.WORKCENTER_GROUP || 'UNKNOWN') !== filter.workcenter_group) {
return false; return false;
} }
@@ -229,13 +212,13 @@ function matchMatrixFilter(eq, filter) {
if (filter.resource && (eq.RESOURCEID || null) !== filter.resource) { if (filter.resource && (eq.RESOURCEID || null) !== filter.resource) {
return false; return false;
} }
return normalizeStatus(eq.EQUIPMENTASSETSSTATUS) === filter.status; return normalizeStatus(eq.EQUIPMENTASSETSSTATUS) === filter.status;
} }
const displayedEquipment = computed(() => { const displayedEquipment = computed(() => {
const filters = matrixFilter.value;
return allEquipment.value.filter((eq) => { return allEquipment.value.filter((eq) => {
if (matrixFilter.value && !matchMatrixFilter(eq, matrixFilter.value)) { if (filters.length > 0 && !filters.some((f) => matchSingleFilter(eq, f))) {
return false; return false;
} }
if (summaryStatusFilter.value && normalizeStatus(eq.EQUIPMENTASSETSSTATUS) !== summaryStatusFilter.value) { if (summaryStatusFilter.value && normalizeStatus(eq.EQUIPMENTASSETSSTATUS) !== summaryStatusFilter.value) {
@@ -248,9 +231,9 @@ const displayedEquipment = computed(() => {
const activeFilterText = computed(() => { const activeFilterText = computed(() => {
const labels = []; const labels = [];
const matrixLabel = buildMatrixFilterLabel(matrixFilter.value); if (matrixFilter.value.length > 0) {
if (matrixLabel) { const parts = matrixFilter.value.map(buildSingleFilterLabel);
labels.push(matrixLabel); labels.push(`矩陣篩選: ${parts.join(' + ')}`);
} }
if (summaryStatusFilter.value) { if (summaryStatusFilter.value) {
@@ -261,20 +244,23 @@ const activeFilterText = computed(() => {
}); });
function applyMatrixFilter(nextFilter) { function applyMatrixFilter(nextFilter) {
if (isSameMatrixFilter(matrixFilter.value, nextFilter)) { const entry = {
matrixFilter.value = null;
return;
}
matrixFilter.value = {
workcenter_group: nextFilter.workcenter_group, workcenter_group: nextFilter.workcenter_group,
status: nextFilter.status, status: nextFilter.status,
family: nextFilter.family || null, family: nextFilter.family || null,
resource: nextFilter.resource || null, resource: nextFilter.resource || null,
}; };
const key = filterKey(entry);
const idx = matrixFilter.value.findIndex((f) => filterKey(f) === key);
if (idx >= 0) {
matrixFilter.value = matrixFilter.value.filter((_, i) => i !== idx);
} else {
matrixFilter.value = [...matrixFilter.value, entry];
}
} }
function clearAllEquipmentFilters() { function clearAllEquipmentFilters() {
matrixFilter.value = null; matrixFilter.value = [];
summaryStatusFilter.value = null; summaryStatusFilter.value = null;
} }

View File

@@ -19,8 +19,8 @@ const props = defineProps({
default: () => ({}), default: () => ({}),
}, },
matrixFilter: { matrixFilter: {
type: Object, type: Array,
default: null, default: () => [],
}, },
}); });
@@ -84,17 +84,19 @@ function calcOuPct(counts) {
return (Number(counts.PRD || 0) / denominator) * 100; return (Number(counts.PRD || 0) / denominator) * 100;
} }
function isMatrixFilterMatch(filter, { group, status, family = null, resource = null }) { function isMatrixFilterMatch(filters, { group, status, family = null, resource = null }) {
if (!filter) { if (!filters || filters.length === 0) {
return false; return false;
} }
const sameGroup = filter.workcenter_group === group; return filters.some((f) => {
const sameStatus = filter.status === status; return (
const sameFamily = (filter.family || null) === (family || null); f.workcenter_group === group &&
const sameResource = (filter.resource || null) === (resource || null); f.status === status &&
(f.family || null) === (family || null) &&
return sameGroup && sameStatus && sameFamily && sameResource; (f.resource || null) === (resource || null)
);
});
} }
function buildMatrixHierarchy(equipment) { function buildMatrixHierarchy(equipment) {
@@ -155,6 +157,10 @@ function buildMatrixHierarchy(equipment) {
}); });
groupNode.children.forEach((familyNode) => { groupNode.children.forEach((familyNode) => {
familyNode.children.sort((left, right) =>
String(left.name).localeCompare(String(right.name), 'zh-Hant')
);
familyNode.selectedColumns = Object.fromEntries( familyNode.selectedColumns = Object.fromEntries(
MATRIX_STATUS_COLUMNS.map((status) => [ MATRIX_STATUS_COLUMNS.map((status) => [
status, status,

File diff suppressed because it is too large Load Diff