178 lines
4.6 KiB
Vue
178 lines
4.6 KiB
Vue
<script setup>
|
||
import {ref, watch, onMounted, onBeforeUnmount} from "vue";
|
||
import {RoundGoldImages} from "@/constants";
|
||
|
||
const props = defineProps({
|
||
tips: {
|
||
type: String,
|
||
default: "",
|
||
},
|
||
total: {
|
||
type: Number,
|
||
default: 15,
|
||
},
|
||
currentRound: {
|
||
type: String,
|
||
default: "round1",
|
||
},
|
||
});
|
||
|
||
const barColor = ref("");
|
||
const remain = ref(15);
|
||
const timer = ref(null);
|
||
const loading = ref(false);
|
||
const transitionStyle = ref("all 1s linear");
|
||
const currentTeam = ref(null);
|
||
|
||
const updateRemain = (value) => {
|
||
if (value.stop) {
|
||
if (timer.value) clearInterval(timer.value);
|
||
return
|
||
}
|
||
// zeroThenReset:ToSomeoneShoot 到达时,若进度条仍在倒计时则先瞬间清零(约 150ms 停留)再显示下一玩家满值
|
||
// 若进度条已到 0(loading 状态),直接切换满值
|
||
if (value.zeroThenReset) {
|
||
if (timer.value) clearInterval(timer.value);
|
||
const wasNonZero = remain.value > 0;
|
||
// 更新下一玩家颜色和方向(在清零和满值时均生效)
|
||
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%)";
|
||
transitionStyle.value = "none";
|
||
if (wasNonZero) {
|
||
// 瞬间清零,停留约 150ms 后切换为满值
|
||
remain.value = 0;
|
||
loading.value = true;
|
||
setTimeout(() => {
|
||
remain.value = value.value;
|
||
loading.value = false;
|
||
setTimeout(() => { transitionStyle.value = "all 1s linear"; }, 50);
|
||
}, 150);
|
||
} else {
|
||
// 已在底部,直接切换满值
|
||
remain.value = value.value;
|
||
loading.value = false;
|
||
setTimeout(() => { transitionStyle.value = "all 1s linear"; }, 50);
|
||
}
|
||
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) {
|
||
// 重置前先清除旧计时器,防止超时未射箭时旧 interval 残留,导致进度条震荡
|
||
if (timer.value) clearInterval(timer.value);
|
||
// 重置时瞬间跳满格,禁用 CSS 过渡避免从旧值「涨到满」的动画
|
||
transitionStyle.value = "none";
|
||
remain.value = value.value;
|
||
setTimeout(() => {
|
||
transitionStyle.value = "all 1s linear";
|
||
}, 50);
|
||
return;
|
||
}
|
||
const newVal = Math.round(value.value);
|
||
// 如果剩余时间增加(如轮次切换重置),瞬间变化无动画;否则保持动画
|
||
if (newVal >= remain.value) {
|
||
transitionStyle.value = "none";
|
||
remain.value = newVal;
|
||
setTimeout(() => {
|
||
transitionStyle.value = "all 1s linear";
|
||
}, 50);
|
||
} else {
|
||
remain.value = newVal;
|
||
}
|
||
|
||
// 启动前先清除旧计时器,防止多次 {stop:false} 事件叠加多个 interval
|
||
if (timer.value) clearInterval(timer.value);
|
||
timer.value = setInterval(() => {
|
||
loading.value = remain.value === 0;
|
||
if (remain.value > 0) remain.value--;
|
||
}, 1000);
|
||
};
|
||
|
||
watch(
|
||
() => props.tips,
|
||
(newVal) => {
|
||
|
||
},
|
||
{
|
||
immediate: true,
|
||
}
|
||
);
|
||
|
||
onMounted(() => {
|
||
uni.$on("update-remain", updateRemain);
|
||
});
|
||
|
||
onBeforeUnmount(() => {
|
||
uni.$off("update-remain", updateRemain);
|
||
if (timer.value) clearInterval(timer.value);
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<view class="container">
|
||
<image :src="RoundGoldImages[props.currentRound]" mode="widthFix"/>
|
||
<view
|
||
:style="{
|
||
justifyContent: currentTeam==='red' ? 'flex-end' : 'flex-start',
|
||
}"
|
||
>
|
||
<view
|
||
:style="{
|
||
width: `${(remain / total) * 100}%`,
|
||
background: barColor,
|
||
right: currentTeam==='red' ? 0 : 'unset',
|
||
transition: transitionStyle,
|
||
}"
|
||
/>
|
||
<text v-if="!loading">剩余{{ remain }}秒</text>
|
||
<text v-else>···</text>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.container {
|
||
width: 50vw;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
}
|
||
|
||
.container > image {
|
||
width: 380rpx;
|
||
height: 80rpx;
|
||
transform: translateY(18rpx);
|
||
}
|
||
|
||
.container > view:last-child {
|
||
width: 100%;
|
||
text-align: center;
|
||
background-color: #444444;
|
||
border-radius: 20px;
|
||
height: 24rpx;
|
||
position: relative;
|
||
overflow: hidden;
|
||
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;
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
}
|
||
</style>
|