Docker Build and Push / build-image (push) Successful in 4m3s
SwipeTabs 新增 swipeable 开关, Macro 开启, 打卡/监控保持原行为; 使用 swiper 14 实现整屏分页 + 首尾橡皮筋 + 松手吸附 + 方向锁; 滑动/点击/路由双向同步, 子 tab 切换用 replace 不堆历史; 每个 slide 独立竖向滚动, pane 节点缓存避免横滑时重渲染列表; 宏观内部切 tab 不再重复播放整页入场动画
66 lines
2.7 KiB
React
66 lines
2.7 KiB
React
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 实测校准)
|
|
// - 中间 <main>: flex:1 可滚动主区
|
|
// - 底部 TabBar: antd-mobile TabBar 自带底部安全区
|
|
// - 左上角离线标识 (0.2.1): 离线或服务不可达时显示, 请求恢复后自动隐藏
|
|
//
|
|
// 注意: 这里不再用 antd-mobile 的 <SafeArea position="top" />.
|
|
// 它只是一个空 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 (
|
|
<div className="app-root">
|
|
<div className="app-safe-top" aria-hidden="true" />
|
|
{offline && (
|
|
<div className="offline-badge" title="当前离线:显示本地缓存数据">
|
|
<span className="offline-dot" />
|
|
<span className="offline-text">离线</span>
|
|
</div>
|
|
)}
|
|
<main className="app-main">
|
|
<div className={'page-transition' + (animating ? ' active' : '')}>
|
|
<Outlet />
|
|
</div>
|
|
</main>
|
|
<BottomNav />
|
|
</div>
|
|
)
|
|
}
|