diff --git a/src/components/BowTarget.vue b/src/components/BowTarget.vue
index b1e6c5c..79ee41d 100644
--- a/src/components/BowTarget.vue
+++ b/src/components/BowTarget.vue
@@ -34,6 +34,18 @@ const props = defineProps({
type: Boolean,
default: false,
},
+ targetRadius: {
+ type: Number,
+ default: 20,
+ },
+ hitRadiusPx: {
+ type: Number,
+ default: 2,
+ },
+ zoomHitRadiusPx: {
+ type: Number,
+ default: 5,
+ },
});
const pMode = ref(true);
@@ -80,13 +92,79 @@ watch(
}
);
-function calcRealX(num, offset = 3.4) {
- const len = 20.4 + num;
- return `calc(${(len / 40.8) * 100 - offset / 2}%)`;
+const safeTargetRadius = computed(() => {
+ const radius = Number(props.targetRadius);
+ return Number.isFinite(radius) && radius > 0 ? radius : 20;
+});
+
+const currentHitRadiusPx = computed(() => {
+ const radius = Number(
+ pMode.value ? props.zoomHitRadiusPx : props.hitRadiusPx
+ );
+ return Number.isFinite(radius) && radius >= 0 ? radius : 0;
+});
+
+function getShotPoint(shot, fallbackCenter = false) {
+ const x = Number(shot?.x);
+ const y = Number(shot?.y);
+ if (Number.isFinite(x) && Number.isFinite(y)) return { x, y };
+ return fallbackCenter ? { x: 0, y: 0 } : null;
}
-function calcRealY(num, offset = 3.4) {
- const len = num < 0 ? Math.abs(num) + 20.4 : 20.4 - num;
- return `calc(${(len / 40.8) * 100 - offset / 2}%)`;
+
+function getPointDirection(point) {
+ if (!point) return null;
+ const distance = Math.sqrt(point.x * point.x + point.y * point.y);
+ if (distance === 0) return null;
+
+ return {
+ x: point.x / distance,
+ y: point.y / distance,
+ };
+}
+
+function formatPxOffset(value) {
+ if (!value) return "";
+ const operator = value > 0 ? "+" : "-";
+ return ` ${operator} ${Math.abs(value)}px`;
+}
+
+function formatTargetPosition(percent, offset) {
+ const pxOffset = formatPxOffset(offset);
+ return pxOffset ? `calc(${percent}%${pxOffset})` : `${percent}%`;
+}
+
+function getTargetPositionStyle(point, offsetPx = 0) {
+ if (!point) return { display: "none" };
+
+ const radius = safeTargetRadius.value;
+ const diameter = radius * 2;
+ const direction = getPointDirection(point);
+ const xOffset = direction ? direction.x * offsetPx : 0;
+ const yOffset = direction ? -direction.y * offsetPx : 0;
+ const leftPercent = ((point.x + radius) / diameter) * 100;
+ const topPercent = ((radius - point.y) / diameter) * 100;
+
+ return {
+ left: formatTargetPosition(leftPercent, xOffset),
+ top: formatTargetPosition(topPercent, yOffset),
+ transform: "translate(-50%, -50%)",
+ };
+}
+
+function getHitStyle(shot) {
+ const radius = currentHitRadiusPx.value;
+ const point = getShotPoint(shot);
+
+ return {
+ ...getTargetPositionStyle(point, radius),
+ width: `${radius * 2}px`,
+ height: `${radius * 2}px`,
+ };
+}
+
+function getTipStyle(shot) {
+ const point = getShotPoint(shot, true);
+ return getTargetPositionStyle(point, shot?.ring ? currentHitRadiusPx.value : 0);
}
const simulShoot = async () => {
if (device.value.deviceId) await simulShootAPI(device.value.deviceId);
@@ -169,20 +247,14 @@ onBeforeUnmount(() => {
经验 +1
{{ latestOne.ringX ? "X" : latestOne.ring || "未上靶"
}}环
@@ -193,20 +265,14 @@ onBeforeUnmount(() => {
user.id === bluelatestOne.playerId
"
class="e-value fade-in-out"
- :style="{
- left: calcRealX(bluelatestOne.ring ? bluelatestOne.x : 0, 20),
- top: calcRealY(bluelatestOne.ring ? bluelatestOne.y : 0, 40),
- }"
+ :style="getTipStyle(bluelatestOne)"
>
经验 +1
{{ bluelatestOne.ringX ? "X" : bluelatestOne.ring || "未上靶"
}}环
@@ -217,8 +283,7 @@ onBeforeUnmount(() => {
index === scores.length - 1 && latestOne ? 'pump-in' : ''
}`"
:style="{
- left: calcRealX(bow.x, pMode ? '3.4' : '2'),
- top: calcRealY(bow.y, pMode ? '3.4' : '2'),
+ ...getHitStyle(bow),
backgroundColor: mode === 'solo' ? '#00bf04' : '#FF0000',
}"
>{{ index + 1 }} {
index === blueScores.length - 1 && bluelatestOne ? 'pump-in' : ''
}`"
:style="{
- left: calcRealX(bow.x, pMode ? '3.4' : '2'),
- top: calcRealY(bow.y, pMode ? '3.4' : '2'),
+ ...getHitStyle(bow),
backgroundColor: '#1840FF',
}"
>
@@ -302,21 +366,11 @@ onBeforeUnmount(() => {
z-index: 1;
color: #fff;
transition: all 0.3s ease;
-}
-.s-point {
- width: 4px;
- height: 4px;
- min-width: 4px;
- min-height: 4px;
+ box-sizing: border-box;
}
.b-point {
- width: 10px;
- height: 10px;
- min-width: 10px;
- min-height: 10px;
border: 1px solid #fff;
z-index: 1;
- box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
@@ -332,6 +386,19 @@ onBeforeUnmount(() => {
transform: translate(-50%, -50%);*/
margin-top: 2rpx;
}
+@keyframes target-pump-in {
+ from {
+ transform: translate(-50%, -50%) scale(2);
+ }
+
+ to {
+ transform: translate(-50%, -50%) scale(1);
+ }
+}
+.hit.pump-in {
+ animation: target-pump-in 0.3s ease-out forwards;
+ transform-origin: center center;
+}
.header {
width: 100%;
display: flex;
diff --git a/src/pages/team-battle/components/BowTarget.vue b/src/pages/team-battle/components/BowTarget.vue
index 18f82cc..c855fea 100644
--- a/src/pages/team-battle/components/BowTarget.vue
+++ b/src/pages/team-battle/components/BowTarget.vue
@@ -38,6 +38,18 @@ const props = defineProps({
type: Boolean,
default: false,
},
+ targetRadius: {
+ type: Number,
+ default: 20,
+ },
+ hitRadiusPx: {
+ type: Number,
+ default: 2,
+ },
+ zoomHitRadiusPx: {
+ type: Number,
+ default: 5,
+ },
});
const pMode = ref(true);
@@ -75,13 +87,79 @@ watch(
{ immediate: true }
);
-function calcRealX(num, offset = 3.4) {
- const len = 20.4 + num;
- return `calc(${(len / 40.8) * 100 - offset / 2}%)`;
+const safeTargetRadius = computed(() => {
+ const radius = Number(props.targetRadius);
+ return Number.isFinite(radius) && radius > 0 ? radius : 20;
+});
+
+const currentHitRadiusPx = computed(() => {
+ const radius = Number(
+ pMode.value ? props.zoomHitRadiusPx : props.hitRadiusPx
+ );
+ return Number.isFinite(radius) && radius >= 0 ? radius : 0;
+});
+
+function getShotPoint(shot, fallbackCenter = false) {
+ const x = Number(shot?.x);
+ const y = Number(shot?.y);
+ if (Number.isFinite(x) && Number.isFinite(y)) return { x, y };
+ return fallbackCenter ? { x: 0, y: 0 } : null;
}
-function calcRealY(num, offset = 3.4) {
- const len = num < 0 ? Math.abs(num) + 20.4 : 20.4 - num;
- return `calc(${(len / 40.8) * 100 - offset / 2}%)`;
+
+function getPointDirection(point) {
+ if (!point) return null;
+ const distance = Math.sqrt(point.x * point.x + point.y * point.y);
+ if (distance === 0) return null;
+
+ return {
+ x: point.x / distance,
+ y: point.y / distance,
+ };
+}
+
+function formatPxOffset(value) {
+ if (!value) return "";
+ const operator = value > 0 ? "+" : "-";
+ return ` ${operator} ${Math.abs(value)}px`;
+}
+
+function formatTargetPosition(percent, offset) {
+ const pxOffset = formatPxOffset(offset);
+ return pxOffset ? `calc(${percent}%${pxOffset})` : `${percent}%`;
+}
+
+function getTargetPositionStyle(point, offsetPx = 0) {
+ if (!point) return { display: "none" };
+
+ const radius = safeTargetRadius.value;
+ const diameter = radius * 2;
+ const direction = getPointDirection(point);
+ const xOffset = direction ? direction.x * offsetPx : 0;
+ const yOffset = direction ? -direction.y * offsetPx : 0;
+ const leftPercent = ((point.x + radius) / diameter) * 100;
+ const topPercent = ((radius - point.y) / diameter) * 100;
+
+ return {
+ left: formatTargetPosition(leftPercent, xOffset),
+ top: formatTargetPosition(topPercent, yOffset),
+ transform: "translate(-50%, -50%)",
+ };
+}
+
+function getHitStyle(shot) {
+ const radius = currentHitRadiusPx.value;
+ const point = getShotPoint(shot);
+
+ return {
+ ...getTargetPositionStyle(point, radius),
+ width: `${radius * 2}px`,
+ height: `${radius * 2}px`,
+ };
+}
+
+function getTipStyle(shot) {
+ const point = getShotPoint(shot, true);
+ return getTargetPositionStyle(point, shot?.ring ? currentHitRadiusPx.value : 0);
}
const simulShoot = async () => {
if (device.value.deviceId) await simulShootAPI(device.value.deviceId);
@@ -164,20 +242,14 @@ onBeforeUnmount(() => {
经验 +1
{{ latestOne.ringX ? "X" : latestOne.ring || "未上靶"
}}环
@@ -188,20 +260,14 @@ onBeforeUnmount(() => {
user.id === bluelatestOne.playerId
"
class="e-value fade-in-out"
- :style="{
- left: calcRealX(bluelatestOne.ring ? bluelatestOne.x : 0, 20),
- top: calcRealY(bluelatestOne.ring ? bluelatestOne.y : 0, 40),
- }"
+ :style="getTipStyle(bluelatestOne)"
>
经验 +1
{{ bluelatestOne.ringX ? "X" : bluelatestOne.ring || "未上靶"
}}环
@@ -212,8 +278,7 @@ onBeforeUnmount(() => {
index === scores.length - 1 && latestOne ? 'pump-in' : ''
}`"
:style="{
- left: calcRealX(bow.x, pMode ? '3.4' : '2'),
- top: calcRealY(bow.y, pMode ? '3.4' : '2'),
+ ...getHitStyle(bow),
backgroundColor: mode === 'solo' ? '#00bf04' : '#FF0000',
}"
>{{ index + 1 }} {
index === blueScores.length - 1 && bluelatestOne ? 'pump-in' : ''
}`"
:style="{
- left: calcRealX(bow.x, pMode ? '3.4' : '2'),
- top: calcRealY(bow.y, pMode ? '3.4' : '2'),
+ ...getHitStyle(bow),
backgroundColor: '#1840FF',
}"
>
@@ -297,21 +361,11 @@ onBeforeUnmount(() => {
z-index: 1;
color: #fff;
transition: all 0.3s ease;
-}
-.s-point {
- width: 4px;
- height: 4px;
- min-width: 4px;
- min-height: 4px;
+ box-sizing: border-box;
}
.b-point {
- width: 10px;
- height: 10px;
- min-width: 10px;
- min-height: 10px;
border: 1px solid #fff;
z-index: 1;
- box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
@@ -327,6 +381,19 @@ onBeforeUnmount(() => {
transform: translate(-50%, -50%);*/
margin-top: 2rpx;
}
+@keyframes target-pump-in {
+ from {
+ transform: translate(-50%, -50%) scale(2);
+ }
+
+ to {
+ transform: translate(-50%, -50%) scale(1);
+ }
+}
+.hit.pump-in {
+ animation: target-pump-in 0.3s ease-out forwards;
+ transform-origin: center center;
+}
.header {
width: 100%;
display: flex;