feat: 优化月度/年度报表和数据分析页面
- 创建 MonthlyYearlyReport 组件,显示收支对比、盈余状况、储蓄率和日均消费 - 创建 DetailedAnalysisReport 组件,提供详细的分类统计分析 - 支出/收入分类明细与占比 - TOP5分类排行榜(金银铜奖牌设计) - 可视化进度条和百分比显示 - 在 AnalysisScreen 中新增"报表"视图模式 - 支持分类、成员、报表三种视图切换 - 集成月度/年度报表和详细分析报表 - 更新 README:标记月度/年度报表功能为已完成 - 更新 v1.5 版本历史,记录数据分析优化内容 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e651086e6d
commit
74cc6f36a9
@ -4,7 +4,8 @@
|
||||
"Bash(./gradlew:*)",
|
||||
"Bash(git push:*)",
|
||||
"Bash(git branch:*)",
|
||||
"Bash(git add:*)"
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)"
|
||||
],
|
||||
"deny": []
|
||||
}
|
||||
|
@ -46,7 +46,7 @@
|
||||
### 2. 图表分析 (已完成 🎉)
|
||||
- [x] 支出/收入趋势图表
|
||||
- [x] 分类占比饼图
|
||||
- [ ] 月度/年度报表
|
||||
- [x] 月度/年度报表
|
||||
- [x] 成员消费分析
|
||||
- [x] 自定义统计周期
|
||||
|
||||
@ -82,7 +82,6 @@
|
||||
### 6. 体验优化 (持续进行 🔄)
|
||||
- [x] 深色模式支持
|
||||
- [ ] 手势操作优化
|
||||
- [ ] 快速记账小组件
|
||||
- [ ] 多语言支持
|
||||
- [ ] 自定义主题
|
||||
|
||||
@ -131,6 +130,12 @@
|
||||
- 预算编辑对话框
|
||||
- 预算状态可视化(进度条、超支提醒)
|
||||
- 预算导航集成
|
||||
- 数据分析优化
|
||||
- 月度/年度报表组件
|
||||
- 详细分析报表(分类统计明细)
|
||||
- 收支对比、储蓄率、日均消费分析
|
||||
- TOP分类排行榜
|
||||
- 报表视图集成到分析页面
|
||||
|
||||
### v1.4
|
||||
- 数据安全功能
|
||||
|
@ -0,0 +1,310 @@
|
||||
package com.yovinchen.bookkeeping.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
||||
import com.yovinchen.bookkeeping.model.TransactionType
|
||||
import java.text.NumberFormat
|
||||
import java.time.YearMonth
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 详细分析报表组件
|
||||
* 显示按分类统计的详细信息
|
||||
*/
|
||||
@Composable
|
||||
fun DetailedAnalysisReport(
|
||||
records: List<BookkeepingRecord>,
|
||||
startMonth: YearMonth,
|
||||
endMonth: YearMonth,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val currencyFormatter = NumberFormat.getCurrencyInstance(Locale.CHINA)
|
||||
|
||||
// 按类型分组
|
||||
val incomeRecords = records.filter { it.type == TransactionType.INCOME }
|
||||
val expenseRecords = records.filter { it.type == TransactionType.EXPENSE }
|
||||
|
||||
// 按分类统计
|
||||
val incomeByCategory = incomeRecords.groupBy { it.category }
|
||||
.mapValues { it.value.sumOf { record -> record.amount } }
|
||||
.toList()
|
||||
.sortedByDescending { it.second }
|
||||
|
||||
val expenseByCategory = expenseRecords.groupBy { it.category }
|
||||
.mapValues { it.value.sumOf { record -> record.amount } }
|
||||
.toList()
|
||||
.sortedByDescending { it.second }
|
||||
|
||||
// 总收入和总支出
|
||||
val totalIncome = incomeRecords.sumOf { it.amount }
|
||||
val totalExpense = expenseRecords.sumOf { it.amount }
|
||||
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// 时间范围标题
|
||||
item {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = if (startMonth == endMonth) {
|
||||
"统计期间:${startMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月"))}"
|
||||
} else {
|
||||
"统计期间:${startMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月"))} 至 ${endMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月"))}"
|
||||
},
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 支出分类详情
|
||||
if (expenseByCategory.isNotEmpty()) {
|
||||
item {
|
||||
CategoryDetailCard(
|
||||
title = "支出分类明细",
|
||||
categoryData = expenseByCategory,
|
||||
total = totalExpense,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
currencyFormatter = currencyFormatter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 收入分类详情
|
||||
if (incomeByCategory.isNotEmpty()) {
|
||||
item {
|
||||
CategoryDetailCard(
|
||||
title = "收入分类明细",
|
||||
categoryData = incomeByCategory,
|
||||
total = totalIncome,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
currencyFormatter = currencyFormatter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 分类占比前5名
|
||||
if (expenseByCategory.isNotEmpty()) {
|
||||
item {
|
||||
TopCategoriesCard(
|
||||
title = "支出TOP5",
|
||||
categoryData = expenseByCategory.take(5),
|
||||
total = totalExpense,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
currencyFormatter = currencyFormatter
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类详情卡片
|
||||
*/
|
||||
@Composable
|
||||
private fun CategoryDetailCard(
|
||||
title: String,
|
||||
categoryData: List<Pair<String, Double>>,
|
||||
total: Double,
|
||||
color: Color,
|
||||
currencyFormatter: NumberFormat
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = color,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
|
||||
// 总计
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 12.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = "总计",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(
|
||||
text = currencyFormatter.format(total),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = color
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider(modifier = Modifier.padding(vertical = 8.dp))
|
||||
|
||||
// 分类列表
|
||||
categoryData.forEach { (category, amount) ->
|
||||
val percentage = if (total > 0) (amount / total * 100) else 0.0
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = category,
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Text(
|
||||
text = "${String.format("%.1f", percentage)}%",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = currencyFormatter.format(amount),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
}
|
||||
|
||||
// 进度条
|
||||
LinearProgressIndicator(
|
||||
progress = (percentage / 100).toFloat(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(4.dp)
|
||||
.clip(RoundedCornerShape(2.dp)),
|
||||
color = color.copy(alpha = 0.8f),
|
||||
trackColor = color.copy(alpha = 0.2f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TOP分类卡片
|
||||
*/
|
||||
@Composable
|
||||
private fun TopCategoriesCard(
|
||||
title: String,
|
||||
categoryData: List<Pair<String, Double>>,
|
||||
total: Double,
|
||||
color: Color,
|
||||
currencyFormatter: NumberFormat
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(bottom = 12.dp)
|
||||
)
|
||||
|
||||
categoryData.forEachIndexed { index, (category, amount) ->
|
||||
val percentage = if (total > 0) (amount / total * 100) else 0.0
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 4.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 排名
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.background(
|
||||
when (index) {
|
||||
0 -> Color(0xFFFFD700) // 金色
|
||||
1 -> Color(0xFFC0C0C0) // 银色
|
||||
2 -> Color(0xFFCD7F32) // 铜色
|
||||
else -> MaterialTheme.colorScheme.surfaceVariant
|
||||
}
|
||||
),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "${index + 1}",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = if (index < 3) Color.White else MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = category,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = if (index == 0) FontWeight.Bold else FontWeight.Normal
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Text(
|
||||
text = currencyFormatter.format(amount),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Medium
|
||||
)
|
||||
Text(
|
||||
text = "${String.format("%.1f", percentage)}%",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,258 @@
|
||||
package com.yovinchen.bookkeeping.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
||||
import com.yovinchen.bookkeeping.model.TransactionType
|
||||
import java.text.NumberFormat
|
||||
import java.time.LocalDateTime
|
||||
import java.time.YearMonth
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* 月度/年度报表组件
|
||||
* 显示收支对比、盈余情况等统计信息
|
||||
*/
|
||||
@Composable
|
||||
fun MonthlyYearlyReport(
|
||||
records: List<BookkeepingRecord>,
|
||||
period: String, // "月度" 或 "年度"
|
||||
startMonth: YearMonth,
|
||||
endMonth: YearMonth,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val totalIncome = records
|
||||
.filter { it.type == TransactionType.INCOME }
|
||||
.sumOf { it.amount }
|
||||
|
||||
val totalExpense = records
|
||||
.filter { it.type == TransactionType.EXPENSE }
|
||||
.sumOf { it.amount }
|
||||
|
||||
val balance = totalIncome - totalExpense
|
||||
val savingsRate = if (totalIncome > 0) {
|
||||
((totalIncome - totalExpense) / totalIncome * 100).coerceAtLeast(0.0)
|
||||
} else 0.0
|
||||
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// 标题
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "$period 报表",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
|
||||
Text(
|
||||
text = if (startMonth == endMonth) {
|
||||
startMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月"))
|
||||
} else {
|
||||
"${startMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月"))} - ${endMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月"))}"
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
|
||||
HorizontalDivider()
|
||||
|
||||
// 收支对比
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
// 收入
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = "总收入",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
text = formatCurrency(totalIncome),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
|
||||
// 分隔线
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(1.dp)
|
||||
.height(40.dp)
|
||||
.background(MaterialTheme.colorScheme.outlineVariant)
|
||||
)
|
||||
|
||||
// 支出
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Text(
|
||||
text = "总支出",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
text = formatCurrency(totalExpense),
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 盈余情况
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = if (balance >= 0) {
|
||||
MaterialTheme.colorScheme.primaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.errorContainer
|
||||
}
|
||||
)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = if (balance >= 0) "盈余" else "亏损",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = if (balance >= 0) {
|
||||
MaterialTheme.colorScheme.onPrimaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onErrorContainer
|
||||
}
|
||||
)
|
||||
Text(
|
||||
text = formatCurrency(kotlin.math.abs(balance)),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = if (balance >= 0) {
|
||||
MaterialTheme.colorScheme.onPrimaryContainer
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onErrorContainer
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 储蓄率
|
||||
if (totalIncome > 0) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "储蓄率",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier.weight(2f)
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
progress = (savingsRate / 100).toFloat().coerceIn(0f, 1f),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(8.dp)
|
||||
.clip(RoundedCornerShape(4.dp)),
|
||||
color = when {
|
||||
savingsRate >= 30 -> MaterialTheme.colorScheme.primary
|
||||
savingsRate >= 10 -> MaterialTheme.colorScheme.tertiary
|
||||
else -> MaterialTheme.colorScheme.error
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "${String.format("%.1f", savingsRate)}%",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(start = 8.dp),
|
||||
color = when {
|
||||
savingsRate >= 30 -> MaterialTheme.colorScheme.primary
|
||||
savingsRate >= 10 -> MaterialTheme.colorScheme.tertiary
|
||||
else -> MaterialTheme.colorScheme.error
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 日均消费
|
||||
val dayCount = if (period == "月度") {
|
||||
// 计算月度天数
|
||||
java.time.temporal.ChronoUnit.DAYS.between(
|
||||
startMonth.atDay(1),
|
||||
endMonth.atEndOfMonth()
|
||||
) + 1
|
||||
} else {
|
||||
// 计算年度天数
|
||||
java.time.temporal.ChronoUnit.DAYS.between(
|
||||
startMonth.atDay(1),
|
||||
endMonth.atEndOfMonth()
|
||||
) + 1
|
||||
}
|
||||
|
||||
val dailyAverage = if (dayCount > 0) totalExpense / dayCount else 0.0
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = "日均消费",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
text = formatCurrency(dailyAverage),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化货币
|
||||
*/
|
||||
private fun formatCurrency(amount: Double): String {
|
||||
val format = NumberFormat.getCurrencyInstance(Locale.CHINA)
|
||||
return format.format(amount)
|
||||
}
|
@ -38,12 +38,14 @@ import com.yovinchen.bookkeeping.model.MemberStat
|
||||
import com.yovinchen.bookkeeping.ui.components.CategoryPieChart
|
||||
import com.yovinchen.bookkeeping.ui.components.CategoryStatItem
|
||||
import com.yovinchen.bookkeeping.ui.components.DateRangePicker
|
||||
import com.yovinchen.bookkeeping.ui.components.DetailedAnalysisReport
|
||||
import com.yovinchen.bookkeeping.ui.components.MonthlyYearlyReport
|
||||
import com.yovinchen.bookkeeping.ui.components.TrendLineChart
|
||||
import com.yovinchen.bookkeeping.viewmodel.AnalysisViewModel
|
||||
import java.time.YearMonth
|
||||
|
||||
enum class ViewMode {
|
||||
CATEGORY, MEMBER
|
||||
CATEGORY, MEMBER, REPORT
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@ -93,7 +95,13 @@ fun AnalysisScreen(
|
||||
Button(
|
||||
onClick = { showViewModeMenu = true }
|
||||
) {
|
||||
Text(if (currentViewMode == ViewMode.CATEGORY) "分类" else "成员")
|
||||
Text(
|
||||
when {
|
||||
currentViewMode == ViewMode.CATEGORY -> "分类"
|
||||
currentViewMode == ViewMode.MEMBER -> "成员"
|
||||
else -> "报表"
|
||||
}
|
||||
)
|
||||
Icon(Icons.Default.ArrowDropDown, "切换视图")
|
||||
}
|
||||
DropdownMenu(
|
||||
@ -114,6 +122,13 @@ fun AnalysisScreen(
|
||||
showViewModeMenu = false
|
||||
}
|
||||
)
|
||||
DropdownMenuItem(
|
||||
text = { Text("报表") },
|
||||
onClick = {
|
||||
currentViewMode = ViewMode.REPORT
|
||||
showViewModeMenu = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,6 +174,32 @@ fun AnalysisScreen(
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if (currentViewMode == ViewMode.REPORT) {
|
||||
// 报表视图
|
||||
item {
|
||||
MonthlyYearlyReport(
|
||||
records = records,
|
||||
period = if (startMonth == endMonth) "月度" else "年度",
|
||||
startMonth = startMonth,
|
||||
endMonth = endMonth,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// 详细分析报表
|
||||
item {
|
||||
DetailedAnalysisReport(
|
||||
records = records,
|
||||
startMonth = startMonth,
|
||||
endMonth = endMonth,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 16.dp)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// 饼图视图
|
||||
item {
|
||||
CategoryPieChart(
|
||||
@ -201,3 +242,4 @@ fun AnalysisScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user