AI-accounting-soft-uniApp/pages/index/index.vue
ni ziyi 70715bb0c8 feat(新增):
新增ocr获取信息后,批量直接入库功能
2025-12-25 14:55:08 +08:00

458 lines
9.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="container">
<view class="header">
<view class="header-top">
<text class="title">我的账单</text>
<view class="header-actions">
<view class="action-btn" @click="goToBudget">
<text class="action-icon">📊</text>
</view>
<view class="action-btn" @click="goToAccount">
<text class="action-icon">💰</text>
</view>
</view>
</view>
<!-- 收入支出汇总 -->
<view class="summary-row">
<view class="summary-item">
<text class="label">本月收入</text>
<text class="amount income">¥{{ monthlyIncome.toFixed(2) }}</text>
</view>
<view class="summary-item">
<text class="label">本月支出</text>
<text class="amount expense">¥{{ budget.usedAmount ? budget.usedAmount.toFixed(2) : '0.00' }}</text>
</view>
</view>
<!-- 预算信息 -->
<view class="budget-section">
<view class="budget-chart-wrapper">
<BudgetPieChart
:budget="budget.amount || 0"
:used="budget.usedAmount || 0"
chartId="mainBudgetChart"
/>
</view>
<view class="budget-stats-row">
<view class="budget-stat-item">
<text class="stat-label">本月预算</text>
<text class="stat-value">¥{{ budget.amount ? budget.amount.toFixed(2) : '0.00' }}</text>
</view>
<view class="budget-stat-item">
<text class="stat-label">每日预算</text>
<text class="stat-value">¥{{ budget.remainingDaily ? budget.remainingDaily.toFixed(2) : '0.00' }}</text>
</view>
</view>
</view>
</view>
<scroll-view class="bill-list" scroll-y="true" show-scrollbar="false" enhanced="true" :bounce="false">
<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>
</scroll-view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { getBills } from '../../api/bill'
import { getMonthlyStatistics } from '../../api/statistics'
import { getBudget } from '../../api/budget'
import { formatDate } from '../../utils/date'
import { useUserStore } from '../../store/user'
import BudgetPieChart from '../../components/BudgetPieChart/BudgetPieChart.vue'
const userStore = useUserStore()
const bills = ref([])
const monthlyIncome = ref(0)
const monthlyExpense = ref(0)
const budget = ref({
amount: 0,
usedAmount: 0,
remainingAmount: 0,
remainingDaily: 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 loadBudget = async () => {
try {
const data = await getBudget()
if (data) {
budget.value = {
amount: data.amount || 0,
usedAmount: data.usedAmount || 0,
remainingAmount: data.remainingAmount || 0,
remainingDaily: data.remainingDaily || 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 goToBudget = () => {
// 跳转到预算管理页面
uni.navigateTo({
url: '/pages/budget/budget'
})
}
const onPullDownRefresh = () => {
loadBills()
loadMonthlyStatistics()
loadBudget()
setTimeout(() => {
uni.stopPullDownRefresh()
}, 1000)
}
onMounted(() => {
// 检查登录状态
if (!userStore.isLoggedIn) {
uni.reLaunch({
url: '/pages/login/login'
})
return
}
loadBills()
loadMonthlyStatistics()
loadBudget()
})
// 监听刷新事件
uni.$on('refreshBills', () => {
loadBills()
loadMonthlyStatistics()
loadBudget()
})
</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;
}
.header-actions {
display: flex;
gap: 15rpx;
}
.action-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;
}
.action-btn:active {
background: rgba(255, 255, 255, 0.3);
transform: scale(0.95);
}
.action-icon {
font-size: 36rpx;
}
.summary-row {
display: flex;
justify-content: space-around;
margin-bottom: 20rpx;
}
.summary-item {
text-align: center;
flex: 1;
}
.label {
display: block;
font-size: 24rpx;
opacity: 0.8;
margin-bottom: 10rpx;
}
.amount {
display: block;
font-size: 36rpx;
font-weight: bold;
}
.amount.income {
color: #51cf66;
}
.amount.expense {
color: #ff6b6b;
}
.budget-section {
background: rgba(255, 255, 255, 0.15);
border-radius: 12rpx;
padding: 30rpx 20rpx 20rpx;
backdrop-filter: blur(10px);
}
.budget-chart-wrapper {
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 25rpx;
}
.budget-stats-row {
display: flex;
justify-content: space-around;
gap: 20rpx;
}
.budget-stat-item {
flex: 1;
text-align: center;
background: rgba(255, 255, 255, 0.1);
padding: 20rpx 15rpx;
border-radius: 10rpx;
}
.stat-label {
display: block;
font-size: 22rpx;
opacity: 0.85;
margin-bottom: 10rpx;
color: #fff;
}
.stat-value {
display: block;
font-size: 30rpx;
font-weight: bold;
color: #fff;
}
.bill-list {
padding: 20rpx;
height: calc(100vh - 520rpx - env(safe-area-inset-bottom));
}
.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>