¶Ô±ÈÐÂÎļþ |
| | |
| | | <script setup> |
| | | import { ref, onMounted, nextTick } from 'vue'; |
| | | import classifyData from '@/pages_template/common/classify.data.js'; |
| | | |
| | | const scrollTop = ref(0); // tabæ é¢çæ»å¨æ¡ä½ç½® |
| | | const oldScrollTop = ref(0); |
| | | const current = ref(0); // é¢è®¾å½å项çå¼ |
| | | const menuHeight = ref(0); // 左边èåçé«åº¦ |
| | | const menuItemHeight = ref(0); // 左边èåitemçé«åº¦ |
| | | const itemId = ref(''); // æ ç®å³è¾¹scroll-viewç¨äºæ»å¨çid |
| | | const tabbar = classifyData; |
| | | |
| | | const arr = ref([]); |
| | | const scrollRightTop = ref(0); // å³è¾¹æ ç®scroll-viewçæ»å¨æ¡é«åº¦ |
| | | const timer = ref(null); // 宿¶å¨ |
| | | |
| | | onMounted(() => { |
| | | getMenuItemTop(); |
| | | }); |
| | | |
| | | // ç¹å»å·¦è¾¹çæ ç®åæ¢ |
| | | const swichMenu = async (index) => { |
| | | if (arr.value.length === 0) { |
| | | await getMenuItemTop(); |
| | | } |
| | | if (index === current.value) return; |
| | | scrollRightTop.value = oldScrollTop.value; |
| | | await nextTick(() => { |
| | | scrollRightTop.value = arr.value[index]; |
| | | current.value = index; |
| | | leftMenuStatus(index); |
| | | }); |
| | | }; |
| | | |
| | | // è·åä¸ä¸ªç®æ å
ç´ çé«åº¦ |
| | | const getElRect = (elClass, dataVal) => { |
| | | return new Promise((resolve, reject) => { |
| | | const query = uni.createSelectorQuery().in(this); |
| | | query.select('.' + elClass).fields({ |
| | | size: true |
| | | }, (res) => { |
| | | // 妿èç¹å°æªçæï¼reså¼ä¸ºnullï¼å¾ªç¯è°ç¨æ§è¡ |
| | | if (!res) { |
| | | setTimeout(() => { |
| | | getElRect(elClass, dataVal); |
| | | }, 10); |
| | | return; |
| | | } |
| | | dataVal.value = res.height; |
| | | resolve(); |
| | | }).exec(); |
| | | }); |
| | | }; |
| | | |
| | | // è§æµå
ç´ ç¸äº¤ç¶æ |
| | | const observer = () => { |
| | | tabbar.map((val, index) => { |
| | | let observer = uni.createIntersectionObserver(this); |
| | | // æ£æµå³è¾¹scroll-viewçid为itemxxçå
ç´ ä¸right-boxçç¸äº¤ç¶æ |
| | | // 妿è·.right-boxåºé¨ç¸äº¤ï¼å°±å¨æè®¾ç½®å·¦è¾¹æ ç®çæ´»å¨ç¶æ |
| | | observer.relativeTo('.right-box', { |
| | | top: 0 |
| | | }).observe('#item' + index, (res) => { |
| | | if (res.intersectionRatio > 0) { |
| | | let id = res.id.substring(4); |
| | | leftMenuStatus(id); |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | // 设置左边èåçæ»å¨ç¶æ |
| | | const leftMenuStatus = async (index) => { |
| | | current.value = index; |
| | | // å¦æä¸º0ï¼æå³çå°æªåå§å |
| | | if (menuHeight.value === 0 || menuItemHeight.value === 0) { |
| | | await getElRect('menu-scroll-view', menuHeight); |
| | | await getElRect('u-tab-item', menuItemHeight); |
| | | } |
| | | // å°èåæ´»å¨itemåç´å±
ä¸ |
| | | scrollTop.value = index * menuItemHeight.value + menuItemHeight.value / 2 - menuHeight.value / 2; |
| | | }; |
| | | |
| | | // è·åå³è¾¹èåæ¯ä¸ªitemå°é¡¶é¨çè·ç¦» |
| | | const getMenuItemTop = () => { |
| | | return new Promise((resolve) => { |
| | | let selectorQuery = uni.createSelectorQuery(); |
| | | selectorQuery.selectAll('.class-item').boundingClientRect((rects) => { |
| | | // 妿èç¹å°æªçæï¼rectså¼ä¸º[](å 为ç¨selectAllï¼æä»¥è¿åçæ¯æ°ç»)ï¼å¾ªç¯è°ç¨æ§è¡ |
| | | if (!rects.length) { |
| | | setTimeout(() => { |
| | | getMenuItemTop(); |
| | | }, 10); |
| | | return; |
| | | } |
| | | rects.forEach((rect) => { |
| | | // è¿éåå»rects[0].topï¼æ¯å 为第ä¸é¡¹é¡¶é¨å¯è½ä¸æ¯è´´å°å¯¼èªæ (æ¯å¦æä¸ªæç´¢æ¡çæ
åµ) |
| | | arr.value.push(rect.top - rects[0].top); |
| | | resolve(); |
| | | }); |
| | | }).exec(); |
| | | }); |
| | | }; |
| | | |
| | | // å³è¾¹èåæ»å¨ |
| | | const rightScroll = async (e) => { |
| | | oldScrollTop.value = e.detail.scrollTop; |
| | | if (arr.value.length === 0) { |
| | | await getMenuItemTop(); |
| | | } |
| | | if (timer.value) return; |
| | | if (!menuHeight.value) { |
| | | await getElRect('menu-scroll-view', menuHeight); |
| | | } |
| | | setTimeout(() => { // èæµ |
| | | timer.value = null; |
| | | // scrollHeight为å³è¾¹èååç´ä¸ç¹ä½ç½® |
| | | let scrollHeight = e.detail.scrollTop + menuHeight.value / 2; |
| | | for (let i = 0; i < arr.value.length; i++) { |
| | | let height1 = arr.value[i]; |
| | | let height2 = arr.value[i + 1]; |
| | | // 妿ä¸åå¨height2ï¼æå³çæ°æ®å¾ªç¯å·²ç»å°äºæåä¸ä¸ªï¼è®¾ç½®å·¦è¾¹èå为æåä¸é¡¹å³å¯ |
| | | if (!height2 || scrollHeight >= height1 && scrollHeight < height2) { |
| | | leftMenuStatus(i); |
| | | return; |
| | | } |
| | | } |
| | | }, 10); |
| | | }; |
| | | |
| | | const clickMenu = (menu) => { |
| | | console.log(menu); |
| | | }; |
| | | </script> |
| | | |
| | | <template> |
| | | <view class="u-wrap"> |
| | | <view class="u-search-box"> |
| | | <view class="u-search-inner"> |
| | | <u-icon name="search" color="#909399" :size="28"></u-icon> |
| | | <text class="u-search-text">æç´¢</text> |
| | | </view> |
| | | </view> |
| | | <view class="u-menu-wrap"> |
| | | <scroll-view scroll-y scroll-with-animation class="u-tab-view menu-scroll-view" :scroll-top="scrollTop" |
| | | :scroll-into-view="itemId"> |
| | | <view v-for="(item, index) in tabbar" :key="index" class="u-tab-item" |
| | | :class="[current == index ? 'u-tab-item-active' : '']" @tap.stop="swichMenu(index)"> |
| | | <text class="u-line-1">{{ item.name }}</text> |
| | | </view> |
| | | </scroll-view> |
| | | <scroll-view :scroll-top="scrollRightTop" scroll-y scroll-with-animation class="right-box" |
| | | @scroll="rightScroll"> |
| | | <view class="page-view"> |
| | | <view class="class-item" :id="'item' + index" v-for="(item, index) in tabbar" :key="index"> |
| | | <view class="item-title"> |
| | | <text>{{ item.name }}</text> |
| | | </view> |
| | | <view class="item-container"> |
| | | <view class="thumb-box" v-for="(item1, index1) in item.foods" :key="index1" |
| | | @click="clickMenu(item1)"> |
| | | <image class="item-menu-image" :src="item1.icon" mode=""></image> |
| | | <view class="item-menu-name">{{ item1.name }}</view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </view> |
| | | </scroll-view> |
| | | </view> |
| | | </view> |
| | | </template> |
| | | <style lang="scss" scoped> |
| | | .u-wrap { |
| | | height: calc(100vh); |
| | | /* #ifdef H5 */ |
| | | height: calc(100vh - var(--window-top)); |
| | | /* #endif */ |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .u-search-box { |
| | | padding: 18rpx 30rpx; |
| | | } |
| | | |
| | | .u-menu-wrap { |
| | | flex: 1; |
| | | display: flex; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .u-search-inner { |
| | | background-color: rgb(234, 234, 234); |
| | | border-radius: 100rpx; |
| | | display: flex; |
| | | align-items: center; |
| | | padding: 10rpx 16rpx; |
| | | } |
| | | |
| | | .u-search-text { |
| | | font-size: 26rpx; |
| | | color: $u-tips-color; |
| | | margin-left: 10rpx; |
| | | } |
| | | |
| | | .u-tab-view { |
| | | width: 200rpx; |
| | | height: 100%; |
| | | } |
| | | |
| | | .u-tab-item { |
| | | height: 110rpx; |
| | | background: #f6f6f6; |
| | | box-sizing: border-box; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 26rpx; |
| | | color: #444; |
| | | font-weight: 400; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .u-tab-item-active { |
| | | position: relative; |
| | | color: #000; |
| | | font-size: 30rpx; |
| | | font-weight: 600; |
| | | background: #fff; |
| | | } |
| | | |
| | | .u-tab-item-active::before { |
| | | content: ""; |
| | | position: absolute; |
| | | border-left: 4px solid $u-primary; |
| | | height: 32rpx; |
| | | left: 0; |
| | | top: 39rpx; |
| | | } |
| | | |
| | | .u-tab-view { |
| | | height: 100%; |
| | | } |
| | | |
| | | .right-box { |
| | | background-color: rgb(250, 250, 250); |
| | | } |
| | | |
| | | .page-view { |
| | | padding: 16rpx; |
| | | } |
| | | |
| | | .class-item { |
| | | margin-bottom: 30rpx; |
| | | background-color: #fff; |
| | | padding: 16rpx; |
| | | border-radius: 8rpx; |
| | | } |
| | | |
| | | .class-item:last-child { |
| | | min-height: 100vh; |
| | | } |
| | | |
| | | .item-title { |
| | | font-size: 26rpx; |
| | | color: $u-main-color; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .item-menu-name { |
| | | font-weight: normal; |
| | | font-size: 24rpx; |
| | | color: $u-main-color; |
| | | } |
| | | |
| | | .item-container { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | // å®ä¹é¡¹ç®å°ºå¯¸åé |
| | | $item-size: 100rpx; |
| | | |
| | | .thumb-box { |
| | | width: 33.333333%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | flex-direction: column; |
| | | margin-top: 20rpx; |
| | | padding: 10rpx; |
| | | box-sizing: border-box; |
| | | |
| | | // 使å¾ç容å¨ç宽é«é½åºäºitem-sizeåé |
| | | .item-menu-image { |
| | | width: $item-size; |
| | | height: $item-size; |
| | | } |
| | | |
| | | .item-menu-name { |
| | | font-weight: normal; |
| | | font-size: 24rpx; |
| | | color: $u-main-color; |
| | | margin-top: 10rpx; |
| | | text-align: center; |
| | | } |
| | | } |
| | | |
| | | .item-menu-image { |
| | | width: 120rpx; |
| | | height: 120rpx; |
| | | } |
| | | </style> |