update:修复日志和防抖

This commit is contained in:
2026-07-09 14:35:48 +08:00
parent f8efe4c4a4
commit 12729a1cef
5 changed files with 106 additions and 31 deletions
+7 -2
View File
@@ -36,6 +36,7 @@ const createRoom = debounce(async () => {
} }
if (loading.value === true) return; if (loading.value === true) return;
loading.value = true; loading.value = true;
let keepLoading = false;
let size = 2; let size = 2;
if (battleMode.value === 2) size = 10; if (battleMode.value === 2) size = 10;
if (battleMode.value === 3) size = 4; if (battleMode.value === 3) size = 4;
@@ -49,14 +50,18 @@ const createRoom = debounce(async () => {
if (result.number) { if (result.number) {
props.onConfirm(); props.onConfirm();
await joinRoomAPI(result.number); await joinRoomAPI(result.number);
keepLoading = true;
uni.navigateTo({ uni.navigateTo({
url: "/pages/battle-room?roomNumber=" + result.number + "&target=" + targetMode.value, url: "/pages/battle-room?roomNumber=" + result.number + "&target=" + targetMode.value,
fail: () => {
loading.value = false;
},
}); });
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally { } finally {
loading.value = false; if (!keepLoading) loading.value = false;
} }
}); });
</script> </script>
@@ -112,7 +117,7 @@ const createRoom = debounce(async () => {
<text>40厘米全环靶</text> <text>40厘米全环靶</text>
</view> </view>
</view> </view>
<SButton :onClick="() => $clickSound(createRoom)">创建房间</SButton> <SButton :disabled="loading" :onClick="() => { $clickSound(); return createRoom(); }">创建房间</SButton>
</view> </view>
</template> </template>
+41 -14
View File
@@ -159,7 +159,6 @@ function emitBusinessMessage(message) {
scheduleReadyRouteFallback(message); scheduleReadyRouteFallback(message);
} }
console.log("[match-ws] emit socket-inbox", message.matchWsTypeName, message);
uni.$emit("socket-inbox", message); uni.$emit("socket-inbox", message);
} }
@@ -223,6 +222,13 @@ function getAckAudioKeys(message, businessMessage) {
} }
} }
function shouldCloseAfterAck(message) {
return (
message.type === ServerMessageType.SERVER_MSG_MATCH_END ||
message.type === ServerMessageType.SERVER_MSG_TIMEOUT
);
}
// 追加 token 查询参数;如果后端地址已经带 token,则保持原样。 // 追加 token 查询参数;如果后端地址已经带 token,则保持原样。
function appendQuery(url, key, value) { function appendQuery(url, key, value) {
if (!value || new RegExp(`[?&]${key}=`).test(url)) return url; if (!value || new RegExp(`[?&]${key}=`).test(url)) return url;
@@ -251,32 +257,47 @@ function normalizeServerUrl(serverAddr, token) {
} }
// 统一发送二进制 protobuf 消息,并在日志里保留发送类型。 // 统一发送二进制 protobuf 消息,并在日志里保留发送类型。
function sendBuffer(buffer, label) { function sendBuffer(buffer, label, clientMessage) {
if (!socket || !buffer) return; if (!socket || !buffer) return;
const currentSocket = socket; const currentSocket = socket;
currentSocket.send({ currentSocket.send({
data: buffer, data: buffer,
success: () => { success: () => {
if (label === "heartbeat ack") return; console.log("发送比赛服 WebSocket 消息", label, clientMessage);
console.log(`[match-ws] ${label} sent`, new Date());
}, },
fail: (err) => { fail: (err) => {
if (socket !== currentSocket) return; if (socket !== currentSocket) return;
console.log(`[match-ws] ${label} send failed`, err); console.log("比赛服 WebSocket 消息发送失败", label, clientMessage, err);
}, },
}); });
} }
function sendHeartbeatAck() { function sendHeartbeatAck() {
// 心跳 ACK 不进入 pendingAcks,收到后立即回复。 // 心跳 ACK 不进入 pendingAcks,收到后立即回复。
sendBuffer(createHeartbeatAckMessage(), "heartbeat ack"); const clientMessage = {
type: ClientMessageType.CLIENT_MSG_HEARTBEAT_ACK,
};
sendBuffer(
createHeartbeatAckMessage(),
"CLIENT_MSG_HEARTBEAT_ACK",
clientMessage
);
} }
function sendAck({ matchId, sequence }) { function sendAck({ matchId, sequence }) {
// sequence 由后端处理,前端只原样带回,不做重排和断线补发。 // sequence 由后端处理,前端只原样带回,不做重排和断线补发。
if (sequence === undefined || sequence === null || sequence === "") return; if (sequence === undefined || sequence === null || sequence === "") return;
sendBuffer(createAckMessage({ matchId, sequence }), `ack ${sequence}`); const clientMessage = {
type: ClientMessageType.CLIENT_MSG_ACK,
match_id: matchId,
sequence,
};
sendBuffer(
createAckMessage({ matchId, sequence }),
"CLIENT_MSG_ACK",
clientMessage
);
} }
function completeAckTask(task) { function completeAckTask(task) {
@@ -348,7 +369,7 @@ function queueAckAfterAudio(message, businessMessage) {
pickField(message, "matchId", "match_id") || currentContext?.matchId pickField(message, "matchId", "match_id") || currentContext?.matchId
), ),
sequence: message.sequence, sequence: message.sequence,
leaveAfterAck: message.type === ServerMessageType.SERVER_MSG_MATCH_END, leaveAfterAck: shouldCloseAfterAck(message),
}; };
const audioKeys = getAckAudioKeys(message, businessMessage).filter(Boolean); const audioKeys = getAckAudioKeys(message, businessMessage).filter(Boolean);
@@ -394,9 +415,6 @@ function handleMessage(data) {
return; return;
} }
const typeName = getServerMessageTypeName(message.type);
console.log("[match-ws] message decoded", typeName, message);
const decodedMatchId = normalizeId(pickField(message, "matchId", "match_id")); const decodedMatchId = normalizeId(pickField(message, "matchId", "match_id"));
if (decodedMatchId && currentContext) { if (decodedMatchId && currentContext) {
currentContext.matchId = decodedMatchId; currentContext.matchId = decodedMatchId;
@@ -407,6 +425,9 @@ function handleMessage(data) {
return; return;
} }
const typeName = getServerMessageTypeName(message.type);
console.log("收到比赛服 WebSocket 消息", typeName, message);
const businessMessage = buildBusinessMessage(message); const businessMessage = buildBusinessMessage(message);
if (businessMessage?.matchId && currentContext) { if (businessMessage?.matchId && currentContext) {
currentContext.matchId = businessMessage.matchId; currentContext.matchId = businessMessage.matchId;
@@ -418,12 +439,18 @@ function handleMessage(data) {
function sendLeave() { function sendLeave() {
// 主动关闭、比赛结束、页面离开时发 CLIENT_MSG_LEAVE。 // 主动关闭、比赛结束、页面离开时发 CLIENT_MSG_LEAVE。
if (!socket || !currentContext?.matchId) return; if (!socket || !currentContext?.matchId) return;
const clientMessage = {
type: ClientMessageType.CLIENT_MSG_LEAVE,
match_id: currentContext.matchId,
user_id: currentContext.userId,
};
sendBuffer( sendBuffer(
createLeaveMessage({ createLeaveMessage({
matchId: currentContext.matchId, matchId: clientMessage.match_id,
userId: currentContext.userId, userId: clientMessage.user_id,
}), }),
"leave" "CLIENT_MSG_LEAVE",
clientMessage
); );
} }
+13 -4
View File
@@ -15,7 +15,7 @@ import {
getReadyAPI, getReadyAPI,
kickPlayerAPI, kickPlayerAPI,
} from "@/apis"; } from "@/apis";
import { isLimitError } from "@/util"; import { debounce, isLimitError } from "@/util";
import { MESSAGETYPES, MESSAGETYPESV2 } from "@/constants"; import { MESSAGETYPES, MESSAGETYPESV2 } from "@/constants";
import useStore from "@/store"; import useStore from "@/store";
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
@@ -59,6 +59,7 @@ const allReady = ref(false);
const timer = ref(null); const timer = ref(null);
const goBattle = ref(false); const goBattle = ref(false);
const showLimitModal = ref(false); const showLimitModal = ref(false);
const readySubmitting = ref(false);
/** 从结算页返回时为 true,跳过进场靶纸语音 */ /** 从结算页返回时为 true,跳过进场靶纸语音 */
const skipTargetAudio = ref(false); const skipTargetAudio = ref(false);
@@ -139,17 +140,22 @@ async function refreshRoomData() {
// timer.value = setTimeout(refreshRoomData, 2000); // timer.value = setTimeout(refreshRoomData, 2000);
} }
const getReady = async () => { const getReady = debounce(async () => {
if (readySubmitting.value) return;
readySubmitting.value = true;
try { try {
await getReadyAPI(roomNumber.value); await getReadyAPI(roomNumber.value);
ready.value = true;
} catch (error) { } catch (error) {
if (isLimitError(error)) { if (isLimitError(error)) {
showLimitModal.value = true; showLimitModal.value = true;
return; return;
} }
console.log("room ready error", error); console.log("room ready error", error);
} finally {
readySubmitting.value = false;
} }
}; });
const closeLimitModal = () => { const closeLimitModal = () => {
showLimitModal.value = false; showLimitModal.value = false;
@@ -465,7 +471,10 @@ onBeforeUnmount(() => {
<PlayerSeats v-if="room.battleType === 2" :total="room.count || 10" :players="players" <PlayerSeats v-if="room.battleType === 2" :total="room.count || 10" :players="players"
:removePlayer="removePlayer" :isOwner="owner.id === user.id" /> :removePlayer="removePlayer" :isOwner="owner.id === user.id" />
<view> <view>
<SButton :disabled="!canClick" :onClick="() => $clickSound(getReady)"> <SButton
:disabled="!canClick || readySubmitting"
:onClick="() => { $clickSound(); return getReady(); }"
>
{{ {{
allReady.value allReady.value
? "即将进入对局..." ? "即将进入对局..."
+16 -4
View File
@@ -27,6 +27,7 @@ const data = ref({});
const roomID = ref(""); const roomID = ref("");
const loading = ref(false); const loading = ref(false);
const showLimitModal = ref(false); const showLimitModal = ref(false);
const createRoomLoading = ref(false);
const isSVip = computed(() => user.value.sVip === true); const isSVip = computed(() => user.value.sVip === true);
const isVip = computed(() => user.value.vip === true && !isSVip.value); const isVip = computed(() => user.value.vip === true && !isSVip.value);
const challengeLimitText = computed(() => const challengeLimitText = computed(() =>
@@ -45,6 +46,7 @@ const enterRoom = debounce(async (number) => {
showModal.value = true; showModal.value = true;
return; return;
} }
loading.value = true;
try { try {
const room = await getRoomAPI(number); const room = await getRoomAPI(number);
if (!room.number) { if (!room.number) {
@@ -63,7 +65,6 @@ const enterRoom = debounce(async (number) => {
return; return;
} }
} }
loading.value = true;
uni.navigateTo({ uni.navigateTo({
url: "/pages/battle-room?roomNumber=" + number, url: "/pages/battle-room?roomNumber=" + number,
}); });
@@ -71,8 +72,11 @@ const enterRoom = debounce(async (number) => {
loading.value = false; loading.value = false;
} }
}); });
const onCreateRoom = async () => { const onCreateRoom = debounce(async () => {
if (createRoomLoading.value) return;
if (!canEenter(user.value, device.value, online.value)) return; if (!canEenter(user.value, device.value, online.value)) return;
createRoomLoading.value = true;
try {
const countData = await loadDailyCount(); const countData = await loadDailyCount();
if (isLimitReached(countData.challenge)) { if (isLimitReached(countData.challenge)) {
showLimitModal.value = true; showLimitModal.value = true;
@@ -80,7 +84,10 @@ const onCreateRoom = async () => {
} }
warnning.value = ""; warnning.value = "";
showModal.value = true; showModal.value = true;
}; } finally {
createRoomLoading.value = false;
}
});
const closeLimitModal = () => { const closeLimitModal = () => {
showLimitModal.value = false; showLimitModal.value = false;
@@ -208,7 +215,12 @@ onLoad(async (options) => {
<view v-if="challengeLimitText" class="pp-text"> <view v-if="challengeLimitText" class="pp-text">
{{ challengeLimitText }} {{ challengeLimitText }}
</view> </view>
<SButton width="80%" :rounded="30" :onClick="() => $clickSound(onCreateRoom)"> <SButton
width="80%"
:rounded="30"
:disabled="createRoomLoading"
:onClick="() => { $clickSound(); return onCreateRoom(); }"
>
创建约战房 创建约战房
</SButton> </SButton>
</view> </view>
+23 -1
View File
@@ -13,7 +13,11 @@ import TestDistance from "@/components/TestDistance.vue";
import SModal from "@/components/SModal.vue"; import SModal from "@/components/SModal.vue";
import audioManager from "@/audioManager"; import audioManager from "@/audioManager";
import { getBattleAPI, laserCloseAPI } from "@/apis"; import { getBattleAPI, laserCloseAPI } from "@/apis";
import { closeMatchWebSocket, connectMatchWebSocket } from "@/matchWebsocket"; import {
closeMatchWebSocket,
connectMatchWebSocket,
MATCH_WS_AUDIO_ACK_EVENT,
} from "@/matchWebsocket";
import { MESSAGETYPESV2 } from "@/constants"; import { MESSAGETYPESV2 } from "@/constants";
import { takeMatchReturnSnapshot } from "@/utils/matchReturn"; import { takeMatchReturnSnapshot } from "@/utils/matchReturn";
import useStore from "@/store"; import useStore from "@/store";
@@ -172,6 +176,23 @@ function closeBattleServer(reason) {
closeMatchWebSocket({ reason }); closeMatchWebSocket({ reason });
} }
function notifyMatchAudioAck(message) {
if (
message?.sequence === undefined ||
message?.sequence === null ||
message?.sequence === ""
) {
return;
}
uni.$emit(MATCH_WS_AUDIO_ACK_EVENT, {
matchId: message.matchId || battleId.value,
sequence: message.sequence,
matchWsType: message.matchWsType,
matchWsTypeName: message.matchWsTypeName,
});
}
/** /**
* 监听设备在线状态,大乱斗比赛进行中设备离线时弹窗提示用户 * 监听设备在线状态,大乱斗比赛进行中设备离线时弹窗提示用户
*/ */
@@ -346,6 +367,7 @@ async function onReceiveMessage(msg) {
startHalfRestCountdown(); startHalfRestCountdown();
} else if (msg.type === MESSAGETYPESV2.BattleEnd) { } else if (msg.type === MESSAGETYPESV2.BattleEnd) {
battleEnded = true; battleEnded = true;
notifyMatchAudioAck(msg);
setTimeout(() => { setTimeout(() => {
// 全部跳转到新结算页 // 全部跳转到新结算页
uni.redirectTo({ uni.redirectTo({