feat: Add TokenRecords finance management system

- 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>
This commit is contained in:
你的用户名
2025-08-06 20:09:48 +08:00
parent b93e22c45a
commit 4b4616de1e
193 changed files with 17756 additions and 16 deletions

View File

@@ -0,0 +1,199 @@
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;
}