index2.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. <template>
  2. <view class="u-wrap">
  3. <view class="u-search-box">
  4. <view class="u-search-inner">
  5. <u-icon name="search" color="#909399" :size="28"></u-icon>
  6. <text class="u-search-text">搜索</text>
  7. </view>
  8. </view>
  9. <view class="u-menu-wrap">
  10. <scroll-view scroll-y scroll-with-animation class="u-tab-view menu-scroll-view" :scroll-top="scrollTop"
  11. :scroll-into-view="itemId">
  12. <view v-for="(item, index) in tabbar" :key="index" class="u-tab-item"
  13. :class="[current == index ? 'u-tab-item-active' : '']" @tap.stop="swichMenu(index)">
  14. <text class="u-line-1">{{ item.name }}</text>
  15. </view>
  16. </scroll-view>
  17. <scroll-view :scroll-top="scrollRightTop" scroll-y scroll-with-animation class="right-box"
  18. @scroll="rightScroll">
  19. <view class="page-view">
  20. <view class="class-item" :id="'item' + index" v-for="(item, index) in tabbar" :key="index">
  21. <view class="item-title">
  22. <text>{{ item.name }}</text>
  23. </view>
  24. <view class="item-container">
  25. <view class="thumb-box" v-for="(item1, index1) in item.foods" :key="index1"
  26. @click="clickMenu(item1)">
  27. <image class="item-menu-image" :src="item1.icon" mode=""></image>
  28. <view class="item-menu-name">{{ item1.name }}</view>
  29. </view>
  30. </view>
  31. </view>
  32. </view>
  33. </scroll-view>
  34. </view>
  35. </view>
  36. </template>
  37. <script>
  38. import classifyData from '@/pages_template/common/classify.data.js';
  39. export default {
  40. data() {
  41. return {
  42. scrollTop: 0, //tab标题的滚动条位置
  43. oldScrollTop: 0,
  44. current: 0, // 预设当前项的值
  45. menuHeight: 0, // 左边菜单的高度
  46. menuItemHeight: 0, // 左边菜单item的高度
  47. itemId: '', // 栏目右边scroll-view用于滚动的id
  48. tabbar: classifyData,
  49. arr: [],
  50. scrollRightTop: 0, // 右边栏目scroll-view的滚动条高度
  51. timer: null, // 定时器
  52. }
  53. },
  54. onReady() {
  55. this.getMenuItemTop()
  56. },
  57. methods: {
  58. // 点击左边的栏目切换
  59. async swichMenu(index) {
  60. if (this.arr.length == 0) {
  61. await this.getMenuItemTop();
  62. }
  63. if (index == this.current) return;
  64. this.scrollRightTop = this.oldScrollTop;
  65. this.$nextTick(() => {
  66. this.scrollRightTop = this.arr[index];
  67. this.current = index;
  68. this.leftMenuStatus(index);
  69. })
  70. },
  71. // 获取一个目标元素的高度
  72. getElRect(elClass, dataVal) {
  73. new Promise((resolve, reject) => {
  74. const query = uni.createSelectorQuery().in(this);
  75. query.select('.' + elClass).fields({
  76. size: true
  77. }, res => {
  78. // 如果节点尚未生成,res值为null,循环调用执行
  79. if (!res) {
  80. setTimeout(() => {
  81. this.getElRect(elClass);
  82. }, 10);
  83. return;
  84. }
  85. this[dataVal] = res.height;
  86. resolve();
  87. }).exec();
  88. })
  89. },
  90. // 观测元素相交状态
  91. async observer() {
  92. this.tabbar.map((val, index) => {
  93. let observer = uni.createIntersectionObserver(this);
  94. // 检测右边scroll-view的id为itemxx的元素与right-box的相交状态
  95. // 如果跟.right-box底部相交,就动态设置左边栏目的活动状态
  96. observer.relativeTo('.right-box', {
  97. top: 0
  98. }).observe('#item' + index, res => {
  99. if (res.intersectionRatio > 0) {
  100. let id = res.id.substring(4);
  101. this.leftMenuStatus(id);
  102. }
  103. })
  104. })
  105. },
  106. // 设置左边菜单的滚动状态
  107. async leftMenuStatus(index) {
  108. this.current = index;
  109. // 如果为0,意味着尚未初始化
  110. if (this.menuHeight == 0 || this.menuItemHeight == 0) {
  111. await this.getElRect('menu-scroll-view', 'menuHeight');
  112. await this.getElRect('u-tab-item', 'menuItemHeight');
  113. }
  114. // 将菜单活动item垂直居中
  115. this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2;
  116. },
  117. // 获取右边菜单每个item到顶部的距离
  118. getMenuItemTop() {
  119. new Promise(resolve => {
  120. let selectorQuery = uni.createSelectorQuery();
  121. selectorQuery.selectAll('.class-item').boundingClientRect((rects) => {
  122. // 如果节点尚未生成,rects值为[](因为用selectAll,所以返回的是数组),循环调用执行
  123. if (!rects.length) {
  124. setTimeout(() => {
  125. this.getMenuItemTop();
  126. }, 10);
  127. return;
  128. }
  129. rects.forEach((rect) => {
  130. // 这里减去rects[0].top,是因为第一项顶部可能不是贴到导航栏(比如有个搜索框的情况)
  131. this.arr.push(rect.top - rects[0].top);
  132. resolve();
  133. })
  134. }).exec()
  135. })
  136. },
  137. // 右边菜单滚动
  138. async rightScroll(e) {
  139. this.oldScrollTop = e.detail.scrollTop;
  140. if (this.arr.length == 0) {
  141. await this.getMenuItemTop();
  142. }
  143. if (this.timer) return;
  144. if (!this.menuHeight) {
  145. await this.getElRect('menu-scroll-view', 'menuHeight');
  146. }
  147. setTimeout(() => { // 节流
  148. this.timer = null;
  149. // scrollHeight为右边菜单垂直中点位置
  150. let scrollHeight = e.detail.scrollTop + this.menuHeight / 2;
  151. for (let i = 0; i < this.arr.length; i++) {
  152. let height1 = this.arr[i];
  153. let height2 = this.arr[i + 1];
  154. // 如果不存在height2,意味着数据循环已经到了最后一个,设置左边菜单为最后一项即可
  155. if (!height2 || scrollHeight >= height1 && scrollHeight < height2) {
  156. this.leftMenuStatus(i);
  157. return;
  158. }
  159. }
  160. }, 10)
  161. },
  162. clickMenu(menu) {
  163. console.log(menu);
  164. }
  165. }
  166. }
  167. </script>
  168. <style lang="scss" scoped>
  169. .u-wrap {
  170. height: calc(100vh);
  171. /* #ifdef H5 */
  172. height: calc(100vh - var(--window-top));
  173. /* #endif */
  174. display: flex;
  175. flex-direction: column;
  176. }
  177. .u-search-box {
  178. padding: 18rpx 30rpx;
  179. }
  180. .u-menu-wrap {
  181. flex: 1;
  182. display: flex;
  183. overflow: hidden;
  184. }
  185. .u-search-inner {
  186. background-color: rgb(234, 234, 234);
  187. border-radius: 100rpx;
  188. display: flex;
  189. align-items: center;
  190. padding: 10rpx 16rpx;
  191. }
  192. .u-search-text {
  193. font-size: 26rpx;
  194. color: $u-tips-color;
  195. margin-left: 10rpx;
  196. }
  197. .u-tab-view {
  198. width: 200rpx;
  199. height: 100%;
  200. }
  201. .u-tab-item {
  202. height: 110rpx;
  203. background: #f6f6f6;
  204. box-sizing: border-box;
  205. display: flex;
  206. align-items: center;
  207. justify-content: center;
  208. font-size: 26rpx;
  209. color: #444;
  210. font-weight: 400;
  211. line-height: 1;
  212. }
  213. .u-tab-item-active {
  214. position: relative;
  215. color: #000;
  216. font-size: 30rpx;
  217. font-weight: 600;
  218. background: #fff;
  219. }
  220. .u-tab-item-active::before {
  221. content: "";
  222. position: absolute;
  223. border-left: 4px solid $u-primary;
  224. height: 32rpx;
  225. left: 0;
  226. top: 39rpx;
  227. }
  228. .u-tab-view {
  229. height: 100%;
  230. }
  231. .right-box {
  232. background-color: rgb(250, 250, 250);
  233. }
  234. .page-view {
  235. padding: 16rpx;
  236. }
  237. .class-item {
  238. margin-bottom: 30rpx;
  239. background-color: #fff;
  240. padding: 16rpx;
  241. border-radius: 8rpx;
  242. }
  243. .class-item:last-child {
  244. min-height: 100vh;
  245. }
  246. .item-title {
  247. font-size: 26rpx;
  248. color: $u-main-color;
  249. font-weight: bold;
  250. }
  251. .item-menu-name {
  252. font-weight: normal;
  253. font-size: 24rpx;
  254. color: $u-main-color;
  255. }
  256. .item-container {
  257. display: flex;
  258. flex-wrap: wrap;
  259. }
  260. .thumb-box {
  261. width: 33.333333%;
  262. display: flex;
  263. align-items: center;
  264. justify-content: center;
  265. flex-direction: column;
  266. margin-top: 20rpx;
  267. }
  268. .item-menu-image {
  269. width: 120rpx;
  270. height: 120rpx;
  271. }
  272. </style>