update:新增个人训练重连机制,修复海报
This commit is contained in:
@@ -13,7 +13,7 @@ import PointSwitcher from "@/components/PointSwitcher.vue";
|
||||
import TargetCanvas from "@/components/TargetCanvas.vue";
|
||||
|
||||
import { MESSAGETYPES, MESSAGETYPESV2 } from "@/constants";
|
||||
import { simulShootAPI } from "@/apis";
|
||||
import { simulShootAPI, laserAimAPI, laserCloseAPI } from "@/apis";
|
||||
import useStore from "@/store";
|
||||
import { storeToRefs } from "pinia";
|
||||
const store = useStore();
|
||||
@@ -372,6 +372,14 @@ const simulShoot2 = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const openAim = async () => {
|
||||
await laserAimAPI();
|
||||
};
|
||||
|
||||
const closeAim = async () => {
|
||||
await laserCloseAPI();
|
||||
};
|
||||
|
||||
const env = computed(() => {
|
||||
const accountInfo = uni.getAccountInfoSync();
|
||||
return accountInfo.miniProgram.envVersion;
|
||||
@@ -557,6 +565,8 @@ onBeforeUnmount(() => {
|
||||
<view class="simul" v-if="env !== 'release'">
|
||||
<button @click="simulShoot">模拟</button>
|
||||
<button @click="simulShoot2">射箭</button>
|
||||
<button @click="openAim">开瞄</button>
|
||||
<button @click="closeAim">关瞄</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -14,6 +14,7 @@ import audioManager from "@/audioManager";
|
||||
|
||||
import {
|
||||
createPractiseV2API,
|
||||
getCurrentPractiseAPI,
|
||||
startPractiseAPI,
|
||||
endPractiseAPI,
|
||||
getPractiseAPI,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
import {
|
||||
connectMatchWebSocket,
|
||||
closeMatchWebSocket,
|
||||
setMatchAppHideResumable,
|
||||
MATCH_WS_PRACTICE_SYNC_EVENT,
|
||||
MATCH_WS_STATE_EVENT,
|
||||
} from "@/matchWebsocket";
|
||||
@@ -76,6 +78,10 @@ const stopCompleted = ref(false);
|
||||
const stopInFlight = ref(false);
|
||||
const exiting = ref(false);
|
||||
const hiddenWhileActive = ref(false);
|
||||
const resumeInFlight = ref(false);
|
||||
const foregroundResumeSyncPending = ref(false);
|
||||
const pageVisible = ref(true);
|
||||
const appHideResumable = ref(false);
|
||||
const connectionClosed = ref(true);
|
||||
let stopPracticeTask = null;
|
||||
let practiceSyncTimer = null;
|
||||
@@ -108,6 +114,12 @@ const isVip = computed(
|
||||
() => practiceInfo.value.vip === true && !isSvip.value
|
||||
);
|
||||
|
||||
const setPracticeAppHideResumable = (enabled) => {
|
||||
const nextValue = enabled === true;
|
||||
appHideResumable.value = nextValue;
|
||||
setMatchAppHideResumable(nextValue);
|
||||
};
|
||||
|
||||
const trainingType = computed(
|
||||
() => practiceInfo.value.trainingType || trainingParams.value.type || ""
|
||||
);
|
||||
@@ -561,6 +573,19 @@ const startPracticeSyncTimer = () => {
|
||||
practiceSyncTimer = null;
|
||||
if (!waitingPracticeSync) return;
|
||||
waitingPracticeSync = false;
|
||||
|
||||
if (foregroundResumeSyncPending.value) {
|
||||
foregroundResumeSyncPending.value = false;
|
||||
closePracticeConnection("training-practice-resume-timeout", {
|
||||
sendLeave: false,
|
||||
});
|
||||
uni.showToast({
|
||||
title: "训练重连失败,请重试",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageStage.value !== pageStages.LOADING) return;
|
||||
|
||||
uni.showToast({
|
||||
@@ -597,7 +622,9 @@ const onPracticeInfoSync = (payload = {}) => {
|
||||
|
||||
const shouldShowDistance =
|
||||
waitingPracticeSync && pageStage.value === pageStages.LOADING;
|
||||
const resumedFromForeground = foregroundResumeSyncPending.value;
|
||||
cancelPracticeSyncWait();
|
||||
foregroundResumeSyncPending.value = false;
|
||||
invalidateShotPresentations();
|
||||
|
||||
// 14 是完整快照,先清空旧值,避免 proto3 省略的 0 沿用上一份状态。
|
||||
@@ -607,6 +634,10 @@ const onPracticeInfoSync = (payload = {}) => {
|
||||
applyVisiblePrecisionTarget(practiceInfo.value);
|
||||
scores.value = Array.isArray(snapshot.details) ? snapshot.details : [];
|
||||
|
||||
if (resumedFromForeground) {
|
||||
hiddenWhileActive.value = false;
|
||||
}
|
||||
|
||||
if (shouldShowDistance) {
|
||||
start.value = false;
|
||||
pageStage.value = pageStages.DISTANCE;
|
||||
@@ -650,18 +681,21 @@ const clearPracticeRuntimeContext = () => {
|
||||
uni.setStorageSync(trainingDifficultyStorageKey, selectionContext);
|
||||
};
|
||||
|
||||
const closePracticeConnection = (reason) => {
|
||||
const closePracticeConnection = (reason, { sendLeave = true } = {}) => {
|
||||
cancelPracticeSyncWait();
|
||||
if (connectionClosed.value) return;
|
||||
connectionClosed.value = true;
|
||||
closeMatchWebSocket({ reason });
|
||||
closeMatchWebSocket({ reason, sendLeave });
|
||||
};
|
||||
|
||||
const connectPracticeServer = () => {
|
||||
const connectPracticeServer = ({
|
||||
resumableOnAppHide = appHideResumable.value,
|
||||
} = {}) => {
|
||||
if (!practiseId.value || !String(serverAddr.value || "").trim()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
appHideResumable.value = resumableOnAppHide === true;
|
||||
cancelPracticeSyncWait();
|
||||
closeMatchWebSocket({ reason: "training-practice-switch" });
|
||||
preparePracticeSyncWait();
|
||||
@@ -670,6 +704,7 @@ const connectPracticeServer = () => {
|
||||
matchId: practiseId.value,
|
||||
userId: user.value.id,
|
||||
requestPracticeInfoOnOpen: true,
|
||||
appHideResumable: appHideResumable.value,
|
||||
});
|
||||
connectionClosed.value = false;
|
||||
return true;
|
||||
@@ -703,6 +738,97 @@ const stopCurrentPractice = () => {
|
||||
return stopPracticeTask;
|
||||
};
|
||||
|
||||
// 当前训练不可恢复时,始终使用页面本地的练习 ID 停止。
|
||||
const stopEndedCurrentPracticeAndExit = async () => {
|
||||
hiddenWhileActive.value = false;
|
||||
foregroundResumeSyncPending.value = false;
|
||||
setPracticeAppHideResumable(false);
|
||||
exiting.value = true;
|
||||
|
||||
try {
|
||||
await stopCurrentPractice();
|
||||
} finally {
|
||||
closePracticeConnection("training-practice-current-missing");
|
||||
clearPracticeRuntimeContext();
|
||||
uni.showToast({
|
||||
title: "训练已结束,请重新进入",
|
||||
icon: "none",
|
||||
});
|
||||
uni.navigateBack();
|
||||
}
|
||||
};
|
||||
|
||||
// 回到前台后先获取最新比赛服地址,再通过 type 5/type 14 恢复完整快照。
|
||||
const resumeCurrentPractice = async () => {
|
||||
if (
|
||||
resumeInFlight.value ||
|
||||
!hiddenWhileActive.value ||
|
||||
!appHideResumable.value ||
|
||||
exiting.value
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
resumeInFlight.value = true;
|
||||
try {
|
||||
let currentPractice;
|
||||
try {
|
||||
currentPractice = await getCurrentPractiseAPI();
|
||||
} catch (error) {
|
||||
if (!pageVisible.value || exiting.value) return;
|
||||
console.error("get current practice failed", error);
|
||||
await stopEndedCurrentPracticeAndExit();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(1111111111111111111111, currentPractice)
|
||||
if (
|
||||
!pageVisible.value ||
|
||||
!hiddenWhileActive.value ||
|
||||
exiting.value
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const latestPracticeId = currentPractice?.id;
|
||||
const latestServerAddr = String(
|
||||
currentPractice?.serverAddr || ""
|
||||
).trim();
|
||||
if (
|
||||
currentPractice === null ||
|
||||
!latestPracticeId ||
|
||||
!latestServerAddr
|
||||
) {
|
||||
await stopEndedCurrentPracticeAndExit();
|
||||
return;
|
||||
}
|
||||
|
||||
practiseId.value = latestPracticeId;
|
||||
serverAddr.value = latestServerAddr;
|
||||
updateTrainingContext({
|
||||
id: latestPracticeId,
|
||||
serverAddr: latestServerAddr,
|
||||
});
|
||||
|
||||
foregroundResumeSyncPending.value = true;
|
||||
setPracticeAppHideResumable(true);
|
||||
if (!connectPracticeServer({ resumableOnAppHide: true })) {
|
||||
foregroundResumeSyncPending.value = false;
|
||||
await stopEndedCurrentPracticeAndExit();
|
||||
}
|
||||
} catch (error) {
|
||||
foregroundResumeSyncPending.value = false;
|
||||
if (!pageVisible.value || exiting.value) return;
|
||||
console.error("training practice resume failed", error);
|
||||
uni.showToast({
|
||||
title: "训练重连失败,请重试",
|
||||
icon: "none",
|
||||
});
|
||||
} finally {
|
||||
resumeInFlight.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const createPractice = async () => {
|
||||
const trainingType = trainingParams.value.type;
|
||||
const difficultyLevel = trainingParams.value.difficulty;
|
||||
@@ -740,7 +866,7 @@ const createPractice = async () => {
|
||||
stopInFlight.value = false;
|
||||
stopPracticeTask = null;
|
||||
updateTrainingContext(result);
|
||||
connectPracticeServer();
|
||||
connectPracticeServer({ resumableOnAppHide: true });
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -839,10 +965,12 @@ const onReady = async () => {
|
||||
shotEffectToken.value = 0;
|
||||
start.value = true;
|
||||
pageStage.value = pageStages.SHOOTING;
|
||||
setPracticeAppHideResumable(true);
|
||||
audioManager.play("练习开始");
|
||||
} catch (error) {
|
||||
start.value = false;
|
||||
pageStage.value = pageStages.DISTANCE;
|
||||
setPracticeAppHideResumable(true);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -851,6 +979,8 @@ const onTimeLimitReached = () => {
|
||||
if (!hasTimeLimit.value || !isShootingStage.value) return;
|
||||
// 本地倒计时只负责停止射击展示,最终结算以 PRACTICE_END 为准。
|
||||
start.value = false;
|
||||
hiddenWhileActive.value = false;
|
||||
setPracticeAppHideResumable(false);
|
||||
};
|
||||
|
||||
const enterPracticeResult = (result = {}) => {
|
||||
@@ -859,6 +989,7 @@ const enterPracticeResult = (result = {}) => {
|
||||
|
||||
// 正常结算不调用 stop,只清理上下文并断开比赛服连接。
|
||||
practiceEnded.value = true;
|
||||
setPracticeAppHideResumable(false);
|
||||
invalidateShotPresentations();
|
||||
clearPracticeRuntimeContext();
|
||||
closePracticeConnection("training-practice-result");
|
||||
@@ -872,6 +1003,7 @@ const onOver = async () => {
|
||||
clearHighlightTestTimer();
|
||||
pageStage.value = pageStages.LOADING;
|
||||
start.value = false;
|
||||
setPracticeAppHideResumable(false);
|
||||
|
||||
try {
|
||||
const apiResult = (await getPractiseAPI(practiseId.value)) || {};
|
||||
@@ -885,6 +1017,7 @@ const onOver = async () => {
|
||||
}
|
||||
start.value = true;
|
||||
pageStage.value = pageStages.SHOOTING;
|
||||
setPracticeAppHideResumable(true);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -945,6 +1078,7 @@ async function onReceiveMessage(msg) {
|
||||
};
|
||||
}
|
||||
practiceEnded.value = true;
|
||||
setPracticeAppHideResumable(false);
|
||||
invalidateShotPresentations();
|
||||
clearPracticeRuntimeContext();
|
||||
// setTimeout(onOver, 1500);
|
||||
@@ -955,6 +1089,7 @@ function onComplete() {
|
||||
pageStage.value = pageStages.LOADING;
|
||||
start.value = false;
|
||||
practiceEnded.value = true;
|
||||
setPracticeAppHideResumable(false);
|
||||
invalidateShotPresentations();
|
||||
clearPracticeRuntimeContext();
|
||||
closePracticeConnection("training-practice-complete");
|
||||
@@ -964,6 +1099,7 @@ function onComplete() {
|
||||
|
||||
async function onRetry() {
|
||||
pageStage.value = pageStages.LOADING;
|
||||
setPracticeAppHideResumable(false);
|
||||
clearHighlightTestTimer();
|
||||
useHighlightTest.value = false;
|
||||
practiseId.value = "";
|
||||
@@ -1006,6 +1142,7 @@ const updateSound = () => {
|
||||
const exitPractice = async () => {
|
||||
if (exiting.value) return;
|
||||
exiting.value = true;
|
||||
setPracticeAppHideResumable(false);
|
||||
invalidateShotPresentations();
|
||||
|
||||
try {
|
||||
@@ -1018,36 +1155,28 @@ const exitPractice = async () => {
|
||||
};
|
||||
|
||||
onHide(() => {
|
||||
invalidateShotPresentations();
|
||||
// 小程序被切到后台时尽早通知后端,作为杀进程前的尽力兜底。
|
||||
pageVisible.value = false;
|
||||
if (
|
||||
!exiting.value &&
|
||||
practiseId.value &&
|
||||
!practiceEnded.value &&
|
||||
!stopCompleted.value
|
||||
!appHideResumable.value ||
|
||||
exiting.value ||
|
||||
!practiseId.value ||
|
||||
practiceEnded.value ||
|
||||
stopCompleted.value
|
||||
) {
|
||||
hiddenWhileActive.value = true;
|
||||
clearPracticeRuntimeContext();
|
||||
void stopCurrentPractice();
|
||||
return;
|
||||
}
|
||||
closePracticeConnection("training-practice-hide");
|
||||
|
||||
hiddenWhileActive.value = true;
|
||||
invalidateShotPresentations();
|
||||
});
|
||||
|
||||
onShow(async () => {
|
||||
if (!hiddenWhileActive.value || exiting.value) return;
|
||||
|
||||
hiddenWhileActive.value = false;
|
||||
await stopCurrentPractice();
|
||||
clearPracticeRuntimeContext();
|
||||
exiting.value = true;
|
||||
uni.showToast({
|
||||
title: "训练已结束,请重新进入",
|
||||
icon: "none",
|
||||
});
|
||||
uni.navigateBack();
|
||||
pageVisible.value = true;
|
||||
await resumeCurrentPractice();
|
||||
});
|
||||
|
||||
onUnload(() => {
|
||||
setPracticeAppHideResumable(false);
|
||||
invalidateShotPresentations();
|
||||
clearPracticeRuntimeContext();
|
||||
void stopCurrentPractice();
|
||||
@@ -1064,7 +1193,7 @@ onMounted(() => {
|
||||
uni.$on(MATCH_WS_STATE_EVENT, onMatchSocketState);
|
||||
uni.$on("share-image", onClickShare);
|
||||
uni.$on("audioEnded", onAudioEnded);
|
||||
if (!connectPracticeServer()) {
|
||||
if (!connectPracticeServer({ resumableOnAppHide: true })) {
|
||||
uni.showToast({
|
||||
title: "练习连接信息异常,请重试",
|
||||
icon: "none",
|
||||
@@ -1076,6 +1205,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
setPracticeAppHideResumable(false);
|
||||
invalidateShotPresentations();
|
||||
clearPracticeRuntimeContext();
|
||||
void stopCurrentPractice();
|
||||
|
||||
Reference in New Issue
Block a user