Files
accounting/项目计划书.md
2026-03-26 01:23:19 +08:00

870 lines
40 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 记账助手 - 项目计划书
> 个人记账 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` | 通用过渡 |