feat: 用户信息完善 + 一起记功能
- users 表添加 nickname、avatar_url 字段 - 用户信息 API(GET/PUT /me)+ 头像上传(multer) - 头像通过 API 路由获取(公开,不经过 auth) - 登录接口返回用户昵称和头像 - 用户信息 Store + 个人资料编辑页面 - 我的页面和首页显示真实用户信息 - 群组表(groups、group_members)+ transactions 添加 group_id - 群组 API(创建/加入/退出/解散) - 交易和统计 API 支持 group_id 视图切换 - 群组客户端 Store + 身份切换 - 群组管理页面 - App 初始化群组 Store - UPLOAD_DIR 配置化 - Node.js 备份替代 mysqldump - 统一前端 BASE_URL(config.ts) - uploads 加入 .gitignore - 修复 trust proxy 警告
This commit is contained in:
@@ -0,0 +1,589 @@
|
||||
# 用户信息完善 & 一起记功能 实现计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 两个功能:① 用户可编辑昵称和上传头像,替换全局硬编码的"小菜";② 用户可创建/加入群组,在「我的」页切换身份,群组仅作为数据聚合视图。
|
||||
|
||||
**Architecture:**
|
||||
- 用户信息:扩展 `users` 表 → 服务端 multer 上传头像 → `/api/user` 路由 → 前端 `userStore` + 编辑页
|
||||
- 一起记:新建 `groups`/`group_members` 表 → `transactions` 加 `group_id` 标签 → 「我的」页身份切换 → 群组仅做数据聚合
|
||||
|
||||
**Tech Stack:** Vue 3 Composition API (uni-app), Express + mysql2 + multer, SCSS Claymorphism
|
||||
|
||||
---
|
||||
|
||||
## 一、核心数据模型
|
||||
|
||||
### 所有权 vs 聚合
|
||||
|
||||
```
|
||||
transactions 表:
|
||||
user_id = 谁记的(数据所有权,永远不变)
|
||||
group_id = 群组标签(仅用于聚合视图,可变)
|
||||
```
|
||||
|
||||
**个人视图:** `WHERE user_id = 我`
|
||||
- 看到我的**所有记录**,不管 group_id 是什么
|
||||
- 记录始终属于我
|
||||
|
||||
**群组视图:** `WHERE group_id = X AND user_id IN (当前成员)`
|
||||
- 只看到当前成员在该群组下记的记录
|
||||
- 离开的成员的记录自动消失
|
||||
|
||||
**加入群组:** 新记的账可选择记到群组(group_id = X),仅自己可见的旧记录不动
|
||||
|
||||
**退出群组:** 该用户所有 `group_id = X` 的记录,`group_id` 设为 NULL
|
||||
- 记录回到个人视图
|
||||
- 群组其他人看不到
|
||||
|
||||
**解散群组:** CASCADE 删除 groups → group_members 自动清理,transactions.group_id 设为 NULL
|
||||
|
||||
### 举例
|
||||
|
||||
```
|
||||
用户 A 创建群组 G,用户 B 加入
|
||||
|
||||
A 在群组模式下记了午餐 ¥30:
|
||||
→ { user_id: A, group_id: G, amount: 3000 }
|
||||
|
||||
B 在群组模式下记了打车 ¥20:
|
||||
→ { user_id: B, group_id: G, amount: 2000 }
|
||||
|
||||
A 的个人视图:看到自己所有记录(包括这笔 ¥30)
|
||||
B 的个人视图:看到自己所有记录(包括这笔 ¥20)
|
||||
群组 G 视图: A 和 B 都看到 ¥30 + ¥20 = ¥50
|
||||
|
||||
B 退出群组:
|
||||
→ B 的记录 group_id 设为 NULL
|
||||
→ B 的个人视图:仍然看到 ¥20(记录属于 B)
|
||||
→ A 看群组 G:只看到 ¥30(B 的记录已不在)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 二、数据库设计
|
||||
|
||||
### 现有表(不变)
|
||||
|
||||
```
|
||||
users(id, openid, session_key, created_at, updated_at)
|
||||
categories(id, user_id, name, icon, color, type, sort_order, is_custom, created_at)
|
||||
transactions(id, user_id, amount, type, category_id, note, date, created_at, updated_at)
|
||||
budgets(id, user_id, amount, month, created_at, updated_at)
|
||||
```
|
||||
|
||||
### 变更
|
||||
|
||||
```
|
||||
users 表新增:
|
||||
nickname VARCHAR(50) DEFAULT '小菜'
|
||||
avatar_url VARCHAR(500) DEFAULT '' -- 服务端相对路径
|
||||
|
||||
新建 groups 表:
|
||||
id INT AUTO_INCREMENT PK
|
||||
name VARCHAR(100) NOT NULL
|
||||
invite_code VARCHAR(10) UNIQUE NOT NULL -- 6位邀请码
|
||||
created_by INT NOT NULL FK→users.id
|
||||
created_at TIMESTAMP
|
||||
updated_at TIMESTAMP
|
||||
|
||||
新建 group_members 表:
|
||||
id INT AUTO_INCREMENT PK
|
||||
group_id INT NOT NULL FK→groups.id ON DELETE CASCADE
|
||||
user_id INT NOT NULL FK→users.id ON DELETE CASCADE
|
||||
role ENUM('owner','member') DEFAULT 'member'
|
||||
nickname VARCHAR(50) DEFAULT '' -- 群内昵称(预留)
|
||||
created_at TIMESTAMP
|
||||
UNIQUE KEY (group_id, user_id)
|
||||
|
||||
transactions 表新增:
|
||||
group_id INT DEFAULT NULL -- 群组标签,NULL=纯个人记录
|
||||
INDEX idx_group_date (group_id, date)
|
||||
-- 注意:不要 FK ON DELETE CASCADE,因为退出群组时需要保留记录
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、API 设计
|
||||
|
||||
### 新增接口
|
||||
|
||||
| 方法 | 路径 | 说明 |
|
||||
|------|------|------|
|
||||
| GET | `/api/user/me` | 获取当前用户信息 |
|
||||
| PUT | `/api/user/me` | 更新昵称 |
|
||||
| POST | `/api/user/avatar` | 上传头像 |
|
||||
| POST | `/api/groups` | 创建群组 |
|
||||
| GET | `/api/groups` | 获取用户的群组列表 |
|
||||
| GET | `/api/groups/:id` | 获取群组详情+成员 |
|
||||
| POST | `/api/groups/join` | 通过邀请码加入群组 |
|
||||
| POST | `/api/groups/:id/leave` | 退出群组(清除该用户的 group_id 标记) |
|
||||
| DELETE | `/api/groups/:id` | 解散群组(仅 owner) |
|
||||
|
||||
### 变更接口
|
||||
|
||||
**交易接口(`/api/transactions`):**
|
||||
|
||||
| 参数 | 说明 |
|
||||
|------|------|
|
||||
| `group_id` 不传 | 个人视图:`WHERE user_id = ?`(所有记录) |
|
||||
| `group_id = 数字` | 群组视图:`WHERE group_id = ? AND user_id IN (当前成员)` |
|
||||
|
||||
**统计接口(`/api/stats`):** 同上逻辑
|
||||
|
||||
**退出群组(`POST /api/groups/:id/leave`)关键逻辑:**
|
||||
```sql
|
||||
-- 1. 清除该用户在该群组的记录标签
|
||||
UPDATE transactions SET group_id = NULL WHERE user_id = ? AND group_id = ?
|
||||
-- 2. 删除成员关系
|
||||
DELETE FROM group_members WHERE group_id = ? AND user_id = ?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四、前端架构
|
||||
|
||||
### 新增文件
|
||||
|
||||
```
|
||||
client/src/
|
||||
├── api/user.ts -- 用户信息 API(含头像上传)
|
||||
├── api/group.ts -- 群组 API
|
||||
├── stores/user.ts -- 用户信息 Store
|
||||
├── stores/group.ts -- 群组 Store(身份切换状态)
|
||||
├── pages/profile-edit/index.vue -- 编辑资料页
|
||||
└── pages/group-manage/index.vue -- 群组管理页(创建/加入/列表/退出/解散)
|
||||
```
|
||||
|
||||
### 修改文件
|
||||
|
||||
```
|
||||
server/src/
|
||||
├── db/schema.sql, db/init.ts -- 迁移
|
||||
├── index.ts -- 注册路由 + 静态文件
|
||||
├── routes/auth.ts -- 登录返回用户信息
|
||||
├── routes/user.ts -- [新建]
|
||||
├── routes/group.ts -- [新建]
|
||||
├── routes/transaction.ts -- 查询逻辑改造
|
||||
└── routes/stats.ts -- 统计逻辑改造
|
||||
|
||||
client/src/
|
||||
├── api/auth.ts, api/transaction.ts, api/stats.ts -- 类型扩展
|
||||
├── stores/transaction.ts, stores/stats.ts -- 自动传 group_id
|
||||
├── App.vue -- 初始化
|
||||
├── pages.json -- 注册页面
|
||||
├── pages/index/index.vue -- 显示真实昵称
|
||||
└── pages/profile/index.vue -- 身份切换 + 头像/昵称
|
||||
```
|
||||
|
||||
### 身份切换交互(「我的」页)
|
||||
|
||||
```
|
||||
┌─────────────────────────┐
|
||||
│ profile-card (点击编辑) │
|
||||
│ [头像] 昵称 > │
|
||||
├─────────────────────────┤
|
||||
│ identity-card (点击切换) │
|
||||
│ 👤 个人账本 仅自己 > │
|
||||
├─────────────────────────┤
|
||||
│ quick-stats │
|
||||
│ menu-card │
|
||||
└─────────────────────────┘
|
||||
|
||||
点击 identity-card → 底部弹出 ActionSheet:
|
||||
┌─────────────────────────┐
|
||||
│ 切换账本 │
|
||||
│ ✓ 个人账本 │
|
||||
│ 家庭账本 3人 │
|
||||
│ 情侣账本 2人 │
|
||||
│ ───────────────────── │
|
||||
│ + 创建群组 │
|
||||
│ + 加入群组 │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
**创建群组流程:**
|
||||
1. 点击「创建群组」→ 弹出输入框输入名称 → 确认
|
||||
2. 创建成功 → 显示邀请码 → 自动复制到剪贴板
|
||||
3. 群组自动出现在身份列表中
|
||||
|
||||
**加入群组流程:**
|
||||
1. 点击「加入群组」→ 弹出输入框输入邀请码 → 确认
|
||||
2. 加入成功 → 群组自动出现在身份列表中
|
||||
|
||||
**退出/解散(群组管理页):**
|
||||
- 点击群组卡片进入详情 → 显示成员列表、邀请码
|
||||
- 成员可见「退出群组」按钮
|
||||
- 群主可见「解散群组」按钮
|
||||
|
||||
---
|
||||
|
||||
## 五、任务清单
|
||||
|
||||
### Part 1: 用户信息完善(Task 1-7)
|
||||
|
||||
#### Task 1: 数据库 — users 表扩展
|
||||
|
||||
**Files:** `server/src/db/schema.sql`, `server/src/db/init.ts`
|
||||
|
||||
- [ ] **Step 1: 更新 schema.sql users 表**
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
openid VARCHAR(100) UNIQUE,
|
||||
session_key VARCHAR(100),
|
||||
nickname VARCHAR(50) DEFAULT '小菜' COMMENT '用户昵称',
|
||||
avatar_url VARCHAR(500) DEFAULT '' COMMENT '头像URL',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
```
|
||||
|
||||
- [ ] **Step 2: init.ts runMigrations 添加迁移**
|
||||
|
||||
```typescript
|
||||
const hasNickname = await columnExists(conn, 'users', 'nickname')
|
||||
if (!hasNickname) {
|
||||
await conn.query("ALTER TABLE users ADD COLUMN nickname VARCHAR(50) DEFAULT '小菜'")
|
||||
}
|
||||
const hasAvatarUrl = await columnExists(conn, 'users', 'avatar_url')
|
||||
if (!hasAvatarUrl) {
|
||||
await conn.query("ALTER TABLE users ADD COLUMN avatar_url VARCHAR(500) DEFAULT ''")
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证** `cd server && npm run db:init`
|
||||
- [ ] **Step 4: Commit** `feat: users 表添加 nickname、avatar_url`
|
||||
|
||||
---
|
||||
|
||||
#### Task 2: 用户信息 API + 头像上传
|
||||
|
||||
**Files:** Create `server/src/routes/user.ts`, Modify `server/src/index.ts`
|
||||
|
||||
- [ ] **Step 1: 安装 multer** `cd server && npm install multer && npm install -D @types/multer`
|
||||
|
||||
- [ ] **Step 2: 创建 user 路由**
|
||||
|
||||
三个接口:
|
||||
- `GET /me` — 查询用户信息
|
||||
- `PUT /me` — 更新昵称(验证非空、≤50字符)
|
||||
- `POST /avatar` — multer 接收 `file` 字段,存到 `uploads/avatars/`,更新 `users.avatar_url`,删除旧文件
|
||||
|
||||
- [ ] **Step 3: index.ts 注册路由 + 静态文件**
|
||||
|
||||
```typescript
|
||||
import userRoutes from './routes/user'
|
||||
import path from 'path'
|
||||
|
||||
app.use('/uploads', express.static(path.join(__dirname, '../uploads')))
|
||||
app.use('/api/user', apiLimiter, userRoutes)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit** `feat: 用户信息 API + 头像上传`
|
||||
|
||||
---
|
||||
|
||||
#### Task 3: 登录返回用户信息
|
||||
|
||||
**Files:** `server/src/routes/auth.ts`, `client/src/api/auth.ts`, `client/src/App.vue`
|
||||
|
||||
- [ ] **Step 1: auth.ts 两个登录路由返回 `nickname`, `avatar_url`**
|
||||
- [ ] **Step 2: auth.ts LoginResult 类型扩展**
|
||||
- [ ] **Step 3: App.vue 登录后存储 `xc:nickname`, `xc:avatar_url`**
|
||||
- [ ] **Step 4: Commit** `feat: 登录返回用户信息`
|
||||
|
||||
---
|
||||
|
||||
#### Task 4: 用户信息 Store
|
||||
|
||||
**Files:** Create `client/src/api/user.ts`, Create `client/src/stores/user.ts`
|
||||
|
||||
- [ ] **Step 1: api/user.ts** — `getUserInfo`, `updateNickname`, `uploadAvatar`(用 `uni.uploadFile`)
|
||||
- [ ] **Step 2: stores/user.ts** — `nickname`, `avatarUrl` computed,`fetchUserInfo`, `updateProfile`, `uploadAvatar`
|
||||
- [ ] **Step 3: Commit** `feat: 用户信息 Store`
|
||||
|
||||
---
|
||||
|
||||
#### Task 5: 个人资料编辑页面
|
||||
|
||||
**Files:** Create `client/src/pages/profile-edit/index.vue`, Modify `client/src/pages.json`
|
||||
|
||||
- [ ] **Step 1: pages.json 注册页面**
|
||||
- [ ] **Step 2: 创建编辑页** — 头像点击选择+上传,昵称输入框,保存按钮
|
||||
- [ ] **Step 3: Commit** `feat: 个人资料编辑页面`
|
||||
|
||||
---
|
||||
|
||||
#### Task 6: 「我的」页面显示真实用户信息
|
||||
|
||||
**Files:** `client/src/pages/profile/index.vue`
|
||||
|
||||
- [ ] **Step 1: 引入 userStore,替换硬编码头像和昵称**
|
||||
- [ ] **Step 2: 点击 profile-card 跳转编辑页**
|
||||
- [ ] **Step 3: Commit** `feat: 我的页面显示真实头像和昵称`
|
||||
|
||||
---
|
||||
|
||||
#### Task 7: 首页显示真实昵称
|
||||
|
||||
**Files:** `client/src/pages/index/index.vue`
|
||||
|
||||
- [ ] **Step 1: 引入 userStore,替换 `小菜` 为 `userStore.nickname`**
|
||||
- [ ] **Step 2: Commit** `feat: 首页问候语显示真实昵称`
|
||||
|
||||
---
|
||||
|
||||
### Part 2: 一起记功能(Task 8-14)
|
||||
|
||||
#### Task 8: 数据库 — 群组表
|
||||
|
||||
**Files:** `server/src/db/schema.sql`, `server/src/db/init.ts`
|
||||
|
||||
- [ ] **Step 1: schema.sql 添加 groups, group_members 表,transactions 添加 group_id**
|
||||
|
||||
注意:`transactions.group_id` **不加外键约束**,因为退出群组时需要保留记录(group_id 设为 NULL)。
|
||||
|
||||
- [ ] **Step 2: init.ts runMigrations 添加群组迁移**
|
||||
|
||||
```typescript
|
||||
const hasGroupId = await columnExists(conn, 'transactions', 'group_id')
|
||||
if (!hasGroupId) {
|
||||
// 创建 groups 表
|
||||
// 创建 group_members 表
|
||||
// ALTER TABLE transactions ADD COLUMN group_id INT DEFAULT NULL
|
||||
// ALTER TABLE transactions ADD INDEX idx_group_date (group_id, date)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证** `cd server && npm run db:init`
|
||||
- [ ] **Step 4: Commit** `feat: 群组表创建,transactions 添加 group_id`
|
||||
|
||||
---
|
||||
|
||||
#### Task 9: 群组 API
|
||||
|
||||
**Files:** Create `server/src/routes/group.ts`, Modify `server/src/index.ts`
|
||||
|
||||
- [ ] **Step 1: 创建 group 路由**
|
||||
|
||||
| 接口 | 关键逻辑 |
|
||||
|------|---------|
|
||||
| `POST /` | 生成邀请码,事务插入 groups + group_members(role=owner) |
|
||||
| `GET /` | JOIN group_members,含 member_count |
|
||||
| `GET /:id` | 验证成员身份,返回群组信息 + 成员列表(含 nickname, avatar_url) |
|
||||
| `POST /join` | 验证邀请码,检查非成员,插入 group_members(role=member) |
|
||||
| `POST /:id/leave` | **关键:先 `UPDATE transactions SET group_id = NULL WHERE user_id = ? AND group_id = ?`,再 `DELETE FROM group_members`** |
|
||||
| `DELETE /:id` | 仅 owner,`DELETE FROM groups`(CASCADE 清理 members,transactions.group_id 因无外键需手动清理) |
|
||||
|
||||
**解散群组时手动清理 transactions:**
|
||||
```sql
|
||||
UPDATE transactions SET group_id = NULL WHERE group_id = ?
|
||||
DELETE FROM groups WHERE id = ?
|
||||
```
|
||||
|
||||
- [ ] **Step 2: index.ts 注册路由**
|
||||
- [ ] **Step 3: Commit** `feat: 群组 API`
|
||||
|
||||
---
|
||||
|
||||
#### Task 10: 交易和统计 API 支持群组视图
|
||||
|
||||
**Files:** `server/src/routes/transaction.ts`, `server/src/routes/stats.ts`
|
||||
|
||||
**核心改造:查询逻辑根据 `group_id` 参数切换视图模式。**
|
||||
|
||||
- [ ] **Step 1: transaction.ts GET / 改造**
|
||||
|
||||
```typescript
|
||||
const { group_id } = req.query
|
||||
|
||||
if (group_id && group_id !== 'null') {
|
||||
// 群组视图:群组下所有成员的记录
|
||||
// 先验证当前用户是群组成员
|
||||
const [memberCheck] = await pool.query(
|
||||
'SELECT id FROM group_members WHERE group_id = ? AND user_id = ?',
|
||||
[group_id, req.userId]
|
||||
)
|
||||
if ((memberCheck as any[]).length === 0) {
|
||||
return res.status(403).json({ code: 40300, message: '无权访问此群组' })
|
||||
}
|
||||
where = `WHERE t.group_id = ? AND t.user_id IN (SELECT user_id FROM group_members WHERE group_id = ?)`
|
||||
params = [group_id, group_id]
|
||||
} else {
|
||||
// 个人视图:我的所有记录(不管 group_id)
|
||||
where = 'WHERE t.user_id = ?'
|
||||
params = [req.userId]
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: transaction.ts POST / 改造**
|
||||
|
||||
创建记录时接受可选 `group_id` 参数,写入数据库。
|
||||
|
||||
- [ ] **Step 3: transaction.ts PUT/DELETE 改造**
|
||||
|
||||
编辑/删除权限:只能操作自己 `user_id` 的记录(不管 group_id)。
|
||||
|
||||
- [ ] **Step 4: stats.ts 三个路由改造**
|
||||
|
||||
与 transaction 相同的视图切换逻辑。
|
||||
|
||||
- [ ] **Step 5: Commit** `feat: 交易和统计 API 支持群组视图`
|
||||
|
||||
---
|
||||
|
||||
#### Task 11: 群组客户端 Store
|
||||
|
||||
**Files:** Create `client/src/api/group.ts`, Create `client/src/stores/group.ts`, Modify `client/src/stores/transaction.ts`, Modify `client/src/stores/stats.ts`
|
||||
|
||||
- [ ] **Step 1: api/group.ts** — 封装群组 API
|
||||
- [ ] **Step 2: stores/group.ts**
|
||||
|
||||
```typescript
|
||||
// 核心状态
|
||||
const currentGroupId = ref<number | null>(null) // null = 个人模式
|
||||
const isGroupMode = computed(() => currentGroupId.value !== null)
|
||||
|
||||
// 切换方法
|
||||
function switchToPersonal() {
|
||||
currentGroupId.value = null
|
||||
uni.removeStorageSync('xc:currentGroupId')
|
||||
}
|
||||
function switchToGroup(id: number) {
|
||||
currentGroupId.value = id
|
||||
uni.setStorageSync('xc:currentGroupId', id)
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 更新 transaction/stats Store**
|
||||
|
||||
每个 fetch 方法自动读取 `useGroupStore().currentGroupId` 并传给 API。
|
||||
|
||||
- [ ] **Step 4: Commit** `feat: 群组 Store + 数据层自动跟随`
|
||||
|
||||
---
|
||||
|
||||
#### Task 12: 「我的」页身份切换
|
||||
|
||||
**Files:** `client/src/pages/profile/index.vue`
|
||||
|
||||
- [ ] **Step 1: 在 profile-card 和 quick-stats 之间添加身份切换卡片**
|
||||
|
||||
```vue
|
||||
<view class="identity-card" @tap="showIdentityPicker">
|
||||
<Icon :name="groupStore.isGroupMode ? 'users' : 'user'" :size="32" color="#FF8C69" />
|
||||
<view class="id-info">
|
||||
<text class="id-name">{{ groupStore.isGroupMode ? groupStore.currentGroup?.name : '个人账本' }}</text>
|
||||
<text class="id-desc">{{ groupStore.isGroupMode ? groupStore.currentGroup?.member_count + ' 人共享' : '仅自己可见' }}</text>
|
||||
</view>
|
||||
<Icon name="chevronRight" :size="24" color="#BFB3B3" />
|
||||
</view>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 底部弹出身份选择面板**
|
||||
|
||||
```vue
|
||||
<view class="identity-modal" v-if="showIdentity" @tap="showIdentity = false">
|
||||
<view class="modal-content" @tap.stop>
|
||||
<text class="modal-title">切换账本</text>
|
||||
|
||||
<!-- 个人账本 -->
|
||||
<view class="id-option" :class="{ active: !groupStore.isGroupMode }" @tap="switchToPersonal">
|
||||
<Icon name="user" :size="36" ... />
|
||||
<text>个人账本</text>
|
||||
<Icon v-if="!groupStore.isGroupMode" name="check" ... />
|
||||
</view>
|
||||
|
||||
<!-- 群组列表 -->
|
||||
<view v-for="g in groupStore.groups" ... @tap="switchToGroup(g.id)">
|
||||
...
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<view class="id-actions">
|
||||
<view @tap="promptCreateGroup">创建群组</view>
|
||||
<view @tap="promptJoinGroup">加入群组</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 创建群组交互**
|
||||
|
||||
点击「创建群组」→ `uni.showModal` 输入名称 → 调用 `groupStore.createGroup()` → 成功后 `uni.setClipboardData` 复制邀请码 + Toast
|
||||
|
||||
- [ ] **Step 4: 加入群组交互**
|
||||
|
||||
点击「加入群组」→ `uni.showModal` 输入邀请码 → 调用 `groupStore.joinGroup()` → 成功后 Toast
|
||||
|
||||
- [ ] **Step 5: 切换后刷新数据**
|
||||
|
||||
切换身份后调用 `refreshData()` 重新加载当前页的 stats 和 transactions。
|
||||
|
||||
- [ ] **Step 6: Commit** `feat: 我的页身份切换`
|
||||
|
||||
---
|
||||
|
||||
#### Task 13: 群组管理页面
|
||||
|
||||
**Files:** Create `client/src/pages/group-manage/index.vue`, Modify `client/src/pages.json`
|
||||
|
||||
- [ ] **Step 1: pages.json 注册页面**
|
||||
- [ ] **Step 2: 创建群组管理页面**
|
||||
|
||||
页面功能:
|
||||
- 群组列表(卡片形式,显示名称、人数、邀请码)
|
||||
- 点击邀请码可复制
|
||||
- 成员列表
|
||||
- 「退出群组」按钮(普通成员)— 二次确认:"退出后你在该群组的记录将回到个人账本"
|
||||
- 「解散群组」按钮(群主)— 二次确认:"所有群组记录将被清除"
|
||||
|
||||
- [ ] **Step 3: Commit** `feat: 群组管理页面`
|
||||
|
||||
---
|
||||
|
||||
#### Task 14: App.vue 初始化 + 最终集成
|
||||
|
||||
**Files:** `client/src/App.vue`, `client/src/pages/index/index.vue`
|
||||
|
||||
- [ ] **Step 1: App.vue 初始化 groupStore**
|
||||
|
||||
登录成功后 `groupStore.init()` + `groupStore.fetchGroups()`
|
||||
|
||||
- [ ] **Step 2: 首页 fetchTodayData 传 group_id**
|
||||
|
||||
```typescript
|
||||
const groupStore = useGroupStore()
|
||||
const data = await getOverview({ startDate: today, endDate: today, group_id: groupStore.currentGroupId })
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 验证完整流程**
|
||||
|
||||
- [ ] **Step 4: Commit** `feat: 集成完成`
|
||||
|
||||
---
|
||||
|
||||
## 六、验证清单
|
||||
|
||||
### 用户信息
|
||||
- [ ] 「我的」页显示真实头像和昵称
|
||||
- [ ] 点击头像区域跳转编辑页,可上传头像(存到服务端)、修改昵称
|
||||
- [ ] 保存后首页/我的页同步更新
|
||||
|
||||
### 一起记 — 创建/加入
|
||||
- [ ] 身份切换面板中点击「创建群组」→ 输入名称 → 获得邀请码(自动复制)
|
||||
- [ ] 点击「加入群组」→ 输入邀请码 → 加入成功
|
||||
- [ ] 身份列表中出现新群组
|
||||
|
||||
### 一起记 — 记账归属
|
||||
- [ ] 群组模式下记账 → 记录归属自己(user_id = 我),group_id = 群组
|
||||
- [ ] 个人视图:看到自己所有记录(含群组中记的)
|
||||
- [ ] 群组视图:看到所有成员在该群组下记的记录
|
||||
- [ ] 编辑/删除:只能操作自己记的记录
|
||||
|
||||
### 一起记 — 退出/解散
|
||||
- [ ] 退出群组 → 自己的记录回到个人视图,群组看不到
|
||||
- [ ] 解散群组 → 群组消失,所有人的记录回到个人视图
|
||||
- [ ] 退出/解散后自动切回个人模式
|
||||
Reference in New Issue
Block a user