From bac5e0a26a8308980c61fece30a5e3cd9cec3f02 Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Thu, 16 Apr 2026 13:34:48 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=89=8B=E6=9C=BA=E5=B7=A6=E5=8F=B3?= =?UTF-8?q?=E6=BB=91=E5=8A=A8=E5=88=87=E6=8D=A2=20tab=20=E4=B8=8D=E7=94=9F?= =?UTF-8?q?=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 加 touch-action: pan-y 阻止浏览器抢占水平手势 - 用 touchmove 实时跟踪手指位置(比 touchend.changedTouches 更可靠) - 用 touchstart 的 target 判断 no-swipe 区域(手指移动后 target 可能变) - 弹窗/遮罩层打开时跳过滑动切换 - 阈值从 50px 调到 60px 减少误触 Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/App.vue | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 0a7e668..b766d69 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -38,7 +38,7 @@ -
+
@@ -167,23 +167,36 @@ function toggleUserMenu() { } // ── 左右滑动切换 tab ── -// 滑动顺序 = visibleTabs 的顺序(根据用户角色动态决定) -// 轮播区域(data-no-tab-swipe)内的滑动不触发 tab 切换 +// touch-action: pan-y on .main tells the browser to only handle vertical scroll natively, +// leaving horizontal swipe gestures to our JS handler. const swipeStartX = ref(0) const swipeStartY = ref(0) +const swipeEndX = ref(0) +const swipeEndY = ref(0) +const swipeStartTarget = ref(null) function onSwipeStart(e) { swipeStartX.value = e.touches[0].clientX swipeStartY.value = e.touches[0].clientY + swipeEndX.value = e.touches[0].clientX + swipeEndY.value = e.touches[0].clientY + swipeStartTarget.value = e.target } -function onSwipeEnd(e) { - const dx = e.changedTouches[0].clientX - swipeStartX.value - const dy = e.changedTouches[0].clientY - swipeStartY.value - // 必须是水平滑动 > 50px,且水平距离大于垂直距离 - if (Math.abs(dx) < 50 || Math.abs(dy) > Math.abs(dx)) return - // 轮播区域内不触发 tab 切换 - if (e.target.closest && e.target.closest('[data-no-tab-swipe]')) return +function onSwipeMove(e) { + swipeEndX.value = e.touches[0].clientX + swipeEndY.value = e.touches[0].clientY +} + +function onSwipeEnd() { + const dx = swipeEndX.value - swipeStartX.value + const dy = swipeEndY.value - swipeStartY.value + // Must be primarily horizontal (>60px) and more horizontal than vertical + if (Math.abs(dx) < 60 || Math.abs(dy) > Math.abs(dx)) return + // Carousel area excluded + if (swipeStartTarget.value?.closest?.('[data-no-tab-swipe]')) return + // Skip when modal/overlay is open + if (document.querySelector('.modal-overlay, .detail-overlay, .dialog-overlay')) return const tabs = visibleTabs.value.map(t => t.key) const currentIdx = tabs.indexOf(ui.currentSection) @@ -193,8 +206,7 @@ function onSwipeEnd(e) { if (dx < 0 && currentIdx < tabs.length - 1) nextIdx = currentIdx + 1 else if (dx > 0 && currentIdx > 0) nextIdx = currentIdx - 1 if (nextIdx >= 0) { - const tab = visibleTabs.value[nextIdx] - handleTabClick(tab) + handleTabClick(visibleTabs.value[nextIdx]) } }