记录--左右菜单联动

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

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

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

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

基本UI结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<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: [],
                //
            }
        },
        onLoad() {
            // 模拟左侧菜单栏分类数据
            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>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<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节点
        onLoad() {
            // 模拟右侧内容数据
            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>

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<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节点
    onLoad() {
      // 模拟右侧内容数据
      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);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
<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节点
    onLoad() {
      // 模拟右侧内容数据
      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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<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

1
2
3
// 引入全局加载动画
import loading from '@/components/common/loading/loading.vue';
Vue.component('loading', loading)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
<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节点
        onLoad() {
            // 模拟右侧内容数据
            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>

实际应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
<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]
                }
            }
        },
        onLoad() {
            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

1
2
3
4
5
6
7
8
9
10
11
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default {
    data(){
        return {
            beforeReady:true,
        }
    },
    onReady() {
        this.$nextTick(()=>{
            setTimeout(()=>{
                this.beforeReady = false
            },500)
        })
    },
}

本文转载于:

https://juejin.cn/post/7295310885635719195

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

posted @   林恒  阅读(381)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)
欢迎阅读『记录--左右菜单联动』
点击右上角即可分享
微信分享提示