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;
// 防止双击缩放
touch-action: manipulation;
touch-action: pan-x pan-y;
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
@ -1010,7 +1010,7 @@
width: 100%;
height: auto;
// 防止双击缩放和长按菜单
touch-action: manipulation;
touch-action: pan-x pan-y;
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
@ -1031,8 +1031,8 @@
border: 2rpx dashed rgba($accent-cyan, 0.5);
}
// 观察者旗帜标记样式
&.has-flag {
// 观察者画板标记样式
&.has-mark {
border: 2rpx solid rgba($color-warning, 0.6);
background: rgba($color-warning, 0.1);
}
@ -1221,18 +1221,40 @@
font-weight: 900;
}
// 观察者旗帜图标
.spectator-flag {
// 观察者画板标记
.spectator-mark {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
animation: flagWave 2s ease-in-out infinite;
animation: markAppear 0.3s ease-out;
.flag-icon {
.mark-icon {
font-size: 36rpx;
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 {
display: flex;
justify-content: center;
@ -1329,6 +1360,56 @@
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 {
flex: 1;
height: 100%;

View File

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