feat(app): 实现完整的财务管家记账聊天与统计功能

- 新增App主组件,实现聊天、账本、统计三大功能模块切换
- 支持语音及文本输入,自动解析记账信息并生成交易记录
- 交易记录支持编辑、删除、提醒等操作,数据本地持久化
- 管家性格预设和自定义设定,包含头像上传和语气调整
- 会话展示支持关联交易显示,样式区分收入与支出
- 账本页添加日期分组交易列表及饼图统计展示
- 消息列表实现渐变背景和交互动画,提升用户体验
- 支持图片上传并同步发送给AI进行解析记账
- 底部导航实现三标签切换并同步显示对应内容
- 全局事务编辑弹窗支持直接调用和删除操作
- 增加环境变量示例和.gitignore规则,完善工程配置
This commit is contained in:
2026-01-19 20:20:54 +08:00
commit c06e74bd01
24 changed files with 7081 additions and 0 deletions

View File

@@ -0,0 +1,907 @@
# 数据持久化接口文档
## 概述
本文档详细列出了智能账本应用中所有需要持久化数据的接口,包括存储键名、数据结构、操作方法和使用场景。应用采用浏览器 `localStorage` 作为持久化存储方案。
---
## 1. 交易记录持久化
### 存储键名
```typescript
'cui_transactions'
```
### 数据类型
```typescript
Transaction[]
```
### 数据结构
```typescript
interface Transaction {
id: string; // 唯一标识符 (UUID)
item: string; // 交易项目名称
amount: number; // 交易金额
type: TransactionType; // 交易类型: 'expense' | 'income'
category: string; // 交易分类 (来自 ExpenseCategory 或 IncomeCategory)
date: string; // 日期 (ISO格式 YYYY-MM-DD)
timestamp: number; // 时间戳 (毫秒)
}
```
### 支持的分类
#### 支出分类 (ExpenseCategory)
- 餐饮、购物、交通、住房、娱乐、医疗
- 人情、学习、金融、孩子、宠物、汽车
- 美容、经营、杂项
#### 收入分类 (IncomeCategory)
- 工资、奖金、理财、兼职、红包、其他收入
### 操作接口
#### 读取
```typescript
// 位置: App.tsx L102
const storedTransactions = localStorage.getItem('cui_transactions');
if (storedTransactions) setTransactions(JSON.parse(storedTransactions));
```
#### 写入
```typescript
// 位置: App.tsx L129
useEffect(() => {
localStorage.setItem('cui_transactions', JSON.stringify(transactions));
}, [transactions]);
```
#### 新增交易
```typescript
// 位置: App.tsx L192-204
const newTransaction: Transaction = {
id: uuidv4(),
item: result.transaction.item,
amount: result.transaction.amount,
type: result.transaction.type,
category: result.transaction.category,
date: getLocalDateString(),
timestamp: Date.now()
};
setTransactions(prev => [...prev, newTransaction]);
```
#### 更新交易
```typescript
// 位置: App.tsx L144-148
const handleUpdateTransaction = (updated: Transaction) => {
setTransactions(prev => prev.map(t => t.id === updated.id ? updated : t));
};
```
#### 删除交易
```typescript
// 位置: App.tsx L150-156
const handleDeleteTransaction = (id: string) => {
if (confirm('确定要删除这条记录吗?')) {
setTransactions(prev => prev.filter(t => t.id !== id));
}
};
```
### 使用场景
- 聊天界面语音/文字输入记账
- 图片识别账单自动记账
- 账本页面手动编辑交易
- 统计页面数据查询和分析
- 日历视图显示每日交易
---
## 2. 聊天消息持久化
### 存储键名
```typescript
'cui_messages'
```
### 数据类型
```typescript
Message[]
```
### 数据结构
```typescript
interface Message {
id: string; // 消息唯一标识符 (UUID)
role: 'user' | 'assistant'; // 消息角色
content: string; // 消息内容
timestamp: number; // 时间戳 (毫秒)
relatedTransactionId?: string; // 关联的交易ID (可选)
}
```
### 操作接口
#### 读取
```typescript
// 位置: App.tsx L103
const storedMessages = localStorage.getItem('cui_messages');
if (storedMessages) setMessages(JSON.parse(storedMessages));
```
#### 写入
```typescript
// 位置: App.tsx L135-138
useEffect(() => {
localStorage.setItem('cui_messages', JSON.stringify(messages));
if (activeTab === 'chat') scrollToBottom();
}, [messages, activeTab]);
```
#### 初始化欢迎消息
```typescript
// 位置: App.tsx L119-126
if (!storedMessages) {
setMessages([{
id: 'init',
role: 'assistant',
content: `我是${aiSettings.name}。✨\n想记账?快说!🎤`,
timestamp: Date.now()
}]);
}
```
#### 新增用户消息
```typescript
// 位置: App.tsx L179-185
const userMsg: Message = {
id: uuidv4(),
role: 'user',
content: imageBase64 ? '已上传图片' : text,
timestamp: Date.now()
};
setMessages(prev => [...prev, userMsg]);
```
#### 新增助手消息(含交易回执)
```typescript
// 位置: App.tsx L206-212
setMessages(prev => [...prev, {
id: uuidv4(),
role: 'assistant',
content: 'RECEIPT',
timestamp: Date.now(),
relatedTransactionId: newTransactionId
}]);
```
### 使用场景
- 聊天界面显示对话历史
- 显示交易回执卡片
- 从聊天界面编辑/删除关联交易
- 保留用户与AI的完整对话记录
---
## 3. 预算数据持久化
### 存储键名
```typescript
'cui_budget'
```
### 数据类型
```typescript
Record<string, number>
```
### 数据结构
```typescript
// 键值对形式,支持多预算项
{
'总预算': 3000,
'餐饮预算': 1000,
'购物预算': 500,
// ... 更多预算项
}
```
### 操作接口
#### 读取(兼容旧版)
```typescript
// 位置: App.tsx L104, L111-114
const storedBudget = localStorage.getItem('cui_budget');
if (storedBudget) {
const parsed = JSON.parse(storedBudget);
setBudgets(typeof parsed === 'number' ? { '总预算': parsed } : parsed);
}
```
#### 写入
```typescript
// 位置: App.tsx L130
useEffect(() => {
localStorage.setItem('cui_budget', JSON.stringify(budgets));
}, [budgets]);
```
#### 更新预算
```typescript
// 通过 setBudgets 更新
setBudgets({ ...budgets, '总预算': 5000 });
```
### 使用场景
- 账本页面显示月度预算对比
- 预算超支提醒
- 多项预算管理(未来扩展)
### 兼容性说明
- 支持旧版单一预算值(纯数字)
- 新版支持多预算项(对象格式)
- 自动兼容转换
---
## 4. 账单提醒持久化
### 存储键名
```typescript
'cui_reminders'
```
### 数据类型
```typescript
BillReminder[]
```
### 数据结构
```typescript
interface BillReminder {
id: string; // 唯一标识符 (UUID)
title: string; // 提醒标题
amount?: number; // 金额 (可选)
frequency: 'monthly' | 'once'; // 频率: 每月 | 单次
targetDate: string; // 目标日期: 'DD' (每月) 或 'YYYY-MM-DD' (单次)
isCompleted: boolean; // 完成状态
}
```
### 操作接口
#### 读取
```typescript
// 位置: App.tsx L105
const storedReminders = localStorage.getItem('cui_reminders');
if (storedReminders) setReminders(JSON.parse(storedReminders));
```
#### 写入
```typescript
// 位置: App.tsx L131
useEffect(() => {
localStorage.setItem('cui_reminders', JSON.stringify(reminders));
}, [reminders]);
```
#### 新增提醒
```typescript
// 位置: App.tsx L158-160
const handleAddReminder = (reminder: BillReminder) => {
setReminders(prev => [...prev, reminder]);
};
```
#### 更新提醒
```typescript
// 位置: App.tsx L162-164
const handleUpdateReminder = (updated: BillReminder) => {
setReminders(prev => prev.map(r => r.id === updated.id ? updated : r));
};
```
#### 删除提醒
```typescript
// 位置: App.tsx L166-170
const handleDeleteReminder = (id: string) => {
if (confirm('确定要删除这个提醒吗?')) {
setReminders(prev => prev.filter(r => r.id !== id));
}
};
```
#### 切换完成状态
```typescript
// 位置: App.tsx L172-174
const handleToggleReminder = (id: string) => {
setReminders(prev => prev.map(r =>
r.id === id ? { ...r, isCompleted: !r.isCompleted } : r
));
};
```
### 剩余天数计算
```typescript
// 位置: BillReminders.tsx L14-38
export const getDaysRemaining = (r: BillReminder): number => {
const today = new Date();
today.setHours(0, 0, 0, 0);
let targetDate = new Date();
targetDate.setHours(0, 0, 0, 0);
if (r.frequency === 'monthly') {
const day = parseInt(r.targetDate);
targetDate.setDate(day);
// 如果本月已过期,移至下月
if (targetDate < today) {
targetDate.setMonth(targetDate.getMonth() + 1);
}
} else {
// 单次提醒
targetDate = new Date(r.targetDate);
targetDate.setHours(0, 0, 0, 0);
}
const diffTime = targetDate.getTime() - today.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
};
```
### 使用场景
- 月度账单还款提醒(房贷、信用卡等)
- 一次性待办事项
- 日历视图显示提醒日期
- 账本页面待办列表
- 逾期提醒和倒计时显示
---
## 5. AI助手设置持久化
### 存储键名
```typescript
'cui_ai_settings'
```
### 数据类型
```typescript
AISettings
```
### 数据结构
```typescript
interface AISettings {
name: string; // AI助手名称
persona: string; // AI性格描述
avatar?: string; // 头像 (Base64编码图片,可选)
}
```
### 默认值
```typescript
// 位置: App.tsx L18-21
const DEFAULT_AI_SETTINGS: AISettings = {
name: "崔经理",
persona: "一个精明、毒舌但实际上很负责任的财务经理。说话喜欢带刺,吐槽用户乱花钱,但关键时刻会给出靠谱建议。"
};
```
### 操作接口
#### 读取
```typescript
// 位置: App.tsx L106
const storedAiSettings = localStorage.getItem('cui_ai_settings');
if (storedAiSettings) setAiSettings(JSON.parse(storedAiSettings));
```
#### 写入
```typescript
// 位置: App.tsx L132
useEffect(() => {
localStorage.setItem('cui_ai_settings', JSON.stringify(aiSettings));
}, [aiSettings]);
```
#### 更新设置
```typescript
// 修改名称
setAiSettings({ ...aiSettings, name: '新名字' });
// 修改性格
setAiSettings({ ...aiSettings, persona: '新的性格描述' });
// 更新头像
setAiSettings(prev => ({ ...prev, avatar: base64Data }));
```
### 预设性格列表
```typescript
// 位置: App.tsx L23-44
[
{
id: 'toxic',
name: '崔经理 (默认)',
description: '精明、毒舌、傲娇。喜欢吐槽你乱花钱...'
},
{
id: 'gentle',
name: '温柔管家',
description: '温柔、体贴、治愈。无论你怎么花钱都会温柔记录...'
},
{
id: 'strict',
name: '严厉会计',
description: '一丝不苟、冷酷无情。只关心数字...'
},
{
id: 'custom',
name: '自定义',
description: '可以根据你喜欢的方式设定助手的语气和风格'
}
]
```
### 使用场景
- 聊天界面显示AI名称和头像
- AI回复风格控制
- Gemini API调用时的persona参数
- 设置模态框显示和编辑
---
## 6. 自定义性格预设持久化
### 存储键名
```typescript
'cui_custom_preset_info'
```
### 数据类型
```typescript
{ name: string; description: string }
```
### 数据结构
```typescript
{
name: '自定义', // 自定义预设名称
description: '可以根据你喜欢的方式设定助手的语气和风格' // 描述
}
```
### 操作接口
#### 读取
```typescript
// 位置: App.tsx L107
const storedCustom = localStorage.getItem('cui_custom_preset_info');
if (storedCustom) setCustomPreset(JSON.parse(storedCustom));
```
#### 写入
```typescript
// 位置: App.tsx L133
useEffect(() => {
localStorage.setItem('cui_custom_preset_info', JSON.stringify(customPreset));
}, [customPreset]);
```
#### 自动保存自定义预设
```typescript
// 位置: App.tsx L286-292
const handleSaveSettings = () => {
const currentPreset = INITIAL_PERSONA_PRESETS.find(
p => p.description === aiSettings.persona && p.id !== 'custom'
);
if (!currentPreset) {
setCustomPreset({ name: aiSettings.name, description: aiSettings.persona });
}
setIsSettingsOpen(false);
};
```
### 使用场景
- 保存用户自定义的AI性格设置
- 性格预设列表中显示自定义预设
- 区分预设性格和自定义性格
---
## 7. 主题设置持久化
### 存储键名
```typescript
'cui_card_theme'
```
### 数据类型
```typescript
string (数字索引)
```
### 数据值
```typescript
'0' | '1' | '2' | '3' | '4' // 对应5个主题的索引
```
### 主题列表
```typescript
// 位置: LedgerPage.tsx L20-26
const CARD_THEMES = [
{ id: 'dark-indigo', class: 'from-gray-900 via-indigo-950 to-black', themeColor: 'bg-indigo-600', name: '暗夜霓虹' },
{ id: 'sunset', class: 'from-orange-500 via-rose-600 to-pink-700', themeColor: 'bg-rose-600', name: '落日熔金' },
{ id: 'emerald', class: 'from-emerald-600 via-teal-700 to-cyan-900', themeColor: 'bg-emerald-600', name: '薄荷森林' },
{ id: 'royal', class: 'from-blue-700 via-indigo-800 to-indigo-950', themeColor: 'bg-blue-700', name: '皇家礼赞' },
{ id: 'rose', class: 'from-rose-500 via-pink-600 to-purple-800', themeColor: 'bg-purple-600', name: '浪漫巴黎' },
];
```
### 操作接口
#### 读取
```typescript
// 位置: LedgerPage.tsx L41
const [themeIndex, setThemeIndex] = useState(() =>
Number(localStorage.getItem('cui_card_theme')) || 0
);
```
#### 写入
```typescript
// 位置: LedgerPage.tsx L43-45
useEffect(() => {
localStorage.setItem('cui_card_theme', themeIndex.toString());
}, [themeIndex]);
```
#### 切换主题
```typescript
// 位置: LedgerPage.tsx L56
const cycleTheme = () => setThemeIndex((prev) => (prev + 1) % CARD_THEMES.length);
```
### 使用场景
- 账本页面顶部卡片主题
- 标签页按钮颜色主题
- 用户个性化界面体验
---
## 8. AI服务接口(非持久化,但涉及数据处理)
### Gemini API 调用接口
#### 服务文件
```typescript
services/geminiService.ts
```
#### 处理用户消息
```typescript
// 位置: geminiService.ts L67-115
export const processUserMessage = async (
message: string,
aiName: string,
aiPersona: string,
imageBase64?: string,
mimeType?: string
): Promise<ParseResult>
```
#### 返回结果
```typescript
// 位置: geminiService.ts L57-65
interface ParseResult {
transaction: {
item: string;
amount: number;
type: TransactionType;
category: string;
} | null;
reply: string;
}
```
#### API模型配置
```typescript
// 位置: geminiService.ts L4-7
const ai = new GoogleGenAI({ apiKey: process.env.API_KEY });
const MODEL_NAME = "gemini-3-flash-preview";
```
#### 系统指令模板
```typescript
// 位置: geminiService.ts L9-29
const getSystemInstruction = (aiName: string, aiPersona: string) => `
你现在的身份是"${aiName}"。
你的性格设定是:${aiPersona}
你的任务是:
1. 从用户的输入中提取交易信息(内容、金额、类别、交易类型)。
2. 判断是【支出】(expense) 还是【收入】(income)。
3. **严格回复规范**:
- 回复必须**极度简练**。
- 每一段话的字数必须控制在 **30字左右**(越短越好)。
- 全部的回复**最多只能分成 2 段**(中间用一个 \n 换行符隔开)。
- 必须在每一段中都加入**生动的表情包 (Emoji)** 🌈。
- 说话风格要完全符合"${aiName}"的设定。
`;
```
### 使用场景
- 语音/文字输入转交易记录
- 图片识别账单信息
- AI对话回复生成
- 自动分类和金额提取
---
## 数据流架构图
```mermaid
graph TB
subgraph 浏览器存储层
LS[localStorage]
LS --> T[cui_transactions]
LS --> M[cui_messages]
LS --> B[cui_budget]
LS --> R[cui_reminders]
LS --> AS[cui_ai_settings]
LS --> CP[cui_custom_preset_info]
LS --> TH[cui_card_theme]
end
subgraph React状态层
ST[State Management]
ST --> transactions[transactions]
ST --> messages[messages]
ST --> budgets[budgets]
ST --> reminders[reminders]
ST --> aiSettings[aiSettings]
ST --> customPreset[customPreset]
ST --> themeIndex[themeIndex]
end
subgraph 组件层
APP[App.tsx]
LEDGER[LedgerPage.tsx]
EDIT[EditTransactionModal.tsx]
REMIND[BillReminders.tsx]
CAL[CalendarView.tsx]
STATS[StatisticsPage.tsx]
end
subgraph 外部服务
GEMINI[Gemini API]
end
T <--> transactions
M <--> messages
B <--> budgets
R <--> reminders
AS <--> aiSettings
CP <--> customPreset
TH <--> themeIndex
transactions --> APP
transactions --> LEDGER
transactions --> STATS
transactions --> CAL
messages --> APP
reminders --> LEDGER
reminders --> REMIND
reminders --> CAL
APP --> GEMINI
GEMINI --> APP
APP --> EDIT
LEDGER --> EDIT
```
---
## 数据大小估算
### 单条记录大小
- **Transaction**: ~150 bytes
- **Message**: ~100 bytes
- **BillReminder**: ~120 bytes
- **AISettings**: ~200 bytes (不含头像) / ~5KB (含头像)
### 容量预估(localStorage限制 5-10MB)
- **1000条交易**: ~150KB
- **500条消息**: ~50KB
- **50个提醒**: ~6KB
- **AI设置**: ~5KB (含头像)
**总计**: 约 211KB (不含聊天历史图片)
### 容量优化建议
1. 定期归档历史数据(超过1年)
2. 限制聊天历史记录数量(如保留最近500条)
3. 图片压缩后再转Base64
4. 实现数据导出/导入功能
---
## 错误处理与兼容性
### JSON解析容错
```typescript
try {
const data = JSON.parse(localStorage.getItem('cui_transactions'));
setTransactions(data);
} catch (error) {
console.error('数据解析失败', error);
setTransactions([]); // 回退到默认值
}
```
### 版本兼容
- **预算数据**: 自动兼容旧版纯数字格式
- **新增字段**: 使用可选字段(如 BillReminder.amount?)
- **默认值**: 所有状态都有合理的初始值
### 数据校验建议
```typescript
// 类型守卫示例
function isValidTransaction(obj: any): obj is Transaction {
return (
typeof obj.id === 'string' &&
typeof obj.item === 'string' &&
typeof obj.amount === 'number' &&
['expense', 'income'].includes(obj.type) &&
typeof obj.date === 'string'
);
}
```
---
## 数据备份与恢复
### 导出数据(建议实现)
```typescript
function exportAllData() {
const data = {
transactions: JSON.parse(localStorage.getItem('cui_transactions') || '[]'),
messages: JSON.parse(localStorage.getItem('cui_messages') || '[]'),
budgets: JSON.parse(localStorage.getItem('cui_budget') || '{}'),
reminders: JSON.parse(localStorage.getItem('cui_reminders') || '[]'),
aiSettings: JSON.parse(localStorage.getItem('cui_ai_settings') || '{}'),
customPreset: JSON.parse(localStorage.getItem('cui_custom_preset_info') || '{}'),
themeIndex: localStorage.getItem('cui_card_theme') || '0',
exportDate: new Date().toISOString()
};
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `账本数据备份_${new Date().toISOString().slice(0,10)}.json`;
a.click();
}
```
### 导入数据(建议实现)
```typescript
function importAllData(file: File) {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target?.result as string);
localStorage.setItem('cui_transactions', JSON.stringify(data.transactions));
localStorage.setItem('cui_messages', JSON.stringify(data.messages));
localStorage.setItem('cui_budget', JSON.stringify(data.budgets));
localStorage.setItem('cui_reminders', JSON.stringify(data.reminders));
localStorage.setItem('cui_ai_settings', JSON.stringify(data.aiSettings));
localStorage.setItem('cui_custom_preset_info', JSON.stringify(data.customPreset));
localStorage.setItem('cui_card_theme', data.themeIndex);
alert('数据导入成功!请刷新页面');
window.location.reload();
} catch (error) {
alert('数据导入失败:' + error.message);
}
};
reader.readAsText(file);
}
```
---
## 安全性考虑
### 敏感数据
- **交易记录**: 包含个人财务信息
- **AI头像**: Base64图片数据
- **聊天记录**: 可能包含敏感对话
### 安全建议
1. **不存储**: Gemini API Key(使用环境变量)
2. **加密存储**: 考虑对敏感数据加密(如使用Web Crypto API)
3. **定期清理**: 提供清除所有数据的功能
4. **隐私模式**: 提示用户不要在公共设备上使用
### 清除数据示例
```typescript
function clearAllData() {
if (confirm('确定要清除所有数据吗?此操作不可恢复!')) {
localStorage.removeItem('cui_transactions');
localStorage.removeItem('cui_messages');
localStorage.removeItem('cui_budget');
localStorage.removeItem('cui_reminders');
localStorage.removeItem('cui_ai_settings');
localStorage.removeItem('cui_custom_preset_info');
localStorage.removeItem('cui_card_theme');
window.location.reload();
}
}
```
---
## 性能优化建议
### 1. 批量写入优化
```typescript
// 避免频繁写入,使用防抖
import { debounce } from 'lodash';
const debouncedSave = debounce((key: string, data: any) => {
localStorage.setItem(key, JSON.stringify(data));
}, 500);
```
### 2. 数据分页加载
```typescript
// 仅加载当前月份数据到UI
const currentMonthTransactions = transactions.filter(
t => t.date.startsWith(selectedMonth)
);
```
### 3. 索引优化
```typescript
// 创建日期索引,加速查询
const transactionsByDate = useMemo(() => {
return transactions.reduce((acc, t) => {
if (!acc[t.date]) acc[t.date] = [];
acc[t.date].push(t);
return acc;
}, {} as Record<string, Transaction[]>);
}, [transactions]);
```
---
## 总结
本文档详细列出了应用中所有7个持久化数据接口:
1. ✅ **交易记录** (cui_transactions) - 核心财务数据
2. ✅ **聊天消息** (cui_messages) - AI对话历史
3. ✅ **预算数据** (cui_budget) - 预算管理
4. ✅ **账单提醒** (cui_reminders) - 待办事项
5. ✅ **AI设置** (cui_ai_settings) - AI助手配置
6. ✅ **自定义预设** (cui_custom_preset_info) - 性格预设
7. ✅ **主题设置** (cui_card_theme) - 界面主题
所有接口均采用 **localStorage** 存储,使用 **JSON** 序列化,并通过 **React useEffect** 实现自动持久化。
---
**文档版本**: v1.0
**生成时间**: 2026-01-19
**最后更新**: 2026-01-19
**维护者**: 开发团队