diff --git a/apps/web/src/views/StatsView.vue b/apps/web/src/views/StatsView.vue index 5f8b87b..dbabaab 100644 --- a/apps/web/src/views/StatsView.vue +++ b/apps/web/src/views/StatsView.vue @@ -176,11 +176,13 @@ const totals = computed(() => summarize(entries.value)); const convertedEntries = computed(() => entries.value.filter((entry) => entry.baseAmount !== null)); const pendingConversionCount = computed(() => entries.value.length - convertedEntries.value.length); const calendarCursorMonth = ref(selectedMonth.value); +const calendarCursorYear = ref(selectedYear.value); watch([rangeMode, selectedMonth, selectedYear, customStart, customEnd], () => { const bounds = rangeBounds.value; if (!bounds) return; calendarCursorMonth.value = `${bounds.start.getFullYear()}-${String(bounds.start.getMonth() + 1).padStart(2, "0")}`; + calendarCursorYear.value = bounds.start.getFullYear(); }); const dailyStats = computed(() => { @@ -243,17 +245,14 @@ const calendarDays = computed(() => { }); const calendarMonthCells = computed(() => { - const bounds = rangeBounds.value; - if (!bounds) return []; + if (!rangeBounds.value) return []; const summary = new Map(flowStats.value.map((item) => [item.date, item])); const cells: Array<{ date: string; label: string; income: number; expense: number }> = []; - const cursor = new Date(bounds.start.getFullYear(), bounds.start.getMonth(), 1); - const end = new Date(bounds.end.getFullYear(), bounds.end.getMonth(), 1); - while (cursor < end) { + for (let month = 0; month < 12; month += 1) { + const cursor = new Date(calendarCursorYear.value, month, 1); const date = `${cursor.getFullYear()}-${String(cursor.getMonth() + 1).padStart(2, "0")}`; const item = summary.get(date); cells.push({ date, label: `${cursor.getFullYear()}年${cursor.getMonth() + 1}月`, income: item?.income ?? 0, expense: item?.expense ?? 0 }); - cursor.setMonth(cursor.getMonth() + 1); } return cells; }); @@ -517,6 +516,16 @@ function shiftCalendarMonth(offset: -1 | 1) { if (next < first || next >= last) return; calendarCursorMonth.value = `${next.getFullYear()}-${String(next.getMonth() + 1).padStart(2, "0")}`; } + +function shiftCalendarYear(offset: -1 | 1) { + const bounds = rangeBounds.value; + if (!bounds) return; + const firstYear = bounds.start.getFullYear(); + const lastYear = new Date(bounds.end.getTime() - 1).getFullYear(); + const nextYear = calendarCursorYear.value + offset; + if (nextYear < firstYear || nextYear > lastYear) return; + calendarCursorYear.value = nextYear; +} +
- + {{ cell.label }}{{ compactDailyAmount(cell.income || cell.expense) }}
@@ -735,6 +752,8 @@ function shiftCalendarMonth(offset: -1 | 1) { .period-calendar-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:7px; } .period-calendar-cell { min-width:0; min-height:66px; display:grid; align-content:center; justify-items:center; gap:5px; border:1px solid #e6eeec; border-radius:7px; padding:7px 5px; background:#fff; } .period-calendar-cell b { color:#536762; font-size:11px; font-weight:680; } +.period-calendar-cell.muted { background:#f8faf9; border-color:#edf2f0; } +.period-calendar-cell.muted b { color:#b0bbb7; } .period-calendar-cell i { width:0; max-width:80%; height:4px; display:block; border-radius:3px; } .period-calendar-cell i.income { background:#20a57d; } .period-calendar-cell i.expense { background:#ef6a4d; }