- Created new finance application based on Vue Vben Admin - Implemented transaction management, category management, and loan tracking - Added person management for tracking financial relationships - Integrated budget management and financial analytics - Added data import/export functionality - Implemented responsive design for mobile support - Added comprehensive testing with Playwright 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
199 lines
4.9 KiB
TypeScript
199 lines
4.9 KiB
TypeScript
import type { Transaction, Category, Person } from '#/types/finance';
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
/**
|
|
* 导出数据为CSV格式
|
|
*/
|
|
export function exportToCSV(data: any[], filename: string) {
|
|
if (data.length === 0) {
|
|
return;
|
|
}
|
|
|
|
// 获取所有列名
|
|
const headers = Object.keys(data[0]);
|
|
|
|
// 创建CSV内容
|
|
let csvContent = '\uFEFF'; // UTF-8 BOM
|
|
|
|
// 添加表头
|
|
csvContent += headers.join(',') + '\n';
|
|
|
|
// 添加数据行
|
|
data.forEach(row => {
|
|
const values = headers.map(header => {
|
|
const value = row[header];
|
|
// 处理包含逗号或换行符的值
|
|
if (typeof value === 'string' && (value.includes(',') || value.includes('\n'))) {
|
|
return `"${value.replace(/"/g, '""')}"`;
|
|
}
|
|
return value ?? '';
|
|
});
|
|
csvContent += values.join(',') + '\n';
|
|
});
|
|
|
|
// 创建Blob并下载
|
|
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
const link = document.createElement('a');
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
link.setAttribute('href', url);
|
|
link.setAttribute('download', `${filename}_${dayjs().format('YYYYMMDD_HHmmss')}.csv`);
|
|
link.style.visibility = 'hidden';
|
|
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
}
|
|
|
|
/**
|
|
* 导出交易数据
|
|
*/
|
|
export function exportTransactions(
|
|
transactions: Transaction[],
|
|
categories: Category[],
|
|
persons: Person[]
|
|
) {
|
|
// 创建分类和人员的映射
|
|
const categoryMap = new Map(categories.map(c => [c.id, c.name]));
|
|
const personMap = new Map(persons.map(p => [p.id, p.name]));
|
|
|
|
// 转换交易数据为导出格式
|
|
const exportData = transactions.map(t => ({
|
|
日期: t.date,
|
|
类型: t.type === 'income' ? '收入' : '支出',
|
|
分类: categoryMap.get(t.categoryId) || '',
|
|
金额: t.amount,
|
|
货币: t.currency,
|
|
项目: t.project || '',
|
|
付款人: t.payer || '',
|
|
收款人: t.payee || '',
|
|
数量: t.quantity,
|
|
单价: t.quantity > 1 ? (t.amount / t.quantity).toFixed(2) : t.amount,
|
|
状态: t.status === 'completed' ? '已完成' : t.status === 'pending' ? '待处理' : '已取消',
|
|
描述: t.description || '',
|
|
记录人: t.recorder || '',
|
|
创建时间: t.created_at,
|
|
更新时间: t.updated_at
|
|
}));
|
|
|
|
exportToCSV(exportData, '交易记录');
|
|
}
|
|
|
|
/**
|
|
* 导出数据为JSON格式
|
|
*/
|
|
export function exportToJSON(data: any, filename: string) {
|
|
const jsonContent = JSON.stringify(data, null, 2);
|
|
|
|
const blob = new Blob([jsonContent], { type: 'application/json;charset=utf-8;' });
|
|
const link = document.createElement('a');
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
link.setAttribute('href', url);
|
|
link.setAttribute('download', `${filename}_${dayjs().format('YYYYMMDD_HHmmss')}.json`);
|
|
link.style.visibility = 'hidden';
|
|
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
}
|
|
|
|
/**
|
|
* 生成导入模板
|
|
*/
|
|
export function generateImportTemplate() {
|
|
const template = [
|
|
{
|
|
date: '2025-08-05',
|
|
type: 'expense',
|
|
category: '餐饮',
|
|
amount: 100.00,
|
|
currency: 'CNY',
|
|
description: '午餐',
|
|
project: '项目名称',
|
|
payer: '付款人',
|
|
payee: '收款人',
|
|
status: 'completed',
|
|
tags: '标签1,标签2',
|
|
},
|
|
{
|
|
date: '2025-08-05',
|
|
type: 'income',
|
|
category: '工资',
|
|
amount: 5000.00,
|
|
currency: 'CNY',
|
|
description: '月薪',
|
|
project: '',
|
|
payer: '公司',
|
|
payee: '自己',
|
|
status: 'completed',
|
|
tags: '',
|
|
},
|
|
];
|
|
|
|
exportToCSV(template, 'transaction_import_template');
|
|
}
|
|
|
|
/**
|
|
* 导出所有数据(完整备份)
|
|
*/
|
|
export function exportAllData(
|
|
transactions: Transaction[],
|
|
categories: Category[],
|
|
persons: Person[]
|
|
) {
|
|
const exportData = {
|
|
version: '1.0',
|
|
exportDate: dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
|
data: {
|
|
transactions,
|
|
categories,
|
|
persons
|
|
}
|
|
};
|
|
|
|
exportToJSON(exportData, '财务数据备份');
|
|
}
|
|
|
|
/**
|
|
* 解析CSV文件
|
|
*/
|
|
export function parseCSV(text: string): Record<string, any>[] {
|
|
const lines = text.split('\n').filter(line => line.trim());
|
|
if (lines.length === 0) return [];
|
|
|
|
// 解析表头
|
|
const headers = lines[0].split(',').map(h => h.trim());
|
|
|
|
// 解析数据行
|
|
const data = [];
|
|
for (let i = 1; i < lines.length; i++) {
|
|
const values = [];
|
|
let current = '';
|
|
let inQuotes = false;
|
|
|
|
for (let j = 0; j < lines[i].length; j++) {
|
|
const char = lines[i][j];
|
|
|
|
if (char === '"') {
|
|
inQuotes = !inQuotes;
|
|
} else if (char === ',' && !inQuotes) {
|
|
values.push(current.trim());
|
|
current = '';
|
|
} else {
|
|
current += char;
|
|
}
|
|
}
|
|
values.push(current.trim());
|
|
|
|
// 创建对象
|
|
const row: Record<string, any> = {};
|
|
headers.forEach((header, index) => {
|
|
row[header] = values[index] || '';
|
|
});
|
|
data.push(row);
|
|
}
|
|
|
|
return data;
|
|
} |