Improve ledger monthly header
This commit is contained in:
parent
7747323588
commit
8627d8404a
|
|
@ -73,7 +73,7 @@ input:focus-visible {
|
||||||
.ledger-header {
|
.ledger-header {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
min-height: 210px;
|
min-height: 180px;
|
||||||
padding: max(18px, env(safe-area-inset-top)) 18px 20px;
|
padding: max(18px, env(safe-area-inset-top)) 18px 20px;
|
||||||
background: #087f72;
|
background: #087f72;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
|
|
@ -211,8 +211,7 @@ input:focus-visible {
|
||||||
background: #edf7f4;
|
background: #edf7f4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ledger-switcher,
|
.ledger-switcher {
|
||||||
.month-switcher {
|
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
@ -228,19 +227,12 @@ input:focus-visible {
|
||||||
font-weight: 780;
|
font-weight: 780;
|
||||||
}
|
}
|
||||||
|
|
||||||
.month-switcher {
|
|
||||||
margin-top: 22px;
|
|
||||||
padding: 0;
|
|
||||||
color: rgba(255, 255, 255, 0.82);
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monthly-summary {
|
.monthly-summary {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1.35fr 1fr 1fr;
|
grid-template-columns: minmax(0, 1fr) minmax(132px, .72fr);
|
||||||
align-items: end;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 18px;
|
||||||
margin-top: 9px;
|
margin-top: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-main,
|
.summary-main,
|
||||||
|
|
@ -255,11 +247,14 @@ input:focus-visible {
|
||||||
}
|
}
|
||||||
|
|
||||||
.monthly-summary span {
|
.monthly-summary span {
|
||||||
margin-bottom: 3px;
|
|
||||||
color: rgba(255, 255, 255, 0.72);
|
color: rgba(255, 255, 255, 0.72);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.summary-main span {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.summary-main strong {
|
.summary-main strong {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 23px;
|
font-size: 23px;
|
||||||
|
|
@ -267,17 +262,38 @@ input:focus-visible {
|
||||||
text-overflow: ellipsis;
|
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);
|
border-left: 1px solid rgba(255, 255, 255, 0.22);
|
||||||
padding-left: 12px;
|
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 {
|
.summary-stat strong {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 15px;
|
font-size: 14px;
|
||||||
|
text-align: right;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.summary-stat strong.compact {
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.income-stat strong {
|
.income-stat strong {
|
||||||
color: #d9ffe7;
|
color: #d9ffe7;
|
||||||
}
|
}
|
||||||
|
|
@ -287,8 +303,8 @@ input:focus-visible {
|
||||||
}
|
}
|
||||||
|
|
||||||
.ledger-content {
|
.ledger-content {
|
||||||
height: calc(100vh - 210px);
|
height: calc(100vh - 180px);
|
||||||
height: calc(100dvh - 210px);
|
height: calc(100dvh - 180px);
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0 16px 112px;
|
padding: 0 16px 112px;
|
||||||
|
|
@ -346,10 +362,29 @@ input:focus-visible {
|
||||||
|
|
||||||
.list-toolbar-heading {
|
.list-toolbar-heading {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
gap: 8px;
|
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 {
|
.list-toolbar strong {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
@ -1368,14 +1403,15 @@ input:focus-visible {
|
||||||
}
|
}
|
||||||
|
|
||||||
.monthly-summary {
|
.monthly-summary {
|
||||||
gap: 8px;
|
grid-template-columns: minmax(0, 1fr) minmax(126px, .72fr);
|
||||||
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-main strong {
|
.summary-main strong {
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.summary-stat {
|
.summary-secondary {
|
||||||
padding-left: 8px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,15 +35,23 @@ const generatingInvitation = ref(false);
|
||||||
const visibleCount = ref(ENTRY_PAGE_SIZE);
|
const visibleCount = ref(ENTRY_PAGE_SIZE);
|
||||||
const ledgerContent = ref<HTMLElement | null>(null);
|
const ledgerContent = ref<HTMLElement | null>(null);
|
||||||
const loadMoreTrigger = 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;
|
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 monthlyEntries = computed(() => {
|
||||||
const current = new Date();
|
const selected = selectedMonthParts.value;
|
||||||
|
if (!selected) return [];
|
||||||
return store.entries.filter((entry) => {
|
return store.entries.filter((entry) => {
|
||||||
const occurredAt = new Date(entry.occurredAt);
|
const occurredAt = new Date(entry.occurredAt);
|
||||||
return entry.ledgerIds.includes(ledgerStore.currentLedgerId)
|
return entry.ledgerIds.includes(ledgerStore.currentLedgerId)
|
||||||
&& occurredAt.getFullYear() === current.getFullYear()
|
&& occurredAt.getFullYear() === selected.year
|
||||||
&& occurredAt.getMonth() === current.getMonth();
|
&& occurredAt.getMonth() === selected.month;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -57,6 +65,9 @@ const totals = computed(() => {
|
||||||
{ income: 0, expense: 0 },
|
{ 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 visibleMonthlyEntries = computed(() => monthlyEntries.value.slice(0, visibleCount.value));
|
||||||
const hasMoreEntries = computed(() => visibleCount.value < monthlyEntries.value.length);
|
const hasMoreEntries = computed(() => visibleCount.value < monthlyEntries.value.length);
|
||||||
|
|
@ -91,12 +102,8 @@ const groupedEntries = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const monthTitle = computed(() => {
|
const monthTitle = computed(() => {
|
||||||
const date = new Date();
|
const selected = selectedMonthParts.value;
|
||||||
return `${date.getFullYear()}年 ${date.getMonth() + 1}月`;
|
return selected ? `${selected.year}年${selected.month + 1}月` : "选择月份";
|
||||||
});
|
|
||||||
const monthListTitle = computed(() => {
|
|
||||||
const date = new Date();
|
|
||||||
return `${date.getFullYear()}年${date.getMonth() + 1}月流水`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(async () => {
|
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")}`;
|
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) {
|
function dateHeading(value: string) {
|
||||||
const date = new Date(`${value}T00:00:00`);
|
const date = new Date(`${value}T00:00:00`);
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
|
@ -261,23 +279,20 @@ async function shareLedger() {
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
|
||||||
<button class="month-switcher" type="button">
|
<section class="monthly-summary" aria-label="月度收支">
|
||||||
{{ monthTitle }}
|
|
||||||
<ChevronDown :size="14" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<section class="monthly-summary" aria-label="本月收支">
|
|
||||||
<div class="summary-main">
|
<div class="summary-main">
|
||||||
<span>本月结余</span>
|
<span>结余</span>
|
||||||
<strong>¥ {{ formatMoney(totals.income - totals.expense) }}</strong>
|
<strong :class="{ compact: balanceText.length > 12 }">¥ {{ balanceText }}</strong>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="summary-secondary">
|
||||||
<div class="summary-stat income-stat">
|
<div class="summary-stat income-stat">
|
||||||
<span>收入</span>
|
<span>收入</span>
|
||||||
<strong>{{ formatMoney(totals.income) }}</strong>
|
<strong :class="{ compact: incomeText.length > 10 }">{{ incomeText }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="summary-stat expense-stat">
|
<div class="summary-stat expense-stat">
|
||||||
<span>支出</span>
|
<span>支出</span>
|
||||||
<strong>{{ formatMoney(totals.expense) }}</strong>
|
<strong :class="{ compact: expenseText.length > 10 }">{{ expenseText }}</strong>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</header>
|
</header>
|
||||||
|
|
@ -292,7 +307,11 @@ async function shareLedger() {
|
||||||
<div class="list-toolbar">
|
<div class="list-toolbar">
|
||||||
<div class="list-toolbar-copy">
|
<div class="list-toolbar-copy">
|
||||||
<div class="list-toolbar-heading">
|
<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>
|
<span>{{ monthlyEntries.length }} 笔</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="list-toolbar-totals">
|
<div class="list-toolbar-totals">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue