feat: 账单导入、统计维度扩展与收支记录增强

- 账单导入:新增支付宝账单解析/导入接口,按 external_no 去重;前端拖拽上传 + 预览确认弹窗
- 分类可选:category_id 允许为空,支持「未分类」筛选,新增按类型一键批量设置分类
- 备注隐藏:新增 remark_hidden 与切换接口,列表默认显示 ****** 并可单格查看/取消隐藏,导出同步隐藏
- 统计扩展:新增 /statistics/summary、/statistics/group,支持日/周/月/季/年/支付方式/分类/类型维度与时间范围过滤
- 收支记录:金额区间过滤、金额/交易时间列排序、分页快速跳转、本页收支结余汇总、交易时间列前置并合并创建时间提示
- 用户偏好:统计面板与列表的筛选条件持久化到 localStorage,进入自动还原
- 其他:新增默认分类种子脚本(含美容美发)、commons-csv 依赖、上传大小限制 10MB、Vite 监听 0.0.0.0
This commit is contained in:
2026-09-17 13:19:59 +08:00
parent 733ed4bb86
commit 1df6e23510
35 changed files with 3016 additions and 1620 deletions
+5 -2
View File
@@ -25,11 +25,13 @@ CREATE TABLE IF NOT EXISTS t_category (
CREATE TABLE IF NOT EXISTS t_transaction (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '记录ID,主键自增',
user_id BIGINT NOT NULL COMMENT '所属用户ID',
category_id BIGINT NOT NULL COMMENT '所属分类ID',
category_id BIGINT COMMENT '所属分类ID,可为空表示未分类',
type VARCHAR(10) NOT NULL COMMENT '记录类型:INCOME-收入,EXPENSE-支出',
amount DECIMAL(12, 2) NOT NULL COMMENT '金额,精确到分',
payment_method VARCHAR(30) COMMENT '支付方式:WECHAT-微信,ALIPAY-支付宝,CASH-现金,BANK-银行卡',
remark VARCHAR(255) COMMENT '备注说明',
remark_hidden TINYINT(1) NOT NULL DEFAULT 0 COMMENT '备注是否隐藏:1-隐藏,0-显示',
external_no VARCHAR(64) COMMENT '外部账单交易号,用于账单导入去重',
transaction_time DATETIME NOT NULL COMMENT '交易发生时间',
include_in_stats TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否计入统计:1-计入,0-不计入(如借款、转账等)',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
@@ -37,5 +39,6 @@ CREATE TABLE IF NOT EXISTS t_transaction (
INDEX idx_user_id (user_id) COMMENT '用户ID索引',
INDEX idx_category_id (category_id) COMMENT '分类ID索引',
INDEX idx_transaction_time (transaction_time) COMMENT '交易时间索引',
INDEX idx_user_time (user_id, transaction_time) COMMENT '用户ID+交易时间联合索引'
INDEX idx_user_time (user_id, transaction_time) COMMENT '用户ID+交易时间联合索引',
UNIQUE INDEX uk_user_external (user_id, external_no) COMMENT '用户ID+外部交易号唯一索引,用于导入去重'
);
@@ -0,0 +1,44 @@
-- 初始化默认收支分类种子脚本
-- 用途:为现有用户补齐一套默认分类(支出 10 个、收入 6 个)
-- 特性:幂等,按「用户ID + 分类名称 + 分类类型」去重,可重复执行
-- 编码:utf8mb4(图标使用 emoji,需以 --default-character-set=utf8mb4 执行)
INSERT INTO t_category (user_id, name, type, icon, sort_order)
SELECT u.id, d.name, d.type, d.icon, d.sort_order
FROM t_user u
CROSS JOIN (
SELECT '餐饮' AS name, 'EXPENSE' AS type, '🍜' AS icon, 10 AS sort_order
UNION ALL SELECT '交通', 'EXPENSE', '🚌', 20
UNION ALL SELECT '购物', 'EXPENSE', '🛍️', 30
UNION ALL SELECT '居住', 'EXPENSE', '🏠', 40
UNION ALL SELECT '通讯', 'EXPENSE', '📱', 50
UNION ALL SELECT '娱乐', 'EXPENSE', '🎮', 60
UNION ALL SELECT '医疗', 'EXPENSE', '💊', 70
UNION ALL SELECT '教育', 'EXPENSE', '📚', 80
UNION ALL SELECT '美容美发', 'EXPENSE', '💇', 85
UNION ALL SELECT '人情往来', 'EXPENSE', '🎁', 90
UNION ALL SELECT '其他支出', 'EXPENSE', '📦', 100
UNION ALL SELECT '工资', 'INCOME', '💰', 10
UNION ALL SELECT '奖金', 'INCOME', '🧧', 20
UNION ALL SELECT '兼职', 'INCOME', '💼', 30
UNION ALL SELECT '投资收益', 'INCOME', '📈', 40
UNION ALL SELECT '退款', 'INCOME', '↩️', 50
UNION ALL SELECT '其他收入', 'INCOME', '📦', 60
) d
WHERE NOT EXISTS (
SELECT 1 FROM t_category c
WHERE c.user_id = u.id AND c.name = d.name AND c.type = d.type
);
-- 备选写法:若 MySQL 对上述 INSERT ... SELECT 引用目标表报错 1093,
-- 可改用临时表方式:
-- CREATE TEMPORARY TABLE tmp_default_category (
-- name VARCHAR(50), type VARCHAR(10), icon VARCHAR(50), sort_order INT
-- );
-- INSERT INTO tmp_default_category VALUES ('餐饮','EXPENSE','🍜',10), ...;
-- INSERT INTO t_category (user_id, name, type, icon, sort_order)
-- SELECT u.id, d.name, d.type, d.icon, d.sort_order
-- FROM t_user u JOIN tmp_default_category d
-- WHERE NOT EXISTS (SELECT 1 FROM t_category c
-- WHERE c.user_id = u.id AND c.name = d.name AND c.type = d.type);
-- DROP TEMPORARY TABLE tmp_default_category;