feat:观察者修改为画板模式

This commit is contained in:
tsui110 2026-01-05 14:04:10 +08:00
parent 96555e690c
commit 1d2599441e
2 changed files with 184 additions and 54 deletions

View File

@ -991,7 +991,7 @@
// 确保格子始终保持正方形 // 确保格子始终保持正方形
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
// 防止双击缩放 // 防止双击缩放
touch-action: manipulation; touch-action: pan-x pan-y;
user-select: none; user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
-webkit-touch-callout: none; -webkit-touch-callout: none;
@ -1010,7 +1010,7 @@
width: 100%; width: 100%;
height: auto; height: auto;
// 防止双击缩放和长按菜单 // 防止双击缩放和长按菜单
touch-action: manipulation; touch-action: pan-x pan-y;
user-select: none; user-select: none;
-webkit-user-select: none; -webkit-user-select: none;
-webkit-touch-callout: none; -webkit-touch-callout: none;
@ -1031,8 +1031,8 @@
border: 2rpx dashed rgba($accent-cyan, 0.5); border: 2rpx dashed rgba($accent-cyan, 0.5);
} }
// 观察者旗帜标记样式 // 观察者画板标记样式
&.has-flag { &.has-mark {
border: 2rpx solid rgba($color-warning, 0.6); border: 2rpx solid rgba($color-warning, 0.6);
background: rgba($color-warning, 0.1); background: rgba($color-warning, 0.1);
} }
@ -1221,18 +1221,40 @@
font-weight: 900; font-weight: 900;
} }
// 观察者旗帜图标 // 观察者画板标记
.spectator-flag { .spectator-mark {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
animation: flagWave 2s ease-in-out infinite; animation: markAppear 0.3s ease-out;
.flag-icon { .mark-icon {
font-size: 36rpx; font-size: 36rpx;
filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.3)); filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.3));
&.flag {
animation: flagWave 2s ease-in-out infinite;
}
&.check {
animation: checkBounce 0.5s ease-out;
}
}
}
@keyframes markAppear {
0% {
transform: scale(0);
opacity: 0;
}
60% {
transform: scale(1.2);
}
100% {
transform: scale(1);
opacity: 1;
} }
} }
@ -1248,6 +1270,15 @@
} }
} }
@keyframes checkBounce {
0%, 100% {
transform: scale(1);
}
50% {
transform: scale(1.3) rotate(10deg);
}
}
.magnifier-mark { .magnifier-mark {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -1329,6 +1360,56 @@
height: 180rpx; // 减小高度 height: 180rpx; // 减小高度
} }
// 观察者工具栏
.spectator-toolbar {
display: flex;
gap: $spacing-md;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.tool-btn {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: $spacing-xs;
background: rgba(255, 255, 255, 0.05);
border: 2rpx solid rgba(255, 255, 255, 0.1);
border-radius: $radius-lg;
transition: all $transition-normal;
cursor: pointer;
&:active {
transform: scale(0.95);
}
&.active {
background: rgba($brand-primary, 0.2);
border-color: rgba($brand-primary, 0.6);
box-shadow: 0 0 20rpx rgba($brand-primary, 0.3);
.tool-icon {
transform: scale(1.1);
}
}
.tool-icon {
font-size: 48rpx;
transition: transform $transition-normal;
}
.tool-name {
font-size: $font-xs;
color: $text-dark-sub;
font-weight: 600;
}
}
.game-logs { .game-logs {
flex: 1; flex: 1;
height: 100%; height: 100%;

View File

@ -166,11 +166,10 @@
{ {
'revealed': cell.revealed, 'revealed': cell.revealed,
'has-magnifier': myPlayer && myPlayer.revealedCells && myPlayer.revealedCells[i], 'has-magnifier': myPlayer && myPlayer.revealedCells && myPlayer.revealedCells[i],
'has-flag': isSpectator && spectatorFlags[i] // 'has-mark': isSpectator && spectatorMarks[i] //
} }
]" ]"
@tap="handleCellClick(i)" @tap="handleCellClick(i)"
catchtap="handleCellCatchTap(i)"
> >
<view v-if="cell.revealed"> <view v-if="cell.revealed">
<text v-if="cell.type === 'bomb'" class="cell-icon">💣</text> <text v-if="cell.type === 'bomb'" class="cell-icon">💣</text>
@ -190,9 +189,10 @@
<text class="magnifier-badge">🔍</text> <text class="magnifier-badge">🔍</text>
</view> </view>
<!-- 观察者旗帜标记 --> <!-- 观察者画板标记 -->
<view v-else-if="isSpectator && spectatorFlags[i]" class="spectator-flag"> <view v-else-if="isSpectator && spectatorMarks[i]" class="spectator-mark">
<text class="flag-icon">🚩</text> <text v-if="spectatorMarks[i] === 'flag'" class="mark-icon flag">🚩</text>
<text v-else-if="spectatorMarks[i] === 'check'" class="mark-icon check"></text>
</view> </view>
<!-- 格子上的飘字 --> <!-- 格子上的飘字 -->
@ -205,7 +205,25 @@
<!-- 底部面板 --> <!-- 底部面板 -->
<view class="bottom-panel"> <view class="bottom-panel">
<view v-if="myPlayer" class="player-card me" <!-- 观察者工具栏 -->
<view v-if="isSpectator" class="spectator-toolbar">
<view
v-for="tool in [
{ id: 'flag', icon: '🚩', name: '旗帜' },
{ id: 'check', icon: '✅', name: '勾选' },
{ id: 'eraser', icon: '🧹', name: '橡皮擦' }
]"
:key="tool.id"
class="tool-btn"
:class="{ active: spectatorTool === tool.id }"
@tap="selectTool(tool.id)"
>
<text class="tool-icon">{{ tool.icon }}</text>
<text class="tool-name">{{ tool.name }}</text>
</view>
</view>
<view v-if="myPlayer && !isSpectator" class="player-card me"
:class="{ :class="{
'active-turn': isMyTurn, 'active-turn': isMyTurn,
'damaged': damagedPlayers.includes(myPlayer.userId), 'damaged': damagedPlayers.includes(myPlayer.userId),
@ -343,6 +361,8 @@ export default {
onlineCount: 0, onlineCount: 0,
onlineCountInterval: null, onlineCountInterval: null,
spectatorFlags: {}, // { cellIndex: true } spectatorFlags: {}, // { cellIndex: true }
spectatorMarks: {}, // { cellIndex: 'flag' | 'check' }
spectatorTool: 'flag', // : 'flag' | 'check' | 'eraser'
lastTapTime: 0, // lastTapTime: 0, //
lastTapIndex: -1, // lastTapIndex: -1, //
// Timers // Timers
@ -435,6 +455,18 @@ export default {
if (match_id) this.matchId = match_id; if (match_id) this.matchId = match_id;
if (uid) this.stableUserId = uid; if (uid) this.stableUserId = uid;
//
// #ifdef MP-WEIXIN
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
if (currentPage && currentPage.$page) {
currentPage.$page.meta = Object.assign({}, currentPage.$page.meta, {
disableScroll: true,
disableScale: true
});
}
// #endif
if (game_token) { if (game_token) {
this.initNakama(game_token, decodeURIComponent(nakama_server || ''), decodeURIComponent(nakama_key || ''), uid); this.initNakama(game_token, decodeURIComponent(nakama_server || ''), decodeURIComponent(nakama_key || ''), uid);
} else { } else {
@ -883,22 +915,56 @@ export default {
this.addLog('system', '已切断匹配信号'); this.addLog('system', '已切断匹配信号');
}, },
handleCellClick(idx) { handleCellClick(idx) {
// //
if (this.isSpectator) { if (this.isSpectator) {
const now = Date.now(); if (!this.gameState?.grid) return;
const timeDiff = now - this.lastTapTime; if (this.gameState.grid[idx].revealed) return;
const isSameCell = this.lastTapIndex === idx;
// 300ms //
if (isSameCell && timeDiff < 300) { if (this.spectatorTool === 'eraser') {
// //
this.handleCellLongPress(idx); if (this.spectatorMarks[idx]) {
this.lastTapTime = 0; const markType = this.spectatorMarks[idx] === 'flag' ? '旗帜' : '勾选';
this.lastTapIndex = -1; this.$set(this.spectatorMarks, idx, undefined);
} else { this.$nextTick(() => {
// delete this.spectatorMarks[idx];
this.lastTapTime = now; });
this.lastTapIndex = idx; this.addLog('system', `🧹 擦除了位置 ${idx}${markType}`);
}
} else if (this.spectatorTool === 'flag') {
//
if (this.spectatorMarks[idx] === 'flag') {
this.$set(this.spectatorMarks, idx, undefined);
this.$nextTick(() => {
delete this.spectatorMarks[idx];
});
this.addLog('system', `🚩 移除了位置 ${idx} 的旗帜`);
} else {
this.$set(this.spectatorMarks, idx, 'flag');
this.addLog('system', `🚩 在位置 ${idx} 插上了旗帜`);
}
//
// #ifndef MP-ALIPAY
uni.vibrateShort();
// #endif
} else if (this.spectatorTool === 'check') {
//
if (this.spectatorMarks[idx] === 'check') {
this.$set(this.spectatorMarks, idx, undefined);
this.$nextTick(() => {
delete this.spectatorMarks[idx];
});
this.addLog('system', `✅ 移除了位置 ${idx} 的勾选`);
} else {
this.$set(this.spectatorMarks, idx, 'check');
this.addLog('system', `✅ 在位置 ${idx} 标记了勾选`);
}
//
// #ifndef MP-ALIPAY
uni.vibrateShort();
// #endif
} }
return; return;
} }
@ -918,32 +984,15 @@ export default {
// //
nakamaManager.sendMatchState(this.matchId, 3, JSON.stringify({ index: idx })); nakamaManager.sendMatchState(this.matchId, 3, JSON.stringify({ index: idx }));
}, },
// //
handleCellCatchTap(idx) { selectTool(tool) {
// catchtap this.spectatorTool = tool;
// @tap const toolNames = {
// catchtap flag: '🚩 旗帜',
}, check: '✅ 勾选',
// eraser: '🧹 橡皮擦'
handleCellLongPress(idx) { };
// this.addLog('system', `切换工具: ${toolNames[tool]}`);
if (!this.isSpectator) return;
if (!this.gameState?.grid) return;
if (this.gameState.grid[idx].revealed) return;
//
if (this.spectatorFlags[idx]) {
this.$delete(this.spectatorFlags, idx);
this.addLog('system', `🚩 移除了位置 ${idx} 的旗帜`);
} else {
this.$set(this.spectatorFlags, idx, true);
this.addLog('system', `🚩 在位置 ${idx} 插上了旗帜`);
//
// #ifndef MP-ALIPAY
uni.vibrateShort();
// #endif
}
}, },
refreshAndPlayAgain() { refreshAndPlayAgain() {
uni.removeStorageSync('minesweeper_last_match_id'); uni.removeStorageSync('minesweeper_last_match_id');