870 lines
40 KiB
Markdown
Executable File
870 lines
40 KiB
Markdown
Executable File
# 记账助手 - 项目计划书
|
||
|
||
> 个人记账 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 表结构详细说明
|
||
|
||
#### 表 1:categories(分类表)
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
|------|------|------|------|
|
||
| `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 | 创建时间 |
|
||
|
||
#### 表 2:records(记录表)
|
||
|
||
| 字段 | 类型 | 约束 | 说明 |
|
||
|------|------|------|------|
|
||
| `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)` — 按类型过滤加速
|
||
|
||
#### 表 3:balance_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` | 通用过渡 |
|