From 488667bf9c4a53a781d5d6618cf7c0f5101c9700 Mon Sep 17 00:00:00 2001 From: SunCheng Date: Mon, 2 Feb 2026 19:42:35 +0800 Subject: [PATCH] fix --- Web/src/views/CalendarV2.vue | 545 +++++++++++++++++++++++++---------- 1 file changed, 391 insertions(+), 154 deletions(-) diff --git a/Web/src/views/CalendarV2.vue b/Web/src/views/CalendarV2.vue index 3496d8c..1f89212 100644 --- a/Web/src/views/CalendarV2.vue +++ b/Web/src/views/CalendarV2.vue @@ -1,6 +1,6 @@ @@ -214,6 +249,10 @@ const dailyStatsMap = ref({}) // 每日统计数据 Map: { '2026-01-15': { count const currentDate = ref(new Date()) const selectedDate = ref(new Date()) +// 动画方向和 key(用于触发过渡) +const slideDirection = ref('slide-left') +const calendarKey = ref(0) + // 当前月份格式化(中文) const currentMonth = computed(() => { const year = currentDate.value.getFullYear() @@ -305,6 +344,8 @@ const createDayObject = (date, isCurrentMonth) => { // 计算净支出(支出 - 收入) const netAmount = (dayStats.expense || 0) - (dayStats.income || 0) const hasData = dayStats.count > 0 + // 收入大于支出为盈利(绿色),否则为支出(红色) + const isProfitable = hasData && netAmount < 0 return { date: date.getTime(), @@ -314,7 +355,8 @@ const createDayObject = (date, isCurrentMonth) => { isSelected, hasData, amount: hasData ? Math.abs(netAmount).toFixed(0) : '', - isOverLimit: netAmount > (dailyBudget.value || 0) // 超过每日预算标红 + isOverLimit: netAmount > (dailyBudget.value || 0), // 超过每日预算标红 + isProfitable // 是否盈利(收入>支出) } } @@ -354,6 +396,7 @@ const fetchDailyStats = async (year, month) => { // 统计数据 const dailyBudget = ref(0) // 每日预算限额 const selectedDayExpense = ref(0) // 选中日期的支出 +const selectedDayIncome = ref(0) // 选中日期的收入 const transactionCount = computed(() => transactions.value.length) // 交易列表数据 @@ -395,10 +438,14 @@ const fetchDayTransactions = async (date) => { type: txn.type })) - // 计算当日支出 + // 计算当日支出和收入 selectedDayExpense.value = response.data .filter(t => t.type === 0) // 只统计支出 .reduce((sum, t) => sum + t.amount, 0) + + selectedDayIncome.value = response.data + .filter(t => t.type === 1) // 只统计收入 + .reduce((sum, t) => sum + t.amount, 0) } } catch (_error) { showToast('获取交易记录失败') @@ -478,6 +525,12 @@ const onSmartClick = () => { // 切换月份 const changeMonth = async (offset) => { + // 设置动画方向 + slideDirection.value = offset > 0 ? 'slide-left' : 'slide-right' + + // 更新 key 触发过渡 + calendarKey.value += 1 + const newDate = new Date(currentDate.value) newDate.setMonth(newDate.getMonth() + offset) currentDate.value = newDate @@ -486,6 +539,60 @@ const changeMonth = async (offset) => { await fetchDailyStats(newDate.getFullYear(), newDate.getMonth() + 1) } +// 触摸滑动相关 +const touchStartX = ref(0) +const touchStartY = ref(0) +const touchEndX = ref(0) +const touchEndY = ref(0) +const isSwiping = ref(false) +const minSwipeDistance = 50 // 最小滑动距离(像素) + +const onTouchStart = (e) => { + touchStartX.value = e.changedTouches[0].screenX + touchStartY.value = e.changedTouches[0].screenY + touchEndX.value = touchStartX.value + touchEndY.value = touchStartY.value + isSwiping.value = false +} + +const onTouchMove = (e) => { + touchEndX.value = e.changedTouches[0].screenX + touchEndY.value = e.changedTouches[0].screenY + + const deltaX = Math.abs(touchEndX.value - touchStartX.value) + const deltaY = Math.abs(touchEndY.value - touchStartY.value) + + // 如果水平滑动距离大于垂直滑动距离,判定为滑动操作 + if (deltaX > deltaY && deltaX > 10) { + isSwiping.value = true + // 阻止页面滚动 + e.preventDefault() + } +} + +const onTouchEnd = async () => { + const distance = touchStartX.value - touchEndX.value + const absDistance = Math.abs(distance) + + // 只有在滑动状态下且达到最小滑动距离时才切换月份 + if (isSwiping.value && absDistance > minSwipeDistance) { + if (distance > 0) { + // 向左滑动 - 下一月 + await changeMonth(1) + } else { + // 向右滑动 - 上一月 + await changeMonth(-1) + } + } + + // 重置触摸位置 + touchStartX.value = 0 + touchStartY.value = 0 + touchEndX.value = 0 + touchEndY.value = 0 + isSwiping.value = false +} + // 监听当前月份变化 watch(() => currentDate.value, async (newDate) => { await fetchDailyStats(newDate.getFullYear(), newDate.getMonth() + 1) @@ -549,23 +656,27 @@ onBeforeUnmount(() => {