feat: 数据mock
This commit is contained in:
3
.env.development
Normal file
3
.env.development
Normal file
@@ -0,0 +1,3 @@
|
||||
# 开发环境启用 Mock 拦截器
|
||||
# 后端接口就绪后设为 false 即可切换到真实请求
|
||||
VITE_USE_MOCK=true
|
||||
5
env.d.ts
vendored
5
env.d.ts
vendored
@@ -1 +1,6 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
/** 是否启用 Mock 拦截器 */
|
||||
readonly VITE_USE_MOCK: string
|
||||
}
|
||||
|
||||
33
src/api/mock/game-list.ts
Normal file
33
src/api/mock/game-list.ts
Normal 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
156
src/api/mock/game-room.ts
Normal 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
101
src/api/mock/index.ts
Normal 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 }
|
||||
3
src/api/modules/game/index.ts
Normal file
3
src/api/modules/game/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './types'
|
||||
export * from './list'
|
||||
export * from './room'
|
||||
14
src/api/modules/game/list.ts
Normal file
14
src/api/modules/game/list.ts
Normal 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')
|
||||
}
|
||||
18
src/api/modules/game/room.ts
Normal file
18
src/api/modules/game/room.ts
Normal 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>
|
||||
)
|
||||
}
|
||||
74
src/api/modules/game/types.ts
Normal file
74
src/api/modules/game/types.ts
Normal 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[]
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
export * from './account/index.ts'
|
||||
export * from './user/index.ts'
|
||||
export * from './game/index.ts'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user