Improve ledger monthly header

This commit is contained in:
openclaw 2026-07-23 00:17:49 +08:00
parent 7747323588
commit 8627d8404a
2 changed files with 102 additions and 47 deletions

View File

@ -73,7 +73,7 @@ input:focus-visible {
.ledger-header {
position: relative;
z-index: 1;
min-height: 210px;
min-height: 180px;
padding: max(18px, env(safe-area-inset-top)) 18px 20px;
background: #087f72;
color: #ffffff;
@ -211,8 +211,7 @@ input:focus-visible {
background: #edf7f4;
}
.ledger-switcher,
.month-switcher {
.ledger-switcher {
display: inline-flex;
align-items: center;
justify-content: center;
@ -228,19 +227,12 @@ input:focus-visible {
font-weight: 780;
}
.month-switcher {
margin-top: 22px;
padding: 0;
color: rgba(255, 255, 255, 0.82);
font-size: 13px;
}
.monthly-summary {
display: grid;
grid-template-columns: 1.35fr 1fr 1fr;
align-items: end;
gap: 12px;
margin-top: 9px;
grid-template-columns: minmax(0, 1fr) minmax(132px, .72fr);
align-items: center;
gap: 18px;
margin-top: 22px;
}
.summary-main,
@ -255,11 +247,14 @@ input:focus-visible {
}
.monthly-summary span {
margin-bottom: 3px;
color: rgba(255, 255, 255, 0.72);
font-size: 12px;
}
.summary-main span {
margin-bottom: 4px;
}
.summary-main strong {
overflow: hidden;
font-size: 23px;
@ -267,17 +262,38 @@ input:focus-visible {
text-overflow: ellipsis;
}
.summary-stat {
.summary-main strong.compact {
font-size: 17px;
}
.summary-secondary {
min-width: 0;
border-left: 1px solid rgba(255, 255, 255, 0.22);
padding-left: 12px;
}
.summary-stat {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 6px;
}
.summary-stat + .summary-stat {
margin-top: 8px;
}
.summary-stat strong {
overflow: hidden;
font-size: 15px;
font-size: 14px;
text-align: right;
text-overflow: ellipsis;
}
.summary-stat strong.compact {
font-size: 10px;
}
.income-stat strong {
color: #d9ffe7;
}
@ -287,8 +303,8 @@ input:focus-visible {
}
.ledger-content {
height: calc(100vh - 210px);
height: calc(100dvh - 210px);
height: calc(100vh - 180px);
height: calc(100dvh - 180px);
min-height: 0;
overflow-y: auto;
padding: 0 16px 112px;
@ -346,10 +362,29 @@ input:focus-visible {
.list-toolbar-heading {
display: flex;
align-items: baseline;
align-items: center;
gap: 8px;
}
.list-month-switcher {
min-height: 32px;
display: inline-flex;
align-items: center;
gap: 4px;
border: 0;
padding: 0;
background: transparent;
color: #26342f;
}
.native-month-input {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
}
.list-toolbar strong {
font-size: 15px;
}
@ -1368,14 +1403,15 @@ input:focus-visible {
}
.monthly-summary {
gap: 8px;
grid-template-columns: minmax(0, 1fr) minmax(126px, .72fr);
gap: 10px;
}
.summary-main strong {
font-size: 20px;
}
.summary-stat {
.summary-secondary {
padding-left: 8px;
}

View File

@ -35,15 +35,23 @@ const generatingInvitation = ref(false);
const visibleCount = ref(ENTRY_PAGE_SIZE);
const ledgerContent = ref<HTMLElement | null>(null);
const loadMoreTrigger = ref<HTMLElement | null>(null);
const monthInput = ref<HTMLInputElement | null>(null);
const selectedMonth = ref(toMonthValue(new Date()));
let loadMoreObserver: IntersectionObserver | null = null;
const selectedMonthParts = computed(() => {
const match = /^(\d{4})-(\d{2})$/.exec(selectedMonth.value);
if (!match) return null;
return { year: Number(match[1]), month: Number(match[2]) - 1 };
});
const monthlyEntries = computed(() => {
const current = new Date();
const selected = selectedMonthParts.value;
if (!selected) return [];
return store.entries.filter((entry) => {
const occurredAt = new Date(entry.occurredAt);
return entry.ledgerIds.includes(ledgerStore.currentLedgerId)
&& occurredAt.getFullYear() === current.getFullYear()
&& occurredAt.getMonth() === current.getMonth();
&& occurredAt.getFullYear() === selected.year
&& occurredAt.getMonth() === selected.month;
});
});
@ -57,6 +65,9 @@ const totals = computed(() => {
{ income: 0, expense: 0 },
);
});
const balanceText = computed(() => formatMoney(totals.value.income - totals.value.expense));
const incomeText = computed(() => formatMoney(totals.value.income));
const expenseText = computed(() => formatMoney(totals.value.expense));
const visibleMonthlyEntries = computed(() => monthlyEntries.value.slice(0, visibleCount.value));
const hasMoreEntries = computed(() => visibleCount.value < monthlyEntries.value.length);
@ -91,12 +102,8 @@ const groupedEntries = computed(() => {
});
const monthTitle = computed(() => {
const date = new Date();
return `${date.getFullYear()}${date.getMonth() + 1}`;
});
const monthListTitle = computed(() => {
const date = new Date();
return `${date.getFullYear()}${date.getMonth() + 1}月流水`;
const selected = selectedMonthParts.value;
return selected ? `${selected.year}${selected.month + 1}` : "选择月份";
});
onMounted(async () => {
@ -129,6 +136,17 @@ function localDateKey(value: string) {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
}
function toMonthValue(date: Date) {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
}
function openMonthPicker() {
const input = monthInput.value;
if (!input) return;
if (typeof input.showPicker === "function") input.showPicker();
else input.click();
}
function dateHeading(value: string) {
const date = new Date(`${value}T00:00:00`);
const today = new Date();
@ -261,23 +279,20 @@ async function shareLedger() {
</div>
</Transition>
<button class="month-switcher" type="button">
{{ monthTitle }}
<ChevronDown :size="14" />
</button>
<section class="monthly-summary" aria-label="本月收支">
<section class="monthly-summary" aria-label="月度收支">
<div class="summary-main">
<span>本月结余</span>
<strong>¥ {{ formatMoney(totals.income - totals.expense) }}</strong>
<span>结余</span>
<strong :class="{ compact: balanceText.length > 12 }">¥ {{ balanceText }}</strong>
</div>
<div class="summary-stat income-stat">
<span>收入</span>
<strong>{{ formatMoney(totals.income) }}</strong>
</div>
<div class="summary-stat expense-stat">
<span>支出</span>
<strong>{{ formatMoney(totals.expense) }}</strong>
<div class="summary-secondary">
<div class="summary-stat income-stat">
<span>收入</span>
<strong :class="{ compact: incomeText.length > 10 }">{{ incomeText }}</strong>
</div>
<div class="summary-stat expense-stat">
<span>支出</span>
<strong :class="{ compact: expenseText.length > 10 }">{{ expenseText }}</strong>
</div>
</div>
</section>
</header>
@ -292,7 +307,11 @@ async function shareLedger() {
<div class="list-toolbar">
<div class="list-toolbar-copy">
<div class="list-toolbar-heading">
<strong>{{ monthListTitle }}</strong>
<button class="list-month-switcher" type="button" @click="openMonthPicker">
<strong>{{ monthTitle }}</strong>
<ChevronDown :size="14" />
</button>
<input ref="monthInput" v-model="selectedMonth" class="native-month-input" type="month" aria-label="选择流水月份" tabindex="-1" />
<span>{{ monthlyEntries.length }} </span>
</div>
<div class="list-toolbar-totals">