概念/行业分析添加日期选择功能
- backend: market_snapshot 支持可选 as_of 参数 - frontend: 两页加 DatePicker,按选定日期查询数据 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -296,13 +296,14 @@ def get_cached(
|
||||
|
||||
|
||||
@router.get("/market-snapshot")
|
||||
def market_snapshot(request: Request):
|
||||
"""最新全市场轻量行情快照,供板块/概念聚合分析使用。"""
|
||||
def market_snapshot(request: Request, as_of: date | None = None):
|
||||
"""全市场轻量行情快照(可选指定日期),供板块/概念聚合分析使用。"""
|
||||
import polars as pl
|
||||
|
||||
repo = request.app.state.repo
|
||||
svc = ScreenerService(repo)
|
||||
as_of = svc.latest_date()
|
||||
if as_of is None:
|
||||
as_of = svc.latest_date()
|
||||
if not as_of:
|
||||
return {"as_of": None, "rows": []}
|
||||
|
||||
|
||||
@@ -882,8 +882,8 @@ export const api = {
|
||||
? `/api/screener/cached?ext_columns=${encodeURIComponent(extColumns)}`
|
||||
: '/api/screener/cached',
|
||||
),
|
||||
marketSnapshot: () =>
|
||||
request<{ as_of: string | null; rows: MarketSnapshotRow[] }>('/api/screener/market-snapshot'),
|
||||
marketSnapshot: (asOf?: string) =>
|
||||
request<{ as_of: string | null; rows: MarketSnapshotRow[] }>(`/api/screener/market-snapshot${asOf ? `?as_of=${asOf}` : ''}`),
|
||||
overviewMarket: (asOf?: string) => request<OverviewMarket>(`/api/overview/market${asOf ? `?as_of=${asOf}` : ''}`),
|
||||
|
||||
// 概念涨幅轮动矩阵: 每列(日期)各自把所有概念按当天涨幅从高到低排序
|
||||
|
||||
@@ -15,6 +15,7 @@ import { PageHeader } from '@/components/PageHeader'
|
||||
import { EmptyState } from '@/components/EmptyState'
|
||||
import { type AnalysisFieldConfig } from '@/components/analysis-shared'
|
||||
import { StockPreviewDialog } from '@/components/StockPreviewDialog'
|
||||
import { DatePicker } from '@/components/DatePicker'
|
||||
import { RpsRotationDialog } from '@/components/RpsRotationDialog'
|
||||
import { api, type MarketSnapshotRow } from '@/lib/api'
|
||||
import { QK } from '@/lib/queryKeys'
|
||||
@@ -238,6 +239,7 @@ export function ConceptAnalysis() {
|
||||
const [previewSymbol, setPreviewSymbol] = useState<string | null>(null)
|
||||
const [previewName, setPreviewName] = useState<string>('')
|
||||
const [showRps, setShowRps] = useState(false)
|
||||
const [asOf, setAsOf] = useState('')
|
||||
|
||||
const configsQuery = useQuery({ queryKey: QK.extData, queryFn: api.extDataList })
|
||||
const availableConfigs = configsQuery.data?.items ?? []
|
||||
@@ -249,14 +251,14 @@ export function ConceptAnalysis() {
|
||||
const activeConfig = availableConfigs.find(c => c.id === activeConfigId)
|
||||
|
||||
const rowsQuery = useQuery({
|
||||
queryKey: QK.extDataRows(activeConfigId, undefined, PAGE_LIMIT),
|
||||
queryFn: () => api.extDataRows(activeConfigId, { limit: PAGE_LIMIT }),
|
||||
queryKey: QK.extDataRows(activeConfigId, asOf || undefined, PAGE_LIMIT),
|
||||
queryFn: () => api.extDataRows(activeConfigId, { date: asOf || undefined, limit: PAGE_LIMIT }),
|
||||
enabled: !!activeConfigId,
|
||||
})
|
||||
|
||||
const marketQuery = useQuery({
|
||||
queryKey: QK.marketSnapshot,
|
||||
queryFn: api.marketSnapshot,
|
||||
queryKey: ['market-snapshot', asOf],
|
||||
queryFn: () => api.marketSnapshot(asOf || undefined),
|
||||
staleTime: 60_000,
|
||||
})
|
||||
|
||||
@@ -317,9 +319,10 @@ export function ConceptAnalysis() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="概念分析"
|
||||
subtitle={`${marketQuery.data?.as_of ?? rowsQuery.data?.date ?? '最新'} · ${stats.length} 个概念 · ${totalSymbols} 只标的`}
|
||||
subtitle={`${asOf || marketQuery.data?.as_of || rowsQuery.data?.date || '最新'} · ${stats.length} 个概念 · ${totalSymbols} 只标的`}
|
||||
right={
|
||||
<div className="flex items-center gap-1">
|
||||
<DatePicker value={asOf} onChange={setAsOf} className="w-28" />
|
||||
{/* RPS 轮动: 打开涨幅轮动矩阵对话框 */}
|
||||
<button
|
||||
onClick={() => setShowRps(true)}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
TrendingDown,
|
||||
TrendingUp,
|
||||
} from 'lucide-react'
|
||||
import { DatePicker } from '@/components/DatePicker'
|
||||
import { PageHeader } from '@/components/PageHeader'
|
||||
import { EmptyState } from '@/components/EmptyState'
|
||||
import { DimensionHeatmap, type AnalysisFieldConfig } from '@/components/analysis-shared'
|
||||
@@ -269,6 +270,7 @@ export function IndustryAnalysis() {
|
||||
const [sortMode, setSortMode] = useState<SortMode>('heat')
|
||||
const [previewSymbol, setPreviewSymbol] = useState<string | null>(null)
|
||||
const [previewName, setPreviewName] = useState<string>('')
|
||||
const [asOf, setAsOf] = useState('')
|
||||
|
||||
const configsQuery = useQuery({ queryKey: QK.extData, queryFn: api.extDataList })
|
||||
const availableConfigs = configsQuery.data?.items ?? []
|
||||
@@ -280,14 +282,14 @@ export function IndustryAnalysis() {
|
||||
const activeConfig = availableConfigs.find(c => c.id === activeConfigId)
|
||||
|
||||
const rowsQuery = useQuery({
|
||||
queryKey: QK.extDataRows(activeConfigId, undefined, PAGE_LIMIT),
|
||||
queryFn: () => api.extDataRows(activeConfigId, { limit: PAGE_LIMIT }),
|
||||
queryKey: QK.extDataRows(activeConfigId, asOf || undefined, PAGE_LIMIT),
|
||||
queryFn: () => api.extDataRows(activeConfigId, { date: asOf || undefined, limit: PAGE_LIMIT }),
|
||||
enabled: !!activeConfigId,
|
||||
})
|
||||
|
||||
const marketQuery = useQuery({
|
||||
queryKey: QK.marketSnapshot,
|
||||
queryFn: api.marketSnapshot,
|
||||
queryKey: ['market-snapshot', asOf],
|
||||
queryFn: () => api.marketSnapshot(asOf || undefined),
|
||||
staleTime: 60_000,
|
||||
})
|
||||
|
||||
@@ -367,7 +369,10 @@ export function IndustryAnalysis() {
|
||||
<>
|
||||
<PageHeader
|
||||
title="行业分析"
|
||||
subtitle={`${industryLevelLabel} · ${marketQuery.data?.as_of ?? rowsQuery.data?.date ?? '最新'} · ${stats.length} 个行业 · ${totalSymbols} 只标的`}
|
||||
subtitle={`${industryLevelLabel} · ${asOf || marketQuery.data?.as_of || rowsQuery.data?.date || '最新'} · ${stats.length} 个行业 · ${totalSymbols} 只标的`}
|
||||
right={
|
||||
<DatePicker value={asOf} onChange={setAsOf} className="w-28" />
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="min-h-full bg-[radial-gradient(circle_at_12%_0%,rgba(245,158,11,0.12),transparent_28%),radial-gradient(circle_at_85%_8%,rgba(244,63,94,0.08),transparent_28%)] px-6 py-5">
|
||||
|
||||
Reference in New Issue
Block a user