diff --git a/frontend/src/composables/useKitCost.js b/frontend/src/composables/useKitCost.js index cd63ef0..adfc194 100644 --- a/frontend/src/composables/useKitCost.js +++ b/frontend/src/composables/useKitCost.js @@ -74,7 +74,7 @@ export function useKitCost() { .sort((a, b) => a.name.localeCompare(b.name, 'zh')) } - // Build full analysis for all kits + // Build full analysis for all kits, sorted by recipe count ascending (fewest recipes first) const kitAnalysis = computed(() => { return KITS.map(kit => { const perDrop = calcKitPerDrop(kit) @@ -85,12 +85,17 @@ export function useKitCost() { recipes, recipeCount: recipes.length, } - }) + }).sort((a, b) => a.recipeCount - b.recipeCount) }) - // Cross-kit comparison: all recipes that at least one kit can make + // Cross-kit comparison: membership-tier style + // Columns: kits ordered by recipe count (fewest→most, from kitAnalysis) + // Rows: recipes available to most kits at top, exclusive recipes at bottom (staircase pattern) const crossComparison = computed(() => { const analysis = kitAnalysis.value + // Kit order for staircase: index in sorted analysis (0 = smallest kit) + const kitOrder = analysis.map(ka => ka.id) + const allRecipeIds = new Set() for (const ka of analysis) { for (const r of ka.recipes) allRecipeIds.add(r._id) @@ -98,16 +103,21 @@ export function useKitCost() { const rows = [] for (const id of allRecipeIds) { - // Find recipe info from any kit that has it let recipe = null const costs = {} - for (const ka of analysis) { + let availCount = 0 + // Track which kit columns have this recipe (by index in sorted order) + let smallestKitIdx = kitOrder.length + for (let i = 0; i < analysis.length; i++) { + const ka = analysis[i] const found = ka.recipes.find(r => r._id === id) if (found) { if (!recipe) recipe = found costs[ka.id] = found.kitCost + availCount++ + if (i < smallestKitIdx) smallestKitIdx = i } else { - costs[ka.id] = null // kit can't make this recipe + costs[ka.id] = null } } rows.push({ @@ -116,14 +126,15 @@ export function useKitCost() { tags: recipe.tags, ingredients: recipe.ingredients, originalCost: recipe.originalCost, - costs, // { aroma: 12.3, family: null, home3988: 10.8, full: 9.5 } + costs, + availCount, + smallestKitIdx, }) } - // Sort by number of kits that can make the recipe (fewest first), then by name + // Staircase sort: most available first, then by smallest kit that has it, then by name rows.sort((a, b) => { - const aCount = Object.values(a.costs).filter(v => v != null).length - const bCount = Object.values(b.costs).filter(v => v != null).length - if (aCount !== bCount) return aCount - bCount + if (a.availCount !== b.availCount) return b.availCount - a.availCount + if (a.smallestKitIdx !== b.smallestKitIdx) return a.smallestKitIdx - b.smallestKitIdx return a.name.localeCompare(b.name, 'zh') }) return rows diff --git a/frontend/src/views/KitExport.vue b/frontend/src/views/KitExport.vue index 3536af5..5fda9d8 100644 --- a/frontend/src/views/KitExport.vue +++ b/frontend/src/views/KitExport.vue @@ -84,7 +84,7 @@ {{ row.name }} - + @@ -404,8 +404,9 @@ async function exportExcel(mode) { .td-cost.original { color: #999; font-weight: 400; } .td-profit { font-weight: 600; color: #4a9d7e; } .td-profit.negative { color: #ef5350; } -.td-kit-cost { font-weight: 500; } -.na { color: #ccc; } +.td-kit-available { font-weight: 500; color: #4a9d7e; background: #f0faf5; } +.td-kit-na { background: #fafafa; } +.na { color: #ddd; } .price-input-wrap { display: inline-flex; align-items: center; gap: 2px; font-size: 13px; color: #3e3a44;