From fca0e261dba4a69001e1717a3bea54f72bdd0ee1 Mon Sep 17 00:00:00 2001 From: shy3130 <415333856@qq.com> Date: Thu, 2 Jul 2026 17:22:52 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=B3=BB=E7=BB=9F=E5=B7=A5=E5=85=B7?= =?UTF-8?q?=E9=A1=B5=E6=96=B0=E5=A2=9E=E5=B0=81=E5=8D=95=E7=9B=91=E6=8E=A7?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增封单监控 tab: 模拟触发 (纯条件判断) 与真实触发 (落盘+推送) 供验证规则是否生效 - 页面标题与文案中性化, 避免暴露内部调试性质 --- frontend/src/pages/Dev.tsx | 147 +++++++++++++++++++++++++++++++++++-- 1 file changed, 142 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/Dev.tsx b/frontend/src/pages/Dev.tsx index 2b52161..81c4e4a 100644 --- a/frontend/src/pages/Dev.tsx +++ b/frontend/src/pages/Dev.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { useMutation, useQueryClient } from '@tanstack/react-query' -import { Loader2, Search, AlertTriangle, CheckCircle2, XCircle, FlaskConical, Activity } from 'lucide-react' +import { Loader2, Search, AlertTriangle, CheckCircle2, XCircle, FlaskConical, Activity, Bell } from 'lucide-react' import { PageHeader } from '@/components/PageHeader' import { api } from '@/lib/api' import { cn } from '@/lib/cn' @@ -315,15 +315,143 @@ function SeedPanel() { ) } +// ── 封单监控模拟触发 ────────────────────────────────── +function LadderTestPanel() { + const [result, setResult] = useState> | null>(null) + const [error, setError] = useState('') + const [pushMsg, setPushMsg] = useState('') + + const testMut = useMutation({ + mutationFn: () => api.monitorRuleTestLadder(), + onSuccess: (data) => { setResult(data); setError('') }, + onError: (e: any) => { setError(e?.message ?? String(e)); setResult(null) }, + }) + + const triggerMut = useMutation({ + mutationFn: () => api.monitorRuleTriggerLadder(), + onSuccess: (data) => { + setPushMsg(`✅ 已真实触发 ${data.triggered} 条预警 (落盘 + 飞书 + SSE)`) + setTimeout(() => setPushMsg(''), 6000) + }, + onError: (e: any) => { + setPushMsg(`❌ 触发失败: ${e?.message ?? String(e)}`) + setTimeout(() => setPushMsg(''), 6000) + }, + }) + + const fmtVal = (v: number | null | undefined, metric: string) => { + if (v == null) return '—' + if (metric === 'sealed_amount') return `${(v / 1e8).toFixed(4)} 亿` + return `${v.toLocaleString()} 手` + } + + return ( +
+
+

封单监控模拟触发

+

+ 用当前 depth 封单数据 + 最新日 enriched, 评估所有 ladder 规则。 +

+
+ +
+ + + {result && ( + + 日期 {result.as_of} · 封单股 {result.sealed_count} · 触发 {result.triggered.length} · 未触发 {result.not_triggered.length} + + )} +
+ +
+ 模拟触发:纯条件判断,不落盘不推送。 + 真实触发:走完整链路(落盘 alerts.jsonl + 推送飞书 + SSE 通知),会在监控中心和飞书看到预警。 +
+ + {pushMsg && ( +
{pushMsg}
+ )} + + {error && ( +
+ + {error} +
+ )} + + {result && result.triggered.length > 0 && ( +
+

✅ 会触发 ({result.triggered.length})

+ {result.triggered.map((ev) => ( +
+
+ {ev.symbol} + {ev.name && {ev.name}} + {ev.type} +
+
{ev.message}
+
+ 当前封单: {fmtVal(ev.sealed_metric === 'sealed_amount' ? ev.current_sealed_amount : ev.current_sealed_vol, ev.sealed_metric)} +
+
+ ))} +
+ )} + + {result && result.not_triggered.length > 0 && ( +
+

⚪ 未触发 ({result.not_triggered.length})

+ {result.not_triggered.map((r) => ( +
+
+ {r.symbol} + {r.rule_name} +
+
+ 阈值 {fmtVal(r.threshold, r.metric)} · 当前 {fmtVal(r.current_value, r.metric)} · {r.reason} +
+
+ ))} +
+ )} + + {result && result.triggered.length === 0 && result.not_triggered.length === 0 && ( +
+ 无 ladder 监控规则,请先在连板梯队页设置封单监控 +
+ )} +
+ ) +} + // ── Dev 主页面 ──────────────────────────────────────── export function Dev() { - const [tab, setTab] = useState<'minute' | 'seed'>('seed') + const [tab, setTab] = useState<'minute' | 'seed' | 'ladder'>('seed') return (
+