fix: v1.4 Bug 修复与体验优化

- 修复所有日期相关逻辑使用 UTC 导致时区偏移问题
- 首页今日数据改用 stats API 聚合查询,消除 100 条上限
- 账单分页加载添加请求序号,防止并发覆盖
- 统计页 maxSingle 改为按金额排序
- 统计页 tab 切换时刷新月对比数据
- 首页 onShow 跳过首次加载,避免双重请求
- 今日卡片同时展示支出/收入,骨架屏对齐
- 账单页统一左滑删除,去掉长按
- 后端 transaction 支持 sortBy 参数
- 后端 stats overview 支持 startDate/endDate 查询
This commit is contained in:
2026-05-29 17:33:24 +08:00
parent 418d5b24f9
commit 4737f160b7
7 changed files with 94 additions and 45 deletions

View File

@@ -37,17 +37,28 @@
</view>
<view class="today-card" v-if="!loading">
<view class="today-left">
<view class="today-item">
<text class="today-label">今日支出</text>
<text class="today-amount">{{ formatAmount(todayExpense) }}</text>
<text class="today-amount expense">{{ formatAmount(todayExpense) }}</text>
</view>
<view class="today-right">
<text class="today-count">{{ todayCount }}</text>
<view class="today-divider"></view>
<view class="today-item">
<text class="today-label">今日收入</text>
<text class="today-amount income">{{ formatAmount(todayIncome) }}</text>
</view>
<view class="today-count">{{ todayCount }}</view>
</view>
<view class="today-card" v-else>
<Skeleton width="200rpx" height="28rpx" variant="text" />
<Skeleton width="160rpx" height="48rpx" variant="rect" />
<view class="today-item">
<Skeleton width="96rpx" height="24rpx" variant="text" />
<Skeleton width="140rpx" height="36rpx" variant="rect" />
</view>
<view class="today-divider"></view>
<view class="today-item">
<Skeleton width="96rpx" height="24rpx" variant="text" />
<Skeleton width="140rpx" height="36rpx" variant="rect" />
</view>
<Skeleton width="48rpx" height="22rpx" variant="text" />
</view>
<view class="quick-actions">
@@ -118,6 +129,7 @@ const recentTx = ref<any[]>([])
const loading = ref(true)
const loadError = ref(false)
const todayExpense = ref(0)
const todayIncome = ref(0)
const todayCount = ref(0)
const greeting = computed(() => {
@@ -133,16 +145,17 @@ async function loadData() {
try {
loading.value = true
loadError.value = false
const today = new Date().toISOString().slice(0, 10)
const now = new Date()
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
await Promise.all([
statsStore.fetchOverview(),
budgetStore.fetchBudget(),
txStore.fetchTransactions({ page: 1 }),
fetchTodayExpense(today)
fetchTodayData(today)
])
overview.value = statsStore.overview
budget.value = budgetStore.budget
recentTx.value = txStore.transactions.slice(0, 10)
recentTx.value = txStore.transactions.slice(0, 5)
} catch (e) {
console.error('Load error:', e)
loadError.value = true
@@ -151,26 +164,28 @@ async function loadData() {
}
}
async function fetchTodayExpense(today: string) {
async function fetchTodayData(today: string) {
try {
const { request } = await import('@/utils/request')
const data = await request<any>({
url: '/transactions',
data: { page: 1, pageSize: 100, type: 'expense', startDate: today, endDate: today }
})
todayExpense.value = data.list.reduce((sum: number, t: any) => sum + t.amount, 0)
todayCount.value = data.list.length
const data = await request<any>({ url: '/stats/overview', data: { startDate: today, endDate: today } })
todayExpense.value = data.expense || 0
todayIncome.value = data.income || 0
todayCount.value = data.count || 0
} catch {
todayExpense.value = 0
todayIncome.value = 0
todayCount.value = 0
}
}
onMounted(() => loadData())
const initialLoaded = ref(false)
onMounted(() => {
loadData().finally(() => { initialLoaded.value = true })
})
// Refresh data when returning from other pages (e.g., after adding a transaction)
onShow(() => {
if (!loading.value) loadData()
if (initialLoaded.value && !loading.value) loadData()
})
onPullDownRefresh(() => {
@@ -311,11 +326,12 @@ function goBills() {
border: 2rpx solid #F0E0D6;
box-shadow: 4rpx 4rpx 12rpx rgba(45, 27, 27, 0.08);
display: flex;
justify-content: space-between;
align-items: center;
gap: 24rpx;
}
.today-left {
.today-item {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
@@ -328,18 +344,24 @@ function goBills() {
.today-amount {
font-family: 'Fredoka', sans-serif;
font-size: 36rpx;
font-size: 32rpx;
font-weight: 600;
color: #FF6B6B;
&.expense { color: #FF6B6B; }
&.income { color: #7BC67E; }
}
.today-right {
text-align: right;
.today-divider {
width: 2rpx;
height: 56rpx;
background: #F0E0D6;
flex-shrink: 0;
}
.today-count {
font-size: 24rpx;
color: #8B7E7E;
font-size: 22rpx;
color: #BFB3B3;
flex-shrink: 0;
}
.quick-actions {