Files
iboard/src/pages/MacroPage.jsx
T
jif2.zhang 902c6a0ebe
Docker Build and Push / build-image (push) Successful in 4m3s
feat(宏观): 子 tab 整屏左右滑动并与顶部同步
SwipeTabs 新增 swipeable 开关, Macro 开启, 打卡/监控保持原行为; 使用 swiper 14 实现整屏分页 + 首尾橡皮筋 + 松手吸附 + 方向锁; 滑动/点击/路由双向同步, 子 tab 切换用 replace 不堆历史; 每个 slide 独立竖向滚动, pane 节点缓存避免横滑时重渲染列表; 宏观内部切 tab 不再重复播放整页入场动画
2026-11-09 15:12:11 +08:00

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>
)
}