Files
gamegroup2/docs/plans/2026-04-17-fix-and-beautify.md
T
2026-04-17 19:35:15 +08:00

882 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 一期修复 + UI 美化实施计划
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** 修复后端核心流程断裂和安全问题,补齐缺失组件,全面美化前端 UI 使其达到可用的 MVP 水平。
**Architecture:** 后端修复 PocketBase hooks 中无效 API 调用和缺失的业务逻辑,补全 migration 和访问控制。前端基于 Element Plus + CSS 变量建立统一设计系统,重构 Layout 为侧边栏布局,美化所有页面。
**Tech Stack:** PocketBase 0.22.4, Vue 3 + TypeScript, Element Plus, Tailwind CSS (已安装但未启用)
---
## Phase 1: 后端 Critical 修复(5 个 Task
### Task 1: 恢复 Migration 文件
**Files:**
- Move: `backend/pb_migrations.bak/go.mod``backend/pb_hooks/go.mod`(如需要)
- Move: `backend/pb_migrations.bak/main.go` → 确认是否为旧 hook 代码
- Create: `backend/pb_migrations/1738717600_init.pb.js`
**Step 1: 清理 pb_migrations.bak**
`pb_migrations.bak/` 里的 `go.mod``main.go` 是错误放置的文件。确认 `pb_hooks/` 目录已有完整的 `go.mod``main.go`,则 `.bak` 内容可忽略。
**Step 2: 创建正式 migration 文件**
创建 `backend/pb_migrations/1738717600_init.pb.js`,基于 PocketBase 0.22.4 的 JS migration API。注意:PocketBase 0.22.x 使用 `migrate()` 函数和 `app.save(collection)` 而非旧版 API。实际集合已在 `pb_data/data.db` 中存在,此 migration 作为可重建备份。
但由于当前数据库已包含集合定义,**不建议用 JS migration 重建**。更好的方案:
1. 确认 `pb_data/data.db` 中集合定义完整
2. 在 README 中记录"数据库通过管理后台创建,首次需手动建表"
3. 清空 `pb_migrations.bak/` 目录
**Step 3: 提交**
```bash
git add -A backend/
git commit -m "fix: clean up migration directory structure"
```
---
### Task 2: 修复后端 Hooks — 删除无效 API + 修复 Invitation 逻辑
**Files:**
- Modify: `backend/pb_hooks/main.go` (全文重写)
**Step 1: 重写 main.go**
删除所有 `app.Subscriptions().*` 调用(PocketBase 0.22.x 无此 API)。修复 invitation 创建逻辑:将 `group` 字段引用改为 `teamSession`。添加 invitation accept hook。
```go
package main
import (
"log"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
)
func main() {
app := pocketbase.New()
// ── Groups ──
app.OnRecordBeforeCreateRequest("groups").Add(func(e *core.RecordCreateEvent) error {
authRecord, _ := e.HttpContext.AuthRecord()
if authRecord == nil {
return apis.NewForbiddenError("需要登录", nil)
}
e.Record.Set("owner", authRecord.Id)
e.Record.Set("members", []string{authRecord.Id})
return nil
})
app.OnRecordBeforeUpdateRequest("groups").Add(func(e *core.RecordUpdateEvent) error {
authRecord, _ := e.HttpContext.AuthRecord()
if authRecord == nil {
return apis.NewForbiddenError("需要登录", nil)
}
// 保护 owner 字段不被篡改
originalOwner, _ := app.Dao().FindRecordById("groups", e.Record.Id)
if originalOwner.GetString("owner") != authRecord.Id {
return apis.NewForbiddenError("只有群组所有者可以更新群组", nil)
}
e.Record.Set("owner", originalOwner.GetString("owner"))
// 验证 members 数量
members := e.Record.GetStringSlice("members")
maxMembers := e.Record.GetInt("maxMembers")
if maxMembers > 0 && len(members) > maxMembers {
return apis.NewBadRequestError("成员数量超过上限", nil)
}
return nil
})
app.OnRecordBeforeDeleteRequest("groups").Add(func(e *core.RecordDeleteEvent) error {
authRecord, _ := e.HttpContext.AuthRecord()
if authRecord == nil {
return apis.NewForbiddenError("需要登录", nil)
}
isOwner, err := isGroupOwner(app, e.Record.Id, authRecord.Id)
if err != nil || !isOwner {
return apis.NewForbiddenError("只有群组所有者可以删除群组", nil)
}
return nil
})
// ── Team Sessions ──
app.OnRecordBeforeCreateRequest("team_sessions").Add(func(e *core.RecordCreateEvent) error {
authRecord, _ := e.HttpContext.AuthRecord()
if authRecord == nil {
return apis.NewForbiddenError("需要登录", nil)
}
groupId := e.Record.GetString("sourceGroup")
isMember, err := isGroupMember(app, groupId, authRecord.Id)
if err != nil || !isMember {
return apis.NewForbiddenError("只有群组成员可以创建团队会话", nil)
}
return nil
})
// ── Invitations ──
app.OnRecordBeforeCreateRequest("invitations").Add(func(e *core.RecordCreateEvent) error {
authRecord, _ := e.HttpContext.AuthRecord()
if authRecord == nil {
return apis.NewForbiddenError("需要登录", nil)
}
// 强制设置 from 为当前用户
e.Record.Set("from", authRecord.Id)
e.Record.Set("status", "pending")
return nil
})
// 接受邀请:自动加入群组成员
app.OnRecordBeforeUpdateRequest("invitations").Add(func(e *core.RecordUpdateEvent) error {
authRecord, _ := e.HttpContext.AuthRecord()
if authRecord == nil {
return apis.NewForbiddenError("需要登录", nil)
}
// 只有 recipient 可以更新邀请
if e.Record.GetString("to") != authRecord.Id {
return apis.NewForbiddenError("无权操作此邀请", nil)
}
newStatus := e.Record.GetString("status")
if newStatus == "accepted" {
teamSessionId := e.Record.GetString("teamSession")
teamSession, err := app.Dao().FindRecordById("team_sessions", teamSessionId)
if err != nil {
return apis.NewNotFoundError("临时小组不存在", nil)
}
// 将用户加入临时小组 members
members := teamSession.GetStringSlice("members")
for _, m := range members {
if m == authRecord.Id {
goto afterJoin
}
}
members = append(members, authRecord.Id)
teamSession.Set("members", members)
if err := app.Dao().SaveRecord(teamSession); err != nil {
return apis.NewBadRequestError("加入临时小组失败", nil)
}
afterJoin:
// 更新用户状态为 in_team
user, err := app.Dao().FindRecordById("users", authRecord.Id)
if err == nil {
user.Set("status", "in_team")
app.Dao().SaveRecord(user)
}
}
return nil
})
if err := app.Start(); err != nil {
log.Fatal(err)
}
}
func isGroupMember(app *pocketbase.PocketBase, groupId string, userId string) (bool, error) {
group, err := app.Dao().FindRecordById("groups", groupId)
if err != nil {
return false, err
}
members := group.GetStringSlice("members")
for _, member := range members {
if member == userId {
return true, nil
}
}
return false, nil
}
func isGroupOwner(app *pocketbase.PocketBase, groupId string, userId string) (bool, error) {
group, err := app.Dao().FindRecordById("groups", groupId)
if err != nil {
return false, err
}
return group.GetString("owner") == userId, nil
}
```
**Step 2: 移除 README 中的默认凭据**
修改 `backend/README.md`,删除 `(admin/admin)` 部分。
**Step 3: 提交**
```bash
git add backend/pb_hooks/main.go backend/README.md
git commit -m "fix: rewrite hooks - remove invalid API calls, fix invitation accept flow, add access control"
```
---
### Task 3: 修复 Invitation 字段不一致
**Files:**
- Verify: `backend/pb_data/data.db``invitations` 集合的字段名
**Step 1: 确认数据库 schema**
通过 PocketBase 管理后台确认 `invitations` 集合中:
- `from` 字段存在且为 relation → users
- `to` 字段存在且为 relation → users
- `teamSession` 字段存在且为 relation → team_sessions
- `status` 为 select (pending/accepted/rejected)
- `rejectReason` 为 text
**Step 2: 确认 team_sessions 集合**
确认字段名是 `team_sessions`PocketBase 会将 camelCase 转为 snake_case)。前端 API 中使用 `teamSessions`,需要确认 PocketBase 客户端会自动转换。
**Step 3: 提交**
如有字段名修复:
```bash
git add backend/
git commit -m "fix: verify and align collection field names"
```
---
### Task 4: 修复前端 API 层与后端对齐
**Files:**
- Modify: `frontend/src/api/invitations.ts`
- Modify: `frontend/src/api/sessions.ts`
**Step 1: 修复 invitations.ts**
移除客户端权限校验(后端已处理)。移除 `respondInvitation` 中直接操作 `teamSession.members` 的代码(后端 hook 已处理)。
```typescript
// 响应邀请
export async function respondInvitation(
invitationId: string,
response: 'accepted' | 'rejected',
rejectReason?: string
) {
const updateData: Record<string, unknown> = {
status: response,
respondedAt: new Date().toISOString()
}
if (response === 'rejected' && rejectReason) {
updateData.rejectReason = rejectReason
}
await pb.collection('invitations').update(invitationId, updateData)
if (response === 'accepted') {
// 后端 hook 会自动处理:加入 team members + 更新用户状态
// 前端只需刷新本地状态
}
return updateData
}
```
**Step 2: 修复 sessions.ts 中的 endGame**
`endGame` 函数直接更新所有成员状态,但后端无对应权限。改为仅解散小组,状态恢复由后端处理或由各用户自行操作。
```typescript
export async function endGame(sessionId: string) {
return updateTeamStatus(sessionId, 'dissolved')
}
```
**Step 3: 提交**
```bash
git add frontend/src/api/invitations.ts frontend/src/api/sessions.ts
git commit -m "fix: align frontend API with backend hooks, remove client-side permission checks"
```
---
### Task 5: 修复前端组件 Bug
**Files:**
- Modify: `frontend/src/views/Home.vue`
- Modify: `frontend/src/views/GroupView.vue`
- Modify: `frontend/src/components/team/IdleMembersList.vue`
**Step 1: 修复 Home.vue 中的组件名**
```html
<!-- 修复前 -->
<idle-membersList />
<!-- 修复后 -->
<IdleMembersList />
```
**Step 2: 修复 GroupView.vue 中的组件名**
```html
<!-- 修复前 -->
<idle-membersList status="idle" />
<idle-membersList />
<!-- 修复后 -->
<IdleMembersList status="idle" />
<IdleMembersList />
```
**Step 3: 修复 IdleMembersList.vue 中 inviteMember 的空 teamSession**
当前 `inviteMember` 传入 `teamSession: ''`,需要先检查或创建 team session。暂时改为:如果没有活跃 team session,提示用户先在 Home 页面创建。
```typescript
async function inviteMember(userId: string, username: string) {
// 检查是否有活跃的 team session
const { getActiveTeamSession } = await import('@/api/sessions')
const session = await getActiveTeamSession()
if (!session) {
ElMessage.warning('请先在首页创建临时小组')
return
}
try {
await sendInvitation({
to: userId,
teamSession: session.id
})
ElMessage.success(`已邀请 ${username}`)
} catch (error: any) {
ElMessage.error(error.message || '邀请失败')
}
}
```
**Step 4: 提交**
```bash
git add frontend/src/views/Home.vue frontend/src/views/GroupView.vue frontend/src/components/team/IdleMembersList.vue
git commit -m "fix: component naming, invitation flow requires active team session"
```
---
## Phase 2: 补齐缺失组件(3 个 Task)
### Task 6: 创建 Notification Store 和 NotificationPanel 组件
**Files:**
- Create: `frontend/src/stores/notification.ts`
- Create: `frontend/src/components/common/NotificationPanel.vue`
**Step 1: 创建 notification store**
```typescript
// src/stores/notification.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Invitation } from '@/types'
import { getPendingInvitations } from '@/api/invitations'
export const useNotificationStore = defineStore('notification', () => {
const pendingInvitations = ref<Invitation[]>([])
const loading = ref(false)
const unreadCount = computed(() => pendingInvitations.value.length)
async function loadPendingInvitations() {
try {
loading.value = true
pendingInvitations.value = await getPendingInvitations()
} catch (error) {
console.error('加载待处理邀请失败:', error)
} finally {
loading.value = false
}
}
function removeInvitation(invitationId: string) {
pendingInvitations.value = pendingInvitations.value.filter(i => i.id !== invitationId)
}
return {
pendingInvitations,
loading,
unreadCount,
loadPendingInvitations,
removeInvitation
}
})
```
**Step 2: 创建 NotificationPanel 组件**
使用 `ElDrawer` 显示待处理邀请列表,内嵌 `InvitationCard` 组件。
**Step 3: 集成到 Layout.vue**
在 Layout 中导入 NotificationStore,通知铃铛按钮打开 NotificationPanel。
**Step 4: 提交**
```bash
git add frontend/src/stores/notification.ts frontend/src/components/common/NotificationPanel.vue frontend/src/views/Layout.vue
git commit -m "feat: add notification store and panel with invitation handling"
```
---
### Task 7: 创建 GameSelectDialog 组件
**Files:**
- Create: `frontend/src/components/team/GameSelectDialog.vue`
**Step 1: 创建游戏选择弹窗**
使用 `ElDialog` + 搜索框 + 游戏列表。允许用户选择游戏或手动输入游戏名。搜索调用 `searchGames` API,结果以列表形式展示。
**Step 2: 提交**
```bash
git add frontend/src/components/team/GameSelectDialog.vue
git commit -m "feat: add GameSelectDialog for team creation flow"
```
---
### Task 8: 创建 InviteButton 组件
**Files:**
- Create: `frontend/src/components/team/InviteButton.vue`
**Step 1: 创建邀请按钮组件**
逻辑:
1. 如果有活跃 team session → 直接发送邀请
2. 如果没有 → 打开 GameSelectDialog → 创建 team session → 发送邀请
**Step 2: 更新 IdleMembersList 使用 InviteButton**
替换当前的直接邀请按钮,改为使用 `InviteButton` 组件。
**Step 3: 提交**
```bash
git add frontend/src/components/team/InviteButton.vue frontend/src/components/team/IdleMembersList.vue
git commit -m "feat: add InviteButton with auto team session creation"
```
---
## Phase 3: UI 美化 — 设计系统 + Layout3 个 Task
### Task 9: 建立全局设计系统和主题变量
**Files:**
- Create: `frontend/src/assets/design.css`
- Modify: `frontend/src/main.ts`(引入 design.css
- Modify: `frontend/tailwind.config.js`(启用 Tailwind 并配置主题色)
**Step 1: 创建 design.css**
定义 CSS 自定义属性:主色调、圆角、阴影、间距、渐变等。覆盖 Element Plus 的默认主题变量使其与游戏主题协调。
```css
:root {
/* 主色调:深空蓝 + 霓虹紫 */
--gg-primary: #6366f1;
--gg-primary-light: #818cf8;
--gg-primary-dark: #4f46e5;
--gg-accent: #a855f7;
--gg-bg: #0f172a;
--gg-bg-card: #1e293b;
--gg-bg-hover: #334155;
--gg-text: #f1f5f9;
--gg-text-secondary: #94a3b8;
--gg-border: #334155;
--gg-success: #22c55e;
--gg-danger: #ef4444;
--gg-warning: #f59e0b;
--gg-radius-sm: 8px;
--gg-radius-md: 12px;
--gg-radius-lg: 16px;
--gg-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
--gg-gradient: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);
/* 覆盖 Element Plus 变量 */
--el-color-primary: #6366f1;
--el-color-primary-light-3: #818cf8;
--el-color-primary-light-5: #a5b4fc;
--el-color-primary-light-7: #c7d2fe;
--el-color-primary-light-9: #eef2ff;
--el-color-primary-dark-2: #4f46e5;
--el-bg-color: #1e293b;
--el-bg-color-page: #0f172a;
--el-bg-color-overlay: #1e293b;
--el-text-color-primary: #f1f5f9;
--el-text-color-regular: #e2e8f0;
--el-text-color-secondary: #94a3b8;
--el-border-color: #334155;
--el-fill-color: #334155;
--el-fill-color-light: #1e293b;
--el-fill-color-lighter: #1e293b;
}
/* 全局暗色模式基础 */
body {
background: var(--gg-bg);
color: var(--gg-text);
font-family: 'Inter', system-ui, -apple-system, sans-serif;
}
/* Element Plus 暗色模式覆盖 */
.el-card {
background: var(--gg-bg-card) !important;
border-color: var(--gg-border) !important;
color: var(--gg-text) !important;
}
.el-dialog {
background: var(--gg-bg-card) !important;
border-color: var(--gg-border) !important;
}
.el-input__wrapper {
background: var(--gg-bg) !important;
box-shadow: 0 0 0 1px var(--gg-border) inset !important;
}
.el-input__inner {
color: var(--gg-text) !important;
}
.el-dropdown-menu {
background: var(--gg-bg-card) !important;
border-color: var(--gg-border) !important;
}
.el-dropdown-menu__item {
color: var(--gg-text) !important;
}
.el-dropdown-menu__item:hover {
background: var(--gg-bg-hover) !important;
color: var(--gg-primary) !important;
}
/* 状态颜色 */
.status-idle { color: #22c55e; }
.status-working { color: #ef4444; }
.status-in_team { color: #3b82f6; }
.status-away { color: #6b7280; }
```
**Step 2: 更新 tailwind.config.js**
启用深色主题色系,与 CSS 变量对齐。
**Step 3: 在 main.ts 中引入**
```typescript
import './assets/design.css'
```
**Step 4: 提交**
```bash
git add frontend/src/assets/design.css frontend/src/main.ts frontend/tailwind.config.js
git commit -m "feat: establish dark theme design system with Element Plus overrides"
```
---
### Task 10: 重构 Layout 为游戏风格侧边栏布局
**Files:**
- Rewrite: `frontend/src/views/Layout.vue`
**Step 1: 重写 Layout.vue**
布局结构改为:
```
┌─────────┬──────────────────────────────────┐
│ Sidebar │ Header │
│ (240px) │──────────────────────────────────│
│ │ │
│ Logo │ Main Content │
│ Nav │ │
│ Groups │ │
│ Status │ │
│ User │ │
└─────────┴──────────────────────────────────┘
```
左侧边栏包含:
- Logo(渐变文字)
- 导航菜单(首页、游戏库)
- 群组列表
- 底部:用户状态 + 工作时间按钮 + 用户头像
右侧:顶部通知栏 + 通知面板,下方 router-view
全部使用深色主题配色。
**Step 2: 提交**
```bash
git add frontend/src/views/Layout.vue
git commit -m "feat: redesign Layout with game-style dark sidebar"
```
---
### Task 11: 美化登录/注册页面
**Files:**
- Rewrite: `frontend/src/views/Login.vue`
- Rewrite: `frontend/src/views/Register.vue`
**Step 1: 美化 Login.vue**
保持深色主题:全屏渐变背景 + 毛玻璃卡片 + 游戏风格图标/标题。表单输入框使用深色风格。登录按钮使用渐变色。
关键设计元素:
- 背景:深空渐变 `#0f172a → #1e1b4b` 带粒子/星星 CSS 效果
- 卡片:半透明 `backdrop-filter: blur` 毛玻璃效果
- 标题:带发光效果的 "Game Group" logo
- 输入框:深色背景 + 紫色聚焦边框
- 按钮:渐变色 + hover 发光
**Step 2: 同样美化 Register.vue**
与 Login 保持一致的视觉风格。
**Step 3: 提交**
```bash
git add frontend/src/views/Login.vue frontend/src/views/Register.vue
git commit -m "feat: beautify login/register with dark gaming theme"
```
---
## Phase 4: UI 美化 — 内容页面(4 个 Task)
### Task 12: 美化 Home 页面
**Files:**
- Rewrite: `frontend/src/views/Home.vue`
**Step 1: 重构 Home 布局**
从三栏改为更现代的卡片式布局:
```
┌────────────────────────────────────────┐
│ 欢迎回来, {username}! [状态指示器] │
├────────────────────┬───────────────────┤
│ 我的群组卡片 │ 当前临时小组 │
│ (网格 2 列) │ (TeamSession) │
│ │ │
├────────────────────┴───────────────────┤
│ 热门游戏 (横向滚动卡片) │
└────────────────────────────────────────┘
```
设计要点:
- 群组卡片:深色底 + 悬浮发光边框 + 成员头像叠放
- 游戏卡片:封面图 + 渐变遮罩 + 游戏名
- 使用 CSS Grid 和 Flexbox
**Step 2: 提交**
```bash
git add frontend/src/views/Home.vue
git commit -m "feat: redesign Home with card-based layout"
```
---
### Task 13: 美化 GroupView 页面
**Files:**
- Rewrite: `frontend/src/views/GroupView.vue`
**Step 1: 重构 GroupView**
```
┌────────────────────────────────────────────┐
│ {群组名} [群主标记] │
│ {描述} · {N} 成员 │
├──────────────────────┬─────────────────────┤
│ 空闲成员列表 │ 当前临时小组 │
│ (卡片列表) │ (TeamSession) │
│ - 头像 + 状态 │ - 成员头像列表 │
│ - 邀请按钮 │ - 游戏名 │
│ │ - 结束按钮 │
├──────────────────────┤ │
│ 所有成员 │ │
│ (按状态分组显示) │ │
└──────────────────────┴─────────────────────┘
```
设计要点:
- 成员按状态分组(空闲/组队中/工作中/离开),每组不同颜色标识
- 邀请按钮用紫色渐变 + 悬浮动画
- 临时小组面板用发光边框
**Step 2: 提交**
```bash
git add frontend/src/views/GroupView.vue
git commit -m "feat: redesign GroupView with status-grouped members"
```
---
### Task 14: 美化组件库
**Files:**
- Rewrite: `frontend/src/components/team/TeamSessionPanel.vue`
- Rewrite: `frontend/src/components/team/IdleMembersList.vue`
- Rewrite: `frontend/src/components/team/InvitationCard.vue`
- Rewrite: `frontend/src/components/team/StatusToggle.vue`
- Rewrite: `frontend/src/components/team/WorkScheduleModal.vue`
**Step 1: 美化 TeamSessionPanel**
- 深色卡片 + 紫色发光边框
- 成员头像使用在线状态指示灯
- 游戏结束按钮改为红色渐变 + 确认动画
**Step 2: 美化 IdleMembersList**
- 成员卡片使用深色悬浮效果
- 邀请按钮使用紫色渐变
- 空状态使用游戏风格图标
**Step 3: 美化 InvitationCard**
- 使用深色卡片 + 发光效果
- 接受按钮绿色渐变,拒绝按钮灰色
- 游戏信息区域使用渐变遮罩
**Step 4: 美化 StatusToggle**
- 下拉菜单使用深色主题
- 当前状态显示带颜色的指示灯(非 emoji,改用 CSS 圆点)
**Step 5: 美化 WorkScheduleModal**
- 弹窗深色背景
- 复选框和时间选择器使用自定义样式
**Step 6: 提交**
```bash
git add frontend/src/components/team/
git commit -m "feat: beautify all team components with dark gaming theme"
```
---
### Task 15: 美化其他页面
**Files:**
- Rewrite: `frontend/src/views/GamesLibrary.vue`
- Rewrite: `frontend/src/views/Profile.vue`
- Rewrite: `frontend/src/views/Settings.vue`
- Rewrite: `frontend/src/views/NotFound.vue`
**Step 1: 美化 GamesLibrary**
- 游戏卡片使用深色底 + 封面图 + 渐变遮罩文字
- 搜索框深色风格
- 平台筛选标签使用彩色徽章
**Step 2: 美化 Profile**
- 深色卡片布局
- 头像区域使用渐变背景
- 状态显示使用彩色指示灯
**Step 3: 美化 Settings**
- 设置项使用深色卡片 + 紫色强调色
- 按钮使用渐变色
**Step 4: 美化 NotFound**
- 游戏风格的 404 页面
- 返回按钮使用渐变色
**Step 5: 提交**
```bash
git add frontend/src/views/GamesLibrary.vue frontend/src/views/Profile.vue frontend/src/views/Settings.vue frontend/src/views/NotFound.vue
git commit -m "feat: beautify remaining pages with dark gaming theme"
```
---
## Phase 5: 集成验证(1 个 Task
### Task 16: 全流程验证
**Step 1: 启动后端**
```bash
cd backend && docker-compose up -d
```
**Step 2: 启动前端**
```bash
cd frontend && npm run dev
```
**Step 3: 验证核心流程**
1. 注册新用户 → 确认注册页面正常
2. 登录 → 确认跳转到首页
3. 创建群组 → 确认群组出现在侧边栏
4. 切换状态 → 确认状态更新
5. 查看空闲成员 → 确认列表正确
6. 邀请成员 → 确认邀请流程(创建 team session → 发送邀请)
7. 接受邀请 → 确认用户加入 team session + 状态变为 in_team
8. 结束游戏 → 确认 team session 解散
9. 检查所有页面 UI → 确认深色主题一致性
**Step 4: 修复验证中发现的问题**
**Step 5: 最终提交**
```bash
git add -A
git commit -m "fix: address issues found during integration testing"
```
---
## 执行优先级
| 优先级 | Phase | 说明 |
|--------|-------|------|
| P0 | Phase 1 | 后端 Critical 修复,不修则核心流程不通 |
| P1 | Phase 2 | 补齐缺失组件,否则前端功能不完整 |
| P2 | Phase 3-4 | UI 美化,提升可用性和观感 |
| P3 | Phase 5 | 集成验证 |
**预计工作量:** 16 个 Task,建议分 3-4 个执行批次。