Docker Build and Push / build-image (push) Successful in 4m3s
SwipeTabs 新增 swipeable 开关, Macro 开启, 打卡/监控保持原行为; 使用 swiper 14 实现整屏分页 + 首尾橡皮筋 + 松手吸附 + 方向锁; 滑动/点击/路由双向同步, 子 tab 切换用 replace 不堆历史; 每个 slide 独立竖向滚动, pane 节点缓存避免横滑时重渲染列表; 宏观内部切 tab 不再重复播放整页入场动画
90 lines
3.6 KiB
React
90 lines
3.6 KiB
React
import { useMemo } from 'react'
|
|
import { useNavigate, useLocation, Routes, Route } from 'react-router-dom'
|
|
import PageHeader from '../components/PageHeader.jsx'
|
|
import SwipeTabs from '../components/SwipeTabs.jsx'
|
|
import ArticlesView from './macro/ArticlesView.jsx'
|
|
import ArticleDetail from './macro/ArticleDetail.jsx'
|
|
import NewsView from './macro/NewsView.jsx'
|
|
import NewsDetail from './news/NewsDetail.jsx'
|
|
import FinanceCategoryView from './macro/FinanceCategoryView.jsx'
|
|
import FinanceItemView from './macro/FinanceItemView.jsx'
|
|
import { useIfinanceInfo } from '../lib/ifinance.js'
|
|
|
|
// 宏观: 本地固定 2 个 tab (新闻 / 经济文章) + 外接 ifinance 分类动态挂载为 tab
|
|
// /macro/news → 新闻 (默认)
|
|
// /macro/articles → 经济文章
|
|
// /macro/finance/:catId → 外接分类 (经济数据 / 策略研究 / …)
|
|
// 详情: /macro/news/:id · /macro/articles/:slug · /macro/finance/:catId/:itemId
|
|
// 说明: 外接分类不写死, 由 info.json 的 categories[] 决定; ifinance 新增分类后
|
|
// 本页自动多出新 tab, 宿主无需改代码。报警已迁到「监控」。
|
|
const BASE_TABS = [
|
|
{ key: 'news', label: '新闻', path: '/macro/news' },
|
|
{ key: 'articles', label: '经济文章', path: '/macro/articles' },
|
|
]
|
|
|
|
function activeFromPath(pathname) {
|
|
if (pathname.startsWith('/macro/news')) return 'news'
|
|
if (pathname.startsWith('/macro/articles')) return 'articles'
|
|
const m = pathname.match(/^\/macro\/finance\/([^/]+)/)
|
|
if (m) return 'finance:' + m[1]
|
|
// 父路由 /macro 走默认 tab (新闻)
|
|
return 'news'
|
|
}
|
|
|
|
export default function MacroPage() {
|
|
const location = useLocation()
|
|
const navigate = useNavigate()
|
|
const { info } = useIfinanceInfo()
|
|
const active = activeFromPath(location.pathname)
|
|
const categories = info?.categories
|
|
|
|
// tabs 只在分类索引变化时重建: 横滑切页时 SwipeTabs 可复用已缓存的 pane 节点,
|
|
// 不会因为父组件重渲染而把所有列表重新渲染一遍
|
|
// (必须放在详情页提前 return 之前: hooks 不能有条件跳过)
|
|
const tabs = useMemo(() => [
|
|
...BASE_TABS.map((t) => ({
|
|
key: t.key,
|
|
label: t.label,
|
|
render: () => (t.key === 'news' ? <NewsView /> : <ArticlesView />),
|
|
})),
|
|
...(categories || []).map((c) => ({
|
|
key: 'finance:' + c.id,
|
|
label: c.name,
|
|
render: () => <FinanceCategoryView category={c} />,
|
|
})),
|
|
], [categories])
|
|
|
|
// 详情页单独走 React Router 渲染 (需要 Route 提供 params), 不参与滑屏
|
|
const p = location.pathname
|
|
const isNewsDetail = /^\/macro\/news\/[^/]+$/.test(p)
|
|
const isArticleDetail = /^\/macro\/articles\/[^/]+$/.test(p)
|
|
const isFinanceDetail = /^\/macro\/finance\/[^/]+\/[^/]+$/.test(p)
|
|
if (isNewsDetail || isArticleDetail || isFinanceDetail) {
|
|
return (
|
|
<Routes>
|
|
<Route path="news/:id" element={<NewsDetail />} />
|
|
<Route path="articles/:slug" element={<ArticleDetail />} />
|
|
<Route path="finance/:catId/:itemId" element={<FinanceItemView />} />
|
|
</Routes>
|
|
)
|
|
}
|
|
|
|
// 子 tab 切换用 replace: 横滑多次不会在历史里堆一串记录,
|
|
// 返回键语义保持为「离开宏观」; 详情页仍按默认 push
|
|
function onChange(key) {
|
|
if (key.startsWith('finance:')) {
|
|
navigate('/macro/finance/' + key.slice('finance:'.length), { replace: true })
|
|
return
|
|
}
|
|
const next = BASE_TABS.find((t) => t.key === key)
|
|
if (next) navigate(next.path, { replace: true })
|
|
}
|
|
|
|
return (
|
|
<div className="page-with-navbar">
|
|
<PageHeader title="宏观" />
|
|
<SwipeTabs tabs={tabs} active={active} onChange={onChange} swipeable />
|
|
</div>
|
|
)
|
|
}
|