import { useEffect, useRef, useState } from 'react' import { Outlet, useLocation } from 'react-router-dom' import BottomNav from '../components/BottomNav.jsx' import { isOffline, subscribeOffline } from '../api/client.js' // 宏观页内部子 tab 的列表路径 (整屏横滑只在这些路径之间切换) // /macro/news · /macro/articles · /macro/finance/:catId // 详情页 (多一段) 不算, 进出详情仍要播放整页入场动画 function isMacroListPath(p) { return p === '/macro/news' || p === '/macro/articles' || /^\/macro\/finance\/[^/]+\/?$/.test(p) } // 移动端应用外壳 // - 顶部安全区: 用 --safe-top 撑开 (由 main.jsx 的 calibrateSafeTop 实测校准) // - 中间
: flex:1 可滚动主区 // - 底部 TabBar: antd-mobile TabBar 自带底部安全区 // - 左上角离线标识 (0.2.1): 离线或服务不可达时显示, 请求恢复后自动隐藏 // // 注意: 这里不再用 antd-mobile 的 . // 它只是一个空 div, 高度完全依赖 env(safe-area-inset-top); 而 Android WebView // (Tauri) 下该变量常为 0, 会导致内容顶到状态栏底下 —— V0.2 把 45px 的 NavBar // 换成 23px 的 PageHeader 后这个问题就暴露了. 改用可控高度的 div + JS 校准. export default function MobileShell() { const location = useLocation() // pathname 变化时给页面容器加动画 class (仅播放入场动画, 不重挂载子树, // 避免破坏 MacroPage 等页内子路由的挂载状态与滚动位置) const [animating, setAnimating] = useState(false) const prevPathRef = useRef(null) useEffect(() => { const prev = prevPathRef.current prevPathRef.current = location.pathname // 宏观内部子 tab 切换已由整屏横滑提供动效, 不要再叠一次整页淡入 + 上移 if (prev !== null && isMacroListPath(prev) && isMacroListPath(location.pathname)) { setAnimating(false) return } setAnimating(true) const t = setTimeout(() => setAnimating(false), 320) return () => clearTimeout(t) }, [location.pathname]) // 离线状态 (窗口左上角标识) const [offline, setOfflineState] = useState(isOffline()) useEffect(() => subscribeOffline(setOfflineState), []) return (
) }