Files
accounting/项目计划书.md

870 lines
40 KiB
Markdown
Raw Permalink Normal View History

2026-03-26 01:23:19 +08:00
# 记账助手 - 项目计划书
> 个人记账 Web 应用,用于日常收支记录、分类管理、统计分析和余额追踪。
---
## 一、项目概述
### 1.1 项目信息
| 项 目 | 说 明 |
|-------|-------|
| 项目名称 | 记账助手 |
| 项目类型 | 个人记账 Web 应用 |
| 架构模式 | 前后端分离SPA + REST API |
| 部署方式 | 生产环境后端 serve 前端静态文件,单端口部署 |
### 1.2 技术栈
| 层 | 技术 | 版本 |
|----|------|------|
| **前端框架** | Vue 3 (Composition API + `<script setup>`) | 3.5.12 |
| **构建工具** | Vite | 5.4.11 |
| **UI 组件库** | Element Plus (中文 locale) | 2.8.8 |
| **状态管理** | Pinia | 2.2.6 |
| **路由** | Vue Router 4 (History 模式) | 4.4.5 |
| **HTTP 客户端** | Axios | 1.7.7 |
| **图表库** | ECharts | 5.5.1 |
| **后端框架** | Express | 4.21.0 |
| **数据库** | sql.js内存 SQLite文件持久化 | 1.11.0 |
### 1.3 项目目录结构
```
记账/
├── package.json # 根 monorepo 脚本 (concurrently 并行启动前后端)
├── client/ # 前端工程
│ ├── index.html # SPA 入口 (lang="zh-CN")
│ ├── vite.config.js # 开发代理 + 手动 chunk 分包
│ ├── package.json
│ └── src/
│ ├── main.js # 应用初始化 (Vue + Router + Pinia + ElementPlus)
│ ├── App.vue # 根组件 (Navbar + <router-view>)
│ ├── api/index.js # Axios 封装 + 全部 API 定义
│ ├── router/index.js # 路由配置 (3 个页面)
│ ├── stores/ # Pinia 状态管理
│ │ ├── records.js # 记录 CRUD + 分类列表
│ │ └── statistics.js # 统计数据 + 余额
│ ├── views/ # 页面组件
│ │ ├── Dashboard.vue # 总览页
│ │ ├── DailyInput.vue# 记账页
│ │ └── Statistics.vue# 统计页
│ ├── components/ # 可复用组件
│ │ ├── Navbar.vue
│ │ ├── BalanceCard.vue
│ │ ├── SummaryCards.vue
│ │ ├── RecordForm.vue
│ │ ├── RecordTable.vue
│ │ ├── TrendChart.vue
│ │ └── CategoryPieChart.vue
│ └── styles/
│ └── global.css # 全局样式 + 颜色变量
└── server/ # 后端工程
├── index.js # Express 入口 + 中间件 + 静态文件
├── db.js # sql.js 初始化 + 建表 + DbWrapper
├── package.json
├── routes/
│ ├── records.js # CRUD /api/records
│ ├── categories.js # CRUD /api/categories
│ ├── statistics.js # 多维度统计 /api/statistics/*
│ └── balance.js # 余额管理 /api/balance
└── data/
└── accounting.db # SQLite 数据库文件 (运行时生成)
```
---
## 二、数据库设计
### 2.1 ER 关系图
```
┌─────────────────────┐ ┌─────────────────────────────┐
│ balance_settings │ │ categories │
├─────────────────────┤ ├─────────────────────────────┤
│ id INTEGER │ │ id INTEGER PK AI │
│ (固定=1, 单行表) │ │ name TEXT NOT NULL │
│ initial_balance REAL│ │ UNIQUE │
│ updated_at DATETIME │ │ type TEXT NOT NULL │
└─────────────────────┘ │ DEFAULT 'expense'│
│ icon TEXT │
│ sort_order INTEGER DEF 0 │
│ created_at DATETIME │
└──────────┬──────────────────┘
│ 1:N (category_id FK)
┌──────────┴──────────────────┐
│ records │
├─────────────────────────────┤
│ id INTEGER PK AI │
│ date TEXT NOT NULL │
│ type TEXT NOT NULL │
│ category_id INTEGER FK │
│ amount REAL NOT NULL │
│ DEFAULT 0 │
│ note TEXT │
│ created_at DATETIME │
│ updated_at DATETIME │
└─────────────────────────────┘
```
### 2.2 表结构详细说明
#### 表 1categories分类表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INTEGER | PRIMARY KEY AUTOINCREMENT | 主键 |
| `name` | TEXT | NOT NULL, UNIQUE | 分类名称,全局唯一 |
| `type` | TEXT | NOT NULL, DEFAULT 'expense' | 类型:`income`(收入)/ `expense`(支出) |
| `icon` | TEXT | 可空 | Element Plus 图标名称 |
| `sort_order` | INTEGER | DEFAULT 0 | 排序权重(升序排列) |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
#### 表 2records记录表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INTEGER | PRIMARY KEY AUTOINCREMENT | 主键 |
| `date` | TEXT | NOT NULL | 记录日期,格式 `YYYY-MM-DD` |
| `type` | TEXT | NOT NULL | 类型:`income` / `expense` |
| `category_id` | INTEGER | NOT NULL, FOREIGN KEY → categories.id | 关联分类 |
| `amount` | REAL | NOT NULL, DEFAULT 0 | 金额(非负数) |
| `note` | TEXT | 可空 | 备注 |
| `created_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 创建时间 |
| `updated_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 更新时间 |
**索引:**
- `idx_records_date``records(date)` — 按日期查询加速
- `idx_records_type``records(type)` — 按类型过滤加速
#### 表 3balance_settings余额设置表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| `id` | INTEGER | PRIMARY KEY, 固定值 1 | 单行表,只有一条记录 |
| `initial_balance` | REAL | NOT NULL, DEFAULT 0 | 用户设置的初始余额 |
| `updated_at` | DATETIME | DEFAULT CURRENT_TIMESTAMP | 更新时间 |
**余额计算公式:**
```
当前余额 = initial_balance + SUM(所有收入) - SUM(所有支出)
```
### 2.3 预置种子数据15 个默认分类)
| sort_order | name | type | icon |
|-----------|------|------|------|
| 1 | 工资 | income | Money |
| 2 | 利息 | income | Coin |
| 3 | 其他收入 | income | Plus |
| 10 | 餐饮 | expense | Bowl |
| 11 | 交通 | expense | Van |
| 12 | 购物 | expense | ShoppingCart |
| 13 | 话费 | expense | Phone |
| 14 | 水电费 | expense | Lightning |
| 15 | 物业费 | expense | House |
| 16 | 云服务 | expense | Monitor |
| 17 | 火车票 | expense | Ticket |
| 18 | 机票 | expense | Position |
| 19 | 公益 | expense | Star |
| 20 | 还款 | expense | CreditCard |
| 99 | 其他 | expense | More |
---
## 三、API 接口设计
### 3.1 接口总览
| 模块 | 方法 | 路径 | 功能 |
|------|------|------|------|
| **记录** | GET | `/api/records` | 查询记录(支持按日期/月份/分页) |
| | POST | `/api/records` | 创建记录 |
| | PUT | `/api/records/:id` | 更新记录(部分更新) |
| | DELETE | `/api/records/:id` | 删除记录 |
| **分类** | GET | `/api/categories` | 查询分类(可按类型过滤) |
| | POST | `/api/categories` | 创建分类 |
| | PUT | `/api/categories/:id` | 更新分类 |
| | DELETE | `/api/categories/:id` | 删除分类(有关联记录时禁止) |
| **统计** | GET | `/api/statistics/daily` | 日统计 |
| | GET | `/api/statistics/weekly` | 周统计(周一至周日) |
| | GET | `/api/statistics/monthly` | 月统计 |
| | GET | `/api/statistics/yearly` | 年统计 |
| | GET | `/api/statistics/balance` | 余额查询 |
| | GET | `/api/statistics/trend` | 趋势数据(日期范围) |
| **余额** | GET | `/api/balance` | 获取余额信息 |
| | PUT | `/api/balance` | 设置初始余额 |
### 3.2 记录接口详细设计
#### GET /api/records — 查询记录
```
Query Parameters:
date (可选) YYYY-MM-DD — 精确日期查询
month (可选) YYYY-MM — 按月份查询
page (可选) Integer — 分页页码
limit (可选) Integer — 每页条数,默认 50
Response 200:
[
{
"id": 1,
"date": "2026-03-08",
"type": "expense",
"category_id": 4,
"amount": 35.50,
"note": "午餐",
"created_at": "2026-03-08T12:00:00",
"updated_at": "2026-03-08T12:00:00",
"category_name": "餐饮",
"category_type": "expense",
"category_icon": "Bowl"
}
]
排序: date DESC, created_at DESC
```
#### POST /api/records — 创建记录
```
Request Body (必填):
{
"date": "2026-03-08", // YYYY-MM-DD
"type": "expense", // income | expense
"category_id": 4, // 必须存在于 categories 表
"amount": 35.50, // 非负数
"note": "午餐" // 可选
}
Response 201: 完整记录对象(含关联分类信息)
验证规则:
- date, type, category_id, amount 为必填
- amount 不能为负数
```
#### PUT /api/records/:id — 更新记录
```
Path: :id — 记录 ID
Request Body (部分更新): { date?, type?, category_id?, amount?, note? }
Response 200: 更新后的完整记录对象
Response 404: { "error": "记录不存在" }
自动更新 updated_at
```
#### DELETE /api/records/:id — 删除记录
```
Path: :id — 记录 ID
Response 200: { "message": "删除成功" }
Response 404: { "error": "记录不存在" }
```
### 3.3 分类接口详细设计
#### GET /api/categories
```
Query: type (可选) — income | expense
Response 200: 分类数组,按 sort_order 升序
```
#### POST /api/categories
```
Request Body:
{
"name": "娱乐", // 必填,唯一
"type": "expense", // 默认 expense
"icon": "Headset", // 可选
"sort_order": 21 // 可选,默认 0
}
Response 201: 新分类对象
Response 400: { "error": "分类名称不能为空" }
Response 400: { "error": "分类名称已存在" }
```
#### PUT /api/categories/:id
```
Request Body (部分更新): { name?, type?, icon?, sort_order? }
Response 200: 更新后的分类对象
Response 404: { "error": "分类不存在" }
```
#### DELETE /api/categories/:id
```
Response 200: { "message": "删除成功" }
Response 400: { "error": "该分类下存在记录,无法删除" }
Response 404: { "error": "分类不存在" }
业务规则: 有关联记录的分类不可删除
```
### 3.4 统计接口详细设计
#### GET /api/statistics/daily
```
Query (必填): date=YYYY-MM-DD
Response 200:
{
"date": "2026-03-08",
"income": 0,
"expense": 85.50,
"balance": -85.50,
"categories": [
{ "name": "餐饮", "icon": "Bowl", "total": 55.00 },
{ "name": "交通", "icon": "Van", "total": 30.50 }
] // 支出分类排行,按金额降序
}
```
#### GET /api/statistics/weekly
```
Query (必填): date=YYYY-MM-DD (该日所在周)
Response 200:
{
"startDate": "2026-03-02", // 周一
"endDate": "2026-03-08", // 周日
"income": 5000,
"expense": 1200,
"balance": 3800,
"dailyData": [
{ "date": "2026-03-02", "type": "expense", "total": 150 },
{ "date": "2026-03-02", "type": "income", "total": 5000 }
], // 每日收支明细,按日期升序
"categories": [...] // 周支出分类排行
}
```
#### GET /api/statistics/monthly
```
Query (必填): month=YYYY-MM
Response 200:
{
"month": "2026-03",
"income": 8000,
"expense": 3500,
"balance": 4500,
"dailyData": [...], // 每日收支明细
"categories": [...] // 月支出分类排行
}
```
#### GET /api/statistics/yearly
```
Query (必填): year=YYYY
Response 200:
{
"year": "2026",
"income": 96000,
"expense": 45000,
"balance": 51000,
"monthlyData": [
{ "month": "2026-01", "type": "expense", "total": 3800 },
{ "month": "2026-01", "type": "income", "total": 8000 }
], // 每月收支明细,按月份升序
"categories": [...] // 年支出分类排行
}
```
#### GET /api/statistics/balance
```
Response 200:
{
"initial_balance": 10000,
"total_income": 96000,
"total_expense": 45000,
"current_balance": 61000 // = 10000 + 96000 - 45000
}
```
#### GET /api/statistics/trend
```
Query (必填): start=YYYY-MM-DD & end=YYYY-MM-DD
Response 200:
[
{ "date": "2026-03-01", "type": "expense", "total": 200 },
{ "date": "2026-03-01", "type": "income", "total": 8000 }
] // 按日期升序
```
### 3.5 余额接口详细设计
#### GET /api/balance
```
Response 200:
{
"initial_balance": 10000,
"total_income": 96000,
"total_expense": 45000,
"current_balance": 61000
}
```
#### PUT /api/balance
```
Request Body (必填):
{ "initial_balance": 15000 }
Response 200: 更新后的完整余额信息
Response 400: { "error": "请提供初始余额" }
```
---
## 四、页面设计
### 4.1 全局布局
```
┌──────────────────────────────────────────────────────────┐
│ [钱包图标] 记账助手 │ 总览 │ 记账 │ 统计 │ ← 顶部导航栏 (sticky, 56px)
├──────────────────────────────────────────────────────────┤
│ │
│ <router-view /> │ ← 主内容区
│ max-width: 1200px │ 居中布局
│ padding: 20px │
│ │
└──────────────────────────────────────────────────────────┘
背景色: #f5f7fa
```
**导航栏 Navbar**
- 固定在页面顶部 (position: sticky)
- 左侧:钱包图标 + "记账助手" 品牌文字(蓝色 #409eff
- 右侧:三个导航链接,当前页面高亮(浅蓝背景 #ecf5ff + 加粗)
- 阴影效果:`0 2px 8px rgba(0,0,0,0.08)`
**颜色体系:**
| 语义 | 颜色 | CSS 类 | 用途 |
|------|------|--------|------|
| 收入 | #67c23a (绿) | `.text-income` | 收入金额、收入标签 |
| 支出 | #f56c6c (红) | `.text-expense` | 支出金额、支出标签 |
| 结余 | #409eff (蓝) | `.text-balance` | 余额、品牌色 |
**响应式断点:**
- `≥ 768px`:图表区域双列布局
- `< 768px`:单列堆叠
- `≥ 640px`:汇总卡片三列
- `< 640px`:汇总卡片单列
---
### 4.2 页面一:总览页 Dashboard路由 `/`
**页面标题:** "总览 - 记账助手"
**布局结构:**
```
┌──────────────────────────────────────────────────────────┐
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ ✨ 余额卡片 (BalanceCard) ✨ │ │
│ │ 渐变背景 #667eea#764ba2 │ │
│ │ │ │
│ │ "当前余额" │ │
│ │ ¥ 61,000.00 │ │
│ │ (32px 加粗白色字) │ │
│ │ "点击设置初始余额" │ │
│ │ │ │
│ │ 呼吸动画 (3秒循环缩放 1→1.02) │ │
│ │ 悬停: scale(1.01) + 阴影增强 │ │
│ │ 点击: 弹出修改初始余额对话框 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 本月收入 │ │ 本月支出 │ │ 本月结余 │ │
│ │ ¥8,000.00 │ │ ¥3,500.00 │ │ ¥4,500.00 │ │
│ │ (绿色) │ │ (红色) │ │ (蓝色) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ↑ SummaryCards 组件 — 三列网格 │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ 本月支出趋势 │ │ 支出分类 │ │
│ │ (TrendChart) │ │ (CategoryPieChart) │ │
│ │ │ │ │ │
│ │ 折线图 + 面积 │ │ 环形饼图 │ │
│ │ 红色渐变填充 │ │ 内半径40% 外70% │ │
│ │ X: 日期 (MM-DD) │ │ 右侧图例 │ │
│ │ Y: 金额 ¥ │ │ Tooltip: 名称+% │ │
│ │ smooth 平滑曲线 │ │ │ │
│ │ 高度: 320px │ │ 高度: 320px │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ ↑ 双列布局 (768px 以下单列) │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 最近记录 (el-table) │ │
│ │ ┌────────┬──────────┬──────────┬─────────┐ │ │
│ │ │ 日期 │ 分类 │ 金额 │ 备注 │ │ │
│ │ ├────────┼──────────┼──────────┼─────────┤ │ │
│ │ │03-08 │ 🏷餐饮 │ -¥55.00 │ 午餐 │ │ │
│ │ │ │ (红tag) │ (红字) │ │ │ │
│ │ │03-08 │ 🏷工资 │ +¥8000 │ 3月薪资 │ │ │
│ │ │ │ (绿tag) │ (绿字) │ │ │ │
│ │ └────────┴──────────┴──────────┴─────────┘ │ │
│ │ 显示最近 20 条记录 │ │
│ │ 分类列: Tag 组件 (income→success绿, expense→danger红)│ │
│ │ 金额列: 收入 +¥ 绿色 / 支出 -¥ 红色 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
```
**初始余额修改对话框:**
```
┌─── 设置初始余额 ──────────────┐
│ │
│ 初始余额: [ 10000.00 ▲▼ ] │ ← el-input-number
│ precision: 2 │ step: 100
│ │
│ [取消] [确认] │
└───────────────────────────────┘
```
**数据加载时机:** 页面挂载时并行加载余额、当月统计、当月记录
---
### 4.3 页面二:记账页 DailyInput路由 `/input`
**页面标题:** "记账 - 记账助手"
**布局结构:**
```
┌──────────────────────────────────────────────────────────┐
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ [◀] 📅 2026-03-08 (日期选择器) [▶] [今天] │ │
│ │ │ │
│ │ ◀ 前一天 / ▶ 后一天 / 今天按钮快速跳转 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 记录表单 (RecordForm) │ │
│ │ │ │
│ │ 类型: ○ 支出(默认) ○ 收入 │ │
│ │ │ │
│ │ 分类: [ 下拉选择 ▼ ] │ │
│ │ (根据类型自动过滤可选分类) │ │
│ │ (切换类型时清空已选分类) │ │
│ │ │ │
│ │ 金额: [ 0.00 ▲▼ ] │ │
│ │ step: 10, precision: 2 │ │
│ │ │ │
│ │ 备注: [ 输入备注(可选) ] │ │
│ │ (支持回车直接提交) │ │
│ │ │ │
│ │ [ 添加记录 ] │ │
│ │ │ │
│ │ 验证: 分类必选 + 金额 > 0 │ │
│ │ 提交后: 重置金额和备注,保留类型和分类 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 当日记录列表 (RecordTable) │ │
│ │ ┌──────────┬──────────┬────────┬──────────────┐ │ │
│ │ │ 分类 │ 金额 │ 备注 │ 操作 │ │ │
│ │ ├──────────┼──────────┼────────┼──────────────┤ │ │
│ │ │ 🏷餐饮 │ -¥55.00 │ 午餐 │ [编辑][删除] │ │ │
│ │ │ 🏷交通 │ -¥30.50 │ 地铁 │ [编辑][删除] │ │ │
│ │ ├──────────┼──────────┼────────┼──────────────┤ │ │
│ │ │ 支出合计: ¥85.50 (红) 收入合计: ¥0.00 (绿) │ │ │
│ │ └──────────┴──────────┴────────┴──────────────┘ │ │
│ │ │ │
│ │ 空状态: "暂无记录" │ │
│ │ 条纹样式表格 + 加载遮罩 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
```
**编辑记录对话框:**
```
┌─── 编辑记录 (宽 400px) ─────────┐
│ │
│ 分类: [ 下拉选择 ▼ ] │ ← 根据记录类型过滤
│ │
│ 金额: [ 55.00 ▲▼ ] │
│ │
│ 备注: [ 午餐 ] │
│ │
│ [取消] [确认] │
└──────────────────────────────────┘
```
**交互流程:**
1. 页面加载 → 获取分类列表 + 当日记录
2. 切换日期 → 自动拉取对应日期的记录
3. 填写表单 → 提交 → 新记录插入到列表最前面 → 提示"添加成功"
4. 点击编辑 → 弹出对话框 → 修改 → 提示"修改成功"
5. 点击删除 → 确认后删除 → 提示"删除成功"
---
### 4.4 页面三:统计页 Statistics路由 `/statistics`
**页面标题:** "统计 - 记账助手"
**布局结构:**
```
┌──────────────────────────────────────────────────────────┐
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 维度切换: [日] [周] [月(默认)] [年] │ │
│ │ ↑ el-radio-group 按钮组 │ │
│ │ │ │
│ │ 日期选择: 📅 [ 2026-03 ▼ ] │ │
│ │ ↑ 根据维度显示不同类型: │ │
│ │ 日/周 → type="date" │ │
│ │ 月 → type="month" │ │
│ │ 年 → type="year" │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 收入 │ │ 支出 │ │ 结余 │ │
│ │ ¥8,000.00 │ │ ¥3,500.00 │ │ ¥4,500.00 │ │
│ │ (绿色) │ │ (红色) │ │ (蓝色) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ ↑ SummaryCards │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 收支趋势对比 (ECharts 柱状图) │ │
│ │ │ │
│ │ ██ │ │
│ │ ██ ██ ██ │ │
│ │ ██ ██ ██ ██ ██ │ │
│ │ ────────────────── │ │
│ │ 3月 4月 5月 6月 │ │
│ │ │ │
│ │ ■ 收入(绿色柱) ■ 支出(红色柱) │ │
│ │ 年维度: 按月展示 / 其他维度: 按日展示 │ │
│ │ 高度: 320px │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ 分类排行 │ │ 支出分类 │ │
│ │ (横向柱状图) │ │ (CategoryPieChart) │ │
│ │ │ │ │ │
│ │ 餐饮 ████████ │ │ 环形饼图 │ │
│ │ 交通 ██████ │ │ │ │
│ │ 购物 ████ │ │ │ │
│ │ │ │ │ │
│ │ 渐变色: 蓝→紫 │ │ │ │
│ │ 从低到高排序 │ │ │ │
│ └─────────────────────┘ └─────────────────────┘ │
│ ↑ 双列布局 │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 明细数据 (el-table) │ │
│ │ ┌────────┬──────────┬──────────┬─────────┐ │ │
│ │ │ 日期 ↕ │ 分类 │ 金额 ↕ │ 备注 │ │ │
│ │ ├────────┼──────────┼──────────┼─────────┤ │ │
│ │ │03-08 │ 🏷餐饮 │ -¥55.00 │ 午餐 │ │ │
│ │ └────────┴──────────┴──────────┴─────────┘ │ │
│ │ 支持日期和金额列排序 │ │
│ │ 默认按日期倒序 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────┘
```
**交互流程:**
1. 默认加载当月统计
2. 切换维度 → 自动拉取对应维度的统计数据
3. 更改日期 → 刷新所有图表和表格
4. 图表自适应窗口大小 (resize 监听)
---
## 五、组件设计
### 5.1 组件层级关系
```
App.vue
├── Navbar.vue // 全局导航栏
└── <router-view>
├── Dashboard.vue // 总览页
│ ├── BalanceCard.vue // 余额卡片 + 修改对话框
│ ├── SummaryCards.vue // 收支汇总三卡片
│ ├── TrendChart.vue // 支出趋势折线图
│ ├── CategoryPieChart.vue // 分类饼图
│ └── el-table (内联) // 最近记录表格
├── DailyInput.vue // 记账页
│ ├── RecordForm.vue // 记录输入表单
│ └── RecordTable.vue // 当日记录列表 + 编辑对话框
└── Statistics.vue // 统计页
├── SummaryCards.vue // 收支汇总三卡片 (复用)
├── CategoryPieChart.vue // 分类饼图 (复用)
├── ECharts (内联初始化) // 趋势柱状图 + 排行图
└── el-table (内联) // 明细数据表格
```
### 5.2 组件 Props 接口
| 组件 | Props | 类型 | 默认值 | 说明 |
|------|-------|------|--------|------|
| **BalanceCard** | `balance` | Number | 0 | 当前余额 |
| **SummaryCards** | `income` | Number | 0 | 收入总额 |
| | `expense` | Number | 0 | 支出总额 |
| **TrendChart** | `data` | Array | [] | 记录数据数组 |
| | `title` | String | '支出趋势' | 图表标题 |
| **CategoryPieChart** | `data` | Array | [] | `[{name, total}]` |
| | `title` | String | '支出分类' | 图表标题 |
| **RecordForm** | `categories` | Array | [] | 可选分类列表 |
| | `date` | String | (必填) | 当前日期 |
| **RecordTable** | `records` | Array | [] | 记录列表 |
| | `categories` | Array | [] | 分类列表 |
| | `loading` | Boolean | false | 加载状态 |
### 5.3 组件 Events
| 组件 | 事件 | 载荷 | 说明 |
|------|------|------|------|
| **RecordForm** | `submit` | `{date, type, category_id, amount, note}` | 提交新记录 |
| **RecordTable** | `edit` | `{id, type, category_id, amount, note}` | 编辑记录 |
| | `delete` | `id` | 删除记录 |
---
## 六、状态管理设计
### 6.1 Records Store
```
┌─ records store ──────────────────────────┐
│ │
│ State: │
│ records: [] // 当前记录列表 │
│ categories: [] // 全部分类列表 │
│ loading: false // 加载状态 │
│ │
│ Actions: │
│ fetchCategories() → GET /categories │
│ fetchByDate(date) → GET /records │
│ fetchByMonth(month) → GET /records │
│ addRecord(data) → POST /records │
│ updateRecord(id,data)→ PUT /records/:id│
│ deleteRecord(id) → DELETE /records │
│ │
│ 数据更新策略: │
│ add → unshift 到数组前端 │
│ update → 原地替换 │
│ delete → filter 过滤 │
│ │
└──────────────────────────────────────────┘
```
### 6.2 Statistics Store
```
┌─ statistics store ───────────────────────┐
│ │
│ State: │
│ balanceInfo: { │
│ initial_balance: 0 │
│ total_income: 0 │
│ total_expense: 0 │
│ current_balance: 0 │
│ } │
│ monthlyStats: null │
│ loading: false │
│ │
│ Actions: │
│ fetchBalance() → 持久state│
│ updateInitialBalance(amt) → 持久state│
│ fetchMonthlyStats(month) → 持久state│
│ fetchDailyStats(date) → 直接返回 │
│ fetchWeeklyStats(date) → 直接返回 │
│ fetchYearlyStats(year) → 直接返回 │
│ fetchTrend(start, end) → 直接返回 │
│ │
└──────────────────────────────────────────┘
```
---
## 七、关键设计决策
### 7.1 数据库选型 — sql.js
- 采用 sql.js 在内存中运行 SQLite无需安装原生数据库
- 每次写操作后自动调用 `saveDb()` 将内存数据导出到 `data/accounting.db` 文件
- DbWrapper 封装了 `all()` / `get()` / `run()` 三个方法,屏蔽了 sql.js 底层 prepare/step/getAsObject 的复杂性
- 适合个人使用场景,不适合高并发
### 7.2 开发与生产环境
| 环境 | 前端 | 后端 | API 通信 |
|------|------|------|---------|
| 开发 | Vite dev server :5173 | Express :3000 | Vite proxy `/api` → :3000 |
| 生产 | 构建为 `client/dist` 静态文件 | Express :3000 serve 静态文件 | 同源直接请求 |
### 7.3 前端构建优化 — 手动 chunk 分包
```javascript
manualChunks: {
'element-plus': ['element-plus'], // UI 组件库独立包
'echarts': ['echarts'], // 图表库独立包
'vue': ['vue', 'vue-router', 'pinia'] // Vue 核心独立包
}
```
### 7.4 SPA 路由支持
生产环境下,后端对所有非 `/api` 开头的请求返回 `index.html`确保前端路由History 模式)正常工作。
### 7.5 API 响应拦截
Axios response interceptor 自动提取 `response.data`Store 层无需手动解包。
---
## 八、样式规范
### 8.1 全局样式 Token
| Token | 值 | 用途 |
|-------|-----|------|
| 字体 | PingFang SC, Microsoft YaHei, sans-serif | 中文优先字体栈 |
| 等宽数字字体 | Menlo, Monaco, Consolas, monospace | 金额展示 |
| 页面背景 | #f5f7fa | 全局浅灰背景 |
| 文字颜色 | #303133 | 主要文字 |
| 次要文字 | #909399 | 标签、说明 |
| 卡片圆角 | 12px | 统一圆角 |
| 卡片阴影 | `0 4px 12px rgba(0,0,0,0.08)` | 统一浮起效果 |
| 余额卡片渐变 | `#667eea → #764ba2 (135deg)` | 主视觉焦点 |
### 8.2 动画
| 动画 | 属性 | 说明 |
|------|------|------|
| 呼吸动画 | `scale(1) → scale(1.02)`3s 循环 | 余额卡片 |
| 悬停缩放 | `scale(1.01)`0.3s ease | 余额卡片 hover |
| 过渡 | `transition: all 0.3s ease` | 通用过渡 |