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

25 KiB
Raw Blame History

一期修复 + 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.modbackend/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.modmain.go 是错误放置的文件。确认 pb_hooks/ 目录已有完整的 go.modmain.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: 提交

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。

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: 提交

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.dbinvitations 集合的字段名

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_sessionsPocketBase 会将 camelCase 转为 snake_case)。前端 API 中使用 teamSessions,需要确认 PocketBase 客户端会自动转换。

Step 3: 提交

如有字段名修复:

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 已处理)。

// 响应邀请
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 函数直接更新所有成员状态,但后端无对应权限。改为仅解散小组,状态恢复由后端处理或由各用户自行操作。

export async function endGame(sessionId: string) {
  return updateTeamStatus(sessionId, 'dissolved')
}

Step 3: 提交

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 中的组件名

<!-- 修复前 -->
<idle-membersList />

<!-- 修复后 -->
<IdleMembersList />

Step 2: 修复 GroupView.vue 中的组件名

<!-- 修复前 -->
<idle-membersList status="idle" />
<idle-membersList />

<!-- 修复后 -->
<IdleMembersList status="idle" />
<IdleMembersList />

Step 3: 修复 IdleMembersList.vue 中 inviteMember 的空 teamSession

当前 inviteMember 传入 teamSession: '',需要先检查或创建 team session。暂时改为:如果没有活跃 team session,提示用户先在 Home 页面创建。

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: 提交

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

// 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: 提交

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: 提交

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: 提交

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 的默认主题变量使其与游戏主题协调。

: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 中引入

import './assets/design.css'

Step 4: 提交

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: 提交

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: 提交

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: 提交

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: 提交

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: 提交

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: 提交

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: 启动后端

cd backend && docker-compose up -d

Step 2: 启动前端

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: 最终提交

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 个执行批次。