3ae141ba56
- Fix other members' status not visible due to users collection viewRule restriction - Fix empty status treated as 'away' instead of 'idle' in membersByStatus - Auto-set creator to 'in_team' status when creating team session - Filter current user from idle members invite list - Fix group store isGroupOwner using pb.authStore instead of localStorage - Add nginx no-cache headers for index.html - Add join_requests collection migration and join approval flow - Update groups collection rules and add requireApproval field - Add Memory types for Phase 2 planning Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
584 lines
15 KiB
Markdown
584 lines
15 KiB
Markdown
# Game Group V2 设计文档
|
||
|
||
**日期**: 2026-04-17
|
||
**版本**: v2.0
|
||
**目标**: 重构游戏小队约玩系统,简化组队和投票流程,改进多群组隔离性
|
||
|
||
---
|
||
|
||
## 一、项目概述
|
||
|
||
### 1.1 现有问题分析
|
||
|
||
基于现有 game-group 项目的使用反馈:
|
||
|
||
- **组队麻烦** - 原有匹配机制难以避免时间对齐问题,朋友之间闹矛盾会被强制匹配到一起
|
||
- **投票麻烦** - 投票流程过于复杂
|
||
- **隔离性不足** - 只有一个群组,缺乏多群组支持
|
||
|
||
### 1.2 设计目标
|
||
|
||
- **握手式匹配** - 类似 Steam 邀请机制,自主选择是否加入
|
||
- **多群组支持** - 每人可加入 3-10 个群组(管理员可配置)
|
||
- **临时小组** - 组队后创建临时小组,游戏结束后解散
|
||
- **轻量级后端** - 使用自建 BaaS 服务(PocketBase),部署在 NAS
|
||
|
||
---
|
||
|
||
## 二、技术架构
|
||
|
||
### 2.1 技术栈
|
||
|
||
| 层级 | 技术选型 | 说明 |
|
||
|------|---------|------|
|
||
| 后端 | PocketBase | Go 语言,单文件可执行,内置 SQLite |
|
||
| 数据库 | SQLite | 轻量级,适合 NAS 部署 |
|
||
| 前端 | Vue 3 + TypeScript | 保持原有技术栈 |
|
||
| UI 框架 | Element Plus | 保持原有技术栈 |
|
||
| 样式 | Tailwind CSS | 保持原有技术栈 |
|
||
| 实时通信 | WebSocket | PocketBase 内置支持 |
|
||
|
||
### 2.2 部署架构
|
||
|
||
```
|
||
NAS 服务器
|
||
├── PocketBase (端口可配置,默认 8090)
|
||
│ ├── SQLite 数据库文件
|
||
│ └── 上传文件存储
|
||
└── 前端静态文件 (可选 NAS Web 服务器)
|
||
```
|
||
|
||
---
|
||
|
||
## 三、数据模型
|
||
|
||
### 3.1 核心数据集合
|
||
|
||
```javascript
|
||
// users - 用户
|
||
{
|
||
id: string
|
||
username: string
|
||
email: string
|
||
avatar: string
|
||
status: "idle" | "working" | "in_team" | "away" // 用户状态
|
||
statusNote: string // 状态备注/拒绝原因
|
||
maxGroups: number // 可加入群组数(默认5)
|
||
workdays: number[] // 工作日 [1-7]
|
||
workStartTime: string // 工作开始时间 "HH:mm"
|
||
nextWorkTime: number // 下次工作时间戳
|
||
points: number // 积分(二期)
|
||
createdAt: date
|
||
updatedAt: date
|
||
}
|
||
|
||
// groups - 群组(长期)
|
||
{
|
||
id: string
|
||
name: string
|
||
description: string
|
||
owner: string (userId)
|
||
members: string[] (userIds)
|
||
maxMembers: number
|
||
honors: array (二期 - 荣誉墙)
|
||
createdAt: date
|
||
updatedAt: date
|
||
}
|
||
|
||
// teamSessions - 临时小组(短期)
|
||
{
|
||
id: string
|
||
sourceGroup: string (groupId)
|
||
name: string
|
||
gameName: string
|
||
members: string[] (userIds)
|
||
status: "recruiting" | "playing" | "finished" | "dissolved"
|
||
createdAt: date
|
||
dissolvedAt: date
|
||
}
|
||
|
||
// invitations - 邀请记录
|
||
{
|
||
id: string
|
||
from: string (userId)
|
||
to: string (userId)
|
||
teamSession: string (teamSessionId)
|
||
status: "pending" | "accepted" | "rejected"
|
||
rejectReason: string
|
||
createdAt: date
|
||
respondedAt: date
|
||
}
|
||
|
||
// games - 游戏库
|
||
{
|
||
id: string
|
||
name: string
|
||
platform: "PC" | "PS5" | "Xbox" | "Switch" | "Mobile"
|
||
tags: string[]
|
||
cover: string (url)
|
||
popularCount: number
|
||
createdAt: date
|
||
}
|
||
```
|
||
|
||
### 3.2 二期数据集合
|
||
|
||
```javascript
|
||
// appointments - 预约系统
|
||
{
|
||
id: string
|
||
groupId: string
|
||
gameName: string
|
||
scheduledTime: date
|
||
initiator: string (userId)
|
||
participants: array
|
||
[{ userId, status: "going" | "maybe" | "not_going" }]
|
||
note: string
|
||
status: "pending" | "confirmed" | "cancelled"
|
||
createdAt: date
|
||
}
|
||
|
||
// pointsHistory - 积分记录
|
||
{
|
||
id: string
|
||
userId: string
|
||
groupId: string
|
||
type: "team_joined" | "game_completed" | "appointment_attended" | "abandoned"
|
||
amount: number
|
||
reason: string
|
||
createdAt: date
|
||
}
|
||
|
||
// honors - 荣誉墙
|
||
{
|
||
id: string
|
||
userId: string
|
||
groupId: string
|
||
title: string
|
||
description: string
|
||
icon: string
|
||
awardedAt: date
|
||
}
|
||
|
||
// memories - 多媒体记忆(音视频等)
|
||
{
|
||
id: string
|
||
groupId: string
|
||
uploader: string (userId)
|
||
title: string
|
||
description: string
|
||
file: string (PocketBase file field)
|
||
fileType: "image" | "video" | "audio" | "other"
|
||
size: number (bytes)
|
||
createdAt: date
|
||
}
|
||
```
|
||
|
||
### 3.3 三期数据集合
|
||
|
||
```javascript
|
||
// ledgers - 账目
|
||
{
|
||
id: string
|
||
groupId: string
|
||
type: "income" | "expense" | "settlement"
|
||
amount: number
|
||
category: string
|
||
description: string
|
||
createdBy: string (userId)
|
||
relatedMembers: string[] (userIds)
|
||
settled: boolean
|
||
occurredAt: date
|
||
createdAt: date
|
||
}
|
||
|
||
// ledgerShares - 分摊明细
|
||
{
|
||
id: string
|
||
ledgerId: string
|
||
userId: string
|
||
shareAmount: number
|
||
paidAmount: number
|
||
status: "pending" | "paid" | "waived"
|
||
}
|
||
|
||
// assets - 资产
|
||
{
|
||
id: string
|
||
groupId: string
|
||
name: string
|
||
type: "game_account" | "console" | "equipment"
|
||
credentials: string (加密)
|
||
status: "available" | "borrowed" | "maintenance"
|
||
currentBorrower: string (userId)
|
||
note: string
|
||
createdAt: date
|
||
}
|
||
|
||
// borrowLogs - 借还记录
|
||
{
|
||
id: string
|
||
assetId: string
|
||
borrower: string (userId)
|
||
borrowTime: date
|
||
returnTime: date
|
||
conditionNote: string
|
||
status: "active" | "returned" | "damaged"
|
||
}
|
||
```
|
||
|
||
### 3.4 四期数据集合
|
||
|
||
```javascript
|
||
// blacklist - 黑名单
|
||
{
|
||
id: string
|
||
groupId: string
|
||
reporter: string (userId)
|
||
targetGameId: string
|
||
reason: string
|
||
category: "behavior" | "cheating" | "abandonment" | "other"
|
||
description: string
|
||
evidence: string[] (urls)
|
||
status: "pending" | "approved" | "rejected"
|
||
reviewedBy: string (userId)
|
||
reviewedNote: string
|
||
createdAt: date
|
||
reviewedAt: date
|
||
}
|
||
|
||
// bets - 竞猜
|
||
{
|
||
id: string
|
||
groupId: string
|
||
title: string
|
||
type: "winner" | "score" | "custom"
|
||
options: array
|
||
[{ id, name, odds }]
|
||
participants: object (userId -> optionId)
|
||
stakeAmount: number
|
||
status: "open" | "closed" | "settled"
|
||
resultOptionId: string
|
||
createdBy: string (userId)
|
||
closeTime: date
|
||
createdAt: date
|
||
settledAt: date
|
||
}
|
||
|
||
// betParticipations - 下注记录
|
||
{
|
||
id: string
|
||
betId: string
|
||
userId: string
|
||
selectedOptionId: string
|
||
stakeAmount: number
|
||
winAmount: number
|
||
status: "pending" | "won" | "lost"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 四、核心功能设计
|
||
|
||
### 4.1 用户状态(中文化)
|
||
|
||
| 状态值 | 显示 | 图标 |
|
||
|--------|------|------|
|
||
| idle | 空闲 | 🟢 |
|
||
| working | 工作中 | 🔴 |
|
||
| in_team | 组队中 | 🔵 |
|
||
| away | 离开 | ⚫ |
|
||
|
||
### 4.2 握手匹配流程
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ 状态管理与匹配 │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
|
||
1. 用户设置状态
|
||
用户 → 点击"我空了" → status: idle
|
||
用户 → 设置工作时间 → workSchedule 更新
|
||
定时任务 → 检查 nextWorkTime → 自动变更为 working
|
||
|
||
2. 查看空闲成员
|
||
进入群组 → 显示 members 中 status=idle 的用户
|
||
实时订阅 → members 状态变更时自动更新列表
|
||
|
||
3. 发起邀请
|
||
选择空闲成员 → 创建 teamSession (status: recruiting)
|
||
批量创建 invitations (status: pending)
|
||
被邀请者收到实时通知
|
||
|
||
4. 响应邀请
|
||
接受 → invitation.status: accepted, user.status: in_team
|
||
加入 teamSession.members
|
||
拒绝 → invitation.status: rejected, 填写 rejectReason
|
||
发送者看到拒绝原因
|
||
|
||
5. 组队完成
|
||
所有受邀者响应 → teamSession.status: playing
|
||
显示临时小组信息
|
||
|
||
6. 解散小组
|
||
任何人点击"游戏结束" → teamSession.status: dissolved
|
||
所有成员 status 恢复原状
|
||
```
|
||
|
||
### 4.3 工作时间自动管理
|
||
|
||
```
|
||
用户设置:
|
||
- workdays: [1,2,3,4,5] (周一到周五)
|
||
- workStartTime: "09:00"
|
||
|
||
系统计算:
|
||
- nextWorkTime = 下一个工作日的 09:00
|
||
- 定时检查: 当前时间 >= nextWorkTime ? 自动变更为 working
|
||
- 手动改状态后: nextWorkTime = 下一个工作日的 09:00
|
||
```
|
||
|
||
### 4.4 邀请机制
|
||
|
||
- **一对一邀请** - 逐个发起,可连续邀请
|
||
- **拒绝原因隐私** - 只有发起邀请的人能看到拒绝原因
|
||
- **实时通知** - 使用 PocketBase 实时订阅推送邀请
|
||
|
||
---
|
||
|
||
## 五、前端界面设计
|
||
|
||
### 5.1 页面结构
|
||
|
||
```
|
||
登录/注册页
|
||
↓
|
||
主布局
|
||
├── 侧边栏
|
||
│ ├── 群组列表
|
||
│ ├── 工作时间设置
|
||
│ └── 个人状态切换
|
||
├── 主内容区
|
||
│ ├── 当前群组视图
|
||
│ │ ├── 空闲成员列表
|
||
│ │ │ └── 每个成员: 头像 + 状态 + "邀请"按钮
|
||
│ │ ├── 游戏选择
|
||
│ │ └── 我的临时小组
|
||
│ │ └── 成员状态 + 游戏结束按钮
|
||
│ └── 游戏库页面
|
||
│ ├── 游戏列表/搜索
|
||
│ └── 热门游戏
|
||
└── 通知中心
|
||
└── 邀请通知 (接受/拒绝)
|
||
```
|
||
|
||
### 5.2 核心交互
|
||
|
||
1. **状态切换**
|
||
- 顶部状态指示器,点击切换
|
||
- 工作时间弹窗设置
|
||
|
||
2. **邀请流程**
|
||
- 点击成员旁"邀请" → 选择游戏 → 创建临时小组 → 发送邀请
|
||
- 已邀请的成员显示"等待响应"
|
||
|
||
3. **响应邀请**
|
||
- 通知中心弹出邀请卡片
|
||
- 显示:发起人、游戏、其他已加入成员
|
||
- 按钮:接受 / 拒绝(输入原因)
|
||
|
||
4. **临时小组**
|
||
- 显示所有已加入成员
|
||
- 谁都可以点"游戏结束"解散
|
||
|
||
---
|
||
|
||
## 六、功能分期
|
||
|
||
### 第一期(MVP)
|
||
|
||
**目标**: 核心组队功能
|
||
|
||
- [x] 用户认证(注册/登录)
|
||
- [x] 状态管理(空闲/工作中/组队中/离开)
|
||
- [x] 工作时间设定
|
||
- [x] 群组管理(创建/加入/退出)
|
||
- [x] 握手式邀请(发起/接受/拒绝)
|
||
- [x] 临时小组(创建/解散)
|
||
- [x] 游戏库(列表/搜索/热门)
|
||
- [x] 实时状态同步
|
||
|
||
### 第二期
|
||
|
||
**目标**: 预约 + 积分 + 荣誉 + 记忆
|
||
|
||
- [ ] 预约系统(简化投票)
|
||
- [ ] 积分系统(获取/记录)
|
||
- [ ] 荣誉墙(自动授予)
|
||
- [ ] 多媒体记忆(支持音视频等多媒体文件的上传、下载与在线预览)
|
||
|
||
### 第三期
|
||
|
||
**目标**: 财务 + 资产
|
||
|
||
- [ ] 账目管理(记录/分摊/结算)
|
||
- [ ] 资产管理(登记/借用/归还)
|
||
|
||
### 第四期
|
||
|
||
**目标**: 黑名单 + 竞猜
|
||
|
||
- [ ] 黑名单系统(举报/审核)
|
||
- [ ] 竞猜系统(创建/下注/开奖)
|
||
|
||
---
|
||
|
||
## 七、项目文件结构
|
||
|
||
```
|
||
game-group-v2/
|
||
├── backend/
|
||
│ ├── pocketbase/
|
||
│ │ ├── pb_migrations/ # 数据库迁移文件
|
||
│ │ ├── pb_data/ # 数据存储(SQLite)
|
||
│ │ └── pocketbase # 可执行文件
|
||
│ ├── .env # 配置(端口等)
|
||
│ └── docker-compose.yml # 可选:容器化部署
|
||
│
|
||
├── frontend/
|
||
│ ├── src/
|
||
│ │ ├── api/
|
||
│ │ │ ├── pocketbase.ts # PB 实例初始化
|
||
│ │ │ ├── users.ts # 用户相关
|
||
│ │ │ ├── groups.ts # 群组相关
|
||
│ │ │ ├── sessions.ts # 临时小组相关
|
||
│ │ │ ├── invitations.ts # 邀请相关
|
||
│ │ │ └── games.ts # 游戏库相关
|
||
│ │ │ ├── appointments.ts # 预约(二期)
|
||
│ │ │ ├── ledgers.ts # 账目(三期)
|
||
│ │ │ ├── assets.ts # 资产(三期)
|
||
│ │ │ ├── blacklist.ts # 黑名单(四期)
|
||
│ │ │ └── bets.ts # 竞猜(四期)
|
||
│ │ ├── components/
|
||
│ │ │ ├── common/ # 通用组件
|
||
│ │ │ ├── layout/ # 布局组件
|
||
│ │ │ └── team/ # 组队相关组件
|
||
│ │ │ ├── StatusToggle.vue # 状态切换
|
||
│ │ │ ├── IdleMembersList.vue # 空闲成员
|
||
│ │ │ ├── InviteButton.vue # 邀请按钮
|
||
│ │ │ ├── InvitationCard.vue # 邀请卡片
|
||
│ │ │ ├── TeamSessionPanel.vue # 临时小组面板
|
||
│ │ │ └── WorkScheduleModal.vue # 工作时间设置
|
||
│ │ ├── views/
|
||
│ │ │ ├── Login.vue
|
||
│ │ │ ├── Register.vue
|
||
│ │ │ ├── GroupView.vue # 群组主页
|
||
│ │ │ └── GamesLibrary.vue # 游戏库
|
||
│ │ ├── stores/
|
||
│ │ │ ├── user.ts # 用户状态
|
||
│ │ │ ├── group.ts # 当前群组
|
||
│ │ │ └── team.ts # 当前临时小组
|
||
│ │ ├── types/
|
||
│ │ │ ├── user.ts
|
||
│ │ │ ├── group.ts
|
||
│ │ │ ├── session.ts
|
||
│ │ │ └── game.ts
|
||
│ │ ├── router/
|
||
│ │ ├── utils/
|
||
│ │ └── App.vue
|
||
│ ├── .env # API_URL 等配置
|
||
│ ├── package.json
|
||
│ └── vite.config.ts
|
||
│
|
||
└── docs/
|
||
├── design.md # 本设计文档
|
||
├── api.md # API 文档
|
||
└── migration.md # 迁移指南
|
||
```
|
||
|
||
---
|
||
|
||
## 八、配置说明
|
||
|
||
### 8.1 后端环境变量
|
||
|
||
```env
|
||
# .env
|
||
PB_PORT=8090 # PocketBase 端口(可配置)
|
||
PB_DATA=./pb_data # 数据存储路径
|
||
PB_MIGRATIONS=./pb_migrations
|
||
```
|
||
|
||
### 8.2 前端环境变量
|
||
|
||
```env
|
||
# .env
|
||
VITE_PB_URL=http://your-nas-ip:8090
|
||
```
|
||
|
||
---
|
||
|
||
## 九、从原项目迁移
|
||
|
||
### 9.1 保留的设计
|
||
|
||
| 功能 | 原实现 | 新实现 |
|
||
|------|--------|--------|
|
||
| 用户认证 | JWT + NestJS | PocketBase 内置 |
|
||
| 权限管理 | 自定义 Guard | PocketBase API Rules |
|
||
| 实时通信 | - | PocketBase 实时订阅 |
|
||
| 前端框架 | Vue 3 + Element Plus | 保持不变 |
|
||
|
||
### 9.2 主要改进
|
||
|
||
| 方面 | 原实现 | 新实现 |
|
||
|------|--------|--------|
|
||
| 匹配方式 | 自动匹配 | 握手式邀请 |
|
||
| 投票 | 复杂投票 | 快速响应(去/可能/不去) |
|
||
| 群组 | 单群组 | 多群组 + 临时小组 |
|
||
| 数据库 | MySQL | SQLite |
|
||
| 后端 | NestJS | PocketBase |
|
||
|
||
---
|
||
|
||
## 十、附录
|
||
|
||
### 10.1 状态码对照表
|
||
|
||
| 类别 | 值 | 显示 |
|
||
|------|-----|------|
|
||
| 用户状态 | idle | 空闲 🟢 |
|
||
| | working | 工作中 🔴 |
|
||
| | in_team | 组队中 🔵 |
|
||
| | away | 离开 ⚫ |
|
||
| 临时小组 | recruiting | 招募中 |
|
||
| | playing | 游戏中 |
|
||
| | finished | 已结束 |
|
||
| | dissolved | 已解散 |
|
||
| 邀请 | pending | 等待响应 |
|
||
| | accepted | 已接受 |
|
||
| | rejected | 已拒绝 |
|
||
|
||
### 10.2 积分规则(二期)
|
||
|
||
| 行为 | 积分 |
|
||
|------|------|
|
||
| 成功组队 | +10 |
|
||
| 完成游戏 | +20 |
|
||
| 准时赴约 | +15 |
|
||
| 放鸽子 | -30 |
|
||
|
||
### 10.3 荣誉徽章(二期)
|
||
|
||
| 条件 | 徽章 |
|
||
|------|------|
|
||
| 组队100次 | 组队达人 |
|
||
| 连续10次赴约 | 全勤玩家 |
|
||
| 积分TOP1 | 群组之星 |
|
||
|
||
---
|
||
|
||
**文档版本**: v1.0
|
||
**更新日期**: 2026-04-17
|