Files
shoot-miniprograms/src/components/BowTarget.vue
2025-07-16 12:09:27 +08:00

308 lines
6.7 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, watch } from "vue";
import BowPower from "@/components/BowPower.vue";
import StartCountdown from "@/components/StartCountdown.vue";
import { simulShootAPI } from "@/apis";
import useStore from "@/store";
import { storeToRefs } from "pinia";
const store = useStore();
const { user, device } = storeToRefs(store);
const props = defineProps({
currentRound: {
type: Number,
default: 0,
},
totalRound: {
type: Number,
default: 0,
},
avatar: {
type: String,
default: "",
},
power: {
type: Number,
default: 0,
},
scores: {
type: Array,
default: () => [],
},
blueScores: {
type: Array,
default: () => [],
},
mode: {
type: String,
default: "solo", // solo 单排team 双排
},
// start: {
// type: Boolean,
// default: false,
// },
stop: {
type: Boolean,
default: false,
},
});
const latestOne = ref(null);
const prevScores = ref([]);
const prevBlueScores = ref([]);
// const startCount = ref(false);
const timer = ref(null);
// watch(
// () => props.start,
// (newVal) => {
// startCount.value = newVal;
// },
// {
// immediate: true,
// }
// );
watch(
() => props.scores,
(newVal) => {
if (newVal.length - prevScores.value.length === 1) {
latestOne.value = newVal[newVal.length - 1];
if (timer.value) clearTimeout(timer.value);
timer.value = setTimeout(() => {
latestOne.value = null;
}, 1000);
}
prevScores.value = [...newVal];
},
{
deep: true,
}
);
watch(
() => props.blueScores,
(newVal) => {
if (newVal.length - prevBlueScores.value.length === 1) {
latestOne.value = newVal[newVal.length - 1];
if (timer.value) clearTimeout(timer.value);
timer.value = setTimeout(() => {
latestOne.value = null;
}, 1000);
}
prevBlueScores.value = [...newVal];
},
{
deep: true,
}
);
function calcRealX(num, offset = 12) {
const len = 20.4 + num;
return `calc(${(len / 40.8) * 100}% - ${offset / 2}px)`;
}
function calcRealY(num, offset = 12) {
const len = num < 0 ? Math.abs(num) + 20.4 : 20.4 - num;
return `calc(${(len / 40.8) * 100}% - ${offset / 2}px)`;
}
const simulShoot = async () => {
if (device.value.deviceId) await simulShootAPI(device.value.deviceId);
};
const simulShoot2 = async () => {
if (device.value.deviceId) await simulShootAPI(device.value.deviceId, 1, 1);
};
</script>
<template>
<view class="container">
<view class="header" v-if="totalRound > 0 || power">
<text v-if="totalRound > 0" class="round-count">{{
(currentRound > totalRound ? totalRound : currentRound) +
"/" +
totalRound
}}</text>
<BowPower :power="power" />
</view>
<view class="target">
<view v-if="stop" class="stop-sign">中场休息</view>
<view
v-if="latestOne && user.id === latestOne.playerId"
class="e-value fade-in-out"
:style="{
left: calcRealX(latestOne.ring ? latestOne.x : 0, 66),
top: calcRealY(latestOne.ring ? latestOne.y : 0, 150),
}"
>
经验 +1
</view>
<view
v-if="latestOne"
class="round-tip fade-in-out"
:style="{
left: calcRealX(latestOne.ring ? latestOne.x : 0, 100),
top: calcRealY(latestOne.ring ? latestOne.y : 0, 100),
}"
>{{ latestOne.ring || "未上靶"
}}<text v-if="latestOne.ring"></text></view
>
<block v-for="(bow, index) in scores" :key="index">
<view
v-if="bow.ring > 0"
:class="`hit ${
index === scores.length - 1 && latestOne ? 'pump-in' : ''
}`"
:style="{
left: calcRealX(bow.x),
top: calcRealY(bow.y),
backgroundColor:
index === scores.length - 1 &&
!blueScores.length &&
latestOne &&
mode !== 'team'
? 'green'
: 'red',
}"
><text>{{ index + 1 }}</text></view
>
</block>
<block v-for="(bow, index) in blueScores" :key="index">
<view
v-if="bow.ring > 0"
:class="`hit ${
index === blueScores.length - 1 && latestOne ? 'pump-in' : ''
}`"
:style="{
left: calcRealX(bow.x),
top: calcRealY(bow.y),
backgroundColor: 'blue',
}"
>
<text>{{ index + 1 }}</text>
</view>
</block>
<image src="../static/bow-target.png" mode="widthFix" />
</view>
<view v-if="avatar" class="footer">
<image :src="avatar" mode="widthFix" />
</view>
<view class="simul">
<button @click="simulShoot">模拟</button>
<button @click="simulShoot2">射箭</button>
</view>
<!-- <text :style="{ color: '#fff', wordBreak: 'break-all' }">{{
scores.length ? scores[scores.length - 1] : ""
}}</text> -->
<!-- <StartCountdown :start="startCount" /> -->
</view>
</template>
<style scoped>
.container {
width: calc(100% - 30px);
padding: 15px;
position: relative;
}
.target {
position: relative;
margin: 10px;
}
.e-value {
position: absolute;
/* top: 30%;
left: 60%; */
background-color: #0006;
color: #fff;
font-size: 12px;
padding: 4px 7px;
border-radius: 5px;
z-index: 2;
width: 50px;
text-align: center;
}
.round-tip {
position: absolute;
/* top: 38%; */
/* left: 60%; */
color: #fff;
font-size: 30px;
font-weight: bold;
z-index: 2;
width: 100px;
text-align: center;
}
.round-tip > text {
font-size: 24px;
margin-left: 5px;
}
.target > image:last-child {
width: 100%;
}
.hit {
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
border: 1px solid #fff;
z-index: 1;
color: #fff;
font-size: 8px;
text-align: center;
line-height: 10px;
box-sizing: border-box;
}
.hit > text {
transform: scaleX(0.7);
display: block;
font-weight: bold;
}
.header {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: -10px;
}
.header > image:first-child {
width: 40px;
height: 40px;
}
.round-count {
font-size: 20px;
color: #fed847;
top: 75px;
font-weight: bold;
}
.footer {
width: calc(100% - 20px);
padding: 0 10px;
display: flex;
margin-top: -40px;
}
.footer > image {
width: 40px;
min-height: 40px;
max-height: 40px;
border-radius: 50%;
border: 1px solid #fff;
}
.simul {
position: absolute;
bottom: 20px;
right: 20px;
margin-left: 20px;
}
.simul > button {
color: #fff;
}
.stop-sign {
position: absolute;
font-size: 40px;
color: #fff;
text-align: center;
width: 200px;
height: 60px;
left: calc(50% - 100px);
top: calc(50% - 30px);
}
</style>