Files
Aq-Accounting-Flutter/lib/models/statistics_response.dart
T
2026-09-24 14:24:34 +08:00

91 lines
2.8 KiB
Dart
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.
import 'json_util.dart';
/// 统计响应(对应后端 StatisticsResponse)
///
/// 口径说明:后端的 totalIncome / totalExpense / categoryStatistics 是按
/// **分类的 type** 判定收支的,而 /bills 列表是按 **账单的 type** 判定。
/// 两者可能不一致——前端不做交叉校验,汇总/饼图一律以此接口为准。
class StatisticsResponse {
final double totalIncome;
final double totalExpense;
final double balance;
final List<CategoryStat> categoryStatistics;
final List<DailyStat> dailyStatistics;
const StatisticsResponse({
required this.totalIncome,
required this.totalExpense,
required this.balance,
required this.categoryStatistics,
required this.dailyStatistics,
});
bool get isEmpty => categoryStatistics.isEmpty && totalIncome == 0 && totalExpense == 0;
factory StatisticsResponse.fromJson(Map<String, dynamic> json) {
final income = asDouble(json['totalIncome']);
final expense = asDouble(json['totalExpense']);
final balanceRaw = json['balance'];
return StatisticsResponse(
totalIncome: income,
totalExpense: expense,
// 后端没给 balance 时本地兜底计算
balance: balanceRaw == null ? income - expense : asDouble(balanceRaw),
categoryStatistics:
asMapList(json['categoryStatistics']).map(CategoryStat.fromJson).toList(),
dailyStatistics:
asMapList(json['dailyStatistics']).map(DailyStat.fromJson).toList(),
);
}
static const empty = StatisticsResponse(
totalIncome: 0,
totalExpense: 0,
balance: 0,
categoryStatistics: [],
dailyStatistics: [],
);
}
/// 分类维度的统计项
class CategoryStat {
final int categoryId;
final String categoryName;
final String categoryIcon;
final double amount;
final int type; // 1=支出 2=收入
const CategoryStat({
required this.categoryId,
required this.categoryName,
required this.categoryIcon,
required this.amount,
required this.type,
});
bool get isExpense => type != 2;
factory CategoryStat.fromJson(Map<String, dynamic> json) => CategoryStat(
categoryId: asInt(json['categoryId']),
categoryName: asNonEmpty(json['categoryName'], '未分类'),
categoryIcon: asNonEmpty(json['categoryIcon'], 'other'),
amount: asDouble(json['amount']),
type: asInt(json['type'], fallback: 1) == 2 ? 2 : 1,
);
}
/// 按日趋势项(本期统计页不画折线图,保留以便后续扩展)
class DailyStat {
final String date; // yyyy-MM-dd
final double income;
final double expense;
const DailyStat({required this.date, required this.income, required this.expense});
factory DailyStat.fromJson(Map<String, dynamic> json) => DailyStat(
date: asString(json['date']),
income: asDouble(json['income']),
expense: asDouble(json['expense']),
);
}