337 lines
6.7 KiB
Vue
337 lines
6.7 KiB
Vue
<template>
|
||
<view class="container">
|
||
<view class="header">
|
||
<view class="header-top">
|
||
<text class="title">我的账单</text>
|
||
<view class="account-btn" @click="goToAccount">
|
||
<text class="account-icon">💰</text>
|
||
</view>
|
||
</view>
|
||
<view class="summary">
|
||
<view class="summary-item">
|
||
<text class="label">本月支出</text>
|
||
<text class="amount expense">¥{{ monthlyExpense.toFixed(2) }}</text>
|
||
</view>
|
||
<view class="summary-item">
|
||
<text class="label">本月收入</text>
|
||
<text class="amount income">¥{{ monthlyIncome.toFixed(2) }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="bill-list">
|
||
<view v-for="(group, date) in groupedBills" :key="date" class="bill-group">
|
||
<view class="group-header">
|
||
<text class="date">{{ date }}</text>
|
||
<text class="total">¥{{ getGroupTotal(group).toFixed(2) }}</text>
|
||
</view>
|
||
<view v-for="bill in group" :key="bill.id" class="bill-item" @click="viewBill(bill)">
|
||
<view class="bill-icon">{{ bill.categoryIcon || '📦' }}</view>
|
||
<view class="bill-info">
|
||
<text class="bill-category">{{ bill.categoryName || '未分类' }}</text>
|
||
<text class="bill-desc" v-if="bill.description">{{ bill.description }}</text>
|
||
</view>
|
||
<text class="bill-amount" :class="bill.amount > 0 ? 'income' : 'expense'">
|
||
{{ bill.type > 0 ? '-' : '+' }}¥{{ Math.abs(bill.amount).toFixed(2) }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="bills.length === 0 && !loading" class="empty">
|
||
<text>暂无账单记录</text>
|
||
<text class="empty-tip">点击下方"记账"按钮添加账单</text>
|
||
</view>
|
||
|
||
<view v-if="loading" class="loading">
|
||
<text>加载中...</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import { getBills } from '../../api/bill'
|
||
import { getMonthlyStatistics } from '../../api/statistics'
|
||
import { formatDate } from '../../utils/date'
|
||
import { useUserStore } from '../../store/user'
|
||
|
||
const userStore = useUserStore()
|
||
const bills = ref([])
|
||
const monthlyIncome = ref(0)
|
||
const monthlyExpense = ref(0)
|
||
const loading = ref(false)
|
||
|
||
const groupedBills = computed(() => {
|
||
const groups = {}
|
||
bills.value.forEach(bill => {
|
||
const date = formatDate(bill.billDate)
|
||
if (!groups[date]) {
|
||
groups[date] = []
|
||
}
|
||
groups[date].push(bill)
|
||
})
|
||
return groups
|
||
})
|
||
|
||
const getGroupTotal = (group) => {
|
||
return group.reduce((sum, bill) => sum + parseFloat(bill.amount || 0), 0)
|
||
}
|
||
|
||
const loadBills = async () => {
|
||
loading.value = true
|
||
try {
|
||
const data = await getBills()
|
||
bills.value = data || []
|
||
} catch (error) {
|
||
console.error('加载账单失败', error)
|
||
if (error.message && error.message.includes('401')) {
|
||
// token过期,跳转到登录页
|
||
userStore.logout()
|
||
uni.reLaunch({
|
||
url: '/pages/login/login'
|
||
})
|
||
}
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
}
|
||
|
||
const loadMonthlyStatistics = async () => {
|
||
try {
|
||
const now = new Date()
|
||
const data = await getMonthlyStatistics(now.getFullYear(), now.getMonth() + 1)
|
||
monthlyIncome.value = data.totalIncome || 0
|
||
monthlyExpense.value = data.totalExpense || 0
|
||
} catch (error) {
|
||
console.error('加载统计失败', error)
|
||
}
|
||
}
|
||
|
||
const viewBill = (bill) => {
|
||
// 跳转到账单编辑页面
|
||
uni.navigateTo({
|
||
url: `/pages/bill/detail?id=${bill.id}`
|
||
})
|
||
}
|
||
|
||
const goToAccount = () => {
|
||
// 跳转到账户管理页面
|
||
uni.navigateTo({
|
||
url: '/pages/account/account'
|
||
})
|
||
}
|
||
|
||
const onPullDownRefresh = () => {
|
||
loadBills()
|
||
loadMonthlyStatistics()
|
||
setTimeout(() => {
|
||
uni.stopPullDownRefresh()
|
||
}, 1000)
|
||
}
|
||
|
||
onMounted(() => {
|
||
// 检查登录状态
|
||
if (!userStore.isLoggedIn) {
|
||
uni.reLaunch({
|
||
url: '/pages/login/login'
|
||
})
|
||
return
|
||
}
|
||
|
||
loadBills()
|
||
loadMonthlyStatistics()
|
||
})
|
||
|
||
// 监听刷新事件
|
||
uni.$on('refreshBills', () => {
|
||
loadBills()
|
||
loadMonthlyStatistics()
|
||
})
|
||
</script>
|
||
|
||
<script>
|
||
// uni-app 页面生命周期钩子
|
||
export default {
|
||
onShow() {
|
||
// 页面显示时刷新 - 通过事件触发
|
||
uni.$emit('refreshBills')
|
||
},
|
||
onPullDownRefresh() {
|
||
// 下拉刷新 - 通过事件触发
|
||
uni.$emit('refreshBills')
|
||
setTimeout(() => {
|
||
uni.stopPullDownRefresh()
|
||
}, 1000)
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.container {
|
||
min-height: calc(100vh - 100rpx);
|
||
background: #f5f5f5;
|
||
padding-bottom: calc(170rpx + env(safe-area-inset-bottom));
|
||
}
|
||
|
||
.header {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
padding: 40rpx 30rpx;
|
||
color: #fff;
|
||
}
|
||
|
||
.header-top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30rpx;
|
||
}
|
||
|
||
.title {
|
||
font-size: 40rpx;
|
||
font-weight: bold;
|
||
display: block;
|
||
}
|
||
|
||
.account-btn {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.account-btn:active {
|
||
background: rgba(255, 255, 255, 0.3);
|
||
transform: scale(0.95);
|
||
}
|
||
|
||
.account-icon {
|
||
font-size: 36rpx;
|
||
}
|
||
|
||
.summary {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
}
|
||
|
||
.summary-item {
|
||
text-align: center;
|
||
}
|
||
|
||
.label {
|
||
display: block;
|
||
font-size: 24rpx;
|
||
opacity: 0.8;
|
||
margin-bottom: 10rpx;
|
||
}
|
||
|
||
.amount {
|
||
display: block;
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.bill-list {
|
||
padding: 20rpx;
|
||
}
|
||
|
||
.bill-group {
|
||
background: #fff;
|
||
border-radius: 12rpx;
|
||
margin-bottom: 20rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.group-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 20rpx 30rpx;
|
||
background: #f8f8f8;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.date {
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.total {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.bill-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 30rpx;
|
||
border-bottom: 1rpx solid #f0f0f0;
|
||
}
|
||
|
||
.bill-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.bill-icon {
|
||
font-size: 48rpx;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.bill-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.bill-category {
|
||
font-size: 30rpx;
|
||
color: #333;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.bill-desc {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.bill-amount {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.bill-amount.income {
|
||
color: #19be6b;
|
||
}
|
||
|
||
.bill-amount.expense {
|
||
color: #fa3534;
|
||
}
|
||
|
||
.empty {
|
||
text-align: center;
|
||
padding: 100rpx 0;
|
||
color: #999;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.empty-tip {
|
||
font-size: 24rpx;
|
||
margin-top: 20rpx;
|
||
color: #ccc;
|
||
}
|
||
|
||
.loading {
|
||
text-align: center;
|
||
padding: 40rpx 0;
|
||
color: #999;
|
||
}
|
||
</style>
|