first commit
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
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'], '📦'),
|
||||
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']),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user