PMS信号区: 默认折叠+级别筛选跨刷新保留; mtf资金异动拆data取真实字段(方向/异动z/净额),去空级别列

This commit is contained in:
zlt 2026-08-14 14:00:05 +08:00
parent ae2c7bd5b7
commit 1fde77f728
2 changed files with 31 additions and 12 deletions

View File

@ -182,14 +182,27 @@ def _read_alerts(c, ymd):
def _read_metrics(c):
"""mtf 资金异动: payload schema 未在契约里写全, 挑常见字段, 其余整体带过去供页面兜底显示。"""
"""mtf 资金异动: 真实载荷打包在 stream 的 data(JSON字符串)里, 顶层只有 ts_code ——
之前直接取顶层 z_dd/direction 全落空(页面显示为空)这里拆 data 取真实字段, 顶层作兜底
mtf 侧字段: direction(inflow/outflow), z_dd(横截面异动z), window_net(净额), window_ret(区间涨跌); 无原生 level"""
key = "mtf:intraday:stream:metrics"
out = []
for _id, f in c.xrevrange(key, count=_limit()):
code = f.get("ts_code") or f.get("code")
out.append({"ts_code": code, "z_dd": _num(f.get("z_dd")), "direction": f.get("direction"),
"level": f.get("level"), "value": _num(f.get("value") if f.get("value") is not None else f.get("net")),
"raw": {k: v for k, v in f.items() if k not in ("ts_code", "code")}})
payload = f.get("data")
if isinstance(payload, str):
try:
payload = json.loads(payload)
except Exception:
payload = {}
if not isinstance(payload, dict):
payload = {}
g = lambda k, d=None: payload.get(k, f.get(k, d))
out.append({"ts_code": code, "direction": g("direction"),
"z_dd": _num(g("z_dd")), "window_net": _num(g("window_net")),
"window_ret": _num(g("window_ret")), "level": g("level"),
"value": _num(g("value") if g("value") is not None else g("net")),
"raw": payload})
return {"key": key, "items": out}

View File

@ -263,11 +263,11 @@ pre.json{background:var(--surface-2);border:1px solid var(--hair);border-radius:
<div class="up-cell">
<div class="up-h">mtf 资金异动<span class="src-err" v-if="usrc('metrics') && usrc('metrics').ok===false"> · 读取失败</span></div>
<el-table :data="((usrc('metrics')||{}).items||[]).filter(x=>passLevel(x.level)).slice(0,8)" size="small" max-height="200" empty-text="暂无">
<el-table :data="((usrc('metrics')||{}).items||[]).slice(0,8)" size="small" max-height="200" empty-text="暂无">
<el-table-column label="股票" min-width="120"><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="78"><template #default="s"><span class="muted">{{ s.row.direction || '—' }}</span></template></el-table-column>
<el-table-column label="z_dd" width="66" align="right"><template #default="s"><span class="mono">{{ s.row.z_dd==null?'—':s.row.z_dd }}</span></template></el-table-column>
<el-table-column label="级别" width="86"><template #default="s"><span class="disp" :class="lvClass(s.row.level)">{{ lvLabel(s.row.level) }}</span></template></el-table-column>
<el-table-column label="方向" width="60"><template #default="s"><span v-if="flowText(s.row.direction)!=='—'" :class="flowClass(s.row.direction)">{{ flowText(s.row.direction) }}</span><span v-else class="muted"></span></template></el-table-column>
<el-table-column label="异动z" width="62" align="right"><template #default="s"><span class="mono">{{ s.row.z_dd==null?'—':s.row.z_dd }}</span></template></el-table-column>
<el-table-column label="净额" width="98" align="right"><template #default="s"><span class="mono" :class="s.row.window_net==null?'muted':((s.row.window_net||0)>=0?'clr-up':'clr-down')">{{ s.row.window_net==null?'—':money(s.row.window_net) }}</span></template></el-table-column>
</el-table>
</div>
@ -1404,10 +1404,14 @@ createApp({
const lastRefresh = ref('');
const autoRefresh = ref(true);
const upstream = ref({ sources: {} });
const showUpstream = ref(true);
const showUpstream = ref(false); // 默认折叠, 点标题展开
const showAlerts = ref(false);
const alertShowMap = reactive({ money_flow:true, capital:true, qrs:true, volume:true, multi:true, price_notice:false, buy_emitted:false, other:true });
const levelShow = reactive({ DANGER:true, WARNING:true, IMPORTANT:true, INFO:false, NORMAL:false });
// 级别筛选记忆: 存 sessionStorage —— 普通刷新保留, 关标签页/新开标签=回默认。默认关 普通/提示。
const LV_STORE_KEY = 'pms_level_show';
const _lvDefault = { DANGER:true, WARNING:true, IMPORTANT:true, INFO:false, NORMAL:false };
const _lvLoad = () => { try { const v = JSON.parse(sessionStorage.getItem(LV_STORE_KEY)); if (v && typeof v === 'object') return { ..._lvDefault, ...v }; } catch (e) {} return { ..._lvDefault }; };
const levelShow = reactive(_lvLoad());
const openScan = ref({ by_code: {}, notes: [] });
const insTab = ref('live');
const stratDlg = reactive({ visible:false, busy:false, reasons:[], ts_code:'', price:0,
@ -1853,7 +1857,7 @@ createApp({
// 级别筛选 (纯前端): 已知级别按开关过滤; 未知级别一律放行 (风控从宽, 不误杀)。
const KNOWN_LV = new Set(['DANGER','WARNING','IMPORTANT','INFO','NORMAL']);
const levelShown = k => levelShow[k] !== false;
function toggleLevel(k){ levelShow[k] = !levelShown(k); }
function toggleLevel(k){ levelShow[k] = !levelShown(k); try { sessionStorage.setItem(LV_STORE_KEY, JSON.stringify(levelShow)); } catch (e) {} }
const passLevel = lv => { const x = String(lv == null ? '' : lv); return !KNOWN_LV.has(x) || levelShow[x] !== false; };
const alertItemsF = computed(() => alertItems.value.filter(a => passLevel(a.level)));
const levelCount = k => alertItems.value.reduce((n, a) => n + (String(a.level) === k ? 1 : 0), 0);
@ -1864,6 +1868,8 @@ createApp({
const LV_LABEL = { DANGER:'危险', WARNING:'警告', IMPORTANT:'重要', INFO:'提示', NORMAL:'普通' };
const lvLabel = v => LV_LABEL[v] || v || '—';
const lvClass = v => v === 'DANGER' ? 'deny' : ((v === 'WARNING' || v === 'IMPORTANT') ? 'queue' : 'wait');
const flowText = d => d === 'inflow' ? '流入' : (d === 'outflow' ? '流出' : (d || '—'));
const flowClass = d => d === 'inflow' ? 'clr-up' : (d === 'outflow' ? 'clr-down' : 'muted');
// 告警固定分类格子: 6 类常驻 + 其他(catch-all), 永远这几个框、空的显示暂无 —— 不再随数据冒出冒消。
const ALERT_CATS_FIXED = [
{ key:'money_flow', label:'资金流强度' }, { key:'capital', label:'资金分布' }, { key:'qrs', label:'日QRS对称' },
@ -2188,7 +2194,7 @@ createApp({
upstream, showUpstream, usrc, loadUpstream,
showAlerts, alertItems, alertLatest, alertLevelClass, alertGroups, alertStripItems,
alertStripCount, alertTotalCount, catShown, toggleCat, ageText, alertRowClass,
alertItemsF, levelShown, toggleLevel, levelCount, ALERT_LEVELS, lvLabel, lvClass, alertGridFixed, passLevel };
alertItemsF, levelShown, toggleLevel, levelCount, ALERT_LEVELS, lvLabel, lvClass, alertGridFixed, passLevel, flowText, flowClass };
}
}).use(ElementPlus).mount('#app');
</script>