mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 15:34:16 +08:00
refactor(watchlist): 分组涨跌幅最优性复审 — 可见性门控与格式化去重
复审发现两处可优化: 1. 侧栏分组涨跌幅查询原为 groupsInNav 开启即无条件拉取 — 改为 「侧栏展开 ∧ 自选二级菜单展开」才请求, 收起状态下不再为隐藏 UI 发 watchlist/enriched 请求(展开即时拉取, 与自选页共享缓存去重) 2. formatGroupPct 与全站 fmtPct(lib/format)逻辑完全重复 — 删除并 复用 fmtPct, 格式随全局统一; 顺带消除 Layout 中 navCollapsed 状态声明的位置耦合(上移至查询前) 验证: tsc 通过; 浏览器实测 — 子菜单收起 0 次 enriched 请求, 展开 后 1 次请求即显示; 分组 tab/侧栏数值与优化前逐位一致(全部+0.59% 半导体-1.55% 等), 悬停明细正常。
This commit is contained in:
@@ -57,7 +57,8 @@ import { Logo } from './Logo'
|
||||
import { api, type IndexQuote } from '@/lib/api'
|
||||
import { cn } from '@/lib/cn'
|
||||
import { resolveWatchlistGroupColor } from '@/lib/watchlist-group-colors'
|
||||
import { computeGroupPcts, formatGroupPct, groupPctColor, groupPctTitle } from '@/lib/watchlistGroupStats'
|
||||
import { computeGroupPcts, groupPctColor, groupPctTitle } from '@/lib/watchlistGroupStats'
|
||||
import { fmtPct } from '@/lib/format'
|
||||
import { toggleTheme, useTheme } from '@/lib/theme'
|
||||
import { setCurrentTotal as setAlertTotal, useUnreadAlerts } from '@/lib/monitorBadge'
|
||||
import { ExtensionSlot } from '@/extensions/ExtensionSlot'
|
||||
@@ -302,18 +303,29 @@ export function Layout() {
|
||||
staleTime: 60_000,
|
||||
})
|
||||
const watchlistGroups = watchlistGroupsData?.groups ?? []
|
||||
// 自选二级菜单展开状态 — 默认当前在自选页时展开
|
||||
const [watchlistNavExpanded, setWatchlistNavExpanded] = useState(location.pathname === '/watchlist')
|
||||
|
||||
// 侧边栏收起状态 — 持久化到 localStorage
|
||||
const [navCollapsed, setNavCollapsed] = useState(() => {
|
||||
if (typeof window !== 'undefined' && window.matchMedia('(max-width: 767px)').matches) return true
|
||||
try { return localStorage.getItem('tf-nav-collapsed') === '1' } catch { return false }
|
||||
})
|
||||
|
||||
// 分组等权平均涨跌幅 — 复用 watchlist/enriched 查询缓存(与自选页同 key,
|
||||
// 盘中随 SSE 刷新); 侧栏开启分组时才拉取
|
||||
// 盘中随 SSE 刷新)。可见性门控: 子菜单实际可见(侧栏展开 + 二级菜单展开)
|
||||
// 时才拉取, 收起状态下不为隐藏 UI 发请求。
|
||||
const navGroupPctVisible = groupsInNav && !navCollapsed && watchlistNavExpanded
|
||||
const { data: navWatchlist } = useQuery({
|
||||
queryKey: QK.watchlist,
|
||||
queryFn: api.watchlistList,
|
||||
enabled: groupsInNav,
|
||||
enabled: navGroupPctVisible,
|
||||
staleTime: 60_000,
|
||||
})
|
||||
const { data: navEnriched } = useQuery({
|
||||
queryKey: QK.watchlistEnriched(undefined),
|
||||
queryFn: () => api.watchlistEnriched(),
|
||||
enabled: groupsInNav,
|
||||
enabled: navGroupPctVisible,
|
||||
staleTime: 60_000,
|
||||
})
|
||||
const navGroupPcts = useMemo(
|
||||
@@ -323,8 +335,6 @@ export function Layout() {
|
||||
),
|
||||
[navWatchlist, navEnriched],
|
||||
)
|
||||
// 自选二级菜单展开状态 — 默认当前在自选页时展开
|
||||
const [watchlistNavExpanded, setWatchlistNavExpanded] = useState(location.pathname === '/watchlist')
|
||||
|
||||
// 数据同步状态轮询: 有活跃 job 时「数据」菜单项显示转圈
|
||||
const { data: pipelineJobs } = useQuery({
|
||||
@@ -356,11 +366,6 @@ export function Layout() {
|
||||
const realtimeEnabled = prefs?.realtime_quotes_enabled ?? false
|
||||
// Free 档监控限制提示: 可手动关闭, 不持久化 (刷新后恢复显示)
|
||||
const [dismissFreeHint, setDismissFreeHint] = useState(false)
|
||||
// 侧边栏收起状态 — 持久化到 localStorage
|
||||
const [navCollapsed, setNavCollapsed] = useState(() => {
|
||||
if (typeof window !== 'undefined' && window.matchMedia('(max-width: 767px)').matches) return true
|
||||
try { return localStorage.getItem('tf-nav-collapsed') === '1' } catch { return false }
|
||||
})
|
||||
useEffect(() => {
|
||||
const compact = window.matchMedia('(max-width: 767px)')
|
||||
const syncSidebarWithViewport = (event: MediaQueryListEvent | MediaQueryList) => {
|
||||
@@ -676,7 +681,7 @@ export function Layout() {
|
||||
const info = navGroupPcts['all']
|
||||
return info && info.pct != null ? (
|
||||
<span className={`ml-auto font-mono text-[10px] tabular-nums ${groupPctColor(info.pct)}`} title={groupPctTitle(info)}>
|
||||
{formatGroupPct(info.pct)}
|
||||
{fmtPct(info.pct)}
|
||||
</span>
|
||||
) : null
|
||||
})()}
|
||||
@@ -701,7 +706,7 @@ export function Layout() {
|
||||
<span className="truncate">{group.name}</span>
|
||||
{pctInfo && pctInfo.pct != null && (
|
||||
<span className={`ml-auto font-mono text-[10px] tabular-nums ${groupPctColor(pctInfo.pct)}`} title={groupPctTitle(pctInfo)}>
|
||||
{formatGroupPct(pctInfo.pct)}
|
||||
{fmtPct(pctInfo.pct)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
|
||||
@@ -5,9 +5,9 @@ import { Modal } from '@/components/Modal'
|
||||
import { api } from '@/lib/api'
|
||||
import { QK } from '@/lib/queryKeys'
|
||||
import { usePreferences } from '@/lib/useSharedQueries'
|
||||
import { fmtPct } from '@/lib/format'
|
||||
import type { WatchlistGroup, WatchlistGroupColor } from '@/lib/api'
|
||||
import {
|
||||
formatGroupPct,
|
||||
groupPctColor,
|
||||
groupPctTitle,
|
||||
type GroupPctMap,
|
||||
@@ -91,7 +91,7 @@ export function WatchlistGroupBar({
|
||||
className={`font-mono text-[10px] tabular-nums ${groupPctColor(info.pct)}`}
|
||||
title={groupPctTitle(info)}
|
||||
>
|
||||
{formatGroupPct(info.pct)}
|
||||
{fmtPct(info.pct)}
|
||||
</span>
|
||||
)
|
||||
})()}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
* 自选分组涨跌幅 — 等权平均口径。
|
||||
*
|
||||
* 组内每只成员取「实时优先、收盘兜底」的涨跌幅(与自选表格展示同源:
|
||||
* rt_pct ?? change_pct, 百分数单位), 算术平均即分组涨跌幅。等权最贴合
|
||||
* rt_pct ?? change_pct, 小数单位), 算术平均即分组涨跌幅。等权最贴合
|
||||
* 自选的"个人组合"视角 — 透明且无需市值数据。
|
||||
*/
|
||||
|
||||
import { fmtPct } from '@/lib/format'
|
||||
|
||||
export interface GroupPctInfo {
|
||||
/** 等权平均涨跌幅(小数, 0.0123 = +1.23%, 与 enriched change_pct 同单位); 无有效样本为 null */
|
||||
pct: number | null
|
||||
@@ -52,21 +54,14 @@ export function computeGroupPcts(
|
||||
return out
|
||||
}
|
||||
|
||||
/** 涨跌幅文本: +1.23% / -0.50% / 0.00% / — (无样本)。入参为小数(0.0123) */
|
||||
export function formatGroupPct(pct: number | null): string {
|
||||
if (pct == null) return '—'
|
||||
const v = pct * 100
|
||||
return `${v > 0 ? '+' : ''}${v.toFixed(2)}%`
|
||||
}
|
||||
|
||||
/** 涨跌色 (A 股惯例红涨绿跌) */
|
||||
export function groupPctColor(pct: number | null): string {
|
||||
if (pct == null || pct === 0) return 'text-muted'
|
||||
return pct > 0 ? 'text-bull' : 'text-bear'
|
||||
}
|
||||
|
||||
/** 悬停明细: 等权平均 +1.23% · 上涨12 下跌5 平1 */
|
||||
/** 悬停明细: 等权平均 +1.23% · 上涨12 下跌5 平1 (格式化复用全站 fmtPct) */
|
||||
export function groupPctTitle(info: GroupPctInfo | undefined): string {
|
||||
if (!info || info.pct == null) return '暂无涨跌幅数据'
|
||||
return `等权平均 ${formatGroupPct(info.pct)} · 上涨${info.up} 下跌${info.down} 平${info.flat} (共${info.sampled}只)`
|
||||
return `等权平均 ${fmtPct(info.pct)} · 上涨${info.up} 下跌${info.down} 平${info.flat} (共${info.sampled}只)`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user