feat: 数据mock

This commit is contained in:
2026-06-18 13:10:09 +08:00
parent deae38fb05
commit 9bd85a4e43
15 changed files with 466 additions and 403 deletions

3
.env.development Normal file
View File

@@ -0,0 +1,3 @@
# 开发环境启用 Mock 拦截器
# 后端接口就绪后设为 false 即可切换到真实请求
VITE_USE_MOCK=true

5
env.d.ts vendored
View File

@@ -1 +1,6 @@
/// <reference types="vite/client" />
interface ImportMetaEnv {
/** 是否启用 Mock 拦截器 */
readonly VITE_USE_MOCK: string
}

33
src/api/mock/game-list.ts Normal file
View File

@@ -0,0 +1,33 @@
import type { GameItem } from '../modules/game/types'
/** 游戏列表 Mock 数据 */
export const mockGameList: GameItem[] = [
{
id: 'snake',
icon: '🐍',
name: '贪吃蛇',
description: '经典贪吃蛇竞技,在狭小空间内争夺生存权',
gameplay: '方向键控制蛇的移动,吃到食物变长。撞墙或其他蛇的身体即死亡,最后存活的玩家获胜。'
},
{
id: 'tetris',
icon: '🧊',
name: '俄罗斯方块',
description: '经典下落消除,与对手一较高下',
gameplay: '方向键移动和旋转方块,填满横排即可消除。同时向对手发送干扰行,坚持到最后的玩家获胜。'
},
{
id: 'gomoku',
icon: '⚫',
name: '五子棋',
description: '黑白对弈,先连成五子者胜',
gameplay: '鼠标点击落子,黑白双方轮流在棋盘上放置棋子。先在横、竖、斜任意方向连成五子的一方获胜。'
},
{
id: 'guess-number',
icon: '🔢',
name: '猜数字',
description: '逻辑推理对战,猜中对方的秘密数字',
gameplay: '每轮输入猜测的数字组合,系统会提示位置和数字正确的数量。率先猜中对手数字组合的玩家获胜。'
}
]

156
src/api/mock/game-room.ts Normal file
View File

@@ -0,0 +1,156 @@
import type { RoomItem } from '../modules/game/types'
/** 根据名字生成头像 URL */
function avatarUrl(name: string): string {
return `https://api.dicebear.com/7.x/bottts-neutral/svg?seed=${encodeURIComponent(name)}`
}
/** 各游戏的房间 Mock 数据 */
export const mockRoomList: Record<string, RoomItem[]> = {
/* ---- 贪吃蛇 ---- */
snake: [
{
id: 'snake-001', name: '桌 01', gameIcon: '🐍', gameId: 'snake',
maxPlayers: 4,
players: [{ avatar: avatarUrl('小龙'), nickname: '小龙' }, { avatar: avatarUrl('阿蟒'), nickname: '阿蟒' }],
status: 'waiting', statusText: '等待中', stakes: 300, mode: '经典模式', creatorName: '小龙'
},
{
id: 'snake-002', name: '桌 02', gameIcon: '🐍', gameId: 'snake',
maxPlayers: 4,
players: [
{ avatar: avatarUrl('闪电'), nickname: '闪电' }, { avatar: avatarUrl('疾风'), nickname: '疾风' },
{ avatar: avatarUrl('游侠'), nickname: '游侠' }, { avatar: avatarUrl('幻影'), nickname: '幻影' }
],
status: 'playing', statusText: '进行中', stakes: 500, mode: '道具模式', creatorName: '闪电'
},
{
id: 'snake-003', name: '桌 03', gameIcon: '🐍', gameId: 'snake',
maxPlayers: 4,
players: [{ avatar: avatarUrl('青蛇'), nickname: '青蛇' }],
status: 'waiting', statusText: '等待中', stakes: 200, mode: '加速模式', creatorName: '青蛇'
},
{
id: 'snake-004', name: '桌 04', gameIcon: '🐍', gameId: 'snake',
maxPlayers: 4, players: [],
status: 'waiting', statusText: '等待中', stakes: 100, mode: '经典模式', creatorName: '毒牙'
},
{
id: 'snake-005', name: '桌 05', gameIcon: '🐍', gameId: 'snake',
maxPlayers: 4,
players: [
{ avatar: avatarUrl('巨蟒'), nickname: '巨蟒' }, { avatar: avatarUrl('灵蛇'), nickname: '灵蛇' },
{ avatar: avatarUrl('毒牙'), nickname: '毒牙' }
],
status: 'waiting', statusText: '等待中', stakes: 400, mode: '道具模式', creatorName: '巨蟒'
}
],
/* ---- 俄罗斯方块 ---- */
tetris: [
{
id: 'tetris-001', name: '桌 01', gameIcon: '🧊', gameId: 'tetris',
maxPlayers: 2,
players: [{ avatar: avatarUrl('方块大师'), nickname: '方块大师' }],
status: 'waiting', statusText: '等待中', stakes: 300, mode: '1v1 对战', creatorName: '方块大师'
},
{
id: 'tetris-002', name: '桌 02', gameIcon: '🧊', gameId: 'tetris',
maxPlayers: 2,
players: [{ avatar: avatarUrl('极速下落'), nickname: '极速下落' }, { avatar: avatarUrl('消行高手'), nickname: '消行高手' }],
status: 'playing', statusText: '进行中', stakes: 500, mode: '竞速模式', creatorName: '极速下落'
},
{
id: 'tetris-003', name: '桌 03', gameIcon: '🧊', gameId: 'tetris',
maxPlayers: 2, players: [],
status: 'waiting', statusText: '等待中', stakes: 150, mode: '1v1 对战', creatorName: '堆叠王者'
},
{
id: 'tetris-004', name: '桌 04', gameIcon: '🧊', gameId: 'tetris',
maxPlayers: 2,
players: [{ avatar: avatarUrl('T旋大师'), nickname: 'T旋大师' }],
status: 'waiting', statusText: '等待中', stakes: 200, mode: '1v1 对战', creatorName: 'T旋大师'
},
{
id: 'tetris-005', name: '桌 05', gameIcon: '🧊', gameId: 'tetris',
maxPlayers: 2,
players: [{ avatar: avatarUrl('连击王'), nickname: '连击王' }, { avatar: avatarUrl('消除者'), nickname: '消除者' }],
status: 'playing', statusText: '进行中', stakes: 400, mode: '竞速模式', creatorName: '连击王'
}
],
/* ---- 五子棋 ---- */
gomoku: [
{
id: 'gomoku-001', name: '桌 01', gameIcon: '⚫', gameId: 'gomoku',
maxPlayers: 2,
players: [{ avatar: avatarUrl('棋圣'), nickname: '棋圣' }],
status: 'waiting', statusText: '等待中', stakes: 200, mode: '无禁手', creatorName: '棋圣'
},
{
id: 'gomoku-002', name: '桌 02', gameIcon: '⚫', gameId: 'gomoku',
maxPlayers: 2,
players: [{ avatar: avatarUrl('黑白子'), nickname: '黑白子' }, { avatar: avatarUrl('五连珠'), nickname: '五连珠' }],
status: 'playing', statusText: '进行中', stakes: 400, mode: '有禁手', creatorName: '黑白子'
},
{
id: 'gomoku-003', name: '桌 03', gameIcon: '⚫', gameId: 'gomoku',
maxPlayers: 2, players: [],
status: 'waiting', statusText: '等待中', stakes: 100, mode: '无禁手', creatorName: '落子无悔'
},
{
id: 'gomoku-004', name: '桌 04', gameIcon: '⚫', gameId: 'gomoku',
maxPlayers: 2,
players: [{ avatar: avatarUrl('天元'), nickname: '天元' }],
status: 'waiting', statusText: '等待中', stakes: 300, mode: '有禁手', creatorName: '天元'
},
{
id: 'gomoku-005', name: '桌 05', gameIcon: '⚫', gameId: 'gomoku',
maxPlayers: 2,
players: [{ avatar: avatarUrl('先手必胜'), nickname: '先手必胜' }, { avatar: avatarUrl('后手无敌'), nickname: '后手无敌' }],
status: 'playing', statusText: '进行中', stakes: 500, mode: '有禁手', creatorName: '先手必胜'
}
],
/* ---- 猜数字 ---- */
'guess-number': [
{
id: 'guess-001', name: '桌 01', gameIcon: '🔢', gameId: 'guess-number',
maxPlayers: 4,
players: [{ avatar: avatarUrl('逻辑怪'), nickname: '逻辑怪' }, { avatar: avatarUrl('推理王'), nickname: '推理王' }],
status: 'waiting', statusText: '等待中', stakes: 200, mode: '经典模式', creatorName: '逻辑怪'
},
{
id: 'guess-002', name: '桌 02', gameIcon: '🔢', gameId: 'guess-number',
maxPlayers: 4,
players: [
{ avatar: avatarUrl('秒猜'), nickname: '秒猜' },
{ avatar: avatarUrl('心算'), nickname: '心算' },
{ avatar: avatarUrl('直觉'), nickname: '直觉' }
],
status: 'waiting', statusText: '等待中', stakes: 300, mode: '限时模式', creatorName: '秒猜'
},
{
id: 'guess-003', name: '桌 03', gameIcon: '🔢', gameId: 'guess-number',
maxPlayers: 4,
players: [{ avatar: avatarUrl('数字猎人'), nickname: '数字猎人' }],
status: 'waiting', statusText: '等待中', stakes: 150, mode: '经典模式', creatorName: '数字猎人'
},
{
id: 'guess-004', name: '桌 04', gameIcon: '🔢', gameId: 'guess-number',
maxPlayers: 4,
players: [
{ avatar: avatarUrl('密码破解'), nickname: '密码破解' },
{ avatar: avatarUrl('枚举大师'), nickname: '枚举大师' },
{ avatar: avatarUrl('二分法'), nickname: '二分法' },
{ avatar: avatarUrl('穷举王'), nickname: '穷举王' }
],
status: 'playing', statusText: '进行中', stakes: 500, mode: '限时模式', creatorName: '密码破解'
},
{
id: 'guess-005', name: '桌 05', gameIcon: '🔢', gameId: 'guess-number',
maxPlayers: 4, players: [],
status: 'waiting', statusText: '等待中', stakes: 100, mode: '经典模式', creatorName: '神秘数字'
}
]
}

101
src/api/mock/index.ts Normal file
View File

@@ -0,0 +1,101 @@
import type { AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'
import { requestClient } from '@/api/request'
import { mockGameList } from './game-list'
import { mockRoomList } from './game-room'
import type { GetRoomListRequest } from '../modules/game/types'
/**
* Mock 处理器函数类型
*
* 接收 axios 配置,返回 mock 响应数据体ApiResponse.data 部分)
*/
type MockHandler = (config: InternalAxiosRequestConfig) => unknown
/** Mock 路由条目 */
interface MockRoute {
/** 请求方法,小写 */
method: string
/** URL 路径(支持精确匹配和前缀匹配) */
url: string
/** 模拟网络延迟(毫秒),默认 150ms */
delay?: number
/** 处理器 */
handler: MockHandler
}
/** Mock 路由表 */
const mockRoutes: MockRoute[] = [
{
method: 'get',
url: '/game/list',
delay: 120,
handler: () => ({ list: mockGameList })
},
{
method: 'get',
url: '/game/room/list',
delay: 150,
handler: (config) => {
const params = config.params as GetRoomListRequest | undefined
const gameId = params?.gameId ?? 'snake'
const statusFilter = params?.status
let rooms = mockRoomList[gameId] ?? []
if (statusFilter) {
rooms = rooms.filter((r) => r.status === statusFilter)
}
return { list: rooms }
}
}
]
/**
* 安装 Mock 拦截器
*
* 在 axios 请求拦截器链中注入 mock 逻辑:
* 当请求 URL + method 匹配 mock 路由表时,将请求适配器替换为 mock 处理器,
* 绕开真实 HTTP 请求,直接返回模拟数据。
*
* 仅在开发环境且 VITE_USE_MOCK=true 时启用。
*/
export function setupMock(): void {
// 仅在 mock 模式启用时注册拦截器
if (import.meta.env.VITE_USE_MOCK !== 'true') {
return
}
requestClient.instance.interceptors.request.use((config) => {
const method = (config.method ?? 'get').toLowerCase()
const url = config.url ?? ''
// 查找匹配的 mock 路由
const route = mockRoutes.find((r) => r.method === method && url === r.url)
if (!route) return config
// 替换适配器,使请求不发出 HTTP 调用,直接返回 mock 数据
config.adapter = async (adapterConfig: AxiosRequestConfig) => {
// 模拟网络延迟
const delay = route.delay ?? 150
await new Promise((resolve) => setTimeout(resolve, delay))
const mockData = route.handler(adapterConfig)
// 构造符合 ApiResponse 格式的响应
const response: AxiosResponse = {
data: { code: 0, message: 'ok', data: mockData },
status: 200,
statusText: 'OK',
headers: {},
config: adapterConfig as InternalAxiosRequestConfig,
request: {}
}
return response
}
return config
})
}
/**
* 重新导出 mock 数据,供开发调试使用
*/
export { mockGameList, mockRoomList }

View File

@@ -0,0 +1,3 @@
export * from './types'
export * from './list'
export * from './room'

View File

@@ -0,0 +1,14 @@
import { requestClient } from '@/api/request'
import type { GetGameListResponse } from './types'
/**
* 获取游戏列表
*
* 返回所有可用的游戏信息,包括图标、名称、描述和玩法介绍。
* 该接口无需登录即可调用。
*
* @returns 游戏列表
*/
export function getGameList() {
return requestClient.get<GetGameListResponse>('/game/list')
}

View File

@@ -0,0 +1,18 @@
import { requestClient } from '@/api/request'
import type { GetRoomListRequest, GetRoomListResponse } from './types'
/**
* 获取指定游戏的房间列表
*
* 返回某个游戏下所有房间(桌台)的信息,支持按状态筛选。
* 该接口无需登录即可调用。
*
* @param params - 查询参数,包含 gameId 和可选的 status 筛选
* @returns 房间列表
*/
export function getRoomList(params: GetRoomListRequest) {
return requestClient.get<GetRoomListResponse>(
'/game/room/list',
params as unknown as Record<string, unknown>
)
}

View File

@@ -0,0 +1,74 @@
/**
* 游戏大厅相关类型定义
*
* 此文件为前后端接口契约,后端应按此结构提供数据。
*/
/** 游戏信息 */
export interface GameItem {
/** 游戏唯一标识 */
id: string
/** 图标emoji */
icon: string
/** 游戏名称 */
name: string
/** 简短描述 */
description: string
/** 玩法介绍 */
gameplay: string
}
/** 房间内玩家 */
export interface RoomPlayer {
/** 头像 URL */
avatar: string
/** 昵称 */
nickname: string
}
/** 房间状态 */
export type RoomStatus = 'waiting' | 'playing' | 'finished'
/** 房间(桌台)信息 */
export interface RoomItem {
/** 房间唯一标识 */
id: string
/** 桌号显示名,如 "桌 01" */
name: string
/** 游戏图标 emoji */
gameIcon: string
/** 游戏 ID */
gameId: string
/** 最大玩家数 */
maxPlayers: number
/** 已入座玩家列表 */
players: RoomPlayer[]
/** 房间状态 */
status: RoomStatus
/** 状态展示文字 */
statusText: string
/** 底注金额 */
stakes: number
/** 玩法模式标签 */
mode: string
/** 房主昵称 */
creatorName: string
}
/** 获取游戏列表 — 无请求参数 */
export interface GetGameListResponse {
list: GameItem[]
}
/** 获取房间列表请求参数 */
export interface GetRoomListRequest {
/** 游戏 ID */
gameId: string
/** 筛选状态 */
status?: RoomStatus
}
/** 获取房间列表响应 */
export interface GetRoomListResponse {
list: RoomItem[]
}

View File

@@ -1,2 +1,3 @@
export * from './account/index.ts'
export * from './user/index.ts'
export * from './game/index.ts'

View File

@@ -36,8 +36,8 @@ function resolvePathParams(url: string, pathParams?: Record<string, string | num
* ```
*/
class RequestClient {
/** axios 实例 */
private instance: AxiosInstance
/** axios 实例(暴露给 mock 等外部模块使用) */
public instance: AxiosInstance
/** 是否正在刷新 token */
isRefreshing = false

View File

@@ -37,61 +37,47 @@
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useBaseLayoutStore } from '@/stores'
import { storeToRefs } from 'pinia'
import { getGameList, type GameItem } from '@/api/modules/game'
/** 游戏数据接口 */
interface GameInfo {
id: string
/** 图标emoji 或文字占位) */
icon: string
name: string
description: string
gameplay: string
/** 游戏列表 */
const games = ref<GameItem[]>([])
/** 加载状态 */
const loading = ref<boolean>(false)
/** 获取游戏列表 */
async function fetchGameList() {
loading.value = true
try {
const res = await getGameList()
games.value = res.data.list
// 默认选中第一个游戏
if (games.value.length > 0 && !activeGameId.value) {
const first = games.value[0]
if (first) {
baseLayoutStore.setActiveGame(first.id, first.icon, first.name)
}
}
} finally {
loading.value = false
}
}
/** 游戏列表数据 */
const games: GameInfo[] = [
{
id: 'snake',
icon: '🐍',
name: '贪吃蛇',
description: '经典贪吃蛇竞技,在狭小空间内争夺生存权',
gameplay: '方向键控制蛇的移动,吃到食物变长。撞墙或其他蛇的身体即死亡,最后存活的玩家获胜。'
},
{
id: 'tetris',
icon: '🧊',
name: '俄罗斯方块',
description: '经典下落消除,与对手一较高下',
gameplay: '方向键移动和旋转方块,填满横排即可消除。同时向对手发送干扰行,坚持到最后的玩家获胜。'
},
{
id: 'gomoku',
icon: '⚫',
name: '五子棋',
description: '黑白对弈,先连成五子者胜',
gameplay: '鼠标点击落子,黑白双方轮流在棋盘上放置棋子。先在横、竖、斜任意方向连成五子的一方获胜。'
},
{
id: 'guess-number',
icon: '🔢',
name: '猜数字',
description: '逻辑推理对战,猜中对方的秘密数字',
gameplay: '每轮输入猜测的数字组合,系统会提示位置和数字正确的数量。率先猜中对手数字组合的玩家获胜。'
}
]
onMounted(() => {
fetchGameList()
})
const baseLayoutStore = useBaseLayoutStore()
const { activeGameId } = storeToRefs(baseLayoutStore)
/** 当前选中的游戏对象 */
const activeGame = computed(() => games.find((g) => g.id === activeGameId.value) ?? null)
const activeGame = computed(() => games.value.find((g) => g.id === activeGameId.value) ?? null)
/** 选择游戏 */
function selectGame(id: string) {
const game = games.find((g) => g.id === id)
const game = games.value.find((g) => g.id === id)
if (game) {
baseLayoutStore.setActiveGame(game.id, game.icon, game.name)
}

View File

@@ -43,9 +43,10 @@
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import TableCard from './TableCard.vue'
import type { Room } from './TableCard.vue'
import type { RoomItem } from '@/api/modules/game'
import { useBaseLayoutStore } from '@/stores'
import { storeToRefs } from 'pinia'
import { getRoomList } from '@/api/modules/game'
/** 状态筛选选项 */
const statusFilters = [
@@ -58,336 +59,30 @@ const activeFilter = ref('all')
const baseLayoutStore = useBaseLayoutStore()
const { activeGameId } = storeToRefs(baseLayoutStore)
/** 切换游戏时重置筛选 */
/** 房间列表 */
const rooms = ref<RoomItem[]>([])
/** 加载状态 */
const loading = ref<boolean>(false)
/** 获取房间列表 */
async function fetchRoomList() {
loading.value = true
try {
const res = await getRoomList({ gameId: activeGameId.value })
rooms.value = res.data.list
} finally {
loading.value = false
}
}
/** 切换游戏时重新获取房间列表并重置筛选 */
watch(activeGameId, () => {
activeFilter.value = 'all'
fetchRoomList()
})
/** 根据名字生成头像 URL */
function avatarUrl(name: string): string {
return `https://api.dicebear.com/7.x/bottts-neutral/svg?seed=${encodeURIComponent(name)}`
}
/**
* 各游戏的模拟房间数据
* key 对应 GameList 中的 gameId
*/
const ALL_ROOMS: Record<string, Room[]> = {
/* ---- 贪吃蛇 ---- */
snake: [
{
id: 'snake-001',
name: '桌 01',
gameIcon: '🐍',
gameId: 'snake',
maxPlayers: 4,
players: [
{ avatar: avatarUrl('小龙'), nickname: '小龙' },
{ avatar: avatarUrl('阿蟒'), nickname: '阿蟒' }
],
status: 'waiting',
statusText: '等待中',
stakes: 300,
mode: '经典模式',
creatorName: '小龙'
},
{
id: 'snake-002',
name: '桌 02',
gameIcon: '🐍',
gameId: 'snake',
maxPlayers: 4,
players: [
{ avatar: avatarUrl('闪电'), nickname: '闪电' },
{ avatar: avatarUrl('疾风'), nickname: '疾风' },
{ avatar: avatarUrl('游侠'), nickname: '游侠' },
{ avatar: avatarUrl('幻影'), nickname: '幻影' }
],
status: 'playing',
statusText: '进行中',
stakes: 500,
mode: '道具模式',
creatorName: '闪电'
},
{
id: 'snake-003',
name: '桌 03',
gameIcon: '🐍',
gameId: 'snake',
maxPlayers: 4,
players: [{ avatar: avatarUrl('青蛇'), nickname: '青蛇' }],
status: 'waiting',
statusText: '等待中',
stakes: 200,
mode: '加速模式',
creatorName: '青蛇'
},
{
id: 'snake-004',
name: '桌 04',
gameIcon: '🐍',
gameId: 'snake',
maxPlayers: 4,
players: [],
status: 'waiting',
statusText: '等待中',
stakes: 100,
mode: '经典模式',
creatorName: '毒牙'
},
{
id: 'snake-005',
name: '桌 05',
gameIcon: '🐍',
gameId: 'snake',
maxPlayers: 4,
players: [
{ avatar: avatarUrl('巨蟒'), nickname: '巨蟒' },
{ avatar: avatarUrl('灵蛇'), nickname: '灵蛇' },
{ avatar: avatarUrl('毒牙'), nickname: '毒牙' }
],
status: 'waiting',
statusText: '等待中',
stakes: 400,
mode: '道具模式',
creatorName: '巨蟒'
}
],
/* ---- 俄罗斯方块 ---- */
tetris: [
{
id: 'tetris-001',
name: '桌 01',
gameIcon: '🧊',
gameId: 'tetris',
maxPlayers: 2,
players: [{ avatar: avatarUrl('方块大师'), nickname: '方块大师' }],
status: 'waiting',
statusText: '等待中',
stakes: 300,
mode: '1v1 对战',
creatorName: '方块大师'
},
{
id: 'tetris-002',
name: '桌 02',
gameIcon: '🧊',
gameId: 'tetris',
maxPlayers: 2,
players: [
{ avatar: avatarUrl('极速下落'), nickname: '极速下落' },
{ avatar: avatarUrl('消行高手'), nickname: '消行高手' }
],
status: 'playing',
statusText: '进行中',
stakes: 500,
mode: '竞速模式',
creatorName: '极速下落'
},
{
id: 'tetris-003',
name: '桌 03',
gameIcon: '🧊',
gameId: 'tetris',
maxPlayers: 2,
players: [],
status: 'waiting',
statusText: '等待中',
stakes: 150,
mode: '1v1 对战',
creatorName: '堆叠王者'
},
{
id: 'tetris-004',
name: '桌 04',
gameIcon: '🧊',
gameId: 'tetris',
maxPlayers: 2,
players: [{ avatar: avatarUrl('T旋大师'), nickname: 'T旋大师' }],
status: 'waiting',
statusText: '等待中',
stakes: 200,
mode: '1v1 对战',
creatorName: 'T旋大师'
},
{
id: 'tetris-005',
name: '桌 05',
gameIcon: '🧊',
gameId: 'tetris',
maxPlayers: 2,
players: [
{ avatar: avatarUrl('连击王'), nickname: '连击王' },
{ avatar: avatarUrl('消除者'), nickname: '消除者' }
],
status: 'playing',
statusText: '进行中',
stakes: 400,
mode: '竞速模式',
creatorName: '连击王'
}
],
/* ---- 五子棋 ---- */
gomoku: [
{
id: 'gomoku-001',
name: '桌 01',
gameIcon: '⚫',
gameId: 'gomoku',
maxPlayers: 2,
players: [{ avatar: avatarUrl('棋圣'), nickname: '棋圣' }],
status: 'waiting',
statusText: '等待中',
stakes: 200,
mode: '无禁手',
creatorName: '棋圣'
},
{
id: 'gomoku-002',
name: '桌 02',
gameIcon: '⚫',
gameId: 'gomoku',
maxPlayers: 2,
players: [
{ avatar: avatarUrl('黑白子'), nickname: '黑白子' },
{ avatar: avatarUrl('五连珠'), nickname: '五连珠' }
],
status: 'playing',
statusText: '进行中',
stakes: 400,
mode: '有禁手',
creatorName: '黑白子'
},
{
id: 'gomoku-003',
name: '桌 03',
gameIcon: '⚫',
gameId: 'gomoku',
maxPlayers: 2,
players: [],
status: 'waiting',
statusText: '等待中',
stakes: 100,
mode: '无禁手',
creatorName: '落子无悔'
},
{
id: 'gomoku-004',
name: '桌 04',
gameIcon: '⚫',
gameId: 'gomoku',
maxPlayers: 2,
players: [{ avatar: avatarUrl('天元'), nickname: '天元' }],
status: 'waiting',
statusText: '等待中',
stakes: 300,
mode: '有禁手',
creatorName: '天元'
},
{
id: 'gomoku-005',
name: '桌 05',
gameIcon: '⚫',
gameId: 'gomoku',
maxPlayers: 2,
players: [
{ avatar: avatarUrl('先手必胜'), nickname: '先手必胜' },
{ avatar: avatarUrl('后手无敌'), nickname: '后手无敌' }
],
status: 'playing',
statusText: '进行中',
stakes: 500,
mode: '有禁手',
creatorName: '先手必胜'
}
],
/* ---- 猜数字 ---- */
'guess-number': [
{
id: 'guess-001',
name: '桌 01',
gameIcon: '🔢',
gameId: 'guess-number',
maxPlayers: 4,
players: [
{ avatar: avatarUrl('逻辑怪'), nickname: '逻辑怪' },
{ avatar: avatarUrl('推理王'), nickname: '推理王' }
],
status: 'waiting',
statusText: '等待中',
stakes: 200,
mode: '经典模式',
creatorName: '逻辑怪'
},
{
id: 'guess-002',
name: '桌 02',
gameIcon: '🔢',
gameId: 'guess-number',
maxPlayers: 4,
players: [
{ avatar: avatarUrl('秒猜'), nickname: '秒猜' },
{ avatar: avatarUrl('心算'), nickname: '心算' },
{ avatar: avatarUrl('直觉'), nickname: '直觉' }
],
status: 'waiting',
statusText: '等待中',
stakes: 300,
mode: '限时模式',
creatorName: '秒猜'
},
{
id: 'guess-003',
name: '桌 03',
gameIcon: '🔢',
gameId: 'guess-number',
maxPlayers: 4,
players: [{ avatar: avatarUrl('数字猎人'), nickname: '数字猎人' }],
status: 'waiting',
statusText: '等待中',
stakes: 150,
mode: '经典模式',
creatorName: '数字猎人'
},
{
id: 'guess-004',
name: '桌 04',
gameIcon: '🔢',
gameId: 'guess-number',
maxPlayers: 4,
players: [
{ avatar: avatarUrl('密码破解'), nickname: '密码破解' },
{ avatar: avatarUrl('枚举大师'), nickname: '枚举大师' },
{ avatar: avatarUrl('二分法'), nickname: '二分法' },
{ avatar: avatarUrl('穷举王'), nickname: '穷举王' }
],
status: 'playing',
statusText: '进行中',
stakes: 500,
mode: '限时模式',
creatorName: '密码破解'
},
{
id: 'guess-005',
name: '桌 05',
gameIcon: '🔢',
gameId: 'guess-number',
maxPlayers: 4,
players: [],
status: 'waiting',
statusText: '等待中',
stakes: 100,
mode: '经典模式',
creatorName: '神秘数字'
}
]
}
/** 当前游戏的房间列表 */
const rooms = computed(() => ALL_ROOMS[activeGameId.value] ?? [])
/** 首次加载 */
fetchRoomList()
/** 筛选后的房间列表 */
const filteredRooms = computed(() => {

View File

@@ -72,40 +72,10 @@
<script setup lang="ts">
import { computed } from 'vue'
/** 房间内玩家 */
interface RoomPlayer {
avatar: string
nickname: string
}
/** 房间数据 */
export interface Room {
id: string
/** 桌号显示名,如 "桌 01" */
name: string
/** 游戏图标 emoji */
gameIcon: string
/** 游戏 ID */
gameId: string
/** 最大玩家数 */
maxPlayers: number
/** 已入座玩家列表 */
players: RoomPlayer[]
/** 房间状态 */
status: 'waiting' | 'playing' | 'finished'
/** 状态展示文字 */
statusText: string
/** 底注金额 */
stakes: number
/** 玩法模式标签 */
mode: string
/** 房主昵称 */
creatorName: string
}
import type { RoomItem, RoomPlayer, RoomStatus } from '@/api/modules/game'
const props = defineProps<{
room: Room
room: RoomItem
}>()
const emit = defineEmits<{

View File

@@ -3,10 +3,14 @@ import 'element-plus/dist/index.css'
import { createApp } from 'vue'
import { initPinia } from './stores'
import { setupMock } from './api/mock'
import App from './App.vue'
import router from './router'
/** 开发环境启用 Mock 数据拦截 */
setupMock()
const app = createApp(App)
app.use(initPinia())