# 数据持久化接口文档 ## 概述 本文档详细列出了智能账本应用中所有需要持久化数据的接口,包括存储键名、数据结构、操作方法和使用场景。应用采用浏览器 `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 ``` ### 数据结构 ```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 ``` #### 返回结果 ```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); }, [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 **维护者**: 开发团队