uniapp 实现点击Tab自动定位锚点,滚动页面,自动切换Tab,自动吸顶,支持H5、小程序、app

896 阅读4分钟

需求:点击Tab实现自动定位到对应的内容高度,页面滚动时候,tab自定切换,自动吸顶功能,废话不多说,直接上代码。

image.png

image.png

<template>
	<view class="full-container">
		 <u-navbar></u-navbar>
		<view class="swiper-box hight-swiper-box">
			<u-swiper @click="onSwiper" :list="detailContent.miediaList" name="mediaPath" border-radius="0" mode="rect" indicator-pos="bottomRight" img-mode="aspectFill"></u-swiper>
		</view>
		<view class="main-box">
			<view class="scienc-info">
				<view class="desc ellipsis">{{ detailContent.routeName }}</view>
				<view class="tip two-ellipsis">{{ detailContent.description }}</view>
				<view class="extension-vontainer" @click="getExtension">
					<view class="flex">
						<image class="notice-icon" :src="$IMG + '/images/notice-icon.png'"></image>
						<view class="common-tip puoduct">推广计划产品</view>
						<view class="common-tip plan">本产品已参与推广计划</view>
						</view>
					<image class="two-right-icon" :src="$IMG + '/images/two-right-icon.png'"></image>
				</view>
				<view class="price-box">
					<view class="money">	
						<view class="hot-flex">
							<view class="symbol"></view>
							<view class="money">{{ detailContent.price }}</view>
							<view class="text">起/人</view>
						</view>
					</view>
					<view class="person" v-if="detailContent.joinNumber > 0">{{ detailContent.joinNumber }}人参加</view>
				</view>  
				<view class="operate-box">
					<view class="flex">
						<view class="flex">
							<image class="icon-img" src="/static/images/adress-icon.png"></image>
							<view class="text">{{ detailContent.destination }}</view>
						</view>
						<view class="flex">
							<image class="icon-img" src="/static/images/time-icon.png"></image>
							<view class="text">{{ detailContent.days }}天</view>
						</view>
						<view class="flex">
							<image class="icon-img" src="/static/images/person-icon.png"></image>
							<view class="text">{{ detailContent.groupNumber }} 人</view>
						</view>
					</view>
					<view class="flex" @click="getShowShare">
						<image class="icon-img share-icon" src="/static/images/share.png"></image>
						<view class="text">分享</view>
					</view>
				</view>
			</view>
	<!-- 		<view class="package-info common-box">
				<view class="nav-tit">
					<text class="tit">套餐信息</text>
					<text class="desc">(进入拼团后再选择)</text>
				</view>
				<view class="flex-container">
					<view class="package-list" v-for="(item, index) in detailContent.packageList" :key="index">
						<text class="name" v-if="!item.tip">{{ item.name }}:¥{{ item.price }}</text>
						<text class="name" v-else>{{ item.name }}({{ item.tip }}):¥{{ item.price }}</text>
					</view>
				</view>
			</view> -->
			<view class="set-out-time common-box">
				<view class="nav-tit">出发日期</view>
				<ticketInfo v-if="showStatus" :dateVoList="dateVoList" :classSyle="classSyle" :pageType="pageType" @getSelectPackage="getSelectPackage"></ticketInfo>
				
			</view>
			<view class="aggregate-info common-box">
				<view class="nav-tit">集合信息</view>
				<view class="list-item" v-for="(item, index) in detailContent.rendezvousList" :key="index">
					</block>
					<view class="default-icon">
						<image class="icon-img" src="/static/images/default-icon.png"></image>
						<text class="icon-index">{{ index + 1}}</text>
					</view>
					<text class="text-desc">{{ item }}</text>
				</view>
			</view>
			<view v-if="detailContent.joinNumber > 0" class="person-info common-box">
				<view class="nav-tit flex" @click="getMorePerson">
					<view class="left">已参加的人</view>
					<view class="flex">
						<view class="number">{{ detailContent.joinNumber }}</view>
						<view class="person"></view>
						<image class="more-arr-img" src="/static/images/more-right.png"></image>
					</view>
				</view>
				<view class="recommend-scroll">
					<scroll-view class="wrap-scroller" scroll-x="true">
						<view class="list-item" v-for="(item, index) in detailContent.personVos" :key="index">
							<image class="avatar-z" :src="item.avatar" mode="aspectFill"></image>
							<view class="name"> {{ item.customerName }} </view>
						</view>
					</scroll-view>
				</view>
			</view>
			
			<view class="bottom-operate">
				<view class="flex">
					<view class="ico-box home-icon" @click.stop="getHome">
						<image class="icon-img" src="/static/images/detail-home.png"></image>
						<view class="text">首页</view>
					</view>
					<view class="ico-box">
						<button open-type="contact" hover-class='none' class='item tui-skeleton-rect' >
							<image class="icon-img" src="/static/images/customer-service.png"></image>
						</button>
						<view class="text">客服</view>
					</view>
				</view>
				<view class="btn-customer-bg" @click="getJoinGroup">加入该团</view>
			</view>
			<lf-popup v-model="showPersonPopup" class="customer-popup">
				<div class="titile-box" @click="onClose">
					<view></view>
					<view class="popup-tit">人员名单</view>
					<image src="/static/images/login-close.png"></image>
				</div>
				<view class="sign-up">已报名</view>
				<view class="list-box">
					<view class="list-item" v-for="(item, index) in detailContent.personVos" :key="index">
						<image class="avatar-z" :src="item.avatar" mode="aspectFill"></image>
						<view class="name"> {{ item.customerName }} </view>
					</view>
				</view>
				
				<view class="bottom">只显示部分报名用户信息</view>
			</lf-popup>
			<!-- 分享选择弹窗 -->
			<lf-popup v-model="showShare" height="240rpx">
				<view class="share-wrap" @click="closeShare">
					<!-- #ifdef MP-WEIXIN -->
					<button open-type="share" class="share-item u-reset-button">
						<image style="width: 86rpx; height: 86rpx; margin-top: 1rpx;" src="/static/wechat.png"></image>
						<text>微信好友</text>
					</button>
					<!-- #endif -->
					<!-- #ifdef H5 -->
					<view class="share-item2" @click="copyPageUrl">
						<image src="/static/images/share.png"></image>
						<text>分享链接</text>
					</view>
					<!-- #endif -->
					<!-- #ifdef H5 || MP-WEIXIN -->
					<view class="share-item" @click="shareCanvas" style="margin-top: -7rpx;">
						<image src="/static/images/share2.png"></image>
						<text style="margin-top: 8rpx;">分享海报</text>
					</view>
					<!-- #endif -->
				</view>
			</lf-popup>
			<toast v-if="loadingFlag" color="#fff" type="rotate3"></toast>
		</view>
		
		<view class="tab-content">
			<Anchor
			:tabList="tabs"
			:barFixed="true"
			:transitionShow="true"
			barHeight="50"
			:barTop="barTop"
			barId="0"
			@clickTabs="clickTabs"
			ref="barTabNav"
			>
				<!-- id与数据tabs->navTarget对应 -->
				<view id="item1">
					<view class="box">
						<mp-html :content="detailContent.routeExtendList[0].details" selectable="true" />
					</view>
					
				</view>
				<view id="item2">
					<view class="box">
						<mp-html :content="detailContent.routeExtendList[1].details" selectable="true" />
					</view>
				</view>
				<view id="item3">
					<view class="box">
						<mp-html :content="detailContent.routeExtendList[2].details" selectable="true" />
					</view>
				</view>
				<view id="item4">
					<view class="box">
						<mp-html :content="detailContent.routeExtendList[3].details" selectable="true" />
					</view>
					<!-- 热门推荐-->
					<group-buying ref="groupBuying" :pageType="pageType" :tittle="groupTittle" :classStyle="classStyle"></group-buying>
				</view>
			</Anchor>
		</view>
		<lf-back-top :show-tag="showTag"></lf-back-top>
		<!-- 生成海报组件 -->
		<poster v-if="showCanvas" ref="posterRef" :configData="configData" @close="closePopup"/>
		<phone-login ref="phoneLogin"></phone-login>
		<extension-popup ref="extensionPopup" @getShare="getShare"></extension-popup>
	</view>
</template>

<script>
import poster from "./poster.vue";
import Anchor from './anchor.vue'
import extensionPopup from './components/extension-popup.vue'
import ticketInfo from '@/subpages/content/components/ticketInfo/index.vue'
export default {
	components:{ 
		poster,
		Anchor,
		ticketInfo,
		extensionPopup
	},
	data() {
		return {
			tabs: [
				{
					text: "详情亮点",//名称
					navTarget: "#item1",//锚点
					value: 'one'
				},
				{
					text: "行程准备",
					navTarget: "#item2",
					value: 'two'
				},
				{
					text: "费用须知",
					navTarget: "#item3",
					value: 'three'
				},
				{
					text: "注意事项",
					navTarget: "#item4",
					value: 'four'
				}
			],
			$IMG: this.$IMG,
			configData: {},
			posterUrl: '',
			showTag: false,
			current: 'one',
			show: false,
			show: true,
			tabCount: '',
			dateVoList: [],
			goTimeOpenId: '',
			showShare: false,
			barTop: 0,
			postId: 0,
			page: 1,
			form: {
				pid: 0,
				type: 1,
				toUid: '',
				toCid: 0,
				postId: '',
				content: ''
			},
			showCanvas: false,
			showStatus: false,
			classSyle: 'buy-ticket',
			postDetail: {},
			commentList: [],
			groupTittle: '热门推荐',
			postingUser: {},
			pageType: 'buyTicket',
			loadingFlag: true,
			detailContent: {
				days: 0,
				price: 0,
				groupNumber: 0,
				routeName: '',
				joinNumber: 0,
				description: '',
				destination: ''
			},
			classStyle: 'buy-ticket',
			tabList: [
				{	name: '详情亮点'	, id: '0' }, 
				{	name: '行程准备'	, id: '1' }, 
				{	name: '费用须知'	, id: '2' }, 
				{	name: '注意事项'	, id: '3' }
			],
			clickFlag: false,
			myScroll: 0,
			currentItem: 0,
			currentTabs: 0,
			showPersonPopup: false,
			shareCover: '',
		}
	},
	
	onShareAppMessage(res) {
		let imgURL = this.shareCover;
		if (this.detailContent.miediaList.length > 0) {
			imgURL = this.detailContent.miediaList[0].mediaPath
		}
		let customerPushCode = uni.getStorageSync("userInfo").pushCode || ''
		return {
			title: this.detailContent.routeName ? this.detailContent.routeName : this.$c.miniappName,
			path: `/subpages/content/buyTicket/index?routeId=${this.postId}&pushCode=${customerPushCode}`,
			imageUrl: imgURL
		};
	},
	onShareTimeline(res) {
		let imgURL = this.shareCover;
		if (this.detailContent.miediaList.length > 0) {
			imgURL = this.detailContent.miediaList[0].mediaPath
		}
		let customerPushCode = uni.getStorageSync("userInfo").pushCode || ''
		return {
			title: this.detailContent.routeName ? this.detailContent.routeName : this.$c.miniappName,
			query: `routeId=${this.postId}&pushCode=${customerPushCode}`,
			imageUrl: imgURL
		};
	},
	onShow() {
		uni.removeStorageSync('getDetailFlag')
	},
	onLoad(options) {
		if ('q' in options) {
		const q = decodeURIComponent(options.q);
			const querys = q
			.split('?')[1]
			.split('&')
			.reduce((acc, it) => {
				let r = it.split(/=/);
				return Object.assign(acc, {
				[r[0]]: r[1]
				})
			}, {});
			if ('routeId' in querys) {
				this.postId = querys.routeId.trim();
			}
		}else{
			this.postId = options.routeId.trim();
		}
		this.form.postId = this.postId;
		this.loadingFlag = true
		this.$loading(true)
		this.getInit()
		this.getSysInfo();
		// this.getCommentList()
		uni.setStorageSync('clickTabFlag', false)
		uni.setStorageSync('scrollBtttom', false)
		uni.setStorageSync('clickTabIndex', '0')
		uni.getSystemInfo({
			success: (res) => {
				this.barTop = (res.statusBarHeight) + (res.system.indexOf('iOS') > -1 ? 44 : 48)
			}
		})
	
		this.postingUser = uni.getStorageSync('userInfo')
		//#ifdef MP-WEIXIN
		wx.showShareMenu({
			withShareTicket: true,
			menus: ['shareAppMessage', 'shareTimeline']
		});
		//#endif
	},
	// 1. 点击并且滚动页面,采用点击切换Tab
	onPageScroll(e) {
		this.showTag = e.scrollTop > 750
		uni.setStorageSync('scrollPageFlag', true)
	  this.$refs.barTabNav.getSelectedTab(e.scrollTop, 'scrollPage');
	},
	onReachBottom() {
		uni.setStorageSync('scrollBtttom', true)
		this.$refs.groupBuying.getReachBottom()
		this.$refs.barTabNav.getSelectedTab();
	},
	onPullDownRefresh() {
		setTimeout(() => {
			uni.stopPullDownRefresh();
		}, 500)
	},
	methods: {
		getSysInfo() {
				this.$H.get('system/basic').then(res => {
					this.shareCover = res.shareImg;
				});
			},
		// 获取二维码路径参数
			GetWxMiniProgramUrlParam(url) {
				let theRequest = {};
				if (url.indexOf("#") != -1) {
					const str = url.split("#")[1];
					const strs = str.split("&");
					for (let i = 0; i < strs.length; i++) {
						theRequest[strs[i].split("=")[0]] = decodeURI(strs[i].split("=")[1]);
					}
				} else if (url.indexOf("?") != -1) {
					const str = url.split("?")[1];
					const strs = str.split("&");
					for (let i = 0; i < strs.length; i++) {
						theRequest[strs[i].split("=")[0]] = decodeURI(strs[i].split("=")[1]);
					}
				}
				return theRequest;
			},
		onSwiper(index) {
			let arr = []
			this.detailContent.miediaList.forEach((item) => {
				arr.push(item.mediaPath)
			})
			let currnetImg = this.detailContent.miediaList[index].mediaPath
			uni.previewImage({
				 urls: arr, //预览图片列表
			  current: currnetImg //图片路径
			})
		},
		clickTabs(row, status) {
			console.log(row, '当前点击row')
			// 点击Tab标识
			this.clickFlag = status
			// 避免重复点击
			if(this.current == row.value) return false;
			// 操作处理
			this.current = row.value
		},
		itemChange(id) {
				this.currentItem = id;
			},
			getInit() {
				this.$H.get('/business/app/tripTourRoute/info/' + this.postId).then(res => {
					if (res.code  == 0) {
						this.detailContent = res.result
						if(res.result.personVos !== null) {
							if (res.result.personVos.length > 0) {
								res.result.personVos.forEach((item) => {
									item.customerName = item.nickname.slice(0, 1) + '**'
								})
							}
						}
						this.dateVoList = res.result.goDateVoList
						this.tabCount = this.detailContent.routeExtendList[0].details
						this.showStatus = true
						this.loadingFlag = false
					}
				})
			},
			tabChange(index) {
				this.currentTabs = index;
				this.tabCount = this.detailContent.routeExtendList[index].details
			},
			getMorePerson() {
				this.showPersonPopup = true
			},
			onClose() {
				this.showPersonPopup = false
			},
			getHome() {
				uni.reLaunch({
					url: '/pages/index/index'
				})
			},
			getSelectPackage(data) {
				this.goTimeOpenId = data.openId
				// 清除出行人信息
				uni.setStorageSync('travelersAddFlag', false)
				uni.setStorageSync('finallTravelersUserIds', [])
				uni.setStorageSync('defaultArrData', [])
				uni.setStorageSync('OriginExistenceIds', [])
			},
			getJoinGroup() {
				if (!uni.getStorageSync('hasLogin')) {
					setTimeout(() => {
						uni.showModal({
						  title: '提示',
						  content: '您还未登录,登录体验更多精彩',
						  confirmText: '去登录',
						  cancelText: '随便逛逛',
						  success: (res) => {
						    if (res.confirm) {
						      this.$refs.phoneLogin.getShow()
						    } else if (res.cancel) {
						      console.log('用户点击取消');
						    }
						  }
						});
					}, 500)
				} else {
					if (this.goTimeOpenId) {
						uni.setStorageSync('currentOpenId', this.goTimeOpenId)
						uni.setStorageSync('resortArr', this.detailContent.rendezvousList)
						uni.navigateTo({
							url: `/subpages/content/joinGroup/index?openId=${this.goTimeOpenId}`
						})
					} else {
						this.$u.toast('请选择出发日期');
					}
				}
			},
			getShare() {
				this.showShare = true
			},
			getShowShare() {
				if (!uni.getStorageSync('hasLogin')) {
					setTimeout(() => {
						uni.showModal({
						  title: '提示',
						  content: '您还未登录,登录体验更多精彩',
						  confirmText: '去登录',
						  cancelText: '随便逛逛',
						  success: (res) => {
						    if (res.confirm) {
						      this.$refs.phoneLogin.getShow()
						    } else if (res.cancel) {
						      console.log('用户点击取消');
						    }
						  }
						});
					}, 500)
				} else {
					this.showShare = true
				}
			},
		closeShare() {
			this.showShare = false
		},
			getReplyMore(index) {
				this.commentList[index].copyChildren = this.commentList[index].children
				this.commentList[index].openReplyStatus = false
			},
			// 回复评论
			onReply(e) {
				this.placeholder = '回复:' + e.userInfo.username;
				this.focus = true;
				// toCid: 如果是一级评论传0,如果是针对某一条评论进行回复,传当前评论的id
				this.form.toCid = e.id ? e.id : 0
				// pid: 如果是针对帖子评论传0,如果是针对某一条评论进行回复,传当前评论的id
				this.form.pid = e.pid ? e.pid : e.id
				this.form.toUid = e.userInfo.uid;
				this.form.postId = this.postId;
				this.focusFlag = true
			},
			jumpUser(uid) {
				uni.navigateTo({
					url: '/subpages/childrenPackge/user/home?uid=' + uid
				});
			},
			getCommentList() {
				this.$H.get('comment/list', {
					postId: '1814259461133684738',
					page: this.page
				}).then(res => {
					if (res.code ==0) {
						res.result.data.forEach((item) => {
							if (item.children.length > 1) {
								// item.openReplyStatus = true
								// item.copyChildren = item.children.slice(0, 1)
							} else {
								// item.openReplyStatus = false
								// item.copyChildren = item.children
							}
						})
						this.commentList = this.commentList.concat(res.result.data);
					}
				})
			},
			getExtension() {
				if (!uni.getStorageSync('hasLogin')) {
					setTimeout(() => {
						uni.showModal({
						  title: '提示',
						  content: '您还未登录,登录体验更多精彩',
						  confirmText: '去登录',
						  cancelText: '随便逛逛',
						  success: (res) => {
						    if (res.confirm) {
						      this.$refs.phoneLogin.getShow()
						    } else if (res.cancel) {
						      console.log('用户点击取消');
						    }
						  }
						});
					}, 500)
				} else {
					// 1是推广人
					const userInfo = uni.getStorageSync('userInfo');
					if (userInfo.isPromoter) {
					    if (userInfo.isPromoter === 0) {
					      uni.navigateTo({
					      	url: '/subpages/content/promotion/illustrate'
					      })
					    } else {
					      this.$refs.extensionPopup.getShow(this.postId);
					    }
					} else {
					  uni.navigateTo({
					  	url: '/subpages/content/promotion/illustrate'
					  })
					}
				}
			},
			handleInput() {
				if (this.form.content == '') {
					this.$u.toast('评论不能为空');
					return;
				}
				if (val.detail.cursor == 50) {
					this.$u.toast('内容字数最多为50个字符');
					return;
				}
				uni.showLoading({
					mask: true,
					title: '发布中'
				});
				this.$H.post('post/addComment', this.form).then(res => {
					uni.hideLoading();
					if (res.code == 0) {
						if(res.check){
							uni.showModal({
								title: '提示',
								content: '评论审核通过后发布哦,请耐心等待',
								showCancel: false,
								success: function (res) {
									if (res.confirm) {
									}
								}
							});
						}else{
							this.$u.toast('评论成功');
							this.postDetail.commentCount+=1
						}
						this.form.content = '';
						this.page = 1;
						this.commentList = [];
						this.form.pid = 0;
						// this.getCommentList();
					}else if(res.code==500){
						this.$u.toast(res.msg);
					}
				});
			},
			// 长按 删除评论
			delComment(e, index, index2) {
				let that = this;
				let user = uni.getStorageSync('userInfo');
				//如果是子评论需要遍历子评论查询是否存在自己回复的评论消息
				var flag = false; //这个用来确定父评论下是否存在用户自己的子评论
				var i = 0; //这个用来锁定子评论楼层位置
				e.children.map((item) => {
					if (item.uid == user.uid) {
						flag = true;
						e.id = item.id;
						if (index2) {
							index2 = i;
						}
					}
					i++;
				});
			
				if (e.uid != user.uid) {
					if (!flag) {
						return;
					}
				}
				uni.showModal({
					title: '提示',
					content: '确定删除自己的评论嘛?',
					success: function(res) {
						if (res.confirm) {
							that.$H
								.post('comment/del', {
									id: e.id
								})
								.then(res2 => {
									if (res2.code == 0) {
										// if (index2 || index2 === 0) {
										// 	that.commentList[index].children.splice(index2, 1);
										// } else {
										// 	that.commentList.splice(index, 1);
										// }
										that.postDetail.commentCount --
										// that.getCommentList('delete');
									}
								});
						}
					}
				});
			},
		
			// 生成分享海报
			shareCanvas() {
				// this.showCanvas = true;
				uni.showLoading({
					mask: true,
					title: '正在生成海报'
				});////
				// #ifdef H5
				let origin = "h5";
				let url = this.$c.shareH5Url + "subpages/content/buyTicket/index?routeId=" + this.postId
				// #endif
				// #ifdef MP-WEIXIN
				let origin = "weixin";
				let customerPushCode = uni.getStorageSync("userInfo").pushCode || ''
				let url = `subpages/content/buyTicket/index?routeId=${this.postId}&pushCode=${customerPushCode}`
				// #endif
				this.$H.get('/business/app/tripTourRoute/qrWxCode', {
						// routeId: this.postId,
						// pushCode: customerPushCode,
						url: url
					})
					.then(res => {
						if (res.code == 0) {
							this.configData = {
								posterQrcode: res.result,
								routeName: this.detailContent.routeName,
								posterImg: this.detailContent.miediaList[0].mediaPath
							}
							this.showCanvas = true;
						}
						uni.hideLoading();
					});
			},
			// 保存海报到相册
			saveImg() {
				// #ifdef MP-WEIXIN
				// errMsg: "saveImageToPhotosAlbum: fail api scope is not declared in the privacy agreement"
				uni.getImageInfo({
					src: this.posterUrl,
					success: function(image) {
						uni.saveImageToPhotosAlbum({
							filePath: image.path,
							success: function(sucess) {
								uni.showToast({
									title: '图片保存成功'
								});
							},
							fail: function(error) {
								uni.showModal({
									title: '图片保存失败',
									content: '请确认是否已开启授权',
									confirmText: '开启授权',
									success(res) {
										if (res.confirm) {
											uni.openSetting({
												success(settingdata) {
													if (settingdata.authSetting[
															"scope.writePhotosAlbum"
														]) {
														uni.showToast({
															title: '授权成功,请重试哦~',
															icon: "none"
														});
													} else {
														uni.showToast({
															title: '请确定已打开保存权限',
															icon: "none"
														});
													}
												}
											})
										}
									}
								})
							},
						});
					},
					fail() {}
				});
				// #endif
				// #ifdef H5
				var oA = document.createElement('a');
				oA.download = ''; // 设置下载的文件名,默认是’下载’
				oA.href = this.posterUrl;
				document.body.appendChild(oA);
				oA.click();
				oA.remove(); // 下载之后把创建的元素删除
				// #endif
			},
			// 销毁组件
			closePopup() {
			  this.showCanvas = false;
			},
	}
}
</script>

<style scoped lang="scss">
	.tab-content {
		padding-bottom: 125rpx;
	}

/* 占位内容 */
.seize {
	padding: 40px;
	text-align: center;
}
/* END */


/* 选项卡样式 */
.tabs {
	background: #fff;
	width: 100%;
}
/* END */

.box {
	padding: 20px;
	background-color: #fff;
	border-bottom: 20rpx solid #F6F7F9;
}



.btn-customer-bg {
		width: 408rpx;
	}
	.full-container {
		background-color: #F6F7F9;
		.common-box {
			background: #fff;
			margin: 10rpx 0;
			padding: 0 40rpx 20rpx 40rpx;
		}
		.flex {
			display: flex;
			align-items: center;
		}
		.list-item {
			text-align: center;
			display: inline-block;
			margin-right: 80rpx;
			.avatar-z {
				width: 70rpx;
				height: 70rpx;
				border-radius: 50%;
			}
			.name {
				color: #000000;
				margin-top: -6rpx;
				font-size: 24rpx;
			}
		}
		.list-item:nth-child(5n) {
			margin-right: 0;
		}
		.banner-img {
			width: 750rpx;
			height: 560rpx;
			display: block;
		}
		.main-box {
			// padding-bottom: 149rpx;
			.scienc-info {
				padding: 0 40rpx;
				background: #fff;
				.desc {
					font-weight: 600;
					color: #333333;
					font-size: 30rpx;
					padding: 20rpx 0 10rpx 0;
				}
				.tip {
					color: #333333;
					font-size: 28rpx;
					margin-bottom: 20rpx;
				}
				.price-box {
					display: flex;
					align-items: center;
					justify-content: space-between;
					.person {
						color: #666666;
						font-size: 24rpx;
					}
				}
				.extension-vontainer {
					display: flex;
					width: 670rpx;
					height: 46rpx;
					background: #E4E3FF;
					align-items: center;
					margin-bottom: 20rpx;
					border-radius: 23rpx;
					padding: 0 20rpx 0 10rpx;
					justify-content: space-between;
					.notice-icon {
						width: 34rpx;
						height: 34rpx;
						margin-right: 10rpx;
					}
					.common-tip {
						color: #817EF7;
						font-size: 24rpx;
					}
					.puoduct {
						padding-top: 9rpx;
					}
					.plan {
						padding-top: 8rpx;
					}
					.puoduct::after {
						content: '';
						height: 38rpx;
						width: 2rpx;
						display: inline-block;
						background-color: #817EF7;
						vertical-align: middle;
						margin: 0 10rpx 8rpx 10rpx;
					}
					.two-right-icon {
						width: 20rpx;
						height: 20rpx;
					}
				}
				.operate-box {
					padding: 30rpx 0 20rpx 0;
					.flex {
						display: flex;
						align-items: center;
						margin-right: 20rpx;
					}
					display: flex;
					justify-content: space-between;
					.icon-img {
						width: 40rpx;
						height: 40rpx;
					}
					.text {
						color: #666666;
						font-size: 24rpx;
						margin-left: 8rpx;
					}
				}
			}
			.package-info {
				.nav-tit {
					display: flex;
					align-items: center;
					padding: 20rpx 0 0 0;
					.tit {
						font-size: 30rpx;
						font-weight: 600;
					}
					.desc {
						font-size: 24rpx;
						margin-top: 5rpx;
					}
				}
				.flex-container {
					display: flex;
					flex-wrap: wrap;
					.package-list {
						margin-bottom: 10rpx;
						.name {
							font-size: 26rpx;
							color: #000000;
							padding: 10rpx 16rpx;
							background: #F6F7F9;
							margin: 0 20rpx 10rpx 0;
							border-radius: 4rpx 4rpx 4rpx 4rpx;
						}
						.name:last-child {
							margin: 0 20rpx 0 0;
						}
						.desc {
							color: #333333;
							font-size: 24rpx;
							margin: 5prx 0 0 10rpx;
						}
					}
				}
			}
			.share-icon{
				animation: name 0.75s ease infinite;
			}
			@keyframes name{
				0% {transform: scale(1);}
				50% {transform: scale(1.35);}
				100% {transform: scale(1)}
			}


			.set-out-time {
				padding: 0 40rpx 20rpx 40rpx;
				.nav-tit {
					// padding: 20rpx 0 20rpx 40rpx;
				}
				
			}
			.aggregate-info {
				.list-item {
					display: flex;
					align-items: center;
					margin-bottom: 26rpx;
					.text-desc {
						font-size: 28rpx;
					}
					.icon-img {
						width: 42rpx;
						height: 42rpx;
						margin-right: 12rpx;
					}
					.name {
						color: #333333;
						font-size: 24rpx;
					}
					.default-icon {
						display: flex;
						position: relative;
						.icon-index {
							top: 50%;
							left: 40%;
							color: #FFFFFF;
							font-size: 20rpx;
							transform: translate(-50%, -50%);
							position: absolute;
							
						}
					}
				}
				.list-item:last-child {
					margin-bottom: 0;
				}
			}
			.person-info {
				.nav-tit {
					justify-content: space-between;
					.number {
						color: #817EF7;
						font-size: 24rpx;
					}
					.person {
						font-size: 24rpx;
						color: #333333;
						margin-right: 8rpx;
					}
				}
			}
			.tab-info {
				.tab-content {
					padding-top: 15rpx;
					padding-left: 40rpx;
					background-color: #fff;
					.tabs-item {
						margin-right: 60rpx;
						.flex {
							margin-left: 28rpx;
							.line-bottom {
								width: 50rpx;
							}
						}
					}
				}
				.conent {
					color: #666666;
					font-size: 28rpx;
					padding: 40rpx;
					background: #fff;
				}
			}
			.comment-box {
				padding-bottom: 180rpx;
				.avatar {
					width: 80rpx;
					height: 80rpx;
					border-radius: 50%;
					margin-right: 10rpx;
				}
				.comment-tool {
					display: flex;
					align-items: center;
					margin-bottom: 40rpx;
					.send-input {
						height: 36rpx;
						color: #666666;
						font-size: 26rpx;						
						background-color: #F6F6F6;
						padding: 27rpx 30rpx 16rpx 30rpx;
						border-radius: 34rpx 34rpx 34rpx 34rpx;
					}
				}
				.comment-item {
					.flex {
						.user {
							.user-name {
								color: #333333;
								font-size: 28rpx;
								font-weight: 600;
								margin-right: 10rpx;
							}
							.user-time {
								color: #666666;
								font-size: 26rpx;
							}
						}
					}
					.reply-desc {
						color: #666666;
						font-size: 26rpx;
						margin-left: 90rpx;
					}
				}
				.reply-comment-list {
					background: #F6F7F9;
					margin: 20rpx 0 0 90rpx;
					padding: 20rpx 30rpx;
					border-radius: 10rpx 10rpx 10rpx 10rpx;
					.sub-comment-item {
						margin-bottom: 10rpx;
						.flex {
							color: #333333;
							font-size: 24rpx;
							.name {
								color: #817EF7;
							}
							.content {
								word-break: break-all;
							}
						}
						.open-more {
							color: #6F97CB;
							font-size: 24rpx;
							margin: 10rpx 0 0 52rpx;
						}
					}
					.sub-comment-item:last-child {
						margin-bottom: 0;
					}
				}
			}
		}
		.bottom-operate {
			.flex {
				display: flex;
				.home-icon {
					margin: 0 35rpx 0 20rpx;
				}
				.ico-box {
					text-align: center;
					.tui-skeleton-rect {
						line-height: 34rpx;
						background-color: #fff;
					}
					.icon-img {
						width: 52rpx;
						height: 52rpx;
						display: block;
					}
					.text {
						color: #666666;
						font-size: 18rpx;
					}
				}
			}
			.play-group {
				width: 408rpx;
			}
		}
	}
</style>

Anchor.vue文件

<!-- 可根据现有需求,"适当"更改组件源码! -->

<template>
	<view :id="'luBarTabNav'+barId" class="lu-bar-tab-nav">
		<view v-if="!!barFixed" id="luTabFixed" class="lu-bar-tab lu-bar-tab-fixed" :style="{ top: barTopStyles,height:barHeightStyles,display:barShowStyles }">
<!-- 			<view class="lu-tab-item" v-for="(item,index) in tabList" :key="index" 
				:class="[selectedIndex==index? 'lu-active' : '',!!iconShow? 'lu-icon-show' : '']" 
				:style="selectedIndex==index?tabActiveStyles:tabStyles"
				@tap="getScrollToTarget(index, item)">
				<view :class="selectedIndex==index? 'lu-tab-text lu-tab-text-active' : 'lu-tab-text'">
					{{item.text}}
				</view>
			</view> -->
			
			<view class="customer-tabs order-tabs">
				<view class="tabs-item lu-tab-item" :class="selectedIndex == index ? 'activeStyle' : ''" v-for="(item, index) in tabList" :key="index" :style="selectedIndex==index?tabActiveStyles:tabStyles" @click="getScrollToTarget(index, item)">
					<view>{{ item.text }}</view>
					<view class="flex" v-show="selectedIndex == index">
						<view class="cricel-line"></view>
						<view class="line-bottom"></view>
					</view>
				</view>
			</view>
		</view>
		<view id="luTabStatic" class="lu-bar-tab lu-bar-tab-static" :style="{height:barHeightStyles}">
			<view class="customer-tabs order-tabs">
				<view class="tabs-item lu-tab-item" :class="selectedIndex == index ? 'activeStyle' : ''" v-for="(item, index) in tabList" :key="index" :style="selectedIndex==index?tabActiveStyles:tabStyles" @click="getScrollToTarget(index, item)">
					<view>{{ item.text }}</view>
					<view class="flex" v-show="selectedIndex == index">
						<view class="cricel-line"></view>
						<view class="line-bottom"></view>
					</view>
				</view>
			</view>
		</view>
		<view class="lu-tab-content"><slot></slot></view>
	</view>
</template>

<script>
	export default {
		props: {
			barFixed:{//选项卡是否启用浮动功能(可选)
				type:Boolean,
				default:true
			},
			iconShow:{//是否启用选项卡图标(可选)
				type:Boolean,
				default:false
			},
			transitionShow:{
				type:Boolean,
				default:false
			},
			barHeight:{
				type:[String,Number],
				default:44
			},
			barTop:{
				type:[String,Number],
				default:0
			},
			barId:{
				type:[String,Number],
				default:0
			},
			tabList: {
				type:Array,
				default:function () {
					return []
				}
			}
			
		},
		data() {
			return {
				clickTab: false,
				barShow:false,
				selectedIndex:0,
			};
		},
		computed:{
			tabStyles:function () {
				return (!!this.color?'color:'+this.color+';':'')+(!!this.backgroundColor?'backgroundColor:'+this.backgroundColor+';':'');
			},
			tabActiveStyles:function () {
				return (!!this.selectedColor?'color:'+this.selectedColor+';':'')+(!!this.selectedBackgroundColor?'backgroundColor:'+this.selectedBackgroundColor+';':'');
			},
			barTopStyles:function () {
				// #ifndef H5
					return 'calc('+this.barTop+'px);';
				// #endif
				// #ifdef H5
					return 'calc('+this.barTop+'px + var(--window-top));';
				// #endif
			},
			barHeightStyles:function () {
				return this.barHeight?this.barHeight+'px':'44px';
			},
			barShowStyles:function () {
				return !this.barShow?'none':'';
			},
		},
		methods: {
			_barInit:async function (index){
				let navTargetTop = [];
				let duration = 0;
				let viewScrollTop = 0;
				let viewHeight = 0;
				for (let i = 0,len=this.tabList.length; i < len; i++) {
					navTargetTop[i]= await this._queryMultipleNodes(this.tabList[i]["navTarget"]).then(res => {
						let navTarget = res[0],
							viewPort = res[1];
						viewHeight = viewPort.height;
						viewScrollTop = viewPort.scrollTop;
						const scrollTop = parseInt(navTarget.top) + viewPort.scrollTop - this.barTop - this.barHeight;
						return scrollTop;
					});
				}
				if (!!this.transitionShow) {
					duration = 200;
				} 
				return {
					navTargetTop:navTargetTop,
					duration:duration,
					viewHeight:viewHeight,
					viewScrollTop:viewScrollTop
				};
			},
			getPageScroll:async function(i){
				const elment = await this._barInit(i);
				let scrollTop = elment["navTargetTop"][i];
				let duration = elment["duration"];
				let viewHeight = elment["viewHeight"];
				let viewScrollTop = elment["viewScrollTop"];
				if (Math.abs(scrollTop-viewScrollTop)>viewHeight) {
					if (scrollTop>viewScrollTop) {
						// 向下移
						await uni.pageScrollTo({
							scrollTop:(scrollTop-viewHeight),
							duration:0
						});
					}else{
						// 向上移
						await uni.pageScrollTo({
							scrollTop:(scrollTop+viewHeight),
							duration:0
						});
					}
				}
				await uni.pageScrollTo({
					scrollTop:(scrollTop - 10),
					duration:duration
				});
				this.selectedIndex = i
			},
			// 点击切换Tab
			getScrollToTarget:function(i, item) {
				this.clickTabFlag = true
				uni.setStorageSync('clickTabFlag', true)
				uni.setStorageSync('clickTabIndex', i)
				this.getPageScroll(i, 'clickCurrnetTab');
				this.$emit('clickTabs',item)
			},
			_queryMultipleNodes:function (e,notThis) {
				return new Promise((resolve, reject) => {
					let view = uni.createSelectorQuery();
					if (!!notThis) {
						view.in(this);
					}
					if (!!e) {
						view.select(e).boundingClientRect();
					}
					view.selectViewport().fields({size: true,scrollOffset: true});
					view.exec(function(res) {
						resolve(res);
					});
					
				});
			},
			_showBarFixed:function () {
				this._queryMultipleNodes("#luTabStatic",true).then(res => {
					let tabNav = res[0];
					if (tabNav.top<=this.barTop) {
						this.barShow=true;
					}else{
						this.barShow=false;
					}
				});
			},
			getSelectedTab:function (y, type) {
				this._barInit().then((res)=>{
					let itemIndex = 0;
					for (let i = 0,len=res["navTargetTop"].length; i < len; i++) {
						if (y >=res["navTargetTop"][i] - 12) {
							itemIndex = i;
						}
					}
					// 如果到底,则直接重置最后一个Tab
					if (uni.getStorageSync('scrollBtttom')) {
						this.selectedIndex = this.tabList.length - 1
						setTimeout(() => {
							uni.setStorageSync('scrollBtttom', false)
						}, 800)
						return
					} else {
						// 如果是点击Tab,采用点击切换tab
						if (uni.getStorageSync('clickTabFlag') && uni.getStorageSync('scrollPageFlag')) {
							this.selectedIndex = uni.getStorageSync('clickTabIndex')
							setTimeout(() => {
								uni.setStorageSync('clickTabFlag', false)
							}, 800)
							return
						} else {
							this.selectedIndex = itemIndex;
						}
					}
					uni.setStorageSync('clickTabFlag', false)
					uni.setStorageSync('clickTabIndex', '0')
				});
				if (!!this.barFixed) {
					this._showBarFixed();
				}
			}
		}
	};
</script>

<style lang="scss" >
	.order-tabs {
		.flex {
			margin-left: -4rpx;
		}
	}
	lu-bar-tab-nav{
		position:relative;
		width: 100%;
	}
	.lu-bar-tab-nav{
		position:relative;
		width: 100%;
		.lu-bar-tab{
			width: 100%;
			display: flex;
			flex-flow: row wrap;
			justify-content: space-around;
			align-items:center;
			background-color: #fff;
			height: 44px;
			.lu-tab-item{
				//默认状态
				position: relative;
				flex: 1 1 auto;
				text-align: center;
				color: #333;
				display: flex;
				flex-flow: column nowrap;
				justify-content: center;
				align-items:center;
				// &::before{
				// 	position: absolute;
				// 	top: calc(50% - 15px);
				// 	left: 0px;
				// 	content: " ";
				// 	width: 1px;
				// 	height: 30px;
				// 	background-color: #eee;
				// }
				&:first-child::before{
					display: none;
				}
				.lu-tab-icon{
					font-size: inherit;
					color: inherit;
				}
				// 选项卡默认样式
				.lu-tab-text{
					font-size: inherit;
					color: inherit;
					// border: 1px solid red;
					padding-bottom: 6px;
					// 加个默认白色,防止高度不统一
					border-bottom: 3px solid #fff;
				}
				// 激活的选项卡-下划线
				.lu-tab-text-active {
					border-bottom: 3px solid #2979ff;
				}
				// 显示图标
				&.lu-icon-show{
					.lu-tab-icon{
						height: 24px;
						width: 24px;
						background-position: center center;
						background-repeat: no-repeat;
						background-size: 100% 100%;
					}
					.lu-tab-text{
						font-size: 12px;
						line-height: 16px;
					}
				}
				// 选中状态
				&.lu-active{
					color: #2979ff;
					// font-weight: bold;
					.lu-tab-icon{
						background-position: center center;
						background-repeat: no-repeat;
						background-size: 100% 100%;
					}
				}
			}
			
		}
		.lu-bar-tab-fixed{
			position:fixed;
			z-index: 1;
			top:calc(0px + var(--window-top));
		}
		.lu-bar-tab-static{
			position:static;
			z-index: 0;
		}
	}
</style>

END...