import 'json_util.dart'; /// 统计响应(对应后端 StatisticsResponse) /// /// 口径说明:后端的 totalIncome / totalExpense / categoryStatistics 是按 /// **分类的 type** 判定收支的,而 /bills 列表是按 **账单的 type** 判定。 /// 两者可能不一致——前端不做交叉校验,汇总/饼图一律以此接口为准。 class StatisticsResponse { final double totalIncome; final double totalExpense; final double balance; final List categoryStatistics; final List 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 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 json) => CategoryStat( categoryId: asInt(json['categoryId']), categoryName: asNonEmpty(json['categoryName'], '未分类'), categoryIcon: asNonEmpty(json['categoryIcon'], '📦'), 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 json) => DailyStat( date: asString(json['date']), income: asDouble(json['income']), expense: asDouble(json['expense']), ); }