首页 > 其他分享 >uniapp 手摸手实现左右菜单联动

uniapp 手摸手实现左右菜单联动

时间:2024-09-13 08:53:56浏览次数:3  
标签:uniapp 菜单 name data list 菜单栏 let 手摸 true

对于左右菜单联动的需求是很常见的在小程序里,主要表现为:

  • 点击左侧的菜单栏,右侧会切换到对应的内容区域
  • 滑动右侧的内容,左侧会自动切换到对应的菜单项

主要利用的是 scroll-view标签,以及相关的一些API,可参考:uniapp.dcloud.net.cn/api/ui/node… 去获取当前的所有节点集合,再配合 scroll-view 的 scroll-top 属性,使其在点击左侧菜单栏的时候动态赋值右侧scroll-view 的 scroll-top 属性,从而实现点击左侧菜单栏时右侧内容区域进行滚动

基本UI结构:

<template>
  <view class="d-flex border-top border-light-secondary" style="height: 100%; box-sizing: border-box;">
    <!-- 左侧菜单栏 -->
    <scroll-view scroll-y style="flex: 1; height: 100%;" class="border-right border-light-secondary">
      <view class="border-bottom border-light-secondary py-1" hover-class="bg-light-secondary"
        v-for="(item,index) in cate" :key="item.id" @click="changeCate(index)">
        <view class="py-1 font-md text-muted text-center" :class="activeIndex == index ? 'class-active' : ''">
          {{item.name}}
        </view>
      </view>
    </scroll-view>
    <!-- 右侧数据 -->
    <scroll-view scroll-y style="flex: 3.5; height: 100%;">
      <view class="row" v-for="(item,index) in list" :key="index">
        <view class="span-8 text-center py-2" v-for="(item2,index2) in item.list" :key="index2">
          <image :src="item2.src" mode="" style="width: 120upx;height: 120upx;"></image>
          <text class="d-block">{{item2.name}}</text>
        </view>
      </view>
    </scroll-view>
  </view>
</template>

<script>
  export default {
    data() {
      return {
        // 左侧菜单栏当前选中的分类
        activeIndex: 0,
        // 左侧菜单栏分类数据
        cate: [],
        // 右侧内容
        list: [],
        // 
      }
    },
    onl oad() {
      // 模拟左侧菜单栏分类数据
      for (let i = 0; i < 20; i++) {
        this.cate.push({
          name: "分类" + i,
          id: i
        })
      }

      // 模拟右侧内容数据
      for (let i = 0; i < 15; i++) {
        this.list.push({
          list: [{
            src: '/static/images/demo/cate_01.png',
            name: '商品一'
          },
                 {
                   src: '/static/images/demo/cate_02.png',
                   name: '商品一'
                 },
                 {
                   src: '/static/images/demo/cate_06.png',
                   name: '商品一'
                 },
                 {
                   src: '/static/images/demo/cate_05.png',
                   name: '商品一'
                 }
                ]
        })
      }
    },
    methods: {
      // 点击左侧菜单栏,当前选中项高亮--切换
      changeCate(index) {
        this.activeIndex = index;
      },
      // 
    }
  }
</script>

<style lang="scss" scoped>
  .class-active {
    border-left: 8upx solid #FD6801;
    color: #FD6801 !important;
  }
</style>

点击左侧菜单栏-右侧内容滚动到对应区域

思路:

拿到左右两边每个dom距离屏幕顶端的距离(top),放入cate[]和list[]中备用,当我们点击左边导航菜单项时,我们改变右边相同索引dom在竖直方向 向上的滚动距离(其滚动距离就是该索引对应dom距离顶部的距离)

在uniapp的scroll-view组件有个scroll-top属性可以很方便的改变top值。

<template>
  <view class="d-flex border-top border-light-secondary" style="height: 100%; box-sizing: border-box;">
    <!-- 左侧菜单栏 -->
    <scroll-view scroll-y style="flex: 1; height: 100%;" class="border-right border-light-secondary">
      <view class="border-bottom border-light-secondary py-1 left-scroll-item" hover-class="bg-light-secondary"
        v-for="(item,index) in cate" :key="item.id" @click="changeCate(index)">
        <view class="py-1 font-md text-muted text-center" :class="activeIndex == index ? 'class-active' : ''">
          {{item.name}}
        </view>
      </view>
    </scroll-view>

    <!-- 右侧数据 -->
    <scroll-view scroll-y style="flex: 3.5; height: 100%;" :scroll-top="rightScrollTop"
      :scroll-with-animation="true">
      <view class="row right-scroll-item" v-for="(item,index) in list" :key="index">
        <view class="span-8 text-center py-2" v-for="(item2,index2) in item.list" :key="index2">
          <image :src="item2.src" mode="" style="width: 120upx;height: 120upx;"></image>
          <text class="d-block">{{item2.name}}</text>
        </view>
      </view>
    </scroll-view>
  </view>
</template>

<script>
  export default {
    data() {
      return {
        // 左侧菜单栏当前选中的分类
        activeIndex: 0,
        // 左侧菜单栏分类数据
        cate: [],
        // 右侧内容
        list: [],
        // 记录左侧导航里的每一个导航栏距离顶部的距离
        leftDomsTop: [],
        // 记录右侧菜单距离顶部的距离
        rightDomsTop: [],
        // 右侧内容区块滚动的距离
        rightScrollTop: 0,
      }
    },
    // 页面加载中类似于created--获取不到DOM节点
    onl oad() {
      // 模拟右侧内容数据
      this.getData();
    },
    // 页面渲染完成-可获取DOM节点,相当于mounted
    onReady() {
      const query = uni.createSelectorQuery().in(this);
      // 左侧导航栏中的每一个导航栏距离顶部距离
      query.selectAll('.left-scroll-item').boundingClientRect(data => {
        this.leftDomsTop = data.map(v => v.top);
      }).exec();
      // 右侧内容中的每一个距离顶部距离
      query.selectAll('.right-scroll-item').boundingClientRect(data => {
        this.rightDomsTop = data.map(v => v.top);
      }).exec();
    },
    methods: {
      // 获取数据
      getData() {
        // 模拟左侧菜单栏分类数据
        for (let i = 0; i < 20; i++) {
          // 左侧导航
          this.cate.push({
            name: "分类" + i,
            id: i
          })
          // 右侧内容
          this.list.push({
            list: []
          })
          for (let i = 0; i < this.list.length; i++) {
            for (let j = 0; j < 24; j++) {
              this.list[i].list.push({
                src: '/static/images/demo/cate_01.png',
                name: '分类' + i + '-商品' + j
              })
            }
          }
        }
      },
      // 点击左侧菜单栏,当前选中项高亮--切换
      changeCate(index) {
        this.activeIndex = index;
        // 右边内容scroll-view滚动到对应的区块
        this.rightScrollTop = this.rightDomsTop[index];
      },
      // 
    }
  }
</script>

<style lang="scss" scoped>
  .class-active {
    border-left: 8upx solid #FD6801;
    color: #FD6801 !important;
  }
</style>

滚动右侧内容-左侧菜单栏跟着联动到对应菜单栏项

原理

  • 监听右边菜单的滚动事件(@scroll="onRightScroll"),可以拿到右边dom滚动的top值数组,拿到后去匹配左边相同索引的dom,将该索引值赋值给activeIndex即可。
  • 监听左边菜单项activeIndex变化的时候,当左边状态为active的dom的leftDomsTop[activeIndex] + 该dom本身高度(cateItemHeight)> 左边scroll-view的高度(H)+ 其本身的top值(ST)时,我们让该dom节点向上滚动其本身高度距离(cateItemHeight),向上滚动也是同样的道理

<template>
  <view class="d-flex border-top border-light-secondary" style="height: 100%; box-sizing: border-box;">
    <!-- 左侧菜单栏 -->
    <scroll-view scroll-y style="flex: 1; height: 100%;" class="border-right border-light-secondary" id="leftScroll" :scroll-top="leftScrollTop">
      <view class="border-bottom border-light-secondary py-1 left-scroll-item" hover-class="bg-light-secondary"
        v-for="(item,index) in cate" :key="item.id" @click="changeCate(index)">
        <view class="py-1 font-md text-muted text-center" :class="activeIndex == index ? 'class-active' : ''">
          {{item.name}}
        </view>
      </view>
    </scroll-view>

    <!-- 右侧数据 -->
    <scroll-view scroll-y style="flex: 3.5; height: 100%;" :scroll-top="rightScrollTop" :scroll-with-animation="true" @scroll="onRightScroll">
      <view class="row right-scroll-item" v-for="(item,index) in list" :key="index">
        <view class="span-8 text-center py-2" v-for="(item2,index2) in item.list" :key="index2">
          <image :src="item2.src" mode="" style="width: 120upx;height: 120upx;"></image>
          <text class="d-block">{{item2.name}}</text>
        </view>
      </view>
    </scroll-view>
  </view>
</template>

<script>
  export default {
    data() {
      return {
        // 加载效果
        showLoading:true,
        // 左侧菜单栏当前选中的分类
        activeIndex: 0,
        // 左侧菜单栏分类数据
        cate: [],
        // 右侧内容
        list: [],
        // 记录左侧导航里的每一个导航栏距离顶部的距离
        leftDomsTop: [],
        // 记录右侧菜单距离顶部的距离
        rightDomsTop: [],
        // 右侧内容区块滚动的距离
        rightScrollTop: 0,
        leftScrollTop:0,
        cateItemHeight: 0,
      }
    },
    // 页面加载中类似于created--获取不到DOM节点
    onl oad() {
      // 模拟右侧内容数据
      this.getData();
    },
    watch: {
      async activeIndex(newValue, oldValue) {
        // 获取scroll-view高度以及scrollTop
        const query = uni.createSelectorQuery().in(this);
        // 左侧导航栏中的每一个导航栏距离顶部距离
        query.select('#leftScroll').fields({
          size: true,
          scrollOffset: true
        }, data => {
          let H = data.height;
          let ST = data.scrollTop;
          // 下边
          if((this.leftDomsTop[newValue] + this.cateItemHeight) > (H+ST)){
            return this.leftScrollTop = this.leftDomsTop[newValue] + this.cateItemHeight - H;
          }
          // 上边
          if(ST > this.cateItemHeight){
            this.leftScrollTop = this.leftDomsTop[newValue];
          }
        }).exec();
      },
    },
    // 页面渲染完成-可获取DOM节点,相当于mounted
    onReady() {
      const query = uni.createSelectorQuery().in(this);
      // 左侧导航栏中的每一个导航栏距离顶部距离
      query.selectAll('.left-scroll-item').fields({
        size: true,
        rect: true
      }, data => {
        this.leftDomsTop = data.map(v => {
          this.cateItemHeight = v.height;
          return v.top;
        });
      }).exec();
      // 右侧内容中的每一个距离顶部距离
      query.selectAll('.right-scroll-item').boundingClientRect(data => {
        this.rightDomsTop = data.map(v => v.top);
          }).exec();
    },
    methods: {
      // 获取数据
      getData() {
        // 模拟左侧菜单栏分类数据
        for (let i = 0; i < 20; i++) {
          // 左侧导航
          this.cate.push({
            name: "分类" + i,
            id: i
          })
          // 右侧内容
          this.list.push({
            list: []
          })
          for(let i = 0; i < this.list.length; i++){
            for(let j = 0; j < 24; j++){
              this.list[i].list.push({
                  src: '/static/images/demo/cate_01.png',
                  name: '分类'+i+'-商品'+j
                })
            }
          }
        }
      },
      // 点击左侧菜单栏,当前选中项高亮--切换
      changeCate(index) {
        this.activeIndex = index;
        // 右边内容scroll-view滚动到对应的区块
        this.rightScrollTop = this.rightDomsTop[index];
      },
      // 监听右侧内容滚动事件
      async onRightScroll(e) {
        // console.log(e.detail.scrollTop);
        // 匹配当前scrollTop所处的索引
        this.rightDomsTop.forEach((v,k) => {
          if(v < e.detail.scrollTop + 3){
            this.activeIndex = k;
            return false;
          }
        })
      },
      // 
    }
  }
</script>

<style lang="scss" scoped>
  .class-active {
    border-left: 8upx solid #FD6801;
    color: #FD6801 !important;
  }
</style>

优化

对上面的代码进行优化重构--因为有一些代码是重复使用的比如 const query = uni.createSelectorQuery().in(this);

<template>
  <view class="d-flex border-top border-light-secondary" style="height: 100%; box-sizing: border-box;">
    <!-- 左侧菜单栏 -->
    <scroll-view scroll-y style="flex: 1; height: 100%;" class="border-right border-light-secondary" id="leftScroll"
      :scroll-top="leftScrollTop">
      <view class="border-bottom border-light-secondary py-1 left-scroll-item" hover-class="bg-light-secondary"
        v-for="(item,index) in cate" :key="item.id" @click="changeCate(index)">
        <view class="py-1 font-md text-muted text-center" :class="activeIndex == index ? 'class-active' : ''">
          {{item.name}}
        </view>
      </view>
    </scroll-view>

    <!-- 右侧数据 -->
    <scroll-view scroll-y style="flex: 3.5; height: 100%;" :scroll-top="rightScrollTop" :scroll-with-animation="true"
      @scroll="onRightScroll">
      <view class="row right-scroll-item" v-for="(item,index) in list" :key="index">
        <view class="span-8 text-center py-2" v-for="(item2,index2) in item.list" :key="index2">
          <image :src="item2.src" mode="" style="width: 120upx;height: 120upx;"></image>
          <text class="d-block">{{item2.name}}</text>
        </view>
      </view>
    </scroll-view>
  </view>
</template>

<script>
  export default {
    data() {
      return {
        // 加载效果
        showLoading: true,
        // 左侧菜单栏当前选中的分类
        activeIndex: 0,
        // 左侧菜单栏分类数据
        cate: [],
        // 右侧内容
        list: [],
        // 记录左侧导航里的每一个导航栏距离顶部的距离
        leftDomsTop: [],
        // 记录右侧菜单距离顶部的距离
        rightDomsTop: [],
        // 右侧内容区块滚动的距离
        rightScrollTop: 0,
        leftScrollTop: 0,
        cateItemHeight: 0,
      }
    },
    // 页面加载中类似于created--获取不到DOM节点
    onl oad() {
      // 模拟右侧内容数据
      this.getData();
    },
    watch: {
      async activeIndex(newValue, oldValue) {
        // 获取scroll-view高度以及scrollTop
        let data = await this.getElInfo({
          size:true,
          scrollOffset:true
        })
        let H = data.height;
        let ST = data.scrollTop;
        // 下边
        if ((this.leftDomsTop[newValue] + this.cateItemHeight) > (H + ST)) {
          return this.leftScrollTop = this.leftDomsTop[newValue] + this.cateItemHeight - H;
        }
        // 上边
        if (ST > this.cateItemHeight) {
          this.leftScrollTop = this.leftDomsTop[newValue];
        }
      },
    },
    // 页面渲染完成-可获取DOM节点,相当于mounted
    onReady() {
      this.getElInfo({
        all: 'left',
        size: true,
        rect: true
      }).then(data => {
        this.leftDomsTop = data.map(v => {
          this.cateItemHeight = v.height;
          return v.top;
        });
      })

      this.getElInfo({
        all: 'right',
        size: false,
        rect: true
      }).then(data => {
        this.rightDomsTop = data.map(v => v.top);
      })
    },
    methods: {
      // 获取节点信息
      getElInfo(obj = {}) {
        return new Promise((res, rej) => {
          let option = {
            size: obj.size ? true : false,
            rect: obj.rect ? true : false,
            scrollOffset: obj.scrollOffset ? true : false
          };
          const query = uni.createSelectorQuery().in(this);
          let q = obj.all ? query.selectAll(`.${obj.all}-scroll-item`) : query.select('#leftScroll');
          q.fields(option, data => {
            res(data);
          }).exec();
        })
      },
      // 获取数据
      getData() {
        // 模拟左侧菜单栏分类数据
        for (let i = 0; i < 20; i++) {
          // 左侧导航
          this.cate.push({
            name: "分类" + i,
            id: i
          })
          // 右侧内容
          this.list.push({
            list: []
          })
          for (let i = 0; i < this.list.length; i++) {
            for (let j = 0; j < 24; j++) {
              this.list[i].list.push({
                src: '/static/images/demo/cate_01.png',
                name: '分类' + i + '-商品' + j
              })
            }
          }
        }
      },
      // 点击左侧菜单栏,当前选中项高亮--切换
      changeCate(index) {
        this.activeIndex = index;
        // 右边内容scroll-view滚动到对应的区块
        this.rightScrollTop = this.rightDomsTop[index];
      },
      // 监听右侧内容滚动事件
      async onRightScroll(e) {
        // 匹配当前scrollTop所处的索引
        this.rightDomsTop.forEach((v, k) => {
          if (v < e.detail.scrollTop + 3) {
            this.activeIndex = k;
            return false;
          }
        })
      },
      // 
    }
  }
</script>

<style lang="scss" scoped>
  .class-active {
    border-left: 8upx solid #FD6801;
    color: #FD6801 !important;
  }
</style>

给分类页匹配加载动画效果

components/common/loading/loading.vue

<template>
  <view class="position-fixed top-0 left-0 right-0 bottom-0 loading-model"
    v-if="show">
    <view class="spinner">
      <view class="double-bounce1"></view>
      <view class="double-bounce2"></view>
    </view>
  </view>
</template>

<script>
  export default {
    props:{
      show:{
        type:Boolean,
        default:false
      }
    }
  }
</script>

<style scoped>
  .loading-model{
    background: rgba(255, 255, 255, 0.6);
    z-index: 1000;
  }
  .spinner {
    width: 60px;
    height: 60px;

    position: relative;
    margin: 300upx auto;
    z-index: 1000;
  }

  .double-bounce1, .double-bounce2 {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    background-color: #FD6801;
    opacity: 0.6;
    position: absolute;
    top: 0;
    left: 0;
    animation: bounce 2.0s infinite ease-in-out;
    z-index: 1000;
  }

  .double-bounce2 {
    animation-delay: -1.0s;
  }


  @keyframes bounce {
    0%, 100% {
      transform: scale(0.0);
    } 50% {
      transform: scale(1.0);
    }
  }
</style>

main.js

// 引入全局加载动画
import loading from '@/components/common/loading/loading.vue';
Vue.component('loading', loading)
<template>
  <view class="d-flex border-top border-light-secondary" style="height: 100%; box-sizing: border-box;">
    <loading :show="showLoading"></loading>

    <!-- 左侧菜单栏 -->
    <scroll-view scroll-y style="flex: 1; height: 100%;" class="border-right border-light-secondary" id="leftScroll"
      :scroll-top="leftScrollTop">
      <view class="border-bottom border-light-secondary py-1 left-scroll-item" hover-class="bg-light-secondary"
        v-for="(item,index) in cate" :key="item.id" @click="changeCate(index)">
        <view class="py-1 font-md text-muted text-center" :class="activeIndex == index ? 'class-active' : ''">
          {{item.name}}
        </view>
      </view>
    </scroll-view>

    <!-- 右侧数据 -->
    <scroll-view scroll-y style="flex: 3.5; height: 100%;" :scroll-top="rightScrollTop"
      :scroll-with-animation="true" @scroll="onRightScroll">
      <view class="row right-scroll-item" v-for="(item,index) in list" :key="index">
        <view class="span-8 text-center py-2" v-for="(item2,index2) in item.list" :key="index2">
          <image :src="item2.src" mode="" style="width: 120upx;height: 120upx;"></image>
          <text class="d-block">{{item2.name}}</text>
        </view>
      </view>
    </scroll-view>
  </view>
</template>

<script>
  export default {
    data() {
      return {
        // 加载效果
        showLoading: true,

        // 左侧菜单栏当前选中的分类
        activeIndex: 0,
        // 左侧菜单栏分类数据
        cate: [],
        // 右侧内容
        list: [],
        // 记录左侧导航里的每一个导航栏距离顶部的距离
        leftDomsTop: [],
        // 记录右侧菜单距离顶部的距离
        rightDomsTop: [],
        // 右侧内容区块滚动的距离
        rightScrollTop: 0,
        leftScrollTop: 0,
        cateItemHeight: 0,
      }
    },
    // 页面加载中类似于created--获取不到DOM节点
    onl oad() {
      // 模拟右侧内容数据
      this.getData();
    },
    watch: {
      async activeIndex(newValue, oldValue) {
        // 获取scroll-view高度以及scrollTop
        let data = await this.getElInfo({
          size: true,
          scrollOffset: true
        })
        let H = data.height;
        let ST = data.scrollTop;
        // 下边
        if ((this.leftDomsTop[newValue] + this.cateItemHeight) > (H + ST)) {
          return this.leftScrollTop = this.leftDomsTop[newValue] + this.cateItemHeight - H;
        }
        // 上边
        if (ST > this.cateItemHeight) {
          this.leftScrollTop = this.leftDomsTop[newValue];
        }
      },
    },
    // 页面渲染完成-可获取DOM节点,相当于mounted
    onReady() {
      this.getElInfo({
        all: 'left',
        size: true,
        rect: true
      }).then(data => {
        this.leftDomsTop = data.map(v => {
          this.cateItemHeight = v.height;
          return v.top;
        });
      })

      this.getElInfo({
        all: 'right',
        size: false,
        rect: true
      }).then(data => {
        this.rightDomsTop = data.map(v => v.top);
      })
    },
    methods: {
      // 获取节点信息
      getElInfo(obj = {}) {
        return new Promise((res, rej) => {
          let option = {
            size: obj.size ? true : false,
            rect: obj.rect ? true : false,
            scrollOffset: obj.scrollOffset ? true : false
          };
          const query = uni.createSelectorQuery().in(this);
          let q = obj.all ? query.selectAll(`.${obj.all}-scroll-item`) : query.select('#leftScroll');
          q.fields(option, data => {
            res(data);
					}).exec();
				})
			},
			// 获取数据
			getData() {
				// 模拟左侧菜单栏分类数据
				for (let i = 0; i < 20; i++) {
					// 左侧导航
					this.cate.push({
						name: "分类" + i,
						id: i
					})
					this.$nextTick(() => {
						this.showLoading = false;
					})
					// 右侧内容
					this.list.push({
						list: []
					})
					for (let i = 0; i < this.list.length; i++) {
						for (let j = 0; j < 24; j++) {
							this.list[i].list.push({
								src: '/static/images/demo/cate_01.png',
								name: '分类' + i + '-商品' + j
							})
						}
					}
				}
			},
			// 点击左侧菜单栏,当前选中项高亮--切换
			changeCate(index) {
				this.activeIndex = index;
				// 右边内容scroll-view滚动到对应的区块
				this.rightScrollTop = this.rightDomsTop[index];
			},
			// 监听右侧内容滚动事件
			async onRightScroll(e) {
				// 匹配当前scrollTop所处的索引
				this.rightDomsTop.forEach((v, k) => {
					if (v < e.detail.scrollTop + 3) {
						this.activeIndex = k;
						return false;
					}
				})
			},
			// 
		}
	}
</script>

<style lang="scss" scoped>
	.class-active {
		border-left: 8upx solid #FD6801;
		color: #FD6801 !important;
	}
</style>

实际应用

<template>
  <view style="height: 100vh;" class="d-flex flex-column">
    <!-- #ifdef MP -->
    <!-- 自定义导航 -->
    <view class="d-flex a-center" style="height: 90rpx;">
      <!-- 左边 -->
      <view style="width: 85rpx;" class="d-flex a-center j-center">
        <text class="iconfont icon-xiaoxi"></text>
      </view>
      <!-- 中间 -->
      <view class="flex-1 bg-light rounded d-flex a-center text-light-muted" style="height: 65rpx;" @click="openSearch">
        <text class="iconfont icon-sousuo mx-2"></text>
        智能积木
      </view>
      <!-- 右边 -->
      <view style="width: 85rpx;" class="d-flex a-center j-center">
        <text class="iconfont icon-richscan_icon"></text>
      </view>
    </view>
    <!-- #endif -->
    <view class="d-flex border-top border-light-secondary animated fadeIn faster" style="height: 100%;box-sizing: border-box;">

      <loading-plus v-if="beforeReady"></loading-plus>

      <!-- <loading :show="showLoading"></loading> -->

      <scroll-view id="leftScroll" scroll-y style="flex: 1;height: 100%;" 
        class="border-right border-light-secondary" :scroll-top="leftScrollTop">
        <view class="border-bottom border-light-secondary py-1 left-scroll-item"
          hover-class="bg-light-secondary"
          v-for="(item,index) in cate" :key="index"
          @tap="changeCate(index)">
          <view class="py-1 font-md text-muted text-center"
            :class="activeIndex === index ? 'class-active' : ''">
            {{item.name}}</view>
        </view>
      </scroll-view>
      <scroll-view scroll-y style="flex: 3.5;height: 100%;" 
        :scroll-top="rightScrollTop" :scroll-with-animation="true"
        @scroll="onRightScroll">
        <view class="row right-scroll-item" 
          v-for="(item,index) in list" 
          :key="index">
          <view class="span24-8 text-center py-2"
            v-for="(item2,index2) in item.list" :key="index2"
            @click="openDetail(item2)">
            <image :src="item2.cover"
              style="width: 120upx;height: 120upx;"></image>
            <text class="d-block">{{item2.name}}</text>
          </view>
        </view>
      </scroll-view>
    </view>
  </view>
</template>

<script>
  import loading from "@/common/mixin/loading.js"
  export default {
    mixins:[loading],
    data() {
      return {
        showLoading:true,
        // 当前选中的分类
        activeIndex:0,
        cate:[],
        list:[],
        leftDomsTop:[],
        rightDomsTop:[],
        rightScrollTop:0,
        leftScrollTop:0,
        cateItemHeight:0
      }
    },
    watch: {
      async activeIndex(newValue, oldValue) {
        // 获取scroll-view高度,scrollTop
        let data = await this.getElInfo({
          size:true,
          scrollOffset:true
        })
        let H = data.height
        let ST = data.scrollTop
        // 下边
        if ((this.leftDomsTop[newValue]+this.cateItemHeight) > (H+ST) ) {
          return this.leftScrollTop = this.leftDomsTop[newValue]+this.cateItemHeight - H
        }
        // 上边
        if (ST > this.cateItemHeight) {
          this.leftScrollTop = this.leftDomsTop[newValue]
        }
      }
    },
    onl oad() {
      this.getData()
    },
    methods: {
      openSearch(){
        uni.navigateTo({
          url: '../search/search',
        });
      },
      // 获取节点信息
      getElInfo(obj = {}){
                return new Promise((res,rej)=>{
					let option = {
						size:obj.size ? true : false,
						rect:obj.rect ? true : false,
						scrollOffset:obj.scrollOffset ? true : false,
					}
					const query = uni.createSelectorQuery().in(this);
					let q = obj.all ? query.selectAll(`.${obj.all}-scroll-item`):query.select('#leftScroll')
					q.fields(option,data => {
					  res(data)
					}).exec();
				})
			},
			getData(){
				/*
				cate:[{
					name:"分类1"
				},{
					name:"分类2"
				}]
				
				list:[{
					list:[...]
				},{
					list:[...]
				}]
				*/
				this.$H.get('/category/app_category').then(res=>{
					var cate = []
					var list = []
					res.forEach(v=>{
						cate.push({
							id:v.id,
							name:v.name
						})
						list.push({
							list:v.app_category_items
						})
					})
					this.cate = cate
					this.list = list
					this.$nextTick(()=>{
						this.getElInfo({
							all:'left',
							size:true,
							rect:true
						}).then(data=>{
							this.leftDomsTop = data.map(v=>{
								this.cateItemHeight = v.height
								return v.top
							})
						})
						this.getElInfo({
							all:'right',
							size:false,
							rect:true
						}).then(data=>{
							this.rightDomsTop = data.map(v=> v.top)
						})
						this.showLoading = false
					})
				})
			},
			// 点击左边分类
			changeCate(index){
				this.activeIndex = index
				// 右边scroll-view滚动到对应区块
				this.rightScrollTop = this.rightDomsTop[index]
			},
			// 监听右边滚动事件
			async onRightScroll(e){
				// 匹配当前scrollTop所处的索引
				this.rightDomsTop.forEach((v,k)=>{
					if (v < e.detail.scrollTop + 3) {
						this.activeIndex = k
						return false
					}
				})
			},
			// 打开详情页
			openDetail(item){
				/*
				{
					"id":1,
					"name":"新品",
					"cover":"https://res.vmallres.com/pimages/product/6901443331376/428_428_FAF5BBAB67C16D7426B5B1A2A38F9001DED6D011A0EE9977mp.png",
					"category_id":1,
					"goods_id":25,
					"order":50,
					"create_time":"2019-08-17 00:57:12",
					"update_time":"2019-08-17 00:57:12"
				}
				*/
				uni.navigateTo({
					url: '../detail/detail?detail='+JSON.stringify({
						id:item.goods_id,
						title:item.name
					}),
				});
			}
		}
	}
</script>

<style>
.class-active{
	border-left: 8upx solid #FD6801;color: #FD6801!important;
}
</style>

common/mixin/loading-plus.vue

<template>
  <view class="position-fixed top-0 left-0 right-0 bottom-0 bg-white font-md d-flex a-center j-center main-text-color" style="z-index: 10000;">
    加载中...
  </view>
</template>

<script>
</script>

<style>
</style>

common/mixin/loading.js

export default {
  data(){
    return {
      beforeReady:true,
    }
  },
  onReady() {
    this.$nextTick(()=>{
      setTimeout(()=>{
        this.beforeReady = false
      },500)
    })
  },
}

标签:uniapp,菜单,name,data,list,菜单栏,let,手摸,true
From: https://blog.csdn.net/weixin_43285360/article/details/142179133

相关文章

  • WPF树形菜单
    WPF保姆级教程怎么实现一个树形菜单 先看一下效果吧:   我们直接通过改造一下原版的TreeView来实现上面这个效果我们先创建一个普通的TreeView代码很简单:<TreeView><TreeViewItemHeader="人事部"/><TreeViewItemHeader="技......
  • 基于SpringBoot+Vue+uniapp的农产品质量安全检测网站(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 基于SpringBoot+Vue+uniapp的物资物流系统(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 基于SpringBoot+Vue+uniapp的敏捷工贸公司销售管理系统(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • 基于SpringBoot+Vue+uniapp的社区团购网站(源码+lw+部署文档+讲解等)
    文章目录前言详细视频演示具体实现截图技术栈后端框架SpringBoot前端框架Vue持久层框架MyBaitsPlus系统测试系统测试目的系统功能测试系统测试结论为什么选择我代码参考数据库参考源码获取前言......
  • uniapp vue3使用crypto-js加密解密
    开启crypto-js加密解密的研究历程如何查看crypto-js的版本号?检查crypto-js是否正常我是这样认为的Nativecryptomodulecouldnotbeusedtogetsecurerandomnumber.本机加密模块无法用于获取安全随机数。PC端调试好好的,然后在微信小程序,安卓模拟器,真机调试就......
  • uniapp调安卓原生功能实现蓝牙通信
    uniapp开发的APP要接入三方厂商SDK功能,通过蓝牙连接控制手持设备。我采用uniapp调用原生aar包接口的方式。方式一是调用uni接口我们看官网是有提供APIuniapp.dcloud.net.cn/api/system/…使用例子很多,如blog.csdn.net/weixin_4710…markdown代码解读复制代码**初始......
  • uniapp使用路由名称跳转
    由于web端和app公用一套菜单,而两个项目的路径是不同的,为解决这个问题,封装了一套使用路由名称作为跳转路由的方法1.在pages.json文件里pages对应的页面配置里添加routeName字段(自定义),我做的app里面的菜单是后台获取的,所以这里的value值对应的是后台返回的页面路由 2.开始封......
  • 种草分享|动态朋友圈|瀑布流|uniapp​ V1.0.7
    种草分享评论点赞消息提醒系统,发布动态,分享种草生活,可以收藏关注点赞,消息提醒,同时支持H5/小程序/app多端。V1.0.7修复前端显示问题修复一处三方登录变量赋值问题。......
  • uniapp js 划消小游戏 2.0
    1.效果图  代码:game.vue  <template> <viewclass="wrap">  <!--<imgclass="imgBG"src="@/static/image/hxBG.png"alt=""/>-->  <imageclass="imgBG"mode="aspectFill&q......