320 lines
7.2 KiB
Vue
320 lines
7.2 KiB
Vue
<template>
|
||
<view class="page">
|
||
<view class="bg-decoration"></view>
|
||
|
||
<view class="header">
|
||
<text class="title">实时对战信号</text>
|
||
<view class="refresh-text-btn" :class="{ loading: loading }" @tap="loadRooms">
|
||
{{ loading ? '同步中...' : '刷新信号' }}
|
||
</view>
|
||
</view>
|
||
|
||
<scroll-view scroll-y class="content" @refresherrefresh="loadRooms" :refresher-enabled="true" :refresher-triggered="isRefreshing">
|
||
<view v-if="rooms.length > 0" class="room-list">
|
||
<view v-for="room in rooms" :key="room.match_id" class="room-card glass-card fadeInUp">
|
||
<view class="room-main">
|
||
<view class="room-info">
|
||
<view class="room-header">
|
||
<text class="room-id">房间 #{{ room.match_id.split('.')[0].substring(0, 6) }}</text>
|
||
<view class="status-badge" :class="room.started ? 'started' : 'waiting'">
|
||
{{ room.started ? '进行中' : '等待中' }}
|
||
</view>
|
||
</view>
|
||
|
||
<view class="room-stats">
|
||
<view class="stat-item">
|
||
<text class="stat-icon">👥</text>
|
||
<text class="stat-text">{{ room.player_count }}/{{ room.max_players }} 玩家</text>
|
||
</view>
|
||
<view class="stat-item">
|
||
<text class="stat-icon">📡</text>
|
||
<text class="stat-text">延迟: {{ Math.floor(Math.random() * 50) + 20 }}ms</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="room-actions">
|
||
<view v-if="!room.started && room.player_count < room.max_players" class="btn-action join" @tap="joinRoom(room)">
|
||
<text class="action-text">加入</text>
|
||
</view>
|
||
<view class="btn-action watch" @tap="watchRoom(room)">
|
||
<text class="action-text">围观</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-else-if="!loading" class="empty-box">
|
||
<view class="empty-icon">🛰️</view>
|
||
<text class="empty-text">未监测到活跃战局</text>
|
||
<view class="btn-primary start-new" @tap="goBack">去发起匹配</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import { nakamaManager } from '../../../utils/nakamaManager.js';
|
||
import { authRequest } from '../../../utils/request.js';
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
rooms: [],
|
||
loading: false,
|
||
isRefreshing: false,
|
||
gameToken: '',
|
||
nakamaServer: '',
|
||
nakamaKey: ''
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
this.gameToken = options.game_token;
|
||
this.nakamaServer = decodeURIComponent(options.nakama_server || '');
|
||
this.nakamaKey = decodeURIComponent(options.nakama_key || '');
|
||
this.initAndLoad();
|
||
},
|
||
methods: {
|
||
async initAndLoad() {
|
||
this.loading = true;
|
||
try {
|
||
if (!nakamaManager.isConnected) {
|
||
nakamaManager.initClient(this.nakamaServer || 'wss://game.1024tool.vip', this.nakamaKey || 'defaultkey');
|
||
await nakamaManager.authenticateWithGameToken(this.gameToken);
|
||
}
|
||
await this.loadRooms();
|
||
} catch (e) {
|
||
uni.showToast({ title: '连接通讯中心失败', icon: 'none' });
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
async loadRooms() {
|
||
this.isRefreshing = true;
|
||
try {
|
||
const res = await nakamaManager.rpc('list_matches', {});
|
||
this.rooms = res || [];
|
||
} catch (e) {
|
||
console.error('Failed to load rooms', e);
|
||
} finally {
|
||
this.isRefreshing = false;
|
||
this.loading = false;
|
||
}
|
||
},
|
||
goBack() {
|
||
uni.navigateBack();
|
||
},
|
||
joinRoom(room) {
|
||
// 通过 MatchID 传参给 play.vue
|
||
uni.navigateTo({
|
||
url: `/pages-game/game/minesweeper/play?match_id=${room.match_id}&game_token=${encodeURIComponent(this.gameToken)}&nakama_server=${encodeURIComponent(this.nakamaServer)}&nakama_key=${encodeURIComponent(this.nakamaKey)}`
|
||
});
|
||
},
|
||
watchRoom(room) {
|
||
uni.navigateTo({
|
||
url: `/pages-game/game/minesweeper/play?match_id=${room.match_id}&is_spectator=1&game_token=${encodeURIComponent(this.gameToken)}&nakama_server=${encodeURIComponent(this.nakamaServer)}&nakama_key=${encodeURIComponent(this.nakamaKey)}`
|
||
});
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import '@/uni.scss';
|
||
|
||
.page {
|
||
min-height: 100vh;
|
||
background-color: #0f172a;
|
||
color: #f8fafc;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.bg-decoration {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 400rpx;
|
||
background: radial-gradient(circle at 50% 0%, rgba(59, 130, 246, 0.15) 0%, transparent 70%);
|
||
z-index: 0;
|
||
}
|
||
|
||
.header {
|
||
position: relative;
|
||
z-index: 10;
|
||
padding: 100rpx 40rpx 40rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.title {
|
||
font-size: 38rpx;
|
||
font-weight: 800;
|
||
letter-spacing: 2rpx;
|
||
color: #f8fafc;
|
||
}
|
||
|
||
.refresh-text-btn {
|
||
padding: 12rpx 24rpx;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
border-radius: 12rpx;
|
||
font-size: 24rpx;
|
||
color: #94a3b8;
|
||
transition: all 0.2s;
|
||
|
||
&.loading {
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
}
|
||
|
||
&:active {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
transform: scale(0.95);
|
||
}
|
||
}
|
||
|
||
.content {
|
||
flex: 1;
|
||
padding: 0 30rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.room-list {
|
||
padding-bottom: 60rpx;
|
||
}
|
||
|
||
.room-card {
|
||
margin-bottom: 24rpx;
|
||
padding: 32rpx;
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.room-main {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.room-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 16rpx;
|
||
}
|
||
|
||
.room-id {
|
||
font-size: 28rpx;
|
||
font-weight: 600;
|
||
color: #94a3b8;
|
||
margin-right: 16rpx;
|
||
}
|
||
|
||
.status-badge {
|
||
padding: 4rpx 16rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 20rpx;
|
||
font-weight: 700;
|
||
|
||
&.waiting {
|
||
background: rgba(34, 197, 94, 0.2);
|
||
color: #4ade80;
|
||
}
|
||
|
||
&.started {
|
||
background: rgba(59, 130, 246, 0.2);
|
||
color: #60a5fa;
|
||
}
|
||
}
|
||
|
||
.room-stats {
|
||
display: flex;
|
||
gap: 24rpx;
|
||
}
|
||
|
||
.stat-item {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.stat-icon {
|
||
font-size: 24rpx;
|
||
margin-right: 8rpx;
|
||
}
|
||
|
||
.stat-text {
|
||
font-size: 24rpx;
|
||
color: #cbd5e1;
|
||
}
|
||
|
||
.room-actions {
|
||
display: flex;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.btn-action {
|
||
padding: 16rpx 32rpx;
|
||
border-radius: 12rpx;
|
||
font-size: 24rpx;
|
||
font-weight: 700;
|
||
transition: all 0.2s;
|
||
|
||
&.join {
|
||
background: #3b82f6;
|
||
color: white;
|
||
box-shadow: 0 4rpx 12rpx rgba(59, 130, 246, 0.3);
|
||
}
|
||
|
||
&.watch {
|
||
background: rgba(255, 255, 255, 0.1);
|
||
color: #f8fafc;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
&:active {
|
||
transform: scale(0.95);
|
||
}
|
||
}
|
||
|
||
.empty-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding-top: 200rpx;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 120rpx;
|
||
margin-bottom: 40rpx;
|
||
opacity: 0.5;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 32rpx;
|
||
color: #64748b;
|
||
margin-bottom: 60rpx;
|
||
}
|
||
|
||
.start-new {
|
||
width: 320rpx;
|
||
}
|
||
|
||
@keyframes fadeInUp {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(20rpx);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
.fadeInUp {
|
||
animation: fadeInUp 0.4s ease-out both;
|
||
}
|
||
</style>
|