Files
stock/serve/frontend/src/components/data/EnrichedRebuildPanel.tsx
T
2026-07-04 16:59:25 +08:00

112 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from 'react'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { Loader2 } from 'lucide-react'
import { api } from '@/lib/api'
import { QK } from '@/lib/queryKeys'
import { usePreferences } from '@/lib/useSharedQueries'
export function EnrichedRebuildPanel({ isRunning, onStart }: { isRunning: boolean; onStart: () => void }) {
const qc = useQueryClient()
const prefs = usePreferences()
const batchSize = prefs.data?.enriched_batch_size ?? 1000
const [editing, setEditing] = useState(false)
const [draftSize, setDraftSize] = useState(String(batchSize))
const [hint, setHint] = useState<string | null>(null)
function clampAndSave(raw: number) {
if (isNaN(raw) || 1 > raw) { setHint('已自动设为最小值 1'); saveBatch.mutate(1); return }
if (raw > 10000) { setHint('已自动设为上限 10000'); saveBatch.mutate(10000); return }
setHint(null); saveBatch.mutate(raw)
}
const saveBatch = useMutation({
mutationFn: (size: number) => api.updateEnrichedBatchSize(size),
onSuccess: () => {
qc.invalidateQueries({ queryKey: QK.preferences })
setEditing(false)
},
})
const rebuild = useMutation({
mutationFn: api.rebuildEnriched,
onSuccess: () => {
onStart()
qc.invalidateQueries({ queryKey: QK.pipelineJobs })
},
})
return (
<div className="px-4 pb-4 pt-3 border-t border-accent/20 space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
<div>
<div className="text-xs font-medium text-foreground">批次大小</div>
<div className="text-[10px] text-muted">每批计算的标的数量,影响内存占用与进度粒度</div>
</div>
{editing ? (
<div className="flex items-center gap-1.5">
<input
type="number"
value={draftSize}
onChange={e => setDraftSize(e.target.value)}
className="w-20 px-2 py-1 text-xs font-mono rounded-btn border border-border bg-surface text-foreground text-right tabular-nums focus:outline-none focus:border-accent"
min={1}
max={10000}
autoFocus
onKeyDown={e => {
if (e.key === 'Enter') clampAndSave(parseInt(draftSize))
if (e.key === 'Escape') { setEditing(false); setHint(null) }
}}
/>
<button
onClick={() => clampAndSave(parseInt(draftSize))}
disabled={saveBatch.isPending}
className="px-2 py-1 text-[10px] rounded-btn bg-accent/15 text-accent hover:bg-accent/25 disabled:opacity-50 transition-colors"
>
{saveBatch.isPending ? '…' : '保存'}
</button>
<button
onClick={() => setEditing(false)}
className="px-2 py-1 text-[10px] rounded-btn bg-elevated text-muted hover:text-foreground transition-colors"
>
取消
</button>
</div>
) : (
<button
onClick={() => { setDraftSize(String(batchSize)); setEditing(true) }}
className="px-2.5 py-1 rounded-btn border border-border bg-surface text-xs font-mono text-foreground hover:border-accent/50 transition-colors tabular-nums"
>
{batchSize} /
</button>
)}
</div>
<div className="flex items-start gap-1.5 px-3 py-1.5 rounded-btn bg-warning/10 border border-warning/20">
<span className="text-[10px] text-warning leading-relaxed">
每批内存占用 = 批次大小 × K历史天数。批次越大或日K历史越长,内存占用越高,可能导致程序崩溃。内存不足时请适当降低此值。
</span>
</div>
{hint && (
<div className="px-3 py-1 rounded-btn bg-accent/10 border border-accent/20 text-[10px] text-accent">
{hint}
</div>
)}
</div>
<div>
<div className="text-[10px] text-muted mb-2">基于已有 kline_daily + adj_factor 全量计算前复权 + 技术指标 + 信号</div>
<button
onClick={() => rebuild.mutate()}
disabled={isRunning || rebuild.isPending}
className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-btn bg-accent/90 text-base text-xs font-medium hover:bg-accent disabled:opacity-40 disabled:pointer-events-none transition-colors duration-150"
>
{rebuild.isPending ? (
<><Loader2 className="h-3 w-3 animate-spin" />计算中…</>
) : (
<>全量计算</>
)}
</button>
</div>
</div>
)
}