Files
shoot-miniprograms/src/pages/practise-two.vue
2026-05-19 18:30:44 +08:00

211 lines
6.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from "vue";
import Container from "@/components/Container.vue";
import ShootProgress from "@/components/ShootProgress.vue";
import BowTarget from "@/components/BowTarget.vue";
import ScorePanel from "@/components/ScorePanel.vue";
import ScoreResult from "@/components/ScoreResult.vue";
import SButton from "@/components/SButton.vue";
import Avatar from "@/components/Avatar.vue";
import BowPower from "@/components/BowPower.vue";
import TestDistance from "@/components/TestDistance.vue";
import BubbleTip from "@/components/BubbleTip.vue";
import audioManager from "@/audioManager";
import {
createPractiseAPI,
startPractiseAPI,
endPractiseAPI,
getPractiseAPI,
} from "@/apis";
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 } = storeToRefs(store);
const start = ref(false);
const scores = ref([]);
const total = 36;
/** 当前练习中连续 X 环计数,用于触发 tententen 音效 */
const xRingStreak = ref(0);
const practiseResult = ref({});
const practiseId = ref("");
const showGuide = ref(false);
const targetType = ref(1);
onLoad((options) => {
if (options.target) {
targetType.value = Number(options.target);
}
});
const onReady = async () => {
await startPractiseAPI();
scores.value = [];
xRingStreak.value = 0; // 新一局开始,重置 X 环连续计数
start.value = true;
audioManager.play("练习开始");
};
const onOver = async () => {
practiseResult.value = await getPractiseAPI(practiseId.value);
start.value = false;
};
/**
* 检测连续 X 环是否达到 3 箭,达到则播放 tententen 音效
* @param {boolean} isXRing - 本次射击是否为 X 环
*/
function checkAndPlayTententen(isXRing) {
if (isXRing) {
xRingStreak.value += 1;
// 连续 3 箭均为 X 环,在环数播报入队后追加 tententen避免播放顺序颠倒
if (xRingStreak.value >= 3) {
xRingStreak.value = 0;
nextTick(() => audioManager.play("tententen", false));
}
} else {
// 非 X 环则重置连续计数
xRingStreak.value = 0;
}
}
async function onReceiveMessage(msg) {
if (msg.type === MESSAGETYPESV2.ShootResult) {
const prevLen = scores.value.length;
scores.value = msg.details;
// 有新箭时取最后一箭判断是否 X 环并检测连续计数
if (scores.value.length > prevLen) {
const latestArrow = scores.value[scores.value.length - 1];
checkAndPlayTententen(!!(latestArrow?.ringX && latestArrow?.ring));
}
} else if (msg.type === MESSAGETYPESV2.BattleEnd) {
setTimeout(onOver, 1500);
}
// messages.forEach((msg) => {
// if (msg.constructor === MESSAGETYPES.ShootSyncMeArrowID) {
// if (scores.value.length < total) {
// scores.value.push(msg.target);
// if (practiseId && scores.value.length === total / 2) {
// showGuide.value = true;
// setTimeout(() => {
// showGuide.value = false;
// }, 3000);
// }
// if (scores.value.length === total) {
// setTimeout(onOver, 1500);
// }
// }
// }
// });
}
async function onComplete() {
const validArrows = (practiseResult.value.details || []).filter(
(a) => a.x !== -30 && a.y !== -30
);
if (validArrows.length === total) {
uni.navigateBack();
} else {
practiseId.value = "";
practiseResult.value = {};
start.value = false;
scores.value = [];
xRingStreak.value = 0; // 重新开始练习,重置 X 环连续计数
const result = await createPractiseAPI(total, 3600);
if (result) practiseId.value = result.id;
}
}
const onClickShare = debounce(async () => {
await sharePractiseData("shareCanvas", 3, user.value, practiseResult.value);
await wxShare("shareCanvas");
});
onMounted(async () => {
uni.setKeepScreenOn({
keepScreenOn: true,
});
uni.$on("socket-inbox", onReceiveMessage);
uni.$on("share-image", onClickShare);
const result = await createPractiseAPI(total, 3600, targetType.value);
if (result) practiseId.value = result.id;
});
onBeforeUnmount(() => {
uni.setKeepScreenOn({
keepScreenOn: false,
});
uni.$off("socket-inbox", onReceiveMessage);
uni.$off("share-image", onClickShare);
audioManager.stopAll();
endPractiseAPI();
});
</script>
<template>
<Container
:bgType="1"
title="日常耐力挑战"
:showBottom="!start && !scores.length"
>
<view>
<TestDistance v-if="!start && !practiseResult.id" />
<block v-else>
<ShootProgress
:tips="`请连续射${total}支箭`"
:start="start"
:total="3600"
:onStop="onOver"
/>
<view class="user-row">
<Avatar :src="user.avatar" :size="35" />
<BubbleTip v-if="showGuide" type="normal2">
<text>完成过半胜利</text>
<text>在望💪</text>
</BubbleTip>
<BowPower />
</view>
<BowTarget
:currentRound="scores.length"
:totalRound="start ? total : 0"
:scores="scores"
/>
<ScorePanel
v-if="start"
:arrows="scores"
:total="total"
:rowCount="total / 4"
:margin="1.5"
:font-size="20"
/>
<ScoreResult
v-if="practiseResult.details"
:total="total"
:rowCount="9"
:onClose="onComplete"
:result="practiseResult"
:tipSrc="`../static/${
practiseResult.details.filter(
(arrow) => arrow.x !== -30 && arrow.y !== -30
).length < total
? '2un'
: ''
}finish-tip.png`"
/>
<canvas class="share-canvas" id="shareCanvas" type="2d"></canvas>
</block>
</view>
<template #bottom>
<SButton :onClick="onReady">准备好了直接开始</SButton>
</template>
</Container>
</template>
<style scoped></style>