fix: show monthly stats by calendar year
This commit is contained in:
parent
52c6563ac7
commit
180ba7e17e
|
|
@ -176,11 +176,13 @@ const totals = computed(() => summarize(entries.value));
|
||||||
const convertedEntries = computed(() => entries.value.filter((entry) => entry.baseAmount !== null));
|
const convertedEntries = computed(() => entries.value.filter((entry) => entry.baseAmount !== null));
|
||||||
const pendingConversionCount = computed(() => entries.value.length - convertedEntries.value.length);
|
const pendingConversionCount = computed(() => entries.value.length - convertedEntries.value.length);
|
||||||
const calendarCursorMonth = ref(selectedMonth.value);
|
const calendarCursorMonth = ref(selectedMonth.value);
|
||||||
|
const calendarCursorYear = ref(selectedYear.value);
|
||||||
|
|
||||||
watch([rangeMode, selectedMonth, selectedYear, customStart, customEnd], () => {
|
watch([rangeMode, selectedMonth, selectedYear, customStart, customEnd], () => {
|
||||||
const bounds = rangeBounds.value;
|
const bounds = rangeBounds.value;
|
||||||
if (!bounds) return;
|
if (!bounds) return;
|
||||||
calendarCursorMonth.value = `${bounds.start.getFullYear()}-${String(bounds.start.getMonth() + 1).padStart(2, "0")}`;
|
calendarCursorMonth.value = `${bounds.start.getFullYear()}-${String(bounds.start.getMonth() + 1).padStart(2, "0")}`;
|
||||||
|
calendarCursorYear.value = bounds.start.getFullYear();
|
||||||
});
|
});
|
||||||
|
|
||||||
const dailyStats = computed(() => {
|
const dailyStats = computed(() => {
|
||||||
|
|
@ -243,17 +245,14 @@ const calendarDays = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const calendarMonthCells = computed(() => {
|
const calendarMonthCells = computed(() => {
|
||||||
const bounds = rangeBounds.value;
|
if (!rangeBounds.value) return [];
|
||||||
if (!bounds) return [];
|
|
||||||
const summary = new Map(flowStats.value.map((item) => [item.date, item]));
|
const summary = new Map(flowStats.value.map((item) => [item.date, item]));
|
||||||
const cells: Array<{ date: string; label: string; income: number; expense: number }> = [];
|
const cells: Array<{ date: string; label: string; income: number; expense: number }> = [];
|
||||||
const cursor = new Date(bounds.start.getFullYear(), bounds.start.getMonth(), 1);
|
for (let month = 0; month < 12; month += 1) {
|
||||||
const end = new Date(bounds.end.getFullYear(), bounds.end.getMonth(), 1);
|
const cursor = new Date(calendarCursorYear.value, month, 1);
|
||||||
while (cursor < end) {
|
|
||||||
const date = `${cursor.getFullYear()}-${String(cursor.getMonth() + 1).padStart(2, "0")}`;
|
const date = `${cursor.getFullYear()}-${String(cursor.getMonth() + 1).padStart(2, "0")}`;
|
||||||
const item = summary.get(date);
|
const item = summary.get(date);
|
||||||
cells.push({ date, label: `${cursor.getFullYear()}年${cursor.getMonth() + 1}月`, income: item?.income ?? 0, expense: item?.expense ?? 0 });
|
cells.push({ date, label: `${cursor.getFullYear()}年${cursor.getMonth() + 1}月`, income: item?.income ?? 0, expense: item?.expense ?? 0 });
|
||||||
cursor.setMonth(cursor.getMonth() + 1);
|
|
||||||
}
|
}
|
||||||
return cells;
|
return cells;
|
||||||
});
|
});
|
||||||
|
|
@ -517,6 +516,16 @@ function shiftCalendarMonth(offset: -1 | 1) {
|
||||||
if (next < first || next >= last) return;
|
if (next < first || next >= last) return;
|
||||||
calendarCursorMonth.value = `${next.getFullYear()}-${String(next.getMonth() + 1).padStart(2, "0")}`;
|
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;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -614,8 +623,16 @@ function shiftCalendarMonth(offset: -1 | 1) {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template v-else-if="flowGranularity === 'month'">
|
||||||
|
<div class="calendar-month-nav"><button type="button" aria-label="上一年" title="上一年" :disabled="calendarCursorYear <= (rangeBounds?.start.getFullYear() ?? calendarCursorYear)" @click="shiftCalendarYear(-1)"><ChevronLeft :size="17" /></button><strong>{{ calendarCursorYear }}年</strong><button type="button" aria-label="下一年" title="下一年" :disabled="calendarCursorYear >= (rangeBounds ? new Date(rangeBounds.end.getTime() - 1).getFullYear() : calendarCursorYear)" @click="shiftCalendarYear(1)"><ChevronRight :size="17" /></button></div>
|
||||||
|
<div class="period-calendar-grid">
|
||||||
|
<span v-for="cell in calendarMonthCells" :key="cell.date" class="period-calendar-cell" :class="{ muted: !cell.income && !cell.expense }">
|
||||||
|
<b>{{ cell.label.replace(`${calendarCursorYear}年`, '') }}</b><i class="income" :style="{ width: `${Math.min(100, (cell.income / lineMaxAmount) * 100)}%` }"></i><i class="expense" :style="{ width: `${Math.min(100, (cell.expense / lineMaxAmount) * 100)}%` }"></i><small v-if="cell.income || cell.expense">{{ compactDailyAmount(cell.income || cell.expense) }}</small>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<div v-else class="period-calendar-grid">
|
<div v-else class="period-calendar-grid">
|
||||||
<span v-for="cell in flowGranularity === 'month' ? calendarMonthCells : calendarYearCells" :key="cell.date" class="period-calendar-cell">
|
<span v-for="cell in calendarYearCells" :key="cell.date" class="period-calendar-cell" :class="{ muted: !cell.income && !cell.expense }">
|
||||||
<b>{{ cell.label }}</b><i class="income" :style="{ width: `${Math.min(100, (cell.income / lineMaxAmount) * 100)}%` }"></i><i class="expense" :style="{ width: `${Math.min(100, (cell.expense / lineMaxAmount) * 100)}%` }"></i><small v-if="cell.income || cell.expense">{{ compactDailyAmount(cell.income || cell.expense) }}</small>
|
<b>{{ cell.label }}</b><i class="income" :style="{ width: `${Math.min(100, (cell.income / lineMaxAmount) * 100)}%` }"></i><i class="expense" :style="{ width: `${Math.min(100, (cell.expense / lineMaxAmount) * 100)}%` }"></i><small v-if="cell.income || cell.expense">{{ compactDailyAmount(cell.income || cell.expense) }}</small>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -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-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 { 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 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 { width:0; max-width:80%; height:4px; display:block; border-radius:3px; }
|
||||||
.period-calendar-cell i.income { background:#20a57d; }
|
.period-calendar-cell i.income { background:#20a57d; }
|
||||||
.period-calendar-cell i.expense { background:#ef6a4d; }
|
.period-calendar-cell i.expense { background:#ef6a4d; }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue