From 0b999eebb0926d7f3785b14362a11e71299551c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Thu, 18 Jun 2026 10:45:37 +0800 Subject: [PATCH 01/12] docs(mobile): add uat mobile frontend redo design spec --- .../2026-06-18-mobile-frontend-redo-design.md | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-18-mobile-frontend-redo-design.md diff --git a/docs/superpowers/specs/2026-06-18-mobile-frontend-redo-design.md b/docs/superpowers/specs/2026-06-18-mobile-frontend-redo-design.md new file mode 100644 index 0000000..04e1101 --- /dev/null +++ b/docs/superpowers/specs/2026-06-18-mobile-frontend-redo-design.md @@ -0,0 +1,182 @@ +# 手机端前端(uat 重做)设计文档 + +> 日期:2026-06-18 +> 仓库:gamegroup2 +> 分支:`uat`(基于 `origin/uat`) +> 状态:已确认,待实施 +> 背景:master 上已有 8 个移动端 commit(基于 6-15 设计),但基于**旧 master**,不知 uat 后续新增的 PC 端功能。本次在 uat 上**重新做**移动端:迁移 master 已写好的实现 + 适配 uat 的 PC 端变化 + 补全 uat 新增功能的移动端页面。 + +## 1. 目标与范围 + +**目标**:在 uat 分支上产出完整、可用的手机端前端,覆盖 uat PC 端的全部功能(含信息公示板、事件/活动、游戏库增强、邀请落地页等 uat 独有功能),手机浏览器访问自动进入移动版。 + +**做法**(用户已定): +- **选项 A**:在 uat 上重新做整套移动端(不是直接 cherry-pick 8 个 commit) +- **"重新做"含义**:迁移 master 上已写好的 22 个移动端文件 + 基础设施,然后适配 uat 的 API/stores/types/router 变化,并补全 uat 独有功能的移动端页面。不从零写。 +- **新功能覆盖**:全部覆盖(公示板浏览+发帖、事件浏览+创建、游戏库详情+评论+收藏+添加+导入、邀请落地页 2 个) +- **设备分流**:路由层分流(沿用 master 方案) +- **实施节奏**:分阶段提交,每阶段可验证 + +## 2. 与 master 移动端的差异(迁移适配点) + +### 2.1 uat 相对 master 新增的 PC 端功能域(移动端需补) + +| 功能域 | PC 端位置 | 移动端处理 | +|---|---|---| +| 信息公示板 (bulletin) | `components/bulletin/*` (4) + `api/bulletins.ts` | 新增 `components-mobile/bulletin/*` + 群组详情新增"公示板" tab | +| 事件/活动 (event) | `components/event/*` (3) + `api/events.ts` | 新增 `components-mobile/event/*` + 群组详情新增"活动" tab + 首页接入事件板 | +| 游戏库增强 | `components/game/*` (AddGame/GameDetail/GameForm/ImportGames) | GamesLibraryMobile 增加添加/导入入口;游戏详情面板增加评论+收藏 | +| 邀请落地页 | `views/JoinGroupPage.vue`、`JoinTeamPage.vue` | 新增 `views-mobile/JoinGroupPageMobile.vue`、`JoinTeamPageMobile.vue`,路由层分流 | + +### 2.2 uat 相对 master 的基础设施变化(迁移要适配) + +| 项 | master 移动端写法 | uat 现状 | 适配方式 | +|---|---|---|---| +| router/index.ts | 旧结构(无 JoinGroup/JoinTeam 路由) | 有 `JoinGroup`/`JoinTeam` 顶层路由 + Layout children 嵌套 | **在 uat router 基础上叠加**:每个路由 component 用 `isMobile()` 三元分流,保留 JoinGroup/JoinTeam | +| package.json deps | 仅 vant | uat 已有 `livekit-client`、`tailwindcss`、`vue-tsc` 等 | 迁移时仅追加 `vant@^4` + `@vant/auto-import-resolver`,不动其他 | +| vite.config.ts | master 已加 auto-import-resolver + 手动分包 | uat 是基础配置 | 迁移 master 的 vant 插件配置 + 在 uat 基础上追加 manualChunks(保留 uat 现有 proxy/alias) | +| index.html viewport | master 改为含 `maximum-scale=1, viewport-fit=cover` | uat 基础版 | 采用 master 版本(适配键盘弹起、刘海屏) | +| types/index.ts | 移动端引用的 Event/Bulletin 等类型 | uat 已新增这些类型 | 迁移代码直接复用 uat 的 types,无需改动 | + +### 2.3 pocketbase 导出兼容性(已确认) + +uat 的 `api/pocketbase.ts` 同时提供 **命名导出 `{ pb }`** 和 **默认导出 `pb`**。master 移动端代码用默认导入 `import pb from './pocketbase'` 兼容;bulletins.ts 用命名导入也兼容。**无需改动**。 + +## 3. 目录结构(uat 上最终形态) + +``` +frontend/src/ +├─ api/ # 共享(uat 已有 events.ts/bulletins.ts/invitations.ts) +├─ stores/ # 共享 +├─ types/ # 共享(uat 已含 Event/BulletinPost 等类型) +├─ composables/ # 共享 +├─ assets/ +│ ├─ design.css # 共享 +│ └─ mobile.css # 迁移:Vant 主题映射 +├─ views/ # uat PC 端(不动) +├─ components/ # uat PC 端(不动) +├─ views-mobile/ # 迁移 + 新增 +│ ├─ LoginMobile.vue / RegisterMobile.vue / HomeMobile.vue / GroupsMobile.vue +│ ├─ GroupViewMobile.vue / VoiceRoomMobile.vue +│ ├─ GamesLibraryMobile.vue(增强:添加/导入入口) +│ ├─ LedgerMobile.vue / AssetMobile.vue / BlacklistMobile.vue +│ ├─ NotificationsMobile.vue / ProfileMobile.vue / SettingsMobile.vue / ChangelogMobile.vue +│ ├─ Placeholder.vue +│ └─ JoinGroupPageMobile.vue / JoinTeamPageMobile.vue # 新增 +├─ components-mobile/ # 迁移 + 新增 +│ ├─ bet/BetListMobile.vue +│ ├─ poll/PollListMobile.vue +│ ├─ memory/MemoryGridMobile.vue +│ ├─ stats/StatsPanelMobile.vue +│ ├─ group/ActivityFeedMobile.vue / MemberListMobile.vue +│ ├─ game/GameDetailSheetMobile.vue # 新增(详情底部面板) +│ ├─ game/AddGameSheetMobile.vue # 新增(添加游戏) +│ ├─ game/ImportGamesSheetMobile.vue # 新增(导入游戏) +│ ├─ bulletin/BulletinListMobile.vue # 新增(公示板列表) +│ ├─ bulletin/BulletinPostSheetMobile.vue # 新增(发帖/详情) +│ ├─ event/EventListMobile.vue # 新增(事件列表) +│ └─ event/CreateEventSheetMobile.vue # 新增(创建事件) +├─ mobile/ +│ ├─ MobileLayout.vue # 迁移 +│ ├─ useDevice.ts # 迁移 +│ └─ DeviceGuard.ts # 迁移(若 master 有) +├─ router/index.ts # 在 uat 基础上叠加 isMobile() 分流 +└─ main.ts # 追加 vant 按需注册 + mobile.css 引入 +``` + +## 4. 实施阶段(11 个阶段,每阶段一次提交,可验证) + +每个阶段完成后在 uat 提交,提交信息前缀 `feat(mobile): `。每阶段验证标准见各阶段说明。 + +### 阶段 1:基础设施迁移 +**迁移内容**: +- `package.json`:追加 `vant@^4` + `@vant/auto-import-resolver` +- `vite.config.ts`:在 uat 配置基础上追加 Vant 自动导入 resolver + manualChunks 分包 +- `src/assets/mobile.css`:迁移(Vant 主题变量映射到 `--gg-*`) +- `src/mobile/useDevice.ts`:迁移 +- `src/mobile/MobileLayout.vue`:迁移(已去除 logout handler 的版本) +- `index.html`:viewport 改为含 `maximum-scale=1, viewport-fit=cover` +- `src/main.ts`:追加 `import 'vant/lib/index.css'` + `import './assets/mobile.css'` + vant 按需注册 +- `src/router/index.ts`:在 uat 现有 router 上叠加——每个业务路由 component 改为 `isMobile() ? mobile : desktop`,保留 JoinGroup/JoinTeam 路由(也加分流) +- `src/views-mobile/Placeholder.vue`:迁移(占位页,让分流能跑起来) + +**验证**:`npm install` + `npm run build` 通过;PC 浏览器访问仍是桌面版;移动 UA 访问看到 Placeholder。 + +### 阶段 2:认证 +迁移 `LoginMobile.vue` + `RegisterMobile.vue`,适配 uat 的 user store / pocketbase API(已确认兼容)。 +**验证**:手机 UA 访问 `/login`、`/register` 正常,登录注册全流程通。 + +### 阶段 3:首页 + 群组列表 +迁移 `HomeMobile.vue` + `GroupsMobile.vue`。 +**适配**:首页接入事件板(listEvents 调用 + 事件卡片展示),适配 uat 的 group store 字段变化。 +**验证**:首页显示状态/群组/热门游戏/事件板;群组列表创建/加入正常。 + +### 阶段 4:群组详情核心 +迁移 `GroupViewMobile.vue` + `components-mobile/group/ActivityFeedMobile.vue` + `MemberListMobile.vue`。 +**适配**:标签栏结构对照 uat PC GroupView 的 tab 列表,预留"活动"和"公示板"两个新 tab 位置(阶段 10/11 填充)。 +**验证**:群组详情 9 个原有标签可访问。 + +### 阶段 5:组队 + 语音房 +迁移 `VoiceRoomMobile.vue` + 适配 uat 的 team store / TeamSessionPanel 相关逻辑(GameSelectDialog/InviteButton 等的移动端交互通过 ActionSheet 实现)。 +**验证**:发起组队/邀请/接受/状态切换正常;语音房 UI 显示占位提示。 + +### 阶段 6:投票 + 竞猜 +迁移 `components-mobile/poll/PollListMobile.vue` + `bet/BetListMobile.vue`,适配 uat 的 poll/bet store。 +**验证**:创建/参与/结算正常。 + +### 阶段 7:游戏库增强 +迁移 `GamesLibraryMobile.vue` + 新增 `components-mobile/game/GameDetailSheetMobile.vue`(详情底部面板:封面/平台/标签/评论/收藏/发起组队)、`AddGameSheetMobile.vue`、`ImportGamesSheetMobile.vue`。 +**适配**:GamesLibraryMobile 增加添加/导入入口按钮,接 `api/games.ts` 的 create/import。 +**验证**:浏览/搜索/详情/评论/收藏/添加/导入正常。 + +### 阶段 8:账本 + 资产 + 黑名单 +迁移 `LedgerMobile.vue` + `AssetMobile.vue` + `BlacklistMobile.vue`。 +**验证**:增删改查正常,资产转移正常。 + +### 阶段 9:回忆 + 统计 + 个人/设置/更新日志 +迁移 `MemoryGridMobile.vue` + `StatsPanelMobile.vue` + `ProfileMobile.vue` + `SettingsMobile.vue` + `ChangelogMobile.vue`。 +**适配**:Profile/Changelog 对照 uat v0.3.5/0.3.6 新数据。 +**验证**:回忆上传/浏览正常;统计只读展示;个人/设置/日志正常。 + +### 阶段 10:信息公示板 +新增 `components-mobile/bulletin/BulletinListMobile.vue`(置顶帖+普通列表)+ `BulletinPostSheetMobile.vue`(发帖/查看详情)。 +集成:群组详情标签栏新增"公示板" tab(接 `api/bulletins.ts`)。 +**验证**:群内发帖/置顶/浏览正常。 + +### 阶段 11:事件 + 邀请落地页 +新增 `components-mobile/event/EventListMobile.vue` + `CreateEventSheetMobile.vue`;群组详情新增"活动" tab;首页事件板(阶段 3 已接)的事件卡片点击进事件详情。 +新增 `views-mobile/JoinGroupPageMobile.vue` + `JoinTeamPageMobile.vue`,路由层 `JoinGroup`/`JoinTeam` 加 `isMobile()` 分流。 +**验证**:事件创建/RSVP/浏览正常;邀请链接手机端访问显示移动版落地页。 + +## 5. 验收标准 + +- [ ] PC 浏览器访问 uat 仍是桌面版,行为不变 +- [ ] 手机浏览器访问 uat 自动进入移动版 +- [ ] 登录/注册/退出全流程正常 +- [ ] 首页:状态切换、群组入口、热门游戏、事件板正常 +- [ ] 群组详情:9 个原有标签 + 活动 + 公示板 = 11 个标签可访问 +- [ ] 组队/语音房占位 UI 正常 +- [ ] 投票/竞猜:创建/参与/结算正常 +- [ ] 游戏库:浏览/搜索/详情/评论/收藏/添加/导入正常 +- [ ] 账本/资产/黑名单:增删改查 + 资产转移正常 +- [ ] 回忆上传/浏览、统计只读正常 +- [ ] 信息公示板:发帖/置顶/浏览正常 +- [ ] 事件:创建/RSVP/浏览正常 +- [ ] 邀请落地页:手机端访问 `/join/group/:id` 和 `/join/team/:id` 显示移动版 +- [ ] `npm run build` 通过,无 TS 错误 + +## 6. 风险与缓解 + +| 风险 | 缓解 | +|---|---| +| uat 的 stores/types 与 master 移动端代码假设不符 | 阶段 1-9 迁移时逐阶段核对 `git diff master..HEAD` 的 stores/types 改动,按报错适配 | +| Vant + Element Plus 样式冲突 | 路由分流隔离,不同时挂载;mobile.css 已映射主题变量 | +| 邀请落地页在未登录态下访问,移动版守卫逻辑 | JoinGroup/JoinTeam 路由 meta 不加 requiresAuth(沿用 uat),分流只换组件 | +| 构建体积 | Vant 按需引入 + manualChunks 分包(master 已验证) | + +## 7. 不在本次范围内 + +- 语音 WebRTC 实际接入(移动端仅 UI + 占位) +- PWA / 离线支持 +- 原生推送通知 +- 桌面端代码改动 From 38c13ec50e2d379c65e23ac681131baeada6c80d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Thu, 18 Jun 2026 10:54:12 +0800 Subject: [PATCH 02/12] feat(mobile): stage 1 - infrastructure (vant, useDevice, MobileLayout, router, mobile.css) - add vant@^4.9.0 dependency - migrate useDevice.ts (device detection + localStorage) - migrate MobileLayout.vue (tabbar + navbar + store init) - migrate mobile.css (vant theme -> --gg-* mapping) - migrate Placeholder.vue for unimplemented mobile views - index.html: viewport for mobile (no-zoom, safe-area) - main.ts: register Vant + import mobile.css - vite.config.ts: manualChunks code splitting (vue/element/vant/pocketbase/livekit) - router/index.ts: device-based routing via isMobile() + view() helper, preserve uat's JoinGroup/JoinTeam routes, mobile views use Placeholder pending stages 2-11 build verified: vue-tsc + vite build pass --- frontend/index.html | 2 +- frontend/package.json | 1 + frontend/src/assets/mobile.css | 53 ++++++++ frontend/src/main.ts | 4 + frontend/src/mobile/MobileLayout.vue | 149 ++++++++++++++++++++++ frontend/src/mobile/useDevice.ts | 51 ++++++++ frontend/src/router/index.ts | 112 +++++++++++++--- frontend/src/views-mobile/Placeholder.vue | 31 +++++ frontend/vite.config.ts | 19 +++ 9 files changed, 406 insertions(+), 16 deletions(-) create mode 100644 frontend/src/assets/mobile.css create mode 100644 frontend/src/mobile/MobileLayout.vue create mode 100644 frontend/src/mobile/useDevice.ts create mode 100644 frontend/src/views-mobile/Placeholder.vue diff --git a/frontend/index.html b/frontend/index.html index ca8701b..700f3a5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,7 +2,7 @@ - + Game Group V2 diff --git a/frontend/package.json b/frontend/package.json index 339a1cf..63b3a4d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,7 @@ "livekit-client": "^2.18.3", "pinia": "^2.1.7", "pocketbase": "^0.21.1", + "vant": "^4.9.0", "vue": "^3.4.21", "vue-router": "^4.3.0" }, diff --git a/frontend/src/assets/mobile.css b/frontend/src/assets/mobile.css new file mode 100644 index 0000000..f34789f --- /dev/null +++ b/frontend/src/assets/mobile.css @@ -0,0 +1,53 @@ +/* src/assets/mobile.css */ +/* Vant 4 主题变量覆盖 —— 映射到项目 design.css 的 --gg-* 设计系统 */ + +:root { + /* 主色(Vant primary → gg-primary 绿色) */ + --van-primary-color: var(--gg-primary); /* #059669 */ + --van-success-color: var(--gg-success); /* #10b981 */ + --van-danger-color: var(--gg-danger); /* #ef4444 */ + --van-warning-color: var(--gg-warning); /* #f59e0b */ + + /* 文字色 */ + --van-text-color: var(--gg-text); /* #1e293b */ + --van-text-color-2: var(--gg-text-secondary); /* #475569 */ + --van-text-color-3: var(--gg-text-muted); /* #94a3b8 */ + + /* 背景 */ + --van-background: var(--gg-bg); /* #f0fdf4 */ + --van-background-2: var(--gg-bg-card); /* #ffffff */ + + /* 边框 */ + --van-border-color: var(--gg-border); /* #e2e8f0 */ + + /* 圆角 */ + --van-radius-sm: var(--gg-radius-sm); + --van-radius-md: var(--gg-radius-md); + --van-radius-lg: var(--gg-radius-lg); + + /* Tabbar(底部导航) */ + --van-tabbar-background: var(--gg-bg-card); + --van-tabbar-item-active-color: var(--gg-primary); + --van-tabbar-item-text-color: var(--gg-text-muted); + --van-tabbar-height: 56px; + + /* NavBar(顶部栏) */ + --van-nav-bar-background: var(--gg-bg-card); + --van-nav-bar-title-font-size: 17px; + --van-nav-bar-height: 52px; + --van-nav-bar-icon-color: var(--gg-text); + + /* 按钮主色渐变 */ + --van-button-primary-background: var(--gg-primary); + --van-button-primary-border-color: var(--gg-primary); +} + +/* 手机端全局:安全区域适配(刘海屏底部 Tab 不被遮挡) */ +.mobile-app { + padding-bottom: env(safe-area-inset-bottom, 0px); +} + +/* 禁止移动端点击高亮 */ +.mobile-app * { + -webkit-tap-highlight-color: transparent; +} diff --git a/frontend/src/main.ts b/frontend/src/main.ts index 7c7181a..7965628 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -3,7 +3,10 @@ import { createApp } from 'vue' import { createPinia } from 'pinia' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' +import Vant from 'vant' +import 'vant/lib/index.css' import './assets/design.css' +import './assets/mobile.css' import * as ElementPlusIconsVue from '@element-plus/icons-vue' import App from './App.vue' import router from './router' @@ -19,5 +22,6 @@ for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.use(pinia) app.use(router) app.use(ElementPlus) +app.use(Vant) app.mount('#app') diff --git a/frontend/src/mobile/MobileLayout.vue b/frontend/src/mobile/MobileLayout.vue new file mode 100644 index 0000000..066af4f --- /dev/null +++ b/frontend/src/mobile/MobileLayout.vue @@ -0,0 +1,149 @@ + + + + + + + diff --git a/frontend/src/mobile/useDevice.ts b/frontend/src/mobile/useDevice.ts new file mode 100644 index 0000000..1618a0c --- /dev/null +++ b/frontend/src/mobile/useDevice.ts @@ -0,0 +1,51 @@ +// src/mobile/useDevice.ts +// 设备检测:判断当前是否移动端,结果存 localStorage 避免重复检测 + +const STORAGE_KEY = 'device_mode' + +/** UA 关键字匹配移动设备 */ +const MOBILE_UA = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i + +/** 屏幕宽度阈值(含平板竖屏) */ +const MOBILE_WIDTH = 768 + +/** + * 原始检测:综合 UA 和屏幕宽度判断 + * - UA 命中移动设备关键字 → 手机 + * - 屏宽 <= 768 → 手机 + * - 否则 → 桌面 + */ +function detectRaw(): 'mobile' | 'desktop' { + if (typeof navigator === 'undefined') return 'desktop' + if (MOBILE_UA.test(navigator.userAgent)) return 'mobile' + if (typeof window !== 'undefined' && window.innerWidth <= MOBILE_WIDTH) return 'mobile' + return 'desktop' +} + +/** 当前设备模式(读取 localStorage,无则检测并存入) */ +export function getDeviceMode(): 'mobile' | 'desktop' { + if (typeof localStorage === 'undefined') return detectRaw() + const stored = localStorage.getItem(STORAGE_KEY) + if (stored === 'mobile' || stored === 'desktop') return stored + const detected = detectRaw() + localStorage.setItem(STORAGE_KEY, detected) + return detected +} + +/** 是否移动端(路由分流用,同步函数) */ +export function isMobile(): boolean { + return getDeviceMode() === 'mobile' +} + +/** + * 手动切换设备模式(设置页"切换到桌面版/手机版"用) + * 切换后需整页刷新以重新走路由解析 + */ +export function setDeviceMode(mode: 'mobile' | 'desktop') { + localStorage.setItem(STORAGE_KEY, mode) +} + +/** 重置为自动检测(登出时调用,避免下个用户沿用上个用户的偏好) */ +export function resetDeviceMode() { + localStorage.removeItem(STORAGE_KEY) +} diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index ad2976d..70436c8 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -2,97 +2,179 @@ import { createRouter, createWebHistory } from 'vue-router' import type { RouteRecordRaw } from 'vue-router' import { isAuthenticated } from '@/api/pocketbase' +import { isMobile } from '@/mobile/useDevice' + +// 动态选择布局:手机端用 MobileLayout,桌面端用 Layout +const LayoutComponent = () => + isMobile() + ? import('@/mobile/MobileLayout.vue') + : import('@/views/Layout.vue') + +// 动态选择视图:同一路由名,根据设备加载桌面/手机视图 +function view(desktop: () => Promise, mobile: () => Promise) { + return isMobile() ? mobile : desktop +} + +// 阶段 1 占位:尚未实现的 mobile 视图统一指向 Placeholder,后续阶段逐个替换为真实组件 +// 阶段 2: LoginMobile / RegisterMobile +// 阶段 3: HomeMobile / GroupsMobile / NotificationsMobile +// 阶段 4: GroupViewMobile +// 阶段 5: VoiceRoomMobile +// 阶段 7: GamesLibraryMobile +// 阶段 8: LedgerMobile / AssetMobile / BlacklistMobile +// 阶段 9: ProfileMobile / SettingsMobile / ChangelogMobile +// 阶段 11: JoinGroupPageMobile / JoinTeamPageMobile +const mobilePlaceholder = () => import('@/views-mobile/Placeholder.vue') // 路由配置 const routes: RouteRecordRaw[] = [ { path: '/login', name: 'Login', - component: () => import('@/views/Login.vue'), + component: view( + () => import('@/views/Login.vue'), + mobilePlaceholder + ), meta: { requiresGuest: true } }, { path: '/register', name: 'Register', - component: () => import('@/views/Register.vue'), + component: view( + () => import('@/views/Register.vue'), + mobilePlaceholder + ), meta: { requiresGuest: true } }, { path: '/', - component: () => import('@/views/Layout.vue'), + component: LayoutComponent, meta: { requiresAuth: true }, children: [ { path: '', name: 'Home', - component: () => import('@/views/Home.vue') + component: view( + () => import('@/views/Home.vue'), + mobilePlaceholder + ) + }, + { + path: 'mobile-groups', + name: 'MobileGroups', + component: view( + () => import('@/views/Home.vue'), // 桌面端无此路由,回退首页 + mobilePlaceholder + ) + }, + { + path: 'mobile-notifications', + name: 'MobileNotifications', + component: view( + () => import('@/views/Home.vue'), + mobilePlaceholder + ) }, { path: 'group/:id', name: 'GroupView', - component: () => import('@/views/GroupView.vue'), + component: view( + () => import('@/views/GroupView.vue'), + mobilePlaceholder + ), props: true }, { path: 'group/:groupId/ledger', name: 'LedgerView', - component: () => import('@/views/LedgerView.vue'), + component: view( + () => import('@/views/LedgerView.vue'), + mobilePlaceholder + ), props: true, meta: { requiresAuth: true } }, { path: 'group/:groupId/assets', name: 'AssetView', - component: () => import('@/views/AssetView.vue'), + component: view( + () => import('@/views/AssetView.vue'), + mobilePlaceholder + ), props: true, meta: { requiresAuth: true } }, { path: 'group/:groupId/blacklist', name: 'BlacklistView', - component: () => import('@/views/BlacklistView.vue'), + component: view( + () => import('@/views/BlacklistView.vue'), + mobilePlaceholder + ), props: true, meta: { requiresAuth: true } }, { path: 'group/:groupId/voice/:sessionId', name: 'VoiceRoom', - component: () => import('@/views/VoiceRoom.vue'), + component: view( + () => import('@/views/VoiceRoom.vue'), + mobilePlaceholder + ), props: true, meta: { requiresAuth: true } }, { path: 'games', name: 'GamesLibrary', - component: () => import('@/views/GamesLibrary.vue') + component: view( + () => import('@/views/GamesLibrary.vue'), + mobilePlaceholder + ) }, { path: 'profile', name: 'Profile', - component: () => import('@/views/Profile.vue') + component: view( + () => import('@/views/Profile.vue'), + mobilePlaceholder + ) }, { path: 'settings', name: 'Settings', - component: () => import('@/views/Settings.vue') + component: view( + () => import('@/views/Settings.vue'), + mobilePlaceholder + ) }, { path: 'changelog', name: 'Changelog', - component: () => import('@/views/Changelog.vue') + component: view( + () => import('@/views/Changelog.vue'), + mobilePlaceholder + ) } ] }, { path: '/join/group/:groupId', name: 'JoinGroup', - component: () => import('@/views/JoinGroupPage.vue'), + // 邀请落地页:手机端组件阶段 11 填充,现先用 Placeholder 让分流跑通 + component: view( + () => import('@/views/JoinGroupPage.vue'), + mobilePlaceholder + ), props: true }, { path: '/join/team/:sessionId', name: 'JoinTeam', - component: () => import('@/views/JoinTeamPage.vue'), + component: view( + () => import('@/views/JoinTeamPage.vue'), + mobilePlaceholder + ), props: true }, { diff --git a/frontend/src/views-mobile/Placeholder.vue b/frontend/src/views-mobile/Placeholder.vue new file mode 100644 index 0000000..6bbe831 --- /dev/null +++ b/frontend/src/views-mobile/Placeholder.vue @@ -0,0 +1,31 @@ + + + + + + + diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 4a66df3..475eb4a 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -9,6 +9,25 @@ export default defineConfig({ '@': path.resolve(__dirname, 'src') } }, + build: { + rollupOptions: { + output: { + // 代码分割:vendor 按依赖分组,避免单个超大 chunk + manualChunks: { + // Vue 核心运行时(vue + vue-router + pinia) + 'vue-vendor': ['vue', 'vue-router', 'pinia'], + // 桌面端 UI 库 + 'element-plus': ['element-plus', '@element-plus/icons-vue'], + // 手机端 UI 库 + 'vant': ['vant'], + // 后端 SDK + 'pocketbase': ['pocketbase'], + // 语音房依赖(仅 VoiceRoom 用到,体积大,单独拆分) + 'livekit': ['livekit-client'], + } + } + } + }, server: { port: Number(process.env.VITE_PORT) || 5173, proxy: { From d4dbb1a10f35e2ba496ff58a9139a91e6af6acde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Thu, 18 Jun 2026 10:57:33 +0800 Subject: [PATCH 03/12] feat(mobile): stage 2 - auth (LoginMobile + RegisterMobile) - migrate LoginMobile.vue (nickname/email login, password field) - migrate RegisterMobile.vue (auto-generated username) - router: wire Login/Register mobile views - verified: user store login()/register() signatures match uat build verified: vue-tsc + vite build pass --- frontend/src/router/index.ts | 4 +- frontend/src/views-mobile/LoginMobile.vue | 158 ++++++++++++++++++ frontend/src/views-mobile/RegisterMobile.vue | 166 +++++++++++++++++++ 3 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 frontend/src/views-mobile/LoginMobile.vue create mode 100644 frontend/src/views-mobile/RegisterMobile.vue diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 70436c8..09d44a8 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -33,7 +33,7 @@ const routes: RouteRecordRaw[] = [ name: 'Login', component: view( () => import('@/views/Login.vue'), - mobilePlaceholder + () => import('@/views-mobile/LoginMobile.vue') ), meta: { requiresGuest: true } }, @@ -42,7 +42,7 @@ const routes: RouteRecordRaw[] = [ name: 'Register', component: view( () => import('@/views/Register.vue'), - mobilePlaceholder + () => import('@/views-mobile/RegisterMobile.vue') ), meta: { requiresGuest: true } }, diff --git a/frontend/src/views-mobile/LoginMobile.vue b/frontend/src/views-mobile/LoginMobile.vue new file mode 100644 index 0000000..311a7e0 --- /dev/null +++ b/frontend/src/views-mobile/LoginMobile.vue @@ -0,0 +1,158 @@ + + + + + + + diff --git a/frontend/src/views-mobile/RegisterMobile.vue b/frontend/src/views-mobile/RegisterMobile.vue new file mode 100644 index 0000000..6400d85 --- /dev/null +++ b/frontend/src/views-mobile/RegisterMobile.vue @@ -0,0 +1,166 @@ + + + + + + + From a303415857b19c265673324a292311fb6b8a25d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Thu, 18 Jun 2026 11:03:51 +0800 Subject: [PATCH 04/12] feat(mobile): stage 3 - home, groups, notifications - migrate HomeMobile.vue (status card, current session, groups, popular games) - migrate GroupsMobile.vue (list + create/join popup + pull refresh) - migrate NotificationsMobile.vue (invitations/join-requests tabs + app notifications) - router: wire Home/MobileGroups/MobileNotifications mobile views - verified: user/group/team/notification stores + groups/invitations APIs all match uat note: event board NOT added to home (uat PC Home.vue doesn't integrate it either); events will be handled in stage 11 group detail tab build verified: vue-tsc + vite build pass --- frontend/src/router/index.ts | 6 +- frontend/src/views-mobile/GroupsMobile.vue | 381 ++++++++++++++ frontend/src/views-mobile/HomeMobile.vue | 463 ++++++++++++++++++ .../src/views-mobile/NotificationsMobile.vue | 393 +++++++++++++++ 4 files changed, 1240 insertions(+), 3 deletions(-) create mode 100644 frontend/src/views-mobile/GroupsMobile.vue create mode 100644 frontend/src/views-mobile/HomeMobile.vue create mode 100644 frontend/src/views-mobile/NotificationsMobile.vue diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 09d44a8..838e26c 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -56,7 +56,7 @@ const routes: RouteRecordRaw[] = [ name: 'Home', component: view( () => import('@/views/Home.vue'), - mobilePlaceholder + () => import('@/views-mobile/HomeMobile.vue') ) }, { @@ -64,7 +64,7 @@ const routes: RouteRecordRaw[] = [ name: 'MobileGroups', component: view( () => import('@/views/Home.vue'), // 桌面端无此路由,回退首页 - mobilePlaceholder + () => import('@/views-mobile/GroupsMobile.vue') ) }, { @@ -72,7 +72,7 @@ const routes: RouteRecordRaw[] = [ name: 'MobileNotifications', component: view( () => import('@/views/Home.vue'), - mobilePlaceholder + () => import('@/views-mobile/NotificationsMobile.vue') ) }, { diff --git a/frontend/src/views-mobile/GroupsMobile.vue b/frontend/src/views-mobile/GroupsMobile.vue new file mode 100644 index 0000000..e02b82d --- /dev/null +++ b/frontend/src/views-mobile/GroupsMobile.vue @@ -0,0 +1,381 @@ + + + + + + + diff --git a/frontend/src/views-mobile/HomeMobile.vue b/frontend/src/views-mobile/HomeMobile.vue new file mode 100644 index 0000000..d2e71ce --- /dev/null +++ b/frontend/src/views-mobile/HomeMobile.vue @@ -0,0 +1,463 @@ + + + + + + + diff --git a/frontend/src/views-mobile/NotificationsMobile.vue b/frontend/src/views-mobile/NotificationsMobile.vue new file mode 100644 index 0000000..5919b42 --- /dev/null +++ b/frontend/src/views-mobile/NotificationsMobile.vue @@ -0,0 +1,393 @@ + + + + + + + From 446dbf8ae011e634ed7e6717ce5065be6fe107d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Thu, 18 Jun 2026 11:07:19 +0800 Subject: [PATCH 05/12] feat(mobile): stage 4 - group detail core (GroupViewMobile + activity/members tabs) - migrate GroupViewMobile.vue (group header + quick entries + swipeable tabs) - migrate ActivityFeedMobile.vue (team status + status-grouped members + create team popup) - migrate MemberListMobile.vue (status groups + owner management: remove/approval/join requests) - router: wire GroupView mobile view - tabs structure: activity/members live; polls/bets/memories/stats use Placeholder pending stage 6/9 (sub-components not yet migrated) - verified: sessions/group/user stores + groups/sessions/invitations APIs match uat build verified: vue-tsc + vite build pass --- .../group/ActivityFeedMobile.vue | 400 ++++++++++++++++++ .../group/MemberListMobile.vue | 315 ++++++++++++++ frontend/src/router/index.ts | 2 +- frontend/src/views-mobile/GroupViewMobile.vue | 230 ++++++++++ 4 files changed, 946 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components-mobile/group/ActivityFeedMobile.vue create mode 100644 frontend/src/components-mobile/group/MemberListMobile.vue create mode 100644 frontend/src/views-mobile/GroupViewMobile.vue diff --git a/frontend/src/components-mobile/group/ActivityFeedMobile.vue b/frontend/src/components-mobile/group/ActivityFeedMobile.vue new file mode 100644 index 0000000..8ad9bfc --- /dev/null +++ b/frontend/src/components-mobile/group/ActivityFeedMobile.vue @@ -0,0 +1,400 @@ + + + + + + + diff --git a/frontend/src/components-mobile/group/MemberListMobile.vue b/frontend/src/components-mobile/group/MemberListMobile.vue new file mode 100644 index 0000000..79b48b0 --- /dev/null +++ b/frontend/src/components-mobile/group/MemberListMobile.vue @@ -0,0 +1,315 @@ + + + + + + + diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 838e26c..146f53d 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -80,7 +80,7 @@ const routes: RouteRecordRaw[] = [ name: 'GroupView', component: view( () => import('@/views/GroupView.vue'), - mobilePlaceholder + () => import('@/views-mobile/GroupViewMobile.vue') ), props: true }, diff --git a/frontend/src/views-mobile/GroupViewMobile.vue b/frontend/src/views-mobile/GroupViewMobile.vue new file mode 100644 index 0000000..6a4b147 --- /dev/null +++ b/frontend/src/views-mobile/GroupViewMobile.vue @@ -0,0 +1,230 @@ + + + + + + + + From 0ec868c949f5f6b9f6e1edc779f771eea9dd521d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Thu, 18 Jun 2026 11:09:10 +0800 Subject: [PATCH 06/12] feat(mobile): stage 5 - voice room (VoiceRoomMobile) - migrate VoiceRoomMobile.vue (member grid + mic/speaker/leave controls + app placeholder) - router: wire VoiceRoom mobile view - verified: users API getUser() + team store loadActiveSession() + types displayName() match uat build verified: vue-tsc + vite build pass --- frontend/src/router/index.ts | 2 +- frontend/src/views-mobile/VoiceRoomMobile.vue | 255 ++++++++++++++++++ 2 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 frontend/src/views-mobile/VoiceRoomMobile.vue diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 146f53d..ca15060 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -119,7 +119,7 @@ const routes: RouteRecordRaw[] = [ name: 'VoiceRoom', component: view( () => import('@/views/VoiceRoom.vue'), - mobilePlaceholder + () => import('@/views-mobile/VoiceRoomMobile.vue') ), props: true, meta: { requiresAuth: true } diff --git a/frontend/src/views-mobile/VoiceRoomMobile.vue b/frontend/src/views-mobile/VoiceRoomMobile.vue new file mode 100644 index 0000000..8b95979 --- /dev/null +++ b/frontend/src/views-mobile/VoiceRoomMobile.vue @@ -0,0 +1,255 @@ + + + + + + + From 1cc23a08365f1e3056b2dda7db1c2a472ae6167d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Thu, 18 Jun 2026 11:11:40 +0800 Subject: [PATCH 07/12] feat(mobile): stage 6 - polls + bets - migrate PollListMobile.vue (list + detail/vote + create + settle) - migrate BetListMobile.vue (list + detail/place-bet + create + close/settle) - GroupViewMobile: wire polls/bets tabs (replace placeholders) - verified: polls API (8 fns) + bets API (8 fns) match uat build verified: vue-tsc + vite build pass --- .../components-mobile/bet/BetListMobile.vue | 410 ++++++++++++++++ .../components-mobile/poll/PollListMobile.vue | 463 ++++++++++++++++++ frontend/src/views-mobile/GroupViewMobile.vue | 7 +- 3 files changed, 879 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components-mobile/bet/BetListMobile.vue create mode 100644 frontend/src/components-mobile/poll/PollListMobile.vue diff --git a/frontend/src/components-mobile/bet/BetListMobile.vue b/frontend/src/components-mobile/bet/BetListMobile.vue new file mode 100644 index 0000000..347ea80 --- /dev/null +++ b/frontend/src/components-mobile/bet/BetListMobile.vue @@ -0,0 +1,410 @@ + + + + + + + diff --git a/frontend/src/components-mobile/poll/PollListMobile.vue b/frontend/src/components-mobile/poll/PollListMobile.vue new file mode 100644 index 0000000..377bab9 --- /dev/null +++ b/frontend/src/components-mobile/poll/PollListMobile.vue @@ -0,0 +1,463 @@ + + + + + + + diff --git a/frontend/src/views-mobile/GroupViewMobile.vue b/frontend/src/views-mobile/GroupViewMobile.vue index 6a4b147..6ecbe66 100644 --- a/frontend/src/views-mobile/GroupViewMobile.vue +++ b/frontend/src/views-mobile/GroupViewMobile.vue @@ -8,6 +8,8 @@ import { useGroupStore } from '@/stores/group' import { pb } from '@/api/pocketbase' import ActivityFeedMobile from '@/components-mobile/group/ActivityFeedMobile.vue' import MemberListMobile from '@/components-mobile/group/MemberListMobile.vue' +import PollListMobile from '@/components-mobile/poll/PollListMobile.vue' +import BetListMobile from '@/components-mobile/bet/BetListMobile.vue' import Placeholder from '@/views-mobile/Placeholder.vue' import { Wallet, Box, Warning } from '@element-plus/icons-vue' @@ -123,7 +125,10 @@ function goBlacklist() { - + + + + From 6ba671d2c3f5afe1c855e72bf311ae02f3757d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Thu, 18 Jun 2026 11:17:38 +0800 Subject: [PATCH 08/12] feat(mobile): stage 7 - games library (group-scoped, detail/comments/favorites/add/import) - rewrite GamesLibraryMobile.vue for uat model (games bound to group, not global): group selector + search + platform filter + 2-col grid + add/import entries - new GameDetailSheetMobile.vue: cover/name/aliases/tags/platform + favorite/edit/delete + quick-team + comments list with rating - new AddGameSheetMobile.vue: name/aliases/platform/tags/cover-upload (bound to group) - new ImportGamesSheetMobile.vue: bulk import via text (name | platform | tags per line) - router: wire GamesLibrary mobile view - diverges from master: uat games API requires groupId (addGame/importGames/getGroupGames) vs master's global getPopularGames/searchGames; mobile rewritten to match uat PC behavior build verified: vue-tsc + vite build pass --- .../game/AddGameSheetMobile.vue | 228 ++++++++ .../game/GameDetailSheetMobile.vue | 553 ++++++++++++++++++ .../game/ImportGamesSheetMobile.vue | 152 +++++ frontend/src/router/index.ts | 2 +- .../src/views-mobile/GamesLibraryMobile.vue | 430 ++++++++++++++ 5 files changed, 1364 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components-mobile/game/AddGameSheetMobile.vue create mode 100644 frontend/src/components-mobile/game/GameDetailSheetMobile.vue create mode 100644 frontend/src/components-mobile/game/ImportGamesSheetMobile.vue create mode 100644 frontend/src/views-mobile/GamesLibraryMobile.vue diff --git a/frontend/src/components-mobile/game/AddGameSheetMobile.vue b/frontend/src/components-mobile/game/AddGameSheetMobile.vue new file mode 100644 index 0000000..a1f5275 --- /dev/null +++ b/frontend/src/components-mobile/game/AddGameSheetMobile.vue @@ -0,0 +1,228 @@ + + + + + + + diff --git a/frontend/src/components-mobile/game/GameDetailSheetMobile.vue b/frontend/src/components-mobile/game/GameDetailSheetMobile.vue new file mode 100644 index 0000000..339c4e5 --- /dev/null +++ b/frontend/src/components-mobile/game/GameDetailSheetMobile.vue @@ -0,0 +1,553 @@ + + + + + + + diff --git a/frontend/src/components-mobile/game/ImportGamesSheetMobile.vue b/frontend/src/components-mobile/game/ImportGamesSheetMobile.vue new file mode 100644 index 0000000..aae6f9f --- /dev/null +++ b/frontend/src/components-mobile/game/ImportGamesSheetMobile.vue @@ -0,0 +1,152 @@ + + + + + + + diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index ca15060..d266031 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -129,7 +129,7 @@ const routes: RouteRecordRaw[] = [ name: 'GamesLibrary', component: view( () => import('@/views/GamesLibrary.vue'), - mobilePlaceholder + () => import('@/views-mobile/GamesLibraryMobile.vue') ) }, { diff --git a/frontend/src/views-mobile/GamesLibraryMobile.vue b/frontend/src/views-mobile/GamesLibraryMobile.vue new file mode 100644 index 0000000..7b82b09 --- /dev/null +++ b/frontend/src/views-mobile/GamesLibraryMobile.vue @@ -0,0 +1,430 @@ + + + + + + + + From 13e87110aecdb9173a21e65b3fdda57220657b0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Thu, 18 Jun 2026 11:20:52 +0800 Subject: [PATCH 09/12] feat(mobile): stage 8 - ledger + assets + blacklist - migrate LedgerMobile.vue (monthly summary + list + add + swipe-delete) - migrate AssetMobile.vue (list + add with image + transfer + delete) - migrate BlacklistMobile.vue (game/player dual tabs + add + delete) - router: wire LedgerView/AssetView/BlacklistView mobile views - verified: ledger/asset stores + assets/gameBlacklist/playerBlacklist APIs + types maps all match uat build verified: vue-tsc + vite build pass --- frontend/src/router/index.ts | 6 +- frontend/src/views-mobile/AssetMobile.vue | 230 +++++++++++++++ frontend/src/views-mobile/BlacklistMobile.vue | 278 ++++++++++++++++++ frontend/src/views-mobile/LedgerMobile.vue | 260 ++++++++++++++++ 4 files changed, 771 insertions(+), 3 deletions(-) create mode 100644 frontend/src/views-mobile/AssetMobile.vue create mode 100644 frontend/src/views-mobile/BlacklistMobile.vue create mode 100644 frontend/src/views-mobile/LedgerMobile.vue diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index d266031..c8adb20 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -89,7 +89,7 @@ const routes: RouteRecordRaw[] = [ name: 'LedgerView', component: view( () => import('@/views/LedgerView.vue'), - mobilePlaceholder + () => import('@/views-mobile/LedgerMobile.vue') ), props: true, meta: { requiresAuth: true } @@ -99,7 +99,7 @@ const routes: RouteRecordRaw[] = [ name: 'AssetView', component: view( () => import('@/views/AssetView.vue'), - mobilePlaceholder + () => import('@/views-mobile/AssetMobile.vue') ), props: true, meta: { requiresAuth: true } @@ -109,7 +109,7 @@ const routes: RouteRecordRaw[] = [ name: 'BlacklistView', component: view( () => import('@/views/BlacklistView.vue'), - mobilePlaceholder + () => import('@/views-mobile/BlacklistMobile.vue') ), props: true, meta: { requiresAuth: true } diff --git a/frontend/src/views-mobile/AssetMobile.vue b/frontend/src/views-mobile/AssetMobile.vue new file mode 100644 index 0000000..fe52a33 --- /dev/null +++ b/frontend/src/views-mobile/AssetMobile.vue @@ -0,0 +1,230 @@ + + + + + + + diff --git a/frontend/src/views-mobile/BlacklistMobile.vue b/frontend/src/views-mobile/BlacklistMobile.vue new file mode 100644 index 0000000..5449f0b --- /dev/null +++ b/frontend/src/views-mobile/BlacklistMobile.vue @@ -0,0 +1,278 @@ + + + + + + + diff --git a/frontend/src/views-mobile/LedgerMobile.vue b/frontend/src/views-mobile/LedgerMobile.vue new file mode 100644 index 0000000..c11ab2a --- /dev/null +++ b/frontend/src/views-mobile/LedgerMobile.vue @@ -0,0 +1,260 @@ + + + + + + + From 4b54f7190224b2316abb6a91133191c1fa55ced3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Thu, 18 Jun 2026 11:25:10 +0800 Subject: [PATCH 10/12] feat(mobile): stage 9 - memories, stats, profile, settings, changelog - migrate MemoryGridMobile.vue (grid + upload + fullscreen preview + delete) - migrate StatsPanelMobile.vue (overview + points ranking) - migrate ProfileMobile.vue (user header + stats + status + entries + logout) - migrate SettingsMobile.vue (notify/sound toggles + desktop switch + about) - migrate ChangelogMobile.vue (timeline; data synced with uat PC v0.3.6 + mobile v2.1.0 entry) - GroupViewMobile: wire memories/stats tabs (replace placeholders) - router: wire Profile/Settings/Changelog mobile views - verified: memories/points APIs + Memory type + user store logout/setStatus match uat build verified: vue-tsc + vite build pass --- .../memory/MemoryGridMobile.vue | 211 ++++++++++++++++++ .../stats/StatsPanelMobile.vue | 109 +++++++++ frontend/src/router/index.ts | 6 +- frontend/src/views-mobile/ChangelogMobile.vue | 152 +++++++++++++ frontend/src/views-mobile/GroupViewMobile.vue | 7 +- frontend/src/views-mobile/ProfileMobile.vue | 137 ++++++++++++ frontend/src/views-mobile/SettingsMobile.vue | 59 +++++ 7 files changed, 677 insertions(+), 4 deletions(-) create mode 100644 frontend/src/components-mobile/memory/MemoryGridMobile.vue create mode 100644 frontend/src/components-mobile/stats/StatsPanelMobile.vue create mode 100644 frontend/src/views-mobile/ChangelogMobile.vue create mode 100644 frontend/src/views-mobile/ProfileMobile.vue create mode 100644 frontend/src/views-mobile/SettingsMobile.vue diff --git a/frontend/src/components-mobile/memory/MemoryGridMobile.vue b/frontend/src/components-mobile/memory/MemoryGridMobile.vue new file mode 100644 index 0000000..54b5368 --- /dev/null +++ b/frontend/src/components-mobile/memory/MemoryGridMobile.vue @@ -0,0 +1,211 @@ + + + + + + + diff --git a/frontend/src/components-mobile/stats/StatsPanelMobile.vue b/frontend/src/components-mobile/stats/StatsPanelMobile.vue new file mode 100644 index 0000000..28a1e12 --- /dev/null +++ b/frontend/src/components-mobile/stats/StatsPanelMobile.vue @@ -0,0 +1,109 @@ + + + + + + + diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index c8adb20..1ce2e8d 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -137,7 +137,7 @@ const routes: RouteRecordRaw[] = [ name: 'Profile', component: view( () => import('@/views/Profile.vue'), - mobilePlaceholder + () => import('@/views-mobile/ProfileMobile.vue') ) }, { @@ -145,7 +145,7 @@ const routes: RouteRecordRaw[] = [ name: 'Settings', component: view( () => import('@/views/Settings.vue'), - mobilePlaceholder + () => import('@/views-mobile/SettingsMobile.vue') ) }, { @@ -153,7 +153,7 @@ const routes: RouteRecordRaw[] = [ name: 'Changelog', component: view( () => import('@/views/Changelog.vue'), - mobilePlaceholder + () => import('@/views-mobile/ChangelogMobile.vue') ) } ] diff --git a/frontend/src/views-mobile/ChangelogMobile.vue b/frontend/src/views-mobile/ChangelogMobile.vue new file mode 100644 index 0000000..65d487d --- /dev/null +++ b/frontend/src/views-mobile/ChangelogMobile.vue @@ -0,0 +1,152 @@ + + + + + + + diff --git a/frontend/src/views-mobile/GroupViewMobile.vue b/frontend/src/views-mobile/GroupViewMobile.vue index 6ecbe66..e99710a 100644 --- a/frontend/src/views-mobile/GroupViewMobile.vue +++ b/frontend/src/views-mobile/GroupViewMobile.vue @@ -10,6 +10,8 @@ import ActivityFeedMobile from '@/components-mobile/group/ActivityFeedMobile.vue import MemberListMobile from '@/components-mobile/group/MemberListMobile.vue' import PollListMobile from '@/components-mobile/poll/PollListMobile.vue' import BetListMobile from '@/components-mobile/bet/BetListMobile.vue' +import MemoryGridMobile from '@/components-mobile/memory/MemoryGridMobile.vue' +import StatsPanelMobile from '@/components-mobile/stats/StatsPanelMobile.vue' import Placeholder from '@/views-mobile/Placeholder.vue' import { Wallet, Box, Warning } from '@element-plus/icons-vue' @@ -128,7 +130,10 @@ function goBlacklist() { - + + + + diff --git a/frontend/src/views-mobile/ProfileMobile.vue b/frontend/src/views-mobile/ProfileMobile.vue new file mode 100644 index 0000000..124b091 --- /dev/null +++ b/frontend/src/views-mobile/ProfileMobile.vue @@ -0,0 +1,137 @@ + + + + + + + diff --git a/frontend/src/views-mobile/SettingsMobile.vue b/frontend/src/views-mobile/SettingsMobile.vue new file mode 100644 index 0000000..fa2a7e8 --- /dev/null +++ b/frontend/src/views-mobile/SettingsMobile.vue @@ -0,0 +1,59 @@ + + + + + + + From 062c044295ff78b8c975260a72ca363145bdfd93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Thu, 18 Jun 2026 11:28:50 +0800 Subject: [PATCH 11/12] =?UTF-8?q?feat(mobile):=20stage=2010=20-=20bulletin?= =?UTF-8?q?=20board=20(group=20detail=20'=E5=85=AC=E5=91=8A'=20tab)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - new BulletinListMobile.vue: pinned section + normal list + publish button + unread dots + realtime subscription + mark-all-read - new BulletinPostSheetMobile.vue: 3-in-1 bottom sheet (detail / edit / create) with priority/pinned/expires + permission-based edit/delete (owner/admin/creator) - GroupViewMobile: add '公告' tab (after 动态), wire BulletinListMobile - reuses uat bulletin store (posts/pinnedPosts/normalPosts/isRead/create/update/remove) build verified: vue-tsc + vite build pass --- .../bulletin/BulletinListMobile.vue | 285 ++++++++++++++++++ .../bulletin/BulletinPostSheetMobile.vue | 276 +++++++++++++++++ frontend/src/views-mobile/GroupViewMobile.vue | 9 +- 3 files changed, 567 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components-mobile/bulletin/BulletinListMobile.vue create mode 100644 frontend/src/components-mobile/bulletin/BulletinPostSheetMobile.vue diff --git a/frontend/src/components-mobile/bulletin/BulletinListMobile.vue b/frontend/src/components-mobile/bulletin/BulletinListMobile.vue new file mode 100644 index 0000000..a9b832f --- /dev/null +++ b/frontend/src/components-mobile/bulletin/BulletinListMobile.vue @@ -0,0 +1,285 @@ + + + + + + + diff --git a/frontend/src/components-mobile/bulletin/BulletinPostSheetMobile.vue b/frontend/src/components-mobile/bulletin/BulletinPostSheetMobile.vue new file mode 100644 index 0000000..3835290 --- /dev/null +++ b/frontend/src/components-mobile/bulletin/BulletinPostSheetMobile.vue @@ -0,0 +1,276 @@ + + + + + + + + + + diff --git a/frontend/src/views-mobile/GroupViewMobile.vue b/frontend/src/views-mobile/GroupViewMobile.vue index e99710a..0605cd5 100644 --- a/frontend/src/views-mobile/GroupViewMobile.vue +++ b/frontend/src/views-mobile/GroupViewMobile.vue @@ -12,6 +12,7 @@ import PollListMobile from '@/components-mobile/poll/PollListMobile.vue' import BetListMobile from '@/components-mobile/bet/BetListMobile.vue' import MemoryGridMobile from '@/components-mobile/memory/MemoryGridMobile.vue' import StatsPanelMobile from '@/components-mobile/stats/StatsPanelMobile.vue' +import BulletinListMobile from '@/components-mobile/bulletin/BulletinListMobile.vue' import Placeholder from '@/views-mobile/Placeholder.vue' import { Wallet, Box, Warning } from '@element-plus/icons-vue' @@ -57,10 +58,10 @@ onUnmounted(async () => { }) // 标签配置 -// 注:polls/bets/memories/stats 子组件阶段 6/9 迁移后接入; -// bulletin/events 阶段 10/11 新增 +// polls/bets/memories/stats 子组件已迁移;bulletin 阶段 10 新增;events 阶段 11 新增 const tabs = [ { name: 'activity', label: '动态' }, + { name: 'bulletin', label: '公告' }, { name: 'polls', label: '投票' }, { name: 'bets', label: '竞猜' }, { name: 'members', label: '成员' }, @@ -133,7 +134,9 @@ function goBlacklist() { - + + + From 2af0025c3e4430793322b3fc5b6b3d41ef77e5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=94=A6=E9=BA=9F=20=E7=8E=8B?= Date: Thu, 18 Jun 2026 11:33:23 +0800 Subject: [PATCH 12/12] feat(mobile): stage 11 - events + join landing pages (final stage) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - new EventListMobile.vue: upcoming/past sections + expandable detail with RSVP (going/interested/maybe) + comments + realtime subscription - new CreateEventSheetMobile.vue: title/desc/location/start/end/max-participants - new JoinGroupPageMobile.vue: group invite landing (login/join/approval flow) - new JoinTeamPageMobile.vue: team invite landing (login/join/source-group check) - GroupViewMobile: add '活动' tab, wire EventListMobile; all 8 tabs now live - router: wire JoinGroup/JoinTeam mobile views; remove unused mobilePlaceholder - reuses uat event store (loadEvents/rsvp/cancelRSVP/addComment/removeEvent) + sessions API (getTeamSession/joinTeamSession) All 11 stages complete. Full mobile frontend on uat covering all PC features including uat-exclusive bulletin board, events, group-scoped games, and invite landing pages. build verified: vue-tsc + vite build pass --- .../event/CreateEventSheetMobile.vue | 168 +++++++++ .../event/EventListMobile.vue | 328 ++++++++++++++++++ frontend/src/router/index.ts | 16 +- frontend/src/views-mobile/GroupViewMobile.vue | 11 +- .../src/views-mobile/JoinGroupPageMobile.vue | 222 ++++++++++++ .../src/views-mobile/JoinTeamPageMobile.vue | 229 ++++++++++++ 6 files changed, 953 insertions(+), 21 deletions(-) create mode 100644 frontend/src/components-mobile/event/CreateEventSheetMobile.vue create mode 100644 frontend/src/components-mobile/event/EventListMobile.vue create mode 100644 frontend/src/views-mobile/JoinGroupPageMobile.vue create mode 100644 frontend/src/views-mobile/JoinTeamPageMobile.vue diff --git a/frontend/src/components-mobile/event/CreateEventSheetMobile.vue b/frontend/src/components-mobile/event/CreateEventSheetMobile.vue new file mode 100644 index 0000000..e933ecf --- /dev/null +++ b/frontend/src/components-mobile/event/CreateEventSheetMobile.vue @@ -0,0 +1,168 @@ + + + + + + + diff --git a/frontend/src/components-mobile/event/EventListMobile.vue b/frontend/src/components-mobile/event/EventListMobile.vue new file mode 100644 index 0000000..317c1c8 --- /dev/null +++ b/frontend/src/components-mobile/event/EventListMobile.vue @@ -0,0 +1,328 @@ + + + + + + + diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 1ce2e8d..5f68f1c 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -15,17 +15,6 @@ function view(desktop: () => Promise, mobile: () => Promise) { return isMobile() ? mobile : desktop } -// 阶段 1 占位:尚未实现的 mobile 视图统一指向 Placeholder,后续阶段逐个替换为真实组件 -// 阶段 2: LoginMobile / RegisterMobile -// 阶段 3: HomeMobile / GroupsMobile / NotificationsMobile -// 阶段 4: GroupViewMobile -// 阶段 5: VoiceRoomMobile -// 阶段 7: GamesLibraryMobile -// 阶段 8: LedgerMobile / AssetMobile / BlacklistMobile -// 阶段 9: ProfileMobile / SettingsMobile / ChangelogMobile -// 阶段 11: JoinGroupPageMobile / JoinTeamPageMobile -const mobilePlaceholder = () => import('@/views-mobile/Placeholder.vue') - // 路由配置 const routes: RouteRecordRaw[] = [ { @@ -161,10 +150,9 @@ const routes: RouteRecordRaw[] = [ { path: '/join/group/:groupId', name: 'JoinGroup', - // 邀请落地页:手机端组件阶段 11 填充,现先用 Placeholder 让分流跑通 component: view( () => import('@/views/JoinGroupPage.vue'), - mobilePlaceholder + () => import('@/views-mobile/JoinGroupPageMobile.vue') ), props: true }, @@ -173,7 +161,7 @@ const routes: RouteRecordRaw[] = [ name: 'JoinTeam', component: view( () => import('@/views/JoinTeamPage.vue'), - mobilePlaceholder + () => import('@/views-mobile/JoinTeamPageMobile.vue') ), props: true }, diff --git a/frontend/src/views-mobile/GroupViewMobile.vue b/frontend/src/views-mobile/GroupViewMobile.vue index 0605cd5..4eab946 100644 --- a/frontend/src/views-mobile/GroupViewMobile.vue +++ b/frontend/src/views-mobile/GroupViewMobile.vue @@ -13,6 +13,7 @@ import BetListMobile from '@/components-mobile/bet/BetListMobile.vue' import MemoryGridMobile from '@/components-mobile/memory/MemoryGridMobile.vue' import StatsPanelMobile from '@/components-mobile/stats/StatsPanelMobile.vue' import BulletinListMobile from '@/components-mobile/bulletin/BulletinListMobile.vue' +import EventListMobile from '@/components-mobile/event/EventListMobile.vue' import Placeholder from '@/views-mobile/Placeholder.vue' import { Wallet, Box, Warning } from '@element-plus/icons-vue' @@ -57,11 +58,11 @@ onUnmounted(async () => { } }) -// 标签配置 -// polls/bets/memories/stats 子组件已迁移;bulletin 阶段 10 新增;events 阶段 11 新增 +// 标签配置(全部 tab 已接入) const tabs = [ { name: 'activity', label: '动态' }, { name: 'bulletin', label: '公告' }, + { name: 'events', label: '活动' }, { name: 'polls', label: '投票' }, { name: 'bets', label: '竞猜' }, { name: 'members', label: '成员' }, @@ -125,18 +126,14 @@ function goBlacklist() { >
- - - - - +
diff --git a/frontend/src/views-mobile/JoinGroupPageMobile.vue b/frontend/src/views-mobile/JoinGroupPageMobile.vue new file mode 100644 index 0000000..3507a2d --- /dev/null +++ b/frontend/src/views-mobile/JoinGroupPageMobile.vue @@ -0,0 +1,222 @@ + + + + + + + diff --git a/frontend/src/views-mobile/JoinTeamPageMobile.vue b/frontend/src/views-mobile/JoinTeamPageMobile.vue new file mode 100644 index 0000000..977c4cd --- /dev/null +++ b/frontend/src/views-mobile/JoinTeamPageMobile.vue @@ -0,0 +1,229 @@ + + + + + + +