pref: 进度条倒计时

This commit is contained in:
2026-05-07 16:49:09 +08:00
parent 9a5d64cc9e
commit 895be17f7c
3 changed files with 122 additions and 91 deletions

View File

@@ -362,6 +362,7 @@ class AudioManager {
}
this.allowPlayMap.set(key, false);
this.onAudioEnded(key);
uni.$emit('audioEnded', key);
});
audio.onStop(() => {

View File

@@ -1,6 +1,7 @@
<script setup>
import { ref, watch, onMounted, onBeforeUnmount } from "vue";
import { RoundGoldImages } from "@/constants";
import {ref, watch, onMounted, onBeforeUnmount} from "vue";
import {RoundGoldImages} from "@/constants";
const props = defineProps({
tips: {
type: String,
@@ -19,13 +20,26 @@ const props = defineProps({
const barColor = ref("");
const remain = ref(15);
const timer = ref(null);
const laoding = ref(false);
const loading = ref(false);
const transitionStyle = ref("all 1s linear");
const currentTeam = ref(null);
const updateRemain = (value) => {
if (timer.value) clearInterval(timer.value);
const newVal = Math.round(value);
if (value.stop) {
if (timer.value) clearInterval(timer.value);
return
}
loading.value = false;
currentTeam.value = value.team
if (value.team === 'red')
barColor.value = "linear-gradient( 180deg, #FFA0A0 0%, #FF6060 100%)";
if (value.team === 'blue')
barColor.value = "linear-gradient( 180deg, #9AB3FF 0%, #4288FF 100%)";
if (value.reset) {
remain.value = value.value;
return;
}
const newVal = Math.round(value.value);
// 如果剩余时间增加(如轮次切换重置),瞬间变化无动画;否则保持动画
if (newVal >= remain.value) {
transitionStyle.value = "none";
@@ -36,28 +50,21 @@ const updateRemain = (value) => {
} else {
remain.value = newVal;
}
timer.value = setInterval(() => {
laoding.value = remain.value === 0;
loading.value = remain.value === 0;
if (remain.value > 0) remain.value--;
}, 1000);
};
watch(
() => props.tips,
(newVal) => {
if (newVal.includes("红队"))
barColor.value = "linear-gradient( 180deg, #FFA0A0 0%, #FF6060 100%)";
if (newVal.includes("蓝队"))
barColor.value = "linear-gradient( 180deg, #9AB3FF 0%, #4288FF 100%)";
if (newVal.includes("重回")) return;
if (newVal.includes("红队") || newVal.includes("蓝队")) {
updateRemain(props.total);
() => props.tips,
(newVal) => {
},
{
immediate: true,
}
},
{
immediate: true,
}
);
onMounted(() => {
@@ -72,21 +79,21 @@ onBeforeUnmount(() => {
<template>
<view class="container">
<image :src="RoundGoldImages[props.currentRound]" mode="widthFix" />
<image :src="RoundGoldImages[props.currentRound]" mode="widthFix"/>
<view
:style="{
justifyContent: tips.includes('红队') ? 'flex-end' : 'flex-start',
:style="{
justifyContent: currentTeam==='red' ? 'flex-end' : 'flex-start',
}"
>
<view
:style="{
:style="{
width: `${(remain / total) * 100}%`,
background: barColor,
right: tips.includes('红队') ? 0 : 'unset',
right: currentTeam==='red' ? 0 : 'unset',
transition: transitionStyle,
}"
/>
<text v-if="!laoding">剩余{{ remain }}</text>
<text v-if="!loading">剩余{{ remain }}</text>
<text v-else>···</text>
</view>
</view>
@@ -99,11 +106,13 @@ onBeforeUnmount(() => {
flex-direction: column;
align-items: center;
}
.container > image {
width: 380rpx;
height: 80rpx;
transform: translateY(18rpx);
}
.container > view:last-child {
width: 100%;
text-align: center;
@@ -115,10 +124,12 @@ onBeforeUnmount(() => {
display: flex;
align-items: center;
}
.container > view:last-child > view {
height: 24rpx;
border-radius: 15px;
}
.container > view:last-child > text {
font-size: 18rpx;
color: #fff;

View File

@@ -1,6 +1,6 @@
<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from "vue";
import { onLoad, onShow, onHide } from "@dcloudio/uni-app";
import {ref, onMounted, onBeforeUnmount, nextTick} from "vue";
import {onLoad, onShow, onHide} from "@dcloudio/uni-app";
import Container from "@/components/Container.vue";
import BattleHeader from "@/components/BattleHeader.vue";
import BowTarget from "@/components/BowTarget.vue";
@@ -11,13 +11,14 @@ import RoundEndTip from "@/components/RoundEndTip.vue";
import TestDistance from "@/components/TestDistance.vue";
import TeamAvatars from "@/components/TeamAvatars.vue";
import ShootProgress2 from "@/components/ShootProgress2.vue";
import { laserCloseAPI, getBattleAPI } from "@/apis";
import { MESSAGETYPESV2 } from "@/constants";
import {laserCloseAPI, getBattleAPI} from "@/apis";
import {MESSAGETYPESV2} from "@/constants";
import audioManager from "@/audioManager";
import useStore from "@/store";
import { storeToRefs } from "pinia";
import {storeToRefs} from "pinia";
const store = useStore();
const { user } = storeToRefs(store);
const {user} = storeToRefs(store);
const start = ref(null);
const tips = ref("");
const battleId = ref("");
@@ -36,15 +37,16 @@ const bluePoints = ref(0);
const showRoundTip = ref(false);
const isFinalShoot = ref(false);
const matchStatus = ref(undefined);
const updateRemainSecond = ref(0);
const recoverData = (battleInfo, { force = false, arrowOnly = false } = {}) => {
const recoverData = (battleInfo, {force = false, arrowOnly = false} = {}) => {
try {
battleId.value = battleInfo.matchId;
// 优先使用接口返回的队伍数据,如果没有则尝试从缓存读取(应对匹配刚完成接口未就绪的情况)
const t1 = battleInfo.teams?.[1] || {};
const t2 = battleInfo.teams?.[2] || {};
if (t1.players) blueTeam.value = [...t1.players];
else {
const cached = uni.getStorageSync("blue-team");
@@ -56,7 +58,7 @@ const recoverData = (battleInfo, { force = false, arrowOnly = false } = {}) => {
const cached = uni.getStorageSync("red-team");
if (cached && cached.length) redTeam.value = cached;
}
start.value = battleInfo.status !== 0;
if (battleInfo.status === 0) {
@@ -71,28 +73,27 @@ const recoverData = (battleInfo, { force = false, arrowOnly = false } = {}) => {
if (!arrowOnly) {
currentShooterId.value = battleInfo.current.playerId;
const redPlayer = battleInfo.teams[2].players.find(
(item) => item.id === battleInfo.current.playerId
(item) => item.id === battleInfo.current.playerId
);
let nextTips = redPlayer ? "请红队射箭" : "请蓝队射箭";
if (force) nextTips += "重回";
if (
battleInfo.current.playerId === user.value.id &&
redTeam.value.length > 1
battleInfo.current.playerId === user.value.id &&
redTeam.value.length > 1
) {
nextTips += "你";
}
tips.value = nextTips;
uni.$emit("update-tips", nextTips);
uni.$emit("update-remain", {reset: true, value: 15, team: redTeam?'red':'blue'});
if (force) {
const remain = (Date.now() - battleInfo.current.startTime) / 1000;
console.log(`当前轮已进行${remain}`);
if (remain > 0 && remain < 15) {
setTimeout(() => {
uni.$emit("update-remain", 15 - remain - 0.2);
}, 200);
updateRemainSecond.value = 15 - remain - 0.2
}
} else {
uni.$emit("update-remain", battleInfo.readyTime);
updateRemainSecond.value = battleInfo.readyTime
}
} else {
currentRound.value = battleInfo.current.round || 1;
@@ -111,15 +112,39 @@ const recoverData = (battleInfo, { force = false, arrowOnly = false } = {}) => {
}
};
function onBattleEnd(){
showRoundTip.value = false
console.log("onBattleEnd ended...")
function onAudioEnded(s) {
if (s.indexOf('请红方射箭') >= 0 || s.indexOf('请蓝方射箭') >= 0) {
let team = s.indexOf('请红方射箭') >= 0 ? 'red' : 'blue';
uni.$emit("update-remain", {stop: false, value: updateRemainSecond.value, team: team});
}
if (s.indexOf("比赛结束") >= 0) {
onBattleEnd()
}
}
function onBattleEnd() {
if (matchStatus.value === 2) {
uni.redirectTo({
url: `/pages/battle-result?battleId=${battleId.value}`,
});
}
}
function onNewRound(msg) {
showRoundTip.value = true;
isFinalShoot.value = msg.current.goldRound;
const latestRound = msg.rounds[currentRound.value - 1];
if (latestRound) {
if (isFinalShoot.value) {
currentBluePoint.value = msg.teams[1].score;
currentRedPoint.value = msg.teams[2].score;
} else {
currentBluePoint.value = latestRound.scores[1].score;
currentRedPoint.value = latestRound.scores[2].score;
}
}
}
async function onReceiveMessage(msg) {
if (Array.isArray(msg)) return;
if (msg.type === MESSAGETYPESV2.BattleStart) {
@@ -127,21 +152,13 @@ async function onReceiveMessage(msg) {
} else if (msg.type === MESSAGETYPESV2.ToSomeoneShoot) {
recoverData(msg);
} else if (msg.type === MESSAGETYPESV2.ShootResult) {
uni.$emit("update-remain", {stop: true})
showRoundTip.value = false;
recoverData(msg, { arrowOnly: true });
recoverData(msg, {arrowOnly: true});
} else if (msg.type === MESSAGETYPESV2.NewRound) {
showRoundTip.value = true;
isFinalShoot.value = msg.current.goldRound;
const latestRound = msg.rounds[currentRound.value - 1];
if (latestRound) {
if (isFinalShoot.value) {
currentBluePoint.value = msg.teams[1].score;
currentRedPoint.value = msg.teams[2].score;
} else {
currentBluePoint.value = latestRound.scores[1].score;
currentRedPoint.value = latestRound.scores[2].score;
}
}
setTimeout(() => {
onNewRound(msg)
}, 800)
} else if (msg.type === MESSAGETYPESV2.BattleEnd) {
matchStatus.value = msg.status;
if (msg.status === 4) {
@@ -169,13 +186,14 @@ onMounted(async () => {
keepScreenOn: true,
});
uni.$on("socket-inbox", onReceiveMessage);
uni.$on("audioEnded", onAudioEnded);
await laserCloseAPI();
});
onBeforeUnmount(() => {
uni.setKeepScreenOn({
keepScreenOn: false,
});
uni.$off("socket-inbox", onReceiveMessage);
uni.$off("audioEnded", onAudioEnded);
audioManager.stopAll();
});
const refreshTimer = ref(null);
@@ -192,7 +210,7 @@ onShow(async () => {
delta: 2,
});
} else {
recoverData(result, { force: true });
recoverData(result, {force: true});
}
}
});
@@ -202,53 +220,53 @@ onShow(async () => {
<Container :bgType="start ? 3 : 1">
<view class="container">
<BattleHeader
v-if="start === false"
:redTeam="redTeam"
:blueTeam="blueTeam"
v-if="start === false"
:redTeam="redTeam"
:blueTeam="blueTeam"
/>
<TestDistance v-if="start === false" :guide="false" :isBattle="true" />
<TestDistance v-if="start === false" :guide="false" :isBattle="true"/>
<view v-if="start" class="players-row">
<TeamAvatars
:team="blueTeam"
:isRed="false"
:currentShooterId="currentShooterId"
:team="blueTeam"
:isRed="false"
:currentShooterId="currentShooterId"
/>
<ShootProgress2
:tips="tips"
:currentRound="
:tips="tips"
:currentRound="
goldenRound > 0 ? 'gold' + goldenRound : 'round' + currentRound
"
/>
<TeamAvatars :team="redTeam" :currentShooterId="currentShooterId" />
<TeamAvatars :team="redTeam" :currentShooterId="currentShooterId"/>
</view>
<BowTarget
v-if="start"
mode="team"
:scores="scores"
:blueScores="blueScores"
v-if="start"
mode="team"
:scores="scores"
:blueScores="blueScores"
/>
<BattleFooter
v-if="start"
:roundResults="roundResults"
:redPoints="redPoints"
:bluePoints="bluePoints"
:goldenRound="goldenRound"
v-if="start"
:roundResults="roundResults"
:redPoints="redPoints"
:bluePoints="bluePoints"
:goldenRound="goldenRound"
/>
<ScreenHint
:show="showRoundTip"
:onClose="() => (showRoundTip = false)"
:mode="isFinalShoot ? 'tall' : 'normal'"
:show="showRoundTip"
:onClose="() => (showRoundTip = false)"
:mode="isFinalShoot ? 'tall' : 'normal'"
>
<RoundEndTip
v-if="showRoundTip"
:isFinal="isFinalShoot"
:round="currentRound"
:bluePoint="currentBluePoint"
:redPoint="currentRedPoint"
:roundData="
v-if="showRoundTip"
:isFinal="isFinalShoot"
:round="currentRound"
:bluePoint="currentBluePoint"
:redPoint="currentRedPoint"
:roundData="
roundResults[currentRound - 1] ? roundResults[currentRound - 1] : []
"
:onAutoClose="onBattleEnd"
:onAutoClose="()=>{ showRoundTip = false}"
/>
</ScreenHint>
</view>
@@ -260,6 +278,7 @@ onShow(async () => {
width: 100%;
height: 100%;
}
.players-row {
display: flex;
align-items: center;