左右菜单联动
对于左右菜单联动的需求是很常见的在小程序里,主要表现为:
- 点击左侧的菜单栏,右侧会切换到对应的内容区域
- 滑动右侧的内容,左侧会自动切换到对应的菜单项
主要利用的是 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: [],
//
};
},
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>
点击左侧菜单栏-右侧内容滚动到对应区域:
<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>
滚动右侧内容-左侧菜单栏跟着联动到对应菜单栏项
<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);
<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
<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 2s infinite ease-in-out;
z-index: 1000;
}
.double-bounce2 {
animation-delay: -1s;
}
@keyframes bounce {
0%,
100% {
transform: scale(0);
}
50% {
transform: scale(1);
}
}
</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节点
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>
实际应用
<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
<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);
});
},
};