349 lines
8.0 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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 ? '同步中...' : `刷新信号 (${countdown}s)` }}
</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: '',
refreshInterval: null,
countdownInterval: null,
countdown: 5
}
},
onLoad(options) {
this.gameToken = options.game_token;
this.nakamaServer = decodeURIComponent(options.nakama_server || '');
this.nakamaKey = decodeURIComponent(options.nakama_key || '');
this.initAndLoad();
// 启动倒计时定时器,每秒更新一次
this.countdownInterval = setInterval(() => {
this.countdown--;
if (this.countdown <= 0) {
this.countdown = 5;
}
}, 1000);
// 启动自动刷新定时器每5秒刷新一次
this.refreshInterval = setInterval(() => {
this.loadRooms();
}, 5000);
},
onUnload() {
// 清理定时器
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
this.refreshInterval = null;
}
if (this.countdownInterval) {
clearInterval(this.countdownInterval);
this.countdownInterval = null;
}
},
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 || [];
// 重置倒计时
this.countdown = 5;
} 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>