update:新版本websocket
This commit is contained in:
@@ -12,6 +12,7 @@ const gameType = ref(0);
|
||||
const teamSize = ref(0);
|
||||
const onComplete = ref(null);
|
||||
const showLimitModal = ref(false);
|
||||
const MATCH_READY_SNAPSHOT_PREFIX = "match-ready-snapshot:";
|
||||
|
||||
/** 匹配超时计时器,用于检测 WS 消息丢失或真正超时 */
|
||||
const matchTimeoutTimer = ref(null);
|
||||
@@ -27,6 +28,17 @@ const clearMatchTimeout = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const cacheReadyBattle = (battle) => {
|
||||
const matchId = String(battle?.matchId || "");
|
||||
if (!matchId) return;
|
||||
|
||||
uni.setStorageSync(`${MATCH_READY_SNAPSHOT_PREFIX}${matchId}`, {
|
||||
...battle,
|
||||
status: battle.status == null ? 0 : Number(battle.status),
|
||||
savedAt: Date.now(),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 超时处理:查询后端是否已分配对局
|
||||
* - 有对局 → WS 消息丢失场景,自动跳入
|
||||
@@ -36,6 +48,7 @@ const handleMatchTimeout = async () => {
|
||||
try {
|
||||
const battle = await getBattleAPI();
|
||||
if (battle && battle.matchId) {
|
||||
cacheReadyBattle(battle);
|
||||
uni.showToast({ title: "匹配成功,正在进入...", icon: "none" });
|
||||
if (battle.mode <= 3) {
|
||||
uni.redirectTo({ url: `/pages/team-battle/index?battleId=${battle.matchId}` });
|
||||
|
||||
@@ -36,6 +36,7 @@ const playersSorted = ref([]);
|
||||
const playersScores = ref([]);
|
||||
const halfTimeTip = ref(false);
|
||||
const halfRest = ref(false);
|
||||
const DEFAULT_READY_TIME = 15;
|
||||
const HALF_REST_SECONDS = 20;
|
||||
const MATCH_READY_SNAPSHOT_PREFIX = "match-ready-snapshot:";
|
||||
const MATCH_STATUS_BY_TEXT = {
|
||||
@@ -46,6 +47,7 @@ const MATCH_STATUS_BY_TEXT = {
|
||||
MATCH_STATUS_UNEXPECTEDLY: 4,
|
||||
};
|
||||
const halfRestRemain = ref(HALF_REST_SECONDS);
|
||||
const readyTime = ref(DEFAULT_READY_TIME);
|
||||
let halfRestTimer = null;
|
||||
/** 控制设备离线提示弹窗的显示状态 */
|
||||
const showOfflineModal = ref(false);
|
||||
@@ -132,6 +134,27 @@ function normalizeBattleInfo(battleInfo) {
|
||||
return battleInfo;
|
||||
}
|
||||
|
||||
function normalizeTimestamp(value) {
|
||||
const numberValue = Number(value || 0);
|
||||
if (!numberValue) return 0;
|
||||
return numberValue < 1000000000000 ? numberValue * 1000 : numberValue;
|
||||
}
|
||||
|
||||
function getReadyTime(battleInfo) {
|
||||
const value = Number(battleInfo?.readyTime);
|
||||
return Number.isFinite(value) && value > 0 ? value : DEFAULT_READY_TIME;
|
||||
}
|
||||
|
||||
function getReadyRemainingSeconds(battleInfo) {
|
||||
const total = getReadyTime(battleInfo);
|
||||
const countdownStartTime = normalizeTimestamp(battleInfo?.countdownStartTime);
|
||||
if (!countdownStartTime) return total;
|
||||
|
||||
const elapsed = Math.max(0, (Date.now() - countdownStartTime) / 1000);
|
||||
console.log('11111111111111111111111111111', total, countdownStartTime, Math.max(0, (Date.now() - countdownStartTime) / 1000))
|
||||
return Math.max(0, total - elapsed);
|
||||
}
|
||||
|
||||
function hasKnownStatus(battleInfo) {
|
||||
return !(
|
||||
battleInfo?.status === undefined ||
|
||||
@@ -168,6 +191,7 @@ function reconnectMatchServer(battleInfo) {
|
||||
serverAddr: battleInfo.serverAddr,
|
||||
matchId: battleInfo.matchId || battleId.value,
|
||||
userId: user.value.id,
|
||||
mode: battleInfo.mode,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -235,10 +259,10 @@ function recoverData(battleInfo, { force = false } = {}) {
|
||||
}
|
||||
|
||||
if (battleInfo.status === 0) {
|
||||
const readyRemain = (Date.now() - (battleInfo.serverTime || Date.now())) / 1000;
|
||||
if (readyRemain > 0 && readyRemain < 15) {
|
||||
setTimeout(() => uni.$emit("update-timer", 15 - readyRemain - 0.2), 200);
|
||||
}
|
||||
readyTime.value = getReadyTime(battleInfo);
|
||||
setTimeout(() => {
|
||||
uni.$emit("update-timer", getReadyRemainingSeconds(battleInfo));
|
||||
}, 200);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -293,7 +317,11 @@ onLoad(async (options) => {
|
||||
return;
|
||||
}
|
||||
const readySnapshot = takeReadySnapshot(battleId.value);
|
||||
if (readySnapshot?.status === 0) recoverData(readySnapshot);
|
||||
if (readySnapshot?.status === 0) {
|
||||
skipNextRestoreOnShow = true;
|
||||
reconnectMatchServer(readySnapshot);
|
||||
recoverData(readySnapshot);
|
||||
}
|
||||
// uni.enableAlertBeforeUnload({
|
||||
// message: "离开比赛可能导致比赛失败,是否继续?",
|
||||
// success: (res) => {
|
||||
@@ -426,7 +454,12 @@ onShow(async () => {
|
||||
<Container :title="title" :bgType="1">
|
||||
<view class="container">
|
||||
<BattleHeader v-if="!start" :players="players" />
|
||||
<TestDistance v-if="start === false" :guide="false" :isBattle="true" />
|
||||
<TestDistance
|
||||
v-if="start === false"
|
||||
:guide="false"
|
||||
:isBattle="true"
|
||||
:count="readyTime"
|
||||
/>
|
||||
<ShootProgress
|
||||
:show="start"
|
||||
:start="start && !halfRest"
|
||||
|
||||
@@ -15,6 +15,7 @@ import audioManager from "@/audioManager";
|
||||
|
||||
import {
|
||||
createPractiseAPI,
|
||||
endPractiseAPI,
|
||||
getPractiseAPI,
|
||||
startPractiseAPI,
|
||||
} from "@/apis";
|
||||
@@ -40,6 +41,7 @@ const showGuide = ref(false);
|
||||
const tips = ref("");
|
||||
const targetType = ref(1);
|
||||
const sharing = ref(false);
|
||||
const exiting = ref(false);
|
||||
const RESULT_TIP_CDN = "https://static.shelingxingqiu.com/shootmini/static";
|
||||
|
||||
onLoad((options) => {
|
||||
@@ -155,6 +157,21 @@ async function onComplete() {
|
||||
}
|
||||
}
|
||||
|
||||
async function exitPractise() {
|
||||
if (exiting.value) return;
|
||||
exiting.value = true;
|
||||
|
||||
try {
|
||||
if (practiseId.value && !practiseResult.value?.details) {
|
||||
await endPractiseAPI(practiseId.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to stop practice", error);
|
||||
} finally {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
|
||||
const getResultTipSrc = (result = {}) => {
|
||||
const validCount = (result.details || []).filter(
|
||||
(arrow) => arrow.x !== -30 && arrow.y !== -30
|
||||
@@ -204,6 +221,7 @@ onBeforeUnmount(() => {
|
||||
:bgType="1"
|
||||
title="个人单组练习"
|
||||
:showBottom="!start && !scores.length"
|
||||
:onBack="exitPractise"
|
||||
>
|
||||
<view>
|
||||
<TestDistance v-if="!start && !practiseResult.id" />
|
||||
|
||||
@@ -14,6 +14,7 @@ import audioManager from "@/audioManager";
|
||||
|
||||
import {
|
||||
createPractiseAPI,
|
||||
endPractiseAPI,
|
||||
getPractiseAPI,
|
||||
startPractiseAPI,
|
||||
} from "@/apis";
|
||||
@@ -39,6 +40,7 @@ const practiseId = ref("");
|
||||
const showGuide = ref(false);
|
||||
const targetType = ref(1);
|
||||
const sharing = ref(false);
|
||||
const exiting = ref(false);
|
||||
const RESULT_TIP_CDN = "https://static.shelingxingqiu.com/shootmini/static";
|
||||
|
||||
onLoad((options) => {
|
||||
@@ -170,6 +172,21 @@ async function onComplete() {
|
||||
}
|
||||
}
|
||||
|
||||
async function exitPractise() {
|
||||
if (exiting.value) return;
|
||||
exiting.value = true;
|
||||
|
||||
try {
|
||||
if (practiseId.value && !practiseResult.value?.details) {
|
||||
await endPractiseAPI(practiseId.value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to stop practice", error);
|
||||
} finally {
|
||||
uni.navigateBack();
|
||||
}
|
||||
}
|
||||
|
||||
const getResultTipSrc = (result = {}) => {
|
||||
const validCount = (result.details || []).filter(
|
||||
(arrow) => arrow.x !== -30 && arrow.y !== -30
|
||||
@@ -218,6 +235,7 @@ onBeforeUnmount(() => {
|
||||
:bgType="1"
|
||||
title="日常耐力挑战"
|
||||
:showBottom="!start && !scores.length"
|
||||
:onBack="exitPractise"
|
||||
>
|
||||
<view>
|
||||
<TestDistance v-if="!start && !practiseResult.id" />
|
||||
|
||||
@@ -32,7 +32,7 @@ const store = useStore();
|
||||
const { user, online } = storeToRefs(store);
|
||||
|
||||
const DEFAULT_SHOOT_TIME = 15;
|
||||
const READY_SECONDS = 15;
|
||||
const DEFAULT_READY_TIME = 15;
|
||||
const READY_TIMER_EMIT_DELAY = 200;
|
||||
const RESTORE_DELAY = 300;
|
||||
const RESTORE_EMPTY_ID_RETRY_DELAY = 50;
|
||||
@@ -81,6 +81,7 @@ const showRoundTip = ref(false);
|
||||
const isFinalShoot = ref(false);
|
||||
const matchStatus = ref(undefined);
|
||||
const shootTimeTotal = ref(DEFAULT_SHOOT_TIME);
|
||||
const readyTime = ref(DEFAULT_READY_TIME);
|
||||
const showOfflineModal = ref(false);
|
||||
const restoreLoading = ref(false);
|
||||
const xRingStreaks = ref({});
|
||||
@@ -144,6 +145,20 @@ function normalizeTimestamp(value) {
|
||||
return numberValue < 1000000000000 ? numberValue * 1000 : numberValue;
|
||||
}
|
||||
|
||||
function getReadyTime(battleInfo) {
|
||||
const value = Number(battleInfo?.readyTime);
|
||||
return Number.isFinite(value) && value > 0 ? value : DEFAULT_READY_TIME;
|
||||
}
|
||||
|
||||
function getReadyRemainingSeconds(battleInfo) {
|
||||
const total = getReadyTime(battleInfo);
|
||||
const countdownStartTime = normalizeTimestamp(battleInfo?.countdownStartTime);
|
||||
if (!countdownStartTime) return total;
|
||||
|
||||
const elapsed = Math.max(0, (Date.now() - countdownStartTime) / 1000);
|
||||
return Math.max(0, total - elapsed);
|
||||
}
|
||||
|
||||
function getReadySnapshotKey(matchId) {
|
||||
return `${MATCH_READY_SNAPSHOT_PREFIX}${String(matchId || "")}`;
|
||||
}
|
||||
@@ -830,6 +845,7 @@ function stopProgressAfterMount() {
|
||||
// 待开局状态:清理比赛态展示,并恢复准备倒计时。
|
||||
function applyReadyState(battleInfo) {
|
||||
hideRestoreLoading();
|
||||
readyTime.value = getReadyTime(battleInfo);
|
||||
start.value = false;
|
||||
showRoundTip.value = false;
|
||||
currentShooterId.value = 0;
|
||||
@@ -841,13 +857,9 @@ function applyReadyState(battleInfo) {
|
||||
clearProgressZeroWaiters();
|
||||
cancelRoundTipDisplay();
|
||||
|
||||
const serverTime = normalizeTimestamp(battleInfo?.serverTime || Date.now());
|
||||
const readyElapsed = (Date.now() - serverTime) / 1000;
|
||||
if (readyElapsed > 0 && readyElapsed < READY_SECONDS) {
|
||||
setTimeout(() => {
|
||||
uni.$emit("update-timer", READY_SECONDS - readyElapsed - 0.2);
|
||||
}, READY_TIMER_EMIT_DELAY);
|
||||
}
|
||||
setTimeout(() => {
|
||||
uni.$emit("update-timer", getReadyRemainingSeconds(battleInfo));
|
||||
}, READY_TIMER_EMIT_DELAY);
|
||||
}
|
||||
|
||||
// 快照恢复入口:只把页面拉到服务端最新状态,不重放已经发生过的语音。
|
||||
@@ -1346,7 +1358,12 @@ onShow(() => {
|
||||
:blueTeam="blueTeam"
|
||||
:winner="0"
|
||||
/>
|
||||
<TestDistance v-if="start === false" :guide="false" :isBattle="true"/>
|
||||
<TestDistance
|
||||
v-if="start === false"
|
||||
:guide="false"
|
||||
:isBattle="true"
|
||||
:count="readyTime"
|
||||
/>
|
||||
<!-- 比赛进行中显示:左右队伍、进度条、靶面和底部比分。 -->
|
||||
<view v-if="start" class="players-row">
|
||||
<TeamAvatars
|
||||
|
||||
Reference in New Issue
Block a user