diff --git a/app/web/static/index.html b/app/web/static/index.html
index be40c1d..ea7a6d1 100644
--- a/app/web/static/index.html
+++ b/app/web/static/index.html
@@ -113,6 +113,21 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius:
.mr-two{display:grid;grid-template-columns:1fr 1fr;gap:16px;font-size:12.5px;}
.mr-row{padding:3px 0;border-bottom:1px solid var(--hair);}
.mr-row:last-child{border-bottom:none;}
+/* 顶部风控告警条 (系统状态色: 琥珀/橙/灰, 不用红绿; 红绿只留给方向看涨看跌) */
+.alert-bar{display:flex;align-items:center;gap:9px;background:var(--surface);border:1px solid var(--hair);
+ border-left:3px solid var(--hair);border-radius:12px;padding:9px 14px;margin:16px 0 8px;box-shadow:var(--shadow);}
+.alert-bar.lv-danger{border-left-color:var(--stop);}
+.alert-bar.lv-warn{border-left-color:var(--warn);}
+.alert-bar .ab-title{font-weight:640;font-size:13px;color:var(--ink-2);white-space:nowrap;display:flex;align-items:center;gap:6px;}
+.alert-bar .ab-badge{font-size:11px;font-weight:600;padding:1px 8px;border-radius:999px;background:rgba(138,136,130,.16);color:var(--ink-2);}
+.alert-bar.lv-danger .ab-badge{background:var(--stop-weak);color:var(--stop);}
+.alert-bar.lv-warn .ab-badge{background:var(--warn-weak);color:var(--warn);}
+.alert-bar .ab-latest{flex:1;min-width:0;font-size:13px;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
+.alert-bar .ab-toggle{flex:none;color:var(--accent);font-size:12.5px;font-weight:560;cursor:pointer;background:none;border:0;padding:4px 4px;}
+.alert-bar .ab-empty{flex:1;color:var(--muted);font-size:12.5px;}
+.alert-dot{width:7px;height:7px;border-radius:50%;flex:none;background:var(--muted);}
+.alert-dot.lv-danger{background:var(--stop);} .alert-dot.lv-warn{background:var(--warn);}
+.alert-panel{margin-top:0;}
@@ -165,6 +180,37 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius:
+
+
+ 风控告警
+
+ {{ alertItems.length }} 条
+ 最新 ·
+ {{ nm(alertLatest.ts_code) }} {{ alertLatest.ts_code }}
+ {{ alertLatest.direction }}
+ {{ alertLatest.level }}
+ {{ alertLatest.source }} · {{ alertLatest.time }}
+
+
+
+
+ · 读取失败:{{ usrc('alerts').error }}
+
+
+ · 暂无风控告警
+
+
+
+
+
+ {{ nm(s.row.ts_code) }} {{ s.row.ts_code }}
+ {{ s.row.direction }}
+ {{ s.row.level }}
+ {{ s.row.source }}
+ {{ s.row.time }}
+
+
+
账户总资产
{{ money(ov.total_asset) }}
@@ -597,17 +643,6 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius:
-
-
双向风控告警 · 读取失败
-
- {{ nm(s.row.ts_code) }}
- {{ s.row.direction }}
- {{ s.row.level }}
- {{ s.row.source }}
- {{ s.row.time }}
-
-
-
mtf 资金异动 · 读取失败
@@ -1345,6 +1380,7 @@ createApp({
const autoRefresh = ref(true);
const upstream = ref({ sources: {} });
const showUpstream = ref(true);
+ const showAlerts = ref(false);
const openScan = ref({ by_code: {}, notes: [] });
const insTab = ref('live');
const stratDlg = reactive({ visible:false, busy:false, reasons:[], ts_code:'', price:0,
@@ -1771,6 +1807,14 @@ createApp({
openScan.value = (d && d.by_code) ? d : { by_code: {}, notes: [] };
}
const usrc = k => (upstream.value.sources || {})[k];
+ const alertItems = computed(() => { const a = usrc('alerts'); return (a && Array.isArray(a.items)) ? a.items : []; });
+ const alertLatest = computed(() => alertItems.value[0] || {});
+ const alertLevelClass = computed(() => {
+ const it = alertItems.value;
+ if (it.some(x => x.level === 'DANGER')) return 'danger';
+ if (it.some(x => x.level === 'WARNING')) return 'warn';
+ return 'none';
+ });
async function loadUpstream() {
const d = await call('get', '/api/upstream-signals');
upstream.value = (d && d.sources) ? d : { sources: {} };
@@ -2045,7 +2089,8 @@ createApp({
showLedger, onPosExpand,
openScan, insTab, todayYmd, insLive, insDone, insEnd, dispOf, loadOpenScan, denyToday,
showOpLog, showSettings, watchCandidates, marketOpen, marketState, lastRefresh, autoRefresh,
- upstream, showUpstream, usrc, loadUpstream };
+ upstream, showUpstream, usrc, loadUpstream,
+ showAlerts, alertItems, alertLatest, alertLevelClass };
}
}).use(ElementPlus).mount('#app');