update:新增个人训练重连机制,修复海报
This commit is contained in:
+254
-8
@@ -1,5 +1,6 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from "vue";
|
||||
import { ref, nextTick, onMounted, onBeforeUnmount } from "vue";
|
||||
import { onHide, onLoad, onShow } from "@dcloudio/uni-app";
|
||||
import Container from "@/components/Container.vue";
|
||||
import ShootProgress from "@/components/ShootProgress.vue";
|
||||
import BowTarget from "@/components/BowTarget.vue";
|
||||
@@ -15,17 +16,23 @@ import audioManager from "@/audioManager";
|
||||
import {
|
||||
createPractiseAPI,
|
||||
endPractiseAPI,
|
||||
getCurrentPractiseAPI,
|
||||
getPractiseAPI,
|
||||
startPractiseAPI,
|
||||
} from "@/apis";
|
||||
import { connectMatchWebSocket, closeMatchWebSocket } from "@/matchWebsocket";
|
||||
import {
|
||||
connectMatchWebSocket,
|
||||
closeMatchWebSocket,
|
||||
setMatchAppHideResumable,
|
||||
MATCH_WS_PRACTICE_SYNC_EVENT,
|
||||
MATCH_WS_STATE_EVENT,
|
||||
} from "@/matchWebsocket";
|
||||
import { sharePractiseData } from "@/canvas";
|
||||
import { wxShare, debounce } from "@/util";
|
||||
import { MESSAGETYPESV2 } from "@/constants";
|
||||
|
||||
import useStore from "@/store";
|
||||
import { storeToRefs } from "pinia";
|
||||
import {onLoad} from "@dcloudio/uni-app";
|
||||
const store = useStore();
|
||||
const { user, device } = storeToRefs(store);
|
||||
|
||||
@@ -35,10 +42,20 @@ const isSvip = ref(false);
|
||||
const total = 36;
|
||||
const practiseResult = ref({});
|
||||
const practiseId = ref("");
|
||||
const serverAddr = ref("");
|
||||
const showGuide = ref(false);
|
||||
const targetType = ref(1);
|
||||
const sharing = 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 restoringSnapshot = ref(false);
|
||||
let practiceSyncTimer = null;
|
||||
let waitingPracticeSync = false;
|
||||
const PRACTICE_SYNC_TIMEOUT_MS = 5000;
|
||||
const RESULT_TIP_CDN = "https://static.shelingxingqiu.com/shootmini/static";
|
||||
|
||||
onLoad((options) => {
|
||||
@@ -47,6 +64,63 @@ onLoad((options) => {
|
||||
}
|
||||
});
|
||||
|
||||
const setPracticeAppHideResumable = (enabled) => {
|
||||
appHideResumable.value = enabled === true;
|
||||
setMatchAppHideResumable(appHideResumable.value);
|
||||
};
|
||||
|
||||
const clearPracticeSyncTimer = () => {
|
||||
if (!practiceSyncTimer) return;
|
||||
clearTimeout(practiceSyncTimer);
|
||||
practiceSyncTimer = null;
|
||||
};
|
||||
|
||||
const cancelPracticeSyncWait = () => {
|
||||
waitingPracticeSync = false;
|
||||
clearPracticeSyncTimer();
|
||||
};
|
||||
|
||||
const startPracticeSyncTimer = () => {
|
||||
clearPracticeSyncTimer();
|
||||
waitingPracticeSync = true;
|
||||
practiceSyncTimer = setTimeout(() => {
|
||||
practiceSyncTimer = null;
|
||||
if (!waitingPracticeSync) return;
|
||||
|
||||
waitingPracticeSync = false;
|
||||
foregroundResumeSyncPending.value = false;
|
||||
hiddenWhileActive.value = false;
|
||||
closeMatchWebSocket({
|
||||
reason: "legacy-practice-resume-timeout",
|
||||
sendLeave: false,
|
||||
});
|
||||
uni.showToast({
|
||||
title: "训练重连失败,请重试",
|
||||
icon: "none",
|
||||
});
|
||||
}, PRACTICE_SYNC_TIMEOUT_MS);
|
||||
};
|
||||
|
||||
const connectPracticeServer = () => {
|
||||
const latestServerAddr = String(serverAddr.value || "").trim();
|
||||
if (!practiseId.value || !latestServerAddr) return false;
|
||||
|
||||
cancelPracticeSyncWait();
|
||||
closeMatchWebSocket({
|
||||
reason: "legacy-practice-switch",
|
||||
sendLeave: false,
|
||||
});
|
||||
connectMatchWebSocket({
|
||||
serverAddr: latestServerAddr,
|
||||
matchId: practiseId.value,
|
||||
userId: user.value.id,
|
||||
requestPracticeInfoOnOpen: true,
|
||||
appHideResumable: true,
|
||||
});
|
||||
setPracticeAppHideResumable(true);
|
||||
return true;
|
||||
};
|
||||
|
||||
const createPractise = async () => {
|
||||
closeMatchWebSocket({ reason: "practice-recreate" });
|
||||
const result = await createPractiseAPI(
|
||||
@@ -63,11 +137,8 @@ const createPractise = async () => {
|
||||
});
|
||||
return null;
|
||||
}
|
||||
connectMatchWebSocket({
|
||||
serverAddr: result.serverAddr,
|
||||
matchId: result.id,
|
||||
userId: user.value.id,
|
||||
});
|
||||
serverAddr.value = result.serverAddr;
|
||||
connectPracticeServer();
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -93,6 +164,10 @@ const onReady = async () => {
|
||||
};
|
||||
|
||||
const onOver = async (message) => {
|
||||
setPracticeAppHideResumable(false);
|
||||
hiddenWhileActive.value = false;
|
||||
foregroundResumeSyncPending.value = false;
|
||||
cancelPracticeSyncWait();
|
||||
practiseResult.value = Array.isArray(message?.details)
|
||||
? message
|
||||
: await getPractiseAPI(practiseId.value);
|
||||
@@ -104,6 +179,10 @@ async function onReceiveMessage(msg) {
|
||||
isSvip.value = msg.sVip === true;
|
||||
scores.value = Array.isArray(msg.details) ? msg.details : scores.value;
|
||||
} else if (msg.type === MESSAGETYPESV2.BattleEnd) {
|
||||
setPracticeAppHideResumable(false);
|
||||
hiddenWhileActive.value = false;
|
||||
foregroundResumeSyncPending.value = false;
|
||||
cancelPracticeSyncWait();
|
||||
setTimeout(() => onOver(msg), 1500);
|
||||
}
|
||||
// messages.forEach((msg) => {
|
||||
@@ -125,6 +204,7 @@ async function onReceiveMessage(msg) {
|
||||
}
|
||||
|
||||
async function onComplete() {
|
||||
setPracticeAppHideResumable(false);
|
||||
const validArrows = (practiseResult.value.details || []).filter(
|
||||
(a) => a.x !== -30 && a.y !== -30
|
||||
);
|
||||
@@ -132,6 +212,7 @@ async function onComplete() {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
practiseId.value = "";
|
||||
serverAddr.value = "";
|
||||
practiseResult.value = {};
|
||||
start.value = false;
|
||||
scores.value = [];
|
||||
@@ -143,6 +224,10 @@ async function onComplete() {
|
||||
async function exitPractise() {
|
||||
if (exiting.value) return;
|
||||
exiting.value = true;
|
||||
setPracticeAppHideResumable(false);
|
||||
hiddenWhileActive.value = false;
|
||||
foregroundResumeSyncPending.value = false;
|
||||
cancelPracticeSyncWait();
|
||||
|
||||
try {
|
||||
if (practiseId.value && !practiseResult.value?.details) {
|
||||
@@ -155,6 +240,141 @@ async function exitPractise() {
|
||||
}
|
||||
}
|
||||
|
||||
const stopMissingCurrentPracticeAndExit = async () => {
|
||||
if (exiting.value) return;
|
||||
|
||||
const localPractiseId = practiseId.value;
|
||||
exiting.value = true;
|
||||
setPracticeAppHideResumable(false);
|
||||
hiddenWhileActive.value = false;
|
||||
foregroundResumeSyncPending.value = false;
|
||||
cancelPracticeSyncWait();
|
||||
|
||||
try {
|
||||
if (localPractiseId) {
|
||||
await endPractiseAPI(localPractiseId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to stop missing current practice", error);
|
||||
} finally {
|
||||
closeMatchWebSocket({ reason: "legacy-practice-current-missing" });
|
||||
practiseId.value = "";
|
||||
serverAddr.value = "";
|
||||
practiseResult.value = {};
|
||||
start.value = false;
|
||||
scores.value = [];
|
||||
isSvip.value = false;
|
||||
uni.showToast({
|
||||
title: "训练已结束,请重新进入",
|
||||
icon: "none",
|
||||
});
|
||||
uni.navigateBack();
|
||||
}
|
||||
};
|
||||
|
||||
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("Failed to get current practice", error);
|
||||
await stopMissingCurrentPracticeAndExit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pageVisible.value || !hiddenWhileActive.value || exiting.value) return;
|
||||
|
||||
const latestPractiseId = currentPractice?.id;
|
||||
const latestServerAddr = String(currentPractice?.serverAddr || "").trim();
|
||||
if (
|
||||
currentPractice === null ||
|
||||
!latestPractiseId ||
|
||||
!latestServerAddr
|
||||
) {
|
||||
await stopMissingCurrentPracticeAndExit();
|
||||
return;
|
||||
}
|
||||
|
||||
practiseId.value = latestPractiseId;
|
||||
serverAddr.value = latestServerAddr;
|
||||
foregroundResumeSyncPending.value = true;
|
||||
if (!connectPracticeServer()) {
|
||||
foregroundResumeSyncPending.value = false;
|
||||
await stopMissingCurrentPracticeAndExit();
|
||||
}
|
||||
} finally {
|
||||
resumeInFlight.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const onMatchSocketState = (event = {}) => {
|
||||
if (event.state !== "open") return;
|
||||
if (
|
||||
event.matchId &&
|
||||
String(event.matchId) !== String(practiseId.value)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
startPracticeSyncTimer();
|
||||
};
|
||||
|
||||
const onPracticeInfoSync = async (payload = {}) => {
|
||||
const responsePractiseId = String(
|
||||
payload.matchId || payload.practiceInfo?.id || ""
|
||||
);
|
||||
if (
|
||||
!responsePractiseId ||
|
||||
responsePractiseId !== String(practiseId.value)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const snapshot = payload.practiceInfo;
|
||||
if (!snapshot || typeof snapshot !== "object") return;
|
||||
|
||||
cancelPracticeSyncWait();
|
||||
foregroundResumeSyncPending.value = false;
|
||||
hiddenWhileActive.value = false;
|
||||
|
||||
restoringSnapshot.value = true;
|
||||
await nextTick();
|
||||
try {
|
||||
scores.value = Array.isArray(snapshot.details) ? snapshot.details : [];
|
||||
isSvip.value = snapshot.sVip === true;
|
||||
|
||||
const status = Number(snapshot.status);
|
||||
if (status === 1) {
|
||||
start.value = false;
|
||||
} else if (status === 2) {
|
||||
start.value = true;
|
||||
}
|
||||
} finally {
|
||||
restoringSnapshot.value = false;
|
||||
await nextTick();
|
||||
}
|
||||
|
||||
const timeLimit = Number(snapshot.timeLimit);
|
||||
const duration = Number(snapshot.duration);
|
||||
if (Number(snapshot.status) === 2 && Number.isFinite(timeLimit) && timeLimit > 0) {
|
||||
uni.$emit(
|
||||
"update-remain",
|
||||
Math.max(0, timeLimit - (Number.isFinite(duration) ? duration : 0))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getResultTipSrc = (result = {}) => {
|
||||
const validCount = (result.details || []).filter(
|
||||
(arrow) => arrow.x !== -30 && arrow.y !== -30
|
||||
@@ -178,21 +398,46 @@ const onClickShare = debounce(async () => {
|
||||
}
|
||||
});
|
||||
|
||||
onHide(() => {
|
||||
pageVisible.value = false;
|
||||
if (
|
||||
!appHideResumable.value ||
|
||||
exiting.value ||
|
||||
!practiseId.value ||
|
||||
practiseResult.value?.details
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
hiddenWhileActive.value = true;
|
||||
});
|
||||
|
||||
onShow(async () => {
|
||||
pageVisible.value = true;
|
||||
await resumeCurrentPractice();
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
void audioManager.warmAll();
|
||||
uni.setKeepScreenOn({
|
||||
keepScreenOn: true,
|
||||
});
|
||||
uni.$on("socket-inbox", onReceiveMessage);
|
||||
uni.$on(MATCH_WS_PRACTICE_SYNC_EVENT, onPracticeInfoSync);
|
||||
uni.$on(MATCH_WS_STATE_EVENT, onMatchSocketState);
|
||||
uni.$on("share-image", onClickShare);
|
||||
await createPractise();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
setPracticeAppHideResumable(false);
|
||||
cancelPracticeSyncWait();
|
||||
uni.setKeepScreenOn({
|
||||
keepScreenOn: false,
|
||||
});
|
||||
uni.$off("socket-inbox", onReceiveMessage);
|
||||
uni.$off(MATCH_WS_PRACTICE_SYNC_EVENT, onPracticeInfoSync);
|
||||
uni.$off(MATCH_WS_STATE_EVENT, onMatchSocketState);
|
||||
uni.$off("share-image", onClickShare);
|
||||
audioManager.stopAll();
|
||||
closeMatchWebSocket({ reason: "practice-leave" });
|
||||
@@ -224,6 +469,7 @@ onBeforeUnmount(() => {
|
||||
<BowPower />
|
||||
</view>
|
||||
<BowTarget
|
||||
v-if="!restoringSnapshot"
|
||||
:currentRound="scores.length"
|
||||
:totalRound="start ? total : 0"
|
||||
:scores="scores"
|
||||
|
||||
Reference in New Issue
Block a user