import { View, Text } from '@tarojs/components'; import Taro, { useDidShow, usePageScroll } from '@tarojs/taro'; import { useState, useEffect, useCallback } from 'react'; import { ChevronLeft, House } from 'lucide-react-taro'; import { IS_H5_ENV } from './env'; interface NavConfig { navigationBarTitleText?: string; navigationBarBackgroundColor?: string; navigationBarTextStyle?: 'black' | 'white'; navigationStyle?: 'default' | 'custom'; transparentTitle?: 'none' | 'always' | 'auto'; } enum LeftIcon { Back = 'back', Home = 'home', None = 'none', } interface NavState { visible: boolean; title: string; bgColor: string; textStyle: 'black' | 'white'; navStyle: 'default' | 'custom'; transparent: 'none' | 'always' | 'auto'; leftIcon: LeftIcon; } const DEFAULT_NAV_STATE: NavState = { visible: false, title: '', bgColor: '#ffffff', textStyle: 'black', navStyle: 'default', transparent: 'none', leftIcon: LeftIcon.None, }; const getGlobalWindowConfig = (): Partial => { const app = Taro.getApp(); return app?.config?.window || {}; }; const getTabBarPages = (): Set => { const tabBar = Taro.getApp()?.config?.tabBar; return new Set( tabBar?.list?.map((item: { pagePath: string }) => item.pagePath) || [], ); }; const getHomePage = (): string => { const app = Taro.getApp(); return app?.config?.pages?.[0] || 'pages/index/index'; }; const cleanPath = (path: string): string => path.replace(/^\//, ''); const computeLeftIcon = ( route: string, tabBarPages: Set, historyLength: number, homePage: string, ): LeftIcon => { if (!route) return LeftIcon.None; const cleanRoute = cleanPath(route); const cleanHomePage = cleanPath(homePage); const isHomePage = cleanRoute === cleanHomePage; const isTabBarPage = tabBarPages.has(cleanRoute) || tabBarPages.has(`/${cleanRoute}`); const hasHistory = historyLength > 1; if (isTabBarPage || isHomePage) return LeftIcon.None; if (hasHistory) return LeftIcon.Back; return LeftIcon.Home; }; export const H5NavBar = () => { const [navState, setNavState] = useState(DEFAULT_NAV_STATE); const [scrollOpacity, setScrollOpacity] = useState(0); const updateNavState = useCallback(() => { const pages = Taro.getCurrentPages(); if (pages.length === 0) { setNavState(prev => ({ ...prev, visible: false })); return; } const currentPage = pages[pages.length - 1]; const route = currentPage?.route || ''; if (!route) return; const pageConfig: NavConfig = (currentPage as any)?.config || {}; const globalConfig = getGlobalWindowConfig(); const tabBarPages = getTabBarPages(); const homePage = getHomePage(); const cleanRoute = cleanPath(route); const cleanHomePage = cleanPath(homePage); const isHomePage = cleanRoute === cleanHomePage; const isTabBarPage = tabBarPages.has(cleanRoute) || tabBarPages.has(`/${cleanRoute}`); const shouldHideNav = tabBarPages.size <= 1 && pages.length <= 1 && (isHomePage || isTabBarPage); setNavState({ visible: !shouldHideNav, title: document.title || pageConfig.navigationBarTitleText || globalConfig.navigationBarTitleText || '', bgColor: pageConfig.navigationBarBackgroundColor || globalConfig.navigationBarBackgroundColor || '#ffffff', textStyle: pageConfig.navigationBarTextStyle || globalConfig.navigationBarTextStyle || 'black', navStyle: pageConfig.navigationStyle || globalConfig.navigationStyle || 'default', transparent: pageConfig.transparentTitle || globalConfig.transparentTitle || 'none', leftIcon: shouldHideNav ? LeftIcon.None : computeLeftIcon(cleanRoute, tabBarPages, pages.length, cleanHomePage), }); }, []); useDidShow(() => { updateNavState(); }); usePageScroll(({ scrollTop }) => { if (navState.transparent === 'auto') { setScrollOpacity(Math.min(scrollTop / 100, 1)); } }); useEffect(() => { if (!IS_H5_ENV) return; let timer: NodeJS.Timeout | null = null; const observer = new MutationObserver(() => { if (timer) clearTimeout(timer); timer = setTimeout(() => { updateNavState(); }, 50); }); observer.observe(document.head, { subtree: true, childList: true, characterData: true, }); updateNavState(); return () => { observer.disconnect(); if (timer) clearTimeout(timer); }; }, [updateNavState]); const shouldRender = IS_H5_ENV && navState.visible && navState.navStyle !== 'custom'; useEffect(() => { if (!IS_H5_ENV) return; if (shouldRender) { document.body.classList.add('h5-navbar-visible'); } else { document.body.classList.remove('h5-navbar-visible'); } }, [shouldRender]); if (!shouldRender) { return <>; } const iconColor = navState.textStyle === 'white' ? '#fff' : '#333'; const textColorClass = navState.textStyle === 'white' ? 'text-white' : 'text-gray-800'; const getBgStyle = () => { if (navState.transparent === 'always') { return { backgroundColor: 'transparent' }; } if (navState.transparent === 'auto') { return { backgroundColor: navState.bgColor, opacity: scrollOpacity }; } return { backgroundColor: navState.bgColor }; }; const handleBack = () => Taro.navigateBack(); const handleGoHome = () => { const homePage = getHomePage(); Taro.reLaunch({ url: `/${homePage}` }); }; return ( <> {navState.leftIcon === LeftIcon.Back && ( )} {navState.leftIcon === LeftIcon.Home && ( )} {navState.title} ); };