将项目文件整理到 refer 目录

This commit is contained in:
2026-07-03 22:26:00 +08:00
parent 49880e785f
commit 9f57af4d37
1223 changed files with 98 additions and 0 deletions
@@ -0,0 +1,120 @@
import { useEffect, useMemo, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { Loader2 } from 'lucide-react'
import { api, type MinuteKlineRow } from '@/lib/api'
import { QK } from '@/lib/queryKeys'
import { EChartsIntraday } from '@/components/EChartsIntraday'
interface Props {
symbol: string
date: string | null
height?: number
prevClose?: number
className?: string
onPriceHover?: (price: number | null) => void
}
export function StockIntradayChart({
symbol,
date,
height = 520,
prevClose,
className,
onPriceHover,
}: Props) {
const qc = useQueryClient()
const [minuteDismissed, setMinuteDismissed] = useState(false)
const minute = useQuery({
queryKey: QK.klineMinute(symbol, date ?? ''),
queryFn: () => api.klineMinute(symbol, date ?? undefined),
enabled: !!symbol && !!date,
})
const fetchMinute = useMutation({
mutationFn: () => api.extendMinuteHistory(5, 'day'),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['kline-minute', symbol] })
qc.invalidateQueries({ queryKey: QK.dataStatus })
qc.invalidateQueries({ queryKey: QK.pipelineJobs })
setMinuteDismissed(false)
},
})
const minuteRows: MinuteKlineRow[] = useMemo(() => minute.data?.rows ?? [], [minute.data?.rows])
// source=none 表示本地无数据且数据源也拉不到 (停牌/复牌延迟/非交易日)
// 此时不弹"是否获取"询问窗, 只做静态提示, 避免误导用户去拉明知拉不到的数据
const sourceIsNone = minute.data?.source === 'none'
useEffect(() => {
setMinuteDismissed(false)
onPriceHover?.(null)
}, [date, onPriceHover])
if (!symbol || !date) return null
return (
<div className={className} style={{ height, flexShrink: 0 }}>
{minute.isLoading && <div className="text-xs text-muted py-2"></div>}
{!minute.isLoading && minuteRows.length === 0 && (
<>
{fetchMinute.isPending ? (
<div className="flex items-center justify-center h-full gap-2 text-xs text-accent">
<Loader2 className="h-3.5 w-3.5 animate-spin" />
<span>5K</span>
</div>
) : sourceIsNone ? (
// 数据源确认无此日分钟数据 (停牌/复牌延迟等): 静态提示 + 保留重试
<div className="flex flex-col items-center justify-center h-full gap-3">
<div className="text-xs text-muted"></div>
<button
onClick={() => fetchMinute.mutate()}
className="px-4 py-1.5 rounded-btn bg-elevated text-secondary text-xs font-medium hover:bg-elevated/80 transition-colors duration-150"
>
</button>
</div>
) : minuteDismissed ? (
<div className="flex flex-col items-center justify-center h-full gap-3">
<div className="text-xs text-muted"></div>
<button
onClick={() => setMinuteDismissed(false)}
className="px-4 py-1.5 rounded-btn bg-accent/90 text-base text-xs font-medium hover:bg-accent transition-colors duration-150"
>
K
</button>
</div>
) : (
<div className="flex flex-col items-center justify-center h-full gap-4">
<div className="text-sm text-foreground">5K</div>
<div className="flex items-center gap-3">
<button
onClick={() => fetchMinute.mutate()}
className="px-4 py-1.5 rounded-btn bg-accent/90 text-base text-xs font-medium hover:bg-accent transition-colors duration-150"
>
</button>
<button
onClick={() => setMinuteDismissed(true)}
className="px-4 py-1.5 rounded-btn bg-elevated text-secondary text-xs hover:bg-elevated/80 transition-colors duration-150"
>
</button>
</div>
</div>
)}
</>
)}
{minuteRows.length > 0 && (
<EChartsIntraday
data={minuteRows}
height={height}
prevClose={prevClose}
date={date}
symbol={symbol}
onPriceHover={onPriceHover}
/>
)}
</div>
)
}