diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index 39f8399..1cee931 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -327,7 +327,20 @@ input:focus-visible { box-shadow: 0 2px 6px rgba(32, 57, 51, 0.04); } -.list-toolbar > .list-toolbar-copy { +.list-toolbar-content { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + transform: translateX(var(--month-swipe-offset, 0px)); + transition: transform 220ms cubic-bezier(.2,.8,.2,1); +} + +.list-toolbar-content.dragging { transition: none; } +.month-swipe-hint { position:absolute; top:50%; left:50%; display:flex; align-items:center; gap:3px; transform:translate(-50%,-50%); border:1px solid #cfe0db; border-radius:999px; padding:4px 8px; background:rgba(255,255,255,.96); color:#087f72; box-shadow:0 4px 12px rgba(26,60,52,.12); font-size:11px; font-weight:700; pointer-events:none; } +.month-swipe-hint.next { color:#3478e5; } + +.list-toolbar-copy { min-width: 0; display: grid; gap: 3px; diff --git a/apps/web/src/views/LedgerView.vue b/apps/web/src/views/LedgerView.vue index 135604c..ef77b7d 100644 --- a/apps/web/src/views/LedgerView.vue +++ b/apps/web/src/views/LedgerView.vue @@ -2,7 +2,9 @@ import { formatCurrencyAmount, type LedgerEntry } from "@cents/domain"; import { BookOpen, + ChevronLeft, ChevronDown, + ChevronRight, CircleUserRound, CloudOff, Copy, @@ -40,6 +42,8 @@ const loadMoreTrigger = ref(null); const monthInput = ref(null); const selectedMonth = ref(toMonthValue(new Date())); const monthSwipeStart = ref<{ x: number; y: number } | null>(null); +const monthSwipeDelta = ref(0); +const monthSwipeDragging = ref(false); let loadMoreObserver: IntersectionObserver | null = null; const selectedMonthParts = computed(() => { @@ -158,7 +162,26 @@ function openMonthPicker() { function startMonthSwipe(event: TouchEvent) { const touch = event.touches[0]; - if (touch) monthSwipeStart.value = { x: touch.clientX, y: touch.clientY }; + if (touch) { + monthSwipeStart.value = { x: touch.clientX, y: touch.clientY }; + monthSwipeDelta.value = 0; + monthSwipeDragging.value = false; + } +} + +function moveMonthSwipe(event: TouchEvent) { + const start = monthSwipeStart.value; + const touch = event.touches[0]; + if (!start || !touch) return; + const deltaX = touch.clientX - start.x; + const deltaY = touch.clientY - start.y; + if (Math.abs(deltaY) > Math.abs(deltaX) && Math.abs(deltaY) > 8) { + monthSwipeDelta.value = 0; + monthSwipeDragging.value = false; + return; + } + monthSwipeDelta.value = Math.max(-96, Math.min(96, deltaX)); + monthSwipeDragging.value = Math.abs(deltaX) > 8; } function finishMonthSwipe(event: TouchEvent) { @@ -168,8 +191,20 @@ function finishMonthSwipe(event: TouchEvent) { if (!start || !touch) return; const deltaX = touch.clientX - start.x; const deltaY = touch.clientY - start.y; - if (Math.abs(deltaX) < 48 || Math.abs(deltaX) <= Math.abs(deltaY)) return; + if (Math.abs(deltaX) < 48 || Math.abs(deltaX) <= Math.abs(deltaY)) { + monthSwipeDelta.value = 0; + monthSwipeDragging.value = false; + return; + } shiftMonth(deltaX < 0 ? 1 : -1); + monthSwipeDelta.value = 0; + monthSwipeDragging.value = false; +} + +function cancelMonthSwipe() { + monthSwipeStart.value = null; + monthSwipeDelta.value = 0; + monthSwipeDragging.value = false; } function shiftMonth(offset: number) { @@ -178,6 +213,13 @@ function shiftMonth(offset: number) { selectedMonth.value = toMonthValue(new Date(selected.year, selected.month + offset, 1)); } +const monthSwipePreview = computed(() => { + const selected = selectedMonthParts.value; + if (!selected || !monthSwipeDelta.value) return ""; + const date = new Date(selected.year, selected.month + (monthSwipeDelta.value < 0 ? 1 : -1), 1); + return `${date.getFullYear()}年${date.getMonth() + 1}月`; +}); + function dateHeading(value: string) { const date = new Date(`${value}T00:00:00`); const today = new Date(); @@ -344,24 +386,31 @@ async function shareLedger() { {{ pendingConversionCount }} 条外币账目等待换算 -
-
-
- - - {{ monthlyEntries.length }} 笔 -
-
- 收入 {{ formatMoney(totals.income) }} - 支出 {{ formatMoney(totals.expense) }} +
+
+
+
+ + + {{ monthlyEntries.length }} 笔 +
+
+ 收入 {{ formatMoney(totals.income) }} + 支出 {{ formatMoney(totals.expense) }} +
+ +
+ -