index.vue 9.12 KB
<template>
	<view class="tab-block">
		<view class='tab-list flex flex-center'
			:class="showMiddleButton === true ? 'tab-list-middle' : 'tab-list-default'">
			<!-- 流动选中指示器 - 暂时禁用 -->
			<!-- <view class="tab-indicator" :style="{ left: indicatorLeft + 'px', width: indicatorWidth + 'px' }"></view> -->
			<view v-for="(item, index) in tabList" :class="'list-item flex flex-column flex-middle ' + item.middleClass"
				@tap="handlePush(item, index)" :key="index" :ref="'tabItem' + index">
				<view class="item-img-box">
					<image class="item-img" :src="currentTabIndex == index ? item.selectedIconPath : item.iconPath" />
				</view>
				<view class="item-text font-20 color-black" :class="{ specialColor: currentTabIndex == index }">
					{{ item.text }}
				</view>
			</view>
		</view>
	</view>
</template>

<script>
export default {
	props: {
		// 当前选中的tab项,如果不传则自动根据当前页面路径计算
		tabIndex: {
			type: Number,
			default: null
		}
	},
	data() {
		return {
			tabList: [],
			showMiddleButton: false,
			indicatorLeft: 0,
			indicatorWidth: 0,
			newuserInfo:{},
			isUpdating: false // 防止无限递归的标志
		};
	},
	computed: {
		// 自动计算当前选中的tab索引
		currentTabIndex() {
			// 如果外部传入了 tabIndex,优先使用外部传入的值
			if (this.tabIndex !== null && this.tabIndex !== undefined) {
				return this.tabIndex;
			}
			// 否则根据当前页面路径自动计算
			const pages = getCurrentPages();
			if (pages.length === 0) return 0;

			const currentPage = pages[pages.length - 1];
			const currentPath = '/' + currentPage.route;

			// 查找匹配的tab项
			const matchedIndex = this.tabList.findIndex(item => {
				return currentPath === item.pagePath || currentPath.startsWith(item.pagePath);
			});

			return matchedIndex >= 0 ? matchedIndex : 0;
		}
	},
	mounted() {
		this.initTabList()
		// 完全禁用指示器功能,避免无限递归导致堆栈溢出
		// 指示器功能暂时不启用
	},
	onShow() {
		// 每次显示时重新初始化,确保用户信息更新后能正确显示
		this.initTabList()
	},
	watch: {
		// 移除所有 watch,避免无限递归
	},
	methods: {
	/**
	 * 初始化 tab 列表
	 */
	async initTabList() {
		if(uni.getStorageSync('newuserInfo')) {
			this.newuserInfo = uni.getStorageSync('newuserInfo') 
		} else{
			await this.API.getUsers(uni.getStorageSync('userInfo').userId).then(res => {
				if (res.code === 200) {
					this.newuserInfo = res.data
				}
			})
		}
		this.newuserInfo = uni.getStorageSync('newuserInfo') || {}
		
		// 通过菜单数据判断是否有"数据中心"菜单
		const hasDataCenter = await this.hasDataCenterMenu()
		this.tabList = this.getTabList(hasDataCenter)
		
		// 调试信息:打印菜单权限和 tab 数量
		console.log('[TabBar] 是否有数据中心菜单:', hasDataCenter, 'Tab数量:', this.tabList.length, 'Tab列表:', this.tabList.map(t => t.text))
	},
	/**
	 * 判断是否有"数据中心"菜单权限
	 * 通过解析菜单数据,查找是否存在 fullName === 'app数据中心' 的节点
	 * @returns {Boolean} 是否存在数据中心菜单
	 */
	async hasDataCenterMenu() {
		try {
			// 先从本地存储读取菜单数据
			const cachedData = uni.getStorageSync('appMenuData')
			if (cachedData && Array.isArray(cachedData) && cachedData.length > 0) {
				return this.parseMenuDataForTabBar(cachedData)
			}
			
			// 如果本地没有,则调用 API
			const userInfo = uni.getStorageSync('userInfo')
			if (!userInfo || !userInfo.userId) {
				return false
			}
			
			const res = await this.API.getAppAuthorize()
			if (res.code === 200 && res.data) {
				const menuData = res.data
				// 缓存菜单数据
				uni.setStorageSync('appMenuData', menuData)
				return this.parseMenuDataForTabBar(menuData)
			}
			
			return false
		} catch (err) {
			console.error('[TabBar] 获取菜单数据失败:', err)
			return false
		}
	},
	
	/**
	 * 解析菜单数据,判断是否存在"数据中心"菜单
	 * 参考 information.vue 中的 parseMenuData() 方法逻辑
	 * @param {Array} menuData - 菜单数据数组
	 * @returns {Boolean} 是否存在数据中心菜单
	 */
	parseMenuDataForTabBar(menuData) {
		if (!menuData || !Array.isArray(menuData) || menuData.length === 0) {
			return false
		}
		
		// 找到根节点(fullName 为 "app数据中心" 的节点)
		const rootNode = menuData.find(item => item.fullName === 'app数据中心')
		if (!rootNode || !rootNode.children) {
			return false
		}
		
		// 如果根节点存在且有子节点,说明有"数据中心"菜单权限
		return true
	},
	
	/**
	 * 根据是否有数据中心菜单权限获取对应的 tab 列表
	 * @param {Boolean} hasDataCenter - 是否有数据中心菜单权限
	 * @returns {Array} tab 配置列表
	 */
	getTabList(hasDataCenter) {
		// 定义所有 tab 配置项
		const tabConfigs = {
			home: {
				iconPath: "/static/tabBar/icon2.png",
				selectedIconPath: "/static/tabBar/icon1.png",
				text: '首页',
				pagePath: '/pages/home/home',
				middleClass: ''
			},
			dataCenter: {
				iconPath: "/static/tabBar/icon4.png",
				selectedIconPath: "/static/tabBar/icon3.png",
				text: '数据中心',
				pagePath: '/pagesA/information/information',
				middleClass: ''
			},
			mine: {
				iconPath: "/static/tabBar/icon6.png",
				selectedIconPath: "/static/tabBar/icon5.png",
				text: '我的',
				pagePath: '/pagesA/me/me',
				middleClass: ''
			}
		}

		// 根据菜单权限组合 tab 列表
		const tabList = [tabConfigs.home]
		if (hasDataCenter) {
			tabList.push(tabConfigs.dataCenter)
		}
		tabList.push(tabConfigs.mine)

		return tabList
	},
		// 更新流动指示器位置 - 暂时禁用
		updateIndicator() {
			// 暂时禁用此功能,避免无限递归
			return

			// 以下代码暂时禁用
			/*
			if (this.isUpdating) {
				return
			}
			this.isUpdating = true
			setTimeout(() => {
				try {
					const currentIndex = this.currentTabIndex
					const query = uni.createSelectorQuery().in(this)
					query.select('.tab-list').boundingClientRect((tabListRect) => {
						if (!tabListRect) {
							this.isUpdating = false
							return
						}
						query.selectAll('.list-item').boundingClientRect((rects) => {
							if (rects && rects.length > 0 && currentIndex >= 0 && currentIndex < rects.length) {
								const currentRect = rects[currentIndex]
								if (currentRect && tabListRect) {
									const newWidth = currentRect.width * 0.6
									const newLeft = currentRect.left - tabListRect.left + (currentRect.width - newWidth) / 2
									this.indicatorWidth = newWidth
									this.indicatorLeft = newLeft
								}
							}
							setTimeout(() => {
								this.isUpdating = false
							}, 100)
						}).exec()
					}).exec()
				} catch (e) {
					console.error('更新指示器失败:', e)
					this.isUpdating = false
				}
			}, 200)
			*/
		},
		// 点击按钮
		handlePush(item, index) {
			if (this.currentTabIndex !== index) {
				uni.reLaunch({
					url: item.pagePath
				})
			}
		}
	}
}
</script>

<style lang="scss">
.specialColor {
	color: #009b4d;
}

.flex {
	display: flex;
	flex-flow: row wrap;
}

.flex-center {
	align-items: center;
	justify-content: center;
}

.flex-column {
	flex-direction: column;
}

.flex-middle {
	align-items: center;
}

.font-20 {
	font-size: 22rpx;
}

.tab-block {
	position: fixed;
	bottom: 32rpx;
	left: 50%;
	transform: translateX(-50%);
	z-index: 99999;
	width: calc(100% - 16rpx);
	// max-width: 680rpx;
	padding: 0 24rpx;
	box-sizing: border-box;
	.tab-list {
		height: 120rpx;
	}

	.tab-list-default {
		background: rgba(255, 255, 255, 0.7);
		backdrop-filter: blur(20px) saturate(180%);
		-webkit-backdrop-filter: blur(20px) saturate(180%);
		border-radius: 28rpx;
		box-shadow: 0rpx 8rpx 32rpx rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(255, 255, 255, 0.3) inset;
		border: 1px solid rgba(255, 255, 255, 0.4);
		position: relative;
		transition: all 0.3s ease;
	}

	.tab-list-middle {
		position: relative;
		background: url('https://res.paquapp.com/popmartvip/home/nav_bar_bg_2x.png') no-repeat center center;
		background-size: cover;
	}

	/* 流动选中指示器 */
	.tab-indicator {
		position: absolute;
		bottom: 8rpx;
		height: 4rpx;
		background: linear-gradient(90deg, #43a047 0%, #66bb6a 100%);
		border-radius: 2rpx;
		transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
		box-shadow: 0 2rpx 8rpx rgba(67, 160, 71, 0.4);
		z-index: 1;
	}

	.list-item {
		flex: 1;
		position: relative;
		z-index: 2;
		transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);

		.item-img-box {
			width: 40rpx;
			height: 40rpx;
			margin-bottom: 6rpx;
			position: relative;
			transition: transform 0.3s ease;
		}

		.item-img {
			width: 40rpx;
			height: 40rpx;
			transition: transform 0.3s ease;
		}

		/* 3D悬浮动效 */
		&:active {
			transform: translateY(-2rpx) scale(0.95);

			.item-img-box {
				transform: scale(1.1);
			}
		}
	}

	.mid-button {
		position: relative;

		.item-img-box {
			width: 106rpx;
			height: 106rpx;
			margin-bottom: 9rpx;
			position: absolute;
			z-index: 1002;
			top: -104rpx;
		}

		.item-img {
			width: 106rpx;
			height: 106rpx;
		}

		.item-text {
			font-size: 20rpx;
			position: absolute;
			z-index: 1002;
			bottom: -40rpx;
		}
	}
}
</style>