update:优化性能问题
This commit is contained in:
@@ -99,8 +99,6 @@ const emit = defineEmits(["shot-effect-complete"]);
|
||||
const pMode = ref(true);
|
||||
const latestOne = ref(null);
|
||||
const bluelatestOne = ref(null);
|
||||
const prevScores = ref([]);
|
||||
const prevBlueScores = ref([]);
|
||||
const timer = ref(null);
|
||||
const dirTimer = ref(null);
|
||||
const angle = ref(null);
|
||||
@@ -113,9 +111,23 @@ const targetRect = ref({ left: 0, top: 0, width: 0, height: 0 });
|
||||
const shakeTimer = ref(null);
|
||||
const instance = getCurrentInstance();
|
||||
let shotEffectRequestGeneration = 0;
|
||||
const MAX_VISIBLE_TARGET_SHOTS = 12;
|
||||
const ROUND_TIP_OFFSET_Y = -32;
|
||||
const EXPERIENCE_TIP_OFFSET_Y = -68;
|
||||
|
||||
// 长时训练只保留最近一组命中点在靶面上,完整成绩仍由父页面保存并用于结算。
|
||||
const getVisibleScoreEntries = (scores = []) => {
|
||||
const startIndex = Math.max(scores.length - MAX_VISIBLE_TARGET_SHOTS, 0);
|
||||
return scores.slice(startIndex).map((shot, offset) => ({
|
||||
shot,
|
||||
index: startIndex + offset,
|
||||
}));
|
||||
};
|
||||
const visibleScoreEntries = computed(() => getVisibleScoreEntries(props.scores));
|
||||
const visibleBlueScoreEntries = computed(() =>
|
||||
getVisibleScoreEntries(props.blueScores)
|
||||
);
|
||||
|
||||
const getNumber = (value, fallback = 0) => {
|
||||
const numberValue = Number(value);
|
||||
return Number.isFinite(numberValue) ? numberValue : fallback;
|
||||
@@ -376,11 +388,11 @@ function handleWindowResize() {
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.scores,
|
||||
(newVal) => {
|
||||
if (newVal.length - prevScores.value.length === 1) {
|
||||
showShotTip(newVal[newVal.length - 1]);
|
||||
} else if (newVal.length < prevScores.value.length) {
|
||||
() => props.scores.length,
|
||||
(newLength, oldLength = 0) => {
|
||||
if (newLength - oldLength === 1) {
|
||||
showShotTip(props.scores[newLength - 1]);
|
||||
} else if (newLength < oldLength) {
|
||||
shotEffectRequestGeneration += 1;
|
||||
pendingShotEffect.value = null;
|
||||
clearTipTimer();
|
||||
@@ -388,10 +400,6 @@ watch(
|
||||
hiddenLatestKey.value = "";
|
||||
shotEffect.value = null;
|
||||
}
|
||||
prevScores.value = [...newVal];
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -412,19 +420,15 @@ watch(
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.blueScores,
|
||||
(newVal) => {
|
||||
if (newVal.length - prevBlueScores.value.length === 1) {
|
||||
bluelatestOne.value = newVal[newVal.length - 1];
|
||||
() => props.blueScores.length,
|
||||
(newLength, oldLength = 0) => {
|
||||
if (newLength - oldLength === 1) {
|
||||
bluelatestOne.value = props.blueScores[newLength - 1];
|
||||
if (timer.value) clearTimeout(timer.value);
|
||||
timer.value = setTimeout(() => {
|
||||
bluelatestOne.value = null;
|
||||
}, 1000);
|
||||
}
|
||||
prevBlueScores.value = [...newVal];
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -580,38 +584,45 @@ onBeforeUnmount(() => {
|
||||
>{{ bluelatestOne.ringX ? "X" : bluelatestOne.ring || "未上靶"
|
||||
}}<text v-if="bluelatestOne.ring">环</text></view
|
||||
>
|
||||
<block v-for="(bow, index) in scores" :key="index">
|
||||
<block v-for="entry in visibleScoreEntries" :key="entry.index">
|
||||
<image
|
||||
v-if="pMode && isSvip && bow.ring > 0 && !shouldHideLatestHit(index)"
|
||||
v-if="
|
||||
pMode &&
|
||||
isSvip &&
|
||||
entry.shot.ring > 0 &&
|
||||
!shouldHideLatestHit(entry.index)
|
||||
"
|
||||
class="svip-hit-bg"
|
||||
src="../../../static/vip/svip-xuan.png"
|
||||
:style="getSvipHitBgStyle(bow)"
|
||||
:style="getSvipHitBgStyle(entry.shot)"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
<view
|
||||
v-if="bow.ring > 0 && !shouldHideLatestHit(index)"
|
||||
v-if="entry.shot.ring > 0 && !shouldHideLatestHit(entry.index)"
|
||||
:class="`hit ${pMode ? 'b' : 's'}-point ${
|
||||
index === scores.length - 1 && latestOne ? 'pump-in' : ''
|
||||
entry.index === scores.length - 1 && latestOne ? 'pump-in' : ''
|
||||
}`"
|
||||
:style="{
|
||||
...getHitStyle(bow),
|
||||
...getHitStyle(entry.shot),
|
||||
backgroundColor: mode === 'solo' ? '#00bf04' : '#FF0000',
|
||||
}"
|
||||
><text v-if="pMode">{{ index + 1 }}</text></view
|
||||
><text v-if="pMode">{{ entry.index + 1 }}</text></view
|
||||
>
|
||||
</block>
|
||||
<block v-for="(bow, index) in blueScores" :key="index">
|
||||
<block v-for="entry in visibleBlueScoreEntries" :key="entry.index">
|
||||
<view
|
||||
v-if="bow.ring > 0"
|
||||
v-if="entry.shot.ring > 0"
|
||||
:class="`hit ${pMode ? 'b' : 's'}-point ${
|
||||
index === blueScores.length - 1 && bluelatestOne ? 'pump-in' : ''
|
||||
entry.index === blueScores.length - 1 && bluelatestOne
|
||||
? 'pump-in'
|
||||
: ''
|
||||
}`"
|
||||
:style="{
|
||||
...getHitStyle(bow),
|
||||
...getHitStyle(entry.shot),
|
||||
backgroundColor: '#1840FF',
|
||||
}"
|
||||
>
|
||||
<text v-if="pMode">{{ index + 1 }}</text>
|
||||
<text v-if="pMode">{{ entry.index + 1 }}</text>
|
||||
</view>
|
||||
</block>
|
||||
<BowShotEffect
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
|
||||
import { ref, watch } from "vue";
|
||||
const props = defineProps({
|
||||
rowCount: {
|
||||
type: Number,
|
||||
@@ -27,8 +27,6 @@ const bgImages = [
|
||||
"../static/complete-light1.png",
|
||||
"../static/complete-light2.png",
|
||||
];
|
||||
const bgIndex = ref(0);
|
||||
|
||||
const getDisplayText = (arrow) => {
|
||||
if (!arrow) return "-";
|
||||
if (arrow.ringX) return "X";
|
||||
@@ -47,35 +45,30 @@ watch(
|
||||
items.value = new Array(newValue).fill(9);
|
||||
}
|
||||
);
|
||||
const timer = ref(null);
|
||||
onMounted(() => {
|
||||
timer.value = setInterval(() => {
|
||||
bgIndex.value = bgIndex.value === 0 ? 1 : 0;
|
||||
}, 200);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
if (timer.value) {
|
||||
clearInterval(timer.value);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<view class="container">
|
||||
<image
|
||||
v-if="total > 0 && arrows.length === total && completeEffect"
|
||||
:src="bgImages[bgIndex]"
|
||||
class="complete-light"
|
||||
:style="{
|
||||
width: `calc(${(100 / (rowCount + 2)) * rowCount}vw + ${
|
||||
(100 / (total * 2)) * (rowCount * 2 + (total === 12 ? 8 : 24))
|
||||
}px)`,
|
||||
height: `calc(${(100 / (rowCount + 2)) * (total / rowCount)}vw + ${
|
||||
(100 / (total * 2)) *
|
||||
((total / rowCount) * 2 + (total === 12 ? 7 : 24))
|
||||
}px)`,
|
||||
top: `${total === 12 ? -2 : -3}vw`,
|
||||
}"
|
||||
/>
|
||||
<template v-if="total > 0 && arrows.length === total && completeEffect">
|
||||
<image
|
||||
v-for="(image, index) in bgImages"
|
||||
:key="image"
|
||||
:src="image"
|
||||
:class="[
|
||||
'complete-light',
|
||||
index === 0 ? 'complete-light--first' : 'complete-light--second',
|
||||
]"
|
||||
:style="{
|
||||
width: `calc(${(100 / (rowCount + 2)) * rowCount}vw + ${
|
||||
(100 / (total * 2)) * (rowCount * 2 + (total === 12 ? 8 : 24))
|
||||
}px)`,
|
||||
height: `calc(${(100 / (rowCount + 2)) * (total / rowCount)}vw + ${
|
||||
(100 / (total * 2)) *
|
||||
((total / rowCount) * 2 + (total === 12 ? 7 : 24))
|
||||
}px)`,
|
||||
top: `${total === 12 ? -2 : -3}vw`,
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
<view
|
||||
v-for="(_, index) in items"
|
||||
:key="index"
|
||||
@@ -154,4 +147,30 @@ onBeforeUnmount(() => {
|
||||
.complete-light {
|
||||
position: absolute;
|
||||
}
|
||||
.complete-light--first {
|
||||
animation: complete-light-first 400ms steps(1, end) infinite;
|
||||
}
|
||||
.complete-light--second {
|
||||
animation: complete-light-second 400ms steps(1, end) infinite;
|
||||
}
|
||||
@keyframes complete-light-first {
|
||||
0%,
|
||||
49.9% {
|
||||
opacity: 1;
|
||||
}
|
||||
50%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes complete-light-second {
|
||||
0%,
|
||||
49.9% {
|
||||
opacity: 0;
|
||||
}
|
||||
50%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -99,6 +99,8 @@ const SHOT_EFFECT_WAIT_TIMEOUT_MS = 1200;
|
||||
const AUDIO_TIMEOUT_BASE = 3500;
|
||||
const AUDIO_TIMEOUT_PER_KEY = 2600;
|
||||
const AUDIO_TIMEOUT_MAX = 12000;
|
||||
const RUNTIME_DIAGNOSTIC_INTERVAL_MS = 60000;
|
||||
let lastRuntimeDiagnosticAt = 0;
|
||||
|
||||
const env = computed(() => {
|
||||
try {
|
||||
@@ -108,6 +110,19 @@ const env = computed(() => {
|
||||
}
|
||||
});
|
||||
|
||||
const maybeLogRuntimeStats = () => {
|
||||
if (env.value !== "develop" && env.value !== "trial") return;
|
||||
const now = Date.now();
|
||||
if (now - lastRuntimeDiagnosticAt < RUNTIME_DIAGNOSTIC_INTERVAL_MS) return;
|
||||
lastRuntimeDiagnosticAt = now;
|
||||
console.log("[training-runtime]", {
|
||||
scoreCount: scores.value.length,
|
||||
renderedTargetShotCount: Math.min(scores.value.length, 12),
|
||||
renderedScoreCardCount: Math.min(scores.value.length, 60) + 1,
|
||||
...audioManager.getRuntimeStats(),
|
||||
});
|
||||
};
|
||||
|
||||
const isDistanceStage = computed(() => pageStage.value === pageStages.DISTANCE);
|
||||
const isShootingStage = computed(() => pageStage.value === pageStages.SHOOTING);
|
||||
const hasPractiseResult = computed(() => !!practiseResult.value?.details);
|
||||
@@ -1111,6 +1126,7 @@ async function onReceiveMessage(msg) {
|
||||
scores.value = msg.details;
|
||||
hasNewShot = msg.details.length === previousScoreLength + 1;
|
||||
latestShot = hasNewShot ? msg.details[msg.details.length - 1] : null;
|
||||
maybeLogRuntimeStats();
|
||||
}
|
||||
|
||||
if (trainingType.value === "precision") {
|
||||
|
||||
Reference in New Issue
Block a user