赞
踩
<template> <view> <scroll-view v-show="isShow" :class="{'fixed':isFixed}" scroll-x="true" class="scroll-view d-flex bg-white position-relative"> <view class="item text-center nowarp" v-for="(item,index) in sources" :key="index" :style="'width:'+(!isScroll?(100/sources.length)+'%':'auto')" :class="{'active font-weight-bold':type==item.type}" @click="clickType(item,index)"> {{item.name}} </view> <!-- 滚动条 --> <view class="line-box" :style="'left:'+activeLeft+'px;'"></view> </scroll-view> <!-- 当固定顶部时,占位元素,高度可修改 --> <view v-show="isFixed && isShow" :style="'height:'+height+'px'"></view> </view> </template> <script> export default { name: "typeTabs", props: { sources: Array, //数据源 isShow: { //是否显示(默认展示,不用可以删除) type: Boolean, default: true }, isFixed: { //是否固定在顶部 type: Boolean, default: false }, isScroll: { //是否可以横向滚动 值false:每项宽度为(100/sources.length)% type: Boolean, //值true: 宽度自适应 default: false }, currentType: { //默认选项,不传为第一项 type: Object, default: () => { {} } } }, data() { return { activeLeft: 0, //滚动条的X值 type: "", //当前选项 height: "44" }; }, mounted: function() { //赋默认值 this.type = this.currentType ? this.currentType.type : this.sources.length ? this.sources[0].type : "" setTimeout(() => { this.$nextTick(() => { this.getActiveElementY(); }) }, 200) if (this.isFixed) { uni.createSelectorQuery().in(this) .select(".fixed") //目标节点 .boundingClientRect((target) => { if (target.height) { this.height = target.height; } }) .exec(); } }, methods: { clickType(item, index, isUpate) { this.type = item.type; setTimeout(() => { if (!isUpate) { this.$emit("clickItem", item, index); } this.$nextTick(() => { this.getActiveElementY(); }) }, 200) }, // 获取当前选项和scroll-view的scrollLeft值计算得出滚动条位置 getActiveElementY(element = '.item.active') { let query = uni.createSelectorQuery().in(this); var promise1 = new Promise((resolve, reject) => { query.select(".scroll-view").fields({ scrollOffset: true }, res => { if (res) { resolve(res.scrollLeft) } resolve(0) }).exec(); }) var promise2 = new Promise((resolve, reject) => { query.select(element).boundingClientRect(async res => { if (res) { resolve(res.left + (res.width / 2)) } resolve(0) }).exec(); }) Promise.all([promise1, promise2].map(item => item.catch(error => ""))).then(res => { var left = 0 res.map(item => { left += item }) this.activeLeft = left; }) } } } </script> <style lang="scss" scoped> .line-box { position: absolute; width: 56rpx; height: 4rpx; // background-color: #2B323C; background-color: #0D6ED9; // bottom: 48rpx; top: 74rpx; transform: translateX(-50%); transition: all 0.1s; } .item { display: inline-block; max-width: 400rpx; padding: 16rpx 40rpx; } .item.active { color: #0D6ED9; } .fixed { position: fixed; top: 0; /* #ifdef H5 */ top: 44px; /* #endif */ left: 0; right: 0; z-index: 2; box-shadow: 0rpx 2rpx 6rpx 0rpx rgba(0, 0, 0, 0.06); } </style>
<typeTabs ref="tabs" :sources="typeList" @clickItem="clickType" :isFixed="true" :isScroll="true"></typeTabs>
methods: { clickType(item) { this.isScroll = true; var _this = this; uni.createSelectorQuery() .select(".container") //对应外层节点 .boundingClientRect((container) => { uni.createSelectorQuery() .select("#" + item.type) //目标节点 .boundingClientRect((target) => { uni.pageScrollTo({ duration: 0, //过渡时间必须为0,uniapp bug,否则运行到手机会报错 scrollTop: target.top - container.top - 44, //滚动到实际距离是元素距离顶部的距离减去最外层盒子的滚动距离 complete: ()=>{ setTimeout(() => { _this.isScroll = false; },100) } }); }) .exec(); }) .exec(); } }
this.$refs.tabs.clickType:调用子组件的方法,第三个值传true,防止页面点击回调多次出发,导致滚动错乱
if (event.scrollTop + 44 >= item.top) {:44可根据实际情况修改,也可以通过节点信息计算
onPageScroll: function(event) { if (this.isScroll || this.timer) return this.timer = setTimeout(() => { this.typeList.map((item, index) => { if (item.isActive) return; if (event.scrollTop + 44 >= item.top) { if (!item.isActive) { item.isActive = true; this.$refs.tabs.clickType(item, index, true); item.isActive = false; } } }) this.timer = null; }, 50) }
onReady:页面加载完成后根据ID获取对应选项在页面中的top值,用于页面滚动时的判断依据
onPageScroll:使用timer节流,防止计算量过大
isScroll: 用于防止选项点击事件与页面滚动事件冲突,如果有其他解决方案可以联系我
<template> <view class="container"> <typeTabs ref="tabs" :sources="typeList" @clickItem="clickType" :isFixed="true" :isScroll="true"></typeTabs> <view class="content-box border-top-24" id="person"> <view class="font-36 font-weight-bold title-box">服务对象</view> <view class="line"> <view class="label-box">头像</view> <view>XXX</view> </view> <view class="line"> <view class="label-box">姓名</view> <view>陈爷爷</view> </view> <view class="line"> <view class="label-box">性别</view> <view>男</view> </view> <view class="line"> <view class="label-box">身份证号</view> <view>234234234234213421X</view> </view> <view class="line"> <view class="label-box">手机号</view> <view>XXXXXX</view> </view> <view class="line"> <view class="label-box">居住地址</view> <view>XXXXXX</view> </view> </view> <view class="content-box border-top-24" id="project"> <view class="font-36 font-weight-bold title-box">服务项目</view> <view class="line"> <view class="label-box">服务项目</view> <view>XXXXXX</view> </view> <view class="line"> <view class="label-box">标准时长</view> <view>XXXXXX</view> </view> <view class="line"> <view class="label-box">服务费用</view> <view>XXXXXX</view> </view> </view> <view class="content-box border-top-24" id="info"> <view class="font-36 font-weight-bold title-box">服务信息</view> <view class="line"> <view class="label-box">开始时间</view> <view>2022年4月6日 10:46</view> </view> <view class="line"> <view class="label-box">开始地址</view> <view>XXXXXX</view> </view> <view class="line"> <view class="label-box">服务时长</view> <view>XXXXXX</view> </view> <view class="line"> <view class="label-box">结束时间</view> <view>2022年4月6日 10:46</view> </view> <view class="line"> <view class="label-box">结束地址</view> <view>XXXXXX</view> </view> </view> <view class="content-box border-top-24" id="video"> <view class="font-36 font-weight-bold title-box border-bottom-2">录音录频</view> <view class="pt-40 d-flex font-24 desc-color"> <view class="operation-box mr-32 d-flex flex-direction-column align-items-center justify-content-center"> <image mode="widthFix" class="icon" src="@/static/images/audio.png" alt=""></image> <view class="mt-16">开始录音</view> </view> <view class="operation-box d-flex flex-direction-column align-items-center justify-content-center"> <image mode="widthFix" class="icon" src="@/static/images/video.png" alt=""></image> <view class="mt-16">开始录频</view> </view> </view> </view> <view class="content-box border-top-24" id="release"> <view class="font-36 font-weight-bold title-box border-bottom-2">服务晒单</view> <view class="pt-40"> <textarea maxlength="-1" class="w-100" placeholder-class="placeholder server" placeholder="说说我的服务感受......" /> </view> </view> </view> </template> <script> import typeTabs from "@/components/typeTabs/typeTabs.vue" export default { data() { return { timer: null, isShow: false, isFixed: false, isScroll: false, form: {}, typeList: [{ type: "person", name: "服务对象" }, { type: "project", name: "服务项目" }, { type: "info", name: "服务信息" }, { type: "video", name: "录音录频" }, { type: "release", name: "服务晒单" }], } }, components: { typeTabs, }, onReady: function() { this.typeList.map(async item => { item.top = await new Promise((reslove, reject) => { uni.createSelectorQuery() .select(".container") //对应外层节点 .boundingClientRect((container) => { uni.createSelectorQuery() .select("#" + item.type) //目标节点 .boundingClientRect((target) => { reslove(target.top) }) .exec(); }) .exec(); }) return item; }) }, onPageScroll: function(event) { if (this.isScroll || this.timer) return this.timer = setTimeout(() => { this.typeList.map((item, index) => { if (item.isActive) return; if (event.scrollTop + 44 >= item.top) { if (!item.isActive) { item.isActive = true; this.$refs.tabs.clickType(item, index, true); item.isActive = false; } } }) this.timer = null; }, 50) }, methods: { clickType(item) { this.isScroll = true; var _this = this; uni.createSelectorQuery() .select(".container") //对应外层节点 .boundingClientRect((container) => { uni.createSelectorQuery() .select("#" + item.type) //目标节点 .boundingClientRect((target) => { uni.pageScrollTo({ duration: 0, //过渡时间必须为0,uniapp bug,否则运行到手机会报错 scrollTop: target.top - container.top - 44, //滚动到实际距离是元素距离顶部的距离减去最外层盒子的滚动距离 complete: ()=>{ setTimeout(() => { _this.isScroll = false; },100) } }); }) .exec(); }) .exec(); } } } </script> <style lang="scss" scoped> .content-box { padding: 0 40rpx; .title-box { padding: 28rpx 0; } .line { display: flex; align-items: center; justify-content: space-between; text-align: right; padding: 28rpx 0; border-top: 2rpx solid #F7F7FE; .head-image { width: 80rpx; height: 80rpx; border-radius: 8rpx; } .label-box { min-width: 160rpx; color: #556172; text-align: left; } .border-left-2 { border-left: 2rpx solid #F7F7FE; } } .operation-box { width: 176rpx; height: 176rpx; background-color: #F6F7FA; border-radius: 8rpx; margin-right: 40rpx; margin-bottom: 40rpx; position: relative; overflow: hidden; image.icon { width: 52rpx; } image.close { width: 32rpx; position: absolute; right: 0; top: 0; } image.img-box { max-width: 100%; } } } .placeholder.server { font-size: 24rpx; } textarea { height: 440rpx; } </style>
代码中出现的样式,如果出现效果异常,可根据class类名意思添加对应样式
<style> .scroll-view{ white-space: nowrap; } .font-weight-bold{ font-weight: bold; } .nowarp { overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; } .font-36 { font-size: 36rpx; } .d-flex { display: flex; } .d-inline-flex { display: inline-flex; } .border-bottom-2 { border-bottom: 2rpx solid $bg-color; } .border-top-24 { border-top: 24rpx solid $bg-color; } .mt-16 { margin-top: 16rpx; } .mr-16{ margin-right: 16rpx; } .mr-32{ margin-right: 32rpx; } .pl-40 { padding-left: 40rpx; } </style>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。