chore: add frontend/src/utils/exportHelper.ts

This commit is contained in:
2026-04-29 10:53:42 +08:00
parent 1223bcfb23
commit e64622d173
+218
View File
@@ -0,0 +1,218 @@
// 导出工具函数 - 将统计数据导出为 Excel 文件
import * as XLSX from 'xlsx';
import type { TrendStat, MonthlyCompare } from '../types';
/**
* 导出趋势数据到 Excel
* @param data 趋势数据 [{date, income, expense}]
* @param filename 文件名(不含扩展名)
*/
export function exportTrendData(data: TrendStat[], filename: string): void {
if (!data || data.length === 0) {
throw new Error('没有可导出的趋势数据');
}
// 构建工作表数据
const wsData = [
['个人记账 - 收支趋势报表', '', '', ''],
[`导出时间: ${new Date().toLocaleString('zh-CN')}`, '', '', ''],
[], // 空行
['日期', '收入(元)', '支出(元)', '盈余(元)'],
...data.map(d => [
d.date,
d.income,
d.expense,
d.income - d.expense
]),
[], // 空行
// 汇总行
['合计',
data.reduce((sum, d) => sum + d.income, 0),
data.reduce((sum, d) => sum + d.expense, 0),
data.reduce((sum, d) => sum + (d.income - d.expense), 0)
],
];
// 创建工作表
const ws = XLSX.utils.aoa_to_sheet(wsData);
// 合并单元格(标题)
ws['!merges'] = [{ s: { r: 0, c: 0 }, e: { r: 0, c: 3 } }];
// 设置列宽
ws['!cols'] = [{ wch: 14 }, { wch: 14 }, { wch: 14 }, { wch: 14 }];
// 创建工作簿
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, '收支趋势');
// 导出文件
XLSX.writeFile(wb, `${filename}.xlsx`);
}
/**
* 导出月度对比数据
* @param data 月度对比数据
* @param filename 文件名(不含扩展名)
*/
export function exportCompareData(data: MonthlyCompare, filename: string): void {
if (!data) {
throw new Error('没有可导出的月度对比数据');
}
const wsData = [
['个人记账 - 月度对比报表', '', '', '', ''],
[`导出时间: ${new Date().toLocaleString('zh-CN')}`, '', '', '', ''],
[], // 空行
['项目', data.currentMonth.label, '上月环比', data.lastMonth.label, ''],
['收入(元)', data.currentMonth.income, '', data.lastMonth.income, ''],
['支出(元)', data.currentMonth.expense, '', data.lastMonth.expense, ''],
['盈余(元)',
data.currentMonth.income - data.currentMonth.expense,
'',
data.lastMonth.income - data.lastMonth.expense,
''
],
[], // 空行
// 收支占比分析
['支出占比',
data.currentMonth.income > 0
? `${((data.currentMonth.expense / data.currentMonth.income) * 100).toFixed(1)}%`
: '0%',
'',
data.lastMonth.income > 0
? `${((data.lastMonth.expense / data.lastMonth.income) * 100).toFixed(1)}%`
: '0%',
''],
];
const ws = XLSX.utils.aoa_to_sheet(wsData);
ws['!merges'] = [{ s: { r: 0, c: 0 }, e: { r: 0, c: 4 } }];
ws['!cols'] = [{ wch: 12 }, { wch: 14 }, { wch: 12 }, { wch: 14 }, { wch: 12 }];
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, '月度对比');
XLSX.writeFile(wb, `${filename}.xlsx`);
}
/**
* 导出支出构成数据
* @param data 分类数据 [{name, value, color}]
* @param totalExpense 总支出
* @param filename 文件名(不含扩展名)
*/
export function exportCategoryData(
data: { name: string; value: number; color: string }[],
totalExpense: number,
filename: string
): void {
if (!data || data.length === 0) {
throw new Error('没有可导出的支出构成数据');
}
const wsData = [
['个人记账 - 支出构成报表', '', ''],
[`导出时间: ${new Date().toLocaleString('zh-CN')}`, '', ''],
[`总支出: ¥${totalExpense.toLocaleString()}`, '', ''],
[], // 空行
['分类', '金额(元)', '占比'],
...data.map(item => [
item.name,
item.value,
totalExpense > 0 ? `${((item.value / totalExpense) * 100).toFixed(1)}%` : '0%',
]),
[], // 空行
// 按金额排序的排名
['排名', '分类', '金额(元)'],
...data
.sort((a, b) => b.value - a.value)
.map((item, index) => [
index + 1,
item.name,
item.value,
]),
];
const ws = XLSX.utils.aoa_to_sheet(wsData);
ws['!merges'] = [{ s: { r: 0, c: 0 }, e: { r: 0, c: 2 } }];
ws['!cols'] = [{ wch: 10 }, { wch: 14 }, { wch: 10 }];
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, '支出构成');
XLSX.writeFile(wb, `${filename}.xlsx`);
}
/**
* 导出全部统计数据(综合报表)
* @param params 所有统计数据
* @param filename 文件名(不含扩展名)
*/
export function exportAllStats(
params: {
trendData?: TrendStat[];
monthlyCompare?: MonthlyCompare;
categoryData?: { name: string; value: number; color: string }[];
totalExpense?: number;
},
filename: string
): void {
const wb = XLSX.utils.book_new();
// 添加趋势数据工作表
if (params.trendData && params.trendData.length > 0) {
const trendWsData = [
['日期', '收入(元)', '支出(元)', '盈余(元)'],
...params.trendData.map(d => [
d.date,
d.income,
d.expense,
d.income - d.expense
]),
['合计',
params.trendData.reduce((sum, d) => sum + d.income, 0),
params.trendData.reduce((sum, d) => sum + d.expense, 0),
params.trendData.reduce((sum, d) => sum + (d.income - d.expense), 0)
],
];
const ws = XLSX.utils.aoa_to_sheet(trendWsData);
ws['!cols'] = [{ wch: 14 }, { wch: 14 }, { wch: 14 }, { wch: 14 }];
XLSX.utils.book_append_sheet(wb, ws, '收支趋势');
}
// 添加月度对比工作表
if (params.monthlyCompare) {
const { monthlyCompare } = params;
const compareWsData = [
['项目', monthlyCompare.currentMonth.label, monthlyCompare.lastMonth.label],
['收入(元)', monthlyCompare.currentMonth.income, monthlyCompare.lastMonth.income],
['支出(元)', monthlyCompare.currentMonth.expense, monthlyCompare.lastMonth.expense],
['盈余(元)',
monthlyCompare.currentMonth.income - monthlyCompare.currentMonth.expense,
monthlyCompare.lastMonth.income - monthlyCompare.lastMonth.expense
],
];
const ws = XLSX.utils.aoa_to_sheet(compareWsData);
ws['!cols'] = [{ wch: 12 }, { wch: 14 }, { wch: 14 }];
XLSX.utils.book_append_sheet(wb, ws, '月度对比');
}
// 添加支出构成工作表
if (params.categoryData && params.categoryData.length > 0) {
const totalExpense = params.totalExpense || 0;
const categoryWsData = [
['分类', '金额(元)', '占比'],
...params.categoryData.map(item => [
item.name,
item.value,
totalExpense > 0 ? `${((item.value / totalExpense) * 100).toFixed(1)}%` : '0%',
]),
];
const ws = XLSX.utils.aoa_to_sheet(categoryWsData);
ws['!cols'] = [{ wch: 10 }, { wch: 14 }, { wch: 10 }];
XLSX.utils.book_append_sheet(wb, ws, '支出构成');
}
XLSX.writeFile(wb, `${filename}.xlsx`);
}