AI-accounting-soft-uniApp/pages/mine/mine.vue

587 lines
13 KiB
Vue

<template>
<scroll-view scroll-y class="mine-page">
<view class="mine-container">
<!-- 用户信息卡片 -->
<view class="user-card">
<view class="user-avatar">👤</view>
<view class="user-info">
<text class="user-name">{{ userInfo.nickname || userInfo.username || '用户' }}</text>
<text class="user-id">ID: {{ userInfo.id || '-' }}</text>
</view>
</view>
<!-- 账户余额卡片 -->
<view class="balance-card" @click="openBalanceModal">
<view class="balance-header">
<text class="balance-title">账户余额</text>
<text class="balance-arrow">></text>
</view>
<text class="balance-amount" :class="account.balance >= 0 ? 'balance-positive' : 'balance-negative'">
¥{{ account.balance ? account.balance.toFixed(2) : '0.00' }}
</text>
<view class="balance-stats">
<view class="balance-stat-item">
<text class="balance-stat-label">总收入</text>
<text class="balance-stat-value income">¥{{ account.totalIncome ? account.totalIncome.toFixed(2) : '0.00' }}</text>
</view>
<view class="balance-stat-item">
<text class="balance-stat-label">总支出</text>
<text class="balance-stat-value expense">¥{{ account.totalExpense ? account.totalExpense.toFixed(2) : '0.00' }}</text>
</view>
</view>
</view>
<!-- 预算卡片 -->
<view class="budget-card" @click="openBudgetModal">
<view class="budget-header">
<text class="budget-title">本月预算</text>
<text class="budget-arrow">></text>
</view>
<view class="budget-content">
<view class="budget-progress-wrapper">
<view class="budget-progress-bar">
<view class="budget-progress-fill" :style="{ width: budgetProgress + '%' }"></view>
</view>
<text class="budget-progress-text">{{ budgetProgress.toFixed(0) }}%</text>
</view>
<view class="budget-stats">
<view class="budget-stat-item">
<text class="budget-stat-label">预算</text>
<text class="budget-stat-value">¥{{ budget.amount ? budget.amount.toFixed(2) : '0.00' }}</text>
</view>
<view class="budget-stat-item">
<text class="budget-stat-label">已用</text>
<text class="budget-stat-value expense">¥{{ budget.usedAmount ? budget.usedAmount.toFixed(2) : '0.00' }}</text>
</view>
<view class="budget-stat-item">
<text class="budget-stat-label">剩余</text>
<text class="budget-stat-value" :class="budget.remainingAmount >= 0 ? 'positive' : 'negative'">
¥{{ budget.remainingAmount !== undefined ? budget.remainingAmount.toFixed(2) : '0.00' }}
</text>
</view>
</view>
</view>
</view>
<!-- 功能菜单 -->
<view class="menu-section">
<view class="menu-item" @click="goToStatistics">
<view class="menu-icon-wrapper">
<text class="menu-icon">📊</text>
</view>
<text class="menu-text">统计分析</text>
<text class="menu-arrow">></text>
</view>
<view class="menu-item" @click="goToAccount">
<view class="menu-icon-wrapper">
<text class="menu-icon">💰</text>
</view>
<text class="menu-text">账户管理</text>
<text class="menu-arrow">></text>
</view>
<view class="menu-item" @click="goToBudget">
<view class="menu-icon-wrapper">
<text class="menu-icon">📝</text>
</view>
<text class="menu-text">预算设置</text>
<text class="menu-arrow">></text>
</view>
</view>
<!-- 退出登录按钮 -->
<button class="logout-button" @click="logout">退出登录</button>
</view>
<!-- 编辑弹窗 -->
<EditModal
:visible="modalState.visible"
:title="modalState.title"
:placeholder="modalState.placeholder"
:currentValue="modalState.currentValue"
@confirm="handleModalConfirm"
@cancel="handleModalCancel"
/>
</scroll-view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { getAccount } from '../../api/account'
import { getBudget } from '../../api/budget'
import { updateAccountBalance, updateBudget } from '../../api/edit-modal'
import EditModal from '../../components/EditModal/EditModal.vue'
const userInfo = ref({
id: null,
username: '',
nickname: ''
})
const account = ref({
balance: 0,
totalIncome: 0,
totalExpense: 0
})
const budget = ref({
amount: 0,
usedAmount: 0,
remainingAmount: 0
})
const modalState = ref({
visible: false,
title: '',
placeholder: '',
currentValue: '',
type: '' // 'balance' 或 'budget'
})
const budgetProgress = computed(() => {
if (!budget.value.amount || budget.value.amount === 0) return 0
const progress = (budget.value.usedAmount / budget.value.amount) * 100
return Math.min(progress, 100)
})
const loadUserInfo = () => {
try {
const info = uni.getStorageSync('userInfo')
if (info) {
userInfo.value = typeof info === 'string' ? JSON.parse(info) : info
}
} catch (error) {
console.error('加载用户信息失败', error)
}
}
const loadAccount = async () => {
try {
const data = await getAccount()
if (data) {
account.value = data
}
} catch (error) {
console.error('加载账户失败', error)
}
}
const loadBudget = async () => {
try {
const data = await getBudget()
if (data) {
budget.value = data
}
} catch (error) {
console.error('加载预算失败', error)
}
}
const openBalanceModal = () => {
modalState.value = {
visible: true,
title: '编辑账户余额',
placeholder: '请输入新的账户余额',
currentValue: account.value.balance || 0,
type: 'balance'
}
}
const openBudgetModal = () => {
modalState.value = {
visible: true,
title: '编辑本月预算',
placeholder: '请输入新的预算金额',
currentValue: budget.value.amount || 0,
type: 'budget'
}
}
const handleModalConfirm = async (value) => {
try {
if (modalState.value.type === 'balance') {
await updateAccountBalance(parseFloat(value))
account.value.balance = parseFloat(value)
uni.showToast({
title: '账户余额已更新',
icon: 'success',
duration: 2000
})
} else if (modalState.value.type === 'budget') {
await updateBudget(parseFloat(value))
budget.value.amount = parseFloat(value)
uni.showToast({
title: '预算已更新',
icon: 'success',
duration: 2000
})
}
handleModalCancel()
} catch (error) {
console.error('更新失败', error)
uni.showToast({
title: '更新失败,请重试',
icon: 'error',
duration: 2000
})
}
}
const handleModalCancel = () => {
modalState.value.visible = false
}
const goToStatistics = () => {
uni.switchTab({
url: '/pages/statistics/statistics'
})
}
const goToAccount = () => {
uni.navigateTo({
url: '/pages/account/account'
})
}
const goToBudget = () => {
uni.navigateTo({
url: '/pages/budget/budget'
})
}
const logout = () => {
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
uni.removeStorageSync('token')
uni.removeStorageSync('userInfo')
uni.reLaunch({
url: '/pages/login/login'
})
}
}
})
}
onMounted(() => {
loadUserInfo()
loadAccount()
loadBudget()
})
</script>
<style scoped>
.mine-page {
height: 100vh;
background: linear-gradient(135deg, #FFE5CC 0%, #FFD4A8 50%, #FFC08A 100%);
}
.mine-container {
min-height: 100vh;
padding: 20rpx;
padding-bottom: calc(170rpx + env(safe-area-inset-bottom));
}
/* 用户信息卡片 */
.user-card {
background: linear-gradient(135deg, #C8956E, #F5D59E, #FFD700);
border-radius: 25rpx;
padding: 40rpx 30rpx;
margin-bottom: 20rpx;
display: flex;
align-items: center;
border: 4rpx solid #5D4037;
box-shadow: 0 10rpx 30rpx rgba(93, 64, 55, 0.2);
}
.user-avatar {
width: 120rpx;
height: 120rpx;
background: rgba(255, 255, 255, 0.9);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 60rpx;
margin-right: 30rpx;
border: 3rpx solid #5D4037;
box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1);
}
.user-info {
flex: 1;
}
.user-name {
display: block;
font-size: 36rpx;
font-weight: bold;
color: #5D4037;
margin-bottom: 8rpx;
}
.user-id {
display: block;
font-size: 24rpx;
color: #8D6E63;
}
/* 账户余额卡片 */
.balance-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 25rpx;
padding: 35rpx 30rpx;
margin-bottom: 20rpx;
border: 3rpx solid #C8956E;
box-shadow: 0 10rpx 30rpx rgba(200, 149, 110, 0.15);
}
.balance-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.balance-title {
font-size: 28rpx;
color: #8D6E63;
font-weight: 600;
}
.balance-arrow {
font-size: 28rpx;
color: #C8956E;
font-weight: bold;
}
.balance-amount {
font-size: 56rpx;
font-weight: bold;
display: block;
margin-bottom: 25rpx;
}
.balance-positive {
color: #FFD700;
}
.balance-negative {
color: #FA3534;
}
.balance-stats {
display: flex;
gap: 20rpx;
}
.balance-stat-item {
flex: 1;
background: rgba(255, 255, 255, 0.8);
border-radius: 15rpx;
padding: 20rpx;
text-align: center;
border: 2rpx solid #C8956E;
}
.balance-stat-label {
display: block;
font-size: 24rpx;
color: #8D6E63;
margin-bottom: 8rpx;
}
.balance-stat-value {
display: block;
font-size: 28rpx;
font-weight: bold;
color: #5D4037;
}
.balance-stat-value.income {
color: #19BE6B;
}
.balance-stat-value.expense {
color: #FA3534;
}
/* 预算卡片 */
.budget-card {
background: rgba(255, 255, 255, 0.95);
border-radius: 25rpx;
padding: 35rpx 30rpx;
margin-bottom: 20rpx;
border: 3rpx solid #C8956E;
box-shadow: 0 10rpx 30rpx rgba(200, 149, 110, 0.15);
}
.budget-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 25rpx;
}
.budget-title {
font-size: 28rpx;
color: #8D6E63;
font-weight: 600;
}
.budget-arrow {
font-size: 28rpx;
color: #C8956E;
font-weight: bold;
}
.budget-content {
}
.budget-progress-wrapper {
display: flex;
align-items: center;
margin-bottom: 20rpx;
}
.budget-progress-bar {
flex: 1;
height: 20rpx;
background: rgba(200, 149, 110, 0.2);
border-radius: 20rpx;
overflow: hidden;
margin-right: 15rpx;
}
.budget-progress-fill {
height: 100%;
background: linear-gradient(90deg, #C8956E, #FFD700);
border-radius: 20rpx;
transition: width 0.5s ease-out;
}
.budget-progress-text {
font-size: 24rpx;
color: #5D4037;
font-weight: bold;
min-width: 60rpx;
text-align: right;
}
.budget-stats {
display: flex;
gap: 15rpx;
}
.budget-stat-item {
flex: 1;
background: rgba(255, 255, 255, 0.8);
border-radius: 15rpx;
padding: 20rpx;
text-align: center;
border: 2rpx solid #C8956E;
}
.budget-stat-label {
display: block;
font-size: 24rpx;
color: #8D6E63;
margin-bottom: 8rpx;
}
.budget-stat-value {
display: block;
font-size: 28rpx;
font-weight: bold;
color: #5D4037;
}
.budget-stat-value.positive {
color: #19BE6B;
}
.budget-stat-value.negative {
color: #FA3534;
}
.budget-stat-value.expense {
color: #FA3534;
}
/* 功能菜单 */
.menu-section {
background: rgba(255, 255, 255, 0.95);
border-radius: 25rpx;
padding: 20rpx 0;
margin-bottom: 20rpx;
border: 3rpx solid #C8956E;
box-shadow: 0 10rpx 30rpx rgba(200, 149, 110, 0.15);
}
.menu-item {
display: flex;
align-items: center;
padding: 30rpx 30rpx;
transition: background 0.3s;
}
.menu-item:active {
background: rgba(255, 215, 0, 0.1);
}
.menu-icon-wrapper {
width: 80rpx;
height: 80rpx;
background: linear-gradient(135deg, rgba(200, 149, 110, 0.2), rgba(255, 215, 0, 0.2));
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 25rpx;
border: 2rpx solid #C8956E;
}
.menu-icon {
font-size: 40rpx;
}
.menu-text {
flex: 1;
font-size: 30rpx;
color: #5D4037;
font-weight: 600;
}
.menu-arrow {
font-size: 28rpx;
color: #C8956E;
font-weight: bold;
}
/* 退出登录按钮 */
.logout-button {
width: 100%;
height: 96rpx;
background: rgba(255, 255, 255, 0.95);
color: #FA3534;
border-radius: 50rpx;
font-size: 32rpx;
font-weight: bold;
border: 3rpx solid #FA3534;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 20rpx rgba(250, 53, 52, 0.2);
transition: all 0.3s;
}
.logout-button:active {
transform: scale(0.98);
background: rgba(250, 53, 52, 0.1);
}
</style>