diff --git a/.gitignore b/.gitignore
index 691eb5d..b007628 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,5 +34,9 @@ client/unpackage/
*.pem
*.key
+# Uploads (user-generated content)
+server/uploads/
+
# Playwright
.playwright-mcp/
+server/backups/
diff --git a/CLAUDE.md b/CLAUDE.md
index a517304..86eb176 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -16,6 +16,8 @@
- **运行时**: Node.js + Express
- **数据库**: MySQL 8.0 (mysql2 连接池)
- **认证**: HMAC-SHA256 签名 token,30 天过期
+- **文件上传**: multer (头像,最大 2MB)
+- **备份**: Node.js mysql2 导出 (不依赖 mysqldump)
- **CORS**: 已配置,支持 credentials
## Design System
@@ -37,10 +39,17 @@
### 关键原则
- 最小触摸目标 44×44px
-- 禁止使用 emoji 作为图标,统一使用 SvgIcon 组件
+- 禁止使用 emoji 作为图标,统一使用 Icon 组件
- 尊重 prefers-reduced-motion
- 文字对比度 ≥ 4.5:1 (WCAG AA)
+### 小程序胶囊适配
+- 微信小程序右上角有胶囊按钮,页面顶部右侧元素(图标、按钮)需避开
+- 使用 `system.ts` 导出的 `capsuleRight` 计算安全距离
+- 导航栏添加动态 padding: `:style="{ paddingRight: capsuleRight + 'px' }"`
+- 居中标题页面(统计、账单、我的)不需要处理
+- 有右侧元素的页面(首页 bell、编辑页保存按钮)必须处理
+
## Project Conventions
### 命名
@@ -58,41 +67,69 @@ client/src/
│ ├── add/ # 添加/编辑记录
│ ├── stats/ # 统计分析
│ ├── bills/ # 全部账单 (下拉刷新/上拉加载)
-│ ├── profile/ # 个人设置
-│ └── category-manage/ # 分类管理
+│ ├── profile/ # 我的(身份切换入口)
+│ ├── profile-edit/ # 编辑资料(昵称+头像)
+│ ├── group-manage/ # 群组管理(创建/加入/退出/解散)
+│ ├── category-manage/ # 分类管理
+│ └── budget/ # 预算设置
├── components/ # 公共组件
│ ├── BudgetBar/ # 预算进度条
│ ├── CategoryIcon/ # 分类图标 (首字+颜色)
+│ ├── Icon/ # 图标组件 (PNG 映射)
│ ├── Numpad/ # 数字键盘
-│ ├── SvgIcon/ # SVG 图标 (base64 data URI)
+│ ├── Skeleton/ # 骨架屏
│ └── TransactionItem/ # 交易列表项
├── stores/ # Pinia stores
-│ ├── transaction.ts # 交易 CRUD
+│ ├── transaction.ts # 交易 CRUD (自动传递 group_id)
│ ├── category.ts # 分类 CRUD
│ ├── budget.ts # 预算管理
-│ └── stats.ts # 统计概览
+│ ├── stats.ts # 统计概览 (自动传递 group_id)
+│ ├── user.ts # 用户信息 (昵称/头像)
+│ └── group.ts # 群组管理 (身份切换)
+├── api/ # API 封装
+│ ├── auth.ts # 登录接口
+│ ├── transaction.ts # 交易接口
+│ ├── stats.ts # 统计接口
+│ ├── user.ts # 用户信息接口 (含头像上传)
+│ └── group.ts # 群组接口
├── utils/
│ ├── format.ts # 金额/日期格式化
-│ ├── request.ts # HTTP 请求封装 (401 处理/自动重登录)
-│ └── system.ts # 系统信息 (状态栏高度等)
+│ ├── request.ts # HTTP 请求封装 (401 自动重登录+队列重试)
+│ ├── system.ts # 系统信息 (statusBarHeight, capsuleRight)
+│ └── app-ready.ts # App 启动就绪机制 (waitForReady)
+├── config.ts # 全局配置 (API_BASE)
└── static/icons/ # tabBar 图标 (PNG)
server/src/
-├── index.ts # Express 入口 (CORS, JSON 解析)
-├── middleware/auth.ts # HMAC token 验证中间件
+├── index.ts # Express 入口 (CORS, JSON, 静态文件, 路由注册)
+├── middleware/auth.ts # HMAC token 验证 (timingSafeEqual)
├── routes/
-│ ├── auth.ts # 登录 / token 签发
-│ ├── transaction.ts # 交易 CRUD (用户隔离)
+│ ├── auth.ts # 登录 / token 签发 (返回用户信息)
+│ ├── transaction.ts # 交易 CRUD (支持 group_id 视图切换)
│ ├── category.ts # 分类 CRUD (默认+自定义)
│ ├── budget.ts # 预算 CRUD (按月)
-│ └── stats.ts # 统计查询
+│ ├── stats.ts # 统计查询 (支持 group_id)
+│ ├── user.ts # 用户信息 + 头像上传
+│ ├── group.ts # 群组 CRUD (创建/加入/退出/解散)
+│ └── backup.ts # 备份 API
+├── utils/
+│ ├── backup.ts # Node.js 数据库备份 (gzip)
+│ └── date.ts # 日期工具
└── db/
├── connection.ts # MySQL 连接池
- ├── init.ts # 建表 + seed (multipleStatements)
+ ├── init.ts # 建表 + seed + 迁移 (幂等)
├── schema.sql # 表结构
└── seed.sql # 默认分类数据
```
+### 数据库表
+- `users` — 用户 (openid, nickname, avatar_url)
+- `categories` — 分类 (user_id=0 默认, 其他=自定义)
+- `transactions` — 交易记录 (user_id, group_id, amount 分)
+- `budgets` — 预算 (user_id, month, amount)
+- `groups` — 群组 (name, invite_code, created_by)
+- `group_members` — 群组成员 (group_id, user_id, role)
+
### 金额处理
- 存储单位: 分 (整数,避免浮点精度问题)
- 显示单位: 元 (保留两位小数)
@@ -101,15 +138,28 @@ server/src/
- 使用 `formatAmount()` 带 ¥ 前缀,`formatAmountRaw()` 纯数字
### 数据持久化
-- 前端本地存储 Key: `xc:token`, `xc:userId`
+- 前端本地存储 Key:
+ - `xc:token` — 认证 token
+ - `xc:userId` — 用户 ID
+ - `xc:nickname` — 用户昵称
+ - `xc:avatar_url` — 头像文件名
+ - `xc:currentGroupId` — 当前选中群组 ID (null=个人模式)
- 后端 MySQL 数据库
-- API 基础地址: `request.ts` 中的 `BASE_URL`
+- API 基础地址: `config.ts` 中的 `API_BASE`
### 认证流程
-- H5: 页面加载时自动 login 获取 token
+- H5: 页面加载时自动 demo-login 获取 token
- 微信小程序: `App.vue` onLaunch 调用 `wx.login` 获取 code,换 token
- Token: HMAC-SHA256 签名,30 天过期
-- 401 处理: 自动清除 token,小程序触发 reLogin,H5 刷新页面
+- 401 处理: 请求入队 → 自动重登录 → 队列中所有请求重试
+- 启动就绪: `app-ready.ts` 的 `waitForReady()` 确保页面在登录完成后才加载数据
+
+### 一起记 (群组)
+- 群组仅做数据聚合,记录始终属于记录者 (user_id)
+- 个人视图: `WHERE user_id = 我` (所有记录)
+- 群组视图: `WHERE group_id = X AND user_id IN (当前成员)`
+- 退出群组: 该用户的 group_id 设为 NULL,记录回到个人视图
+- 身份切换: 「我的」页底部弹窗选择,全局生效
### Git
- 分支: `master` → `main` (注意远程)
@@ -141,9 +191,10 @@ cd server && npm run db:init
## 部署
-- 服务器: `106.14.208.43:3000`
+- 开发服务: `dev.xiaocai.j35.site:3000`
+- 正式服务: `xiaocai.j35.site`
- MySQL: 本地 3306 端口
-- 微信小程序上线需配置 HTTPS 域名 (替换 `request.ts` 中的 `BASE_URL`)
+- 小程序开发版自动连接 dev 服务,正式版/H5 连接线上 (config.ts)
### 手动部署步骤
@@ -163,7 +214,7 @@ cd /var/www/xiaocai && npm install --production
# 5. 重启服务
pm2 restart xiaocai-server
# 或直接运行
-node dist/index.js
+NODE_ENV=production node dist/index.js
```
## 开发
diff --git a/DEV.md b/DEV.md
index d9e0808..392ef01 100644
--- a/DEV.md
+++ b/DEV.md
@@ -26,10 +26,17 @@
- 不要为了注释而注释,避免冗余的废话(例如 `i++; // 自增 i`)。
- 多行 JSDoc/文档注释保持可读性,**不要压缩成单行**。
+## 小程序适配要点
+
+- 页面顶部右侧有元素时,使用 `capsuleRight` 避开胶囊按钮
+- 新页面需要加载数据时,在 `onMounted` 中先 `await waitForReady()` 再请求
+- API 地址统一在 `config.ts` 中配置,不要在各文件中硬编码
+- 头像等用户文件 URL 只存文件名,前端拼接 `API_BASE + 路径 + 文件名`
+
## Never 规则
- Never 修改 dist/ 目录
-- Never 使用内联样式,除非需要动态计算
+- Never 使用内联样式,除非需要动态计算(胶囊 padding 等除外)
- Never 在渲染路径中执行耗时操作
- Never 在列表渲染中省略 key 属性
- Never 修改 lock 文件
@@ -39,4 +46,5 @@
## Requirements
-- 代码提交前检查代码,包括但不限于代码格式、注释、测试覆盖率等。
+- 代码提交前必须检查代码,包括但不限于代码格式、注释、测试覆盖率、文档更新等。
+- 影响到历史数据吧,必须要准备好数据迁移方案。
diff --git a/client/src/App.vue b/client/src/App.vue
index 60fb3b0..0e94386 100644
--- a/client/src/App.vue
+++ b/client/src/App.vue
@@ -1,22 +1,28 @@
+
+
diff --git a/client/src/pages/index/index.vue b/client/src/pages/index/index.vue
index ab11273..d211334 100644
--- a/client/src/pages/index/index.vue
+++ b/client/src/pages/index/index.vue
@@ -2,11 +2,12 @@