这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助
对于左右菜单联动的需求是很常见的在小程序里,主要表现为:
- 点击左侧的菜单栏,右侧会切换到对应的内容区域
- 滑动右侧的内容,左侧会自动切换到对应的菜单项
主要利用的是 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节点 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节点 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) }) }, }