fix feat 一大堆关羽扫雷的

This commit is contained in:
tsui110 2026-01-05 11:08:23 +08:00
parent 237d785a4f
commit 5691d0601d
5 changed files with 260 additions and 19 deletions

View File

@ -442,6 +442,27 @@
margin-bottom: $spacing-xs;
display: flex;
gap: $spacing-sm;
// 最新日志高亮动画第一个元素是最新的
&:first-child {
animation: logHighlight 1s ease-out;
background: rgba($brand-primary, 0.1);
padding: 4rpx 8rpx;
border-radius: 4rpx;
margin-left: -8rpx;
margin-right: -8rpx;
}
}
@keyframes logHighlight {
0% {
background: rgba($brand-primary, 0.3);
transform: scale(1.02);
}
100% {
background: rgba($brand-primary, 0.1);
transform: scale(1);
}
}
.log-time {
@ -969,6 +990,16 @@
border: 1px solid $border-dark;
// 确保格子始终保持正方形
aspect-ratio: 1 / 1;
// 阻止长按缩放
touch-action: none;
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
// 额外的防护
-webkit-tap-highlight-color: transparent;
outline: none;
// 禁用任何手势
overscroll-behavior: none;
}
.grid-cell {
@ -983,6 +1014,16 @@
aspect-ratio: 1 / 1;
width: 100%;
height: auto;
// 阻止长按弹出菜单和缩放
touch-action: none;
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
// 额外的防护
-webkit-tap-highlight-color: transparent;
outline: none;
// 禁用任何手势
overscroll-behavior: none;
&:active {
transform: scale(0.95);
@ -1000,6 +1041,12 @@
border: 2rpx dashed rgba($accent-cyan, 0.5);
}
// 观察者旗帜标记样式
&.has-flag {
border: 2rpx solid rgba($color-warning, 0.6);
background: rgba($color-warning, 0.1);
}
// --- 新增特效样式 ---
&.explosion {
animation: explode 0.5s ease-out;
@ -1184,6 +1231,33 @@
font-weight: 900;
}
// 观察者旗帜图标
.spectator-flag {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
animation: flagWave 2s ease-in-out infinite;
.flag-icon {
font-size: 36rpx;
filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.3));
}
}
@keyframes flagWave {
0%, 100% {
transform: rotate(0deg);
}
25% {
transform: rotate(5deg);
}
75% {
transform: rotate(-5deg);
}
}
.magnifier-mark {
display: flex;
justify-content: center;
@ -1281,6 +1355,15 @@
overflow: hidden;
text-overflow: ellipsis;
// 最新日志高亮动画第一个元素是最新的
&:first-child {
animation: gameLogHighlight 0.8s ease-out;
background: rgba($brand-primary, 0.15);
padding: 2rpx 6rpx;
border-radius: 4rpx;
font-weight: 600;
}
&.log-system {
color: $accent-cyan;
}
@ -1290,6 +1373,17 @@
}
}
@keyframes gameLogHighlight {
0% {
background: rgba($brand-primary, 0.4);
transform: scale(1.05);
}
100% {
background: rgba($brand-primary, 0.15);
transform: scale(1);
}
}
// =====================================
// 弹窗 (全屏遮罩)

View File

@ -165,10 +165,14 @@
cell.revealed ? (cell.type === 'bomb' ? 'type-bomb explosion' : (cell.type === 'item' ? 'type-item item-' + (cell.itemId || 'generic') : 'type-empty')) : 'bg-slate-800',
{
'revealed': cell.revealed,
'has-magnifier': myPlayer && myPlayer.revealedCells && myPlayer.revealedCells[i]
'has-magnifier': myPlayer && myPlayer.revealedCells && myPlayer.revealedCells[i],
'has-flag': isSpectator && spectatorFlags[i] //
}
]"
@tap="handleCellClick(i)"
@touchstart="handleCellTouchStart(i, $event)"
@touchend="handleCellTouchEnd($event)"
@touchmove="handleCellTouchMove($event)"
>
<view v-if="cell.revealed">
<text v-if="cell.type === 'bomb'" class="cell-icon">💣</text>
@ -188,6 +192,11 @@
<text class="magnifier-badge">🔍</text>
</view>
<!-- 观察者旗帜标记 -->
<view v-else-if="isSpectator && spectatorFlags[i]" class="spectator-flag">
<text class="flag-icon">🚩</text>
</view>
<!-- 格子上的飘字 -->
<view v-for="l in getCellLabels(i)" :key="l.id" class="float-label" :class="'text-' + l.type">
{{ l.text }}
@ -335,6 +344,8 @@ export default {
showResultModal: false,
onlineCount: 0,
onlineCountInterval: null,
spectatorFlags: {}, // { cellIndex: true }
longPressTimer: null, //
// Timers
matchInterval: null,
turnInterval: null,
@ -407,11 +418,6 @@ export default {
this.showResultModal = true;
}
},
logs() {
this.$nextTick(() => {
this.logsScrollTop = 99999 + Math.random();
});
},
'gameState.currentTurnIndex'() {
this.resetTurnTimer();
},
@ -422,7 +428,11 @@ export default {
onLoad(options) {
this.fetchGameConfig();
const { game_token, nakama_server, nakama_key, match_id, is_spectator, uid } = options;
if (is_spectator) this.isSpectator = true;
if (is_spectator) {
this.isSpectator = true;
//
this.disablePageZoom();
}
if (match_id) this.matchId = match_id;
if (uid) this.stableUserId = uid;
@ -436,6 +446,33 @@ export default {
this.cleanup();
},
methods: {
disablePageZoom() {
//
// H5
// #ifdef H5
document.addEventListener('contextmenu', (e) => {
e.preventDefault();
e.stopPropagation();
return false;
});
//
document.addEventListener('gesturestart', (e) => {
e.preventDefault();
});
document.addEventListener('gesturechange', (e) => {
e.preventDefault();
});
document.addEventListener('gestureend', (e) => {
e.preventDefault();
});
// #endif
//
// pages.json
},
async fetchGameConfig() {
try {
const res = await new Promise((resolve, reject) => {
@ -464,10 +501,19 @@ export default {
},
addLog(type, content) {
const now = new Date();
const timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0');
const timeStr = now.getHours().toString().padStart(2, '0') + ':' + now.getMinutes().toString().padStart(2, '0') + ':' + now.getSeconds().toString().padStart(2, '0');
const id = Date.now() + Math.random().toString();
this.logs.push({ id, type, content, time: timeStr });
if (this.logs.length > 50) this.logs.shift();
//
this.logs.unshift({ id, type, content, time: timeStr });
// 100
if (this.logs.length > 100) this.logs.pop();
//
this.$nextTick(() => {
this.logsScrollTop = 0;
});
},
spawnLabel(x, y, text, type, cellIndex, targetUserId) {
const id = Date.now() + Math.random().toString();
@ -839,7 +885,10 @@ export default {
},
handleCellClick(idx) {
//
if (this.isSpectator) return;
if (this.isSpectator) {
//
return;
}
if (!this.gameState?.gameStarted) {
uni.showToast({ title: '游戏尚未开始', icon: 'none' });
return;
@ -854,6 +903,69 @@ export default {
//
nakamaManager.sendMatchState(this.matchId, 3, JSON.stringify({ index: idx }));
},
//
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} 插上了旗帜`);
}
},
// -
handleCellTouchStart(idx, event) {
//
if (!this.isSpectator) return;
//
if (event) {
event.preventDefault();
event.stopPropagation();
}
//
if (this.longPressTimer) {
clearTimeout(this.longPressTimer);
}
// 800ms
this.longPressTimer = setTimeout(() => {
//
// #ifndef MP-ALIPAY
uni.vibrateShort();
// #endif
this.handleCellLongPress(idx);
this.longPressTimer = null;
}, 800);
},
// -
handleCellTouchEnd(event) {
//
if (!this.isSpectator) return;
if (this.longPressTimer) {
clearTimeout(this.longPressTimer);
this.longPressTimer = null;
}
},
// -
handleCellTouchMove(event) {
//
if (!this.isSpectator) return;
//
if (this.longPressTimer) {
clearTimeout(this.longPressTimer);
this.longPressTimer = null;
}
},
refreshAndPlayAgain() {
uni.removeStorageSync('minesweeper_last_match_id');
uni.navigateBack();
@ -873,6 +985,13 @@ export default {
clearInterval(this.matchInterval);
clearInterval(this.turnInterval);
clearInterval(this.onlineCountInterval);
//
if (this.longPressTimer) {
clearTimeout(this.longPressTimer);
this.longPressTimer = null;
}
nakamaManager.disconnect();
},
async fetchOnlineCount() {

View File

@ -79,7 +79,7 @@
</view>
</view>
<text class="coupon-rules">{{ item.rules || '全场通用' }}</text>
<text class="coupon-rules">{{ formatRules(item.rules) }}</text>
<!-- 使用进度条 -->
<view class="coupon-progress" v-if="item.amount && item.remaining !== undefined && item.remaining < item.amount">
@ -164,6 +164,18 @@ function formatValue(val) {
return (Number(val) / 100).toFixed(0)
}
//
function formatRules(rules) {
if (!rules) return '全场通用'
// "XXX""¥X.XX"
return rules.replace(/(\d+)分/g, (match, p1) => {
const yuan = (Number(p1) / 100).toFixed(2)
// .00
const formatted = yuan.endsWith('.00') ? yuan.slice(0, -3) : yuan
return `¥${formatted}`
})
}
//
function formatExpiry(item) {
// valid_end

View File

@ -184,6 +184,12 @@
"navigationStyle": "default",
"navigationBarTitleText": "扫雷对战",
"disableScroll": true,
"mp-weixin": {
"disableSwipeBack": true
},
"h5": {
"titleNView": false
},
"app-plus": {
"bounce": "none"
}

View File

@ -268,7 +268,7 @@
</view>
</view>
<text class="coupon-rules">{{ item.rules || '全场通用' }}</text>
<text class="coupon-rules">{{ formatCouponRules(item.rules) }}</text>
<!-- 使用进度条 (仅当有使用记录时显示) -->
<view class="coupon-progress-wrap" v-if="item.amount && item.remaining !== undefined && item.remaining < item.amount">
@ -1135,6 +1135,16 @@ export default {
formatCouponValue(val) {
return (Number(val) / 100).toFixed(0)
},
formatCouponRules(rules) {
if (!rules) return '全场通用'
// "XXX""¥X.XX"
return rules.replace(/(\d+)分/g, (match, p1) => {
const yuan = (Number(p1) / 100).toFixed(2)
// .00
const formatted = yuan.endsWith('.00') ? yuan.slice(0, -3) : yuan
return `¥${formatted}`
})
},
formatCouponExpiry(item) {
if (!item.end_time) return '长期有效'
return `有效期至 ${this.formatDate(item.end_time)}`