记录--左右菜单联动
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
对于左右菜单联动的需求是很常见的在小程序里,主要表现为:
- 点击左侧的菜单栏,右侧会切换到对应的内容区域
- 滑动右侧的内容,左侧会自动切换到对应的菜单项
主要利用的是 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) }) }, } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)