feat: 游戏大厅
This commit is contained in:
4
components.d.ts
vendored
4
components.d.ts
vendored
@@ -24,8 +24,10 @@ declare module 'vue' {
|
|||||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
GameList: typeof import('./src/components/baseLayouts/GameList.vue')['default']
|
GameList: typeof import('./src/components/baseLayouts/GameList.vue')['default']
|
||||||
|
Lobby: typeof import('./src/components/baseLayouts/Lobby.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
TableCard: typeof import('./src/components/baseLayouts/TableCard.vue')['default']
|
||||||
UserBar: typeof import('./src/components/baseLayouts/UserBar.vue')['default']
|
UserBar: typeof import('./src/components/baseLayouts/UserBar.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,7 +46,9 @@ declare global {
|
|||||||
const ElPopover: typeof import('element-plus/es')['ElPopover']
|
const ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||||
const ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
const ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||||
const GameList: typeof import('./src/components/baseLayouts/GameList.vue')['default']
|
const GameList: typeof import('./src/components/baseLayouts/GameList.vue')['default']
|
||||||
|
const Lobby: typeof import('./src/components/baseLayouts/Lobby.vue')['default']
|
||||||
const RouterLink: typeof import('vue-router')['RouterLink']
|
const RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
const RouterView: typeof import('vue-router')['RouterView']
|
const RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
const TableCard: typeof import('./src/components/baseLayouts/TableCard.vue')['default']
|
||||||
const UserBar: typeof import('./src/components/baseLayouts/UserBar.vue')['default']
|
const UserBar: typeof import('./src/components/baseLayouts/UserBar.vue')['default']
|
||||||
}
|
}
|
||||||
@@ -54,46 +54,32 @@ interface GameInfo {
|
|||||||
/** 游戏列表数据 */
|
/** 游戏列表数据 */
|
||||||
const games: GameInfo[] = [
|
const games: GameInfo[] = [
|
||||||
{
|
{
|
||||||
id: 'basketball',
|
id: 'snake',
|
||||||
icon: '🏀',
|
icon: '🐍',
|
||||||
name: '篮球对决',
|
name: '贪吃蛇',
|
||||||
description: '实时篮球对战游戏,与全球玩家同场竞技',
|
description: '经典贪吃蛇竞技,在狭小空间内争夺生存权',
|
||||||
gameplay: '使用方向键移动球员,空格键投篮。每场比赛5分钟,得分多者获胜。支持1v1、3v3模式。'
|
gameplay: '方向键控制蛇的移动,吃到食物变长。撞墙或其他蛇的身体即死亡,最后存活的玩家获胜。'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'football',
|
id: 'tetris',
|
||||||
icon: '⚽',
|
icon: '🧊',
|
||||||
name: '足球竞技',
|
name: '俄罗斯方块',
|
||||||
description: '热血足球比赛,组建你的梦之队',
|
description: '经典下落消除,与对手一较高下',
|
||||||
gameplay: 'WASD控制球员移动,J键传球,K键射门。11人制比赛,配合战术赢得胜利。'
|
gameplay: '方向键移动和旋转方块,填满横排即可消除。同时向对手发送干扰行,坚持到最后的玩家获胜。'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'racing',
|
id: 'gomoku',
|
||||||
icon: '🏎️',
|
|
||||||
name: '极速赛车',
|
|
||||||
description: '极限竞速,漂移过弯超越对手',
|
|
||||||
gameplay: '方向键控制方向,Shift键加速漂移。多种赛道可选,挑战最快圈速记录。'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'chess',
|
|
||||||
icon: '♟️',
|
|
||||||
name: '策略棋牌',
|
|
||||||
description: '经典棋牌合集,运筹帷幄决胜千里',
|
|
||||||
gameplay: '包含围棋、象棋、五子棋等多种棋类。支持人机对战和在线匹配两种模式。'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'shooting',
|
|
||||||
icon: '🎯',
|
icon: '🎯',
|
||||||
name: '射击战场',
|
name: '五子棋',
|
||||||
description: '团队战术射击,配合队友夺取胜利',
|
description: '黑白对弈,先连成五子者胜',
|
||||||
gameplay: '鼠标瞄准,左键射击,R键换弹。多种武器和地图,5v5团队竞技模式。'
|
gameplay: '鼠标点击落子,黑白双方轮流在棋盘上放置棋子。先在横、竖、斜任意方向连成五子的一方获胜。'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'arena',
|
id: 'guess-number',
|
||||||
icon: '🏆',
|
icon: '🔢',
|
||||||
name: '竞技场',
|
name: '猜数字',
|
||||||
description: '综合竞技平台,参与锦标赛赢取奖励',
|
description: '逻辑推理对战,猜中对方的秘密数字',
|
||||||
gameplay: '每日举办多场锦标赛,涵盖各类型游戏。积累积分提升段位,赢取丰厚奖励。'
|
gameplay: '每轮输入猜测的数字组合,系统会提示位置和数字正确的数量。率先猜中对手数字组合的玩家获胜。'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
449
src/components/baseLayouts/Lobby.vue
Normal file
449
src/components/baseLayouts/Lobby.vue
Normal file
@@ -0,0 +1,449 @@
|
|||||||
|
<template>
|
||||||
|
<div class="lobby">
|
||||||
|
<!-- 顶部操作栏 -->
|
||||||
|
<div class="lobby__toolbar">
|
||||||
|
<div class="lobby__filters">
|
||||||
|
<button
|
||||||
|
v-for="f in statusFilters"
|
||||||
|
:key="f.key"
|
||||||
|
class="lobby__filter-btn"
|
||||||
|
:class="{ 'lobby__filter-btn--active': activeFilter === f.key }"
|
||||||
|
@click="activeFilter = f.key"
|
||||||
|
>
|
||||||
|
{{ f.label }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<button class="lobby__create-btn" @click="handleCreateRoom">
|
||||||
|
<span class="lobby__create-icon">+</span>
|
||||||
|
创建房间
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 桌子网格 -->
|
||||||
|
<div class="lobby__grid">
|
||||||
|
<TableCard
|
||||||
|
v-for="room in filteredRooms"
|
||||||
|
:key="room.id"
|
||||||
|
:room="room"
|
||||||
|
@join="handleJoin"
|
||||||
|
@join-seat="handleJoinSeat"
|
||||||
|
@spectate="handleSpectate"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态提示 -->
|
||||||
|
<div v-if="filteredRooms.length === 0" class="lobby__empty">
|
||||||
|
<span class="lobby__empty-icon">🪑</span>
|
||||||
|
<p class="lobby__empty-text">暂无符合条件的房间</p>
|
||||||
|
<p class="lobby__empty-hint">换个筛选条件,或者自己创建一个房间吧</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import TableCard from './TableCard.vue'
|
||||||
|
import type { Room } from './TableCard.vue'
|
||||||
|
import { useBaseLayoutStore } from '@/stores'
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
|
/** 状态筛选选项 */
|
||||||
|
const statusFilters = [
|
||||||
|
{ key: 'all', label: '全部' },
|
||||||
|
{ key: 'waiting', label: '等待中' },
|
||||||
|
{ key: 'playing', label: '进行中' },
|
||||||
|
]
|
||||||
|
const activeFilter = ref('all')
|
||||||
|
|
||||||
|
const baseLayoutStore = useBaseLayoutStore()
|
||||||
|
const { activeGameId } = storeToRefs(baseLayoutStore)
|
||||||
|
|
||||||
|
/** 切换游戏时重置筛选 */
|
||||||
|
watch(activeGameId, () => {
|
||||||
|
activeFilter.value = 'all'
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 根据名字生成头像 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] ?? [])
|
||||||
|
|
||||||
|
/** 筛选后的房间列表 */
|
||||||
|
const filteredRooms = computed(() => {
|
||||||
|
const list = rooms.value
|
||||||
|
if (activeFilter.value === 'all') return list
|
||||||
|
if (activeFilter.value === 'waiting') return list.filter((r) => r.status === 'waiting')
|
||||||
|
if (activeFilter.value === 'playing') return list.filter((r) => r.status === 'playing')
|
||||||
|
return list
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 加入房间 */
|
||||||
|
function handleJoin(roomId: string) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('加入房间:', roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 加入指定座位 */
|
||||||
|
function handleJoinSeat(roomId: string, seatIndex: number) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('加入房间:', roomId, '座位:', seatIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 观战 */
|
||||||
|
function handleSpectate(roomId: string) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('观战房间:', roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建房间 */
|
||||||
|
function handleCreateRoom() {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log('创建房间')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.lobby {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: var(--space-md);
|
||||||
|
gap: var(--space-md);
|
||||||
|
/* 深色底板模拟地板,让白色卡片"浮"在上面 */
|
||||||
|
background:
|
||||||
|
radial-gradient(ellipse at 50% 0%, rgba(79, 70, 229, 0.03) 0%, transparent 60%),
|
||||||
|
linear-gradient(180deg, #e8ecf1 0%, #e2e6ed 100%);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 顶部操作栏 ===== */
|
||||||
|
.lobby__toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby__filters {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
background-color: var(--bg-surface);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby__filter-btn {
|
||||||
|
padding: 6px 16px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
background-color 0.15s ease,
|
||||||
|
color 0.15s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
background-color: var(--bg-surface-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
color: var(--text-inverse);
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--text-inverse);
|
||||||
|
background-color: var(--color-primary-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby__create-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 8px 18px;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
color: var(--text-inverse);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition:
|
||||||
|
background-color 0.15s ease,
|
||||||
|
transform 0.15s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-primary-hover);
|
||||||
|
transform: scale(1.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: scale(0.97);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby__create-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 桌子网格 ===== */
|
||||||
|
.lobby__grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(252px, 1fr));
|
||||||
|
gap: var(--space-md);
|
||||||
|
justify-items: center;
|
||||||
|
align-content: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 空状态 ===== */
|
||||||
|
.lobby__empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1;
|
||||||
|
gap: 8px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby__empty-icon {
|
||||||
|
font-size: 56px;
|
||||||
|
opacity: 0.5;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby__empty-text {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lobby__empty-hint {
|
||||||
|
font-size: 13px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
489
src/components/baseLayouts/TableCard.vue
Normal file
489
src/components/baseLayouts/TableCard.vue
Normal file
@@ -0,0 +1,489 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="table-card"
|
||||||
|
:class="{
|
||||||
|
'table-card--playing': room.status === 'playing',
|
||||||
|
'table-card--full': room.players.length >= room.maxPlayers
|
||||||
|
}"
|
||||||
|
@click="handleClick"
|
||||||
|
>
|
||||||
|
<!-- 桌子+椅子可视区域 -->
|
||||||
|
<div class="table-card__stage">
|
||||||
|
<!-- 椅子:以圆形环绕桌子排布 -->
|
||||||
|
<div
|
||||||
|
v-for="(pos, idx) in chairPositions"
|
||||||
|
:key="idx"
|
||||||
|
class="table-card__chair"
|
||||||
|
:class="{
|
||||||
|
'table-card__chair--occupied': idx < room.players.length,
|
||||||
|
'table-card__chair--empty': idx >= room.players.length
|
||||||
|
}"
|
||||||
|
:style="{ top: pos.top, left: pos.left }"
|
||||||
|
@click.stop="handleChairClick(idx)"
|
||||||
|
>
|
||||||
|
<!-- 有人:头像 + tooltip -->
|
||||||
|
<el-tooltip
|
||||||
|
v-if="room.players[idx]"
|
||||||
|
:content="chairTooltip(idx)"
|
||||||
|
placement="top"
|
||||||
|
:show-after="400"
|
||||||
|
>
|
||||||
|
<div class="table-card__chair-seat">
|
||||||
|
<img
|
||||||
|
:src="room.players[idx].avatar"
|
||||||
|
:alt="room.players[idx].nickname"
|
||||||
|
class="table-card__chair-avatar"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
|
||||||
|
<!-- 空椅子 -->
|
||||||
|
<div v-else class="table-card__chair-seat">
|
||||||
|
<span class="table-card__chair-plus"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 房主皇冠标识 -->
|
||||||
|
<span v-if="isHost(idx)" class="table-card__chair-crown" title="房主">👑</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 圆桌 -->
|
||||||
|
<div class="table-card__table">
|
||||||
|
<span class="table-card__table-icon">{{ room.gameIcon }}</span>
|
||||||
|
<span class="table-card__table-name">{{ room.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部信息区 -->
|
||||||
|
<div class="table-card__info">
|
||||||
|
<div class="table-card__info-row">
|
||||||
|
<span class="table-card__player-count">
|
||||||
|
<span class="table-card__dot" :class="statusDotClass"></span>
|
||||||
|
{{ room.players.length }}/{{ room.maxPlayers }}人
|
||||||
|
</span>
|
||||||
|
<span class="table-card__status" :class="statusLabelClass">{{ room.statusText }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="table-card__info-row table-card__info-row--secondary">
|
||||||
|
<span>底注 {{ room.stakes }}</span>
|
||||||
|
<span>{{ room.mode }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<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
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
room: Room
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'join', roomId: string): void
|
||||||
|
(e: 'join-seat', roomId: string, seatIndex: number): void
|
||||||
|
(e: 'spectate', roomId: string): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
/** 舞台中心坐标(px) */
|
||||||
|
const STAGE_CENTER_X = 124 // 卡片宽度 248px 的一半
|
||||||
|
const STAGE_CENTER_Y = 105 // 舞台高度 210px 的一半
|
||||||
|
/** 椅子环绕半径(px) */
|
||||||
|
const ORBIT_RADIUS = 76
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按角度环绕计算椅子位置
|
||||||
|
* 角度从顶部(12 点钟方向)开始,顺时针递增
|
||||||
|
* @returns 各椅子 {top, left} 像素值数组
|
||||||
|
*/
|
||||||
|
const chairPositions = computed(() => {
|
||||||
|
const n = props.room.maxPlayers
|
||||||
|
const positions: Array<{ top: string; left: string }> = []
|
||||||
|
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
// 从顶部开始,顺时针均分
|
||||||
|
const angleDeg = (360 / n) * i
|
||||||
|
const angleRad = (angleDeg * Math.PI) / 180
|
||||||
|
|
||||||
|
const left = STAGE_CENTER_X + ORBIT_RADIUS * Math.sin(angleRad)
|
||||||
|
const top = STAGE_CENTER_Y - ORBIT_RADIUS * Math.cos(angleRad)
|
||||||
|
|
||||||
|
positions.push({
|
||||||
|
left: `${left}px`,
|
||||||
|
top: `${top}px`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return positions
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 状态指示点样式 */
|
||||||
|
const statusDotClass = computed(() => {
|
||||||
|
switch (props.room.status) {
|
||||||
|
case 'waiting':
|
||||||
|
return 'table-card__dot--waiting'
|
||||||
|
case 'playing':
|
||||||
|
return 'table-card__dot--playing'
|
||||||
|
case 'finished':
|
||||||
|
return 'table-card__dot--finished'
|
||||||
|
default:
|
||||||
|
return 'table-card__dot--waiting'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 状态标签样式 */
|
||||||
|
const statusLabelClass = computed(() => {
|
||||||
|
switch (props.room.status) {
|
||||||
|
case 'waiting':
|
||||||
|
return 'table-card__status--waiting'
|
||||||
|
case 'playing':
|
||||||
|
return 'table-card__status--playing'
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 点击卡片 → 加入或观战 */
|
||||||
|
function handleClick() {
|
||||||
|
if (props.room.status === 'playing') {
|
||||||
|
emit('spectate', props.room.id)
|
||||||
|
} else {
|
||||||
|
emit('join', props.room.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 点击空椅子 → 指定座位加入 */
|
||||||
|
function handleChairClick(idx: number) {
|
||||||
|
if (idx >= props.room.players.length && props.room.status === 'waiting') {
|
||||||
|
emit('join-seat', props.room.id, idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 判断指定座位是否为房主 */
|
||||||
|
function isHost(idx: number): boolean {
|
||||||
|
const player = props.room.players[idx]
|
||||||
|
return !!player && player.nickname === props.room.creatorName
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 椅子 hover 提示文字 */
|
||||||
|
function chairTooltip(idx: number): string {
|
||||||
|
const player = props.room.players[idx]
|
||||||
|
if (!player) return ''
|
||||||
|
return player.nickname === props.room.creatorName
|
||||||
|
? `${player.nickname}(房主)`
|
||||||
|
: player.nickname
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
/* ===== 卡片容器 ===== */
|
||||||
|
.table-card {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 248px;
|
||||||
|
background-color: var(--bg-surface);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border-default);
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
transition:
|
||||||
|
transform 0.2s ease,
|
||||||
|
box-shadow 0.2s ease;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-3px);
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 进行中的房间 — 暖色左边条 */
|
||||||
|
&--playing {
|
||||||
|
border-left: 3px solid var(--color-warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 满员 — 降低饱和度 */
|
||||||
|
&--full {
|
||||||
|
opacity: 0.6;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.82;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 舞台区域 ===== */
|
||||||
|
.table-card__stage {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 210px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
/* 中心微光,突出桌子 */
|
||||||
|
background: radial-gradient(
|
||||||
|
ellipse 65% 55% at 50% 50%,
|
||||||
|
rgba(79, 70, 229, 0.028) 0%,
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 椅子(圆形环绕) ===== */
|
||||||
|
.table-card__chair {
|
||||||
|
position: absolute;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card__chair-seat {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition:
|
||||||
|
border-color 0.2s ease,
|
||||||
|
background-color 0.2s ease,
|
||||||
|
transform 0.2s ease,
|
||||||
|
box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 空椅子 ---- */
|
||||||
|
.table-card__chair--empty .table-card__chair-seat {
|
||||||
|
border: 2px dashed var(--border-default);
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card__chair-plus {
|
||||||
|
/* 使用 CSS 伪元素画十字,保证几何居中 */
|
||||||
|
position: relative;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--text-tertiary);
|
||||||
|
border-radius: 1px;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 横线 */
|
||||||
|
&::before {
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 竖线 */
|
||||||
|
&::after {
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
width: 2px;
|
||||||
|
height: 100%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card__chair--empty:hover .table-card__chair-seat {
|
||||||
|
transform: scale(1.25);
|
||||||
|
border-style: solid;
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
background-color: var(--color-primary-light);
|
||||||
|
box-shadow: 0 0 0 5px rgba(79, 70, 229, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card__chair--empty:hover .table-card__chair-plus {
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 有人椅子 ---- */
|
||||||
|
.table-card__chair--occupied .table-card__chair-seat {
|
||||||
|
border: 2px solid var(--color-primary-light);
|
||||||
|
background-color: var(--bg-surface);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card__chair-avatar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 房主皇冠 ---- */
|
||||||
|
.table-card__chair-crown {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -4px;
|
||||||
|
right: -4px;
|
||||||
|
z-index: 5;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
font-size: 9px;
|
||||||
|
line-height: 1;
|
||||||
|
background-color: var(--bg-surface);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 圆桌(居中) ===== */
|
||||||
|
.table-card__table {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
z-index: 3;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1px;
|
||||||
|
width: 76px;
|
||||||
|
height: 76px;
|
||||||
|
border-radius: 50%;
|
||||||
|
/* 木质桌面 */
|
||||||
|
background: linear-gradient(
|
||||||
|
145deg,
|
||||||
|
#e8d5b0 0%,
|
||||||
|
#d4a574 40%,
|
||||||
|
#c49060 100%
|
||||||
|
);
|
||||||
|
box-shadow:
|
||||||
|
0 3px 12px rgba(0, 0, 0, 0.15),
|
||||||
|
inset 0 1px 2px rgba(255, 255, 255, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card__table-icon {
|
||||||
|
font-size: 26px;
|
||||||
|
line-height: 1;
|
||||||
|
filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card__table-name {
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 底部信息区 ===== */
|
||||||
|
.table-card__info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 10px 14px 12px;
|
||||||
|
border-top: 1px solid var(--border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card__info-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&--secondary {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-card__player-count {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 状态指示点 ---- */
|
||||||
|
.table-card__dot {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&--waiting {
|
||||||
|
background-color: var(--color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--playing {
|
||||||
|
background-color: var(--color-warning);
|
||||||
|
animation: dot-pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--finished {
|
||||||
|
background-color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- 状态标签 ---- */
|
||||||
|
.table-card__status {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
&--waiting {
|
||||||
|
color: var(--color-success);
|
||||||
|
background-color: var(--color-success-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--playing {
|
||||||
|
color: var(--color-warning);
|
||||||
|
background-color: var(--color-warning-light);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dot-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -27,7 +27,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 主内容区域 -->
|
<!-- 主内容区域 -->
|
||||||
<div class="base-layout__content"></div>
|
<div class="base-layout__content">
|
||||||
|
<Lobby />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -35,6 +37,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import UserBar from '@/components/baseLayouts/UserBar.vue'
|
import UserBar from '@/components/baseLayouts/UserBar.vue'
|
||||||
import GameList from '@/components/baseLayouts/GameList.vue'
|
import GameList from '@/components/baseLayouts/GameList.vue'
|
||||||
|
import Lobby from '@/components/baseLayouts/Lobby.vue'
|
||||||
import { useBaseLayoutStore } from '@/stores'
|
import { useBaseLayoutStore } from '@/stores'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
@@ -95,7 +98,8 @@ const { activeGameIcon, activeGameName } = storeToRefs(baseLayoutStore)
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
min-height: 0; /* flex 子元素必须设置,允许收缩到内容以下 */
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.base-layout__sidebar {
|
.base-layout__sidebar {
|
||||||
@@ -157,21 +161,11 @@ const { activeGameIcon, activeGameName } = storeToRefs(baseLayoutStore)
|
|||||||
right: 0;
|
right: 0;
|
||||||
cursor: ew-resize;
|
cursor: ew-resize;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
/* 默认微弱可见,提示可拖拽 */
|
background: transparent;
|
||||||
background: linear-gradient(
|
|
||||||
90deg,
|
|
||||||
transparent 0%,
|
|
||||||
transparent 40%,
|
|
||||||
var(--border-default) 40%,
|
|
||||||
var(--border-default) 60%,
|
|
||||||
transparent 60%,
|
|
||||||
transparent 100%
|
|
||||||
);
|
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-primary-light);
|
background-color: var(--color-primary-light);
|
||||||
background: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
@@ -183,7 +177,8 @@ const { activeGameIcon, activeGameName } = storeToRefs(baseLayoutStore)
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
min-height: 0; /* flex 子元素必须设置,允许收缩 */
|
||||||
|
overflow: hidden;
|
||||||
background-color: var(--bg-body);
|
background-color: var(--bg-body);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ export const useBaseLayoutStore = defineStore(
|
|||||||
/** 顶部导航栏标题 */
|
/** 顶部导航栏标题 */
|
||||||
const topbarTitle = ref<string>('')
|
const topbarTitle = ref<string>('')
|
||||||
/** 当前选中的游戏 ID */
|
/** 当前选中的游戏 ID */
|
||||||
const activeGameId = ref<string>('basketball')
|
const activeGameId = ref<string>('snake')
|
||||||
/** 当前选中的游戏图标 */
|
/** 当前选中的游戏图标 */
|
||||||
const activeGameIcon = ref<string>('🏀')
|
const activeGameIcon = ref<string>('🐍')
|
||||||
/** 当前选中的游戏名称 */
|
/** 当前选中的游戏名称 */
|
||||||
const activeGameName = ref<string>('篮球对决')
|
const activeGameName = ref<string>('贪吃蛇')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置顶部导航栏标题
|
* 设置顶部导航栏标题
|
||||||
|
|||||||
Reference in New Issue
Block a user