Selaa lähdekoodia

锚点定位切换标签

sunshibo 3 viikkoa sitten
vanhempi
commit
1849437652
1 muutettua tiedostoa jossa 62 lisäystä ja 2 poistoa
  1. 62 2
      pages/orderFood/index.vue

+ 62 - 2
pages/orderFood/index.vue

@@ -27,7 +27,13 @@
 
       <!-- 右侧菜品长列表:按分类分段展示,每段标题 + 该分类下菜品 -->
       <view class="food-list-wrap">
-        <scroll-view class="food-list" scroll-y :scroll-into-view="scrollIntoViewId" scroll-with-animation>
+        <scroll-view
+          class="food-list"
+          scroll-y
+          :scroll-into-view="scrollIntoViewId"
+          scroll-with-animation
+          @scroll="onFoodListScroll"
+        >
           <block v-for="(section, sectionIndex) in sectionList" :key="section.category.id ?? sectionIndex">
             <view :id="'section-' + sectionIndex" class="food-section">
               <view class="food-section__title">{{ section.category.categoryName }}</view>
@@ -62,7 +68,7 @@
 <script setup>
 import { onLoad, onShow, onUnload } from "@dcloudio/uni-app";
 import NavBar from "@/components/NavBar/index.vue";
-import { ref, computed, nextTick } from "vue";
+import { ref, computed, nextTick, getCurrentInstance } from "vue";
 import FoodCard from "./components/FoodCard.vue";
 import BottomActionBar from "./components/BottomActionBar.vue";
 import CouponModal from "./components/CouponModal.vue";
@@ -98,6 +104,10 @@ const displayTableNumber = computed(() => {
 let sseRequestTask = null; // 订单 SSE 连接(封装后兼容小程序),页面卸载时需 abort()
 const currentCategoryIndex = ref(0);
 const foodListScrollTop = ref(0);
+/** 点击左侧分类触发的右侧滚动期间,不同步高亮,避免动画过程中乱跳 */
+const syncingCategoryFromClick = ref(false);
+const pageInstance = getCurrentInstance();
+let foodListScrollSyncTimer = null;
 const searchKeyword = ref('');
 const couponModalOpen = ref(false);
 const cartModalOpen = ref(false);
@@ -424,9 +434,58 @@ const fetchCategoriesWithCuisines = async (storeId, keyword = '') => {
   }
 };
 
+function lockCategorySyncFromClick() {
+  syncingCategoryFromClick.value = true;
+  setTimeout(() => {
+    syncingCategoryFromClick.value = false;
+  }, 480);
+}
+
+// 根据右侧列表可视区域与各分类块位置,同步左侧高亮(与美团类似:当前顶部的分类块对应左侧选中项)
+function updateActiveCategoryFromScrollLayout() {
+  if (syncingCategoryFromClick.value) return;
+  const proxy = pageInstance?.proxy;
+  if (!proxy) return;
+  uni.createSelectorQuery()
+    .in(proxy)
+    .select('.food-list')
+    .boundingClientRect()
+    .selectAll('.food-section')
+    .boundingClientRect()
+    .exec((res) => {
+      if (!res || res.length < 2) return;
+      const listRect = res[0];
+      let sections = res[1];
+      if (!listRect || sections == null) return;
+      if (!Array.isArray(sections)) sections = [sections];
+      if (!sections.length) return;
+      // 取滚动可视区上沿略下移一点:最后一个「区块顶边已滚到该区域以上」的索引即为当前分类
+      const anchorY = listRect.top + 6;
+      let active = 0;
+      for (let i = 0; i < sections.length; i++) {
+        const r = sections[i];
+        if (r && typeof r.top === 'number' && r.top <= anchorY) active = i;
+      }
+      if (currentCategoryIndex.value !== active) {
+        currentCategoryIndex.value = active;
+      }
+    });
+}
+
+function onFoodListScroll(e) {
+  foodListScrollTop.value = e.detail?.scrollTop ?? 0;
+  if (syncingCategoryFromClick.value) return;
+  if (foodListScrollSyncTimer != null) clearTimeout(foodListScrollSyncTimer);
+  foodListScrollSyncTimer = setTimeout(() => {
+    foodListScrollSyncTimer = null;
+    updateActiveCategoryFromScrollLayout();
+  }, 40);
+}
+
 // 选择分类:切换高亮并滚动右侧到对应分段(锚点定位)
 const selectCategory = (index) => {
   currentCategoryIndex.value = index;
+  lockCategorySyncFromClick();
   scrollIntoViewId.value = 'section-' + index;
   setTimeout(() => { scrollIntoViewId.value = ''; }, 400);
 };
@@ -434,6 +493,7 @@ const selectCategory = (index) => {
 // 滚动到指定分段锚点(供搜索后定位用)
 const scrollToSection = (index) => {
   currentCategoryIndex.value = index;
+  lockCategorySyncFromClick();
   scrollIntoViewId.value = 'section-' + index;
   setTimeout(() => { scrollIntoViewId.value = ''; }, 400);
 };