|
|
@@ -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);
|
|
|
};
|