From 02f30e88baf5f74803c29766d699f110d831012c Mon Sep 17 00:00:00 2001 From: zhangyibo95 <690096405@qq.com> Date: Mon, 27 Jul 2026 16:55:56 +0800 Subject: [PATCH] =?UTF-8?q?update:=E4=BC=98=E5=8C=96vip=E9=A3=9E=E8=A1=8C?= =?UTF-8?q?=E7=89=B9=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/BowShotEffect.vue | 88 ++++++++++++++----- src/components/BowTarget.vue | 27 ++++-- .../team-battle/components/BowTarget.vue | 27 ++++-- src/pages/training/components/BowTarget.vue | 27 ++++-- 4 files changed, 122 insertions(+), 47 deletions(-) diff --git a/src/components/BowShotEffect.vue b/src/components/BowShotEffect.vue index abc5ecc..058f5d5 100644 --- a/src/components/BowShotEffect.vue +++ b/src/components/BowShotEffect.vue @@ -22,6 +22,14 @@ const props = defineProps({ type: Number, default: 0, }, + targetLeft: { + type: Number, + default: 0, + }, + targetTop: { + type: Number, + default: 0, + }, hitOffsetPx: { type: Number, default: 0, @@ -40,6 +48,8 @@ let timers = []; const isActive = computed(() => phase.value !== "idle"); const ARROW_IMPACT_MS = 340; const COMPLETE_FALLBACK_MS = 980; +// 箭头尖端统一从屏幕中下区域出发,箭身自然延伸到屏幕底部之外。 +const SHOT_START_VIEWPORT_Y_RATIO = 0.82; const safeTargetRadius = computed(() => { const radius = Number(props.targetRadius); @@ -49,12 +59,33 @@ const safeTargetRadius = computed(() => { const safeTargetSize = computed(() => { const width = Number(props.targetWidth); const height = Number(props.targetHeight); + const left = Number(props.targetLeft); + const top = Number(props.targetTop); return { width: Number.isFinite(width) && width > 0 ? width : 0, height: Number.isFinite(height) && height > 0 ? height : 0, + left: Number.isFinite(left) ? left : 0, + top: Number.isFinite(top) ? top : 0, }; }); +function getWindowSize() { + try { + const info = + typeof uni.getWindowInfo === "function" + ? uni.getWindowInfo() + : uni.getSystemInfoSync(); + const width = Number(info?.windowWidth); + const height = Number(info?.windowHeight); + return { + width: Number.isFinite(width) && width > 0 ? width : 0, + height: Number.isFinite(height) && height > 0 ? height : 0, + }; + } catch { + return { width: 0, height: 0 }; + } +} + function hasShotPoint(shot) { const x = Number(shot?.x); const y = Number(shot?.y); @@ -105,23 +136,35 @@ const hitPercent = computed(() => { }; }); -const arrowAngle = computed(() => { +const flightPath = computed(() => { const size = safeTargetSize.value; - if (!size.width || !size.height) { - const dx = hitPercent.value.left - 50; - const dy = 114 - hitPercent.value.top; - const fallbackAngle = Math.atan2(dx, dy || 1) * (180 / Math.PI); - return Math.max(-18, Math.min(18, fallbackAngle)); - } + const windowSize = getWindowSize(); - const startX = size.width * 0.5; - const startY = size.height * 1.14; - const endX = size.width * (hitPercent.value.left / 100) + hitOffset.value.x; - const endY = size.height * (hitPercent.value.top / 100) + hitOffset.value.y; + const hasScreenCoordinates = + size.width > 0 && + size.height > 0 && + windowSize.width > 0 && + windowSize.height > 0; + const startX = hasScreenCoordinates + ? windowSize.width * 0.5 - size.left + : size.width * 0.5; + const startY = hasScreenCoordinates + ? windowSize.height * SHOT_START_VIEWPORT_Y_RATIO - size.top + : size.height * 1.14; + const endX = + size.width * (hitPercent.value.left / 100) + hitOffset.value.x; + const endY = + size.height * (hitPercent.value.top / 100) + hitOffset.value.y; const dx = endX - startX; - const dy = startY - endY; - const angle = Math.atan2(dx, dy || 1) * (180 / Math.PI); - return Math.max(-18, Math.min(18, angle)); + const dy = endY - startY; + + return { + startX, + startY, + translateX: dx, + translateY: dy, + angle: Math.atan2(dx, -dy) * (180 / Math.PI), + }; }); function formatPxOffset(value) { @@ -148,22 +191,21 @@ function getTargetTranslate(percent) { const arrowMoveStyle = computed(() => { const size = safeTargetSize.value; + const path = flightPath.value; let x = getTargetTranslate(hitPercent.value.left - 50); let y = getTargetTranslate(hitPercent.value.top - 114); if (size.width && size.height) { - const startX = size.width * 0.5; - const startY = size.height * 1.14; - const endX = size.width * (hitPercent.value.left / 100) + hitOffset.value.x; - const endY = size.height * (hitPercent.value.top / 100) + hitOffset.value.y; - x = `${endX - startX}px`; - y = `${endY - startY}px`; + x = `${path.translateX}px`; + y = `${path.translateY}px`; } return { + "--shot-start-x": size.width ? `${path.startX}px` : "50%", + "--shot-start-y": size.height ? `${path.startY}px` : "114%", "--shot-tx": x, "--shot-ty": y, - "--shot-angle": `${arrowAngle.value}deg`, + "--shot-angle": `${path.angle}deg`, }; }); @@ -286,8 +328,8 @@ onBeforeUnmount(() => { .shot-arrow-track { position: absolute; - left: 50%; - top: 114%; + left: var(--shot-start-x); + top: var(--shot-start-y); width: 0; height: 0; opacity: 0; diff --git a/src/components/BowTarget.vue b/src/components/BowTarget.vue index 910e3cf..b1c8161 100644 --- a/src/components/BowTarget.vue +++ b/src/components/BowTarget.vue @@ -76,7 +76,7 @@ const shotEffect = ref(null); const hiddenRedLatestKey = ref(""); const hiddenBlueLatestKey = ref(""); const targetShaking = ref(false); -const targetSize = ref({ width: 0, height: 0 }); +const targetRect = ref({ left: 0, top: 0, width: 0, height: 0 }); const shakeTimer = ref(null); const instance = getCurrentInstance(); const ROUND_TIP_OFFSET_Y = -32; @@ -178,7 +178,7 @@ function shakeTarget() { }); } -function updateTargetSize() { +function updateTargetRect() { nextTick(() => { const query = instance?.proxy ? uni.createSelectorQuery().in(instance.proxy) @@ -187,18 +187,27 @@ function updateTargetSize() { query .select(".target") .boundingClientRect((rect) => { + const left = Number(rect?.left); + const top = Number(rect?.top); const width = Number(rect?.width); const height = Number(rect?.height); - if (!Number.isFinite(width) || !Number.isFinite(height)) return; + if ( + !Number.isFinite(left) || + !Number.isFinite(top) || + !Number.isFinite(width) || + !Number.isFinite(height) + ) { + return; + } if (width <= 0 || height <= 0) return; - targetSize.value = { width, height }; + targetRect.value = { left, top, width, height }; }) .exec(); }); } function handleWindowResize() { - updateTargetSize(); + updateTargetRect(); } function shouldHideRedHit(index) { @@ -402,7 +411,7 @@ async function onReceiveMessage(message) { onMounted(() => { uni.$on("socket-inbox", onReceiveMessage); - updateTargetSize(); + updateTargetRect(); if (uni.onWindowResize) uni.onWindowResize(handleWindowResize); }); @@ -515,8 +524,10 @@ onBeforeUnmount(() => { :shot="shotEffect && shotEffect.shot" :playKey="shotEffect ? shotEffect.key : ''" :targetRadius="safeTargetRadius" - :targetWidth="targetSize.width" - :targetHeight="targetSize.height" + :targetLeft="targetRect.left" + :targetTop="targetRect.top" + :targetWidth="targetRect.width" + :targetHeight="targetRect.height" :hitOffsetPx="currentHitRadiusPx" @impact="shakeTarget" @complete="completeShotEffect" diff --git a/src/pages/team-battle/components/BowTarget.vue b/src/pages/team-battle/components/BowTarget.vue index 924ddc2..e71b69f 100644 --- a/src/pages/team-battle/components/BowTarget.vue +++ b/src/pages/team-battle/components/BowTarget.vue @@ -80,7 +80,7 @@ const shotEffect = ref(null); const hiddenRedLatestKey = ref(""); const hiddenBlueLatestKey = ref(""); const targetShaking = ref(false); -const targetSize = ref({ width: 0, height: 0 }); +const targetRect = ref({ left: 0, top: 0, width: 0, height: 0 }); const shakeTimer = ref(null); const instance = getCurrentInstance(); const ROUND_TIP_OFFSET_Y = -32; @@ -180,7 +180,7 @@ function shakeTarget() { }); } -function updateTargetSize() { +function updateTargetRect() { nextTick(() => { const query = instance?.proxy ? uni.createSelectorQuery().in(instance.proxy) @@ -189,18 +189,27 @@ function updateTargetSize() { query .select(".target") .boundingClientRect((rect) => { + const left = Number(rect?.left); + const top = Number(rect?.top); const width = Number(rect?.width); const height = Number(rect?.height); - if (!Number.isFinite(width) || !Number.isFinite(height)) return; + if ( + !Number.isFinite(left) || + !Number.isFinite(top) || + !Number.isFinite(width) || + !Number.isFinite(height) + ) { + return; + } if (width <= 0 || height <= 0) return; - targetSize.value = { width, height }; + targetRect.value = { left, top, width, height }; }) .exec(); }); } function handleWindowResize() { - updateTargetSize(); + updateTargetRect(); } function shouldHideRedHit(index) { @@ -408,7 +417,7 @@ async function onReceiveMessage(message) { onMounted(() => { uni.$on("socket-inbox", onReceiveMessage); - updateTargetSize(); + updateTargetRect(); if (uni.onWindowResize) uni.onWindowResize(handleWindowResize); }); @@ -533,8 +542,10 @@ onBeforeUnmount(() => { :shot="shotEffect && shotEffect.shot" :playKey="shotEffect ? shotEffect.key : ''" :targetRadius="safeTargetRadius" - :targetWidth="targetSize.width" - :targetHeight="targetSize.height" + :targetLeft="targetRect.left" + :targetTop="targetRect.top" + :targetWidth="targetRect.width" + :targetHeight="targetRect.height" :hitOffsetPx="currentHitRadiusPx" @impact="shakeTarget" @complete="completeShotEffect" diff --git a/src/pages/training/components/BowTarget.vue b/src/pages/training/components/BowTarget.vue index deb2ff3..25eea0f 100644 --- a/src/pages/training/components/BowTarget.vue +++ b/src/pages/training/components/BowTarget.vue @@ -100,7 +100,7 @@ const circleColor = ref(""); const shotEffect = ref(null); const hiddenLatestKey = ref(""); const targetShaking = ref(false); -const targetSize = ref({ width: 0, height: 0 }); +const targetRect = ref({ left: 0, top: 0, width: 0, height: 0 }); const shakeTimer = ref(null); const instance = getCurrentInstance(); const ROUND_TIP_OFFSET_Y = -32; @@ -291,7 +291,7 @@ function shakeTarget() { }); } -function updateTargetSize() { +function updateTargetRect() { nextTick(() => { const query = instance?.proxy ? uni.createSelectorQuery().in(instance.proxy) @@ -300,18 +300,27 @@ function updateTargetSize() { query .select(".target") .boundingClientRect((rect) => { + const left = Number(rect?.left); + const top = Number(rect?.top); const width = Number(rect?.width); const height = Number(rect?.height); - if (!Number.isFinite(width) || !Number.isFinite(height)) return; + if ( + !Number.isFinite(left) || + !Number.isFinite(top) || + !Number.isFinite(width) || + !Number.isFinite(height) + ) { + return; + } if (width <= 0 || height <= 0) return; - targetSize.value = { width, height }; + targetRect.value = { left, top, width, height }; }) .exec(); }); } function handleWindowResize() { - updateTargetSize(); + updateTargetRect(); } watch( @@ -422,7 +431,7 @@ async function onReceiveMessage(message) { onMounted(() => { uni.$on("socket-inbox", onReceiveMessage); - updateTargetSize(); + updateTargetRect(); if (uni.onWindowResize) uni.onWindowResize(handleWindowResize); }); @@ -549,8 +558,10 @@ onBeforeUnmount(() => { :shot="shotEffect && shotEffect.shot" :playKey="shotEffect ? shotEffect.key : ''" :targetRadius="safeTargetRadius" - :targetWidth="targetSize.width" - :targetHeight="targetSize.height" + :targetLeft="targetRect.left" + :targetTop="targetRect.top" + :targetWidth="targetRect.width" + :targetHeight="targetRect.height" :hitOffsetPx="currentHitRadiusPx" @impact="shakeTarget" @complete="completeShotEffect"