130 lines
3.4 KiB
TypeScript
130 lines
3.4 KiB
TypeScript
import type { UIBill } from '$lib/models/bill';
|
|
import type { CategoryStat, MonthlyStat, DailyExpenseData, TotalStats, PieChartDataItem } from '$lib/types/analysis';
|
|
import { pieColors } from '$lib/constants/chart';
|
|
|
|
/**
|
|
* 计算分类统计
|
|
*/
|
|
export function calculateCategoryStats(records: UIBill[]): CategoryStat[] {
|
|
const stats = new Map<string, { expense: number; income: number; count: number }>();
|
|
|
|
for (const r of records) {
|
|
if (!stats.has(r.category)) {
|
|
stats.set(r.category, { expense: 0, income: 0, count: 0 });
|
|
}
|
|
const s = stats.get(r.category)!;
|
|
s.count++;
|
|
const amount = r.amount || 0;
|
|
if (r.incomeExpense === '支出') {
|
|
s.expense += amount;
|
|
} else {
|
|
s.income += amount;
|
|
}
|
|
}
|
|
|
|
return [...stats.entries()]
|
|
.map(([category, data]) => ({ category, ...data }))
|
|
.sort((a, b) => b.expense - a.expense);
|
|
}
|
|
|
|
/**
|
|
* 计算月度统计
|
|
*/
|
|
export function calculateMonthlyStats(records: UIBill[]): MonthlyStat[] {
|
|
const stats = new Map<string, { expense: number; income: number }>();
|
|
|
|
for (const r of records) {
|
|
const month = r.time.substring(0, 7); // YYYY-MM
|
|
if (!stats.has(month)) {
|
|
stats.set(month, { expense: 0, income: 0 });
|
|
}
|
|
const s = stats.get(month)!;
|
|
const amount = r.amount || 0;
|
|
if (r.incomeExpense === '支出') {
|
|
s.expense += amount;
|
|
} else {
|
|
s.income += amount;
|
|
}
|
|
}
|
|
|
|
return [...stats.entries()]
|
|
.map(([month, data]) => ({ month, ...data }))
|
|
.sort((a, b) => a.month.localeCompare(b.month));
|
|
}
|
|
|
|
/**
|
|
* 计算每日支出数据(用于面积图)
|
|
*/
|
|
export function calculateDailyExpenseData(records: UIBill[]): DailyExpenseData[] {
|
|
const stats = new Map<string, number>();
|
|
|
|
for (const r of records) {
|
|
if (r.incomeExpense !== '支出') continue;
|
|
const date = r.time.substring(0, 10); // YYYY-MM-DD
|
|
const amount = r.amount || 0;
|
|
stats.set(date, (stats.get(date) || 0) + amount);
|
|
}
|
|
|
|
return [...stats.entries()]
|
|
.map(([date, value]) => ({ date: new Date(date), value }))
|
|
.sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
}
|
|
|
|
/**
|
|
* 计算总计统计
|
|
*/
|
|
export function calculateTotalStats(records: UIBill[]): TotalStats {
|
|
return {
|
|
expense: records
|
|
.filter(r => r.incomeExpense === '支出')
|
|
.reduce((sum, r) => sum + (r.amount || 0), 0),
|
|
income: records
|
|
.filter(r => r.incomeExpense === '收入')
|
|
.reduce((sum, r) => sum + (r.amount || 0), 0),
|
|
count: records.length,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 计算百分比
|
|
*/
|
|
export function getPercentage(value: number, total: number): number {
|
|
return total > 0 ? (value / total) * 100 : 0;
|
|
}
|
|
|
|
/**
|
|
* 计算饼状图数据
|
|
*/
|
|
export function calculatePieChartData(
|
|
categoryStats: CategoryStat[],
|
|
totalExpense: number
|
|
): PieChartDataItem[] {
|
|
return categoryStats
|
|
.filter(s => s.expense > 0)
|
|
.map((stat, i) => ({
|
|
category: stat.category,
|
|
value: stat.expense,
|
|
color: pieColors[i % pieColors.length],
|
|
percentage: getPercentage(stat.expense, totalExpense)
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* 获取 Top N 支出记录
|
|
*/
|
|
export function getTopExpenses(records: UIBill[], n: number = 10): UIBill[] {
|
|
return records
|
|
.filter(r => r.incomeExpense === '支出')
|
|
.sort((a, b) => (b.amount || 0) - (a.amount || 0))
|
|
.slice(0, n);
|
|
}
|
|
|
|
/**
|
|
* 统计支出/收入笔数
|
|
*/
|
|
export function countByType(records: UIBill[], type: '支出' | '收入'): number {
|
|
return records.filter(r => r.incomeExpense === type).length;
|
|
}
|
|
|
|
|