修改页面布局样式

This commit is contained in:
zlt 2026-08-13 13:12:10 +08:00
parent 7bd9dcd998
commit ac2509958f
2 changed files with 73 additions and 24 deletions

View File

@ -103,6 +103,25 @@ def _read_intraday(c, ymd):
return {"key": key, "decision": decision, "timing": timing} return {"key": key, "decision": decision, "timing": timing}
# 告警源 → (方向, 类型key, 类型标签)。权威依据: intraday_timing/alerts/orchestrator.py 的
# DIRECTION_DOWN_SOURCES / DIRECTION_UP_SOURCES (2026-06-15 现行) 与《盘中择时层_信号输出说明》§5。
# · intraday_buy_emitted 是「每发一条 BUY 镜像一条」的事件, **无涨跌方向** (原来按子串误判成看涨)。
# · multi_source 的方向看 metadata.direction。
# · volume_capitulation/breakout 已退役(被 capital_distribution/accumulation 取代), 历史键仍可能在流里, 一并登记。
_ALERT_META = {
"daily_qrs_symmetric_down": ("看跌", "qrs", "日QRS对称"),
"money_flow_out_intensity": ("看跌", "money_flow", "资金流强度"),
"capital_distribution": ("看跌", "capital", "资金分布"),
"volume_capitulation": ("看跌", "volume", "放量异动"),
"daily_qrs_symmetric_up": ("看涨", "qrs", "日QRS对称"),
"money_flow_in_intensity": ("看涨", "money_flow", "资金流强度"),
"capital_accumulation": ("看涨", "capital", "资金分布"),
"volume_breakout": ("看涨", "volume", "放量异动"),
"multi_source": (None, "multi", "多源升级"),
"intraday_buy_emitted": ("", "buy_emitted", "买入镜像"),
}
def _read_alerts(c, ymd): def _read_alerts(c, ymd):
key = "intraday_alerts:%s" % ymd key = "intraday_alerts:%s" % ymd
out = [] out = []
@ -114,16 +133,15 @@ def _read_alerts(c, ymd):
meta = json.loads(meta) meta = json.loads(meta)
except Exception: except Exception:
meta = {} meta = {}
dir_fixed, cat, cat_label = _ALERT_META.get(src, (None, "other", "其他"))
if dir_fixed is None:
# multi_source 或未登记源: 方向以 metadata.direction 为准, 拿不到就 —
mdir = str((meta or {}).get("direction") or "").lower() mdir = str((meta or {}).get("direction") or "").lower()
low = src.lower() d = "看跌" if mdir in ("down", "short", "bear") else ("看涨" if mdir in ("up", "long", "bull") else "")
# 源名都含 "intensity", 不能用 "_in" 这种松匹配 (会误配 out_intensity)。用精确 token, 先判看跌。
if mdir in ("down", "short", "bear") or any(x in low for x in ("_down", "out_intensity", "capitulation")):
d = "看跌"
elif mdir in ("up", "long", "bull") or any(x in low for x in ("_up", "in_intensity", "breakout", "buy_emitted")):
d = "看涨"
else: else:
d = "" d = dir_fixed
out.append({"ts_code": f.get("ts_code"), "source": src, "direction": d, out.append({"ts_code": f.get("ts_code"), "source": src, "direction": d,
"cat": cat, "cat_label": cat_label,
"level": f.get("level"), "value": _num(f.get("value")), "time": _hm(f.get("trigger_time"))}) "level": f.get("level"), "value": _num(f.get("value")), "time": _hm(f.get("trigger_time"))})
return {"key": key, "items": out} return {"key": key, "items": out}

View File

@ -128,6 +128,12 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius:
.alert-dot{width:7px;height:7px;border-radius:50%;flex:none;background:var(--muted);} .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-dot.lv-danger{background:var(--stop);} .alert-dot.lv-warn{background:var(--warn);}
.alert-panel{margin-top:0;} .alert-panel{margin-top:0;}
.alert-wins{display:grid;grid-template-columns:repeat(auto-fit,minmax(430px,1fr));gap:14px;margin:2px 0 16px;}
.alert-win{border:1px solid var(--hair);border-radius:12px;padding:10px 14px 12px;background:var(--surface);box-shadow:var(--shadow);}
.alert-win .aw-h{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px;}
.alert-win .aw-title{font-size:13px;font-weight:640;color:var(--ink-2);}
.alert-win .aw-toggle{font-size:11px;font-weight:560;color:var(--accent);cursor:pointer;border:1px solid var(--accent);border-radius:999px;padding:1px 9px;background:var(--accent-weak);white-space:nowrap;}
.alert-win .aw-toggle.off{color:var(--muted);border-color:var(--hair);background:var(--surface-2);}
</style> </style>
</head> </head>
<body> <body>
@ -180,17 +186,19 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius:
<!-- ==================== 交易员视图 ==================== --> <!-- ==================== 交易员视图 ==================== -->
<div v-show="mode==='trader'"> <div v-show="mode==='trader'">
<!-- 0) 顶部风控告警条 (默认折叠; 条上常驻最新一条, 展开看全部) --> <!-- 0) 顶部风控告警条 (折叠态常驻最新一条; 展开后按类型分窗, 每窗可切「折叠条显示」) -->
<div class="alert-bar" :class="'lv-'+alertLevelClass"> <div class="alert-bar" :class="'lv-'+alertLevelClass">
<span class="ab-title"><span class="alert-dot" :class="'lv-'+alertLevelClass"></span>风控告警</span> <span class="ab-title"><span class="alert-dot" :class="'lv-'+alertLevelClass"></span>风控告警</span>
<template v-if="alertItems.length"> <template v-if="alertItems.length">
<span class="ab-badge">{{ alertItems.length }} 条</span> <span class="ab-badge">{{ alertStripCount }} 条<span v-if="alertTotalCount>alertStripCount" class="muted" style="font-weight:400"> / 全部 {{ alertTotalCount }}</span></span>
<span class="ab-latest">最新 · <span class="ab-latest" v-if="alertLatest.ts_code">最新 ·
<b>{{ nm(alertLatest.ts_code) }}</b> <span class="muted mono">{{ alertLatest.ts_code }}</span> <b>{{ nm(alertLatest.ts_code) }}</b> <span class="muted mono">{{ alertLatest.ts_code }}</span>
<span :class="alertLatest.direction==='看涨'?'clr-up':(alertLatest.direction==='看跌'?'clr-down':'muted')">{{ alertLatest.direction }}</span> <span v-if="alertLatest.direction && alertLatest.direction!=='—'" :class="alertLatest.direction==='看涨'?'clr-up':'clr-down'">{{ alertLatest.direction }}</span>
<span class="muted">{{ alertLatest.cat_label }}</span>
<span class="disp" :class="alertLatest.level==='DANGER'?'deny':(alertLatest.level==='WARNING'?'queue':'wait')" style="padding:1px 7px">{{ alertLatest.level }}</span> <span class="disp" :class="alertLatest.level==='DANGER'?'deny':(alertLatest.level==='WARNING'?'queue':'wait')" style="padding:1px 7px">{{ alertLatest.level }}</span>
<span class="muted">{{ alertLatest.source }} · {{ alertLatest.time }}</span> <span class="muted">{{ alertLatest.time }}</span>
</span> </span>
<span class="ab-latest muted" v-else>· 折叠条已隐藏当前类型(展开后可切回)</span>
<button class="ab-toggle" @click="showAlerts=!showAlerts">{{ showAlerts?'收起 ▲':'展开全部 ▼' }}</button> <button class="ab-toggle" @click="showAlerts=!showAlerts">{{ showAlerts?'收起 ▲':'展开全部 ▼' }}</button>
</template> </template>
<template v-else-if="usrc('alerts') && usrc('alerts').ok===false"> <template v-else-if="usrc('alerts') && usrc('alerts').ok===false">
@ -201,15 +209,21 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius:
</template> </template>
</div> </div>
<div class="panel tblk alert-panel" v-if="showAlerts && alertItems.length"> <div class="alert-wins" v-if="showAlerts && alertItems.length">
<el-table :data="alertItems.slice(0,30)" size="small" max-height="340"> <div class="alert-win" v-for="g in alertGroups" :key="g.key">
<el-table-column label="股票" min-width="150"><template #default="s"><b>{{ nm(s.row.ts_code) }}</b> <span class="muted mono">{{ s.row.ts_code }}</span></template></el-table-column> <div class="aw-h">
<el-table-column label="方向" width="70"><template #default="s"><span :class="s.row.direction==='看涨'?'clr-up':(s.row.direction==='看跌'?'clr-down':'muted')">{{ s.row.direction }}</span></template></el-table-column> <span class="aw-title">{{ g.label }} <span class="muted" style="font-weight:400">· {{ g.items.length }}</span></span>
<el-table-column label="级别" width="96"><template #default="s"><span class="disp" :class="s.row.level==='DANGER'?'deny':(s.row.level==='WARNING'?'queue':'wait')">{{ s.row.level }}</span></template></el-table-column> <span class="aw-toggle" :class="{off:!catShown(g.key)}" @click="toggleCat(g.key)" :title="catShown(g.key)?'点此不在顶部折叠条里显示这类':'点此让这类回到顶部折叠条'">{{ catShown(g.key) ? '折叠条显示 ✓' : '折叠条隐藏' }}</span>
<el-table-column label="来源" min-width="170"><template #default="s"><span class="muted" style="font-size:11.5px">{{ s.row.source }}</span></template></el-table-column> </div>
<el-table-column label="时间" width="64" align="right"><template #default="s"><span class="mono muted">{{ s.row.time }}</span></template></el-table-column> <el-table :data="g.items.slice(0,20)" size="small" max-height="240" empty-text="暂无">
<el-table-column label="股票" min-width="140"><template #default="s"><b>{{ nm(s.row.ts_code) }}</b> <span class="muted mono">{{ s.row.ts_code }}</span></template></el-table-column>
<el-table-column label="方向" width="58"><template #default="s"><span v-if="s.row.direction && s.row.direction!=='—'" :class="s.row.direction==='看涨'?'clr-up':'clr-down'">{{ s.row.direction }}</span><span v-else class="muted"></span></template></el-table-column>
<el-table-column label="级别" width="86"><template #default="s"><span class="disp" :class="s.row.level==='DANGER'?'deny':(s.row.level==='WARNING'?'queue':'wait')">{{ s.row.level }}</span></template></el-table-column>
<el-table-column label="来源" min-width="150"><template #default="s"><span class="muted" style="font-size:11px">{{ s.row.source }}</span></template></el-table-column>
<el-table-column label="时间" width="56" align="right"><template #default="s"><span class="mono muted">{{ s.row.time }}</span></template></el-table-column>
</el-table> </el-table>
</div> </div>
</div>
<!-- 1) 今日一览 + 需要注意 --> <!-- 1) 今日一览 + 需要注意 -->
<div class="metrics"> <div class="metrics">
@ -1381,6 +1395,7 @@ createApp({
const upstream = ref({ sources: {} }); const upstream = ref({ sources: {} });
const showUpstream = ref(true); const showUpstream = ref(true);
const showAlerts = ref(false); const showAlerts = ref(false);
const alertShowMap = reactive({ money_flow:true, capital:true, qrs:true, volume:true, multi:true, buy_emitted:false, other:true });
const openScan = ref({ by_code: {}, notes: [] }); const openScan = ref({ by_code: {}, notes: [] });
const insTab = ref('live'); const insTab = ref('live');
const stratDlg = reactive({ visible:false, busy:false, reasons:[], ts_code:'', price:0, const stratDlg = reactive({ visible:false, busy:false, reasons:[], ts_code:'', price:0,
@ -1808,9 +1823,24 @@ createApp({
} }
const usrc = k => (upstream.value.sources || {})[k]; const usrc = k => (upstream.value.sources || {})[k];
const alertItems = computed(() => { const a = usrc('alerts'); return (a && Array.isArray(a.items)) ? a.items : []; }); const alertItems = computed(() => { const a = usrc('alerts'); return (a && Array.isArray(a.items)) ? a.items : []; });
const alertLatest = computed(() => alertItems.value[0] || {}); const ALERT_CAT_ORDER = ['money_flow','capital','qrs','volume','multi','buy_emitted','other'];
const ALERT_CAT_DIRECTIONAL = { money_flow:true, capital:true, qrs:true, volume:true, multi:true, buy_emitted:false, other:false };
const catShown = k => alertShowMap[k] !== false; // 未登记类默认进折叠条
function toggleCat(k){ alertShowMap[k] = !catShown(k); }
const alertGroups = computed(() => {
const by = {};
for (const a of alertItems.value) { (by[a.cat] || (by[a.cat] = [])).push(a); }
const keys = ALERT_CAT_ORDER.filter(k => by[k]);
Object.keys(by).forEach(k => { if (!keys.includes(k)) keys.push(k); });
return keys.map(k => ({ key:k, label:(by[k][0].cat_label) || k, items:by[k],
directional: ALERT_CAT_DIRECTIONAL[k] !== false }));
});
const alertStripItems = computed(() => alertItems.value.filter(a => catShown(a.cat)));
const alertLatest = computed(() => alertStripItems.value[0] || {});
const alertStripCount = computed(() => alertStripItems.value.length);
const alertTotalCount = computed(() => alertItems.value.length);
const alertLevelClass = computed(() => { const alertLevelClass = computed(() => {
const it = alertItems.value; const it = alertStripItems.value;
if (it.some(x => x.level === 'DANGER')) return 'danger'; if (it.some(x => x.level === 'DANGER')) return 'danger';
if (it.some(x => x.level === 'WARNING')) return 'warn'; if (it.some(x => x.level === 'WARNING')) return 'warn';
return 'none'; return 'none';
@ -2090,7 +2120,8 @@ createApp({
openScan, insTab, todayYmd, insLive, insDone, insEnd, dispOf, loadOpenScan, denyToday, openScan, insTab, todayYmd, insLive, insDone, insEnd, dispOf, loadOpenScan, denyToday,
showOpLog, showSettings, watchCandidates, marketOpen, marketState, lastRefresh, autoRefresh, showOpLog, showSettings, watchCandidates, marketOpen, marketState, lastRefresh, autoRefresh,
upstream, showUpstream, usrc, loadUpstream, upstream, showUpstream, usrc, loadUpstream,
showAlerts, alertItems, alertLatest, alertLevelClass }; showAlerts, alertItems, alertLatest, alertLevelClass, alertGroups, alertStripItems,
alertStripCount, alertTotalCount, catShown, toggleCat };
} }
}).use(ElementPlus).mount('#app'); }).use(ElementPlus).mount('#app');
</script> </script>